Skip to content

Commit

Permalink
feat: support AWS KMS for the SecureBoot signing
Browse files Browse the repository at this point in the history
Fixes #8197

Signed-off-by: pardomue <edgar_ruben.pardo_munoz@roche.com>
Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
(cherry picked from commit 5372188)
  • Loading branch information
pardomue authored and smira committed Feb 21, 2024
1 parent c6e7a95 commit 7d13782
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 13 deletions.
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ require (
github.com/BurntSushi/toml v1.3.2
github.com/aws/aws-sdk-go-v2/config v1.25.6
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5
github.com/aws/aws-sdk-go-v2/service/kms v1.26.5
github.com/aws/smithy-go v1.17.0
github.com/beevik/ntp v1.3.0
github.com/benbjohnson/clock v1.3.5 // project archived on 2023-05-18
Expand Down Expand Up @@ -173,10 +174,10 @@ require (
github.com/ProtonMail/gopenpgp/v2 v2.7.4 // indirect
github.com/adrg/xdg v0.4.0 // indirect
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect
github.com/aws/aws-sdk-go-v2 v1.23.1 // indirect
github.com/aws/aws-sdk-go-v2 v1.23.2 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 // indirect
Expand Down
14 changes: 8 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,26 @@ 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.23.1 h1:qXaFsOOMA+HsZtX8WoCa+gJnbyW7qyFFBlPqvTSzbaI=
github.com/aws/aws-sdk-go-v2 v1.23.1/go.mod h1:i1XDttT4rnf6vxc9AuskLc6s7XBee8rlLilKlc03uAA=
github.com/aws/aws-sdk-go-v2 v1.23.2 h1:UoTll1Y5b88x8h53OlsJGgOHwpggdMr7UVnLjMb3XYg=
github.com/aws/aws-sdk-go-v2 v1.23.2/go.mod h1:i1XDttT4rnf6vxc9AuskLc6s7XBee8rlLilKlc03uAA=
github.com/aws/aws-sdk-go-v2/config v1.25.6 h1:p7b0sR6lHVNNOK/dE4xZgq2R+NNFRjtAXy8WNE6jbpo=
github.com/aws/aws-sdk-go-v2/config v1.25.6/go.mod h1:E/nt0ERX9ZX2RCcJWBax94jFn738UERvjSn4R3msEeQ=
github.com/aws/aws-sdk-go-v2/credentials v1.16.5 h1:oJz7X2VzKl8Y9pX7Fa5sIy4+3OnknF+Ne0KYu7DCoQQ=
github.com/aws/aws-sdk-go-v2/credentials v1.16.5/go.mod h1:2HvVzcP9ih6XR66omXIsgWjtolkL0MlQVqPcK3nXK+E=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 h1:KehRNiVzIfAcj6gw98zotVbb/K67taJE0fkfgM6vzqU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5/go.mod h1:VhnExhw6uXy9QzetvpXDolo1/hjhx4u9qukBGkuUwjs=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 h1:LAm3Ycm9HJfbSCd5I+wqC2S9Ej7FPrgr5CQoOljJZcE=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4/go.mod h1:xEhvbJcyUf/31yfGSQBe01fukXwXJ0gxDp7rLfymWE0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 h1:4GV0kKZzUxiWxSVpn/9gwR0g21NF1Jsyduzo9rHgC/Q=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4/go.mod h1:dYvTNAggxDZy6y1AF7YDwXsPuHFy/VNEpEI/2dWK9IU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.5 h1:16Z1XuMUv63fcyW5bIUno6AFcX4drsrE0gof+xue6g4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.5/go.mod h1:pRvFacV2qbRKy34ZFptHZW4wpauJA445bqFbvA6ikSo=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.5 h1:RxpMuBgzP3Dj1n5CZY6droLFcsn5gc7QsrIcaGQoeCs=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.5/go.mod h1:dO8Js7ym4Jzg/wcjTgCRVln/jFn3nI82XNhsG2lWbDI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 h1:uR9lXYjdPX0xY+NhvaJ4dD8rpSRz5VY81ccIIoNG+lw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 h1:rpkF4n0CyFcrJUG/rNNohoTmhtWlFTRI4BsZOh9PvLs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1/go.mod h1:l9ymW25HOqymeU2m1gbUQ3rUIsTwKs8gYHXkqDQUhiI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 h1:rdovz3rEu0vZKbzoMYPTehp0E8veoE9AyfzqCr5Eeao=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4/go.mod h1:aYCGNjyUCUelhofxlZyj63srdxWUSsBSGg5l6MCuXuE=
github.com/aws/aws-sdk-go-v2/service/kms v1.26.5 h1:MRNoQVbEtjzhYFeKVMifHae4K5q4FuK9B7tTDskIF/g=
github.com/aws/aws-sdk-go-v2/service/kms v1.26.5/go.mod h1:gfe6e+rOxaiz/gr5Myk83ruBD6F9WvM7TZbLjcTNsDM=
github.com/aws/aws-sdk-go-v2/service/sso v1.17.4 h1:WSMiDIMaDGyIiXwruNITU0IJF0d0foXwjxpxRylamqQ=
github.com/aws/aws-sdk-go-v2/service/sso v1.17.4/go.mod h1:oA6VjNsLll2eVuUoF2D+CMyORgNzPEW/3PyUdq6WQjI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.2 h1:GsrlsvTPBNxHvE3KBCwUMnR76MTO/6qnnO1ILSUOpTA=
Expand Down
32 changes: 28 additions & 4 deletions pkg/imager/profile/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/siderolabs/talos/internal/pkg/secureboot/measure"
"github.com/siderolabs/talos/internal/pkg/secureboot/pesign"
"github.com/siderolabs/talos/pkg/archiver"
"github.com/siderolabs/talos/pkg/imager/profile/internal/signer/aws"
"github.com/siderolabs/talos/pkg/imager/profile/internal/signer/azure"
"github.com/siderolabs/talos/pkg/imager/profile/internal/signer/file"
"github.com/siderolabs/talos/pkg/images"
Expand Down Expand Up @@ -91,24 +92,43 @@ type SecureBootAssets struct {

// SigningKeyAndCertificate describes a signing key & certificate.
type SigningKeyAndCertificate struct {
// File-based:
// File-based.
//
// Static key and certificate paths.
KeyPath string `yaml:"keyPath,omitempty"`
CertPath string `yaml:"certPath,omitempty"`
// Azure:
// Azure.
//
// Azure Vault URL and certificate ID, key will be found from the certificate.
AzureVaultURL string `yaml:"azureVaultURL,omitempty"`
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.
AwsKMSKeyID string `yaml:"awsKMSKeyID,omitempty"`
AwsRegion string `yaml:"awsRegion,omitempty"`
AwsCertPath string `yaml:"awsCertPath,omitempty"`
}

// SigningKey describes a signing key.
type SigningKey struct {
// File-based:
// File-based.
//
// Static key path.
KeyPath string `yaml:"keyPath,omitempty"`
// Azure:
// Azure.
//
// Azure Vault URL and key ID.
// AzureKeyVersion might be left empty to use the latest key version.
AzureVaultURL string `yaml:"azureVaultURL,omitempty"`
AzureKeyID string `yaml:"azureKeyID,omitempty"`
AzureKeyVersion string `yaml:"azureKeyVersion,omitempty"`
// AWS.
//
// AWS KMS Key ID and region.
AwsKMSKeyID string `yaml:"awsKMSKeyID,omitempty"`
AwsRegion string `yaml:"awsRegion,omitempty"`
}

// GetSigner returns the signer.
Expand All @@ -118,6 +138,8 @@ func (key SigningKey) GetSigner(ctx context.Context) (measure.RSAKey, error) {
return file.NewPCRSigner(key.KeyPath)
case key.AzureVaultURL != "" && key.AzureKeyID != "":
return azure.NewPCRSigner(ctx, key.AzureVaultURL, key.AzureKeyID, key.AzureKeyVersion)
case key.AwsKMSKeyID != "":
return aws.NewPCRSigner(ctx, key.AwsKMSKeyID, key.AwsRegion)
default:
return nil, fmt.Errorf("unsupported PCR signer")
}
Expand All @@ -130,6 +152,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.AwsCertPath != "":
return aws.NewSecureBootSigner(ctx, keyAndCert.AwsKMSKeyID, keyAndCert.AwsRegion, keyAndCert.AwsCertPath)
default:
return nil, fmt.Errorf("unsupported PCR signer")
}
Expand Down
23 changes: 23 additions & 0 deletions pkg/imager/profile/internal/signer/aws/aws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package aws implements SecureBoot/PCR signers via AWS Key Management Service.
package aws

import (
"context"
"fmt"

"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/kms"
)

func getKmsClient(ctx context.Context, awsRegion string) (*kms.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 kms.NewFromConfig(awsCfg), nil
}
38 changes: 38 additions & 0 deletions pkg/imager/profile/internal/signer/aws/aws_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package aws_test

import (
"context"
"crypto/sha256"
"os"
"testing"

"github.com/stretchr/testify/require"

"github.com/siderolabs/talos/pkg/imager/profile/internal/signer/aws"
)

func TestIntegration(t *testing.T) {
for _, envVar := range []string{"AWS_KMS_KEY_ID", "AWS_REGION", "AWS_CERT_PATH"} {
if os.Getenv(envVar) == "" {
t.Skipf("%s not set", envVar)
}
}

signer, err := aws.NewPCRSigner(context.TODO(), os.Getenv("AWS_KMS_KEY_ID"), os.Getenv("AWS_REGION"))
require.NoError(t, err)

digest := sha256.Sum256(nil)

_, err = signer.Sign(nil, digest[:], nil)
require.NoError(t, err)

sbSigner, err := aws.NewSecureBootSigner(context.TODO(), os.Getenv("AWS_KMS_KEY_ID"), os.Getenv("AWS_REGION"), os.Getenv("AWS_CERT_PATH"))
require.NoError(t, err)

_, err = sbSigner.Signer().Sign(nil, digest[:], nil)
require.NoError(t, err)
}
151 changes: 151 additions & 0 deletions pkg/imager/profile/internal/signer/aws/pcr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package aws

import (
"context"
"crypto"
"crypto/rsa"
"crypto/x509"
"fmt"
"io"
"math/big"

"github.com/aws/aws-sdk-go-v2/service/kms"
"github.com/aws/aws-sdk-go-v2/service/kms/types"

"github.com/siderolabs/talos/internal/pkg/secureboot/measure"
)

// KeySigner implements measure.RSAKey interface.
//
// KeySigner wraps Azure APIs to provide public key and crypto.Signer interface out of Azure Key Vault RSA key.
type KeySigner struct {
keyName string
mode mode

client *kms.Client
publicKey *rsa.PublicKey
}

var algMap = map[mode]map[crypto.Hash]types.SigningAlgorithmSpec{
rsaPKCS1v15: {
crypto.SHA256: types.SigningAlgorithmSpecRsassaPkcs1V15Sha256,
crypto.SHA384: types.SigningAlgorithmSpecRsassaPkcs1V15Sha384,
crypto.SHA512: types.SigningAlgorithmSpecRsassaPkcs1V15Sha512,
},
rsaPSS: {
crypto.SHA256: types.SigningAlgorithmSpecRsassaPssSha256,
crypto.SHA384: types.SigningAlgorithmSpecRsassaPssSha384,
crypto.SHA512: types.SigningAlgorithmSpecRsassaPssSha512,
},
ecdsa: {
crypto.SHA256: types.SigningAlgorithmSpecEcdsaSha256,
crypto.SHA384: types.SigningAlgorithmSpecEcdsaSha384,
crypto.SHA512: types.SigningAlgorithmSpecEcdsaSha512,
},
}

type mode string

const (
rsaPKCS1v15 mode = "pkcs1v15"
rsaPSS mode = "pss"
ecdsa mode = "ecdsa"
)

// PublicRSAKey returns the public key.
func (s *KeySigner) PublicRSAKey() *rsa.PublicKey {
return s.publicKey
}

// Public returns the public key.
func (s *KeySigner) Public() crypto.PublicKey {
return s.PublicRSAKey()
}

// Sign implements the crypto.Signer interface.
func (s *KeySigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
mode := s.mode

inner := algMap[mode]
if inner == nil {
return nil, fmt.Errorf("mode not supported")
}

hf := crypto.SHA256

if opts != nil {
hf = opts.HashFunc()
}

algorithm := inner[hf]
if algorithm == "" {
return nil, fmt.Errorf("algorithm not supported")
}

resp, err := s.client.Sign(context.Background(), &kms.SignInput{
KeyId: &s.keyName,
Message: digest,
MessageType: types.MessageTypeDigest,
SigningAlgorithm: algorithm,
})
if err != nil {
return nil, err
}

return resp.Signature, nil
}

// Verify interface.
var _ measure.RSAKey = (*KeySigner)(nil)

// NewPCRSigner creates a new PCR signer from AWS settings.
func NewPCRSigner(ctx context.Context, kmsKeyID, awsRegion string) (*KeySigner, error) {
client, err := getKmsClient(ctx, awsRegion)
if err != nil {
return nil, fmt.Errorf("failed to build AWS kms client: %w", err)
}

keyResponse, err := client.GetPublicKey(ctx, &kms.GetPublicKeyInput{
KeyId: &kmsKeyID,
})
if err != nil {
return nil, fmt.Errorf("failed to get key: %w", err)
}

if keyResponse.KeyUsage != "SIGN_VERIFY" {
return nil, fmt.Errorf("key usage is not SIGN_VERIFY")
}

switch keyResponse.KeySpec { //nolint:exhaustive
case types.KeySpecRsa2048, types.KeySpecRsa3072, types.KeySpecRsa4096:
// expected, continue
default:
return nil, fmt.Errorf("key type is not RSA")
}

parsedKey, err := x509.ParsePKIXPublicKey(keyResponse.PublicKey)
if err != nil {
return nil, fmt.Errorf("Public key is not valid: %w", err)
}

rsaKey := parsedKey.(*rsa.PublicKey) //nolint:errcheck
if rsaKey.E == 0 {
return nil, fmt.Errorf("property e is empty")
}

if rsaKey.N.Cmp(big.NewInt(0)) == 0 {
return nil, fmt.Errorf("property N is empty")
}

return &KeySigner{
keyName: kmsKeyID,
mode: rsaPKCS1v15, // TODO: make this configurable

publicKey: rsaKey,
client: client,
}, nil
}
Loading

0 comments on commit 7d13782

Please sign in to comment.