We had this weird condition in code that prevented composer to create groups with the same name as a user has. This unfortunately means that you are not able to create a user with a primary group with a certain GID that has the same name as the user. There's the gid field in the user customization, but it requires that the group already exists. In order to allow that, we need to remove the condition. From now on, it's possible to create groups with the same name as a user has, which can be used to create primary groups with a custom gid. Note that the lorax compatibility behaviour was actually wrong. When lorax was given a custom gid for a user, it didn't require the gid to exist. When it didn't, the group was just created. Thus, we still don't have full backward compatibility, but at least we now have support for this. Signed-off-by: Ondřej Budai <ondrej@budai.cz>
335 lines
9.1 KiB
Go
335 lines
9.1 KiB
Go
package blueprint
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
type Customizations struct {
|
|
Hostname *string `json:"hostname,omitempty" toml:"hostname,omitempty"`
|
|
Kernel *KernelCustomization `json:"kernel,omitempty" toml:"kernel,omitempty"`
|
|
SSHKey []SSHKeyCustomization `json:"sshkey,omitempty" toml:"sshkey,omitempty"`
|
|
User []UserCustomization `json:"user,omitempty" toml:"user,omitempty"`
|
|
Group []GroupCustomization `json:"group,omitempty" toml:"group,omitempty"`
|
|
Timezone *TimezoneCustomization `json:"timezone,omitempty" toml:"timezone,omitempty"`
|
|
Locale *LocaleCustomization `json:"locale,omitempty" toml:"locale,omitempty"`
|
|
Firewall *FirewallCustomization `json:"firewall,omitempty" toml:"firewall,omitempty"`
|
|
Services *ServicesCustomization `json:"services,omitempty" toml:"services,omitempty"`
|
|
Filesystem []FilesystemCustomization `json:"filesystem,omitempty" toml:"filesystem,omitempty"`
|
|
InstallationDevice string `json:"installation_device,omitempty" toml:"installation_device,omitempty"`
|
|
FDO *FDOCustomization `json:"fdo,omitempty" toml:"fdo,omitempty"`
|
|
OpenSCAP *OpenSCAPCustomization `json:"openscap,omitempty" toml:"openscap,omitempty"`
|
|
Ignition *IgnitionCustomization `json:"ignition,omitempty" toml:"ignition,omitempty"`
|
|
Directories []DirectoryCustomization `json:"directories,omitempty" toml:"directories,omitempty"`
|
|
Files []FileCustomization `json:"files,omitempty" toml:"files,omitempty"`
|
|
}
|
|
|
|
type IgnitionCustomization struct {
|
|
Embedded *EmbeddedIgnitionCustomization `json:"embedded,omitempty" toml:"embedded,omitempty"`
|
|
FirstBoot *FirstBootIgnitionCustomization `json:"firstboot,omitempty" toml:"firstboot,omitempty"`
|
|
}
|
|
|
|
type EmbeddedIgnitionCustomization struct {
|
|
Config string `json:"config,omitempty" toml:"config,omitempty"`
|
|
}
|
|
|
|
type FirstBootIgnitionCustomization struct {
|
|
ProvisioningURL string `json:"url,omitempty" toml:"url,omitempty"`
|
|
}
|
|
|
|
type FDOCustomization struct {
|
|
ManufacturingServerURL string `json:"manufacturing_server_url,omitempty" toml:"manufacturing_server_url,omitempty"`
|
|
DiunPubKeyInsecure string `json:"diun_pub_key_insecure,omitempty" toml:"diun_pub_key_insecure,omitempty"`
|
|
// This is the output of:
|
|
// echo "sha256:$(openssl x509 -fingerprint -sha256 -noout -in diun_cert.pem | cut -d"=" -f2 | sed 's/://g')"
|
|
DiunPubKeyHash string `json:"diun_pub_key_hash,omitempty" toml:"diun_pub_key_hash,omitempty"`
|
|
DiunPubKeyRootCerts string `json:"diun_pub_key_root_certs,omitempty" toml:"diun_pub_key_root_certs,omitempty"`
|
|
}
|
|
|
|
type KernelCustomization struct {
|
|
Name string `json:"name,omitempty" toml:"name,omitempty"`
|
|
Append string `json:"append" toml:"append"`
|
|
}
|
|
|
|
type SSHKeyCustomization struct {
|
|
User string `json:"user" toml:"user"`
|
|
Key string `json:"key" toml:"key"`
|
|
}
|
|
|
|
type UserCustomization struct {
|
|
Name string `json:"name" toml:"name"`
|
|
Description *string `json:"description,omitempty" toml:"description,omitempty"`
|
|
Password *string `json:"password,omitempty" toml:"password,omitempty"`
|
|
Key *string `json:"key,omitempty" toml:"key,omitempty"`
|
|
Home *string `json:"home,omitempty" toml:"home,omitempty"`
|
|
Shell *string `json:"shell,omitempty" toml:"shell,omitempty"`
|
|
Groups []string `json:"groups,omitempty" toml:"groups,omitempty"`
|
|
UID *int `json:"uid,omitempty" toml:"uid,omitempty"`
|
|
GID *int `json:"gid,omitempty" toml:"gid,omitempty"`
|
|
}
|
|
|
|
type GroupCustomization struct {
|
|
Name string `json:"name" toml:"name"`
|
|
GID *int `json:"gid,omitempty" toml:"gid,omitempty"`
|
|
}
|
|
|
|
type TimezoneCustomization struct {
|
|
Timezone *string `json:"timezone,omitempty" toml:"timezone,omitempty"`
|
|
NTPServers []string `json:"ntpservers,omitempty" toml:"ntpservers,omitempty"`
|
|
}
|
|
|
|
type LocaleCustomization struct {
|
|
Languages []string `json:"languages,omitempty" toml:"languages,omitempty"`
|
|
Keyboard *string `json:"keyboard,omitempty" toml:"keyboard,omitempty"`
|
|
}
|
|
|
|
type FirewallCustomization struct {
|
|
Ports []string `json:"ports,omitempty" toml:"ports,omitempty"`
|
|
Services *FirewallServicesCustomization `json:"services,omitempty" toml:"services,omitempty"`
|
|
Zones []FirewallZoneCustomization `json:"zones,omitempty" toml:"zones,omitempty"`
|
|
}
|
|
|
|
type FirewallZoneCustomization struct {
|
|
Name *string `json:"name,omitempty" toml:"name,omitempty"`
|
|
Sources []string `json:"sources,omitempty" toml:"sources,omitempty"`
|
|
}
|
|
|
|
type FirewallServicesCustomization struct {
|
|
Enabled []string `json:"enabled,omitempty" toml:"enabled,omitempty"`
|
|
Disabled []string `json:"disabled,omitempty" toml:"disabled,omitempty"`
|
|
}
|
|
|
|
type ServicesCustomization struct {
|
|
Enabled []string `json:"enabled,omitempty" toml:"enabled,omitempty"`
|
|
Disabled []string `json:"disabled,omitempty" toml:"disabled,omitempty"`
|
|
}
|
|
|
|
type OpenSCAPCustomization struct {
|
|
DataStream string `json:"datastream,omitempty" toml:"datastream,omitempty"`
|
|
ProfileID string `json:"profile_id,omitempty" toml:"profile_id,omitempty"`
|
|
}
|
|
|
|
type CustomizationError struct {
|
|
Message string
|
|
}
|
|
|
|
func (e *CustomizationError) Error() string {
|
|
return e.Message
|
|
}
|
|
|
|
// CheckCustomizations returns an error of type `CustomizationError`
|
|
// if `c` has any customizations not specified in `allowed`
|
|
func (c *Customizations) CheckAllowed(allowed ...string) error {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
allowMap := make(map[string]bool)
|
|
|
|
for _, a := range allowed {
|
|
allowMap[a] = true
|
|
}
|
|
|
|
t := reflect.TypeOf(*c)
|
|
v := reflect.ValueOf(*c)
|
|
|
|
for i := 0; i < t.NumField(); i++ {
|
|
|
|
empty := false
|
|
field := v.Field(i)
|
|
|
|
switch field.Kind() {
|
|
case reflect.String:
|
|
if field.String() == "" {
|
|
empty = true
|
|
}
|
|
case reflect.Array, reflect.Slice:
|
|
if field.Len() == 0 {
|
|
empty = true
|
|
}
|
|
case reflect.Ptr:
|
|
if field.IsNil() {
|
|
empty = true
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("unhandled customization field type %s, %s", v.Kind(), t.Field(i).Name))
|
|
|
|
}
|
|
|
|
if !empty && !allowMap[t.Field(i).Name] {
|
|
return &CustomizationError{fmt.Sprintf("'%s' is not allowed", t.Field(i).Name)}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Customizations) GetHostname() *string {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.Hostname
|
|
}
|
|
|
|
func (c *Customizations) GetPrimaryLocale() (*string, *string) {
|
|
if c == nil {
|
|
return nil, nil
|
|
}
|
|
if c.Locale == nil {
|
|
return nil, nil
|
|
}
|
|
if len(c.Locale.Languages) == 0 {
|
|
return nil, c.Locale.Keyboard
|
|
}
|
|
return &c.Locale.Languages[0], c.Locale.Keyboard
|
|
}
|
|
|
|
func (c *Customizations) GetTimezoneSettings() (*string, []string) {
|
|
if c == nil {
|
|
return nil, nil
|
|
}
|
|
if c.Timezone == nil {
|
|
return nil, nil
|
|
}
|
|
return c.Timezone.Timezone, c.Timezone.NTPServers
|
|
}
|
|
|
|
func (c *Customizations) GetUsers() []UserCustomization {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
users := []UserCustomization{}
|
|
|
|
// prepend sshkey for backwards compat (overridden by users)
|
|
if len(c.SSHKey) > 0 {
|
|
for _, c := range c.SSHKey {
|
|
users = append(users, UserCustomization{
|
|
Name: c.User,
|
|
Key: &c.Key,
|
|
})
|
|
}
|
|
}
|
|
|
|
users = append(users, c.User...)
|
|
|
|
// sanitize user home directory in blueprint: if it has a trailing slash,
|
|
// it might lead to the directory not getting the correct selinux labels
|
|
for idx := range users {
|
|
u := users[idx]
|
|
if u.Home != nil {
|
|
homedir := strings.TrimRight(*u.Home, "/")
|
|
u.Home = &homedir
|
|
users[idx] = u
|
|
}
|
|
}
|
|
return users
|
|
}
|
|
|
|
func (c *Customizations) GetGroups() []GroupCustomization {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
return c.Group
|
|
}
|
|
|
|
func (c *Customizations) GetKernel() *KernelCustomization {
|
|
var name string
|
|
var append string
|
|
if c != nil && c.Kernel != nil {
|
|
name = c.Kernel.Name
|
|
append = c.Kernel.Append
|
|
}
|
|
|
|
if name == "" {
|
|
name = "kernel"
|
|
}
|
|
|
|
return &KernelCustomization{
|
|
Name: name,
|
|
Append: append,
|
|
}
|
|
}
|
|
|
|
func (c *Customizations) GetFirewall() *FirewallCustomization {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
return c.Firewall
|
|
}
|
|
|
|
func (c *Customizations) GetServices() *ServicesCustomization {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
return c.Services
|
|
}
|
|
|
|
func (c *Customizations) GetFilesystems() []FilesystemCustomization {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.Filesystem
|
|
}
|
|
|
|
func (c *Customizations) GetFilesystemsMinSize() uint64 {
|
|
if c == nil {
|
|
return 0
|
|
}
|
|
var agg uint64
|
|
for _, m := range c.Filesystem {
|
|
agg += m.MinSize
|
|
}
|
|
// This ensures that file system customization `size` is a multiple of
|
|
// sector size (512)
|
|
if agg%512 != 0 {
|
|
agg = (agg/512 + 1) * 512
|
|
}
|
|
return agg
|
|
}
|
|
|
|
func (c *Customizations) GetInstallationDevice() string {
|
|
if c == nil || c.InstallationDevice == "" {
|
|
return ""
|
|
}
|
|
return c.InstallationDevice
|
|
}
|
|
|
|
func (c *Customizations) GetFDO() *FDOCustomization {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.FDO
|
|
}
|
|
|
|
func (c *Customizations) GetOpenSCAP() *OpenSCAPCustomization {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.OpenSCAP
|
|
}
|
|
|
|
func (c *Customizations) GetIgnition() *IgnitionCustomization {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.Ignition
|
|
}
|
|
|
|
func (c *Customizations) GetDirectories() []DirectoryCustomization {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.Directories
|
|
}
|
|
|
|
func (c *Customizations) GetFiles() []FileCustomization {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.Files
|
|
}
|