diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b2871ad466..3c2815e4968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ Here is an overview of all new **experimental** features: - **General**: Support TriggerAuthentication properties from ConfigMap ([#4830](https://github.com/kedacore/keda/issues/4830)) - **Hashicorp Vault**: Add support to get secret that needs write operation (e.g. pki) ([#5067](https://github.com/kedacore/keda/issues/5067)) - **Hashicorp Vault**: Fix operator panic when spec.hashiCorpVault.credential.serviceAccount is not set ([#4964](https://github.com/kedacore/keda/issues/4964)) +- **Hashicorp Vault**: Allow setting vault TriggerAuthentication defaults via ENV variables ([#4965](https://github.com/kedacore/keda/issues/4965)) - **Kafka Scaler**: Ability to set upper bound to the number of partitions with lag ([#3997](https://github.com/kedacore/keda/issues/3997)) - **Kafka Scaler**: Add more logging to check Sarama DescribeTopics method ([#5102](https://github.com/kedacore/keda/issues/5102)) - **Kafka Scaler**: Add support for Kerberos authentication (SASL / GSSAPI) ([#4836](https://github.com/kedacore/keda/issues/4836)) diff --git a/apis/keda/v1alpha1/triggerauthentication_types.go b/apis/keda/v1alpha1/triggerauthentication_types.go index 9483abedb6b..8f6447bb518 100644 --- a/apis/keda/v1alpha1/triggerauthentication_types.go +++ b/apis/keda/v1alpha1/triggerauthentication_types.go @@ -170,9 +170,13 @@ type AuthEnvironment struct { // HashiCorpVault is used to authenticate using Hashicorp Vault type HashiCorpVault struct { - Address string `json:"address"` + Secrets []VaultSecret `json:"secrets"` + + // +optional + Address string `json:"address"` + + // +optional Authentication VaultAuthentication `json:"authentication"` - Secrets []VaultSecret `json:"secrets"` // +optional Namespace string `json:"namespace,omitempty"` diff --git a/config/crd/bases/keda.sh_clustertriggerauthentications.yaml b/config/crd/bases/keda.sh_clustertriggerauthentications.yaml index cc9cacc688f..1e96dfd9fe5 100644 --- a/config/crd/bases/keda.sh_clustertriggerauthentications.yaml +++ b/config/crd/bases/keda.sh_clustertriggerauthentications.yaml @@ -233,8 +233,6 @@ spec: type: object type: array required: - - address - - authentication - secrets type: object podIdentity: diff --git a/config/crd/bases/keda.sh_triggerauthentications.yaml b/config/crd/bases/keda.sh_triggerauthentications.yaml index 6589a44301b..a89265dbfd7 100644 --- a/config/crd/bases/keda.sh_triggerauthentications.yaml +++ b/config/crd/bases/keda.sh_triggerauthentications.yaml @@ -232,8 +232,6 @@ spec: type: object type: array required: - - address - - authentication - secrets type: object podIdentity: diff --git a/pkg/scaling/resolver/hashicorpvault_handler.go b/pkg/scaling/resolver/hashicorpvault_handler.go index b2c7f94f6e5..3226117b253 100644 --- a/pkg/scaling/resolver/hashicorpvault_handler.go +++ b/pkg/scaling/resolver/hashicorpvault_handler.go @@ -27,6 +27,7 @@ import ( vaultapi "github.com/hashicorp/vault/api" kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" + "github.com/kedacore/keda/v2/pkg/util" ) // HashicorpVaultHandler is specification of Hashi Corp Vault @@ -43,9 +44,46 @@ func NewHashicorpVaultHandler(v *kedav1alpha1.HashiCorpVault) *HashicorpVaultHan } } +func (vh *HashicorpVaultHandler) SetFromEnvironment() { + if vh.vault.Address == "" { + vh.vault.Address = util.ResolveOsString("VAULT_ADDR", "") + } + + if vh.vault.Authentication == "" { + vh.vault.Authentication = kedav1alpha1.VaultAuthentication(util.ResolveOsString("VAULT_AUTH", "")) + } + + if vh.vault.Role == "" { + vh.vault.Role = util.ResolveOsString("VAULT_ROLE", "") + } + + if vh.vault.Mount == "" { + vh.vault.Mount = util.ResolveOsString("VAULT_MOUNT", "") + } + + if vh.vault.Credential == nil { + serviceAccount := util.ResolveOsString("VAULT_JWT_PATH", "") + vaultToken := util.ResolveOsString("VAULT_TOKEN", "") + if vaultToken != "" { + vh.vault.Credential = &kedav1alpha1.Credential{} + vh.vault.Credential.Token = vaultToken + } + if serviceAccount != "" { + vh.vault.Credential = &kedav1alpha1.Credential{} + vh.vault.Credential.ServiceAccount = serviceAccount + } + } + + if vh.vault.Namespace == "" { + vh.vault.Namespace = util.ResolveOsString("VAULT_NAMESPACE", "") + } +} + // Initialize the Vault client func (vh *HashicorpVaultHandler) Initialize(logger logr.Logger) error { config := vaultapi.DefaultConfig() + vh.SetFromEnvironment() + client, err := vaultapi.NewClient(config) if err != nil { return err diff --git a/pkg/scaling/resolver/hashicorpvault_handler_test.go b/pkg/scaling/resolver/hashicorpvault_handler_test.go index 9faf1e3c5c7..155a6880ee6 100644 --- a/pkg/scaling/resolver/hashicorpvault_handler_test.go +++ b/pkg/scaling/resolver/hashicorpvault_handler_test.go @@ -24,6 +24,7 @@ import ( "math" "net/http" "net/http/httptest" + "os" "testing" vaultapi "github.com/hashicorp/vault/api" @@ -120,9 +121,13 @@ func TestGetPkiRequest(t *testing.T) { func mockVault(t *testing.T) *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var data map[string]interface{} + var auth *vaultapi.SecretAuth switch r.URL.Path { + case "/v1/auth/kubernetes/login": + auth = &vaultapi.SecretAuth{ + ClientToken: "38fe9691-e623-7238-f618-c94d4e7bc674", + } case "/v1/auth/token/lookup-self": - data = vaultTokenSelf case "/v1/kv_v2/data/keda": //todo: more generic data = kvV2SecretDataKeda @@ -160,7 +165,7 @@ func mockVault(t *testing.T) *httptest.Server { Data: data, Renewable: false, Warnings: nil, - Auth: nil, + Auth: auth, WrapInfo: nil, } var out, _ = json.Marshal(secret) @@ -169,6 +174,49 @@ func mockVault(t *testing.T) *httptest.Server { return server } +func TestHashicorpVaultLoadFromENVVariablesToken(t *testing.T) { + server := mockVault(t) + defer server.Close() + + t.Setenv("VAULT_ADDR", server.URL) + t.Setenv("VAULT_MOUNT", "kubernetes") + t.Setenv("VAULT_AUTH", "token") + t.Setenv("VAULT_ROLE", "my-role") + t.Setenv("VAULT_NAMESPACE", "") + t.Setenv("VAULT_TOKEN", vaultTestToken) + + vault := kedav1alpha1.HashiCorpVault{} + vaultHandler := NewHashicorpVaultHandler(&vault) + + err := vaultHandler.Initialize(logf.Log.WithName("test")) + defer vaultHandler.Stop() + assert.Nil(t, err) +} + +func TestHashicorpVaultLoadFromENVVariablesKubernetes(t *testing.T) { + server := mockVault(t) + defer server.Close() + + t.Setenv("VAULT_ADDR", server.URL) + t.Setenv("VAULT_MOUNT", "kubernetes") + t.Setenv("VAULT_AUTH", "kubernetes") + t.Setenv("VAULT_ROLE", "my-role") + t.Setenv("VAULT_NAMESPACE", "") + + tmpServiceToken, err := os.CreateTemp("", "token") + assert.Nil(t, err) + defer tmpServiceToken.Close() + + t.Setenv("VAULT_JWT_PATH", tmpServiceToken.Name()) + + vault := kedav1alpha1.HashiCorpVault{} + vaultHandler := NewHashicorpVaultHandler(&vault) + + err = vaultHandler.Initialize(logf.Log.WithName("test")) + defer vaultHandler.Stop() + assert.Nil(t, err) +} + func TestHashicorpVaultHandler_getSecretValue_specify_secret_type(t *testing.T) { server := mockVault(t) defer server.Close() diff --git a/pkg/util/env_resolver.go b/pkg/util/env_resolver.go index b8e9cfa8763..952c6d33083 100644 --- a/pkg/util/env_resolver.go +++ b/pkg/util/env_resolver.go @@ -57,6 +57,16 @@ func ResolveOsEnvDuration(envName string) (*time.Duration, error) { return nil, nil } +func ResolveOsString(envName string, defaultvalue string) string { + valueStr, found := os.LookupEnv(envName) + + if found && valueStr != "" { + return valueStr + } + + return defaultvalue +} + // GetClusterObjectNamespace retrieves the cluster object namespace of KEDA, default is the namespace of KEDA Operator & Metrics Server func GetClusterObjectNamespace() (string, error) { // Check if a cached value is available.