mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-05-06 08:15:48 -04:00
joinservice: cache certificates for Azure SEV-SNP attestation (#2336)
* 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>
This commit is contained in:
parent
68d8b29335
commit
a5021c52d3
39 changed files with 1197 additions and 50 deletions
247
joinservice/internal/certcache/certcache_test.go
Normal file
247
joinservice/internal/certcache/certcache_test.go
Normal file
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package certcache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"testing"
|
||||
|
||||
"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/testdata"
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func TestCreateCertChainCache(t *testing.T) {
|
||||
notFoundErr := k8serrors.NewNotFound(schema.GroupResource{}, "test")
|
||||
|
||||
testCases := map[string]struct {
|
||||
kubeClient *stubKubeClient
|
||||
kdsClient *stubKdsClient
|
||||
expectedArk *x509.Certificate
|
||||
expectedAsk *x509.Certificate
|
||||
wantErr bool
|
||||
}{
|
||||
"available in configmap": {
|
||||
kubeClient: &stubKubeClient{
|
||||
askResponse: string(testdata.Ask),
|
||||
arkResponse: string(testdata.Ark),
|
||||
},
|
||||
kdsClient: &stubKdsClient{},
|
||||
expectedArk: mustParsePEM(testdata.Ark),
|
||||
expectedAsk: mustParsePEM(testdata.Ask),
|
||||
},
|
||||
"query from kds": {
|
||||
kubeClient: &stubKubeClient{
|
||||
getConfigMapDataErr: notFoundErr,
|
||||
},
|
||||
kdsClient: &stubKdsClient{
|
||||
askResponse: testdata.Ask,
|
||||
arkResponse: testdata.Ark,
|
||||
},
|
||||
expectedArk: mustParsePEM(testdata.Ark),
|
||||
expectedAsk: mustParsePEM(testdata.Ask),
|
||||
},
|
||||
"only replace uncached cert": {
|
||||
kubeClient: &stubKubeClient{
|
||||
askResponse: string(testdata.Ark), // on purpose
|
||||
},
|
||||
kdsClient: &stubKdsClient{
|
||||
arkResponse: testdata.Ark,
|
||||
askResponse: testdata.Ask,
|
||||
},
|
||||
expectedArk: mustParsePEM(testdata.Ark),
|
||||
expectedAsk: mustParsePEM(testdata.Ark), // on purpose
|
||||
},
|
||||
"only ask available in configmap": {
|
||||
kubeClient: &stubKubeClient{
|
||||
askResponse: string(testdata.Ask),
|
||||
},
|
||||
kdsClient: &stubKdsClient{
|
||||
arkResponse: testdata.Ark,
|
||||
},
|
||||
expectedArk: mustParsePEM(testdata.Ark),
|
||||
expectedAsk: mustParsePEM(testdata.Ask),
|
||||
},
|
||||
"only ark available in configmap": {
|
||||
kubeClient: &stubKubeClient{
|
||||
arkResponse: string(testdata.Ark),
|
||||
},
|
||||
kdsClient: &stubKdsClient{
|
||||
askResponse: testdata.Ask,
|
||||
},
|
||||
expectedArk: mustParsePEM(testdata.Ark),
|
||||
expectedAsk: mustParsePEM(testdata.Ask),
|
||||
},
|
||||
"get config map data err": {
|
||||
kubeClient: &stubKubeClient{
|
||||
getConfigMapDataErr: assert.AnError,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"update configmap err": {
|
||||
kubeClient: &stubKubeClient{
|
||||
askResponse: string(testdata.Ask),
|
||||
updateConfigMapErr: assert.AnError,
|
||||
},
|
||||
kdsClient: &stubKdsClient{
|
||||
arkResponse: testdata.Ark,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"kds cert chain err": {
|
||||
kubeClient: &stubKubeClient{
|
||||
getConfigMapDataErr: notFoundErr,
|
||||
},
|
||||
kdsClient: &stubKdsClient{
|
||||
certChainErr: assert.AnError,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
c := &Client{
|
||||
attVariant: variant.Dummy{},
|
||||
log: logger.NewTest(t),
|
||||
kubeClient: tc.kubeClient,
|
||||
kdsClient: tc.kdsClient,
|
||||
}
|
||||
|
||||
ask, ark, err := c.createCertChainCache(ctx, abi.NoneReportSigner)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.expectedArk, ark)
|
||||
assert.Equal(tc.expectedAsk, ask)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubKdsClient struct {
|
||||
askResponse []byte
|
||||
arkResponse []byte
|
||||
certChainErr error
|
||||
}
|
||||
|
||||
func (c *stubKdsClient) CertChain(abi.ReportSigner) (ask, ark *x509.Certificate, err error) {
|
||||
if c.askResponse != nil {
|
||||
ask = mustParsePEM(c.askResponse)
|
||||
}
|
||||
if c.arkResponse != nil {
|
||||
ark = mustParsePEM(c.arkResponse)
|
||||
}
|
||||
return ask, ark, c.certChainErr
|
||||
}
|
||||
|
||||
func mustParsePEM(pemBytes []byte) *x509.Certificate {
|
||||
cert, err := crypto.PemToX509Cert(pemBytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cert
|
||||
}
|
||||
|
||||
func TestGetCertChainCache(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
kubeClient *stubKubeClient
|
||||
expectedAsk *x509.Certificate
|
||||
expectedArk *x509.Certificate
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
kubeClient: &stubKubeClient{
|
||||
askResponse: string(testdata.Ask),
|
||||
arkResponse: string(testdata.Ark),
|
||||
},
|
||||
expectedAsk: mustParsePEM(testdata.Ask),
|
||||
expectedArk: mustParsePEM(testdata.Ark),
|
||||
},
|
||||
"empty ask": {
|
||||
kubeClient: &stubKubeClient{
|
||||
askResponse: "",
|
||||
arkResponse: string(testdata.Ark),
|
||||
},
|
||||
expectedAsk: nil,
|
||||
expectedArk: mustParsePEM(testdata.Ark),
|
||||
},
|
||||
"empty ark": {
|
||||
kubeClient: &stubKubeClient{
|
||||
askResponse: string(testdata.Ask),
|
||||
arkResponse: "",
|
||||
},
|
||||
expectedAsk: mustParsePEM(testdata.Ask),
|
||||
expectedArk: nil,
|
||||
},
|
||||
"error getting config map data": {
|
||||
kubeClient: &stubKubeClient{
|
||||
getConfigMapDataErr: assert.AnError,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
c := NewClient(logger.NewTest(t), tc.kubeClient, variant.Dummy{})
|
||||
|
||||
ask, ark, err := c.getCertChainCache(ctx)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.expectedArk, ark)
|
||||
assert.Equal(tc.expectedAsk, ask)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubKubeClient struct {
|
||||
askResponse string
|
||||
arkResponse string
|
||||
createConfigMapErr error
|
||||
updateConfigMapErr error
|
||||
getConfigMapDataErr error
|
||||
}
|
||||
|
||||
func (s *stubKubeClient) CreateConfigMap(context.Context, string, map[string]string) error {
|
||||
return s.createConfigMapErr
|
||||
}
|
||||
|
||||
func (s *stubKubeClient) GetConfigMapData(_ context.Context, _ string, key string) (string, error) {
|
||||
if key == constants.CertCacheAskKey {
|
||||
return s.askResponse, s.getConfigMapDataErr
|
||||
}
|
||||
if key == constants.CertCacheArkKey {
|
||||
return s.arkResponse, s.getConfigMapDataErr
|
||||
}
|
||||
return "", s.getConfigMapDataErr
|
||||
}
|
||||
|
||||
func (s *stubKubeClient) UpdateConfigMap(context.Context, string, string, string) error {
|
||||
return s.updateConfigMapErr
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue