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:
Alessandro (Ale) Segala 2023-04-11 14:00:48 -07:00 committed by GitHub
parent cbe0da4b14
commit 3535c7e9b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 143 additions and 28 deletions

View File

@ -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

View File

@ -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.

16
secretstores/local/env/metadata.yaml vendored Normal file
View File

@ -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