constellation/joinservice/internal/certcache/certcache_test.go

248 lines
6.0 KiB
Go
Raw Normal View History

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>
2023-09-29 08:29:50 -04:00
/*
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
}