From 4b274f76159495cc6c2977ec3bbade71e35aade8 Mon Sep 17 00:00:00 2001 From: Tim Jones Date: Wed, 21 Jan 2026 10:52:32 +0100 Subject: [PATCH] feat: support aws cert manager in imager Add support for using certificates stored in AWS Certificate Manager to sign secureboot images in imager. Signed-off-by: Tim Jones --- go.mod | 7 ++-- go.sum | 14 ++++--- pkg/imager/profile/input.go | 7 +++- pkg/imager/profile/internal/signer/aws/aws.go | 10 +++++ .../profile/internal/signer/aws/aws_test.go | 8 +++- .../profile/internal/signer/aws/secureboot.go | 38 +++++++++++++++++++ 6 files changed, 72 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 08c8efc7a..727c3dfc8 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( github.com/alexflint/go-filemutex v1.3.0 github.com/aws/aws-sdk-go-v2/config v1.32.6 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 + github.com/aws/aws-sdk-go-v2/service/acm v1.37.19 github.com/aws/aws-sdk-go-v2/service/kms v1.49.4 github.com/aws/smithy-go v1.24.0 github.com/beevik/ntp v1.5.0 @@ -214,10 +215,10 @@ require ( github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apparentlymart/go-cidr v1.1.0 // indirect github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect - github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.6 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect diff --git a/go.sum b/go.sum index 1f72cd29b..918145b8c 100644 --- a/go.sum +++ b/go.sum @@ -61,20 +61,22 @@ github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hC github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4= -github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= +github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8= github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI= github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE= github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/service/acm v1.37.19 h1:6BPfgg/Y4Pmrdr8KDwHx2CYkw8qPEaGQ+aixjuAY/0U= +github.com/aws/aws-sdk-go-v2/service/acm v1.37.19/go.mod h1:mhOStWeEa1xP99WNNPstX75qgqWgJycL5H7UwZQbqbo= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI= diff --git a/pkg/imager/profile/input.go b/pkg/imager/profile/input.go index 83f9f775d..8b4a2cbeb 100644 --- a/pkg/imager/profile/input.go +++ b/pkg/imager/profile/input.go @@ -111,11 +111,12 @@ type SigningKeyAndCertificate struct { AzureCertificateID string `yaml:"azureCertificateID,omitempty"` // AWS. // - // AWS KMS Key ID and region. - // AWS doesn't have a good way to store a certificate, so it's expected to be a file. + // AWS KMS Key ID, ACM certificate ARN, and region. + // Support local cert file for legacy use cases. AwsKMSKeyID string `yaml:"awsKMSKeyID,omitempty"` AwsRegion string `yaml:"awsRegion,omitempty"` AwsCertPath string `yaml:"awsCertPath,omitempty"` + AwsCertARN string `yaml:"awsCertARN,omitempty"` } // SigningKey describes a signing key. @@ -159,6 +160,8 @@ func (keyAndCert SigningKeyAndCertificate) GetSigner(ctx context.Context) (pesig return file.NewSecureBootSigner(keyAndCert.CertPath, keyAndCert.KeyPath) case keyAndCert.AzureVaultURL != "" && keyAndCert.AzureCertificateID != "": return azure.NewSecureBootSigner(ctx, keyAndCert.AzureVaultURL, keyAndCert.AzureCertificateID, keyAndCert.AzureCertificateID) + case keyAndCert.AwsKMSKeyID != "" && keyAndCert.AwsCertARN != "": + return aws.NewSecureBootACMSigner(ctx, keyAndCert.AwsKMSKeyID, keyAndCert.AwsRegion, keyAndCert.AwsCertARN) case keyAndCert.AwsKMSKeyID != "" && keyAndCert.AwsCertPath != "": return aws.NewSecureBootSigner(ctx, keyAndCert.AwsKMSKeyID, keyAndCert.AwsRegion, keyAndCert.AwsCertPath) default: diff --git a/pkg/imager/profile/internal/signer/aws/aws.go b/pkg/imager/profile/internal/signer/aws/aws.go index 1ec7cb851..a5636c558 100644 --- a/pkg/imager/profile/internal/signer/aws/aws.go +++ b/pkg/imager/profile/internal/signer/aws/aws.go @@ -10,6 +10,7 @@ import ( "fmt" "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/acm" "github.com/aws/aws-sdk-go-v2/service/kms" ) @@ -21,3 +22,12 @@ func getKmsClient(ctx context.Context, awsRegion string) (*kms.Client, error) { return kms.NewFromConfig(awsCfg), nil } + +func getAcmClient(ctx context.Context, awsRegion string) (*acm.Client, error) { + awsCfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(awsRegion)) + if err != nil { + return nil, fmt.Errorf("error initializing AWS default config: %w", err) + } + + return acm.NewFromConfig(awsCfg), nil +} diff --git a/pkg/imager/profile/internal/signer/aws/aws_test.go b/pkg/imager/profile/internal/signer/aws/aws_test.go index 5731ad1a7..e35553726 100644 --- a/pkg/imager/profile/internal/signer/aws/aws_test.go +++ b/pkg/imager/profile/internal/signer/aws/aws_test.go @@ -15,7 +15,7 @@ import ( ) func TestIntegration(t *testing.T) { - for _, envVar := range []string{"AWS_KMS_KEY_ID", "AWS_REGION", "AWS_CERT_PATH"} { + for _, envVar := range []string{"AWS_KMS_KEY_ID", "AWS_REGION", "AWS_CERT_PATH", "AWS_CERT_ARN"} { if os.Getenv(envVar) == "" { t.Skipf("%s not set", envVar) } @@ -34,4 +34,10 @@ func TestIntegration(t *testing.T) { _, err = sbSigner.Signer().Sign(nil, digest[:], nil) require.NoError(t, err) + + sbAcmSigner, err := aws.NewSecureBootACMSigner(t.Context(), os.Getenv("AWS_KMS_KEY_ID"), os.Getenv("AWS_REGION"), os.Getenv("AWS_CERT_ARN")) + require.NoError(t, err) + + _, err = sbAcmSigner.Signer().Sign(nil, digest[:], nil) + require.NoError(t, err) } diff --git a/pkg/imager/profile/internal/signer/aws/secureboot.go b/pkg/imager/profile/internal/signer/aws/secureboot.go index 8c17d48d0..1dbc27e99 100644 --- a/pkg/imager/profile/internal/signer/aws/secureboot.go +++ b/pkg/imager/profile/internal/signer/aws/secureboot.go @@ -12,6 +12,9 @@ import ( "fmt" "os" + "github.com/aws/aws-sdk-go-v2/service/acm" + "github.com/siderolabs/go-pointer" + "github.com/siderolabs/talos/internal/pkg/secureboot/pesign" ) @@ -61,3 +64,38 @@ func NewSecureBootSigner(ctx context.Context, kmsKeyID, awsRegion, certPath stri cert: cert, }, nil } + +// NewSecureBootACMSigner creates a new SecureBootSigner using an ACM certificate. +func NewSecureBootACMSigner(ctx context.Context, kmsKeyID, awsRegion, acmCertificateARN string) (*SecureBootSigner, error) { + keySigner, err := NewPCRSigner(ctx, kmsKeyID, awsRegion) + if err != nil { + return nil, fmt.Errorf("failed to initialize certificate key signer (kms): %w", err) + } + + acmClient, err := getAcmClient(ctx, awsRegion) + if err != nil { + return nil, fmt.Errorf("failed to build ACM client: %w", err) + } + + resp, err := acmClient.GetCertificate(ctx, &acm.GetCertificateInput{ + CertificateArn: &acmCertificateARN, + }) + if err != nil { + return nil, fmt.Errorf("failed to get certificate: %w", err) + } + + certBlock, _ := pem.Decode([]byte(pointer.SafeDeref(resp.Certificate))) + if certBlock == nil { + return nil, fmt.Errorf("failed to decode certificate") + } + + cert, err := x509.ParseCertificate(certBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to decode certificate: %w", err) + } + + return &SecureBootSigner{ + keySigner: keySigner, + cert: cert, + }, nil +}