Env secret store: add "prefix" metadata option (+ metadata.yaml) (#2761)
Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
parent
cbe0da4b14
commit
3535c7e9b7
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2021 The Dapr Authors
|
||||
Copyright 2023 The Dapr Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
@ -26,8 +26,13 @@ import (
|
|||
|
||||
var _ secretstores.SecretStore = (*envSecretStore)(nil)
|
||||
|
||||
type Metadata struct {
|
||||
Prefix string
|
||||
}
|
||||
|
||||
type envSecretStore struct {
|
||||
logger logger.Logger
|
||||
logger logger.Logger
|
||||
metadata Metadata
|
||||
}
|
||||
|
||||
// NewEnvSecretStore returns a new env var secret store.
|
||||
|
@ -38,15 +43,19 @@ func NewEnvSecretStore(logger logger.Logger) secretstores.SecretStore {
|
|||
}
|
||||
|
||||
// Init creates a Local secret store.
|
||||
func (s *envSecretStore) Init(_ context.Context, metadata secretstores.Metadata) error {
|
||||
func (s *envSecretStore) Init(_ context.Context, meta secretstores.Metadata) error {
|
||||
if err := metadata.DecodeMetadata(meta.Properties, &s.metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSecret retrieves a secret from env var using provided key.
|
||||
func (s *envSecretStore) GetSecret(ctx context.Context, req secretstores.GetSecretRequest) (secretstores.GetSecretResponse, error) {
|
||||
var value string
|
||||
if s.isKeyAllowed(req.Name) {
|
||||
value = os.Getenv(req.Name)
|
||||
name := s.metadata.Prefix + req.Name
|
||||
if s.isKeyAllowed(name) {
|
||||
value = os.Getenv(name)
|
||||
} else {
|
||||
s.logger.Warnf("Access to env var %s is forbidden", req.Name)
|
||||
}
|
||||
|
@ -62,10 +71,15 @@ func (s *envSecretStore) BulkGetSecret(ctx context.Context, req secretstores.Bul
|
|||
env := os.Environ()
|
||||
r := make(map[string]map[string]string, len(env))
|
||||
|
||||
lp := len(s.metadata.Prefix)
|
||||
for _, element := range env {
|
||||
envVariable := strings.SplitN(element, "=", 2)
|
||||
if s.isKeyAllowed(envVariable[0]) {
|
||||
r[envVariable[0]] = map[string]string{envVariable[0]: envVariable[1]}
|
||||
key := envVariable[0]
|
||||
if s.metadata.Prefix != "" && !strings.HasPrefix(key, s.metadata.Prefix) {
|
||||
continue
|
||||
}
|
||||
if s.isKeyAllowed(key) {
|
||||
r[key[lp:]] = map[string]string{key[lp:]: envVariable[1]}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,17 +94,18 @@ func (s *envSecretStore) Features() []secretstores.Feature {
|
|||
}
|
||||
|
||||
func (s *envSecretStore) GetComponentMetadata() map[string]string {
|
||||
type unusedMetadataStruct struct{}
|
||||
metadataStruct := unusedMetadataStruct{}
|
||||
metadataStruct := Metadata{}
|
||||
metadataInfo := map[string]string{}
|
||||
metadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo, metadata.SecretStoreType)
|
||||
return metadataInfo
|
||||
}
|
||||
|
||||
func (s *envSecretStore) isKeyAllowed(key string) bool {
|
||||
switch key {
|
||||
case "APP_API_TOKEN", "DAPR_API_TOKEN",
|
||||
"DAPR_TRUST_ANCHORS", "DAPR_CERT_CHAIN", "DAPR_CERT_KEY":
|
||||
key = strings.ToUpper(key)
|
||||
switch {
|
||||
case key == "APP_API_TOKEN":
|
||||
return false
|
||||
case strings.HasPrefix(key, "DAPR_"):
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2021 The Dapr Authors
|
||||
Copyright 2023 The Dapr Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
@ -12,6 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package env
|
||||
|
||||
import (
|
||||
|
@ -22,18 +23,16 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/dapr/components-contrib/metadata"
|
||||
"github.com/dapr/components-contrib/secretstores"
|
||||
"github.com/dapr/kit/logger"
|
||||
)
|
||||
|
||||
func TestEnvStore(t *testing.T) {
|
||||
secret := "secret1"
|
||||
key := "TEST_SECRET"
|
||||
|
||||
s := envSecretStore{logger: logger.NewLogger("test")}
|
||||
|
||||
t.Setenv(key, secret)
|
||||
require.Equal(t, secret, os.Getenv(key))
|
||||
t.Setenv("TEST_SECRET", "secret1")
|
||||
require.Equal(t, "secret1", os.Getenv("TEST_SECRET"))
|
||||
|
||||
t.Run("Init", func(t *testing.T) {
|
||||
err := s.Init(context.Background(), secretstores.Metadata{})
|
||||
|
@ -41,31 +40,32 @@ func TestEnvStore(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
err := s.Init(context.Background(), secretstores.Metadata{})
|
||||
require.NoError(t, err)
|
||||
resp, err := s.GetSecret(context.Background(), secretstores.GetSecretRequest{Name: key})
|
||||
resp, err := s.GetSecret(context.Background(), secretstores.GetSecretRequest{Name: "TEST_SECRET"})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.Equal(t, secret, resp.Data[key])
|
||||
assert.Equal(t, "secret1", resp.Data["TEST_SECRET"])
|
||||
})
|
||||
|
||||
t.Run("Get case sensitive", func(t *testing.T) {
|
||||
resp, err := s.GetSecret(context.Background(), secretstores.GetSecretRequest{Name: "test_secret"})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.Empty(t, resp.Data["test_secret"])
|
||||
})
|
||||
|
||||
t.Run("Bulk get", func(t *testing.T) {
|
||||
err := s.Init(context.Background(), secretstores.Metadata{})
|
||||
require.NoError(t, err)
|
||||
resp, err := s.BulkGetSecret(context.Background(), secretstores.BulkGetSecretRequest{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.Equal(t, secret, resp.Data[key][key])
|
||||
assert.Equal(t, "secret1", resp.Data["TEST_SECRET"]["TEST_SECRET"])
|
||||
})
|
||||
|
||||
t.Run("Disallowed keys", func(t *testing.T) {
|
||||
t.Setenv("APP_API_TOKEN", "ciao")
|
||||
t.Setenv("DAPR_API_TOKEN", "mondo")
|
||||
t.Setenv("dapr_notallowed", "notallowed")
|
||||
t.Setenv("FOO", "bar")
|
||||
|
||||
err := s.Init(context.Background(), secretstores.Metadata{})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
resp, err := s.GetSecret(context.Background(), secretstores.GetSecretRequest{
|
||||
Name: "APP_API_TOKEN",
|
||||
|
@ -73,6 +73,20 @@ func TestEnvStore(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.Data)
|
||||
assert.Empty(t, resp.Data["APP_API_TOKEN"])
|
||||
|
||||
resp, err = s.GetSecret(context.Background(), secretstores.GetSecretRequest{
|
||||
Name: "dapr_notallowed",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.Data)
|
||||
assert.Empty(t, resp.Data["dapr_notallowed"])
|
||||
|
||||
resp, err = s.GetSecret(context.Background(), secretstores.GetSecretRequest{
|
||||
Name: "DAPR_NOTALLOWED",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.Data)
|
||||
assert.Empty(t, resp.Data["DAPR_NOTALLOWED"])
|
||||
})
|
||||
|
||||
t.Run("Bulk get", func(t *testing.T) {
|
||||
|
@ -81,11 +95,81 @@ func TestEnvStore(t *testing.T) {
|
|||
require.NotNil(t, resp.Data)
|
||||
assert.Empty(t, resp.Data["APP_API_TOKEN"])
|
||||
assert.Empty(t, resp.Data["DAPR_API_TOKEN"])
|
||||
assert.Empty(t, resp.Data["DAPR_NOTALLOWED"])
|
||||
assert.Equal(t, "bar", resp.Data["FOO"]["FOO"])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnvStoreWithPrefix(t *testing.T) {
|
||||
s := envSecretStore{logger: logger.NewLogger("test")}
|
||||
|
||||
t.Setenv("TEST_SECRET", "test1")
|
||||
t.Setenv("test_secret2", "test2")
|
||||
t.Setenv("FOO_SECRET", "foo1")
|
||||
require.Equal(t, "test1", os.Getenv("TEST_SECRET"))
|
||||
require.Equal(t, "test2", os.Getenv("test_secret2"))
|
||||
require.Equal(t, "foo1", os.Getenv("FOO_SECRET"))
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
err := s.Init(context.Background(), secretstores.Metadata{
|
||||
Base: metadata.Base{Properties: map[string]string{
|
||||
// Prefix should be case-sensitive
|
||||
"prefix": "TEST_",
|
||||
}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := s.GetSecret(context.Background(), secretstores.GetSecretRequest{Name: "SECRET"})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.Len(t, resp.Data, 1)
|
||||
assert.Equal(t, "test1", resp.Data["SECRET"])
|
||||
})
|
||||
|
||||
t.Run("Bulk get", func(t *testing.T) {
|
||||
err := s.Init(context.Background(), secretstores.Metadata{
|
||||
Base: metadata.Base{Properties: map[string]string{
|
||||
"prefix": "TEST_",
|
||||
}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := s.BulkGetSecret(context.Background(), secretstores.BulkGetSecretRequest{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.Len(t, resp.Data, 1)
|
||||
assert.Equal(t, "test1", resp.Data["SECRET"]["SECRET"])
|
||||
})
|
||||
|
||||
t.Run("Disallowed keys", func(t *testing.T) {
|
||||
t.Setenv("DAPR_API_TOKEN", "hi")
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
err := s.Init(context.Background(), secretstores.Metadata{
|
||||
Base: metadata.Base{Properties: map[string]string{
|
||||
"prefix": "DAPR_",
|
||||
}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := s.GetSecret(context.Background(), secretstores.GetSecretRequest{
|
||||
Name: "API_TOKEN",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.Data)
|
||||
assert.Empty(t, resp.Data["API_TOKEN"])
|
||||
})
|
||||
|
||||
t.Run("Bulk get", func(t *testing.T) {
|
||||
resp, err := s.BulkGetSecret(context.Background(), secretstores.BulkGetSecretRequest{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.Data)
|
||||
assert.Empty(t, resp.Data["API_TOKEN"])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetFeatures(t *testing.T) {
|
||||
s := envSecretStore{logger: logger.NewLogger("test")}
|
||||
// Yes, we are skipping initialization as feature retrieval doesn't depend on it.
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# yaml-language-server: $schema=../../../component-metadata-schema.json
|
||||
schemaVersion: v1
|
||||
type: secretstores
|
||||
name: local.env
|
||||
version: v1
|
||||
status: stable
|
||||
title: "Local environment variables"
|
||||
urls:
|
||||
- title: Reference
|
||||
url: "https://docs.dapr.io/reference/components-reference/supported-secret-stores/envvar-secret-store/"
|
||||
metadata:
|
||||
- name: prefix
|
||||
description: |
|
||||
If set, limits operations to environmental variables with the given prefix. The prefix is case-sensitive and is removed from the returned secrets' names.
|
||||
example: '"MYAPP_"'
|
||||
type: string
|
Loading…
Reference in New Issue