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 <tim.jones@siderolabs.com>
This commit is contained in:
Tim Jones 2026-01-21 10:52:32 +01:00
parent 4172095125
commit 4b274f7615
No known key found for this signature in database
GPG Key ID: A2A702DD5B689F45
6 changed files with 72 additions and 12 deletions

7
go.mod
View File

@ -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

14
go.sum
View File

@ -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=

View File

@ -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:

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}