Skip to content

Commit

Permalink
osbuild: new stage 'cacert'
Browse files Browse the repository at this point in the history
  • Loading branch information
lzap committed Oct 14, 2024
1 parent 347abb4 commit 1cac23c
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 1 deletion.
5 changes: 5 additions & 0 deletions pkg/blueprint/ca_customizations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package blueprint

type CACustomization struct {
PEMCerts []string `json:"pem_certs,omitempty" toml:"pem_certs,omitempty"`
}
26 changes: 26 additions & 0 deletions pkg/blueprint/customizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"slices"
"strings"

"github.com/osbuild/images/pkg/cert"
"github.com/osbuild/images/pkg/customizations/anaconda"
)

Expand All @@ -31,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"`
CACerts *CACustomization `json:"cacerts,omitempty" toml:"ca,omitempty"`
}

type IgnitionCustomization struct {
Expand Down Expand Up @@ -425,3 +427,27 @@ func (c *Customizations) GetRHSM() *RHSMCustomization {
}
return c.RHSM
}

func (c *Customizations) CheckCACerts() error {
if c == nil {
return nil
}

if c.CACerts == nil {
for _, bundle := range c.CACerts.PEMCerts {
_, err := cert.ParseCerts(bundle)
if err != nil {
return err
}
}
}

return nil
}

func (c *Customizations) GetCACerts() *CACustomization {
if c == nil {
return nil
}
return c.CACerts
}
36 changes: 36 additions & 0 deletions pkg/cert/parsecerts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cert

import (
"crypto/x509"
"encoding/pem"
"fmt"
)

// ParseCerts parses a PEM-encoded certificate chain formatted as concatenated strings
// and returns a slice of x509.Certificate.
func ParseCerts(cert string) ([]*x509.Certificate, error) {
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 {
return nil, fmt.Errorf("failed to parse certificate: %w", err)
}
result = append(result, cert)
}

return result, nil
}
46 changes: 46 additions & 0 deletions pkg/cert/parsecerts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cert

import (
"testing"

"github.com/stretchr/testify/assert"
)

// taken from osbuild:test/data/certs/cert1.pem
const exampleCert = `
-----BEGIN CERTIFICATE-----
MIIDhTCCAm2gAwIBAgIUVya7VJ3O8W8SqwuEa0BZ4HSsXvAwDQYJKoZIhvcNAQEL
BQAwUTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVy
bGluMQwwCgYDVQQKDANPcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yNDA4MjYx
MDQyNDBaGA8yMTI0MDgwMjEwNDI0MFowUTELMAkGA1UEBhMCREUxDzANBgNVBAgM
BkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQKDANPcmcxEjAQBgNVBAMM
CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnGjlvN
O3F/Z7Lr/r+6Xp2DosnNwoPHhG2e61KnFzgZfaxbklal5ORpuV/gLIg7lrbpdZe7
WvK+16RanL6fLitis/tYVFyvz1MXqBYYrEoFGvVg9fOiis7hjpdZcpNDH9SngoAN
O0Wvv4T6LQS0cC7ZAFZjvmJ+RiZEbzRkNG5pUddZXbotE6htNfLgA5L1wIBgllrM
4DVkG0yNKmzqPNzfPTbdUgWCfjaQShHy1GP8KNEwFxM31F2wvQxsEb77o1S44Out
mlsi83tti6P7KjDk7w2j2zZO1X0xI8pflv3TBkJT1Am8vnk6rVnNO4pCpop3+kma
pDUEzBQmSQA5R1ECAwEAAaNTMFEwHQYDVR0OBBYEFDxFcFgPEsgsDixfKxB0uYGN
aJmzMB8GA1UdIwQYMBaAFDxFcFgPEsgsDixfKxB0uYGNaJmzMA8GA1UdEwEB/wQF
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFih4lUbLlhKwIAV9x3/W7Mih8xUEdZr
olquZgaHedFet+ByAHvoES3pec7AVYTOD53mjgyZubD6INnVHzKyS4AG9ydD73o4
cmm3DKxBaesvlHeTn0MOKsoM8QCxeyFJmiUPpgDBok/PFnbGR9+JcsrlGJAnsSKD
vWpiwYcBauZ9nnK5yDe5M9XNFPkNDZzbKvWU7Sw3ziMT/+bRJse5vTrYcyOnNGgy
gZNz2nimKy1U8XZVAVwOV0rdGEFrfMln8DkRW86rGK/EncaVsl0SSP/rmjQgiX8Q
3CZraQGujJP932HSwUfdCX9yh+rTjE3MEnbqMoLzJa4BXB2aDQWtywU=
-----END CERTIFICATE-----
`

func TestParseCerts(t *testing.T) {
certs, err := ParseCerts(exampleCert)
assert.Nil(t, err)
assert.Equal(t, len(certs), 1)
assert.Equal(t, "localhost", certs[0].Subject.CommonName)
}

func TestParseConcatenatedCerts(t *testing.T) {
certs, err := ParseCerts(exampleCert + "\n" + exampleCert)
assert.Nil(t, err)
assert.Equal(t, len(certs), 2)
assert.Equal(t, "localhost", certs[0].Subject.CommonName)
}
7 changes: 7 additions & 0 deletions pkg/distro/fedora/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,13 @@ func osCustomizations(
osc.Files = append(osc.Files, imageConfig.Files...)
osc.Directories = append(osc.Directories, imageConfig.Directories...)

if err := c.CheckCACerts(); err != nil {
panic(fmt.Sprintf("unexpected error checking CA certs: %v", err))
}
if ca := c.GetCACerts(); ca != nil {
osc.CACerts = ca.PEMCerts
}

return osc, nil
}

Expand Down
7 changes: 7 additions & 0 deletions pkg/distro/rhel/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,13 @@ func osCustomizations(
osc.NoBLS = *imageConfig.NoBLS
}

if err := c.CheckCACerts(); err != nil {
panic(fmt.Sprintf("unexpected error checking CA certs: %v", err))
}
if ca := c.GetCACerts(); ca != nil {
osc.CACerts = ca.PEMCerts
}

return osc, nil
}

Expand Down
23 changes: 23 additions & 0 deletions pkg/manifest/os.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package manifest

import (
"encoding/pem"
"fmt"
"path/filepath"
"strings"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/osbuild/images/internal/environment"
"github.com/osbuild/images/internal/workload"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/cert"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/customizations/bootc"
"github.com/osbuild/images/pkg/customizations/fsnode"
Expand Down Expand Up @@ -141,6 +143,8 @@ type OSCustomizations struct {
Directories []*fsnode.Directory
Files []*fsnode.File

CACerts []string

FIPS bool

// NoBLS configures the image bootloader with traditional menu entries
Expand Down Expand Up @@ -822,6 +826,25 @@ func (p *OS) serialize() osbuild.Pipeline {
}
}

if len(p.CACerts) > 0 {
for _, cc := range p.CACerts {
certs, err := cert.ParseCerts(cc)
if err != nil {
panic(fmt.Errorf("failed to parse CA certificates: %v", err))
}

for _, c := range certs {
path := filepath.Join("/etc/pki/ca-trust/source/anchors", filepath.Base(c.SerialNumber.Text(16))+".pem")
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
}

Expand Down
7 changes: 7 additions & 0 deletions pkg/osbuild/pki_update_ca_trust_stage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package osbuild

func NewCAStageStage() *Stage {
return &Stage{
Type: "org.osbuild.pki.update-ca-trust",
}
}
7 changes: 6 additions & 1 deletion test/configs/all-customizations.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,12 @@
"-----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"
]
}
]
],
"cacerts": {
"pem_certs": [
"-----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"
]
}
}
}
}

0 comments on commit 1cac23c

Please sign in to comment.