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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
@ -26,8 +26,13 @@ import (
|
||||||
|
|
||||||
var _ secretstores.SecretStore = (*envSecretStore)(nil)
|
var _ secretstores.SecretStore = (*envSecretStore)(nil)
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
Prefix string
|
||||||
|
}
|
||||||
|
|
||||||
type envSecretStore struct {
|
type envSecretStore struct {
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
|
metadata Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEnvSecretStore returns a new env var secret store.
|
// 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.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSecret retrieves a secret from env var using provided key.
|
// GetSecret retrieves a secret from env var using provided key.
|
||||||
func (s *envSecretStore) GetSecret(ctx context.Context, req secretstores.GetSecretRequest) (secretstores.GetSecretResponse, error) {
|
func (s *envSecretStore) GetSecret(ctx context.Context, req secretstores.GetSecretRequest) (secretstores.GetSecretResponse, error) {
|
||||||
var value string
|
var value string
|
||||||
if s.isKeyAllowed(req.Name) {
|
name := s.metadata.Prefix + req.Name
|
||||||
value = os.Getenv(req.Name)
|
if s.isKeyAllowed(name) {
|
||||||
|
value = os.Getenv(name)
|
||||||
} else {
|
} else {
|
||||||
s.logger.Warnf("Access to env var %s is forbidden", req.Name)
|
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()
|
env := os.Environ()
|
||||||
r := make(map[string]map[string]string, len(env))
|
r := make(map[string]map[string]string, len(env))
|
||||||
|
|
||||||
|
lp := len(s.metadata.Prefix)
|
||||||
for _, element := range env {
|
for _, element := range env {
|
||||||
envVariable := strings.SplitN(element, "=", 2)
|
envVariable := strings.SplitN(element, "=", 2)
|
||||||
if s.isKeyAllowed(envVariable[0]) {
|
key := envVariable[0]
|
||||||
r[envVariable[0]] = map[string]string{envVariable[0]: envVariable[1]}
|
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 {
|
func (s *envSecretStore) GetComponentMetadata() map[string]string {
|
||||||
type unusedMetadataStruct struct{}
|
metadataStruct := Metadata{}
|
||||||
metadataStruct := unusedMetadataStruct{}
|
|
||||||
metadataInfo := map[string]string{}
|
metadataInfo := map[string]string{}
|
||||||
metadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo, metadata.SecretStoreType)
|
metadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo, metadata.SecretStoreType)
|
||||||
return metadataInfo
|
return metadataInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *envSecretStore) isKeyAllowed(key string) bool {
|
func (s *envSecretStore) isKeyAllowed(key string) bool {
|
||||||
switch key {
|
key = strings.ToUpper(key)
|
||||||
case "APP_API_TOKEN", "DAPR_API_TOKEN",
|
switch {
|
||||||
"DAPR_TRUST_ANCHORS", "DAPR_CERT_CHAIN", "DAPR_CERT_KEY":
|
case key == "APP_API_TOKEN":
|
||||||
|
return false
|
||||||
|
case strings.HasPrefix(key, "DAPR_"):
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
return true
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
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
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -22,18 +23,16 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/components-contrib/secretstores"
|
"github.com/dapr/components-contrib/secretstores"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEnvStore(t *testing.T) {
|
func TestEnvStore(t *testing.T) {
|
||||||
secret := "secret1"
|
|
||||||
key := "TEST_SECRET"
|
|
||||||
|
|
||||||
s := envSecretStore{logger: logger.NewLogger("test")}
|
s := envSecretStore{logger: logger.NewLogger("test")}
|
||||||
|
|
||||||
t.Setenv(key, secret)
|
t.Setenv("TEST_SECRET", "secret1")
|
||||||
require.Equal(t, secret, os.Getenv(key))
|
require.Equal(t, "secret1", os.Getenv("TEST_SECRET"))
|
||||||
|
|
||||||
t.Run("Init", func(t *testing.T) {
|
t.Run("Init", func(t *testing.T) {
|
||||||
err := s.Init(context.Background(), secretstores.Metadata{})
|
err := s.Init(context.Background(), secretstores.Metadata{})
|
||||||
|
@ -41,31 +40,32 @@ func TestEnvStore(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Get", func(t *testing.T) {
|
t.Run("Get", func(t *testing.T) {
|
||||||
err := s.Init(context.Background(), secretstores.Metadata{})
|
resp, err := s.GetSecret(context.Background(), secretstores.GetSecretRequest{Name: "TEST_SECRET"})
|
||||||
require.NoError(t, err)
|
|
||||||
resp, err := s.GetSecret(context.Background(), secretstores.GetSecretRequest{Name: key})
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, resp)
|
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) {
|
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{})
|
resp, err := s.BulkGetSecret(context.Background(), secretstores.BulkGetSecretRequest{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, resp)
|
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.Run("Disallowed keys", func(t *testing.T) {
|
||||||
t.Setenv("APP_API_TOKEN", "ciao")
|
t.Setenv("APP_API_TOKEN", "ciao")
|
||||||
t.Setenv("DAPR_API_TOKEN", "mondo")
|
t.Setenv("DAPR_API_TOKEN", "mondo")
|
||||||
|
t.Setenv("dapr_notallowed", "notallowed")
|
||||||
t.Setenv("FOO", "bar")
|
t.Setenv("FOO", "bar")
|
||||||
|
|
||||||
err := s.Init(context.Background(), secretstores.Metadata{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Run("Get", func(t *testing.T) {
|
t.Run("Get", func(t *testing.T) {
|
||||||
resp, err := s.GetSecret(context.Background(), secretstores.GetSecretRequest{
|
resp, err := s.GetSecret(context.Background(), secretstores.GetSecretRequest{
|
||||||
Name: "APP_API_TOKEN",
|
Name: "APP_API_TOKEN",
|
||||||
|
@ -73,6 +73,20 @@ func TestEnvStore(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, resp.Data)
|
require.NotNil(t, resp.Data)
|
||||||
assert.Empty(t, resp.Data["APP_API_TOKEN"])
|
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) {
|
t.Run("Bulk get", func(t *testing.T) {
|
||||||
|
@ -81,11 +95,81 @@ func TestEnvStore(t *testing.T) {
|
||||||
require.NotNil(t, resp.Data)
|
require.NotNil(t, resp.Data)
|
||||||
assert.Empty(t, resp.Data["APP_API_TOKEN"])
|
assert.Empty(t, resp.Data["APP_API_TOKEN"])
|
||||||
assert.Empty(t, resp.Data["DAPR_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"])
|
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) {
|
func TestGetFeatures(t *testing.T) {
|
||||||
s := envSecretStore{logger: logger.NewLogger("test")}
|
s := envSecretStore{logger: logger.NewLogger("test")}
|
||||||
// Yes, we are skipping initialization as feature retrieval doesn't depend on it.
|
// 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