mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-26 14:01:39 +01:00
feat(talosctl): append microsoft secure boot certs
This patch adds a flag to `secureboot.database.Generate` to append the Microsoft UEFI secure boot DB and KEK certificates to the appropriate ESLs, in addition to complimentary command line flags. This patch also includes a copy of said Microsoft certificates. The certificates are downloaded from an official Microsoft repo. Signed-off-by: Jean-Francois Roy <jf@devklog.net> Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
parent
fd6ddd11ef
commit
fd54dc191d
13
Dockerfile
13
Dockerfile
@ -309,6 +309,16 @@ FROM scratch AS ipxe-generate
|
||||
COPY --from=pkg-ipxe-amd64 /usr/libexec/snp.efi /amd64/snp.efi
|
||||
COPY --from=pkg-ipxe-arm64 /usr/libexec/snp.efi /arm64/snp.efi
|
||||
|
||||
FROM scratch AS microsoft-secureboot-database
|
||||
ADD https://github.com/microsoft/secureboot_objects.git /
|
||||
|
||||
FROM scratch AS microsoft-key-keys
|
||||
COPY --from=microsoft-secureboot-database /PreSignedObjects/KEK/Certificates/*.der /kek/
|
||||
|
||||
FROM scratch AS microsoft-db-keys
|
||||
COPY --from=microsoft-secureboot-database /PreSignedObjects/DB/Certificates/MicCor*.der /db/
|
||||
COPY --from=microsoft-secureboot-database /PreSignedObjects/DB/Certificates/microsoft*.der /db/
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} scratch AS generate
|
||||
COPY --from=proto-format-build /src/api /api/
|
||||
COPY --from=generate-build /api/common/*.pb.go /pkg/machinery/api/common/
|
||||
@ -333,6 +343,8 @@ COPY --from=go-generate /src/pkg/machinery/extensions/ /pkg/machinery/extensions
|
||||
COPY --from=ipxe-generate / /pkg/provision/providers/vm/internal/ipxe/data/ipxe/
|
||||
COPY --from=embed-abbrev / /
|
||||
COPY --from=pkg-ca-certificates /etc/ssl/certs/ca-certificates /internal/app/machined/pkg/controllers/secrets/data/
|
||||
COPY --from=microsoft-key-keys / /internal/pkg/secureboot/database/certs/
|
||||
COPY --from=microsoft-db-keys / /internal/pkg/secureboot/database/certs/
|
||||
|
||||
# The base target provides a container that can be used to build all Talos
|
||||
# assets.
|
||||
@ -345,6 +357,7 @@ COPY --from=generate /pkg/flannel/ ./pkg/flannel/
|
||||
COPY --from=generate /pkg/imager/ ./pkg/imager/
|
||||
COPY --from=generate /pkg/machinery/ ./pkg/machinery/
|
||||
COPY --from=generate /internal/app/machined/pkg/controllers/secrets/data/ ./internal/app/machined/pkg/controllers/secrets/data/
|
||||
COPY --from=generate /internal/pkg/secureboot/database/certs/ ./internal/pkg/secureboot/database/certs/
|
||||
COPY --from=embed / ./
|
||||
RUN --mount=type=cache,target=/.cache go list all >/dev/null
|
||||
WORKDIR /src/pkg/machinery
|
||||
|
||||
@ -45,6 +45,8 @@ var cmdFlags struct {
|
||||
OverlayName string
|
||||
OverlayImage string
|
||||
OverlayOptions []string
|
||||
// Only used when generating a secure boot iso without also providing a secure boot database.
|
||||
SecurebootIncludeWellKnownCerts bool
|
||||
}
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands.
|
||||
@ -173,6 +175,13 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
prof.Output.ImageOptions.DiskSize = int64(size)
|
||||
}
|
||||
|
||||
if cmdFlags.SecurebootIncludeWellKnownCerts {
|
||||
if prof.Input.SecureBoot == nil {
|
||||
prof.Input.SecureBoot = &profile.SecureBootAssets{}
|
||||
}
|
||||
prof.Input.SecureBoot.IncludeWellKnownCerts = true
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(cmdFlags.OutputPath, 0o755); err != nil {
|
||||
@ -229,4 +238,6 @@ func init() {
|
||||
rootCmd.MarkFlagsMutuallyExclusive("board", "overlay-name")
|
||||
rootCmd.MarkFlagsMutuallyExclusive("board", "overlay-image")
|
||||
rootCmd.MarkFlagsMutuallyExclusive("board", "overlay-option")
|
||||
rootCmd.PersistentFlags().BoolVar(
|
||||
&cmdFlags.SecurebootIncludeWellKnownCerts, "secureboot-include-well-known-certs", false, "Include well-known (Microsoft) UEFI certificates when generating a secure boot database")
|
||||
}
|
||||
|
||||
@ -64,6 +64,7 @@ var genSecurebootPCRCmd = &cobra.Command{
|
||||
var genSecurebootDatabaseCmdFlags struct {
|
||||
enrolledCertificatePath string
|
||||
signingCertificatePath, signingKeyPath string
|
||||
includeWellKnownCerts bool
|
||||
}
|
||||
|
||||
// genSecurebootDatabaseCmd represents the `gen secureboot database` command.
|
||||
@ -78,6 +79,7 @@ var genSecurebootDatabaseCmd = &cobra.Command{
|
||||
genSecurebootDatabaseCmdFlags.enrolledCertificatePath,
|
||||
genSecurebootDatabaseCmdFlags.signingKeyPath,
|
||||
genSecurebootDatabaseCmdFlags.signingCertificatePath,
|
||||
genSecurebootDatabaseCmdFlags.includeWellKnownCerts,
|
||||
)
|
||||
},
|
||||
}
|
||||
@ -140,7 +142,7 @@ func saveAsDER(file string, pem []byte) error {
|
||||
// generateSecureBootDatabase generates a UEFI database to enroll the signing certificate.
|
||||
//
|
||||
// ref: https://blog.hansenpartnership.com/the-meaning-of-all-the-uefi-keys/
|
||||
func generateSecureBootDatabase(path, enrolledCertificatePath, signingKeyPath, signingCertificatePath string) error {
|
||||
func generateSecureBootDatabase(path, enrolledCertificatePath, signingKeyPath, signingCertificatePath string, includeWellKnownCerts bool) error {
|
||||
in := profile.SigningKeyAndCertificate{
|
||||
KeyPath: signingKeyPath,
|
||||
CertPath: signingCertificatePath,
|
||||
@ -156,7 +158,7 @@ func generateSecureBootDatabase(path, enrolledCertificatePath, signingKeyPath, s
|
||||
return err
|
||||
}
|
||||
|
||||
db, err := database.Generate(enrolledPEM, signer)
|
||||
db, err := database.Generate(enrolledPEM, signer, database.IncludeWellKnownCertificates(includeWellKnownCerts))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate database: %w", err)
|
||||
}
|
||||
@ -186,6 +188,8 @@ func init() {
|
||||
&genSecurebootDatabaseCmdFlags.signingCertificatePath, "signing-certificate", helpers.ArtifactPath(constants.SecureBootSigningCertAsset), "path to the certificate used to sign the database")
|
||||
genSecurebootDatabaseCmd.Flags().StringVar(
|
||||
&genSecurebootDatabaseCmdFlags.signingKeyPath, "signing-key", helpers.ArtifactPath(constants.SecureBootSigningKeyAsset), "path to the key used to sign the database")
|
||||
genSecurebootDatabaseCmd.Flags().BoolVar(
|
||||
&genSecurebootDatabaseCmdFlags.includeWellKnownCerts, "include-well-known-uefi-certs", false, "include well-known UEFI (Microsoft) certificates in the database")
|
||||
genSecurebootCmd.AddCommand(genSecurebootDatabaseCmd)
|
||||
}
|
||||
|
||||
|
||||
@ -101,6 +101,12 @@ Talos Linux now supports adding [custom trusted roots](https://www.talos.dev/v1.
|
||||
title = "Default Node Labels"
|
||||
description = """\
|
||||
Talos Linux on config generation now adds a label `node.kubernetes.io/exclude-from-external-load-balancers` by default for the control plane nodes.
|
||||
"""
|
||||
|
||||
[notes.secureboot]
|
||||
title = "Secure Boot"
|
||||
description = """\
|
||||
Talos Linux now can optionally include well-known UEFI (Microsoft) SecureBoot keys into the auto-enrollment UEFI database.
|
||||
"""
|
||||
|
||||
[make_deps]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -7,6 +7,10 @@ package database
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"embed"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/foxboron/go-uefi/efi/signature"
|
||||
"github.com/foxboron/go-uefi/efi/util"
|
||||
@ -23,37 +27,146 @@ type Entry struct {
|
||||
Contents []byte
|
||||
}
|
||||
|
||||
const (
|
||||
microsoftSignatureOwnerGUID = "77fa9abd-0359-4d32-bd60-28f4e78f784b"
|
||||
)
|
||||
|
||||
// Well-known UEFI DB certificates (DER data).
|
||||
//
|
||||
//go:embed certs/db/*.der
|
||||
var wellKnownDB embed.FS
|
||||
|
||||
// Well-known UEFI KEK certificates (PEM data).
|
||||
//
|
||||
//go:embed certs/kek/*.der
|
||||
var wellKnownKEK embed.FS
|
||||
|
||||
func loadWellKnownCertificates(fs embed.FS, path string) ([]*x509.Certificate, error) {
|
||||
certs := []*x509.Certificate{}
|
||||
|
||||
files, err := fs.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
data, err := fs.ReadFile(filepath.Join(path, file.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
var wellKnownDBCertificates = sync.OnceValue(func() []*x509.Certificate {
|
||||
certs, err := loadWellKnownCertificates(wellKnownDB, "certs/db")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return certs
|
||||
})
|
||||
|
||||
var wellKnownKEKCertificates = sync.OnceValue(func() []*x509.Certificate {
|
||||
certs, err := loadWellKnownCertificates(wellKnownKEK, "certs/kek")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return certs
|
||||
})
|
||||
|
||||
// Options for Generate.
|
||||
type Options struct {
|
||||
IncludeWellKnownCertificates bool
|
||||
}
|
||||
|
||||
// Option is a functional option for Generate.
|
||||
type Option func(*Options)
|
||||
|
||||
// IncludeWellKnownCertificates is an option to include well-known certificates.
|
||||
func IncludeWellKnownCertificates(v bool) Option {
|
||||
return func(o *Options) {
|
||||
o.IncludeWellKnownCertificates = v
|
||||
}
|
||||
}
|
||||
|
||||
// Generate generates a UEFI database to enroll the signing certificate.
|
||||
//
|
||||
// ref: https://blog.hansenpartnership.com/the-meaning-of-all-the-uefi-keys/
|
||||
func Generate(enrolledCertificate []byte, signer pesign.CertificateSigner) ([]Entry, error) {
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func Generate(enrolledCertificate []byte, signer pesign.CertificateSigner, opts ...Option) ([]Entry, error) {
|
||||
var options Options
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
// derive UUID from enrolled certificate
|
||||
uuid := uuid.NewHash(sha256.New(), uuid.NameSpaceX500, enrolledCertificate, 4)
|
||||
|
||||
efiGUID := util.StringToGUID(uuid.String())
|
||||
|
||||
// Create ESL
|
||||
// Create PK ESL
|
||||
pk := signature.NewSignatureDatabase()
|
||||
if err := pk.Append(signature.CERT_X509_GUID, *efiGUID, enrolledCertificate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, signedPK, err := signature.SignEFIVariable(efivar.PK, pk, signer.Signer(), signer.Certificate())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create KEK ESL
|
||||
kek := signature.NewSignatureDatabase()
|
||||
if err := kek.Append(signature.CERT_X509_GUID, *efiGUID, enrolledCertificate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.IncludeWellKnownCertificates {
|
||||
owner := util.StringToGUID(microsoftSignatureOwnerGUID)
|
||||
for _, cert := range wellKnownKEKCertificates() {
|
||||
if err := kek.Append(signature.CERT_X509_GUID, *owner, cert.Raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, signedKEK, err := signature.SignEFIVariable(efivar.KEK, kek, signer.Signer(), signer.Certificate())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create db ESL
|
||||
db := signature.NewSignatureDatabase()
|
||||
if err := db.Append(signature.CERT_X509_GUID, *efiGUID, enrolledCertificate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sign the ESL, but for each EFI variable
|
||||
if options.IncludeWellKnownCertificates {
|
||||
owner := util.StringToGUID(microsoftSignatureOwnerGUID)
|
||||
for _, cert := range wellKnownDBCertificates() {
|
||||
if err := db.Append(signature.CERT_X509_GUID, *owner, cert.Raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, signedDB, err := signature.SignEFIVariable(efivar.Db, db, signer.Signer(), signer.Certificate())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, signedKEK, err := signature.SignEFIVariable(efivar.KEK, db, signer.Signer(), signer.Certificate())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, signedPK, err := signature.SignEFIVariable(efivar.PK, db, signer.Signer(), signer.Certificate())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []Entry{
|
||||
{Name: constants.SignatureKeyAsset, Contents: signedDB.Bytes()},
|
||||
{Name: constants.KeyExchangeKeyAsset, Contents: signedKEK.Bytes()},
|
||||
|
||||
@ -133,7 +133,7 @@ func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Repor
|
||||
|
||||
var entries []database.Entry
|
||||
|
||||
entries, err = database.Generate(enrolledPEM, signer)
|
||||
entries, err = database.Generate(enrolledPEM, signer, database.IncludeWellKnownCertificates(i.prof.Input.SecureBoot.IncludeWellKnownCerts))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate database: %w", err)
|
||||
}
|
||||
|
||||
@ -96,6 +96,8 @@ type SecureBootAssets struct {
|
||||
PlatformKeyPath string `yaml:"platformKeyPath,omitempty"`
|
||||
KeyExchangeKeyPath string `yaml:"keyExchangeKeyPath,omitempty"`
|
||||
SignatureKeyPath string `yaml:"signatureKeyPath,omitempty"`
|
||||
// Optional, auto-enrollment include well-known UEFI (Microsoft) certs.
|
||||
IncludeWellKnownCerts bool `yaml:"includeWellKnownCerts,omitempty"`
|
||||
}
|
||||
|
||||
// SigningKeyAndCertificate describes a signing key & certificate.
|
||||
|
||||
@ -1537,6 +1537,7 @@ talosctl gen secureboot database [flags]
|
||||
```
|
||||
--enrolled-certificate string path to the certificate to enroll (default "_out/uki-signing-cert.pem")
|
||||
-h, --help help for database
|
||||
--include-well-known-uefi-certs include well-known UEFI (Microsoft) certificates in the database
|
||||
--signing-certificate string path to the certificate used to sign the database (default "_out/uki-signing-cert.pem")
|
||||
--signing-key string path to the key used to sign the database (default "_out/uki-signing-key.pem")
|
||||
```
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user