diff --git a/pkg/blueprint/ca_customizations.go b/pkg/blueprint/ca_customizations.go new file mode 100644 index 0000000000..b4083dfe4f --- /dev/null +++ b/pkg/blueprint/ca_customizations.go @@ -0,0 +1,5 @@ +package blueprint + +type CACustomization struct { + Certs []string `json:"certs,omitempty" toml:"certs,omitempty"` +} diff --git a/pkg/blueprint/customizations.go b/pkg/blueprint/customizations.go index d361626f53..28091e8268 100644 --- a/pkg/blueprint/customizations.go +++ b/pkg/blueprint/customizations.go @@ -32,6 +32,7 @@ type Customizations struct { Installer *InstallerCustomization `json:"installer,omitempty" toml:"installer,omitempty"` RPM *RPMCustomization `json:"rpm,omitempty" toml:"rpm,omitempty"` RHSM *RHSMCustomization `json:"rhsm,omitempty" toml:"rhsm,omitempty"` + CA *CACustomization `json:"ca,omitempty" toml:"ca,omitempty"` } type IgnitionCustomization struct { @@ -439,3 +440,10 @@ func (c *Customizations) GetRHSM() *RHSMCustomization { } return c.RHSM } + +func (c *Customizations) GetCA() *CACustomization { + if c == nil { + return nil + } + return c.CA +} diff --git a/pkg/distro/fedora/images.go b/pkg/distro/fedora/images.go index c40c42e943..27d900e17b 100644 --- a/pkg/distro/fedora/images.go +++ b/pkg/distro/fedora/images.go @@ -226,6 +226,10 @@ func osCustomizations( osc.Files = append(osc.Files, imageConfig.Files...) osc.Directories = append(osc.Directories, imageConfig.Directories...) + if ca := c.GetCA(); ca != nil { + osc.CACerts = ca.Certs + } + return osc, nil } diff --git a/pkg/distro/rhel/images.go b/pkg/distro/rhel/images.go index 884ff24cdb..0ac394db38 100644 --- a/pkg/distro/rhel/images.go +++ b/pkg/distro/rhel/images.go @@ -271,6 +271,10 @@ func osCustomizations( osc.NoBLS = *imageConfig.NoBLS } + if ca := c.GetCA(); ca != nil { + osc.CACerts = ca.Certs + } + return osc, nil } diff --git a/pkg/manifest/os.go b/pkg/manifest/os.go index d4b1569c4e..4f1ac0fd94 100644 --- a/pkg/manifest/os.go +++ b/pkg/manifest/os.go @@ -1,6 +1,8 @@ package manifest import ( + "crypto/x509" + "encoding/pem" "fmt" "path/filepath" "strings" @@ -22,6 +24,7 @@ import ( "github.com/osbuild/images/pkg/platform" "github.com/osbuild/images/pkg/rhsm/facts" "github.com/osbuild/images/pkg/rpmmd" + "github.com/sirupsen/logrus" ) // OSCustomizations encapsulates all configuration applied to the base @@ -139,6 +142,8 @@ type OSCustomizations struct { Directories []*fsnode.Directory Files []*fsnode.File + CACerts []string + FIPS bool // NoBLS configures the image bootloader with traditional menu entries @@ -863,9 +868,51 @@ func (p *OS) serialize() osbuild.Pipeline { } } + if len(p.CACerts) > 0 { + for _, cc := range p.CACerts { + for _, c := range parseCerts(cc) { + path := filepath.Join("/etc/pki/ca-trust/source/anchors", filepath.Base(c.SerialNumber.Text(16))) + f, err := fsnode.NewFile(path, nil, "root", "root", pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c.Raw})) + if err != nil { + panic(err) + } + pipeline.AddStages(osbuild.GenFileNodesStages([]*fsnode.File{f})...) + } + } + pipeline.AddStage(osbuild.NewCAStageStage()) + } + return pipeline } +func parseCerts(cert string) []*x509.Certificate { + result := make([]*x509.Certificate, 0, 1) + block := []byte(cert) + var blocks [][]byte + for { + var certDERBlock *pem.Block + certDERBlock, block = pem.Decode(block) + if certDERBlock == nil { + break + } + + if certDERBlock.Type == "CERTIFICATE" { + blocks = append(blocks, certDERBlock.Bytes) + } + } + + for _, block := range blocks { + cert, err := x509.ParseCertificate(block) + if err != nil { + logrus.Warnf("failed to parse certificate, skipping: %s", err) + continue + } + result = append(result, cert) + } + + return result +} + func prependKernelCmdlineStage(pipeline osbuild.Pipeline, kernelOptions string, pt *disk.PartitionTable) osbuild.Pipeline { rootFs := pt.FindMountable("/") if rootFs == nil { diff --git a/pkg/osbuild/cacert_stage.go b/pkg/osbuild/cacert_stage.go new file mode 100644 index 0000000000..7948292791 --- /dev/null +++ b/pkg/osbuild/cacert_stage.go @@ -0,0 +1,7 @@ +package osbuild + +func NewCAStageStage() *Stage { + return &Stage{ + Type: "org.osbuild.pki.update-ca-trust", + } +} diff --git a/test/configs/all-customizations.json b/test/configs/all-customizations.json index 67cbd0a40d..48783b3177 100644 --- a/test/configs/all-customizations.json +++ b/test/configs/all-customizations.json @@ -173,6 +173,9 @@ "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQGiBGRBSJURBACzCoe9UNfxOUiFLq9b60weSBFdr39mLViscecDWATNvXtgRoK/\nxl/4qpayzALRCQ2Ek/pMrbKPF/3ngECuBv7S+rI4n/rIia4FNcqzYeZAz4DE4NP/\neUGvz49tWhmH17hX/rmF9kz5kLq2bDZI4GDgZW/oMDdt2ivj092Ljm9jRwCgyQy3\nWEK6RJvIcSEh9vbdwVdMPOcD/iHqNejTMFwGyZfCWB0eIOoxUOUn/ZZpELTL2UpW\nGduCf3txb5SkK7M+WDbb0S5IvNXoi0tc13STiD6Oxg2O9PkSvvYb+8zxlhNoSTwy\n54j7Rf5FlnQ3TAFfjtQ5LCx56LKK73j4RjvKW//ktm5n54exsgo9Ry/e12T46dRg\n7tIlA/91rzLm57Qyc73A7zjgIzef9O6V5ZzowC+pp/jfb5pS9hXgROekLkMgX0vg\niA5rM5OpqK4bArVP1lRWnLyvghwO+TW763RVuXlS0scfzMy4g0NgrG6j7TIOKEqz\n4xQxOuwkudqiQr/kOqKuLxQBXa+5MJkyhfPmqYw5wpqyCwFa/7Q4b3NidWlsZCB0\nZXN0IChvc2J1aWxkIHRlc3QgZ3Bna2V5KSA8b3NidWlsZEBleGFtcGxlLmNvbT6I\newQTEQIAOxYhBGB8woiEPRKBO8Cr31lulpQgMejzBQJkQUiVAhsjBQsJCAcCAiIC\nBhUKCQgLAgQWAgMBAh4HAheAAAoJEFlulpQgMejzapMAoLmUg1mNDTRUaCrN/fzm\nHYLHL6jkAJ9pEKkJQiHB6SfD0fkiD2GkELYLubkBDQRkQUiVEAQAlAAXrQ572vuw\nxI3W8GSZmOQiAYOQmOKRloLEy6VZ3NSOb9y2TXj33QTkJBPOM17AzB7E+YjZrpUt\ngl6LlXmfjMcJAcXhFaUBCilAcMwMlLl7DtnSkLnLIXYmHiN0v83BH/H0EPutOc5l\n0QIyugutifp9SJz2+EWpC4bjA7GFkQ8AAwUD/1tLEGqCJ37O8gfzYt2PWkqBEoOY\n0Z3zwVS6PWW/IIkak9dAJ0iX5NMeFWpzFNfviDPHqhEdUR55zsxyUZIZlCX5jwmA\nt7qm3cbH4HNU1Ogq3Q9hykbTPWPZVkpvNm/TO8TA2brhkz3nuS8Hbmh+rjXFOSZj\nDQBUxItuuj2hhpQEiGAEGBECACAWIQRgfMKIhD0SgTvAq99ZbpaUIDHo8wUCZEFI\nlQIbDAAKCRBZbpaUIDHo83fQAKDHgFIaggaNsvDQkj7vMX0fecHRhACfS9Bvxn2W\nWSb6T+gChmYBseZwk/k=\n=DQ3i\n-----END PGP PUBLIC KEY BLOCK-----\n" ] } + ], + "ca": [ + "-----BEGIN CERTIFICATE-----\nMIIDszCCApugAwIBAgIUJ4lK+JfdJCNgcEVxZDinJfKKbQswDQYJKoZIhvcNAQEL\nBQAwaDELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9saW5hMRAwDgYD\nVQQHDAdSYWxlaWdoMRAwDgYDVQQKDAdSZWQgSGF0MRwwGgYDVQQDDBNUZXN0IENB\nIGZvciBvc2J1aWxkMCAXDTI0MDkwMzEzMjkyMFoYDzIyOTgwNjE4MTMyOTIwWjBo\nMQswCQYDVQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAcM\nB1JhbGVpZ2gxEDAOBgNVBAoMB1JlZCBIYXQxHDAaBgNVBAMME1Rlc3QgQ0EgZm9y\nIG9zYnVpbGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDeA7OcWTrV\ngstoBsUaeJKm8nelg7Lc0WNXH6yOTLsr4td4yHs0YOvFGwgSf+ffV3RAG1mgqnMG\nMgkD2+z+7QhHbHHs3y0d0zfhA2bg0KVvfCWk7fNRPHY0UOePpXk245Bfw3D0VTpl\nF7nePk1I7ZY09snPWUeb2rjKXzYjKjzM0h27+ykV8I8+FbdyPk/pR8whyDqtHLUa\nXfFy2TFloDSYMkHKVd38BnL0bj91x5F+KsZkN4HzfbYwxLbCQfOSgy7q6TWce9kq\nLo6tya9vuvpWFm1dye7L+BodAQAq/dI/JMeCfyTb0eFb+tyzfr5aVIoqqDN+p9ft\ncw4OefpHbhtNAgMBAAGjUzBRMB0GA1UdDgQWBBRV2A9YmusekPzu5Yf08cV0oPL1\nwjAfBgNVHSMEGDAWgBRV2A9YmusekPzu5Yf08cV0oPL1wjAPBgNVHRMBAf8EBTAD\nAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgQZ2Xfj+NxaKBZgn2KNxS0MTbhzHRz6Rn\nqJs+h8OUz2Crmaf6N+RHlmDRZXUrDjSHpxVT2LxFy7ofRrLYIezFDUYfb920VkkV\nSVcxh1YDFROJalfMoE6wdyR/LnK4MJZS9fUpeCJJc/A0J+9FK9CwcyUrHgJ8XbJh\nMKYyQ+cf6O7wzutuBpMyRqSKS+hVM7BQTmSFvv1eAJlo6klGAmmKiYmAEvcQadH1\ndjrujsA3Cn5vX2L+0yuiLB5/zoxqx5cEy97TuKUYB8OqMMujAXNzF4L3HJDUNba2\nAhEkFozMXwYX73TGbGZ0mawPS5D3v3tYTEmJFf6SnVCmUW1fs57g\n-----END CERTIFICATE-----\n" ] } }