Also adds the policy id to the blueprint, this doesn't have any effect on the openscap step, it just puts in place the rhsm fact so instances registered to insights will appear under that policy.
403 lines
12 KiB
Go
403 lines
12 KiB
Go
package blueprint
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/osbuild/images/pkg/disk"
|
|
)
|
|
|
|
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"`
|
|
PartitioningMode string `json:"partitioning_mode,omitempty" toml:"partitioning_mode,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"`
|
|
Repositories []RepositoryCustomization `json:"repositories,omitempty" toml:"repositories,omitempty"`
|
|
FIPS *bool `json:"fips,omitempty" toml:"fips,omitempty"`
|
|
Installer *InstallerCustomization `json:"installer,omitempty" toml:"installer,omitempty"`
|
|
RPM *RPMCustomization `json:"rpm,omitempty" toml:"rpm,omitempty"`
|
|
RHSM *RHSMCustomization `json:"rhsm,omitempty" toml:"rhsm,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"`
|
|
DiMfgStringTypeMacIface string `json:"di_mfg_string_type_mac_iface,omitempty" toml:"di_mfg_string_type_mac_iface,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"`
|
|
ExpireDate *int `json:"expiredate,omitempty" toml:"expiredate,omitempty"`
|
|
ForcePasswordReset *bool `json:"force_password_reset,omitempty" toml:"force_password_reset,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"`
|
|
Masked []string `json:"masked,omitempty" toml:"masked,omitempty"`
|
|
}
|
|
|
|
type OpenSCAPCustomization struct {
|
|
DataStream string `json:"datastream,omitempty" toml:"datastream,omitempty"`
|
|
ProfileID string `json:"profile_id,omitempty" toml:"profile_id,omitempty"`
|
|
Tailoring *OpenSCAPTailoringCustomizations `json:"tailoring,omitempty" toml:"tailoring,omitempty"`
|
|
JSONTailoring *OpenSCAPJSONTailoringCustomizations `json:"json_tailoring,omitempty" toml:"json_tailoring,omitempty"`
|
|
PolicyID string `json:"policy_id,omitempty" toml:"policy_id,omitempty"`
|
|
}
|
|
|
|
type OpenSCAPTailoringCustomizations struct {
|
|
Selected []string `json:"selected,omitempty" toml:"selected,omitempty"`
|
|
Unselected []string `json:"unselected,omitempty" toml:"unselected,omitempty"`
|
|
}
|
|
|
|
type OpenSCAPJSONTailoringCustomizations struct {
|
|
ProfileID string `json:"profile_id,omitempty" toml:"profile_id,omitempty"`
|
|
Filepath string `json:"filepath,omitempty" toml:"filepath,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 _, k := range c.SSHKey {
|
|
key := k.Key
|
|
users = append(users, UserCustomization{
|
|
Name: k.User,
|
|
Key: &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
|
|
}
|
|
|
|
// GetPartitioningMode converts the string to a disk.PartitioningMode type
|
|
func (c *Customizations) GetPartitioningMode() (disk.PartitioningMode, error) {
|
|
if c == nil {
|
|
return disk.DefaultPartitioningMode, nil
|
|
}
|
|
|
|
switch c.PartitioningMode {
|
|
case "raw":
|
|
return disk.RawPartitioningMode, nil
|
|
case "lvm":
|
|
return disk.LVMPartitioningMode, nil
|
|
case "auto-lvm":
|
|
return disk.AutoLVMPartitioningMode, nil
|
|
case "":
|
|
return disk.DefaultPartitioningMode, nil
|
|
default:
|
|
return disk.DefaultPartitioningMode, fmt.Errorf("invalid partitioning mode '%s'", c.PartitioningMode)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (c *Customizations) GetRepositories() ([]RepositoryCustomization, error) {
|
|
if c == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
for idx := range c.Repositories {
|
|
err := validateCustomRepository(&c.Repositories[idx])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return c.Repositories, nil
|
|
}
|
|
|
|
func (c *Customizations) GetFIPS() bool {
|
|
if c == nil || c.FIPS == nil {
|
|
return false
|
|
}
|
|
return *c.FIPS
|
|
}
|