SecretsManager: Unify KMS across OSS and Enterprise (#108085)
* everything is compiling * tests passing * remove used object * write a test for secret key upgrades * misc cleanup * clean up some wording * lint issues * fix a typo * import hashicorp dependency explicitly * simplify oss kmsprovider package structure * consolidate current provider and available providers * add a new manager configuration test * fix hashivault import * fix import issue * fix unit tests * Update go.mod Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com> --------- Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com>
This commit is contained in:
co-authored by
Matheus Macabu
parent
469f94028f
commit
ab51794bdb
+5
-5
@@ -2147,12 +2147,12 @@ alert_rules_state = "paused"
|
||||
|
||||
###################################### Secrets Manager ######################################
|
||||
[secrets_manager]
|
||||
# Used for signing
|
||||
# Current key provider used for envelope encryption
|
||||
encryption_provider = secret_key.v1
|
||||
|
||||
[secrets_manager.encryption.secret_key.v1]
|
||||
# Used to encrypt data keys
|
||||
secret_key = SW2YcwTIb9zpOOhoPsMm
|
||||
# Current key provider used for envelope encryption, default to static value specified by secret_key
|
||||
encryption_provider = secretKey.v1
|
||||
# List of configured key providers, space separated (Enterprise only): e.g., awskms.v1 azurekv.v1
|
||||
available_encryption_providers =
|
||||
|
||||
################################## Frontend development configuration ###################################
|
||||
# Warning! Any settings placed in this section will be available on `process.env.frontend_dev_{foo}` within frontend code
|
||||
|
||||
@@ -194,6 +194,7 @@ require (
|
||||
go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage
|
||||
go.uber.org/zap v1.27.0 // @grafana/identity-access-team
|
||||
gocloud.dev v0.42.0 // @grafana/grafana-app-platform-squad
|
||||
gocloud.dev/secrets/hashivault v0.42.0 // @grafana/grafana-operator-experience-squad
|
||||
golang.org/x/crypto v0.39.0 // @grafana/grafana-backend-group
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // @grafana/alerting-backend
|
||||
golang.org/x/mod v0.25.0 // indirect; @grafana/grafana-backend-group
|
||||
@@ -407,12 +408,17 @@ require (
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-metrics v0.5.4 // indirect
|
||||
github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/memberlist v0.5.2 // indirect
|
||||
github.com/hashicorp/serf v0.10.2 // indirect
|
||||
github.com/hashicorp/vault/api v1.16.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
@@ -501,6 +507,7 @@ require (
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
|
||||
@@ -1719,6 +1719,10 @@ github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa
|
||||
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
|
||||
@@ -1740,6 +1744,7 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP
|
||||
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/hcl/v2 v2.17.0 h1:z1XvSUyXd1HP10U4lrLg5e0JMVz6CPaJvAgxM0KNZVY=
|
||||
github.com/hashicorp/hcl/v2 v2.17.0/go.mod h1:gJyW2PTShkJqQBKpAmPO3yxMxIuoXkOF2TpqXzrQyx4=
|
||||
@@ -1760,6 +1765,8 @@ github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpT
|
||||
github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||
github.com/hashicorp/serf v0.10.2 h1:m5IORhuNSjaxeljg5DeQVDlQyVkhRIjJDimbkCa8aAc=
|
||||
github.com/hashicorp/serf v0.10.2/go.mod h1:T1CmSGfSeGfnfNy/w0odXQUR1rfECGd2Qdsp84DjOiY=
|
||||
github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4=
|
||||
github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.19.1 h1:UU/7h3uc/rdgspM8xkQF7wokmwZXePWDXcLqrQRRzzY=
|
||||
@@ -2295,6 +2302,8 @@ github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfF
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
|
||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
@@ -2634,6 +2643,8 @@ go4.org/netipx v0.0.0-20230125063823-8449b0a6169f h1:ketMxHg+vWm3yccyYiq+uK8D3fR
|
||||
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc=
|
||||
gocloud.dev v0.42.0 h1:qzG+9ItUL3RPB62/Amugws28n+4vGZXEoJEAMfjutzw=
|
||||
gocloud.dev v0.42.0/go.mod h1:zkaYAapZfQisXOA4bzhsbA4ckiStGQ3Psvs9/OQ5dPM=
|
||||
gocloud.dev/secrets/hashivault v0.42.0 h1:kPWIIu1AP6ApHf7HwUrzGrwyRJEfTd46JNUNgnbpsFA=
|
||||
gocloud.dev/secrets/hashivault v0.42.0/go.mod h1:LXprr1XLEAT7BVZ+Y66dJEHQMzDsowIExj5Ktr9HLvM=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
|
||||
+2
-1
@@ -982,7 +982,6 @@ github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6
|
||||
github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4=
|
||||
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
|
||||
github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
|
||||
github.com/hashicorp/mdns v1.0.5 h1:1M5hW1cunYeoXOqHwEb/GBDDHAFo0Yqb/uz/beC6LbE=
|
||||
github.com/hashicorp/mdns v1.0.5/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
@@ -1123,6 +1122,8 @@ github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt
|
||||
github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I=
|
||||
github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg=
|
||||
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8 h1:P48LjvUQpTReR3TQRbxSeSBsMXzfK0uol7eRcr7VBYQ=
|
||||
github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
|
||||
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
|
||||
github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 h1:dnMxwus89s86tI8rcGVp2HwZzlz7c5o92VOy7dSckBQ=
|
||||
github.com/ncw/swift v1.0.53 h1:luHjjTNtekIEvHg5KdAFIBaH7bWfNkefwFnpDffSIks=
|
||||
github.com/ncw/swift v1.0.53/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
_ "github.com/russellhaering/goxmldsig"
|
||||
_ "github.com/spf13/cobra" // used by the standalone apiserver cli
|
||||
_ "github.com/stretchr/testify/require"
|
||||
_ "gocloud.dev/secrets/hashivault"
|
||||
_ "golang.org/x/time/rate"
|
||||
_ "k8s.io/api"
|
||||
_ "k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
|
||||
@@ -14,20 +14,16 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/provider"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
encryptionAlgorithmDelimiter = '*'
|
||||
)
|
||||
|
||||
// Service must not be used for cipher.
|
||||
// Use secrets.Service implementing envelope encryption instead.
|
||||
type Service struct {
|
||||
type cipherService struct {
|
||||
tracer trace.Tracer
|
||||
log log.Logger
|
||||
|
||||
cfg *setting.Cfg
|
||||
usageMetrics usagestats.Service
|
||||
|
||||
cipher cipher.Encrypter
|
||||
@@ -35,16 +31,13 @@ type Service struct {
|
||||
algorithm string
|
||||
}
|
||||
|
||||
func NewEncryptionService(
|
||||
// ProvideAESGCMCipherService provides an AES-GCM cipher for encryption and decryption.
|
||||
// It should not be used to encrypt payloads directly, as it is intended to encrypt data keys for envelope encryption.
|
||||
func ProvideAESGCMCipherService(
|
||||
tracer trace.Tracer,
|
||||
usageMetrics usagestats.Service,
|
||||
cfg *setting.Cfg,
|
||||
) (*Service, error) {
|
||||
if cfg.SecretsManagement.SecretKey == "" {
|
||||
return nil, fmt.Errorf("`[secrets_manager]secret_key` is not set")
|
||||
}
|
||||
|
||||
s := &Service{
|
||||
) (cipher.Cipher, error) {
|
||||
s := &cipherService{
|
||||
tracer: tracer,
|
||||
log: log.New("encryption"),
|
||||
|
||||
@@ -55,7 +48,6 @@ func NewEncryptionService(
|
||||
algorithm: provider.AesGcm,
|
||||
|
||||
usageMetrics: usageMetrics,
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
s.registerUsageMetrics()
|
||||
@@ -63,7 +55,7 @@ func NewEncryptionService(
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Service) registerUsageMetrics() {
|
||||
func (s *cipherService) registerUsageMetrics() {
|
||||
s.usageMetrics.RegisterMetricsFunc(func(context.Context) (map[string]any, error) {
|
||||
return map[string]any{
|
||||
fmt.Sprintf("stats.%s.encryption.cipher.%s.count", encryption.UsageInsightsPrefix, s.algorithm): 1,
|
||||
@@ -71,7 +63,7 @@ func (s *Service) registerUsageMetrics() {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) Decrypt(ctx context.Context, payload []byte, secret string) ([]byte, error) {
|
||||
func (s *cipherService) Decrypt(ctx context.Context, payload []byte, secret string) ([]byte, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "CipherService.Decrypt")
|
||||
defer span.End()
|
||||
|
||||
@@ -99,7 +91,7 @@ func (s *Service) Decrypt(ctx context.Context, payload []byte, secret string) ([
|
||||
return decrypted, err
|
||||
}
|
||||
|
||||
func (s *Service) deriveEncryptionAlgorithm(payload []byte) (string, []byte, error) {
|
||||
func (s *cipherService) deriveEncryptionAlgorithm(payload []byte) (string, []byte, error) {
|
||||
if len(payload) == 0 {
|
||||
return "", nil, fmt.Errorf("unable to derive encryption algorithm")
|
||||
}
|
||||
@@ -120,7 +112,7 @@ func (s *Service) deriveEncryptionAlgorithm(payload []byte) (string, []byte, err
|
||||
return string(algorithm), payload, nil
|
||||
}
|
||||
|
||||
func (s *Service) Encrypt(ctx context.Context, payload []byte, secret string) ([]byte, error) {
|
||||
func (s *cipherService) Encrypt(ctx context.Context, payload []byte, secret string) ([]byte, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "CipherService.Encrypt")
|
||||
defer span.End()
|
||||
|
||||
|
||||
@@ -8,21 +8,14 @@ import (
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher"
|
||||
)
|
||||
|
||||
func newGcmService(t *testing.T) *Service {
|
||||
func newGcmService(t *testing.T) cipher.Cipher {
|
||||
t.Helper()
|
||||
|
||||
usageStats := &usagestats.UsageStatsMock{}
|
||||
settings := &setting.Cfg{
|
||||
SecretsManagement: setting.SecretsManagerSettings{
|
||||
SecretKey: "SdlklWklckeLS",
|
||||
EncryptionProvider: "secretKey.v1",
|
||||
},
|
||||
}
|
||||
|
||||
svc, err := NewEncryptionService(noop.NewTracerProvider().Tracer("test"), usageStats, settings)
|
||||
svc, err := ProvideAESGCMCipherService(noop.NewTracerProvider().Tracer("test"), usageStats)
|
||||
require.NoError(t, err, "failed to set up encryption service")
|
||||
return svc
|
||||
}
|
||||
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
package defaultprovider
|
||||
package kmsproviders
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -7,22 +7,22 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher"
|
||||
)
|
||||
|
||||
type grafanaProvider struct {
|
||||
type secretKeyProvider struct {
|
||||
sk string
|
||||
encryption cipher.Cipher
|
||||
}
|
||||
|
||||
func New(sk string, encryption cipher.Cipher) encryption.Provider {
|
||||
return grafanaProvider{
|
||||
func newSecretKeyProvider(sk string, encryption cipher.Cipher) encryption.Provider {
|
||||
return secretKeyProvider{
|
||||
sk: sk,
|
||||
encryption: encryption,
|
||||
}
|
||||
}
|
||||
|
||||
func (p grafanaProvider) Encrypt(ctx context.Context, blob []byte) ([]byte, error) {
|
||||
func (p secretKeyProvider) Encrypt(ctx context.Context, blob []byte) ([]byte, error) {
|
||||
return p.encryption.Encrypt(ctx, blob, p.sk)
|
||||
}
|
||||
|
||||
func (p grafanaProvider) Decrypt(ctx context.Context, blob []byte) ([]byte, error) {
|
||||
func (p secretKeyProvider) Decrypt(ctx context.Context, blob []byte) ([]byte, error) {
|
||||
return p.encryption.Decrypt(ctx, blob, p.sk)
|
||||
}
|
||||
@@ -1,19 +1,40 @@
|
||||
package kmsproviders
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders/defaultprovider"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
// Default is the identifier of the default kms provider which fallbacks to the configured secret_key
|
||||
Default = "secretKey.v1"
|
||||
// OSSProviderType is the identifier of the default kms provider which fallbacks to the configured secret_key
|
||||
OSSProviderType = "secret_key"
|
||||
// SecretKeyKey is the key in the section that contains the secret key
|
||||
SecretKeyKey = "secret_key"
|
||||
)
|
||||
|
||||
func GetOSSKMSProviders(cfg *setting.Cfg, enc cipher.Cipher) encryption.ProviderMap {
|
||||
return encryption.ProviderMap{
|
||||
Default: defaultprovider.New(cfg.SecretsManagement.SecretKey, enc),
|
||||
// ProvideOSSKMSProviders provides the ProviderConfig expected by the encryption manager in the OSS wire configuration.
|
||||
// It looks for all configured 'secret_key' sections and creates a separate provider for each, each with its own secret key, allowing users to upgrade their secret key without breaking existing secrets.
|
||||
func ProvideOSSKMSProviders(cfg *setting.Cfg, cipher cipher.Cipher) (encryption.ProviderConfig, error) {
|
||||
pCfg := encryption.ProviderConfig{
|
||||
CurrentProvider: encryption.ProviderID(cfg.SecretsManagement.CurrentEncryptionProvider),
|
||||
AvailableProviders: make(encryption.ProviderMap),
|
||||
}
|
||||
|
||||
// Look through the available secret_key providers and add them to the map
|
||||
for providerName, properties := range cfg.SecretsManagement.ConfiguredKMSProviders {
|
||||
if strings.HasPrefix(providerName, OSSProviderType) {
|
||||
secretKey := properties[SecretKeyKey]
|
||||
if secretKey != "" {
|
||||
pCfg.AvailableProviders[encryption.ProviderID(providerName)] = newSecretKeyProvider(secretKey, cipher)
|
||||
} else {
|
||||
return pCfg, fmt.Errorf("missing secret_key for provider %s", providerName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pCfg, nil
|
||||
}
|
||||
|
||||
@@ -20,9 +20,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/service"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@@ -33,16 +30,14 @@ const (
|
||||
type EncryptionManager struct {
|
||||
tracer trace.Tracer
|
||||
store contracts.DataKeyStorage
|
||||
enc cipher.Cipher
|
||||
cfg *setting.Cfg
|
||||
usageStats usagestats.Service
|
||||
|
||||
mtx sync.Mutex
|
||||
|
||||
pOnce sync.Once
|
||||
providers encryption.ProviderMap
|
||||
|
||||
currentProviderID encryption.ProviderID
|
||||
// The cipher is used to encrypt and decrypt payloads with a data key.
|
||||
cipher cipher.Cipher
|
||||
// The providerConfig are used to encrypt and decrypt the data keys.
|
||||
providerConfig encryption.ProviderConfig
|
||||
|
||||
log log.Logger
|
||||
}
|
||||
@@ -51,33 +46,22 @@ type EncryptionManager struct {
|
||||
func ProvideEncryptionManager(
|
||||
tracer trace.Tracer,
|
||||
store contracts.DataKeyStorage,
|
||||
cfg *setting.Cfg,
|
||||
usageStats usagestats.Service,
|
||||
thirdPartyKMS encryption.ProviderMap,
|
||||
enc cipher.Cipher,
|
||||
providerConfig encryption.ProviderConfig,
|
||||
) (contracts.EncryptionManager, error) {
|
||||
currentProviderID := encryption.ProviderID(cfg.SecretsManagement.EncryptionProvider)
|
||||
|
||||
enc, err := service.NewEncryptionService(tracer, usageStats, cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create encryption service: %w", err)
|
||||
currentProviderID := providerConfig.CurrentProvider
|
||||
if _, ok := providerConfig.AvailableProviders[currentProviderID]; !ok {
|
||||
return nil, fmt.Errorf("missing configuration for current encryption provider %s", currentProviderID)
|
||||
}
|
||||
|
||||
s := &EncryptionManager{
|
||||
tracer: tracer,
|
||||
store: store,
|
||||
cfg: cfg,
|
||||
usageStats: usageStats,
|
||||
enc: enc,
|
||||
currentProviderID: currentProviderID,
|
||||
log: log.New("encryption"),
|
||||
}
|
||||
|
||||
if err := s.InitProviders(thirdPartyKMS); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := s.providers[currentProviderID]; !ok {
|
||||
return nil, fmt.Errorf("missing configuration for current encryption provider %s", currentProviderID)
|
||||
tracer: tracer,
|
||||
store: store,
|
||||
usageStats: usageStats,
|
||||
cipher: enc,
|
||||
log: log.New("encryption"),
|
||||
providerConfig: providerConfig,
|
||||
}
|
||||
|
||||
s.registerUsageMetrics()
|
||||
@@ -85,44 +69,20 @@ func ProvideEncryptionManager(
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *EncryptionManager) InitProviders(extraProviders encryption.ProviderMap) (err error) {
|
||||
done := false
|
||||
s.pOnce.Do(func() {
|
||||
providers := kmsproviders.GetOSSKMSProviders(s.cfg, s.enc)
|
||||
|
||||
for id, p := range extraProviders {
|
||||
if _, exists := s.providers[id]; exists {
|
||||
err = fmt.Errorf("provider %s already registered", id)
|
||||
return
|
||||
}
|
||||
providers[id] = p
|
||||
}
|
||||
|
||||
s.providers = providers
|
||||
done = true
|
||||
})
|
||||
|
||||
if !done && err == nil {
|
||||
err = fmt.Errorf("providers were already initialized, no action taken")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *EncryptionManager) registerUsageMetrics() {
|
||||
s.usageStats.RegisterMetricsFunc(func(ctx context.Context) (map[string]any, error) {
|
||||
usageMetrics := make(map[string]any)
|
||||
|
||||
// Current provider
|
||||
kind, err := s.currentProviderID.Kind()
|
||||
kind, err := s.providerConfig.CurrentProvider.Kind()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encryptionManager.registerUsageMetrics: %w", err)
|
||||
}
|
||||
usageMetrics[fmt.Sprintf("stats.%s.encryption.current_provider.%s.count", encryption.UsageInsightsPrefix, kind)] = 1
|
||||
|
||||
// Count by kind
|
||||
countByKind := make(map[string]int, len(s.providers))
|
||||
for id := range s.providers {
|
||||
countByKind := make(map[string]int, len(s.providerConfig.AvailableProviders))
|
||||
for id := range s.providerConfig.AvailableProviders {
|
||||
kind, err := id.Kind()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encryptionManager.registerUsageMetrics: %w", err)
|
||||
@@ -161,7 +121,7 @@ func (s *EncryptionManager) Encrypt(ctx context.Context, namespace string, paylo
|
||||
}
|
||||
}()
|
||||
|
||||
label := encryption.KeyLabel(s.currentProviderID)
|
||||
label := encryption.KeyLabel(s.providerConfig.CurrentProvider)
|
||||
|
||||
var id string
|
||||
var dataKey []byte
|
||||
@@ -172,7 +132,7 @@ func (s *EncryptionManager) Encrypt(ctx context.Context, namespace string, paylo
|
||||
}
|
||||
|
||||
var encrypted []byte
|
||||
encrypted, err = s.enc.Encrypt(ctx, payload, string(dataKey))
|
||||
encrypted, err = s.cipher.Encrypt(ctx, payload, string(dataKey))
|
||||
if err != nil {
|
||||
s.log.Error("Failed to encrypt secret", "error", err)
|
||||
return nil, err
|
||||
@@ -235,7 +195,7 @@ func (s *EncryptionManager) dataKeyByLabel(ctx context.Context, namespace, label
|
||||
}
|
||||
|
||||
// 2.1 Find the encryption provider.
|
||||
provider, exists := s.providers[dataKey.Provider]
|
||||
provider, exists := s.providerConfig.AvailableProviders[dataKey.Provider]
|
||||
if !exists {
|
||||
return "", nil, fmt.Errorf("could not find encryption provider '%s'", dataKey.Provider)
|
||||
}
|
||||
@@ -264,9 +224,9 @@ func (s *EncryptionManager) newDataKey(ctx context.Context, namespace string, la
|
||||
}
|
||||
|
||||
// 2.1 Find the encryption provider.
|
||||
provider, exists := s.providers[s.currentProviderID]
|
||||
provider, exists := s.providerConfig.AvailableProviders[s.providerConfig.CurrentProvider]
|
||||
if !exists {
|
||||
return "", nil, fmt.Errorf("could not find encryption provider '%s'", s.currentProviderID)
|
||||
return "", nil, fmt.Errorf("could not find encryption provider '%s'", s.providerConfig.CurrentProvider)
|
||||
}
|
||||
|
||||
// 2.2 Encrypt the data key.
|
||||
@@ -282,7 +242,7 @@ func (s *EncryptionManager) newDataKey(ctx context.Context, namespace string, la
|
||||
Active: true,
|
||||
UID: id,
|
||||
Namespace: namespace,
|
||||
Provider: s.currentProviderID,
|
||||
Provider: s.providerConfig.CurrentProvider,
|
||||
EncryptedData: encrypted,
|
||||
Label: label,
|
||||
}
|
||||
@@ -351,7 +311,7 @@ func (s *EncryptionManager) Decrypt(ctx context.Context, namespace string, paylo
|
||||
}
|
||||
|
||||
var decrypted []byte
|
||||
decrypted, err = s.enc.Decrypt(ctx, payload, string(dataKey))
|
||||
decrypted, err = s.cipher.Decrypt(ctx, payload, string(dataKey))
|
||||
|
||||
return decrypted, err
|
||||
}
|
||||
@@ -384,7 +344,7 @@ func (s *EncryptionManager) dataKeyById(ctx context.Context, namespace, id strin
|
||||
}
|
||||
|
||||
// 2.1. Find the encryption provider.
|
||||
provider, exists := s.providers[dataKey.Provider]
|
||||
provider, exists := s.providerConfig.AvailableProviders[dataKey.Provider]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("could not find encryption provider '%s'", dataKey.Provider)
|
||||
}
|
||||
@@ -398,6 +358,6 @@ func (s *EncryptionManager) dataKeyById(ctx context.Context, namespace, id strin
|
||||
return decrypted, nil
|
||||
}
|
||||
|
||||
func (s *EncryptionManager) GetProviders() encryption.ProviderMap {
|
||||
return s.providers
|
||||
func (s *EncryptionManager) GetProviders() encryption.ProviderConfig {
|
||||
return s.providerConfig
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
@@ -14,6 +15,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/service"
|
||||
osskmsproviders "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -67,8 +70,8 @@ func TestEncryptionService_EnvelopeEncryption(t *testing.T) {
|
||||
reports, err := svc.usageStats.GetUsageReport(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, reports.Metrics["stats.secrets_manager.encryption.current_provider.secretKey.count"])
|
||||
assert.Equal(t, 1, reports.Metrics["stats.secrets_manager.encryption.providers.secretKey.count"])
|
||||
assert.Equal(t, 1, reports.Metrics["stats.secrets_manager.encryption.current_provider.secret_key.count"])
|
||||
assert.Equal(t, 1, reports.Metrics["stats.secrets_manager.encryption.providers.secret_key.count"])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -161,7 +164,7 @@ func TestEncryptionService_DataKeys(t *testing.T) {
|
||||
func TestEncryptionService_UseCurrentProvider(t *testing.T) {
|
||||
t.Run("When encryption_provider is not specified explicitly, should use 'secretKey' as a current provider", func(t *testing.T) {
|
||||
svc := setupTestService(t)
|
||||
assert.Equal(t, encryption.ProviderID("secretKey.v1"), svc.currentProviderID)
|
||||
assert.Equal(t, encryption.ProviderID("secret_key.v1"), svc.providerConfig.CurrentProvider)
|
||||
})
|
||||
|
||||
t.Run("Should use encrypt/decrypt methods of the current encryption provider", func(t *testing.T) {
|
||||
@@ -175,8 +178,8 @@ func TestEncryptionService_UseCurrentProvider(t *testing.T) {
|
||||
cfg := &setting.Cfg{
|
||||
Raw: raw,
|
||||
SecretsManagement: setting.SecretsManagerSettings{
|
||||
SecretKey: "sdDkslslld",
|
||||
EncryptionProvider: "secretKey.v1",
|
||||
CurrentEncryptionProvider: "secret_key.v1",
|
||||
ConfiguredKMSProviders: map[string]map[string]string{"secret_key.v1": {"secret_key": "SW2YcwTIb9zpOOhoPsMm"}},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -186,12 +189,19 @@ func TestEncryptionService_UseCurrentProvider(t *testing.T) {
|
||||
encryptionStore, err := encryptionstorage.ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
usageStats := &usagestats.UsageStatsMock{T: t}
|
||||
enc, err := service.ProvideAESGCMCipherService(tracer, usageStats)
|
||||
require.NoError(t, err)
|
||||
|
||||
ossProviders, err := osskmsproviders.ProvideOSSKMSProviders(cfg, enc)
|
||||
require.NoError(t, err)
|
||||
|
||||
encMgr, err := ProvideEncryptionManager(
|
||||
tracer,
|
||||
encryptionStore,
|
||||
cfg,
|
||||
&usagestats.UsageStatsMock{T: t},
|
||||
encryption.ProvideThirdPartyProviderMap(),
|
||||
usageStats,
|
||||
enc,
|
||||
ossProviders,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -199,8 +209,10 @@ func TestEncryptionService_UseCurrentProvider(t *testing.T) {
|
||||
|
||||
//override default provider with fake, and register the fake separately
|
||||
fake := &fakeProvider{}
|
||||
encryptionManager.providers[encryption.ProviderID("fakeProvider.v1")] = fake
|
||||
encryptionManager.currentProviderID = "fakeProvider.v1"
|
||||
encryptionManager.providerConfig.AvailableProviders = encryption.ProviderMap{
|
||||
encryption.ProviderID("fakeProvider.v1"): fake,
|
||||
}
|
||||
encryptionManager.providerConfig.CurrentProvider = encryption.ProviderID("fakeProvider.v1")
|
||||
|
||||
namespace := "test-namespace"
|
||||
encrypted, _ := encryptionManager.Encrypt(context.Background(), namespace, []byte{})
|
||||
@@ -212,21 +224,185 @@ func TestEncryptionService_UseCurrentProvider(t *testing.T) {
|
||||
svcDecryptMgr, err := ProvideEncryptionManager(
|
||||
tracer,
|
||||
encryptionStore,
|
||||
cfg,
|
||||
&usagestats.UsageStatsMock{T: t},
|
||||
encryption.ProvideThirdPartyProviderMap(),
|
||||
usageStats,
|
||||
enc,
|
||||
ossProviders,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
svcDecrypt := svcDecryptMgr.(*EncryptionManager)
|
||||
svcDecrypt.providers[encryption.ProviderID("fakeProvider.v1")] = fake
|
||||
svcDecrypt.currentProviderID = "fakeProvider.v1"
|
||||
svcDecrypt.providerConfig.AvailableProviders = encryption.ProviderMap{
|
||||
encryption.ProviderID("fakeProvider.v1"): fake,
|
||||
}
|
||||
svcDecrypt.providerConfig.CurrentProvider = encryption.ProviderID("fakeProvider.v1")
|
||||
|
||||
_, _ = svcDecrypt.Decrypt(context.Background(), namespace, encrypted)
|
||||
assert.True(t, fake.decryptCalled, "fake provider's decrypt should be called")
|
||||
})
|
||||
}
|
||||
|
||||
func TestEncryptionService_SecretKeyVersionUpgrade(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := "test-namespace"
|
||||
|
||||
// Generate random keys for testing
|
||||
oldKey := util.GenerateShortUID() + util.GenerateShortUID() // 32 chars
|
||||
newKey := util.GenerateShortUID() + util.GenerateShortUID() // 32 chars
|
||||
|
||||
t.Run("should encrypt with v1, upgrade to v2, encrypt with v2, and decrypt both", func(t *testing.T) {
|
||||
// Step 1: Set up v1 configuration
|
||||
cfgV1 := &setting.Cfg{
|
||||
SecretsManagement: setting.SecretsManagerSettings{
|
||||
CurrentEncryptionProvider: "secret_key.v1",
|
||||
ConfiguredKMSProviders: map[string]map[string]string{"secret_key.v1": {"secret_key": oldKey}},
|
||||
},
|
||||
}
|
||||
|
||||
testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
|
||||
tracer := noop.NewTracerProvider().Tracer("test")
|
||||
encryptionStore, err := encryptionstorage.ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
usageStats := &usagestats.UsageStatsMock{T: t}
|
||||
enc, err := service.ProvideAESGCMCipherService(tracer, usageStats)
|
||||
require.NoError(t, err)
|
||||
|
||||
ossProviders, err := osskmsproviders.ProvideOSSKMSProviders(cfgV1, enc)
|
||||
require.NoError(t, err)
|
||||
|
||||
svcV1, err := ProvideEncryptionManager(
|
||||
tracer,
|
||||
encryptionStore,
|
||||
usageStats,
|
||||
enc,
|
||||
ossProviders,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Step 2: Encrypt something with v1
|
||||
plaintext := []byte("secret data from v1")
|
||||
encryptedV1, err := svcV1.Encrypt(ctx, namespace, plaintext)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify v1 can decrypt its own data
|
||||
decryptedV1, err := svcV1.Decrypt(ctx, namespace, encryptedV1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, plaintext, decryptedV1)
|
||||
|
||||
// Verify current provider is v1
|
||||
encMgrV1 := svcV1.(*EncryptionManager)
|
||||
assert.Equal(t, encryption.ProviderID("secret_key.v1"), encMgrV1.providerConfig.CurrentProvider)
|
||||
|
||||
// Step 3: Create new configuration with v2 as current provider
|
||||
cfgV2 := &setting.Cfg{
|
||||
SecretsManagement: setting.SecretsManagerSettings{
|
||||
CurrentEncryptionProvider: "secret_key.v2",
|
||||
ConfiguredKMSProviders: map[string]map[string]string{
|
||||
"secret_key.v1": {"secret_key": oldKey},
|
||||
"secret_key.v2": {"secret_key": newKey},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Reinitialize service with v2 configuration (reuse same store)
|
||||
ossProvidersV2, err := osskmsproviders.ProvideOSSKMSProviders(cfgV2, enc)
|
||||
require.NoError(t, err)
|
||||
|
||||
svcV2, err := ProvideEncryptionManager(
|
||||
tracer,
|
||||
encryptionStore,
|
||||
usageStats,
|
||||
enc,
|
||||
ossProvidersV2,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Step 4: Ensure we can encrypt and decrypt with the new key (v2)
|
||||
newPlaintext := []byte("secret data from v2")
|
||||
encryptedV2, err := svcV2.Encrypt(ctx, namespace, newPlaintext)
|
||||
require.NoError(t, err)
|
||||
|
||||
decryptedV2, err := svcV2.Decrypt(ctx, namespace, encryptedV2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newPlaintext, decryptedV2)
|
||||
|
||||
// Verify current provider is v2
|
||||
encMgrV2 := svcV2.(*EncryptionManager)
|
||||
assert.Equal(t, encryption.ProviderID("secret_key.v2"), encMgrV2.providerConfig.CurrentProvider)
|
||||
|
||||
// Step 5: Ensure we can decrypt the old value encrypted with v1
|
||||
decryptedOldWithV2, err := svcV2.Decrypt(ctx, namespace, encryptedV1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, plaintext, decryptedOldWithV2)
|
||||
|
||||
// Verify both providers are available
|
||||
assert.Contains(t, encMgrV2.providerConfig.AvailableProviders, encryption.ProviderID("secret_key.v1"))
|
||||
assert.Contains(t, encMgrV2.providerConfig.AvailableProviders, encryption.ProviderID("secret_key.v2"))
|
||||
assert.Equal(t, 2, len(encMgrV2.providerConfig.AvailableProviders))
|
||||
})
|
||||
|
||||
t.Run("encrypting with v1 then removing the v1 config should cause decryption to fail", func(t *testing.T) {
|
||||
tracer := noop.NewTracerProvider().Tracer("test")
|
||||
testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
|
||||
encryptionStore, err := encryptionstorage.ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
usageStats := &usagestats.UsageStatsMock{T: t}
|
||||
enc, err := service.ProvideAESGCMCipherService(tracer, usageStats)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfgV1 := &setting.Cfg{
|
||||
SecretsManagement: setting.SecretsManagerSettings{
|
||||
CurrentEncryptionProvider: "secret_key.v1",
|
||||
ConfiguredKMSProviders: map[string]map[string]string{
|
||||
"secret_key.v1": {"secret_key": uuid.New().String()},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ossProviders, err := osskmsproviders.ProvideOSSKMSProviders(cfgV1, enc)
|
||||
require.NoError(t, err)
|
||||
|
||||
svcV1, err := ProvideEncryptionManager(
|
||||
tracer,
|
||||
encryptionStore,
|
||||
usageStats,
|
||||
enc,
|
||||
ossProviders,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
rsp, err := svcV1.Encrypt(ctx, namespace, []byte("test"))
|
||||
require.NoError(t, err)
|
||||
|
||||
cfgV2 := &setting.Cfg{
|
||||
SecretsManagement: setting.SecretsManagerSettings{
|
||||
CurrentEncryptionProvider: "secret_key.v2",
|
||||
ConfiguredKMSProviders: map[string]map[string]string{
|
||||
"secret_key.v2": {"secret_key": uuid.New().String()},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ossProvidersV2, err := osskmsproviders.ProvideOSSKMSProviders(cfgV2, enc)
|
||||
require.NoError(t, err)
|
||||
|
||||
svcV2, err := ProvideEncryptionManager(
|
||||
tracer,
|
||||
encryptionStore,
|
||||
usageStats,
|
||||
enc,
|
||||
ossProvidersV2,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = svcV2.Decrypt(ctx, namespace, rsp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
type fakeProvider struct {
|
||||
encryptCalled bool
|
||||
decryptCalled bool
|
||||
@@ -364,14 +540,12 @@ func TestIntegration_SecretsService(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
|
||||
tracer := noop.NewTracerProvider().Tracer("test")
|
||||
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagSecretsManagementAppPlatform)
|
||||
defaultKey := "SdlklWklckeLS"
|
||||
|
||||
cfg := &setting.Cfg{
|
||||
SecretsManagement: setting.SecretsManagerSettings{
|
||||
SecretKey: defaultKey,
|
||||
EncryptionProvider: "secretKey.v1",
|
||||
CurrentEncryptionProvider: "secret_key.v1",
|
||||
ConfiguredKMSProviders: map[string]map[string]string{"secret_key.v1": {"secret_key": "SW2YcwTIb9zpOOhoPsMm"}},
|
||||
},
|
||||
}
|
||||
store, err := encryptionstorage.ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features, nil)
|
||||
@@ -379,12 +553,18 @@ func TestIntegration_SecretsService(t *testing.T) {
|
||||
|
||||
usageStats := &usagestats.UsageStatsMock{T: t}
|
||||
|
||||
enc, err := service.ProvideAESGCMCipherService(tracer, usageStats)
|
||||
require.NoError(t, err)
|
||||
|
||||
ossProviders, err := osskmsproviders.ProvideOSSKMSProviders(cfg, enc)
|
||||
require.NoError(t, err)
|
||||
|
||||
svc, err := ProvideEncryptionManager(
|
||||
tracer,
|
||||
store,
|
||||
cfg,
|
||||
usageStats,
|
||||
encryption.ProvideThirdPartyProviderMap(),
|
||||
enc,
|
||||
ossProviders,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -415,34 +595,28 @@ func TestIntegration_SecretsService(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptionService_ReInitReturnsError(t *testing.T) {
|
||||
svc := setupTestService(t)
|
||||
err := svc.InitProviders(encryption.ProviderMap{
|
||||
"fakeProvider.v1": &fakeProvider{},
|
||||
})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEncryptionService_ThirdPartyProviders(t *testing.T) {
|
||||
cfg := &setting.Cfg{
|
||||
SecretsManagement: setting.SecretsManagerSettings{
|
||||
SecretKey: "SdlklWklckeLS",
|
||||
EncryptionProvider: "secretKey.v1",
|
||||
},
|
||||
}
|
||||
tracer := noop.NewTracerProvider().Tracer("test")
|
||||
usageStats := &usagestats.UsageStatsMock{T: t}
|
||||
|
||||
enc, err := service.ProvideAESGCMCipherService(tracer, usageStats)
|
||||
require.NoError(t, err)
|
||||
|
||||
svc, err := ProvideEncryptionManager(
|
||||
tracer,
|
||||
nil,
|
||||
nil,
|
||||
cfg,
|
||||
&usagestats.UsageStatsMock{},
|
||||
encryption.ProviderMap{
|
||||
"fakeProvider.v1": &fakeProvider{},
|
||||
usageStats,
|
||||
enc,
|
||||
encryption.ProviderConfig{
|
||||
CurrentProvider: encryption.ProviderID("fakeProvider.v1"),
|
||||
AvailableProviders: encryption.ProviderMap{
|
||||
encryption.ProviderID("fakeProvider.v1"): &fakeProvider{},
|
||||
},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
encMgr := svc.(*EncryptionManager)
|
||||
require.Len(t, encMgr.providers, 2)
|
||||
require.Contains(t, encMgr.providers, encryption.ProviderID("fakeProvider.v1"))
|
||||
require.Len(t, encMgr.providerConfig.AvailableProviders, 1)
|
||||
require.Contains(t, encMgr.providerConfig.AvailableProviders, encryption.ProviderID("fakeProvider.v1"))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import (
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/service"
|
||||
osskmsproviders "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -27,8 +28,8 @@ func setupTestService(tb testing.TB) *EncryptionManager {
|
||||
defaultKey := "SdlklWklckeLS"
|
||||
cfg := &setting.Cfg{
|
||||
SecretsManagement: setting.SecretsManagerSettings{
|
||||
SecretKey: defaultKey,
|
||||
EncryptionProvider: "secretKey.v1",
|
||||
CurrentEncryptionProvider: "secret_key.v1",
|
||||
ConfiguredKMSProviders: map[string]map[string]string{"secret_key.v1": {"secret_key": defaultKey}},
|
||||
},
|
||||
}
|
||||
store, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, features, nil)
|
||||
@@ -36,12 +37,18 @@ func setupTestService(tb testing.TB) *EncryptionManager {
|
||||
|
||||
usageStats := &usagestats.UsageStatsMock{T: tb}
|
||||
|
||||
enc, err := service.ProvideAESGCMCipherService(tracer, usageStats)
|
||||
require.NoError(tb, err)
|
||||
|
||||
ossProviders, err := osskmsproviders.ProvideOSSKMSProviders(cfg, enc)
|
||||
require.NoError(tb, err)
|
||||
|
||||
encMgr, err := ProvideEncryptionManager(
|
||||
tracer,
|
||||
store,
|
||||
cfg,
|
||||
usageStats,
|
||||
encryption.ProvideThirdPartyProviderMap(),
|
||||
enc,
|
||||
ossProviders,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
|
||||
@@ -9,7 +9,14 @@ import (
|
||||
|
||||
const UsageInsightsPrefix = "secrets_manager"
|
||||
|
||||
// Provider is a key encryption key provider for envelope encryption
|
||||
type ProviderConfig struct {
|
||||
CurrentProvider ProviderID
|
||||
AvailableProviders ProviderMap
|
||||
}
|
||||
|
||||
type ProviderMap map[ProviderID]Provider
|
||||
|
||||
// Provider is a fully configured key encryption key provider used for to encrypt and decrypt data keys for envelope encryption
|
||||
type Provider interface {
|
||||
Encrypt(ctx context.Context, blob []byte) ([]byte, error)
|
||||
Decrypt(ctx context.Context, blob []byte) ([]byte, error)
|
||||
@@ -17,6 +24,7 @@ type Provider interface {
|
||||
|
||||
type ProviderID string
|
||||
|
||||
// Kind returns the kind of the provider, e.g. "secret_key", "aws_kms", "azure_keyvault", "google_kms", "hashicorp_vault"
|
||||
func (id ProviderID) Kind() (string, error) {
|
||||
idStr := string(id)
|
||||
|
||||
@@ -28,17 +36,11 @@ func (id ProviderID) Kind() (string, error) {
|
||||
return parts[0], nil
|
||||
}
|
||||
|
||||
// KeyLabel returns a label for the data key that is unique to the current provider and today's date.
|
||||
func KeyLabel(providerID ProviderID) string {
|
||||
return fmt.Sprintf("%s@%s", time.Now().Format("2006-01-02"), providerID)
|
||||
}
|
||||
|
||||
type ProviderMap map[ProviderID]Provider
|
||||
|
||||
// ProvideThirdPartyProviderMap fulfills the wire dependency needed by the encryption manager in OSS
|
||||
func ProvideThirdPartyProviderMap() ProviderMap {
|
||||
return ProviderMap{}
|
||||
}
|
||||
|
||||
// BackgroundProvider should be implemented for a provider that has a task that needs to be run in the background.
|
||||
type BackgroundProvider interface {
|
||||
Run(ctx context.Context) error
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/service"
|
||||
osskmsproviders "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
@@ -26,8 +28,8 @@ func TestMain(m *testing.M) {
|
||||
func Test_OSSKeeperService(t *testing.T) {
|
||||
cfg := &setting.Cfg{
|
||||
SecretsManagement: setting.SecretsManagerSettings{
|
||||
SecretKey: "sdDkslslld",
|
||||
EncryptionProvider: "secretKey.v1",
|
||||
CurrentEncryptionProvider: "secret_key.v1",
|
||||
ConfiguredKMSProviders: map[string]map[string]string{"secret_key.v1": {"secret_key": "SW2YcwTIb9zpOOhoPsMm"}},
|
||||
},
|
||||
}
|
||||
keeperService, err := setupTestService(t, cfg)
|
||||
@@ -55,7 +57,13 @@ func setupTestService(t *testing.T, cfg *setting.Cfg) (*OSSKeeperService, error)
|
||||
encValueStore, err := encryptionstorage.ProvideEncryptedValueStorage(database, tracer, features)
|
||||
require.NoError(t, err)
|
||||
|
||||
encryptionManager, err := manager.ProvideEncryptionManager(tracer, dataKeyStore, cfg, &usagestats.UsageStatsMock{T: t}, nil)
|
||||
usageStats := &usagestats.UsageStatsMock{T: t}
|
||||
enc, err := service.ProvideAESGCMCipherService(tracer, usageStats)
|
||||
require.NoError(t, err)
|
||||
|
||||
ossProviders, err := osskmsproviders.ProvideOSSKMSProviders(cfg, enc)
|
||||
require.NoError(t, err)
|
||||
encryptionManager, err := manager.ProvideEncryptionManager(tracer, dataKeyStore, usageStats, enc, ossProviders)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Initialize the keeper service
|
||||
|
||||
@@ -16,7 +16,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/decrypt"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
|
||||
cipher "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/service"
|
||||
osskmsproviders "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/service"
|
||||
@@ -82,8 +83,8 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut {
|
||||
defaultKey := "SdlklWklckeLS"
|
||||
cfg := &setting.Cfg{
|
||||
SecretsManagement: setting.SecretsManagerSettings{
|
||||
SecretKey: defaultKey,
|
||||
EncryptionProvider: "secretKey.v1",
|
||||
CurrentEncryptionProvider: "secret_key.v1",
|
||||
ConfiguredKMSProviders: map[string]map[string]string{"secret_key.v1": {"secret_key": defaultKey}},
|
||||
},
|
||||
}
|
||||
store, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, features, nil)
|
||||
@@ -91,12 +92,18 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut {
|
||||
|
||||
usageStats := &usagestats.UsageStatsMock{T: t}
|
||||
|
||||
enc, err := cipher.ProvideAESGCMCipherService(tracer, usageStats)
|
||||
require.NoError(t, err)
|
||||
|
||||
ossProviders, err := osskmsproviders.ProvideOSSKMSProviders(cfg, enc)
|
||||
require.NoError(t, err)
|
||||
|
||||
encryptionManager, err := manager.ProvideEncryptionManager(
|
||||
tracer,
|
||||
store,
|
||||
cfg,
|
||||
usageStats,
|
||||
encryption.ProvideThirdPartyProviderMap(),
|
||||
enc,
|
||||
ossProviders,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
+2
-2
@@ -43,7 +43,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
secretcontracts "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
secretdecrypt "github.com/grafana/grafana/pkg/registry/apis/secret/decrypt"
|
||||
gsmEncryption "github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
|
||||
cipher "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/service"
|
||||
encryptionManager "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager"
|
||||
secretsecurevalueservice "github.com/grafana/grafana/pkg/registry/apis/secret/service"
|
||||
secretvalidator "github.com/grafana/grafana/pkg/registry/apis/secret/validator"
|
||||
@@ -437,7 +437,7 @@ var wireBasicSet = wire.NewSet(
|
||||
secretdatabase.ProvideDatabase,
|
||||
wire.Bind(new(secretcontracts.Database), new(*secretdatabase.Database)),
|
||||
encryptionManager.ProvideEncryptionManager,
|
||||
gsmEncryption.ProvideThirdPartyProviderMap,
|
||||
cipher.ProvideAESGCMCipherService,
|
||||
// Unified storage
|
||||
resource.ProvideStorageMetrics,
|
||||
resource.ProvideIndexMetrics,
|
||||
|
||||
+69
-54
File diff suppressed because one or more lines are too long
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
apisregistry "github.com/grafana/grafana/pkg/registry/apis"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
gsmKMSProviders "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper"
|
||||
"github.com/grafana/grafana/pkg/registry/backgroundsvcs"
|
||||
"github.com/grafana/grafana/pkg/registry/usagestatssvcs"
|
||||
@@ -130,6 +131,7 @@ var wireExtsBasicSet = wire.NewSet(
|
||||
builder.ProvideDefaultBuildHandlerChainFuncFromBuilders,
|
||||
aggregatorrunner.ProvideNoopAggregatorConfigurator,
|
||||
apisregistry.WireSetExts,
|
||||
gsmKMSProviders.ProvideOSSKMSProviders,
|
||||
)
|
||||
|
||||
var wireExtsSet = wire.NewSet(
|
||||
|
||||
@@ -1,22 +1,37 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/kmsproviders"
|
||||
const (
|
||||
ProviderPrefix = "secrets_manager.encryption."
|
||||
MisconfiguredProvider = "misconfigured"
|
||||
)
|
||||
|
||||
type SecretsManagerSettings struct {
|
||||
SecretKey string
|
||||
EncryptionProvider string
|
||||
AvailableProviders []string
|
||||
CurrentEncryptionProvider string
|
||||
|
||||
// ConfiguredKMSProviders is a map of KMS providers found in the config file. The keys are in the format of <provider>.<keyName>, and the values are a map of the properties in that section
|
||||
// In OSS, the provider type can only be "secret_key". In Enterprise, it can additionally be one of: "aws_kms", "azure_keyvault", "google_kms", "hashicorp_vault"
|
||||
ConfiguredKMSProviders map[string]map[string]string
|
||||
}
|
||||
|
||||
func (cfg *Cfg) readSecretsManagerSettings() {
|
||||
secretsMgmt := cfg.Raw.Section("secrets_manager")
|
||||
cfg.SecretsManagement.EncryptionProvider = secretsMgmt.Key("encryption_provider").MustString(kmsproviders.Default)
|
||||
cfg.SecretsManagement.CurrentEncryptionProvider = secretsMgmt.Key("encryption_provider").MustString(MisconfiguredProvider)
|
||||
|
||||
// TODO: These are not used yet by the secrets manager because we need to distentagle the dependencies with OSS.
|
||||
cfg.SecretsManagement.SecretKey = secretsMgmt.Key("secret_key").MustString("")
|
||||
cfg.SecretsManagement.AvailableProviders = regexp.MustCompile(`\s*,\s*`).Split(secretsMgmt.Key("available_encryption_providers").MustString(""), -1) // parse comma separated list
|
||||
// Extract available KMS providers from configuration sections
|
||||
providers := make(map[string]map[string]string)
|
||||
for _, section := range cfg.Raw.Sections() {
|
||||
sectionName := section.Name()
|
||||
if strings.HasPrefix(sectionName, ProviderPrefix) {
|
||||
// Extract the provider name (everything after the prefix)
|
||||
providerName := strings.TrimPrefix(sectionName, ProviderPrefix)
|
||||
if providerName != "" {
|
||||
providers[providerName] = section.KeysHash()
|
||||
}
|
||||
}
|
||||
}
|
||||
cfg.SecretsManagement.ConfiguredKMSProviders = providers
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestReadSecretsManagerSettings(t *testing.T) {
|
||||
t.Run("should parse basic encryption provider", func(t *testing.T) {
|
||||
iniContent := `
|
||||
[secrets_manager]
|
||||
encryption_provider = aws_kms
|
||||
`
|
||||
cfg, err := NewCfgFromBytes([]byte(iniContent))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "aws_kms", cfg.SecretsManagement.CurrentEncryptionProvider)
|
||||
assert.Empty(t, cfg.SecretsManagement.ConfiguredKMSProviders)
|
||||
})
|
||||
|
||||
t.Run("should parse single KMS provider configuration", func(t *testing.T) {
|
||||
iniContent := `
|
||||
[secrets_manager]
|
||||
encryption_provider = aws_kms.v1
|
||||
|
||||
[secrets_manager.encryption.aws_kms.v1]
|
||||
region = us-east-1
|
||||
key_id = arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012
|
||||
`
|
||||
cfg, err := NewCfgFromBytes([]byte(iniContent))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "aws_kms.v1", cfg.SecretsManagement.CurrentEncryptionProvider)
|
||||
assert.Len(t, cfg.SecretsManagement.ConfiguredKMSProviders, 1)
|
||||
|
||||
awsProvider := cfg.SecretsManagement.ConfiguredKMSProviders["aws_kms.v1"]
|
||||
assert.Equal(t, "us-east-1", awsProvider["region"])
|
||||
assert.Equal(t, "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012", awsProvider["key_id"])
|
||||
})
|
||||
|
||||
t.Run("should parse multiple KMS providers", func(t *testing.T) {
|
||||
iniContent := `
|
||||
[secrets_manager]
|
||||
encryption_provider = aws_kms.v1
|
||||
|
||||
[secrets_manager.encryption.aws_kms.v1]
|
||||
region = us-east-1
|
||||
key_id = arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012
|
||||
|
||||
[secrets_manager.encryption.azure_kv.v1]
|
||||
vault_url = https://myvault.vault.azure.net/
|
||||
key_name = mykey
|
||||
tenant_id = 12345678-1234-1234-1234-123456789012
|
||||
|
||||
[secrets_manager.encryption.secret_key.v1]
|
||||
key = my-secret-key
|
||||
`
|
||||
cfg, err := NewCfgFromBytes([]byte(iniContent))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "aws_kms.v1", cfg.SecretsManagement.CurrentEncryptionProvider)
|
||||
assert.Len(t, cfg.SecretsManagement.ConfiguredKMSProviders, 3)
|
||||
|
||||
// Check AWS KMS provider
|
||||
awsProvider := cfg.SecretsManagement.ConfiguredKMSProviders["aws_kms.v1"]
|
||||
assert.Equal(t, "us-east-1", awsProvider["region"])
|
||||
assert.Equal(t, "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012", awsProvider["key_id"])
|
||||
|
||||
// Check Azure Key Vault provider
|
||||
azureProvider := cfg.SecretsManagement.ConfiguredKMSProviders["azure_kv.v1"]
|
||||
assert.Equal(t, "https://myvault.vault.azure.net/", azureProvider["vault_url"])
|
||||
assert.Equal(t, "mykey", azureProvider["key_name"])
|
||||
assert.Equal(t, "12345678-1234-1234-1234-123456789012", azureProvider["tenant_id"])
|
||||
|
||||
// Check secret key provider
|
||||
secretProvider := cfg.SecretsManagement.ConfiguredKMSProviders["secret_key.v1"]
|
||||
assert.Equal(t, "my-secret-key", secretProvider["key"])
|
||||
})
|
||||
|
||||
t.Run("should default to misconfigured provider when no encryption_provider is set", func(t *testing.T) {
|
||||
iniContent := `
|
||||
[secrets_manager]
|
||||
# no encryption_provider setting
|
||||
`
|
||||
cfg, err := NewCfgFromBytes([]byte(iniContent))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, MisconfiguredProvider, cfg.SecretsManagement.CurrentEncryptionProvider)
|
||||
})
|
||||
|
||||
t.Run("should handle empty sections gracefully", func(t *testing.T) {
|
||||
iniContent := `
|
||||
[secrets_manager]
|
||||
encryption_provider = empty_provider
|
||||
|
||||
[secrets_manager.encryption.empty_provider]
|
||||
# empty section
|
||||
`
|
||||
cfg, err := NewCfgFromBytes([]byte(iniContent))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "empty_provider", cfg.SecretsManagement.CurrentEncryptionProvider)
|
||||
assert.Len(t, cfg.SecretsManagement.ConfiguredKMSProviders, 1)
|
||||
|
||||
emptyProvider := cfg.SecretsManagement.ConfiguredKMSProviders["empty_provider"]
|
||||
assert.NotNil(t, emptyProvider)
|
||||
assert.Empty(t, emptyProvider)
|
||||
})
|
||||
|
||||
t.Run("should ignore sections that don't match provider prefix", func(t *testing.T) {
|
||||
iniContent := `
|
||||
[secrets_manager]
|
||||
encryption_provider = aws_kms.v1
|
||||
|
||||
[secrets_manager.encryption.valid_provider]
|
||||
key = value
|
||||
|
||||
[secrets_manager.other_section]
|
||||
setting = value
|
||||
|
||||
[completely_different_section]
|
||||
some_setting = some_value
|
||||
`
|
||||
cfg, err := NewCfgFromBytes([]byte(iniContent))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "aws_kms.v1", cfg.SecretsManagement.CurrentEncryptionProvider)
|
||||
assert.Len(t, cfg.SecretsManagement.ConfiguredKMSProviders, 1)
|
||||
|
||||
validProvider := cfg.SecretsManagement.ConfiguredKMSProviders["valid_provider"]
|
||||
assert.Equal(t, "value", validProvider["key"])
|
||||
})
|
||||
|
||||
t.Run("should handle provider names with special characters", func(t *testing.T) {
|
||||
iniContent := `
|
||||
[secrets_manager]
|
||||
encryption_provider = aws_kms.v1
|
||||
|
||||
[secrets_manager.encryption.aws_kms.v1]
|
||||
region = us-west-2
|
||||
key_id = test-key
|
||||
|
||||
[secrets_manager.encryption.azure_kv.v1]
|
||||
vault_url = https://test.vault.azure.net/
|
||||
`
|
||||
cfg, err := NewCfgFromBytes([]byte(iniContent))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "aws_kms.v1", cfg.SecretsManagement.CurrentEncryptionProvider)
|
||||
assert.Len(t, cfg.SecretsManagement.ConfiguredKMSProviders, 2)
|
||||
|
||||
awsProvider := cfg.SecretsManagement.ConfiguredKMSProviders["aws_kms.v1"]
|
||||
assert.Equal(t, "us-west-2", awsProvider["region"])
|
||||
assert.Equal(t, "test-key", awsProvider["key_id"])
|
||||
|
||||
azureProvider := cfg.SecretsManagement.ConfiguredKMSProviders["azure_kv.v1"]
|
||||
assert.Equal(t, "https://test.vault.azure.net/", azureProvider["vault_url"])
|
||||
})
|
||||
|
||||
t.Run("should handle configuration with no secrets_manager section", func(t *testing.T) {
|
||||
iniContent := `
|
||||
[server]
|
||||
domain = example.com
|
||||
`
|
||||
cfg, err := NewCfgFromBytes([]byte(iniContent))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, MisconfiguredProvider, cfg.SecretsManagement.CurrentEncryptionProvider)
|
||||
assert.Empty(t, cfg.SecretsManagement.ConfiguredKMSProviders)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user