Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote vault patch 1 #1605

Merged
merged 16 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ Main (unreleased)
for `discovery.*` is reloaded in such a way that no new targets were
discovered. (@ptodev, @thampiotr)

- Fixed an issue (see https://github.com/grafana/alloy/issues/1599) where specifying both path and key in the remote.vault `path`
configuration could result in incorrect URLs. The `path` and `key` arguments have been separated to allow for clear and accurate
specification of Vault secrets. (@PatMis16)

### Other

- Renamed standard library functions. Old names are still valid but are marked deprecated. (@wildum)
Expand Down
5 changes: 4 additions & 1 deletion docs/sources/reference/components/remote/remote.vault.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ labels.
remote.vault "LABEL" {
server = "VAULT_SERVER"
path = "VAULT_PATH"
key = "VAULT_KEY"

// Alternatively, use one of the other auth.* mechanisms.
auth.token {
Expand All @@ -40,6 +41,7 @@ Name | Type | Description
`server` | `string` | The Vault server to connect to. | | yes
`namespace` | `string` | The Vault namespace to connect to (Vault Enterprise only). | | no
`path` | `string` | The path to retrieve a secret from. | | yes
`key` | `string` | The key to retrieve a secret from. | | no
`reread_frequency` | `duration` | Rate to re-read keys. | `"0s"` | no

Tokens with a lease will be automatically renewed roughly two-thirds through their lease duration.
Expand Down Expand Up @@ -290,7 +292,8 @@ local.file "vault_token" {

remote.vault "remote_write" {
server = "https://prod-vault.corporate.internal"
path = "secret/prometheus/remote_write"
path = "secret"
key = "prometheus/remote_write

auth.token {
token = local.file.vault_token.content
Expand Down
35 changes: 25 additions & 10 deletions internal/component/remote/vault/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,31 @@ type secretStore interface {
type kvStore struct{ c *vault.Client }

func (ks *kvStore) Read(ctx context.Context, args *Arguments) (*vault.Secret, error) {
// Split the path so we know which kv mount we want to use.
pathParts := strings.SplitN(args.Path, "/", 2)
if len(pathParts) != 2 {
return nil, fmt.Errorf("missing mount path in %q", args.Path)
}

kv := ks.c.KVv2(pathParts[0])
kvSecret, err := kv.Get(ctx, pathParts[1])
if err != nil {
return nil, err
var kvSecret *vault.KVSecret
var err error

// If a key is provided, we use that to get the secret from the KV store.
// This allows for more flexibility in how secrets are stored in vault.
// eg. long/path/kv/secret/key where long/path/kv is the path and secret/key is the key.
if args.Key != "" {
kv := ks.c.KVv2(args.Path)
kvSecret, err = kv.Get(ctx, args.Key)
if err != nil {
return nil, err
}
} else {
// for backward compatibility, we assume the path is in the format path/secret
// Split the path so we know which kv mount we want to use.
pathParts := strings.SplitN(args.Path, "/", 2)
if len(pathParts) != 2 {
return nil, fmt.Errorf("missing mount path in %q", args.Path)
}

kv := ks.c.KVv2(pathParts[0])
kvSecret, err = kv.Get(ctx, pathParts[1])
if err != nil {
return nil, err
}
}

// kvSecret.Data contains unwrapped data. Let's assign that to the raw secret
Expand Down
1 change: 1 addition & 0 deletions internal/component/remote/vault/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Arguments struct {
Namespace string `alloy:"namespace,attr,optional"`

Path string `alloy:"path,attr"`
Key string `alloy:"key,attr,optional"`

RereadFrequency time.Duration `alloy:"reread_frequency,attr,optional"`

Expand Down
78 changes: 76 additions & 2 deletions internal/component/remote/vault/vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ func Test_GetSecrets(t *testing.T) {

cfg := fmt.Sprintf(`
server = "%s"
path = "secret/test"
path = "secret"
secret = "test"

reread_frequency = "0s"

Expand Down Expand Up @@ -70,7 +71,7 @@ func Test_GetSecrets(t *testing.T) {
require.Equal(t, expectExports, actualExports)
}

func Test_PollSecrets(t *testing.T) {
func Test_PollSecretsWithoutKey(t *testing.T) {
var (
ctx = componenttest.TestContext(t)
l = util.TestLogger(t)
Expand Down Expand Up @@ -142,6 +143,79 @@ func Test_PollSecrets(t *testing.T) {
}
}

func Test_PollSecretsWithKey(t *testing.T) {
var (
ctx = componenttest.TestContext(t)
l = util.TestLogger(t)
)

cli := getTestVaultServer(t)

// Store a secret in value to use from the component.
_, err := cli.KVv2("secret").Put(ctx, "test", map[string]any{
"key": "value",
})
require.NoError(t, err)

cfg := fmt.Sprintf(`
server = "%s"
path = ""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this have a path to more accurately test the issue? Should it be secret and key be test?

key = "secret/test"

reread_frequency = "100ms"

auth.token {
token = "%s"
}
`, cli.Address(), cli.Token())

var args Arguments
require.NoError(t, syntax.Unmarshal([]byte(cfg), &args))

ctrl, err := componenttest.NewControllerFromID(l, "remote.vault")
require.NoError(t, err)

go func() {
require.NoError(t, ctrl.Run(ctx, args))
}()
require.NoError(t, ctrl.WaitRunning(time.Minute))

// Get the initial secret.
{
require.NoError(t, ctrl.WaitExports(time.Minute))

var (
expectExports = Exports{
Data: map[string]alloytypes.Secret{
"key": alloytypes.Secret("value"),
},
}
actualExports = ctrl.Exports().(Exports)
)
require.Equal(t, expectExports, actualExports)
}

// Get an updated secret.
{
_, err := cli.KVv2("secret").Put(ctx, "test", map[string]any{
"key": "newvalue",
})
require.NoError(t, err)

require.NoError(t, ctrl.WaitExports(time.Minute))

var (
expectExports = Exports{
Data: map[string]alloytypes.Secret{
"key": alloytypes.Secret("newvalue"),
},
}
actualExports = ctrl.Exports().(Exports)
)
require.Equal(t, expectExports, actualExports)
}
}

func getTestVaultServer(t *testing.T) *vaultapi.Client {
// TODO: this is broken with go 1.20.6
// waiting on https://github.com/testcontainers/testcontainers-go/issues/1359
Expand Down
Loading