constellation/joinservice/internal/certcache/certcache_test.go
Moritz Sanft a5021c52d3
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 14:29:50 +02:00

248 lines
6.0 KiB
Go

/*
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
}