mirror of https://github.com/grafana/grafana.git
Secrets: add crudl+decrypt state machine test (#107971)
* Secrets: add state machine test for CRUDL+decrpt operations * make update-workspace * make update-workspace * make enterprise-dev * make update-workspace * fix go.mod * make update-workspace * fix gomod * make update-workspace --------- Co-authored-by: Matheus Macabu <macabu.matheus@gmail.com>
This commit is contained in:
parent
39d7fbd66e
commit
9d0a23e1f5
1
go.mod
1
go.mod
|
@ -219,6 +219,7 @@ require (
|
|||
k8s.io/kube-aggregator v0.33.1 // @grafana/grafana-app-platform-squad
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // @grafana/grafana-app-platform-squad
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // @grafana/partner-datasources
|
||||
pgregory.net/rapid v1.2.0 // @grafana/grafana-operator-experience-squad
|
||||
sigs.k8s.io/randfill v1.0.0 // @grafana/grafana-app-platform-squad
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // @grafana-app-platform-squad
|
||||
xorm.io/builder v0.3.6 // @grafana/grafana-backend-group
|
||||
|
|
2
go.sum
2
go.sum
|
@ -3632,6 +3632,8 @@ modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
|
|
|
@ -1434,6 +1434,8 @@ modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJ
|
|||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao=
|
||||
modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
|
||||
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
|
||||
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
|
||||
|
|
|
@ -194,6 +194,7 @@ func (s *SecureValueService) Delete(ctx context.Context, namespace xkube.Namespa
|
|||
))
|
||||
defer span.End()
|
||||
|
||||
// TODO: does this need to be for update?
|
||||
sv, err := s.secureValueMetadataStorage.Read(ctx, namespace, name, contracts.ReadOpts{ForUpdate: true})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching secure value: %+w", err)
|
||||
|
|
|
@ -4,6 +4,9 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/authlib/authn"
|
||||
"github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
encryptionstorage "github.com/grafana/grafana/pkg/storage/secret/encryption"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
|
@ -70,7 +73,10 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut {
|
|||
|
||||
// Initialize access client + access control
|
||||
accessControl := &actest.FakeAccessControl{ExpectedEvaluate: true}
|
||||
accessClient := accesscontrol.NewLegacyAccessClient(accessControl)
|
||||
accessClient := accesscontrol.NewLegacyAccessClient(accessControl, accesscontrol.ResourceAuthorizerOptions{
|
||||
Resource: "securevalues",
|
||||
Attr: "uid",
|
||||
})
|
||||
|
||||
defaultKey := "SdlklWklckeLS"
|
||||
cfg := &setting.Cfg{
|
||||
|
@ -112,13 +118,22 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut {
|
|||
decryptStorage, err := metadata.ProvideDecryptStorage(features, tracer, keeperService, keeperMetadataStorage, secureValueMetadataStorage, decryptAuthorizer, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
return Sut{SecureValueService: secureValueService, SecureValueMetadataStorage: secureValueMetadataStorage, Database: database, DecryptStorage: decryptStorage}
|
||||
decryptService := decrypt.ProvideDecryptService(decryptStorage)
|
||||
|
||||
return Sut{
|
||||
SecureValueService: secureValueService,
|
||||
SecureValueMetadataStorage: secureValueMetadataStorage,
|
||||
Database: database,
|
||||
DecryptStorage: decryptStorage,
|
||||
DecryptService: decryptService,
|
||||
}
|
||||
}
|
||||
|
||||
type Sut struct {
|
||||
SecureValueService *service.SecureValueService
|
||||
SecureValueMetadataStorage contracts.SecureValueMetadataStorage
|
||||
DecryptStorage contracts.DecryptStorage
|
||||
DecryptService service.DecryptService
|
||||
Database *database.Database
|
||||
}
|
||||
|
||||
|
@ -142,6 +157,7 @@ func (s *Sut) CreateSv(ctx context.Context, opts ...func(*CreateSvConfig)) (*sec
|
|||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Description: "desc1",
|
||||
Value: secretv0alpha1.NewExposedSecureValue("v1"),
|
||||
Decrypters: []string{"decrypter1"},
|
||||
},
|
||||
Status: secretv0alpha1.SecureValueStatus{},
|
||||
},
|
||||
|
@ -150,7 +166,7 @@ func (s *Sut) CreateSv(ctx context.Context, opts ...func(*CreateSvConfig)) (*sec
|
|||
opt(&cfg)
|
||||
}
|
||||
|
||||
createdSv, err := s.SecureValueService.Create(ctx, cfg.Sv, "actor")
|
||||
createdSv, err := s.SecureValueService.Create(ctx, cfg.Sv, "actor-uid")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -158,7 +174,7 @@ func (s *Sut) CreateSv(ctx context.Context, opts ...func(*CreateSvConfig)) (*sec
|
|||
}
|
||||
|
||||
func (s *Sut) UpdateSv(ctx context.Context, sv *secretv0alpha1.SecureValue) (*secretv0alpha1.SecureValue, error) {
|
||||
newSv, _, err := s.SecureValueService.Update(ctx, sv, "actor")
|
||||
newSv, _, err := s.SecureValueService.Update(ctx, sv, "actor-uid")
|
||||
return newSv, err
|
||||
}
|
||||
|
||||
|
@ -178,3 +194,31 @@ func newKeeperServiceWrapper(keeper contracts.Keeper) *keeperServiceWrapper {
|
|||
func (wrapper *keeperServiceWrapper) KeeperForConfig(cfg secretv0alpha1.KeeperConfig) (contracts.Keeper, error) {
|
||||
return wrapper.keeper, nil
|
||||
}
|
||||
|
||||
func CreateUserAuthContext(ctx context.Context, namespace string, permissions map[string][]string) context.Context {
|
||||
orgID := int64(1)
|
||||
requester := &identity.StaticRequester{
|
||||
Namespace: namespace,
|
||||
Type: types.TypeUser,
|
||||
UserID: 1,
|
||||
OrgID: orgID,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
orgID: permissions,
|
||||
},
|
||||
}
|
||||
|
||||
return types.WithAuthInfo(ctx, requester)
|
||||
}
|
||||
|
||||
func CreateServiceAuthContext(ctx context.Context, serviceIdentity string, permissions []string) context.Context {
|
||||
requester := &identity.StaticRequester{
|
||||
AccessTokenClaims: &authn.Claims[authn.AccessTokenClaims]{
|
||||
Rest: authn.AccessTokenClaims{
|
||||
Permissions: permissions,
|
||||
ServiceIdentity: serviceIdentity,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return types.WithAuthInfo(ctx, requester)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,419 @@
|
|||
package metadata_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/service"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/testutils"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"pgregory.net/rapid"
|
||||
)
|
||||
|
||||
type modelSecureValue struct {
|
||||
*secretv0alpha1.SecureValue
|
||||
active bool
|
||||
}
|
||||
|
||||
// A simplified model of the grafana secrets manager
|
||||
type model struct {
|
||||
secureValues []*modelSecureValue
|
||||
}
|
||||
|
||||
func newModel() *model {
|
||||
return &model{}
|
||||
}
|
||||
|
||||
func (m *model) getNewVersionNumber(namespace, name string) int64 {
|
||||
latestVersion := int64(0)
|
||||
for _, sv := range m.secureValues {
|
||||
if sv.Namespace == namespace && sv.Name == name {
|
||||
latestVersion = max(latestVersion, sv.Status.Version)
|
||||
}
|
||||
}
|
||||
return latestVersion + 1
|
||||
}
|
||||
|
||||
func (m *model) setVersionToActive(namespace, name string, version int64) {
|
||||
for _, sv := range m.secureValues {
|
||||
if sv.Namespace == namespace && sv.Name == name {
|
||||
sv.active = sv.Status.Version == version
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) setVersionToInactive(namespace, name string, version int64) {
|
||||
for _, sv := range m.secureValues {
|
||||
if sv.Namespace == namespace && sv.Name == name && sv.Status.Version == version {
|
||||
sv.active = false
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) readActiveVersion(namespace, name string) *modelSecureValue {
|
||||
for _, sv := range m.secureValues {
|
||||
if sv.Namespace == namespace && sv.Name == name && sv.active {
|
||||
return sv
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *model) create(sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) {
|
||||
modelSv := &modelSecureValue{sv, false}
|
||||
modelSv.Status.Version = m.getNewVersionNumber(modelSv.Namespace, modelSv.Name)
|
||||
modelSv.Status.ExternalID = fmt.Sprintf("%d", modelSv.Status.Version)
|
||||
m.secureValues = append(m.secureValues, modelSv)
|
||||
m.setVersionToActive(modelSv.Namespace, modelSv.Name, modelSv.Status.Version)
|
||||
return modelSv.SecureValue, nil
|
||||
}
|
||||
|
||||
func (m *model) update(newSecureValue *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, bool, error) {
|
||||
// If the payload doesn't contain a value, get the value from current version
|
||||
if newSecureValue.Spec.Value == "" {
|
||||
sv := m.readActiveVersion(newSecureValue.Namespace, newSecureValue.Name)
|
||||
if sv == nil {
|
||||
return nil, false, contracts.ErrSecureValueNotFound
|
||||
}
|
||||
newSecureValue.Spec.Value = sv.Spec.Value
|
||||
}
|
||||
createdSv, err := m.create(newSecureValue, actorUID)
|
||||
return createdSv, true, err
|
||||
}
|
||||
|
||||
func (m *model) delete(namespace, name string) (*secretv0alpha1.SecureValue, error) {
|
||||
modelSv := m.readActiveVersion(namespace, name)
|
||||
if modelSv == nil {
|
||||
return nil, contracts.ErrSecureValueNotFound
|
||||
}
|
||||
m.setVersionToInactive(namespace, name, modelSv.Status.Version)
|
||||
return modelSv.SecureValue, nil
|
||||
}
|
||||
|
||||
func (m *model) list(namespace string) (*secretv0alpha1.SecureValueList, error) {
|
||||
out := make([]secretv0alpha1.SecureValue, 0)
|
||||
|
||||
for _, v := range m.secureValues {
|
||||
if v.Namespace == namespace && v.active {
|
||||
out = append(out, *v.SecureValue)
|
||||
}
|
||||
}
|
||||
|
||||
return &secretv0alpha1.SecureValueList{Items: out}, nil
|
||||
}
|
||||
|
||||
func (m *model) decrypt(decrypter, namespace, name string) (map[string]service.DecryptResult, error) {
|
||||
for _, v := range m.secureValues {
|
||||
if v.Namespace == namespace &&
|
||||
v.Name == name &&
|
||||
v.active {
|
||||
if slices.ContainsFunc(v.Spec.Decrypters, func(d string) bool { return d == decrypter }) {
|
||||
return map[string]service.DecryptResult{
|
||||
name: service.NewDecryptResultValue(&v.DeepCopy().Spec.Value),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return map[string]service.DecryptResult{
|
||||
name: service.NewDecryptResultErr(contracts.ErrDecryptNotAuthorized),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return map[string]service.DecryptResult{
|
||||
name: service.NewDecryptResultErr(contracts.ErrDecryptNotFound),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *model) read(namespace, name string) (*secretv0alpha1.SecureValue, error) {
|
||||
modelSv := m.readActiveVersion(namespace, name)
|
||||
if modelSv == nil {
|
||||
return nil, contracts.ErrSecureValueNotFound
|
||||
}
|
||||
return modelSv.SecureValue, nil
|
||||
}
|
||||
|
||||
var (
|
||||
decryptersGen = rapid.SampledFrom([]string{"svc1", "svc2", "svc3", "svc4", "svc5"})
|
||||
nameGen = rapid.SampledFrom([]string{"n1", "n2", "n3", "n4", "n5"})
|
||||
namespaceGen = rapid.SampledFrom([]string{"ns1", "ns2", "ns3", "ns4", "ns5"})
|
||||
anySecureValueGen = rapid.Custom(func(t *rapid.T) *secretv0alpha1.SecureValue {
|
||||
return &secretv0alpha1.SecureValue{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: nameGen.Draw(t, "name"),
|
||||
Namespace: namespaceGen.Draw(t, "ns"),
|
||||
},
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Description: rapid.SampledFrom([]string{"d1", "d2", "d3", "d4", "d5"}).Draw(t, "description"),
|
||||
Value: secretv0alpha1.NewExposedSecureValue(rapid.SampledFrom([]string{"v1", "v2", "v3", "v4", "v5"}).Draw(t, "value")),
|
||||
Decrypters: rapid.SliceOfDistinct(decryptersGen, func(v string) string { return v }).Draw(t, "decrypters"),
|
||||
},
|
||||
Status: secretv0alpha1.SecureValueStatus{},
|
||||
}
|
||||
})
|
||||
updateSecureValueGen = rapid.Custom(func(t *rapid.T) *secretv0alpha1.SecureValue {
|
||||
sv := anySecureValueGen.Draw(t, "sv")
|
||||
// Maybe update the secret value, maybe not
|
||||
if !rapid.Bool().Draw(t, "should_update_value") {
|
||||
sv.Spec.Value = ""
|
||||
}
|
||||
return sv
|
||||
})
|
||||
// Any secure value will do
|
||||
deleteSecureValueGen = anySecureValueGen
|
||||
decryptGen = rapid.Custom(func(t *rapid.T) decryptInput {
|
||||
return decryptInput{
|
||||
namespace: namespaceGen.Draw(t, "ns"),
|
||||
name: nameGen.Draw(t, "name"),
|
||||
decrypter: decryptersGen.Draw(t, "decrypter"),
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
type decryptInput struct {
|
||||
namespace string
|
||||
name string
|
||||
decrypter string
|
||||
}
|
||||
|
||||
func TestModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "sv1",
|
||||
Namespace: "ns1",
|
||||
},
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Description: "desc1",
|
||||
Value: secretv0alpha1.NewExposedSecureValue("v1"),
|
||||
Decrypters: []string{"decrypter1"},
|
||||
},
|
||||
Status: secretv0alpha1.SecureValueStatus{},
|
||||
}
|
||||
|
||||
t.Run("creating secure values", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := newModel()
|
||||
|
||||
// Create a secure value
|
||||
sv1, err := m.create(sv.DeepCopy(), "actor-uid")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sv.Namespace, sv1.Namespace)
|
||||
require.Equal(t, sv.Name, sv1.Name)
|
||||
require.EqualValues(t, 1, sv1.Status.Version)
|
||||
|
||||
// Create a new version of a secure value
|
||||
sv2, err := m.create(sv.DeepCopy(), "actor-uid")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sv.Namespace, sv2.Namespace)
|
||||
require.Equal(t, sv.Name, sv2.Name)
|
||||
require.EqualValues(t, 2, sv2.Status.Version)
|
||||
})
|
||||
|
||||
t.Run("updating secure values", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := newModel()
|
||||
|
||||
sv1, err := m.create(sv.DeepCopy(), "actor-uid")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a new version of a secure value by updating it
|
||||
sv2, _, err := m.update(sv1.DeepCopy(), "actor-uid")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sv.Namespace, sv2.Namespace)
|
||||
require.Equal(t, sv.Name, sv2.Name)
|
||||
require.EqualValues(t, 2, sv2.Status.Version)
|
||||
|
||||
// Try updating a secure value that doesn't exist without specifying a value for it
|
||||
sv3 := sv2.DeepCopy()
|
||||
sv3.Name = "i_dont_exist"
|
||||
sv3.Spec.Value = ""
|
||||
_, _, err = m.update(sv3, "actor-uid")
|
||||
require.ErrorIs(t, err, contracts.ErrSecureValueNotFound)
|
||||
|
||||
// Updating a value that doesn't exist creates a new version
|
||||
sv4 := sv3.DeepCopy()
|
||||
sv4.Name = "i_dont_exist"
|
||||
sv4.Spec.Value = secretv0alpha1.NewExposedSecureValue("sv4")
|
||||
sv4, _, err = m.update(sv4, "actor-uid")
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, sv4.Status.Version)
|
||||
})
|
||||
|
||||
t.Run("deleting a secure value", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := newModel()
|
||||
|
||||
sv1, err := m.create(sv.DeepCopy(), "actor-uid")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Deleting a secure value
|
||||
deletedSv, err := m.delete(sv1.Namespace, sv1.Name)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sv1.Namespace, deletedSv.Namespace)
|
||||
require.Equal(t, sv1.Name, deletedSv.Name)
|
||||
require.EqualValues(t, sv1.Status.Version, deletedSv.Status.Version)
|
||||
|
||||
// Deleting a secure value that doesn't exist results in an error
|
||||
_, err = m.delete(sv1.Namespace, sv1.Name)
|
||||
require.ErrorIs(t, err, contracts.ErrSecureValueNotFound)
|
||||
})
|
||||
|
||||
t.Run("listing secure values", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := newModel()
|
||||
|
||||
// No secure values exist yet
|
||||
list, err := m.list(sv.Namespace)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(list.Items))
|
||||
|
||||
// Create a secure value
|
||||
sv1, err := m.create(sv.DeepCopy(), "actor-uid")
|
||||
require.NoError(t, err)
|
||||
|
||||
// 1 secure value exists and it should be returned
|
||||
list, err = m.list(sv.Namespace)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(list.Items))
|
||||
require.Equal(t, sv1.Namespace, list.Items[0].Namespace)
|
||||
require.Equal(t, sv1.Name, list.Items[0].Name)
|
||||
require.EqualValues(t, sv1.Status.Version, list.Items[0].Status.Version)
|
||||
})
|
||||
|
||||
t.Run("decrypting secure values", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := newModel()
|
||||
|
||||
// Decrypting a secure value that does not exist
|
||||
result, err := m.decrypt("decrypter", "namespace", "name")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(result))
|
||||
require.Nil(t, result["name"].Value())
|
||||
require.ErrorIs(t, result["name"].Error(), contracts.ErrDecryptNotFound)
|
||||
|
||||
// Create a secure value
|
||||
sv1, err := m.create(sv.DeepCopy(), "actor-uid")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Decrypt the just created secure value
|
||||
result, err = m.decrypt(sv1.Spec.Decrypters[0], sv1.Namespace, sv1.Name)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(result))
|
||||
require.Nil(t, result[sv1.Name].Error())
|
||||
require.Equal(t, result[sv1.Name].Value().DangerouslyExposeAndConsumeValue(), sv.DeepCopy().Spec.Value.DangerouslyExposeAndConsumeValue())
|
||||
})
|
||||
}
|
||||
|
||||
func TestStateMachine(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := t
|
||||
|
||||
rapid.Check(t, func(t *rapid.T) {
|
||||
sut := testutils.Setup(tt)
|
||||
model := newModel()
|
||||
|
||||
t.Repeat(map[string]func(*rapid.T){
|
||||
"create": func(t *rapid.T) {
|
||||
sv := anySecureValueGen.Draw(t, "sv")
|
||||
modelCreatedSv, modelErr := model.create(sv.DeepCopy(), "actor-uid")
|
||||
|
||||
createdSv, err := sut.CreateSv(t.Context(), testutils.CreateSvWithSv(sv.DeepCopy()))
|
||||
if err != nil || modelErr != nil {
|
||||
require.ErrorIs(t, err, modelErr)
|
||||
return
|
||||
}
|
||||
require.Equal(t, modelCreatedSv.Namespace, createdSv.Namespace)
|
||||
require.Equal(t, modelCreatedSv.Name, createdSv.Name)
|
||||
require.Equal(t, modelCreatedSv.Status.Version, createdSv.Status.Version)
|
||||
},
|
||||
"update": func(t *rapid.T) {
|
||||
sv := updateSecureValueGen.Draw(t, "sv")
|
||||
modelCreatedSv, _, modelErr := model.update(sv.DeepCopy(), "actor-uid")
|
||||
createdSv, err := sut.UpdateSv(t.Context(), sv.DeepCopy())
|
||||
if err != nil || modelErr != nil {
|
||||
require.ErrorIs(t, err, modelErr)
|
||||
return
|
||||
}
|
||||
require.Equal(t, modelCreatedSv.Namespace, createdSv.Namespace)
|
||||
require.Equal(t, modelCreatedSv.Name, createdSv.Name)
|
||||
require.Equal(t, modelCreatedSv.Status.Version, createdSv.Status.Version)
|
||||
},
|
||||
"delete": func(t *rapid.T) {
|
||||
sv := deleteSecureValueGen.Draw(t, "sv")
|
||||
modelSv, modelErr := model.delete(sv.Namespace, sv.Name)
|
||||
deletedSv, err := sut.DeleteSv(t.Context(), sv.Namespace, sv.Name)
|
||||
if err != nil || modelErr != nil {
|
||||
require.ErrorIs(t, err, modelErr)
|
||||
return
|
||||
}
|
||||
require.Equal(t, modelSv.Namespace, deletedSv.Namespace)
|
||||
require.Equal(t, modelSv.Name, deletedSv.Name)
|
||||
require.Equal(t, modelSv.Status.Version, deletedSv.Status.Version)
|
||||
},
|
||||
"list": func(t *rapid.T) {
|
||||
sv := anySecureValueGen.Draw(t, "sv")
|
||||
authCtx := testutils.CreateUserAuthContext(t.Context(), sv.Namespace, map[string][]string{
|
||||
"securevalues:read": {"securevalues:uid:*"},
|
||||
})
|
||||
modelList, modelErr := model.list(sv.Namespace)
|
||||
list, err := sut.SecureValueService.List(authCtx, xkube.Namespace(sv.Namespace))
|
||||
if err != nil || modelErr != nil {
|
||||
require.ErrorIs(t, err, modelErr)
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, len(modelList.Items), len(list.Items))
|
||||
|
||||
// PERFORMANCE: The lists are always small
|
||||
for _, v1 := range modelList.Items {
|
||||
if !slices.ContainsFunc(list.Items, func(v2 secretv0alpha1.SecureValue) bool {
|
||||
return v2.Namespace == v1.Namespace && v2.Name == v1.Name && v2.Status.Version == v1.Status.Version
|
||||
}) {
|
||||
t.Fatalf("expected sut to return secure value ns=%+v name=%+v version=%+v in the result", v1.Namespace, v1.Name, v1.Status.Version)
|
||||
}
|
||||
}
|
||||
},
|
||||
"get": func(t *rapid.T) {
|
||||
sv := anySecureValueGen.Draw(t, "sv")
|
||||
modelSv, modelErr := model.read(sv.Namespace, sv.Name)
|
||||
readSv, err := sut.SecureValueService.Read(t.Context(), xkube.Namespace(sv.Namespace), sv.Name)
|
||||
if err != nil || modelErr != nil {
|
||||
require.ErrorIs(t, err, modelErr)
|
||||
return
|
||||
}
|
||||
require.Equal(t, modelSv.Namespace, readSv.Namespace)
|
||||
require.Equal(t, modelSv.Name, readSv.Name)
|
||||
require.Equal(t, modelSv.Status.Version, readSv.Status.Version)
|
||||
},
|
||||
"decrypt": func(t *rapid.T) {
|
||||
input := decryptGen.Draw(t, "decryptInput")
|
||||
authCtx := testutils.CreateServiceAuthContext(t.Context(), input.decrypter, []string{fmt.Sprintf("secret.grafana.app/securevalues/%+v:decrypt", input.name)})
|
||||
modelResult, modelErr := model.decrypt(input.decrypter, input.namespace, input.name)
|
||||
result, err := sut.DecryptService.Decrypt(authCtx, input.namespace, input.name)
|
||||
if err != nil || modelErr != nil {
|
||||
require.ErrorIs(t, err, modelErr)
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, len(modelResult), len(result))
|
||||
for name := range modelResult {
|
||||
require.Equal(t, modelResult[name].Value(), result[name].Value())
|
||||
require.Equal(t, modelResult[name].Error(), result[name].Error())
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue