The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/storage/secret/metadata/decrypt_store_test.go

320 lines
10 KiB

package metadata_test
import (
"context"
"testing"
"github.com/grafana/authlib/authn"
"github.com/grafana/authlib/types"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
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/testutils"
)
func TestIntegrationDecrypt(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
t.Parallel()
t.Run("when no auth info is present, it returns an error", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
sut := testutils.Setup(t)
exposed, err := sut.DecryptStorage.Decrypt(ctx, "default", "name")
require.Error(t, err)
require.Empty(t, exposed)
})
t.Run("when secure value cannot be found, it returns an error", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
// Create auth context with proper permissions
authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/group1:decrypt"}, "svc", types.TypeUser)
sut := testutils.Setup(t, testutils.WithMutateCfg(func(sc *testutils.SetupConfig) {
sc.AllowList = map[string]struct{}{"group1": {}}
}))
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", "non-existent-value")
require.ErrorIs(t, err, contracts.ErrDecryptNotFound)
require.Empty(t, exposed)
})
t.Run("when auth info is not in allowlist, it returns an unauthorized error", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
svName := "sv-test"
svcIdentity := "svc"
// Create auth context with identity that is not in allowlist
authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/" + svName + ":decrypt"}, svcIdentity, types.TypeUser)
// Create an allowlist that doesn't include the permission
allowList := map[string]struct{}{"allowed-group": {}}
// Setup service
sut := testutils.Setup(t, testutils.WithMutateCfg(func(sc *testutils.SetupConfig) {
sc.AllowList = allowList
}))
// Create a secure value that is not in the allowlist
spec := secretv0alpha1.SecureValueSpec{
Description: "description",
Decrypters: []string{svcIdentity},
Value: secretv0alpha1.NewExposedSecureValue("value"),
}
sv := &secretv0alpha1.SecureValue{Spec: spec}
sv.Name = svName
sv.Namespace = "default"
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
require.NoError(t, err)
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", svName)
require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized)
require.Empty(t, exposed)
})
t.Run("when happy path with valid auth and permissions, it returns decrypted value", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
svcIdentity := "svc"
// Create auth context with proper permissions that match the decrypters
authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues:decrypt"}, svcIdentity, types.TypeUser)
// Include the group in allowlist
allowList := map[string]struct{}{svcIdentity: {}}
// Setup service
sut := testutils.Setup(t, testutils.WithMutateCfg(func(sc *testutils.SetupConfig) {
sc.AllowList = allowList
}))
// Create a secure value that is in the allowlist
spec := secretv0alpha1.SecureValueSpec{
Description: "description",
Decrypters: []string{svcIdentity},
Value: secretv0alpha1.NewExposedSecureValue("value"),
}
sv := &secretv0alpha1.SecureValue{Spec: spec}
sv.Name = "sv-test"
sv.Namespace = "default"
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
require.NoError(t, err)
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", "sv-test")
require.NoError(t, err)
require.NotEmpty(t, exposed)
require.Equal(t, "value", exposed.DangerouslyExposeAndConsumeValue())
})
t.Run("with permissions for a specific secure value but trying to decrypt another one, it returns unauthorized error", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
svName := "sv-test"
svcIdentity := "svc"
// Create auth context with proper permissions that match the decrypters
authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/sv-test2:decrypt"}, svcIdentity, types.TypeUser)
// Include the group in allowlist
allowList := map[string]struct{}{svcIdentity: {}}
// Setup service
sut := testutils.Setup(t, testutils.WithMutateCfg(func(sc *testutils.SetupConfig) {
sc.AllowList = allowList
}))
// Create a secure value that is in the allowlist
spec := secretv0alpha1.SecureValueSpec{
Description: "description",
Decrypters: []string{svcIdentity},
Value: secretv0alpha1.NewExposedSecureValue("value"),
}
sv := &secretv0alpha1.SecureValue{Spec: spec}
sv.Name = svName
sv.Namespace = "default"
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
require.NoError(t, err)
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", svName)
require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized)
require.Empty(t, exposed)
})
t.Run("when permission format is malformed (no verb), it returns unauthorized error", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
svcIdentity := "svc"
// Create auth context with malformed permission (no verb)
authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues"}, svcIdentity, types.TypeUser)
// Setup service
sut := testutils.Setup(t)
// Create a secure value
spec := secretv0alpha1.SecureValueSpec{
Description: "description",
Decrypters: []string{svcIdentity},
Value: secretv0alpha1.NewExposedSecureValue("value"),
}
sv := &secretv0alpha1.SecureValue{Spec: spec}
sv.Name = "sv-test"
sv.Namespace = "default"
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
require.NoError(t, err)
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", "sv-test")
require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized)
require.Empty(t, exposed)
})
t.Run("when permission verb is not 'decrypt', it returns unauthorized error", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
svName := "sv-test"
svcIdentity := "svc"
// Create auth context with wrong verb
authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/" + svName + ":read"}, svcIdentity, types.TypeUser)
// Setup service
sut := testutils.Setup(t)
// Create a secure value
spec := secretv0alpha1.SecureValueSpec{
Description: "description",
Decrypters: []string{svcIdentity},
Value: secretv0alpha1.NewExposedSecureValue("value"),
}
sv := &secretv0alpha1.SecureValue{Spec: spec}
sv.Name = svName
sv.Namespace = "default"
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
require.NoError(t, err)
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", svName)
require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized)
require.Empty(t, exposed)
})
t.Run("when permission has incorrect number of parts, it returns unauthorized error", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
svcIdentity := "svc"
// Create auth context with incorrect number of parts
authCtx := createAuthContext(ctx, "default", []string{"secret.grafana.app/securevalues/:decrypt"}, svcIdentity, types.TypeUser)
// Setup service
sut := testutils.Setup(t)
// Create a secure value
spec := secretv0alpha1.SecureValueSpec{
Description: "description",
Decrypters: []string{svcIdentity},
Value: secretv0alpha1.NewExposedSecureValue("value"),
}
sv := &secretv0alpha1.SecureValue{Spec: spec}
sv.Name = "sv-test"
sv.Namespace = "default"
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
require.NoError(t, err)
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", "sv-test")
require.ErrorIs(t, err, contracts.ErrDecryptNotAuthorized)
require.Empty(t, exposed)
})
t.Run("when permission has incorrect group or resource, it returns unauthorized error", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
svName := "sv-test"
svcIdentity := "svc"
// Create auth context with incorrect group
authCtx := createAuthContext(ctx, "default", []string{"wrong.group/securevalues/" + svName + ":decrypt"}, svcIdentity, types.TypeUser)
// Setup service
sut := testutils.Setup(t)
// Create a secure value
spec := secretv0alpha1.SecureValueSpec{
Description: "description",
Decrypters: []string{svcIdentity},
Value: secretv0alpha1.NewExposedSecureValue("value"),
}
sv := &secretv0alpha1.SecureValue{Spec: spec}
sv.Name = svName
sv.Namespace = "default"
_, err := sut.CreateSv(authCtx, testutils.CreateSvWithSv(sv))
require.NoError(t, err)
exposed, err := sut.DecryptStorage.Decrypt(authCtx, "default", svName)
require.Error(t, err)
require.Equal(t, err.Error(), "not authorized")
require.Empty(t, exposed)
})
// TODO: add more tests for keeper failure scenarios, lets see how the async work will change this though.
}
func createAuthContext(ctx context.Context, namespace string, permissions []string, svc string, identityType types.IdentityType) context.Context {
requester := &identity.StaticRequester{
Type: identityType,
Namespace: namespace,
AccessTokenClaims: &authn.Claims[authn.AccessTokenClaims]{
Rest: authn.AccessTokenClaims{
Permissions: permissions,
ServiceIdentity: svc,
},
},
}
if identityType == types.TypeUser {
requester.UserID = 1
}
return types.WithAuthInfo(ctx, requester)
}