mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-27 08:29:33 -05:00
a5021c52d3
* add ASK caching in joinservice Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * use cached ASK in Azure SEV-SNP attestation Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * update test charts Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix linter Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix typ Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * make caching mechanism less provider-specific Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * update buildfiles Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add `omitempty` flag Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * frontload certificate getter Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * rename frontloaded function Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * pass cached certificates to constructor Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix race condition Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix marshalling of empty certs Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix validator usage Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [wip] add certcache tests Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add certcache tests Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * tidy Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix validator test Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove unused fields in validator Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix certificate precedence Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * use separate context Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * tidy Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * linter fixes Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * linter fixes Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * Remove unnecessary comment Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com> * use background context Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * Use error format directive Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com> * `azure` -> `Azure` Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com> * improve error messages Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add x509 -> PEM util function Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * use crypto util functions Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix certificate replacement logic Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * only require ASK from certcache Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * tidy Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix comment typo Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> --------- Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com>
204 lines
7.1 KiB
Go
204 lines
7.1 KiB
Go
/*
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
// Package certcache implements an in-cluster SEV-SNP certificate cache.
|
|
package certcache
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"fmt"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
|
"github.com/edgelesssys/constellation/v2/internal/crypto"
|
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
|
"github.com/edgelesssys/constellation/v2/joinservice/internal/certcache/amdkds"
|
|
"github.com/google/go-sev-guest/abi"
|
|
"github.com/google/go-sev-guest/verify/trust"
|
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
|
)
|
|
|
|
// Client is a client for interacting with the certificate chain cache.
|
|
type Client struct {
|
|
log *logger.Logger
|
|
attVariant variant.Variant
|
|
kdsClient
|
|
kubeClient kubeClient
|
|
}
|
|
|
|
// NewClient creates a new CertCacheClient.
|
|
func NewClient(log *logger.Logger, kubeClient kubeClient, attVariant variant.Variant) *Client {
|
|
kdsClient := amdkds.NewKDSClient(trust.DefaultHTTPSGetter())
|
|
|
|
return &Client{
|
|
attVariant: attVariant,
|
|
log: log,
|
|
kubeClient: kubeClient,
|
|
kdsClient: kdsClient,
|
|
}
|
|
}
|
|
|
|
// CreateCertChainCache creates a certificate chain cache for the given attestation variant
|
|
// and returns the cached certificates, if applicable.
|
|
// If the certificate chain cache already exists, nothing is done.
|
|
func (c *Client) CreateCertChainCache(ctx context.Context) (*CachedCerts, error) {
|
|
switch c.attVariant {
|
|
case variant.AzureSEVSNP{}:
|
|
c.log.Debugf("Creating Azure SEV-SNP certificate chain cache")
|
|
ask, ark, err := c.createCertChainCache(ctx, abi.VcekReportSigner)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating Azure SEV-SNP certificate chain cache: %w", err)
|
|
}
|
|
return &CachedCerts{
|
|
ask: ask,
|
|
ark: ark,
|
|
}, nil
|
|
// TODO(derpsteb): Add AWS
|
|
default:
|
|
c.log.Debugf("No certificate chain caching possible for attestation variant %s", c.attVariant)
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
// CachedCerts contains the cached certificates.
|
|
type CachedCerts struct {
|
|
ask *x509.Certificate
|
|
ark *x509.Certificate
|
|
}
|
|
|
|
// SevSnpCerts returns the cached SEV-SNP ASK and ARK certificates.
|
|
func (c *CachedCerts) SevSnpCerts() (ask, ark *x509.Certificate) {
|
|
return c.ask, c.ark
|
|
}
|
|
|
|
// createCertChainCache creates a certificate chain cache configmap with the ASK and ARK
|
|
// retrieved from the KDS and returns ASK and ARK. If the configmap already exists and both ASK and ARK are present,
|
|
// nothing is done and the existing ASK and ARK are returned. If the configmap already exists but either ASK or ARK
|
|
// are missing, the missing certificate is retrieved from the KDS and the configmap is updated with the missing value.
|
|
func (c *Client) createCertChainCache(ctx context.Context, signingType abi.ReportSigner) (ask, ark *x509.Certificate, err error) {
|
|
c.log.Debugf("Creating certificate chain cache")
|
|
var shouldCreateConfigMap bool
|
|
|
|
// Check if ASK or ARK is already cached.
|
|
// If both are cached, return them.
|
|
// If only one is cached, retrieve the other one from the
|
|
// KDS and update the configmap with the missing value.
|
|
cacheAsk, cacheArk, err := c.getCertChainCache(ctx)
|
|
if k8serrors.IsNotFound(err) {
|
|
c.log.Debugf("Certificate chain cache does not exist")
|
|
shouldCreateConfigMap = true
|
|
} else if err != nil {
|
|
return nil, nil, fmt.Errorf("getting certificate chain cache: %w", err)
|
|
}
|
|
if cacheAsk != nil && cacheArk != nil {
|
|
c.log.Debugf("ASK and ARK present in cache, returning cached values")
|
|
return cacheAsk, cacheArk, nil
|
|
}
|
|
if cacheAsk != nil {
|
|
ask = cacheAsk
|
|
}
|
|
if cacheArk != nil {
|
|
ark = cacheArk
|
|
}
|
|
|
|
// If only one certificate is cached, retrieve the other one from the KDS.
|
|
c.log.Debugf("Retrieving certificate chain from KDS")
|
|
kdsAsk, kdsArk, err := c.kdsClient.CertChain(signingType)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("retrieving certificate chain from KDS: %w", err)
|
|
}
|
|
if ask == nil && kdsAsk != nil {
|
|
ask = kdsAsk
|
|
}
|
|
if ark == nil && kdsArk != nil {
|
|
ark = kdsArk
|
|
}
|
|
|
|
askPem, err := crypto.X509CertToPem(ask)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("encoding ASK: %w", err)
|
|
}
|
|
arkPem, err := crypto.X509CertToPem(ark)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("encoding ARK: %w", err)
|
|
}
|
|
|
|
if shouldCreateConfigMap {
|
|
// ConfigMap does not exist, create it.
|
|
c.log.Debugf("Creating certificate chain cache configmap")
|
|
if err := c.kubeClient.CreateConfigMap(ctx, constants.SevSnpCertCacheConfigMapName, map[string]string{
|
|
constants.CertCacheAskKey: string(askPem),
|
|
constants.CertCacheArkKey: string(arkPem),
|
|
}); err != nil {
|
|
// If the ConfigMap already exists, another JoinService instance created the certificate cache while this operation was running.
|
|
// Calling this function again should now retrieve the cached certificates.
|
|
if k8serrors.IsAlreadyExists(err) {
|
|
c.log.Debugf("Certificate chain cache configmap already exists, retrieving cached certificates")
|
|
return c.getCertChainCache(ctx)
|
|
}
|
|
return nil, nil, fmt.Errorf("creating certificate chain cache configmap: %w", err)
|
|
}
|
|
} else {
|
|
// ConfigMap already exists but either ASK or ARK are missing. Update the according value.
|
|
if cacheAsk == nil {
|
|
if err := c.kubeClient.UpdateConfigMap(ctx, constants.SevSnpCertCacheConfigMapName,
|
|
constants.CertCacheAskKey, string(askPem)); err != nil {
|
|
return nil, nil, fmt.Errorf("updating ASK in certificate chain cache configmap: %w", err)
|
|
}
|
|
}
|
|
if cacheArk == nil {
|
|
if err := c.kubeClient.UpdateConfigMap(ctx, constants.SevSnpCertCacheConfigMapName,
|
|
constants.CertCacheArkKey, string(arkPem)); err != nil {
|
|
return nil, nil, fmt.Errorf("updating ARK in certificate chain cache configmap: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return ask, ark, nil
|
|
}
|
|
|
|
// getCertChainCache returns the cached ASK and ARK certificate, if available. If either of the keys
|
|
// is not present in the configmap, no error is returned.
|
|
func (c *Client) getCertChainCache(ctx context.Context) (ask, ark *x509.Certificate, err error) {
|
|
c.log.Debugf("Retrieving certificate chain from cache")
|
|
askRaw, err := c.kubeClient.GetConfigMapData(ctx, constants.SevSnpCertCacheConfigMapName, constants.CertCacheAskKey)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("getting ASK from configmap: %w", err)
|
|
}
|
|
if askRaw != "" {
|
|
c.log.Debugf("ASK cache hit")
|
|
ask, err = crypto.PemToX509Cert([]byte(askRaw))
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("parsing ASK: %w", err)
|
|
}
|
|
}
|
|
|
|
arkRaw, err := c.kubeClient.GetConfigMapData(ctx, constants.SevSnpCertCacheConfigMapName, constants.CertCacheArkKey)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("getting ARK from configmap: %w", err)
|
|
}
|
|
if arkRaw != "" {
|
|
c.log.Debugf("ARK cache hit")
|
|
ark, err = crypto.PemToX509Cert([]byte(arkRaw))
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("parsing ARK: %w", err)
|
|
}
|
|
}
|
|
|
|
return ask, ark, nil
|
|
}
|
|
|
|
type kubeClient interface {
|
|
CreateConfigMap(ctx context.Context, name string, data map[string]string) error
|
|
GetConfigMapData(ctx context.Context, name, key string) (string, error)
|
|
UpdateConfigMap(ctx context.Context, name, key, value string) error
|
|
}
|
|
|
|
type kdsClient interface {
|
|
CertChain(signingType abi.ReportSigner) (ask, ark *x509.Certificate, err error)
|
|
}
|