diff --git a/go.mod b/go.mod index dc09979cfb..24d273600a 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/labstack/gommon v0.4.2 github.com/openshift-online/ocm-sdk-go v0.1.438 github.com/oracle/oci-go-sdk/v54 v54.0.0 - github.com/osbuild/images v0.82.0 + github.com/osbuild/images v0.83.0 github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d github.com/osbuild/pulp-client v0.1.0 github.com/prometheus/client_golang v1.20.2 diff --git a/go.sum b/go.sum index 0ce331e9f3..dba2c8b0e6 100644 --- a/go.sum +++ b/go.sum @@ -510,8 +510,8 @@ github.com/openshift-online/ocm-sdk-go v0.1.438 h1:tsLCCUzbLCTL4RZG02y9RuopmGCXp github.com/openshift-online/ocm-sdk-go v0.1.438/go.mod h1:CiAu2jwl3ITKOxkeV0Qnhzv4gs35AmpIzVABQLtcI2Y= github.com/oracle/oci-go-sdk/v54 v54.0.0 h1:CDLjeSejv2aDpElAJrhKpi6zvT/zhZCZuXchUUZ+LS4= github.com/oracle/oci-go-sdk/v54 v54.0.0/go.mod h1:+t+yvcFGVp+3ZnztnyxqXfQDsMlq8U25faBLa+mqCMc= -github.com/osbuild/images v0.82.0 h1:bWfcGHHQR6pYZnv4jAxmLWxEkw669Zb6C2ADcyuf49g= -github.com/osbuild/images v0.82.0/go.mod h1:1kJyvTtEbJfRv00phwd9Dlkai4/V05JhNACglxFTxS8= +github.com/osbuild/images v0.83.0 h1:TFz9/nlueUK0dI3HpRCeUuT+2CeNTnejR/vGlzel1lE= +github.com/osbuild/images v0.83.0/go.mod h1:1kJyvTtEbJfRv00phwd9Dlkai4/V05JhNACglxFTxS8= github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d h1:r9BFPDv0uuA9k1947Jybcxs36c/pTywWS1gjeizvtcQ= github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d/go.mod h1:zR1iu/hOuf+OQNJlk70tju9IqzzM4ycq0ectkFBm94U= github.com/osbuild/pulp-client v0.1.0 h1:L0C4ezBJGTamN3BKdv+rKLuq/WxXJbsFwz/Hj7aEmJ8= diff --git a/internal/blueprint/customizations.go b/internal/blueprint/customizations.go index 75638ba5d5..09ce6684ba 100644 --- a/internal/blueprint/customizations.go +++ b/internal/blueprint/customizations.go @@ -122,6 +122,7 @@ type OpenSCAPCustomization struct { 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 { diff --git a/internal/cloudapi/v2/compose.go b/internal/cloudapi/v2/compose.go index 2ff88c1805..5d270f6af1 100644 --- a/internal/cloudapi/v2/compose.go +++ b/internal/cloudapi/v2/compose.go @@ -8,6 +8,8 @@ import ( "math/big" "reflect" + "github.com/google/uuid" + "github.com/osbuild/images/pkg/customizations/subscription" "github.com/osbuild/images/pkg/disk" "github.com/osbuild/images/pkg/distrofactory" @@ -231,6 +233,11 @@ func (request *ComposeRequest) GetCustomizationsFromBlueprintRequest() (*bluepri oscap := &blueprint.OpenSCAPCustomization{ ProfileID: rbpc.Openscap.ProfileId, } + + if rbpc.Openscap.PolicyId != nil { + oscap.PolicyID = *rbpc.Openscap.PolicyId + } + if rbpc.Openscap.Datastream != nil { oscap.DataStream = *rbpc.Openscap.Datastream } @@ -739,6 +746,11 @@ func (request *ComposeRequest) GetBlueprintFromCustomizations() (blueprint.Bluep openSCAPCustomization := &blueprint.OpenSCAPCustomization{ ProfileID: request.Customizations.Openscap.ProfileId, } + + if request.Customizations.Openscap.PolicyId != nil { + openSCAPCustomization.PolicyID = *request.Customizations.Openscap.PolicyId + } + if request.Customizations.Openscap.Tailoring != nil && request.Customizations.Openscap.JsonTailoring != nil { return bp, fmt.Errorf("OpenSCAP customization error: choose one option between OpenSCAP tailoring and OpenSCAP json tailoring") } @@ -1163,6 +1175,19 @@ func (request *ComposeRequest) GetImageRequests(distroFactory *distrofactory.Fac imageOptions.Facts = &facts.ImageOptions{ APIType: facts.CLOUDV2_APITYPE, } + oscap := bp.Customizations.GetOpenSCAP() + if oscap != nil { + if oscap.ProfileID != "" { + imageOptions.Facts.OpenSCAPProfileID = oscap.ProfileID + } + if oscap.PolicyID != "" { + policyID, err := uuid.Parse(oscap.PolicyID) + if err != nil { + return nil, fmt.Errorf("Unable to parse %s as a uuid: %w", oscap.PolicyID, err) + } + imageOptions.Facts.CompliancePolicyID = policyID + } + } } // Set Subscription from the compose request diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go index 9bc16727e3..9dab740425 100644 --- a/internal/cloudapi/v2/openapi.v2.gen.go +++ b/internal/cloudapi/v2/openapi.v2.gen.go @@ -348,8 +348,12 @@ type BlueprintFirewall struct { type BlueprintOpenSCAP struct { Datastream *string `json:"datastream,omitempty"` JsonTailoring *OpenSCAPJSONTailoring `json:"json_tailoring,omitempty"` - ProfileId string `json:"profile_id"` - Tailoring *OpenSCAPTailoring `json:"tailoring,omitempty"` + + // Puts a specified policy ID in the RHSM facts, so that any instances registered to + // insights will be automatically connected to the compliance policy in the console. + PolicyId *string `json:"policy_id,omitempty"` + ProfileId string `json:"profile_id"` + Tailoring *OpenSCAPTailoring `json:"tailoring,omitempty"` } // BlueprintRepository defines model for BlueprintRepository. @@ -921,8 +925,12 @@ type ObjectReference struct { // OpenSCAP defines model for OpenSCAP. type OpenSCAP struct { JsonTailoring *OpenSCAPJSONTailoring `json:"json_tailoring,omitempty"` - ProfileId string `json:"profile_id"` - Tailoring *OpenSCAPTailoring `json:"tailoring,omitempty"` + + // Puts a specified policy ID in the RHSM facts, so that any instances registered to + // insights will be automatically connected to the compliance policy in the console. + PolicyId *string `json:"policy_id,omitempty"` + ProfileId string `json:"profile_id"` + Tailoring *OpenSCAPTailoring `json:"tailoring,omitempty"` } // OpenSCAPJSONTailoring defines model for OpenSCAPJSONTailoring. @@ -1415,182 +1423,183 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "//9Cpq87zb8AAA==", + "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", + "IoqHWQfn89f/HwAA//80eIXXl8EAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml index 9999a0c369..993384e80f 100644 --- a/internal/cloudapi/v2/openapi.v2.yml +++ b/internal/cloudapi/v2/openapi.v2.yml @@ -975,6 +975,12 @@ components: required: - profile_id properties: + policy_id: + type: string + format: uuid + description: | + Puts a specified policy ID in the RHSM facts, so that any instances registered to + insights will be automatically connected to the compliance policy in the console. profile_id: type: string tailoring: @@ -986,6 +992,12 @@ components: required: - profile_id properties: + policy_id: + type: string + format: uuid + description: | + Puts a specified policy ID in the RHSM facts, so that any instances registered to + insights will be automatically connected to the compliance policy in the console. profile_id: type: string datastream: diff --git a/test/cases/api.sh b/test/cases/api.sh index b504b64726..5b1d6287ac 100755 --- a/test/cases/api.sh +++ b/test/cases/api.sh @@ -409,6 +409,7 @@ case "${IMAGE_TYPE}" in , "openscap": { "profile_id": "pci-dss", + "policy_id": "1af6cced-581c-452c-89cd-33b7bddb816a", "tailoring": { "unselected": [ "rpm_verify_permissions" ] } diff --git a/test/cases/api/common/common.sh b/test/cases/api/common/common.sh index 5faf25bb0f..5b9665cda3 100644 --- a/test/cases/api/common/common.sh +++ b/test/cases/api/common/common.sh @@ -40,11 +40,24 @@ function _instanceCheck() { FACTS=$($_ssh sudo subscription-manager facts) if ! grep -q "image-builder.osbuild-composer.api-type: cloudapi-v2" <<< "$FACTS"; then - echo "System doesn't contain the expected osbuild facts" + echo "System doesn't contain the expected image-builder.osbuild-composer facts" echo "$FACTS" | grep image-builder exit 1 fi + if [ -n "$OPENSCAP_CUSTOMIZATION_BLOCK" ]; then + if ! grep -q "image-builder.insights.openscap-profile-id: pci-dss" <<< "$FACTS"; then + echo "System doesn't contain the expected image-builder.insights facts (profile-id)" + echo "$FACTS"| grep image-builder + exit 1 + fi + if ! grep -q "image-builder.insights.compliance-policy-id: 1af6cced-581c-452c-89cd-33b7bddb816a" <<< "$FACTS"; then + echo "System doesn't contain the expected image-builder.insights facts (policy-id)" + echo "$FACTS"| grep image-builder + exit 1 + fi + fi + # Unregister subscription $_ssh sudo subscription-manager unregister else diff --git a/vendor/github.com/osbuild/images/pkg/distro/rhel/images.go b/vendor/github.com/osbuild/images/pkg/distro/rhel/images.go index 884ff24cdb..7a20febcc3 100644 --- a/vendor/github.com/osbuild/images/pkg/distro/rhel/images.go +++ b/vendor/github.com/osbuild/images/pkg/distro/rhel/images.go @@ -145,7 +145,7 @@ func osCustomizations( } if t.IsRHEL() && options.Facts != nil { - osc.FactAPIType = &options.Facts.APIType + osc.RHSMFacts = options.Facts } var err error @@ -467,6 +467,7 @@ func EdgeInstallerImage(workload workload.Workload, img.Platform = t.platform img.ExtraBasePackages = packageSets[InstallerPkgsKey] + img.Subscription = options.Subscription if t.Arch().Distro().Releasever() == "8" { // NOTE: RHEL 8 only supports the older Anaconda configs diff --git a/vendor/github.com/osbuild/images/pkg/distro/rhel/rhel10/ami.go b/vendor/github.com/osbuild/images/pkg/distro/rhel/rhel10/ami.go index 830cd4d308..22026d27e3 100644 --- a/vendor/github.com/osbuild/images/pkg/distro/rhel/rhel10/ami.go +++ b/vendor/github.com/osbuild/images/pkg/distro/rhel/rhel10/ami.go @@ -96,15 +96,6 @@ func baseEc2ImageConfig() *distro.ImageConfig { }, }, }, - // COMPOSER-1807 - DracutConf: []*osbuild.DracutConfStageOptions{ - { - Filename: "sgdisk.conf", - Config: osbuild.DracutConfigFile{ - Install: []string{"sgdisk"}, - }, - }, - }, SystemdUnit: []*osbuild.SystemdUnitStageOptions{ // RHBZ#1822863 { diff --git a/vendor/github.com/osbuild/images/pkg/image/anaconda_live_installer.go b/vendor/github.com/osbuild/images/pkg/image/anaconda_live_installer.go index 9455fac42a..b1acc99b58 100644 --- a/vendor/github.com/osbuild/images/pkg/image/anaconda_live_installer.go +++ b/vendor/github.com/osbuild/images/pkg/image/anaconda_live_installer.go @@ -65,6 +65,9 @@ func (img *AnacondaLiveInstaller) InstantiateManifest(m *manifest.Manifest, livePipeline.Variant = img.Variant livePipeline.Biosdevname = (img.Platform.GetArch() == arch.ARCH_X86_64) + // The live installer has SElinux enabled and targeted + livePipeline.SElinux = "targeted" + livePipeline.Checkpoint() rootfsImagePipeline := manifest.NewISORootfsImg(buildPipeline, livePipeline) diff --git a/vendor/github.com/osbuild/images/pkg/image/anaconda_ostree_installer.go b/vendor/github.com/osbuild/images/pkg/image/anaconda_ostree_installer.go index a88edaebe9..cba994e7e3 100644 --- a/vendor/github.com/osbuild/images/pkg/image/anaconda_ostree_installer.go +++ b/vendor/github.com/osbuild/images/pkg/image/anaconda_ostree_installer.go @@ -9,6 +9,7 @@ import ( "github.com/osbuild/images/pkg/artifact" "github.com/osbuild/images/pkg/customizations/anaconda" "github.com/osbuild/images/pkg/customizations/kickstart" + "github.com/osbuild/images/pkg/customizations/subscription" "github.com/osbuild/images/pkg/manifest" "github.com/osbuild/images/pkg/osbuild" "github.com/osbuild/images/pkg/ostree" @@ -24,6 +25,9 @@ type AnacondaOSTreeInstaller struct { Kickstart *kickstart.Options + // Subscription options to include + Subscription *subscription.ImageOptions + SquashfsCompression string ISOLabel string @@ -119,6 +123,12 @@ func (img *AnacondaOSTreeInstaller) InstantiateManifest(m *manifest.Manifest, // enable ISOLinux on x86_64 only isoLinuxEnabled := img.Platform.GetArch() == arch.ARCH_X86_64 + var subscriptionPipeline *manifest.Subscription + if img.Subscription != nil { + // pipeline that will create subscription service and key file to be copied out + subscriptionPipeline = manifest.NewSubscription(buildPipeline, img.Subscription) + } + isoTreePipeline := manifest.NewAnacondaInstallerISOTree(buildPipeline, anacondaPipeline, rootfsImagePipeline, bootTreePipeline) isoTreePipeline.PartitionTable = efiBootPartitionTable(rng) isoTreePipeline.Release = img.Release @@ -132,6 +142,7 @@ func (img *AnacondaOSTreeInstaller) InstantiateManifest(m *manifest.Manifest, if img.FIPS { isoTreePipeline.KernelOpts = append(isoTreePipeline.KernelOpts, "fips=1") } + isoTreePipeline.SubscriptionPipeline = subscriptionPipeline isoPipeline := manifest.NewISO(buildPipeline, isoTreePipeline, img.ISOLabel) isoPipeline.SetFilename(img.Filename) diff --git a/vendor/github.com/osbuild/images/pkg/manifest/anaconda_installer.go b/vendor/github.com/osbuild/images/pkg/manifest/anaconda_installer.go index 6a326280f4..bb82f5556d 100644 --- a/vendor/github.com/osbuild/images/pkg/manifest/anaconda_installer.go +++ b/vendor/github.com/osbuild/images/pkg/manifest/anaconda_installer.go @@ -84,6 +84,11 @@ type AnacondaInstaller struct { // Uses the old, deprecated, Anaconda config option "kickstart-modules". // Only for RHEL 8. UseLegacyAnacondaConfig bool + + // SELinux policy, when set it enables the labeling of the installer + // tree with the selected profile and selects the required package + // for depsolving + SElinux string } func NewAnacondaInstaller(installerType AnacondaInstallerType, @@ -159,14 +164,24 @@ func (p *AnacondaInstaller) getBuildPackages(Distro) []string { ) } + if p.SElinux != "" { + packages = append(packages, "policycoreutils", fmt.Sprintf("selinux-policy-%s", p.SElinux)) + } + return packages } func (p *AnacondaInstaller) getPackageSetChain(Distro) []rpmmd.PackageSet { packages := p.anacondaBootPackageSet() + if p.Biosdevname { packages = append(packages, "biosdevname") } + + if p.SElinux != "" { + packages = append(packages, fmt.Sprintf("selinux-policy-%s", p.SElinux)) + } + return []rpmmd.PackageSet{ { Include: append(packages, p.ExtraPackages...), @@ -306,6 +321,13 @@ func (p *AnacondaInstaller) payloadStages() []*osbuild.Stage { stages = append(stages, osbuild.NewSELinuxConfigStage(&osbuild.SELinuxConfigStageOptions{State: osbuild.SELinuxStatePermissive})) + // SElinux is not supported on the non-live-installers (see the previous + // stage setting SELinux to permissive. It's an error to set it to anything + // that isn't an empty string + if p.SElinux != "" { + panic("payload installers do not support SELinux policies") + } + if p.InteractiveDefaults != nil { var ksUsers []users.User var ksGroups []users.Group @@ -370,7 +392,11 @@ func (p *AnacondaInstaller) liveStages() []*osbuild.Stage { dracutOptions.AddDrivers = p.AdditionalDrivers stages = append(stages, osbuild.NewDracutStage(dracutOptions)) - stages = append(stages, osbuild.NewSELinuxConfigStage(&osbuild.SELinuxConfigStageOptions{State: osbuild.SELinuxStatePermissive})) + if p.SElinux != "" { + stages = append(stages, osbuild.NewSELinuxStage(&osbuild.SELinuxStageOptions{ + FileContexts: fmt.Sprintf("etc/selinux/%s/contexts/files/file_contexts", p.SElinux), + })) + } return stages } diff --git a/vendor/github.com/osbuild/images/pkg/manifest/anaconda_installer_iso_tree.go b/vendor/github.com/osbuild/images/pkg/manifest/anaconda_installer_iso_tree.go index 269d248f4b..3ebe27c551 100644 --- a/vendor/github.com/osbuild/images/pkg/manifest/anaconda_installer_iso_tree.go +++ b/vendor/github.com/osbuild/images/pkg/manifest/anaconda_installer_iso_tree.go @@ -3,6 +3,7 @@ package manifest import ( "fmt" "path" + "path/filepath" "sort" "strings" @@ -58,6 +59,10 @@ type AnacondaInstallerISOTree struct { Kickstart *kickstart.Options Files []*fsnode.File + + // Pipeline object where subscription-related files are created for copying + // onto the ISO. + SubscriptionPipeline *Subscription } func NewAnacondaInstallerISOTree(buildPipeline Build, anacondaPipeline *AnacondaInstaller, rootfsPipeline *ISORootfsImg, bootTreePipeline *EFIBootTree) *AnacondaInstallerISOTree { @@ -446,7 +451,7 @@ func (p *AnacondaInstallerISOTree) bootcInstallerKickstartStages() []*osbuild.St if err != nil { panic(err) } - p.Files = []*fsnode.File{kickstartFile} + p.Files = append(p.Files, kickstartFile) return append(stages, osbuild.GenFileNodesStages(p.Files)...) } @@ -516,8 +521,7 @@ bootc switch --mutate-in-place --transport %s %s panic(err) } - p.Files = []*fsnode.File{kickstartFile} - + p.Files = append(p.Files, kickstartFile) return append(stages, osbuild.GenFileNodesStages(p.Files)...) } @@ -570,9 +574,7 @@ func (p *AnacondaInstallerISOTree) makeKickstartStages(stageOptions *osbuild.Kic panic(err) } - p.Files = []*fsnode.File{kickstartFile} - - stages = append(stages, osbuild.GenFileNodesStages(p.Files)...) + p.Files = append(p.Files, kickstartFile) } } @@ -610,7 +612,44 @@ func (p *AnacondaInstallerISOTree) makeKickstartStages(stageOptions *osbuild.Kic stages = append(stages, osbuild.NewKickstartStage(stageOptions)) - hardcodedKickstartBits := makeKickstartSudoersPost(kickstartOptions.SudoNopasswd) + hardcodedKickstartBits := "" + hardcodedKickstartBits += makeKickstartSudoersPost(kickstartOptions.SudoNopasswd) + + if p.SubscriptionPipeline != nil { + subscriptionPath := "/subscription" + stages = append(stages, osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{Paths: []osbuild.MkdirStagePath{{Path: subscriptionPath, Parents: true, ExistOk: true}}})) + inputName := "subscription-tree" + copyInputs := osbuild.NewPipelineTreeInputs(inputName, p.SubscriptionPipeline.Name()) + copyOptions := &osbuild.CopyStageOptions{} + copyOptions.Paths = append(copyOptions.Paths, + osbuild.CopyStagePath{ + From: fmt.Sprintf("input://%s/", inputName), + To: fmt.Sprintf("tree://%s/", subscriptionPath), + }, + ) + stages = append(stages, osbuild.NewCopyStageSimple(copyOptions, copyInputs)) + systemPath := "/mnt/sysimage" + if p.ostreeCommitSpec != nil || p.containerSpec != nil { + // ostree based system: use /mnt/sysroot instead + systemPath = "/mnt/sysroot" + + } + hardcodedKickstartBits += makeKickstartSubscriptionPost(subscriptionPath, systemPath) + + // include a readme file on the ISO in the subscription path to explain what it's for + subscriptionReadme, err := fsnode.NewFile( + filepath.Join(subscriptionPath, "README"), + nil, nil, nil, + []byte(`Subscription services and credentials + +This directory contains files necessary for registering the system on first boot after installation. These files are copied to the installed system and services are enabled to activate the subscription on boot.`), + ) + if err != nil { + panic(err) + } + p.Files = append(p.Files, subscriptionReadme) + } + if hardcodedKickstartBits != "" { // Because osbuild core only supports a subset of options, // we append to the base here with hardcoded wheel group with NOPASSWD option @@ -619,10 +658,10 @@ func (p *AnacondaInstallerISOTree) makeKickstartStages(stageOptions *osbuild.Kic panic(err) } - p.Files = []*fsnode.File{kickstartFile} - - stages = append(stages, osbuild.GenFileNodesStages(p.Files)...) + p.Files = append(p.Files, kickstartFile) } + stages = append(stages, osbuild.GenFileNodesStages(p.Files)...) + return stages } @@ -660,3 +699,17 @@ restorecon -rvF /etc/sudoers.d return fmt.Sprintf(kickstartSudoersPost, strings.Join(entries, "\n")) } + +func makeKickstartSubscriptionPost(source, dest string) string { + // we need to use --nochroot so the command can access files on the ISO + fullSourcePath := filepath.Join("/run/install/repo", source, "etc/*") + kickstartSubscriptionPost := ` +%%post --nochroot +cp -r %s %s +%%end +%%post +systemctl enable osbuild-subscription-register.service +%%end +` + return fmt.Sprintf(kickstartSubscriptionPost, fullSourcePath, dest) +} diff --git a/vendor/github.com/osbuild/images/pkg/manifest/os.go b/vendor/github.com/osbuild/images/pkg/manifest/os.go index 7d6181bdfc..91911f84c1 100644 --- a/vendor/github.com/osbuild/images/pkg/manifest/os.go +++ b/vendor/github.com/osbuild/images/pkg/manifest/os.go @@ -5,6 +5,8 @@ import ( "path/filepath" "strings" + "github.com/google/uuid" + "github.com/osbuild/images/internal/common" "github.com/osbuild/images/internal/environment" "github.com/osbuild/images/internal/workload" @@ -124,7 +126,6 @@ type OSCustomizations struct { UdevRules *osbuild.UdevRulesStageOptions WSLConfig *osbuild.WSLConfStageOptions LeapSecTZ *string - FactAPIType *facts.APIType Presets []osbuild.Preset ContainersStorage *string @@ -134,6 +135,7 @@ type OSCustomizations struct { Subscription *subscription.ImageOptions // The final RHSM config to be applied to the image RHSMConfig *subscription.RHSMConfig + RHSMFacts *facts.ImageOptions // Custom directories and files to create in the image Directories []*fsnode.Directory @@ -424,6 +426,13 @@ func (p *OS) serialize() osbuild.Pipeline { if p.OSTreeRef != "" { rpmOptions.OSTreeBooted = common.ToPtr(true) rpmOptions.DBPath = "/usr/share/rpm" + // The dracut-config-rescue package will create a rescue kernel when + // installed. This creates an issue with ostree-based images because + // rpm-ostree requires that only one kernel exists in the image. + // Disabling dracut for ostree-based systems resolves this issue. + // Dracut will be run by rpm-ostree itself while composing the image. + // https://github.com/osbuild/images/issues/624 + rpmOptions.DisableDracut = true } pipeline.AddStage(osbuild.NewRPMStage(rpmOptions, osbuild.NewRpmStageSourceFilesInputs(p.packageSpecs))) @@ -580,74 +589,15 @@ func (p *OS) serialize() osbuild.Pipeline { pipeline.AddStage(osbuild.NewPwqualityConfStage(p.PwQuality)) } - // If subscription settings are included there are 3 possible setups: - // - Register the system with rhc and enable Insights - // - Register with subscription-manager, no Insights or rhc - // - Register with subscription-manager and enable Insights, no rhc if p.Subscription != nil { - // Write a key file that will contain the org ID and activation key to be sourced in the systemd service. - // The file will also act as the ConditionFirstBoot file. - subkeyFilepath := "/etc/osbuild-subscription-register.env" - subkeyContent := fmt.Sprintf("ORG_ID=%s\nACTIVATION_KEY=%s", p.Subscription.Organization, p.Subscription.ActivationKey) - if subkeyFile, err := fsnode.NewFile(subkeyFilepath, nil, "root", "root", []byte(subkeyContent)); err == nil { - p.Files = append(p.Files, subkeyFile) - } else { + subStage, subDirs, subFiles, subServices, err := subscriptionService(*p.Subscription, &subscriptionServiceOptions{InsightsOnBoot: p.OSTreeRef != ""}) + if err != nil { panic(err) } - - var commands []string - if p.Subscription.Rhc { - // TODO: replace org ID and activation key with env vars - // Use rhc for registration instead of subscription manager - commands = []string{fmt.Sprintf("/usr/bin/rhc connect --organization=${ORG_ID} --activation-key=${ACTIVATION_KEY} --server %s", p.Subscription.ServerUrl)} - // insights-client creates the .gnupg directory during boot process, and is labeled incorrectly - commands = append(commands, "restorecon -R /root/.gnupg") - // execute the rhc post install script as the selinuxenabled check doesn't work in the buildroot container - commands = append(commands, "/usr/sbin/semanage permissive --add rhcd_t") - if p.OSTreeRef != "" { - p.runInsightsClientOnBoot() - } - } else { - commands = []string{fmt.Sprintf("/usr/sbin/subscription-manager register --org=${ORG_ID} --activationkey=${ACTIVATION_KEY} --serverurl %s --baseurl %s", p.Subscription.ServerUrl, p.Subscription.BaseUrl)} - - // Insights is optional when using subscription-manager - if p.Subscription.Insights { - commands = append(commands, "/usr/bin/insights-client --register") - // insights-client creates the .gnupg directory during boot process, and is labeled incorrectly - commands = append(commands, "restorecon -R /root/.gnupg") - if p.OSTreeRef != "" { - p.runInsightsClientOnBoot() - } - } - } - - commands = append(commands, fmt.Sprintf("/usr/bin/rm %s", subkeyFilepath)) - - subscribeServiceFile := "osbuild-subscription-register.service" - regServiceStageOptions := &osbuild.SystemdUnitCreateStageOptions{ - Filename: subscribeServiceFile, - UnitType: "system", - UnitPath: osbuild.Usr, - Config: osbuild.SystemdServiceUnit{ - Unit: &osbuild.Unit{ - Description: "First-boot service for registering with Red Hat subscription manager and/or insights", - ConditionPathExists: []string{subkeyFilepath}, - Wants: []string{"network-online.target"}, - After: []string{"network-online.target"}, - }, - Service: &osbuild.Service{ - Type: osbuild.Oneshot, - RemainAfterExit: false, - ExecStart: commands, - EnvironmentFile: []string{subkeyFilepath}, - }, - Install: &osbuild.Install{ - WantedBy: []string{"default.target"}, - }, - }, - } - pipeline.AddStage(osbuild.NewSystemdUnitCreateStage(regServiceStageOptions)) - p.EnabledServices = append(p.EnabledServices, subscribeServiceFile) + pipeline.AddStage(subStage) + p.Directories = append(p.Directories, subDirs...) + p.Files = append(p.Files, subFiles...) + p.EnabledServices = append(p.EnabledServices, subServices...) } if p.RHSMConfig != nil { @@ -740,11 +690,21 @@ func (p *OS) serialize() osbuild.Pipeline { pipeline.AddStage(bootloader) } - if p.FactAPIType != nil { + if p.RHSMFacts != nil { + rhsmFacts := osbuild.RHSMFacts{ + ApiType: p.RHSMFacts.APIType.String(), + } + + if p.RHSMFacts.OpenSCAPProfileID != "" { + rhsmFacts.OpenSCAPProfileID = p.RHSMFacts.OpenSCAPProfileID + } + + if p.RHSMFacts.CompliancePolicyID != uuid.Nil { + rhsmFacts.CompliancePolicyID = p.RHSMFacts.CompliancePolicyID.String() + } + pipeline.AddStage(osbuild.NewRHSMFactsStage(&osbuild.RHSMFactsStageOptions{ - Facts: osbuild.RHSMFacts{ - ApiType: p.FactAPIType.String(), - }, + Facts: rhsmFacts, })) } @@ -919,43 +879,3 @@ func (p *OS) getInline() []string { return inlineData } - -// For ostree-based systems, creates a drop-in file for the insights-client -// service to run on boot and enables the service. This is only meant for -// ostree-based systems. -func (p *OS) runInsightsClientOnBoot() { - // Insights-client collection must occur at boot time so - // that the current ostree commit hash can be reflected - // after upgrade. Otherwise, the upgrade shows as failed in - // the console UI. - // Add a drop-in file that enables insights-client.service to - // run on successful boot. - // See https://issues.redhat.com/browse/HMS-4031 - // - // NOTE(akoutsou): drop-in files can normally be created with the - // org.osbuild.systemd.unit stage but the stage doesn't support - // all the options we need. This is a temporary workaround - // until we get the stage updated to support everything we need. - icDropinFilepath, icDropinContents := insightsClientDropin() - if icDropinDirectory, err := fsnode.NewDirectory(filepath.Dir(icDropinFilepath), nil, "root", "root", true); err == nil { - p.Directories = append(p.Directories, icDropinDirectory) - } - if icDropinFile, err := fsnode.NewFile(icDropinFilepath, nil, "root", "root", []byte(icDropinContents)); err == nil { - p.Files = append(p.Files, icDropinFile) - } else { - panic(err) - } - // Enable the service now that it's "enable-able" - p.EnabledServices = append(p.EnabledServices, "insights-client.service") -} - -// Filename and contents for the insights-client service drop-in. -// This is a temporary workaround until the org.osbuild.systemd.unit stage -// gains support for all the options we need. -func insightsClientDropin() (string, string) { - return "/etc/systemd/system/insights-client.service.d/override.conf", `[Unit] -Requisite=greenboot-healthcheck.service -After=network-online.target greenboot-healthcheck.service osbuild-first-boot.service -[Install] -WantedBy=multi-user.target` -} diff --git a/vendor/github.com/osbuild/images/pkg/manifest/ostree_deployment.go b/vendor/github.com/osbuild/images/pkg/manifest/ostree_deployment.go index 084610337d..a6886013eb 100644 --- a/vendor/github.com/osbuild/images/pkg/manifest/ostree_deployment.go +++ b/vendor/github.com/osbuild/images/pkg/manifest/ostree_deployment.go @@ -521,7 +521,7 @@ func createMountpointService(serviceName string, mountpoints []string) *osbuild. After: []string{"ostree-remount.service"}, } service := osbuild.Service{ - Type: osbuild.Oneshot, + Type: osbuild.OneshotServiceType, RemainAfterExit: false, // compatibility with composefs, will require transient rootfs to be enabled too. ExecStartPre: []string{"/bin/sh -c \"if grep -Uq composefs /run/ostree-booted; then echo 'Warning: composefs enabled! ensure transient rootfs is enabled too.'; else chattr -i /; fi\""}, @@ -547,7 +547,7 @@ func createMountpointService(serviceName string, mountpoints []string) *osbuild. } options := osbuild.SystemdUnitCreateStageOptions{ Filename: serviceName, - UnitPath: osbuild.Etc, + UnitPath: osbuild.EtcUnitPath, UnitType: osbuild.System, Config: osbuild.SystemdServiceUnit{ Unit: &unit, diff --git a/vendor/github.com/osbuild/images/pkg/manifest/subscription.go b/vendor/github.com/osbuild/images/pkg/manifest/subscription.go new file mode 100644 index 0000000000..1d37858b0a --- /dev/null +++ b/vendor/github.com/osbuild/images/pkg/manifest/subscription.go @@ -0,0 +1,223 @@ +package manifest + +import ( + "fmt" + "path/filepath" + + "github.com/osbuild/images/pkg/customizations/fsnode" + "github.com/osbuild/images/pkg/customizations/subscription" + "github.com/osbuild/images/pkg/osbuild" +) + +type Subscription struct { + Base + + Subscription *subscription.ImageOptions + + // Custom directories and files to create in the pipeline + Directories []*fsnode.Directory + Files []*fsnode.File +} + +// NewSubscription creates a new subscription pipeline for creating files +// required to register a system on first boot. +// The pipeline is intended to be used to create the files necessary for +// registering a system, but outside the OS tree, so they can be copied to +// other locations in the tree after they're created (for example, to an ISO). +func NewSubscription(buildPipeline Build, subOptions *subscription.ImageOptions) *Subscription { + name := "subscription" + p := &Subscription{ + Base: NewBase(name, buildPipeline), + Subscription: subOptions, + } + buildPipeline.addDependent(p) + return p +} + +func (p *Subscription) serialize() osbuild.Pipeline { + pipeline := p.Base.serialize() + if p.Subscription != nil { + serviceDir, err := fsnode.NewDirectory("/etc/systemd/system", nil, nil, nil, true) + if err != nil { + panic(err) + } + p.Directories = append(p.Directories, serviceDir) + + subStage, subDirs, subFiles, _, err := subscriptionService(*p.Subscription, &subscriptionServiceOptions{InsightsOnBoot: true, UnitPath: osbuild.EtcUnitPath}) + if err != nil { + panic(err) + } + p.Directories = append(p.Directories, subDirs...) + p.Files = append(p.Files, subFiles...) + + pipeline.AddStages(osbuild.GenDirectoryNodesStages(p.Directories)...) + pipeline.AddStages(osbuild.GenFileNodesStages(p.Files)...) + pipeline.AddStage(subStage) + } + return pipeline +} + +func (p *Subscription) getInline() []string { + inlineData := []string{} + + // inline data for custom files + for _, file := range p.Files { + inlineData = append(inlineData, string(file.Data())) + } + + return inlineData +} + +type subscriptionServiceOptions struct { + // InsightsOnBoot controls whether the insights client service will be + // modified (with a drop-in) to run on boot as well as on a timer. + InsightsOnBoot bool + + // UnitPath controls the path where the systemd unit will be created, + // /usr/lib/systemd or /etc/systemd. + UnitPath osbuild.SystemdUnitPath +} + +// subscriptionService creates the necessary stage and modifications to the +// pipeline for activating a system on first boot. +// +// If subscription settings are included there are 3 possible setups: +// - Register the system with rhc and enable Insights +// - Register with subscription-manager, no Insights or rhc +// - Register with subscription-manager and enable Insights, no rhc +func subscriptionService(subscriptionOptions subscription.ImageOptions, serviceOptions *subscriptionServiceOptions) (*osbuild.Stage, []*fsnode.Directory, []*fsnode.File, []string, error) { + dirs := make([]*fsnode.Directory, 0) + files := make([]*fsnode.File, 0) + services := make([]string, 0) + + insightsOnBoot := false + unitPath := osbuild.UsrUnitPath + if serviceOptions != nil { + insightsOnBoot = serviceOptions.InsightsOnBoot + if serviceOptions.UnitPath != "" { + unitPath = serviceOptions.UnitPath + } + } + + // Write a key file that will contain the org ID and activation key to be sourced in the systemd service. + // The file will also act as the ConditionFirstBoot file. + subkeyFilepath := "/etc/osbuild-subscription-register.env" + subkeyContent := fmt.Sprintf("ORG_ID=%s\nACTIVATION_KEY=%s", subscriptionOptions.Organization, subscriptionOptions.ActivationKey) + + // NOTE: Ownership is left as nil:nil, which implicitly creates files as + // root:root. Adding an explicit owner requires chroot to run the + // org.osbuild.chown stage, which we can't run in the subscription pipeline + // since it has no packages. + if subkeyFile, err := fsnode.NewFile(subkeyFilepath, nil, nil, nil, []byte(subkeyContent)); err == nil { + files = append(files, subkeyFile) + } else { + return nil, nil, nil, nil, err + } + + var commands []string + if subscriptionOptions.Rhc { + // Use rhc for registration instead of subscription manager + commands = []string{fmt.Sprintf("/usr/bin/rhc connect --organization=${ORG_ID} --activation-key=${ACTIVATION_KEY} --server %s", subscriptionOptions.ServerUrl)} + // insights-client creates the .gnupg directory during boot process, and is labeled incorrectly + commands = append(commands, "restorecon -R /root/.gnupg") + // execute the rhc post install script as the selinuxenabled check doesn't work in the buildroot container + commands = append(commands, "/usr/sbin/semanage permissive --add rhcd_t") + if insightsOnBoot { + icDir, icFile, err := runInsightsClientOnBoot() + if err != nil { + return nil, nil, nil, nil, err + } + dirs = append(dirs, icDir) + files = append(files, icFile) + } + } else { + commands = []string{fmt.Sprintf("/usr/sbin/subscription-manager register --org=${ORG_ID} --activationkey=${ACTIVATION_KEY} --serverurl %s --baseurl %s", subscriptionOptions.ServerUrl, subscriptionOptions.BaseUrl)} + + // Insights is optional when using subscription-manager + if subscriptionOptions.Insights { + commands = append(commands, "/usr/bin/insights-client --register") + // insights-client creates the .gnupg directory during boot process, and is labeled incorrectly + commands = append(commands, "restorecon -R /root/.gnupg") + if insightsOnBoot { + icDir, icFile, err := runInsightsClientOnBoot() + if err != nil { + return nil, nil, nil, nil, err + } + dirs = append(dirs, icDir) + files = append(files, icFile) + } + } + } + + commands = append(commands, fmt.Sprintf("/usr/bin/rm %s", subkeyFilepath)) + + subscribeServiceFile := "osbuild-subscription-register.service" + regServiceStageOptions := &osbuild.SystemdUnitCreateStageOptions{ + Filename: subscribeServiceFile, + UnitType: "system", + UnitPath: unitPath, + Config: osbuild.SystemdServiceUnit{ + Unit: &osbuild.Unit{ + Description: "First-boot service for registering with Red Hat subscription manager and/or insights", + ConditionPathExists: []string{subkeyFilepath}, + Wants: []string{"network-online.target"}, + After: []string{"network-online.target"}, + }, + Service: &osbuild.Service{ + Type: osbuild.OneshotServiceType, + RemainAfterExit: false, + ExecStart: commands, + EnvironmentFile: []string{subkeyFilepath}, + }, + Install: &osbuild.Install{ + WantedBy: []string{"default.target"}, + }, + }, + } + services = append(services, subscribeServiceFile) + unitStage := osbuild.NewSystemdUnitCreateStage(regServiceStageOptions) + return unitStage, dirs, files, services, nil +} + +// Creates a drop-in file for the insights-client service to run on boot and +// enables the service. This is only meant for ostree-based systems. +func runInsightsClientOnBoot() (*fsnode.Directory, *fsnode.File, error) { + // Insights-client collection must occur at boot time so + // that the current ostree commit hash can be reflected + // after upgrade. Otherwise, the upgrade shows as failed in + // the console UI. + // Add a drop-in file that enables insights-client.service to + // run on successful boot. + // See https://issues.redhat.com/browse/HMS-4031 + // + // NOTE(akoutsou): drop-in files can normally be created with the + // org.osbuild.systemd.unit stage but the stage doesn't support + // all the options we need. This is a temporary workaround + // until we get the stage updated to support everything we need. + icDropinFilepath, icDropinContents := insightsClientDropin() + + // NOTE: Ownership is left as nil:nil, which implicitly creates files as + // root:root. Adding an explicit owner requires chroot to run the + // org.osbuild.chown stage, which we can't run in the subscription pipeline + // since it has no packages. + icDropinDirectory, err := fsnode.NewDirectory(filepath.Dir(icDropinFilepath), nil, nil, nil, true) + if err != nil { + return nil, nil, err + } + icDropinFile, err := fsnode.NewFile(icDropinFilepath, nil, nil, nil, []byte(icDropinContents)) + if err != nil { + return nil, nil, err + } + return icDropinDirectory, icDropinFile, nil +} + +// Filename and contents for the insights-client service drop-in. +// This is a temporary workaround until the org.osbuild.systemd.unit stage +// gains support for all the options we need. +func insightsClientDropin() (string, string) { + return "/etc/systemd/system/insights-client.service.d/override.conf", `[Unit] +Requisite=greenboot-healthcheck.service +After=network-online.target greenboot-healthcheck.service osbuild-first-boot.service +[Install] +WantedBy=multi-user.target` +} diff --git a/vendor/github.com/osbuild/images/pkg/osbuild/rhsm_facts_stage.go b/vendor/github.com/osbuild/images/pkg/osbuild/rhsm_facts_stage.go index e4984338ec..de839fa80b 100644 --- a/vendor/github.com/osbuild/images/pkg/osbuild/rhsm_facts_stage.go +++ b/vendor/github.com/osbuild/images/pkg/osbuild/rhsm_facts_stage.go @@ -5,7 +5,9 @@ type RHSMFactsStageOptions struct { } type RHSMFacts struct { - ApiType string `json:"image-builder.osbuild-composer.api-type"` + ApiType string `json:"image-builder.osbuild-composer.api-type"` + OpenSCAPProfileID string `json:"image-builder.insights.openscap-profile-id,omitempty"` + CompliancePolicyID string `json:"image-builder.insights.compliance-policy-id,omitempty"` } func (RHSMFactsStageOptions) isStageOptions() {} diff --git a/vendor/github.com/osbuild/images/pkg/osbuild/systemd_unit_create_stage.go b/vendor/github.com/osbuild/images/pkg/osbuild/systemd_unit_create_stage.go index 8b0c39f31b..74263e5fb7 100644 --- a/vendor/github.com/osbuild/images/pkg/osbuild/systemd_unit_create_stage.go +++ b/vendor/github.com/osbuild/images/pkg/osbuild/systemd_unit_create_stage.go @@ -5,20 +5,21 @@ import ( "regexp" ) -type serviceType string -type unitPath string +type SystemdServiceType string +type SystemdUnitPath string const ( - Simple serviceType = "simple" - Exec serviceType = "exec" - Forking serviceType = "forking" - Oneshot serviceType = "oneshot" - Dbus serviceType = "dbus" - Notify serviceType = "notify" - NotifyReloadservice serviceType = "notify-reload" - Idle serviceType = "idle" - Etc unitPath = "etc" - Usr unitPath = "usr" + SimpleServiceType SystemdServiceType = "simple" + ExecServiceType SystemdServiceType = "exec" + ForkingServiceType SystemdServiceType = "forking" + OneshotServiceType SystemdServiceType = "oneshot" + DbusServiceType SystemdServiceType = "dbus" + NotifyServiceType SystemdServiceType = "notify" + NotifyReloadServiceType SystemdServiceType = "notify-reload" + IdleServiceType SystemdServiceType = "idle" + + EtcUnitPath SystemdUnitPath = "etc" + UsrUnitPath SystemdUnitPath = "usr" ) type Unit struct { @@ -33,7 +34,7 @@ type Unit struct { } type Service struct { - Type serviceType `json:"Type,omitempty"` + Type SystemdServiceType `json:"Type,omitempty"` RemainAfterExit bool `json:"RemainAfterExit,omitempty"` ExecStartPre []string `json:"ExecStartPre,omitempty"` ExecStopPost []string `json:"ExecStopPost,omitempty"` @@ -56,7 +57,7 @@ type SystemdServiceUnit struct { type SystemdUnitCreateStageOptions struct { Filename string `json:"filename"` UnitType unitType `json:"unit-type,omitempty"` // unitType defined in ./systemd_unit_stage.go - UnitPath unitPath `json:"unit-path,omitempty"` + UnitPath SystemdUnitPath `json:"unit-path,omitempty"` Config SystemdServiceUnit `json:"config"` } diff --git a/vendor/github.com/osbuild/images/pkg/rhsm/facts/facts.go b/vendor/github.com/osbuild/images/pkg/rhsm/facts/facts.go index af0a1db8bb..19786d83e2 100644 --- a/vendor/github.com/osbuild/images/pkg/rhsm/facts/facts.go +++ b/vendor/github.com/osbuild/images/pkg/rhsm/facts/facts.go @@ -1,6 +1,10 @@ package facts -import "fmt" +import ( + "fmt" + + "github.com/google/uuid" +) type APIType uint64 @@ -25,5 +29,7 @@ const ( // The ImageOptions specify things to be stored into the Insights facts // storage. This mostly relates to how the build of the image was performed. type ImageOptions struct { - APIType APIType + APIType APIType + OpenSCAPProfileID string + CompliancePolicyID uuid.UUID } diff --git a/vendor/modules.txt b/vendor/modules.txt index 63e30955db..9cbfca2ad7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -947,7 +947,7 @@ github.com/oracle/oci-go-sdk/v54/identity github.com/oracle/oci-go-sdk/v54/objectstorage github.com/oracle/oci-go-sdk/v54/objectstorage/transfer github.com/oracle/oci-go-sdk/v54/workrequests -# github.com/osbuild/images v0.82.0 +# github.com/osbuild/images v0.83.0 ## explicit; go 1.21.0 github.com/osbuild/images/internal/common github.com/osbuild/images/internal/environment