diff --git a/args.go b/args.go new file mode 100644 index 0000000..e5c670a --- /dev/null +++ b/args.go @@ -0,0 +1,40 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 main + +import ( + "fmt" + "os/exec" +) + +// ExtractEntrypoint extracts entrypoint data in the form of binary path and its arguments from the +// os.Args. Note that the path to the binary will be returned as the first element. +func ExtractEntrypoint(args []string) (string, []string, error) { + if len(args) <= 1 { + return "", nil, fmt.Errorf("no args provided") + } + + binaryPath, err := exec.LookPath(args[1]) + if err != nil { + return "", nil, fmt.Errorf("binary %s not found", args[1]) + } + + var binaryArgs []string + if len(args) >= 2 { + binaryArgs = args[2:] // returns the arguments for the binary + } + + return binaryPath, binaryArgs, nil +} diff --git a/args_test.go b/args_test.go new file mode 100644 index 0000000..b817805 --- /dev/null +++ b/args_test.go @@ -0,0 +1,68 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 main + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExtractEntrypoint(t *testing.T) { + tests := []struct { + name string + args []string + expectedBinaryPath string + expectedBinaryArgs []string + err error + }{ + { + name: "Valid case with one argument", + args: []string{"secret-init", "env"}, + expectedBinaryPath: "/usr/bin/env", + expectedBinaryArgs: []string{}, + }, + { + name: "Valid case with more than two arguments", + args: []string{"secret-init", "env", "|", "grep", "secrets"}, + expectedBinaryPath: "/usr/bin/env", + expectedBinaryArgs: []string{"|", "grep", "secrets"}, + }, + { + name: "Invalid case - no arguments", + args: []string{"secret-init"}, + err: fmt.Errorf("no args provided"), + }, + { + name: "Invalid case - binary not found", + args: []string{"secret-init", "nonexistentBinary"}, + err: fmt.Errorf("binary nonexistentBinary not found"), + }, + } + + for _, tt := range tests { + ttp := tt + t.Run(ttp.name, func(t *testing.T) { + binaryPath, binaryArgs, err := ExtractEntrypoint(ttp.args) + if err != nil { + assert.EqualError(t, ttp.err, err.Error(), "Unexpected error message") + } else { + assert.Equal(t, ttp.expectedBinaryPath, binaryPath, "Unexpected binary path") + assert.Equal(t, ttp.expectedBinaryArgs, binaryArgs, "Unexpected binary args") + } + }) + } +} diff --git a/common/config.go b/common/config.go new file mode 100644 index 0000000..9990ea1 --- /dev/null +++ b/common/config.go @@ -0,0 +1,51 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 common + +import ( + "os" + "time" + + "github.com/spf13/cast" +) + +const ( + LogLevelEnv = "SECRET_INIT_LOG_LEVEL" + JSONLogEnv = "SECRET_INIT_JSON_LOG" + LogServerEnv = "SECRET_INIT_LOG_SERVER" + DaemonEnv = "SECRET_INIT_DAEMON" + DelayEnv = "SECRET_INIT_DELAY" + ProviderEnv = "SECRET_INIT_PROVIDER" +) + +type Config struct { + LogLevel string `json:"log_level"` + JSONLog bool `json:"json_log"` + LogServer string `json:"log_server"` + Daemon bool `json:"daemon"` + Delay time.Duration `json:"delay"` + Provider string `json:"provider"` +} + +func LoadConfig() (*Config, error) { + return &Config{ + LogLevel: os.Getenv(LogLevelEnv), + JSONLog: cast.ToBool(os.Getenv(JSONLogEnv)), + LogServer: os.Getenv(LogServerEnv), + Daemon: cast.ToBool(os.Getenv(DaemonEnv)), + Delay: cast.ToDuration(os.Getenv(DelayEnv)), + Provider: os.Getenv(ProviderEnv), + }, nil +} diff --git a/common/config_test.go b/common/config_test.go new file mode 100644 index 0000000..d7ca13d --- /dev/null +++ b/common/config_test.go @@ -0,0 +1,67 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 common + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfig(t *testing.T) { + tests := []struct { + name string + env map[string]string + wantConfig *Config + }{ + { + name: "Valid configuration", + env: map[string]string{ + LogLevelEnv: "debug", + JSONLogEnv: "true", + LogServerEnv: "", + DaemonEnv: "true", + ProviderEnv: "vault", + }, + wantConfig: &Config{ + LogLevel: "debug", + JSONLog: true, + LogServer: "", + Daemon: true, + Provider: "vault", + }, + }, + } + + for _, tt := range tests { + ttp := tt + t.Run(ttp.name, func(t *testing.T) { + for envKey, envVal := range ttp.env { + os.Setenv(envKey, envVal) + } + + config, err := LoadConfig() + assert.Nil(t, err, "Unexpected error") + + assert.Equal(t, ttp.wantConfig, config, "Unexpected config") + + // unset envs for the next test + for envKey := range ttp.env { + os.Unsetenv(envKey) + } + }) + } +} diff --git a/env.go b/env.go deleted file mode 100644 index 606fa77..0000000 --- a/env.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright © 2023 Bank-Vaults Maintainers -// -// 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 -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// 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 main - -import ( - "fmt" - "os" - "strings" - - "github.com/bank-vaults/secret-init/provider" - "github.com/bank-vaults/secret-init/provider/file" -) - -func GetEnvironMap() map[string]string { - environ := make(map[string]string, len(os.Environ())) - for _, env := range os.Environ() { - split := strings.SplitN(env, "=", 2) - name := split[0] - value := split[1] - environ[name] = value - } - - return environ -} - -func ExtractPathsFromEnvs(envs map[string]string) []string { - var secretPaths []string - - for _, path := range envs { - if p, path := getProviderPath(path); p != nil { - secretPaths = append(secretPaths, path) - } - } - - return secretPaths -} - -func CreateSecretEnvsFrom(envs map[string]string, secrets []provider.Secret) ([]string, error) { - // Reverse the map so we can match - // the environment variable key to the secret - // by using the secret path - reversedEnvs := make(map[string]string) - for envKey, path := range envs { - if p, path := getProviderPath(path); p != nil { - reversedEnvs[path] = envKey - } - } - - var secretsEnv []string - for _, secret := range secrets { - path := secret.Path - value := secret.Value - key, ok := reversedEnvs[path] - if !ok { - return nil, fmt.Errorf("failed to find environment variable key for secret path: %s", path) - } - secretsEnv = append(secretsEnv, fmt.Sprintf("%s=%s", key, value)) - } - - return secretsEnv, nil -} - -// Returns the detected provider name and path with removed prefix -func getProviderPath(path string) (*string, string) { - if strings.HasPrefix(path, "file:") { - var fileProviderName = file.ProviderName - return &fileProviderName, strings.TrimPrefix(path, "file:") - } - - return nil, path -} diff --git a/env_store.go b/env_store.go new file mode 100644 index 0000000..d8f27d0 --- /dev/null +++ b/env_store.go @@ -0,0 +1,123 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 main + +import ( + "fmt" + "os" + "strings" + + "github.com/bank-vaults/secret-init/provider" + "github.com/bank-vaults/secret-init/provider/file" + "github.com/bank-vaults/secret-init/provider/vault" +) + +// EnvStore is a helper for managing interactions between environment variables and providers, +// including tasks like extracting and converting provider-specific paths and secrets. +type EnvStore struct { + data map[string]string +} + +func NewEnvStore() *EnvStore { + environ := make(map[string]string, len(os.Environ())) + for _, env := range os.Environ() { + split := strings.SplitN(env, "=", 2) + name := split[0] + value := split[1] + environ[name] = value + } + + return &EnvStore{ + data: environ, + } +} + +func (s *EnvStore) GetProviderPaths(provider provider.Provider) ([]string, error) { + var secretPaths []string + + for envKey, path := range s.data { + p, path := getProviderPath(path) + + // TODO(csatib02): Implement multi-provider support + if p == provider.GetProviderName() { + // The injector function expects a map of key:value pairs + if p == vault.ProviderName { + path = envKey + "=" + path + } + + secretPaths = append(secretPaths, path) + } + } + + return secretPaths, nil +} + +// ConvertProviderSecrets converts the loaded secrets to environment variables +// In case of the Vault provider, the secrets are already in the correct format +func (s *EnvStore) ConvertProviderSecrets(provider provider.Provider, secrets []provider.Secret) ([]string, error) { + switch provider.GetProviderName() { + case vault.ProviderName: + // The Vault provider already returns the secrets with the environment variable keys + var vaultEnv []string + for _, secret := range secrets { + vaultEnv = append(vaultEnv, fmt.Sprintf("%s=%s", secret.Path, secret.Value)) + } + return vaultEnv, nil + + default: + return createSecretEnvsFrom(s.data, secrets) + } +} + +// Returns the detected provider name and path with removed prefix +func getProviderPath(path string) (string, string) { + if strings.HasPrefix(path, "file:") { + var fileProviderName = file.ProviderName + return fileProviderName, strings.TrimPrefix(path, "file:") + } + if strings.HasPrefix(path, "vault:") { + var vaultProviderName = vault.ProviderName + // Do not remove the prefix since it will be processed during injection + return vaultProviderName, path + } + + return "", path +} + +func createSecretEnvsFrom(envs map[string]string, secrets []provider.Secret) ([]string, error) { + // Reverse the map so we can match + // the environment variable key to the secret + // by using the secret path + reversedEnvs := make(map[string]string) + for envKey, path := range envs { + p, path := getProviderPath(path) + if p != "" { + reversedEnvs[path] = envKey + } + } + + var secretsEnv []string + for _, secret := range secrets { + path := secret.Path + key, ok := reversedEnvs[path] + if !ok { + return nil, fmt.Errorf("failed to find environment variable key for secret path: %s", path) + } + + secretsEnv = append(secretsEnv, fmt.Sprintf("%s=%s", key, secret.Value)) + } + + return secretsEnv, nil +} diff --git a/env_store_test.go b/env_store_test.go new file mode 100644 index 0000000..c653a10 --- /dev/null +++ b/env_store_test.go @@ -0,0 +1,163 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 main + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/bank-vaults/secret-init/provider" + "github.com/bank-vaults/secret-init/provider/file" + "github.com/bank-vaults/secret-init/provider/vault" +) + +func TestEnvStore_GetPathsFor(t *testing.T) { + tests := []struct { + name string + provider provider.Provider + wantPaths []string + err error + }{ + { + name: "Vault provider", + provider: &vault.Provider{}, + wantPaths: []string{ + "MYSQL_PASSWORD=vault:secret/data/test/mysql#MYSQL_PASSWORD", + "AWS_SECRET_ACCESS_KEY=vault:secret/data/test/aws#AWS_SECRET_ACCESS_KEY", + }, + }, + { + name: "File provider", + provider: &file.Provider{}, + wantPaths: []string{ + "test/secrets/mysql.txt", + "test/secrets/aws.txt", + }, + }, + } + + for _, tt := range tests { + ttp := tt + t.Run(ttp.name, func(t *testing.T) { + createEnvsForProvider(ttp.provider) + + envStore := NewEnvStore() + + paths, err := envStore.GetProviderPaths(ttp.provider) + if err != nil { + assert.EqualError(t, err, ttp.err.Error(), "Unexpected error message") + } + + if ttp.wantPaths != nil { + assert.Contains(t, paths, ttp.wantPaths[0], "Unexpected path") + assert.Contains(t, paths, ttp.wantPaths[1], "Unexpected path") + } + }) + } +} + +func TestEnvStore_GetProviderSecrets(t *testing.T) { + tests := []struct { + name string + provider provider.Provider + secrets []provider.Secret + wantSecrets []string + err error + }{ + { + name: "Vault provider", + provider: &vault.Provider{}, + secrets: []provider.Secret{ + { + Path: "MYSQL_PASSWORD", + Value: "3xtr3ms3cr3t", + }, + { + Path: "AWS_SECRET_ACCESS_KEY", + Value: "s3cr3t", + }, + }, + wantSecrets: []string{ + "MYSQL_PASSWORD=3xtr3ms3cr3t", + "AWS_SECRET_ACCESS_KEY=s3cr3t", + }, + }, + { + name: "File provider", + provider: &file.Provider{}, + secrets: []provider.Secret{ + { + Path: "test/secrets/mysql.txt", + Value: "3xtr3ms3cr3t", + }, + { + Path: "test/secrets/aws.txt", + Value: "s3cr3t", + }, + }, + wantSecrets: []string{ + "MYSQL_PASSWORD=3xtr3ms3cr3t", + "AWS_SECRET_ACCESS_KEY=s3cr3t", + }, + }, + { + name: "File provider - missing environment variable", + provider: &file.Provider{}, + secrets: []provider.Secret{ + { + Path: "test/secrets/mysql.txt", + Value: "3xtr3ms3cr3t", + }, + { + Path: "test/secrets/aws/invalid", + Value: "s3cr3t", + }, + }, + err: fmt.Errorf("failed to find environment variable key for secret path: test/secrets/aws/invalid"), + }, + } + + for _, tt := range tests { + ttp := tt + t.Run(ttp.name, func(t *testing.T) { + createEnvsForProvider(ttp.provider) + + envStore := NewEnvStore() + + secretsEnv, err := envStore.ConvertProviderSecrets(ttp.provider, ttp.secrets) + if err != nil { + assert.EqualError(t, ttp.err, err.Error(), "Unexpected error message") + } + + if ttp.wantSecrets != nil { + assert.Equal(t, ttp.wantSecrets, secretsEnv, "Unexpected secrets") + } + }) + } +} + +func createEnvsForProvider(provider provider.Provider) { + switch provider.GetProviderName() { + case vault.ProviderName: + os.Setenv("MYSQL_PASSWORD", "vault:secret/data/test/mysql#MYSQL_PASSWORD") + os.Setenv("AWS_SECRET_ACCESS_KEY", "vault:secret/data/test/aws#AWS_SECRET_ACCESS_KEY") + case file.ProviderName: + os.Setenv("MYSQL_PASSWORD", "file:test/secrets/mysql.txt") + os.Setenv("AWS_SECRET_ACCESS_KEY", "file:test/secrets/aws.txt") + } +} diff --git a/go.mod b/go.mod index 2818680..142f585 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,112 @@ module github.com/bank-vaults/secret-init go 1.21 require ( + emperror.dev/errors v0.8.1 + github.com/hashicorp/vault/api v1.10.0 github.com/samber/slog-multi v1.0.2 github.com/samber/slog-syslog v1.0.0 github.com/spf13/cast v1.6.0 ) require ( + cloud.google.com/go v0.110.7 // indirect + cloud.google.com/go/compute v1.23.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.3 // indirect + cloud.google.com/go/kms v1.15.2 // indirect + cloud.google.com/go/storage v1.31.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/aws/aws-sdk-go v1.47.5 // indirect + github.com/aws/aws-sdk-go-v2 v1.20.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11 // indirect + github.com/aws/aws-sdk-go-v2/config v1.18.32 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.31 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.76 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.37 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.31 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.38 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.12 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.32 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.31 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.0 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.38.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.21.1 // indirect + github.com/aws/smithy-go v1.14.0 // indirect + github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.1 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/google/wire v0.5.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.2 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/huandu/xstrings v1.3.3 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/leosayous21/go-azure-msi v0.0.0-20210509193526-19353bedcfc8 // indirect + github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.0 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + go.opencensus.io v0.24.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + gocloud.dev v0.34.0 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.13.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/api v0.142.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb // indirect + google.golang.org/grpc v1.57.1 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( + github.com/bank-vaults/internal v0.2.0 + github.com/bank-vaults/vault-sdk v0.9.1 github.com/samber/lo v1.38.1 // indirect github.com/stretchr/testify v1.8.4 - golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect ) diff --git a/go.sum b/go.sum index aec0008..f4465fd 100644 --- a/go.sum +++ b/go.sum @@ -1,30 +1,390 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o= +cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= +cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= +cloud.google.com/go/kms v1.15.2 h1:lh6qra6oC4AyWe5fUUUBe/S27k12OHAleOOOw6KakdE= +cloud.google.com/go/kms v1.15.2/go.mod h1:3hopT4+7ooWRCjc2DxgnpESFxhIraaI2IpAVUEhbT/w= +cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI= +cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0= +emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0= +emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 h1:nVocQV40OQne5613EeLayJiRAJuKlBGy+m22qWG+WRg= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.47.5 h1:U2JlfPmrUoz5p+2X/XwKxmaJFo2oV+LbJqx8jyEvyAY= +github.com/aws/aws-sdk-go v1.47.5/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v1.20.0 h1:INUDpYLt4oiPOJl0XwZDK2OVAVf0Rzo+MGVTv9f+gy8= +github.com/aws/aws-sdk-go-v2 v1.20.0/go.mod h1:uWOr0m0jDsiWw8nnXiqZ+YG6LdvAlGYDLLf2NmHZoy4= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11 h1:/MS8AzqYNAhhRNalOmxUvYs8VEbNGifTnzhPFdcRQkQ= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11/go.mod h1:va22++AdXht4ccO3kH2SHkHHYvZ2G9Utz+CXKmm2CaU= +github.com/aws/aws-sdk-go-v2/config v1.18.32 h1:tqEOvkbTxwEV7hToRcJ1xZRjcATqwDVsWbAscgRKyNI= +github.com/aws/aws-sdk-go-v2/config v1.18.32/go.mod h1:U3ZF0fQRRA4gnbn9GGvOWLoT2EzzZfAWeKwnVrm1rDc= +github.com/aws/aws-sdk-go-v2/credentials v1.13.31 h1:vJyON3lG7R8VOErpJJBclBADiWTwzcwdkQpTKx8D2sk= +github.com/aws/aws-sdk-go-v2/credentials v1.13.31/go.mod h1:T4sESjBtY2lNxLgkIASmeP57b5j7hTQqCbqG0tWnxC4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.7 h1:X3H6+SU21x+76LRglk21dFRgMTJMa5QcpW+SqUf5BBg= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.7/go.mod h1:3we0V09SwcJBzNlnyovrR2wWJhWmVdqAsmVs4uronv8= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.76 h1:DJ1kHj0GI9BbX+XhF0kHxlzOVjcncmDUXmCvXdbfdAE= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.76/go.mod h1:/AZCdswMSgwpB2yMSFfY5H4pVeBLnCuPehdmO/r3xSM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.37 h1:zr/gxAZkMcvP71ZhQOcvdm8ReLjFgIXnIn0fw5AM7mo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.37/go.mod h1:Pdn4j43v49Kk6+82spO3Tu5gSeQXRsxo56ePPQAvFiA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.31 h1:0HCMIkAkVY9KMgueD8tf4bRTUanzEYvhw7KkPXIMpO0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.31/go.mod h1:fTJDMe8LOFYtqiFFFeHA+SVMAwqLhoq0kcInYoLa9Js= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.38 h1:+i1DOFrW3YZ3apE45tCal9+aDKK6kNEbW6Ib7e1nFxE= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.38/go.mod h1:1/jLp0OgOaWIetycOmycW+vYTYgTZFPttJQRgsI1PoU= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.0 h1:U5yySdwt2HPo/pnQec04DImLzWORbeWML1fJiLkKruI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.0/go.mod h1:EhC/83j8/hL/UB1WmExo3gkElaja/KlmZM/gl1rTfjM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.12 h1:uAiiHnWihGP2rVp64fHwzLDrswGjEjsPszwRYMiYQPU= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.12/go.mod h1:fUTHpOXqRQpXvEpDPSa3zxCc2fnpW6YnBoba+eQr+Bg= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.32 h1:kvN1jPHr9UffqqG3bSgZ8tx4+1zKVHz/Ktw/BwW6hX8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.32/go.mod h1:QmMEM7es84EUkbYWcpnkx8i5EW2uERPfrTFeOch128Y= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.31 h1:auGDJ0aLZahF5SPvkJ6WcUuX7iQ7kyl2MamV7Tm8QBk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.31/go.mod h1:3+lloe3sZuBQw1aBc5MyndvodzQlyqCZ7x1QPDHaWP4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.0 h1:Wgjft9X4W5pMeuqgPCHIQtbZ87wsgom7S5F8obreg+c= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.0/go.mod h1:FWNzS4+zcWAP05IF7TDYTY1ysZAzIvogxWaDT9p8fsA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.38.1 h1:mTgFVlfQT8gikc5+/HwD8UL9jnUro5MGv8n/VEYF12I= +github.com/aws/aws-sdk-go-v2/service/s3 v1.38.1/go.mod h1:6SOWLiobcZZshbmECRTADIRYliPL0etqFSigauQEeT0= +github.com/aws/aws-sdk-go-v2/service/sso v1.13.1 h1:DSNpSbfEgFXRV+IfEcKE5kTbqxm+MeF5WgyeRlsLnHY= +github.com/aws/aws-sdk-go-v2/service/sso v1.13.1/go.mod h1:TC9BubuFMVScIU+TLKamO6VZiYTkYoEHqlSQwAe2omw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.1 h1:hd0SKLMdOL/Sl6Z0np1PX9LeH2gqNtBe0MhTedA8MGI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.1/go.mod h1:XO/VcyoQ8nKyKfFW/3DMsRQXsfh/052tHTWmg3xBXRg= +github.com/aws/aws-sdk-go-v2/service/sts v1.21.1 h1:pAOJj+80tC8sPVgSDHzMYD6KLWsaLQ1kZw31PTeORbs= +github.com/aws/aws-sdk-go-v2/service/sts v1.21.1/go.mod h1:G8SbvL0rFk4WOJroU8tKBczhsbhj2p/YY7qeJezJ3CI= +github.com/aws/smithy-go v1.14.0 h1:+X90sB94fizKjDmwb4vyl2cTTPXTE5E2G/1mjByb0io= +github.com/aws/smithy-go v1.14.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/bank-vaults/internal v0.2.0 h1:5XsIXopabKXBokZKywz6wo2IxpQz/DLRAUpZfY5M8rY= +github.com/bank-vaults/internal v0.2.0/go.mod h1:/RhggnxWFLNMDuADB/uSrYwIZW8rQ4v39Jc5IFLyokc= +github.com/bank-vaults/vault-sdk v0.9.1 h1:zjb6a9re2bms9EZLoUY/EseTxE0ojXJYrQ6hg675uVk= +github.com/bank-vaults/vault-sdk v0.9.1/go.mod h1:HIe7MEwwyseIDO2iT0b1GokTgy/pf0C/ntkCHQ0gQiY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= +github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE= +github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk= +github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk= +github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= +github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= +github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= +github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +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.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +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.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +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/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= +github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leosayous21/go-azure-msi v0.0.0-20210509193526-19353bedcfc8 h1:C9EWiKUP5Hrm0eHxF63E2TpCUj3047oCZXrUM2T8Mnw= +github.com/leosayous21/go-azure-msi v0.0.0-20210509193526-19353bedcfc8/go.mod h1:GfJ7YCWVSRJBC6YwUyO1Is2v+HaTrwR3yMfS92tIIWo= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/ryanuber/columnize v2.1.0+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/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/slog-multi v1.0.2 h1:6BVH9uHGAsiGkbbtQgAOQJMpKgV8unMrHhhJaw+X1EQ= github.com/samber/slog-multi v1.0.2/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= github.com/samber/slog-syslog v1.0.0 h1:4tf8sNv9+qTQ6Fj8+N6U1ZEtUbqbAIzd+q26/NegWFM= github.com/samber/slog-syslog v1.0.0/go.mod h1:jjupk+yHPVSuXuGhKleoClYc/HEaC+Ro5X4YYeBrt6g= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +gocloud.dev v0.34.0 h1:LzlQY+4l2cMtuNfwT2ht4+fiXwWf/NmPTnXUlLmGif4= +gocloud.dev v0.34.0/go.mod h1:psKOachbnvY3DAOPbsFVmLIErwsbWPUG2H5i65D38vE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.142.0 h1:mf+7EJ94fi5ZcnpPy+m0Yv2dkz8bKm+UL0snTCuwXlY= +google.golang.org/api v0.142.0/go.mod h1:zJAN5o6HRqR7O+9qJUFOWrZkYE66RH+efPBdTLA4xBA= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= +google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= +google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 h1:nIgk/EEq3/YlnmVVXVnm14rC2oxgs1o0ong4sD/rd44= +google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb h1:Isk1sSH7bovx8Rti2wZK0UZF6oraBDK74uoyLEEVFN0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg= +google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index 5c42d0d..bcfd8b4 100644 --- a/main.go +++ b/main.go @@ -24,162 +24,121 @@ import ( "os/exec" "os/signal" "slices" - "syscall" "time" slogmulti "github.com/samber/slog-multi" slogsyslog "github.com/samber/slog-syslog" - "github.com/spf13/cast" + "github.com/bank-vaults/secret-init/common" "github.com/bank-vaults/secret-init/provider" "github.com/bank-vaults/secret-init/provider/file" + "github.com/bank-vaults/secret-init/provider/vault" ) func NewProvider(providerName string) (provider.Provider, error) { switch providerName { case file.ProviderName: - provider, err := file.NewProvider(os.DirFS("/")) + config := file.LoadConfig() + provider, err := file.NewProvider(config) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create file provider: %w", err) } - return provider, nil - default: - return nil, errors.New("invalid provider specified") - } -} - -func main() { - var logger *slog.Logger - { - var level slog.Level - - err := level.UnmarshalText([]byte(os.Getenv("VAULT_LOG_LEVEL"))) - if err != nil { // Silently fall back to info level - level = slog.LevelInfo - } - - levelFilter := func(levels ...slog.Level) func(ctx context.Context, r slog.Record) bool { - return func(ctx context.Context, r slog.Record) bool { - return slices.Contains(levels, r.Level) - } - } - - router := slogmulti.Router() - - if cast.ToBool(os.Getenv("VAULT_JSON_LOG")) { - // Send logs with level higher than warning to stderr - router = router.Add( - slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelWarn}), - levelFilter(slog.LevelWarn, slog.LevelError), - ) - - // Send info and debug logs to stdout - router = router.Add( - slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}), - levelFilter(slog.LevelDebug, slog.LevelInfo), - ) - } else { - // Send logs with level higher than warning to stderr - router = router.Add( - slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelWarn}), - levelFilter(slog.LevelWarn, slog.LevelError), - ) - - // Send info and debug logs to stdout - router = router.Add( - slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}), - levelFilter(slog.LevelDebug, slog.LevelInfo), - ) + case vault.ProviderName: + config, err := vault.LoadConfig() + if err != nil { + return nil, fmt.Errorf("failed to create vault config: %w", err) } - if logServerAddr := os.Getenv("VAULT_ENV_LOG_SERVER"); logServerAddr != "" { - writer, err := net.Dial("udp", logServerAddr) - - // We silently ignore syslog connection errors for the lack of a better solution - if err == nil { - router = router.Add(slogsyslog.Option{Level: slog.LevelInfo, Writer: writer}.NewSyslogHandler()) - } + provider, err := vault.NewProvider(config) + if err != nil { + return nil, fmt.Errorf("failed to create vault provider: %w", err) } + return provider, nil - // TODO: add level filter handler - logger = slog.New(router.Handler()) - logger = logger.With(slog.String("app", "vault-secret-init")) - - slog.SetDefault(logger) + default: + return nil, fmt.Errorf("provider %s not supported", providerName) } +} - provider, err := NewProvider(os.Getenv("PROVIDER")) +func main() { + // Load application config + config, err := common.LoadConfig() if err != nil { - logger.Error(fmt.Errorf("failed to create provider: %w", err).Error()) - + slog.Error(fmt.Errorf("failed to load config: %w", err).Error()) os.Exit(1) } - if len(os.Args) == 1 { - logger.Error("no command is given, vault-env can't determine the entrypoint (command), please specify it explicitly or let the webhook query it (see documentation)") + initLogger(config) + // Get entrypoint data from arguments + binaryPath, binaryArgs, err := ExtractEntrypoint(os.Args) + if err != nil { + slog.Error(fmt.Errorf("failed to extract entrypoint: %w", err).Error()) os.Exit(1) } - daemonMode := cast.ToBool(os.Getenv("VAULT_ENV_DAEMON")) - delayExec := cast.ToDuration(os.Getenv("VAULT_ENV_DELAY")) - - entrypointCmd := os.Args[1:] - - binary, err := exec.LookPath(entrypointCmd[0]) + // Create requested provider and extract relevant secret data + // TODO(csatib02): Implement multi-provider support + provider, err := NewProvider(config.Provider) if err != nil { - logger.Error("binary not found", slog.String("binary", entrypointCmd[0])) - + slog.Error(fmt.Errorf("failed to create provider: %w", err).Error()) os.Exit(1) } - environ := GetEnvironMap() - paths := ExtractPathsFromEnvs(environ) + envStore := NewEnvStore() - ctx := context.Background() - secrets, err := provider.LoadSecrets(ctx, paths) + providerPaths, err := envStore.GetProviderPaths(provider) if err != nil { - logger.Error(fmt.Errorf("failed to load secrets from provider: %w", err).Error()) - + slog.Error(fmt.Errorf("failed to extract paths: %w", err).Error()) os.Exit(1) } - secretsEnv, err := CreateSecretEnvsFrom(environ, secrets) - if err != nil { - logger.Error(fmt.Errorf("failed to create environment variables from loaded secrets: %w", err).Error()) + providerSecrets, err := provider.LoadSecrets(context.Background(), providerPaths) + if err != nil { + slog.Error(fmt.Errorf("failed to load secrets: %w", err).Error()) os.Exit(1) } - sigs := make(chan os.Signal, 1) + secretsEnv, err := envStore.ConvertProviderSecrets(provider, providerSecrets) + if err != nil { + slog.Error(fmt.Errorf("failed to convert secrets to envs: %w", err).Error()) + os.Exit(1) + } - if delayExec > 0 { - logger.Info(fmt.Sprintf("sleeping for %s...", delayExec)) - time.Sleep(delayExec) + // Delay if needed + // NOTE(ramizpolic): any specific reason why this is here? + if config.Delay > 0 { + slog.Info(fmt.Sprintf("sleeping for %s...", config.Delay)) + time.Sleep(config.Delay) } - logger.Info("spawning process", slog.String("entrypoint", fmt.Sprint(entrypointCmd))) + slog.Info("spawning process for provided entrypoint command") - if daemonMode { - logger.Info("in daemon mode...") - cmd := exec.Command(binary, entrypointCmd[1:]...) - cmd.Env = append(os.Environ(), secretsEnv...) - cmd.Stdin = os.Stdin - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout + cmd := exec.Command(binaryPath, binaryArgs...) + cmd.Env = append(os.Environ(), secretsEnv...) + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout - signal.Notify(sigs) + sigs := make(chan os.Signal, 1) + signal.Notify(sigs) - err = cmd.Start() - if err != nil { - logger.Error(fmt.Errorf("failed to start process: %w", err).Error(), slog.String("entrypoint", fmt.Sprint(entrypointCmd))) + err = cmd.Start() + if err != nil { + slog.Error(fmt.Errorf("failed to start process: %w", err).Error()) + os.Exit(1) + } - os.Exit(1) - } + if config.Daemon { + // in daemon mode, pass signals to the actual process + slog.Info("running in daemon mode") go func() { for sig := range sigs { + slog.Info("received signal", slog.String("signal", sig.String())) + // We don't want to signal a non-running process. if cmd.ProcessState != nil && cmd.ProcessState.Exited() { break @@ -187,36 +146,90 @@ func main() { err := cmd.Process.Signal(sig) if err != nil { - logger.Warn(fmt.Errorf("failed to signal process: %w", err).Error(), slog.String("signal", sig.String())) - } else { - logger.Info("received signal", slog.String("signal", sig.String())) + slog.Warn( + fmt.Errorf("failed to signal process: %w", err).Error(), + slog.String("signal", sig.String()), + ) } } }() + } - err = cmd.Wait() + err = cmd.Wait() - close(sigs) + close(sigs) - if err != nil { - exitCode := -1 - // try to get the original exit code if possible - var exitError *exec.ExitError - if errors.As(err, &exitError) { - exitCode = exitError.ExitCode() - } + if err != nil { + slog.Error(fmt.Errorf("failed to exec process: %w", err).Error()) - logger.Error(fmt.Errorf("failed to exec process: %w", err).Error(), slog.String("entrypoint", fmt.Sprint(entrypointCmd))) + // Exit with the original exit code if possible + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + os.Exit(exitErr.ExitCode()) + } - os.Exit(exitCode) + os.Exit(-1) + } + + os.Exit(cmd.ProcessState.ExitCode()) +} + +func initLogger(config *common.Config) { + var level slog.Level + + err := level.UnmarshalText([]byte(config.LogLevel)) + if err != nil { // Silently fall back to info level + level = slog.LevelInfo + } + + levelFilter := func(levels ...slog.Level) func(ctx context.Context, r slog.Record) bool { + return func(ctx context.Context, r slog.Record) bool { + return slices.Contains(levels, r.Level) } + } - os.Exit(cmd.ProcessState.ExitCode()) + router := slogmulti.Router() + + if config.JSONLog { + // Send logs with level higher than warning to stderr + router = router.Add( + slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelWarn}), + levelFilter(slog.LevelWarn, slog.LevelError), + ) + + // Send info and debug logs to stdout + router = router.Add( + slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}), + levelFilter(slog.LevelDebug, slog.LevelInfo), + ) + } else { + // Send logs with level higher than warning to stderr + router = router.Add( + slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelWarn}), + levelFilter(slog.LevelWarn, slog.LevelError), + ) + + // Send info and debug logs to stdout + router = router.Add( + slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}), + levelFilter(slog.LevelDebug, slog.LevelInfo), + ) } - err = syscall.Exec(binary, entrypointCmd, secretsEnv) - if err != nil { - logger.Error(fmt.Errorf("failed to exec process: %w", err).Error(), slog.String("entrypoint", fmt.Sprint(entrypointCmd))) - os.Exit(1) + if config.LogServer != "" { + writer, err := net.Dial("udp", config.LogServer) + + // We silently ignore syslog connection errors for the lack of a better solution + if err == nil { + router = router.Add(slogsyslog.Option{Level: slog.LevelInfo, Writer: writer}.NewSyslogHandler()) + } } + + // TODO: add level filter handler + logger := slog.New(router.Handler()) + logger = logger.With(slog.String("app", "vault-secret-init")) + + // Set the default logger to the configured logger, + // enabling direct usage of the slog package for logging. + slog.SetDefault(logger) } diff --git a/provider/file/config.go b/provider/file/config.go new file mode 100644 index 0000000..9eeff89 --- /dev/null +++ b/provider/file/config.go @@ -0,0 +1,40 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 file + +import ( + "log/slog" + "os" +) + +const ( + defaultMountPath = "/" + + MountPathEnv = "FILE_MOUNT_PATH" +) + +type Config struct { + MountPath string `json:"mount_path"` +} + +func LoadConfig() *Config { + mountPath, ok := os.LookupEnv(MountPathEnv) + if !ok { + slog.Warn("file provider mount path not provided, using default", slog.String("mount-path", defaultMountPath)) + mountPath = defaultMountPath + } + + return &Config{MountPath: mountPath} +} diff --git a/provider/file/config_test.go b/provider/file/config_test.go new file mode 100644 index 0000000..01d071b --- /dev/null +++ b/provider/file/config_test.go @@ -0,0 +1,61 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 file + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfig(t *testing.T) { + tests := []struct { + name string + env map[string]string + wantMountPath string + }{ + { + name: "Default mount path", + env: map[string]string{}, + wantMountPath: "/", + }, + { + name: "Custom mount path", + env: map[string]string{ + MountPathEnv: "/test/secrets", + }, + wantMountPath: "/test/secrets", + }, + } + + for _, tt := range tests { + ttp := tt + t.Run(ttp.name, func(t *testing.T) { + for envKey, envVal := range ttp.env { + os.Setenv(envKey, envVal) + } + + config := LoadConfig() + + assert.Equal(t, ttp.wantMountPath, config.MountPath, "Unexpected mount path") + + // unset envs for the next test + for envKey := range ttp.env { + os.Unsetenv(envKey) + } + }) + } +} diff --git a/provider/file/file.go b/provider/file/file.go index ba6a080..5d6cc35 100644 --- a/provider/file/file.go +++ b/provider/file/file.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "io/fs" + "os" "strings" "github.com/bank-vaults/secret-init/provider" @@ -29,12 +30,18 @@ type Provider struct { fs fs.FS } -func NewProvider(fs fs.FS) (provider.Provider, error) { - if fs == nil { - return nil, fmt.Errorf("file system is nil") +func NewProvider(config *Config) (provider.Provider, error) { + // Check whether the path exists + fileInfo, err := os.Stat(config.MountPath) + if err != nil { + return nil, fmt.Errorf("failed to access path: %w", err) + } + + if !fileInfo.IsDir() { + return nil, fmt.Errorf("provided path is not a directory") } - return &Provider{fs: fs}, nil + return &Provider{fs: os.DirFS(config.MountPath)}, nil } func (p *Provider) LoadSecrets(_ context.Context, paths []string) ([]provider.Secret, error) { @@ -55,9 +62,13 @@ func (p *Provider) LoadSecrets(_ context.Context, paths []string) ([]provider.Se return secrets, nil } -func (p *Provider) getSecretFromFile(filepath string) (string, error) { - filepath = strings.TrimLeft(filepath, "/") - content, err := fs.ReadFile(p.fs, filepath) +func (p *Provider) GetProviderName() string { + return ProviderName +} + +func (p *Provider) getSecretFromFile(path string) (string, error) { + path = strings.TrimLeft(path, "/") + content, err := fs.ReadFile(p.fs, path) if err != nil { return "", fmt.Errorf("failed to read file: %w", err) } diff --git a/provider/file/file_test.go b/provider/file/file_test.go index 50ddddb..a70c1c5 100644 --- a/provider/file/file_test.go +++ b/provider/file/file_test.go @@ -16,7 +16,9 @@ package file import ( "context" + "fmt" "io/fs" + "os" "testing" "testing/fstest" @@ -26,42 +28,54 @@ import ( ) func TestNewProvider(t *testing.T) { + tempDir := t.TempDir() + secretFile := newSecretFile(t, "3xtr3ms3cr3t") + defer os.Remove(secretFile) + tests := []struct { name string - fs fs.FS - wantErr bool + config *Config + err error wantType bool + wantFs fs.FS }{ { - name: "Valid file system", - fs: fstest.MapFS{ - "test/secrets/sqlpass.txt": &fstest.MapFile{Data: []byte("3xtr3ms3cr3t")}, - "test/secrets/awsaccess.txt": &fstest.MapFile{Data: []byte("s3cr3t")}, - "test/secrets/awsid.txt": &fstest.MapFile{Data: []byte("secretId")}, + name: "Valid config - directory", + config: &Config{ + MountPath: tempDir, }, - wantErr: false, wantType: true, + wantFs: os.DirFS(tempDir), + }, + { + name: "Invalid config - directory does not exist", + config: &Config{ + MountPath: "test/secrets/invalid", + }, + err: fmt.Errorf("failed to access path: stat test/secrets/invalid: no such file or directory"), }, { - name: "Nil file system", - fs: nil, - wantErr: true, - wantType: false, + name: "Invalid config - file instead of directory", + config: &Config{ + MountPath: secretFile, + }, + err: fmt.Errorf("provided path is not a directory"), }, } for _, tt := range tests { ttp := tt t.Run(ttp.name, func(t *testing.T) { - prov, err := NewProvider(ttp.fs) - if (err != nil) != ttp.wantErr { - t.Fatalf("NewProvider() error = %v, wantErr %v", err, ttp.wantErr) - return + provider, err := NewProvider(ttp.config) + if err != nil { + assert.EqualError(t, err, ttp.err.Error(), "Unexpected error message") } - // Use type assertion to check if the provider is of the correct type - _, ok := prov.(*Provider) - if ok != ttp.wantType { - t.Fatalf("NewProvider() = %v, wantType %v", ok, ttp.wantType) + if ttp.wantType { + assert.Equal(t, ttp.wantType, provider != nil, "Unexpected provider type") + + if ttp.wantFs != nil { + assert.Equal(t, ttp.wantFs, provider.(*Provider).fs, "Unexpected file system") + } } }) } @@ -69,26 +83,19 @@ func TestNewProvider(t *testing.T) { func TestLoadSecrets(t *testing.T) { tests := []struct { - name string - fs fs.FS - paths []string - wantErr bool - wantData []provider.Secret + name string + paths []string + err error + wantSecrets []provider.Secret }{ { name: "Load secrets successfully", - fs: fstest.MapFS{ - "test/secrets/sqlpass.txt": &fstest.MapFile{Data: []byte("3xtr3ms3cr3t")}, - "test/secrets/awsaccess.txt": &fstest.MapFile{Data: []byte("s3cr3t")}, - "test/secrets/awsid.txt": &fstest.MapFile{Data: []byte("secretId")}, - }, paths: []string{ "test/secrets/sqlpass.txt", "test/secrets/awsaccess.txt", "test/secrets/awsid.txt", }, - wantErr: false, - wantData: []provider.Secret{ + wantSecrets: []provider.Secret{ {Path: "test/secrets/sqlpass.txt", Value: "3xtr3ms3cr3t"}, {Path: "test/secrets/awsaccess.txt", Value: "s3cr3t"}, {Path: "test/secrets/awsid.txt", Value: "secretId"}, @@ -96,30 +103,46 @@ func TestLoadSecrets(t *testing.T) { }, { name: "Fail to load secrets due to invalid path", - fs: fstest.MapFS{ - "test/secrets/sqlpass.txt": &fstest.MapFile{Data: []byte("3xtr3ms3cr3t")}, - "test/secrets/awsaccess.txt": &fstest.MapFile{Data: []byte("s3cr3t")}, - "test/secrets/awsid.txt": &fstest.MapFile{Data: []byte("secretId")}, - }, paths: []string{ "test/secrets/mistake/sqlpass.txt", "test/secrets/mistake/awsaccess.txt", "test/secrets/mistake/awsid.txt", }, - wantErr: true, - wantData: nil, + err: fmt.Errorf("failed to get secret from file: failed to read file: open test/secrets/mistake/sqlpass.txt: file does not exist"), }, } for _, tt := range tests { ttp := tt t.Run(ttp.name, func(t *testing.T) { - provider, err := NewProvider(ttp.fs) - if assert.NoError(t, err, "Unexpected error") { - secrets, err := provider.LoadSecrets(context.Background(), ttp.paths) - assert.Equal(t, ttp.wantErr, err != nil, "Unexpected error status") - assert.ElementsMatch(t, ttp.wantData, secrets, "Unexpected secrets loaded") + fs := fstest.MapFS{ + "test/secrets/sqlpass.txt": {Data: []byte("3xtr3ms3cr3t")}, + "test/secrets/awsaccess.txt": {Data: []byte("s3cr3t")}, + "test/secrets/awsid.txt": {Data: []byte("secretId")}, + } + provider := Provider{fs: fs} + secrets, err := provider.LoadSecrets(context.Background(), ttp.paths) + if err != nil { + assert.EqualError(t, ttp.err, err.Error(), "Unexpected error message") + } + if ttp.wantSecrets != nil { + assert.ElementsMatch(t, ttp.wantSecrets, secrets, "Unexpected secrets") } }) } } + +func newSecretFile(t *testing.T, content string) string { + dir := t.TempDir() + "/test/secrets" + err := os.MkdirAll(dir, 0755) + assert.Nil(t, err, "Failed to create directory") + + file, err := os.CreateTemp(dir, "secret.txt") + assert.Nil(t, err, "Failed to create a temporary file") + defer file.Close() + + _, err = file.WriteString(content) + assert.Nil(t, err, "Failed to write to the temporary file") + + return file.Name() +} diff --git a/provider/provider.go b/provider/provider.go index 14e07fc..e484070 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -19,6 +19,7 @@ import "context" // Provider is an interface for securely loading secrets based on environment variables. type Provider interface { LoadSecrets(ctx context.Context, paths []string) ([]Secret, error) + GetProviderName() string } // Secret holds Provider-specific secret data. diff --git a/provider/vault/client_logger.go b/provider/vault/client_logger.go new file mode 100644 index 0000000..da9237e --- /dev/null +++ b/provider/vault/client_logger.go @@ -0,0 +1,59 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 vault + +import ( + "log/slog" + + "github.com/bank-vaults/vault-sdk/vault" +) + +var _ vault.Logger = &clientLogger{} + +type clientLogger struct { + logger *slog.Logger +} + +func (l clientLogger) Trace(msg string, args ...map[string]interface{}) { + l.Debug(msg, args...) +} + +func (l clientLogger) Debug(msg string, args ...map[string]interface{}) { + l.logger.Debug(msg, l.argsToAttrs(args...)...) +} + +func (l clientLogger) Info(msg string, args ...map[string]interface{}) { + l.logger.Info(msg, l.argsToAttrs(args...)...) +} + +func (l clientLogger) Warn(msg string, args ...map[string]interface{}) { + l.logger.Warn(msg, l.argsToAttrs(args...)...) +} + +func (l clientLogger) Error(msg string, args ...map[string]interface{}) { + l.logger.Error(msg, l.argsToAttrs(args...)...) +} + +func (clientLogger) argsToAttrs(args ...map[string]interface{}) []any { + var attrs []any + + for _, arg := range args { + for key, value := range arg { + attrs = append(attrs, slog.Any(key, value)) + } + } + + return attrs +} diff --git a/provider/vault/config.go b/provider/vault/config.go new file mode 100644 index 0000000..b8b69bc --- /dev/null +++ b/provider/vault/config.go @@ -0,0 +1,182 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 vault + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cast" +) + +const ( + // The special value for VAULT_TOKEN which marks + // that the login token needs to be passed through to the application + // which was acquired during the vault client initialization. + vaultLogin = "vault:login" + + TokenEnv = "VAULT_TOKEN" + TokenFileEnv = "VAULT_TOKEN_FILE" + AddrEnv = "VAULT_ADDR" + AgentAddrEnv = "VAULT_AGENT_ADDR" + CACertEnv = "VAULT_CACERT" + CAPathEnv = "VAULT_CAPATH" + ClientCertEnv = "VAULT_CLIENT_CERT" + ClientKeyEnv = "VAULT_CLIENT_KEY" + ClientTimeoutEnv = "VAULT_CLIENT_TIMEOUT" + SRVLookupEnv = "VAULT_SRV_LOOKUP" + SkipVerifyEnv = "VAULT_SKIP_VERIFY" + NamespaceEnv = "VAULT_NAMESPACE" + TLSServerNameEnv = "VAULT_TLS_SERVER_NAME" + WrapTTLEnv = "VAULT_WRAP_TTL" + MFAEnv = "VAULT_MFA" + MaxRetriesEnv = "VAULT_MAX_RETRIES" + ClusterAddrEnv = "VAULT_CLUSTER_ADDR" + RedirectAddrEnv = "VAULT_REDIRECT_ADDR" + CLINoColorEnv = "VAULT_CLI_NO_COLOR" + RateLimitEnv = "VAULT_RATE_LIMIT" + RoleEnv = "VAULT_ROLE" + PathEnv = "VAULT_PATH" + AuthMethodEnv = "VAULT_AUTH_METHOD" + TransitKeyIDEnv = "VAULT_TRANSIT_KEY_ID" + TransitPathEnv = "VAULT_TRANSIT_PATH" + TransitBatchSizeEnv = "VAULT_TRANSIT_BATCH_SIZE" + IgnoreMissingSecretsEnv = "VAULT_IGNORE_MISSING_SECRETS" + PassthroughEnv = "VAULT_PASSTHROUGH" + RevokeTokenEnv = "VAULT_REVOKE_TOKEN" + FromPathEnv = "VAULT_FROM_PATH" +) + +type Config struct { + IsLogin bool `json:"is_login"` + Token string `json:"token"` + TokenFile string `json:"token_file"` + Role string `json:"role"` + AuthPath string `json:"auth_path"` + AuthMethod string `json:"auth_method"` + TransitKeyID string `json:"transit_key_id"` + TransitPath string `json:"transit_path"` + TransitBatchSize int `json:"transit_batch_size"` + IgnoreMissingSecrets bool `json:"ignore_missing_secrets"` + FromPath string `json:"from_path"` + RevokeToken bool `json:"revoke_token"` +} + +type envType struct { + login bool +} + +var sanitizeEnvmap = map[string]envType{ + TokenEnv: {login: true}, + AddrEnv: {login: true}, + AgentAddrEnv: {login: true}, + CACertEnv: {login: true}, + CAPathEnv: {login: true}, + ClientCertEnv: {login: true}, + ClientKeyEnv: {login: true}, + ClientTimeoutEnv: {login: true}, + SRVLookupEnv: {login: true}, + SkipVerifyEnv: {login: true}, + NamespaceEnv: {login: true}, + TLSServerNameEnv: {login: true}, + WrapTTLEnv: {login: true}, + MFAEnv: {login: true}, + MaxRetriesEnv: {login: true}, + ClusterAddrEnv: {login: false}, + RedirectAddrEnv: {login: false}, + CLINoColorEnv: {login: false}, + RateLimitEnv: {login: false}, + RoleEnv: {login: false}, + PathEnv: {login: false}, + AuthMethodEnv: {login: false}, + TransitKeyIDEnv: {login: false}, + TransitPathEnv: {login: false}, + TransitBatchSizeEnv: {login: false}, + IgnoreMissingSecretsEnv: {login: false}, + PassthroughEnv: {login: false}, + RevokeTokenEnv: {login: false}, + FromPathEnv: {login: false}, +} + +func LoadConfig() (*Config, error) { + var ( + role, authPath, authMethod string + hasRole, hasPath, hasAuthMethod bool + ) + + // The login procedure takes the token from a file (if using Vault Agent) + // or requests one for itself (Kubernetes Auth, or GCP, etc...), + // so if we got a VAULT_TOKEN for the special value with "vault:login" + vaultToken := os.Getenv(TokenEnv) + isLogin := vaultToken == vaultLogin + tokenFile, ok := os.LookupEnv(TokenFileEnv) + if ok { + // load token from vault-agent .vault-token or injected webhook + tokenFileContent, err := os.ReadFile(tokenFile) + if err != nil { + return nil, fmt.Errorf("failed to read token file %s: %w", tokenFile, err) + } + vaultToken = string(tokenFileContent) + } else { + if isLogin { + _ = os.Unsetenv(TokenEnv) + } + + // will use role/path based authentication + role, hasRole = os.LookupEnv(RoleEnv) + if !hasRole { + return nil, fmt.Errorf("incomplete authentication configuration: %s missing", RoleEnv) + } + authPath, hasPath = os.LookupEnv(PathEnv) + if !hasPath { + return nil, fmt.Errorf("incomplete authentication configuration: %s missing", PathEnv) + } + authMethod, hasAuthMethod = os.LookupEnv(AuthMethodEnv) + if !hasAuthMethod { + return nil, fmt.Errorf("incomplete authentication configuration: %s missing", AuthMethodEnv) + } + } + + passthroughEnvVars := strings.Split(os.Getenv(PassthroughEnv), ",") + if isLogin { + _ = os.Setenv(TokenEnv, vaultLogin) + passthroughEnvVars = append(passthroughEnvVars, TokenEnv) + } + + // do not sanitize env vars specified in VAULT_PASSTHROUGH + for _, envVar := range passthroughEnvVars { + if trimmed := strings.TrimSpace(envVar); trimmed != "" { + delete(sanitizeEnvmap, trimmed) + } + } + + return &Config{ + IsLogin: isLogin, + Token: vaultToken, + TokenFile: tokenFile, + Role: role, + AuthPath: authPath, + AuthMethod: authMethod, + // injector configuration + TransitKeyID: os.Getenv(TransitKeyIDEnv), + TransitPath: os.Getenv(TransitPathEnv), + TransitBatchSize: cast.ToInt(os.Getenv(TransitBatchSizeEnv)), + // Used both for reading secrets and transit encryption + IgnoreMissingSecrets: cast.ToBool(os.Getenv(IgnoreMissingSecretsEnv)), + FromPath: os.Getenv(FromPathEnv), + RevokeToken: cast.ToBool(os.Getenv(RevokeTokenEnv)), + }, nil +} diff --git a/provider/vault/config_test.go b/provider/vault/config_test.go new file mode 100644 index 0000000..6e554f9 --- /dev/null +++ b/provider/vault/config_test.go @@ -0,0 +1,142 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 vault + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfig(t *testing.T) { + tokenFile := newTokenFile(t) + defer os.Remove(tokenFile) + + tests := []struct { + name string + env map[string]string + wantConfig *Config + err error + }{ + { + name: "Valid login configuration with Token", + env: map[string]string{ + TokenEnv: vaultLogin, + TokenFileEnv: tokenFile, + PassthroughEnv: AgentAddrEnv + ", " + CLINoColorEnv, + TransitKeyIDEnv: "test-key", + TransitPathEnv: "transit", + TransitBatchSizeEnv: "10", + IgnoreMissingSecretsEnv: "true", + RevokeTokenEnv: "true", + FromPathEnv: "secret/data/test", + }, + wantConfig: &Config{ + IsLogin: true, + Token: "root", + TokenFile: tokenFile, + TransitKeyID: "test-key", + TransitPath: "transit", + TransitBatchSize: 10, + IgnoreMissingSecrets: true, + FromPath: "secret/data/test", + RevokeToken: true, + }, + }, + { + name: "Valid login configuration with Role and Path", + env: map[string]string{ + TokenEnv: vaultLogin, + RoleEnv: "test-app-role", + PathEnv: "auth/approle/test/login", + AuthMethodEnv: "test-approle", + }, + wantConfig: &Config{ + IsLogin: true, + Token: vaultLogin, + Role: "test-app-role", + AuthPath: "auth/approle/test/login", + AuthMethod: "test-approle", + }, + }, + { + name: "Invalid login configuration using tokenfile - missing token file", + env: map[string]string{ + TokenFileEnv: tokenFile + "/invalid", + }, + err: fmt.Errorf("failed to read token file " + tokenFile + "/invalid: open " + tokenFile + "/invalid: not a directory"), + }, + { + name: "Invalid login configuration using role/path - missing role", + env: map[string]string{ + PathEnv: "auth/approle/test/login", + AuthMethodEnv: "k8s", + }, + err: fmt.Errorf("incomplete authentication configuration: VAULT_ROLE missing"), + }, + { + name: "Invalid login configuration using role/path - missing path", + env: map[string]string{ + RoleEnv: "test-app-role", + AuthMethodEnv: "k8s", + }, + err: fmt.Errorf("incomplete authentication configuration: VAULT_PATH missing"), + }, + { + name: "Invalid login configuration using role/path - missing auth method", + env: map[string]string{ + RoleEnv: "test-app-role", + PathEnv: "auth/approle/test/login", + }, + err: fmt.Errorf("incomplete authentication configuration: VAULT_AUTH_METHOD missing"), + }, + } + + for _, tt := range tests { + ttp := tt + t.Run(ttp.name, func(t *testing.T) { + for envKey, envVal := range ttp.env { + os.Setenv(envKey, envVal) + } + + config, err := LoadConfig() + if err != nil { + assert.EqualError(t, ttp.err, err.Error(), "Unexpected error message") + } + + if ttp.wantConfig != nil { + assert.Equal(t, ttp.wantConfig, config, "Unexpected config") + } + + // unset envs for the next test + for envKey := range ttp.env { + os.Unsetenv(envKey) + } + }) + } +} + +func newTokenFile(t *testing.T) string { + tokenFile, err := os.CreateTemp("", "vault-token") + assert.Nil(t, err, "Failed to create a temporary token file") + defer tokenFile.Close() + + _, err = tokenFile.Write([]byte("root")) + assert.Nil(t, err, "Failed to write to a temporary token file") + + return tokenFile.Name() +} diff --git a/provider/vault/daemon_secret_renewer.go b/provider/vault/daemon_secret_renewer.go new file mode 100644 index 0000000..2ed5ba5 --- /dev/null +++ b/provider/vault/daemon_secret_renewer.go @@ -0,0 +1,70 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 vault + +import ( + "log/slog" + "os" + "syscall" + "time" + + "emperror.dev/errors" + "github.com/bank-vaults/vault-sdk/vault" + vaultapi "github.com/hashicorp/vault/api" +) + +type daemonSecretRenewer struct { + client *vault.Client + sigs chan os.Signal +} + +func (r daemonSecretRenewer) Renew(path string, secret *vaultapi.Secret) error { + watcherInput := vaultapi.LifetimeWatcherInput{Secret: secret} + watcher, err := r.client.RawClient().NewLifetimeWatcher(&watcherInput) + if err != nil { + return errors.Wrap(err, "failed to create secret watcher") + } + + go watcher.Start() + + go func() { + defer watcher.Stop() + for { + select { + case renewOutput := <-watcher.RenewCh(): + slog.Info("secret renewed", slog.String("path", path), slog.Duration("lease-duration", time.Duration(renewOutput.Secret.LeaseDuration)*time.Second)) + case doneError := <-watcher.DoneCh(): + if !secret.Renewable { + leaseDuration := time.Duration(secret.LeaseDuration) * time.Second + time.Sleep(leaseDuration) + + slog.Info("secret lease has expired", slog.String("path", path), slog.Duration("lease-duration", leaseDuration)) + } + + slog.Info("secret renewal has stopped, sending SIGTERM to process", slog.String("path", path), slog.Any("done-error", doneError)) + + r.sigs <- syscall.SIGTERM + + timeout := <-time.After(10 * time.Second) + slog.Info("killing process due to SIGTERM timeout", slog.Time("timeout", timeout)) + r.sigs <- syscall.SIGKILL + + return + } + } + }() + + return nil +} diff --git a/provider/vault/vault.go b/provider/vault/vault.go new file mode 100644 index 0000000..f7bafbc --- /dev/null +++ b/provider/vault/vault.go @@ -0,0 +1,175 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 vault + +import ( + "context" + "fmt" + "log/slog" + "os" + "os/signal" + "strings" + + "github.com/bank-vaults/internal/injector" + "github.com/bank-vaults/vault-sdk/vault" + + "github.com/bank-vaults/secret-init/common" + "github.com/bank-vaults/secret-init/provider" +) + +const ProviderName = "vault" + +type Provider struct { + isLogin bool + client *vault.Client + injectorConfig injector.Config + secretRenewer injector.SecretRenewer + fromPath string + revokeToken bool +} + +type sanitized struct { + secrets []provider.Secret + login bool +} + +// VAULT_* variables are not populated into this list if this is not a login scenario. +func (s *sanitized) append(key string, value string) { + envType, ok := sanitizeEnvmap[key] + // If the key being appended is not present in sanitizeEnvmap, it signifies that + // it is not a VAULT_* variable. + // Additionally, in a login scenario, we include VAULT_* variables in the secrets list. + if !ok || (s.login && envType.login) { + // Path here is actually the secret's key, + // An example of this can be found at the LoadSecrets() function below + secret := provider.Secret{ + Path: key, + Value: value, + } + + s.secrets = append(s.secrets, secret) + } +} + +func NewProvider(config *Config) (provider.Provider, error) { + clientOptions := []vault.ClientOption{vault.ClientLogger(clientLogger{slog.Default()})} + if config.TokenFile != "" { + clientOptions = append(clientOptions, vault.ClientToken(config.Token)) + } else { + // use role/path based authentication + clientOptions = append(clientOptions, + vault.ClientRole(config.Role), + vault.ClientAuthPath(config.AuthPath), + vault.ClientAuthMethod(config.AuthMethod), + ) + } + + client, err := vault.NewClientWithOptions(clientOptions...) + if err != nil { + return nil, fmt.Errorf("failed to create vault client: %w", err) + } + + // Accessing the application configuration is necessary + // to determine whether daemon mode is enabled + appConfig, err := common.LoadConfig() + if err != nil { + return nil, fmt.Errorf("failed to load application config: %w", err) + } + + injectorConfig := injector.Config{ + TransitKeyID: config.TransitKeyID, + TransitPath: config.TransitPath, + TransitBatchSize: config.TransitBatchSize, + IgnoreMissingSecrets: config.IgnoreMissingSecrets, + DaemonMode: appConfig.Daemon, + } + + var secretRenewer injector.SecretRenewer + + if appConfig.Daemon { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs) + + secretRenewer = daemonSecretRenewer{client: client, sigs: sigs} + slog.Info("Daemon mode enabled. Will renew secrets in the background.") + } + + return &Provider{ + isLogin: config.IsLogin, + client: client, + injectorConfig: injectorConfig, + secretRenewer: secretRenewer, + fromPath: config.FromPath, + revokeToken: config.RevokeToken, + }, nil +} + +// LoadSecret's path formatting: = +// This formatting is necessary because the injector expects a map of key=value pairs. +// It also returns a map of key:value pairs, where the key is the environment variable name +// and the value is the secret value +// E.g. paths: MYSQL_PASSWORD=secret/data/mysql/password +// returns: []provider.Secret{provider.Secret{Path: "MYSQL_PASSWORD", Value: "password"}} +func (p *Provider) LoadSecrets(_ context.Context, paths []string) ([]provider.Secret, error) { + sanitized := sanitized{login: p.isLogin} + vaultEnviron := parsePathsToMap(paths) + + secretInjector := injector.NewSecretInjector(p.injectorConfig, p.client, p.secretRenewer, slog.Default()) + inject := func(key, value string) { + sanitized.append(key, value) + } + + err := secretInjector.InjectSecretsFromVault(vaultEnviron, inject) + if err != nil { + return nil, fmt.Errorf("failed to inject secrets from vault: %w", err) + } + + if p.fromPath != "" { + err = secretInjector.InjectSecretsFromVaultPath(p.fromPath, inject) + if err != nil { + return nil, fmt.Errorf("failed to inject secrets from vault path: %w", err) + } + } + + if p.revokeToken { + // ref: https://www.vaultproject.io/api/auth/token/index.html#revoke-a-token-self- + err := p.client.RawClient().Auth().Token().RevokeSelf(p.client.RawClient().Token()) + if err != nil { + // Do not exit on error, token revoking can be denied by policy + slog.Warn("failed to revoke token") + } + + p.client.Close() + } + + return sanitized.secrets, nil +} + +func (p *Provider) GetProviderName() string { + return ProviderName +} + +func parsePathsToMap(paths []string) map[string]string { + vaultEnviron := make(map[string]string) + + for _, path := range paths { + split := strings.SplitN(path, "=", 2) + key := split[0] + value := split[1] + vaultEnviron[key] = value + } + + return vaultEnviron +} diff --git a/provider/vault/vault_test.go b/provider/vault/vault_test.go new file mode 100644 index 0000000..0503fd0 --- /dev/null +++ b/provider/vault/vault_test.go @@ -0,0 +1,102 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 vault + +import ( + "fmt" + "io" + "log/slog" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +var originalLogger *slog.Logger + +func TestMain(m *testing.M) { + setupTestLogger() + code := m.Run() + restoreLogger() + os.Exit(code) +} + +func TestNewProvider(t *testing.T) { + tests := []struct { + name string + config *Config + err error + wantType bool + }{ + { + name: "Valid Provider with Token", + config: &Config{ + IsLogin: true, + TokenFile: "root", + Token: "root", + TransitKeyID: "test-key", + TransitPath: "transit", + TransitBatchSize: 10, + IgnoreMissingSecrets: true, + FromPath: "secret/data/test", + RevokeToken: true, + }, + wantType: true, + }, + { + name: "Valid Provider with vault:login as Token and daemon mode", + config: &Config{ + IsLogin: true, + Token: vaultLogin, + TokenFile: "root", + IgnoreMissingSecrets: true, + FromPath: "secret/data/test", + }, + wantType: true, + }, + { + name: "Fail to create vault client due to timeout", + config: &Config{}, + err: fmt.Errorf("failed to create vault client: timeout [10s] during waiting for Vault token"), + }, + } + + for _, tt := range tests { + ttp := tt + + t.Run(ttp.name, func(t *testing.T) { + provider, err := NewProvider(ttp.config) + if err != nil { + assert.EqualError(t, ttp.err, err.Error(), "Unexpected error message") + } + if ttp.wantType { + assert.Equal(t, ttp.wantType, provider != nil, "Unexpected provider type") + } + }) + } +} + +func setupTestLogger() { + originalLogger = slog.Default() + + // Redirect logs to avoid polluting the test output + handler := slog.NewTextHandler(io.Discard, nil) + testLogger := slog.New(handler) + slog.SetDefault(testLogger) +} + +func restoreLogger() { + slog.SetDefault(originalLogger) +}