From ddfdfcf3e247493706d0af1da6e2293f0389feb5 Mon Sep 17 00:00:00 2001 From: Maya Strandboge Date: Sun, 1 Mar 2026 14:53:28 -0700 Subject: [PATCH 1/5] add new authentication methods to the vault plugin --- go.mod | 6 + go.sum | 15 ++ plugins/secretstores/vault/README.md | 65 ++++++++- plugins/secretstores/vault/auth/approle.go | 59 ++++++++ plugins/secretstores/vault/auth/auth.go | 12 ++ plugins/secretstores/vault/auth/aws.go | 116 ++++++++++++++++ plugins/secretstores/vault/auth/azure.go | 49 +++++++ plugins/secretstores/vault/auth/kubernetes.go | 53 +++++++ plugins/secretstores/vault/auth/userpass.go | 53 +++++++ plugins/secretstores/vault/sample.conf | 66 ++++++++- plugins/secretstores/vault/vault.go | 129 +++++++++++------- plugins/secretstores/vault/vault_test.go | 7 +- 12 files changed, 560 insertions(+), 70 deletions(-) create mode 100644 plugins/secretstores/vault/auth/approle.go create mode 100644 plugins/secretstores/vault/auth/auth.go create mode 100644 plugins/secretstores/vault/auth/aws.go create mode 100644 plugins/secretstores/vault/auth/azure.go create mode 100644 plugins/secretstores/vault/auth/kubernetes.go create mode 100644 plugins/secretstores/vault/auth/userpass.go diff --git a/go.mod b/go.mod index 3b1475c536921..9460bf64b3737 100644 --- a/go.mod +++ b/go.mod @@ -124,6 +124,10 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/vault/api v1.22.0 github.com/hashicorp/vault/api/auth/approle v0.11.0 + github.com/hashicorp/vault/api/auth/aws v0.11.0 + github.com/hashicorp/vault/api/auth/azure v0.10.0 + github.com/hashicorp/vault/api/auth/kubernetes v0.10.0 + github.com/hashicorp/vault/api/auth/userpass v0.11.0 github.com/influxdata/influxdb-observability/common v0.5.12 github.com/influxdata/influxdb-observability/influx2otel v0.5.12 github.com/influxdata/influxdb-observability/otel2influx v0.5.12 @@ -308,6 +312,7 @@ require ( github.com/aristanetworks/glog v0.0.0-20191112221043-67e8567f59f3 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/awnumar/memcall v0.4.0 // indirect + github.com/aws/aws-sdk-go v1.55.7 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.43 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect @@ -415,6 +420,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/awsutil v0.3.0 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.7 // indirect diff --git a/go.sum b/go.sum index d9d350c2f92ee..2ae85e51cd5b1 100644 --- a/go.sum +++ b/go.sum @@ -892,7 +892,10 @@ github.com/aws/aws-msk-iam-sasl-signer-go v1.0.4 h1:2jAwFwA0Xgcx94dUId+K24yFabsK github.com/aws/aws-msk-iam-sasl-signer-go v1.0.4/go.mod h1:MVYeeOhILFFemC/XlYTClvBjYZrg/EPd3ts885KrNTI= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.29.11/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= +github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.44.263/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= +github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls= github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4= @@ -1579,6 +1582,7 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 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 v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -1597,6 +1601,8 @@ github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3 github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 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/awsutil v0.3.0 h1:I8bynUKMh9I7JdwtW9voJ0xmHvBpxQtLjrMFDYmhOxY= +github.com/hashicorp/go-secure-stdlib/awsutil v0.3.0/go.mod h1:oKHSQs4ivIfZ3fbXGQOop1XuDfdSb8RIsWTGaAanSfg= github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= @@ -1639,6 +1645,14 @@ github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicH github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= github.com/hashicorp/vault/api/auth/approle v0.11.0 h1:ViUvgqoSTqHkMi1L1Rr/LnQ+PWiRaGUBGvx4UPfmKOw= github.com/hashicorp/vault/api/auth/approle v0.11.0/go.mod h1:v8ZqBRw+GP264ikIw2sEBKF0VT72MEhLWnZqWt3xEG8= +github.com/hashicorp/vault/api/auth/aws v0.11.0 h1:lWdUxrzvPotg6idNr62al4w97BgI9xTDdzMCTViNH2s= +github.com/hashicorp/vault/api/auth/aws v0.11.0/go.mod h1:PWqdH/xqaudapmnnGP9ip2xbxT/kRW2qEgpqiQff6Gc= +github.com/hashicorp/vault/api/auth/azure v0.10.0 h1:soTc1xmzmszDN3+xtKn1MpaWE1/mRPVC418J9Z1uP5I= +github.com/hashicorp/vault/api/auth/azure v0.10.0/go.mod h1:5u/66YseDanWOycDJhEu6frHmsMw4UFnHK0I7w3AVx8= +github.com/hashicorp/vault/api/auth/kubernetes v0.10.0 h1:5rqWmUFxnu3S7XYq9dafURwBgabYDFzo2Wv+AMopPHs= +github.com/hashicorp/vault/api/auth/kubernetes v0.10.0/go.mod h1:cZZmhF6xboMDmDbMY52oj2DKW6gS0cQ9g0pJ5XIXQ5U= +github.com/hashicorp/vault/api/auth/userpass v0.11.0 h1:iPw1PL6vzQTn2w14quKd0ZnJV+cfPe+p5CA22M45jsA= +github.com/hashicorp/vault/api/auth/userpass v0.11.0/go.mod h1:FZ/baZ5rhruevb6kED9eh9KhorGtwM+xxVBvtXSxZsY= github.com/henrybear327/Proton-API-Bridge v1.0.0 h1:gjKAaWfKu++77WsZTHg6FUyPC5W0LTKWQciUm8PMZb0= github.com/henrybear327/Proton-API-Bridge v1.0.0/go.mod h1:gunH16hf6U74W2b9CGDaWRadiLICsoJ6KRkSt53zLts= github.com/henrybear327/go-proton-api v1.0.0 h1:zYi/IbjLwFAW7ltCeqXneUGJey0TN//Xo851a/BgLXw= @@ -1752,6 +1766,7 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/jlaffaye/ftp v0.2.1-0.20240918233326-1b970516f5d3 h1:ZxO6Qr2GOXPdcW80Mcn3nemvilMPvpWqxrNfK2ZnNNs= github.com/jlaffaye/ftp v0.2.1-0.20240918233326-1b970516f5d3/go.mod h1:dvLUr/8Fs9a2OBrEnCC5duphbkz/k/mSy5OkXg3PAgI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= 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= diff --git a/plugins/secretstores/vault/README.md b/plugins/secretstores/vault/README.md index d7b48ddab85b7..628bc2f497ba6 100644 --- a/plugins/secretstores/vault/README.md +++ b/plugins/secretstores/vault/README.md @@ -49,15 +49,66 @@ store usage. ## By default will use the kv-v2 engine. # engine = "kv-v2" - [secretstores.vault.approle] - ## The Role ID for AppRole Authentication, a UUID string - role_id = "" + # [secretstores.vault.approle] + # ## The Role ID for AppRole Authentication, a UUID string + # role_id = "" + # + # ## Whether the Secret ID is configured to be response wrapped or not + # # response_wrapped = false + # + # ## The Secret ID for AppRole Authentication + # secret = "" - ## Whether the Secret ID is configured to be response wrapped or not - # response_wrapped = false + # [secretstores.vault.aws_ec2] + # ## The Role Name for AWS EC2 authentication + # role_name = "" + # + # ## The AWS region, defaulting to "us-east-1" if unset + # # region = "us-east-1" + # + # ## The signature type to use, defaulting to "pkcs7" + # ## Allowed options: "pkcs7", "identity", "rsa2048" + # # signature_type = "pkcs7" - ## The Secret ID for AppRole Authentication - secret = "" + # ## Credentials will be set using the values in the environment variables: + # ## - AWS_ACCESS_KEY_ID + # ## - AWS_SECRET_ACCESS_KEY + # ## - AWS_SESSION_TOKEN + # ## To specify a path to a credentials file instead, set: + # ## - AWS_SHARED_CREDENTIALS_FILE + # [secretstores.vault.aws_iam] + # ## The Role Name for AWS IAM authentication + # role_name = "" + # + # ## The AWS region, defaulting to "us-east-1" if unset + # # region = "us-east-1" + # + # ## An optional server ID header to provide, with the key + # ## "X-Vault-AWS-IAM-Server-ID" + # # server_id_header = "" + + # [secretstores.vault.azure] + # ## The Role Name for Azure authentication + # role_name = "" + # + # ## The Azure Resource URL to use as the aud value on the JWT token to + # ## use rather than the default of Azure Public Cloud's ARM URL. + # ## Defaults to "https://management.azure.com/" + # # resource_url = "https://management.azure.com/" + + # [secretstores.vault.kubernetes] + # ## The Kubernetes service account role name + # role_name = "" + # + # ## The Kubernetes service account token + # secret = "" + + # [secretstores.vault.userpass] + # ## The Vault UserPass username + # username = "" + # + # ## The Vault UserPass password + # password = "" ``` [vault]: https://www.hashicorp.com/en/products/vault diff --git a/plugins/secretstores/vault/auth/approle.go b/plugins/secretstores/vault/auth/approle.go new file mode 100644 index 0000000000000..afcc2e078cf0b --- /dev/null +++ b/plugins/secretstores/vault/auth/approle.go @@ -0,0 +1,59 @@ +package auth + +import ( + "context" + "errors" + "fmt" + + vault "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/api/auth/approle" + + "github.com/influxdata/telegraf/config" +) + +type AppRole struct { + RoleID string `toml:"role_id"` + ResponseWrapped bool `toml:"response_wrapped"` + Secret config.Secret `toml:"secret"` +} + +// Validate checks if the provided configuration fields are valid +func (a *AppRole) Validate() error { + if a.RoleID == "" { + return errors.New("approle role_id missing") + } + if a.Secret.Empty() { + return errors.New("approle secret missing") + } + return nil +} + +// Authenticate uses the provided configuration to authenticate to Vault +func (a *AppRole) Authenticate(v *vault.Client) (*vault.Secret, error) { + secret, err := a.Secret.Get() + if err != nil { + return nil, fmt.Errorf("getting secret failed: %w", err) + } + secretID := &approle.SecretID{FromString: secret.String()} + defer secret.Destroy() + + opts := make([]approle.LoginOption, 0) + if a.ResponseWrapped { + opts = append(opts, approle.WithWrappingToken()) + } + + appRoleAuth, err := approle.NewAppRoleAuth(a.RoleID, secretID, opts...) + if err != nil { + return nil, fmt.Errorf("unable to initialize AppRole auth method: %w", err) + } + + authInfo, err := v.Auth().Login(context.Background(), appRoleAuth) + if err != nil { + return nil, fmt.Errorf("unable to login to AppRole auth method: %w", err) + } + if authInfo == nil { + return nil, errors.New("no auth info was returned after login") + } + + return authInfo, nil +} diff --git a/plugins/secretstores/vault/auth/auth.go b/plugins/secretstores/vault/auth/auth.go new file mode 100644 index 0000000000000..a926de274d2d8 --- /dev/null +++ b/plugins/secretstores/vault/auth/auth.go @@ -0,0 +1,12 @@ +package auth + +import vault "github.com/hashicorp/vault/api" + +type VaultAuth interface { + + // Validate checks if the provided configuration fields are valid + Validate() error + + // Authenticate uses the provided configuration to authenticate to Vault + Authenticate(*vault.Client) (*vault.Secret, error) +} diff --git a/plugins/secretstores/vault/auth/aws.go b/plugins/secretstores/vault/auth/aws.go new file mode 100644 index 0000000000000..f662422949d88 --- /dev/null +++ b/plugins/secretstores/vault/auth/aws.go @@ -0,0 +1,116 @@ +package auth + +import ( + "context" + "errors" + "fmt" + + vault "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/api/auth/aws" +) + +type AwsIAM struct { + RoleName string `toml:"role_name"` + Region string `toml:"region"` + ServerIDHeader string `toml:"server_id_header"` +} + +// Validate checks if the provided configuration fields are valid +func (a *AwsIAM) Validate() error { + if a.RoleName == "" { + return errors.New("aws iam role_name missing") + } + + if a.Region == "" { + a.Region = "us-east-1" + } + + return nil +} + +// Authenticate uses the provided configuration to authenticate to Vault +func (a *AwsIAM) Authenticate(v *vault.Client) (*vault.Secret, error) { + opts := []aws.LoginOption{ + aws.WithIAMAuth(), + aws.WithRole(a.RoleName), + aws.WithRegion(a.Region), + } + if a.ServerIDHeader != "" { + opts = append(opts, aws.WithIAMServerIDHeader(a.ServerIDHeader)) + } + + awsAuth, err := aws.NewAWSAuth(opts...) + if err != nil { + return nil, fmt.Errorf("unable to initialize AWS IAM auth method: %w", err) + } + + authInfo, err := v.Auth().Login(context.Background(), awsAuth) + if err != nil { + return nil, fmt.Errorf("unable to login to AWS IAM auth method: %w", err) + } + if authInfo == nil { + return nil, errors.New("no auth info was returned after login") + } + + return authInfo, nil +} + +type AwsEC2 struct { + RoleName string `toml:"role_name"` + Region string `toml:"region"` + SignatureType string `toml:"signature_type"` +} + +// Validate checks if the provided configuration fields are valid +func (a *AwsEC2) Validate() error { + if a.RoleName == "" { + return errors.New("aws ec2 role_name missing") + } + + switch a.SignatureType { + case "": + a.SignatureType = "pkcs7" + case "pkcs7", "identity", "rsa2048": + default: + return errors.New("unknown signature type: " + a.SignatureType) + } + + if a.Region == "" { + a.Region = "us-east-1" + } + + return nil +} + +// Authenticate uses the provided configuration to authenticate to Vault +func (a *AwsEC2) Authenticate(v *vault.Client) (*vault.Secret, error) { + opts := []aws.LoginOption{ + aws.WithEC2Auth(), + aws.WithRole(a.RoleName), + aws.WithRegion(a.Region), + } + + switch a.SignatureType { + case "pkcs7": + opts = append(opts, aws.WithPKCS7Signature()) + case "identity": + opts = append(opts, aws.WithIdentitySignature()) + case "rsa2048": + opts = append(opts, aws.WithRSA2048Signature()) + } + + awsAuth, err := aws.NewAWSAuth(opts...) + if err != nil { + return nil, fmt.Errorf("unable to initialize AWS EC2 auth method: %w", err) + } + + authInfo, err := v.Auth().Login(context.Background(), awsAuth) + if err != nil { + return nil, fmt.Errorf("unable to login to AWS EC2 auth method: %w", err) + } + if authInfo == nil { + return nil, errors.New("no auth info was returned after login") + } + + return authInfo, nil +} diff --git a/plugins/secretstores/vault/auth/azure.go b/plugins/secretstores/vault/auth/azure.go new file mode 100644 index 0000000000000..289dc70503496 --- /dev/null +++ b/plugins/secretstores/vault/auth/azure.go @@ -0,0 +1,49 @@ +package auth + +import ( + "context" + "errors" + "fmt" + + vault "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/api/auth/azure" +) + +type Azure struct { + RoleName string `toml:"role_name"` + ResourceURL string `toml:"resource_url"` +} + +// Validate checks if the provided configuration fields are valid +func (a *Azure) Validate() error { + if a.RoleName == "" { + return errors.New("azure role_name missing") + } + + if a.ResourceURL == "" { + a.ResourceURL = "https://management.azure.com/" + } + + return nil +} + +// Authenticate uses the provided configuration to authenticate to Vault +func (a *Azure) Authenticate(v *vault.Client) (*vault.Secret, error) { + azureAuth, err := azure.NewAzureAuth( + a.RoleName, + azure.WithResource(a.ResourceURL), + ) + if err != nil { + return nil, fmt.Errorf("unable to initialize Azure auth method: %w", err) + } + + authInfo, err := v.Auth().Login(context.Background(), azureAuth) + if err != nil { + return nil, fmt.Errorf("unable to login to Azure auth method: %w", err) + } + if authInfo == nil { + return nil, errors.New("no auth info was returned after login") + } + + return authInfo, nil +} diff --git a/plugins/secretstores/vault/auth/kubernetes.go b/plugins/secretstores/vault/auth/kubernetes.go new file mode 100644 index 0000000000000..13cbf1e3dc2a6 --- /dev/null +++ b/plugins/secretstores/vault/auth/kubernetes.go @@ -0,0 +1,53 @@ +package auth + +import ( + "context" + "errors" + "fmt" + + vault "github.com/hashicorp/vault/api" + auth "github.com/hashicorp/vault/api/auth/kubernetes" + + "github.com/influxdata/telegraf/config" +) + +type Kubernetes struct { + RoleName string + Secret config.Secret +} + +// Validate checks if the provided configuration fields are valid +func (k *Kubernetes) Validate() error { + if k.RoleName == "" { + return errors.New("kubernetes role_name missing") + } + if k.Secret.Empty() { + return errors.New("kubernetes secret missing") + } + return nil +} + +// Authenticate uses the provided configuration to authenticate to Vault +func (k *Kubernetes) Authenticate(client *vault.Client) (*vault.Secret, error) { + secret, err := k.Secret.Get() + if err != nil { + return nil, fmt.Errorf("getting secret failed: %w", err) + } + opt := auth.WithServiceAccountToken(secret.String()) + defer secret.Destroy() + + kubernetesAuth, err := auth.NewKubernetesAuth(k.RoleName, opt) + if err != nil { + return nil, fmt.Errorf("unable to initialize Kubernetes auth method: %w", err) + } + + authInfo, err := client.Auth().Login(context.Background(), kubernetesAuth) + if err != nil { + return nil, fmt.Errorf("unable to login to Kubernetes auth method: %w", err) + } + if authInfo == nil { + return nil, errors.New("no auth info was returned after login") + } + + return authInfo, nil +} diff --git a/plugins/secretstores/vault/auth/userpass.go b/plugins/secretstores/vault/auth/userpass.go new file mode 100644 index 0000000000000..2b20cf974afcb --- /dev/null +++ b/plugins/secretstores/vault/auth/userpass.go @@ -0,0 +1,53 @@ +package auth + +import ( + "context" + "errors" + "fmt" + + vault "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/api/auth/userpass" + + "github.com/influxdata/telegraf/config" +) + +type UserPass struct { + Username string `toml:"username"` + Password config.Secret `toml:"password"` +} + +// Validate checks if the provided configuration fields are valid +func (u *UserPass) Validate() error { + if u.Username == "" { + return errors.New("userpass username missing") + } + if u.Password.Empty() { + return errors.New("userpass password missing") + } + return nil +} + +// Authenticate uses the provided configuration to authenticate to Vault +func (u *UserPass) Authenticate(v *vault.Client) (*vault.Secret, error) { + secret, err := u.Password.Get() + if err != nil { + return nil, fmt.Errorf("getting secret failed: %w", err) + } + password := &userpass.Password{FromString: secret.String()} + defer secret.Destroy() + + userPassAuth, err := userpass.NewUserpassAuth(u.Username, password) + if err != nil { + return nil, fmt.Errorf("unable to initialize Userpass auth method: %w", err) + } + + authInfo, err := v.Auth().Login(context.Background(), userPassAuth) + if err != nil { + return nil, fmt.Errorf("unable to login to Userpass auth method: %w", err) + } + if authInfo == nil { + return nil, errors.New("no auth info was returned after login") + } + + return authInfo, nil +} diff --git a/plugins/secretstores/vault/sample.conf b/plugins/secretstores/vault/sample.conf index 3e8db637a1245..b1d6ae9cfd5a3 100644 --- a/plugins/secretstores/vault/sample.conf +++ b/plugins/secretstores/vault/sample.conf @@ -26,12 +26,64 @@ ## By default will use the kv-v2 engine. # engine = "kv-v2" - [secretstores.vault.approle] - ## The Role ID for AppRole Authentication, a UUID string - role_id = "" + # [secretstores.vault.approle] + # ## The Role ID for AppRole Authentication, a UUID string + # role_id = "" + # + # ## Whether the Secret ID is configured to be response wrapped or not + # # response_wrapped = false + # + # ## The Secret ID for AppRole Authentication + # secret = "" - ## Whether the Secret ID is configured to be response wrapped or not - # response_wrapped = false + # [secretstores.vault.aws_ec2] + # ## The Role Name for AWS EC2 authentication + # role_name = "" + # + # ## The AWS region, defaulting to "us-east-1" if unset + # # region = "us-east-1" + # + # ## The signature type to use, defaulting to "pkcs7" + # ## Allowed options: "pkcs7", "identity", "rsa2048" + # # signature_type = "pkcs7" + + # ## Credentials will be set using the values in the environment variables: + # ## - AWS_ACCESS_KEY_ID + # ## - AWS_SECRET_ACCESS_KEY + # ## - AWS_SESSION_TOKEN + # ## To specify a path to a credentials file instead, set: + # ## - AWS_SHARED_CREDENTIALS_FILE + # [secretstores.vault.aws_iam] + # ## The Role Name for AWS IAM authentication + # role_name = "" + # + # ## The AWS region, defaulting to "us-east-1" if unset + # # region = "us-east-1" + # + # ## An optional server ID header to provide, with the key + # ## "X-Vault-AWS-IAM-Server-ID" + # # server_id_header = "" + + # [secretstores.vault.azure] + # ## The Role Name for Azure authentication + # role_name = "" + # + # ## The Azure Resource URL to use as the aud value on the JWT token to + # ## use rather than the default of Azure Public Cloud's ARM URL. + # ## Defaults to "https://management.azure.com/" + # # resource_url = "https://management.azure.com/" + + # [secretstores.vault.kubernetes] + # ## The Kubernetes service account role name + # role_name = "" + # + # ## The Kubernetes service account token + # secret = "" + + # [secretstores.vault.userpass] + # ## The Vault Userpass username + # username = "" + # + # ## The Vault Userpass password + # password = "" - ## The Secret ID for AppRole Authentication - secret = "" diff --git a/plugins/secretstores/vault/vault.go b/plugins/secretstores/vault/vault.go index 7e85b37af1b31..be640d0031ef5 100644 --- a/plugins/secretstores/vault/vault.go +++ b/plugins/secretstores/vault/vault.go @@ -10,33 +10,33 @@ import ( "slices" vault "github.com/hashicorp/vault/api" - "github.com/hashicorp/vault/api/auth/approle" "github.com/influxdata/telegraf" - "github.com/influxdata/telegraf/config" "github.com/influxdata/telegraf/plugins/secretstores" + "github.com/influxdata/telegraf/plugins/secretstores/vault/auth" ) //go:embed sample.conf var sampleConfig string type Vault struct { - ID string `toml:"id"` - Address string `toml:"address"` - MountPath string `toml:"mount_path"` - SecretPath string `toml:"secret_path"` - Engine string `toml:"engine"` - AppRole *appRole `toml:"approle"` - + ID string `toml:"id"` + Address string `toml:"address"` + MountPath string `toml:"mount_path"` + SecretPath string `toml:"secret_path"` + Engine string `toml:"engine"` + + AppRole *auth.AppRole `toml:"approle"` + AwsEC2 *auth.AwsEC2 `toml:"aws_ec2"` + AwsIAM *auth.AwsIAM `toml:"aws_iam"` + Azure *auth.Azure `toml:"azure"` + Kubernetes *auth.Kubernetes `toml:"kubernetes"` + UserPass *auth.UserPass `toml:"userpass"` + + auth auth.VaultAuth client *vault.Client } -type appRole struct { - RoleID string `toml:"role_id"` - ResponseWrapped bool `toml:"response_wrapped"` - Secret config.Secret `toml:"secret"` -} - func (*Vault) SampleConfig() string { return sampleConfig } @@ -50,9 +50,10 @@ func (v *Vault) Init() error { return fmt.Errorf("unsupported engine: %s", v.Engine) } - if v.AppRole == nil { - return errors.New("approle configuration missing") + if err := v.validateAuth(); err != nil { + return err } + if v.ID == "" { return errors.New("id missing") } @@ -75,7 +76,64 @@ func (v *Vault) Init() error { v.client = client - return v.authenticate() + authInfo, err := v.auth.Authenticate(v.client) + if err != nil { + return err + } + + if renewable, err := authInfo.TokenIsRenewable(); renewable && err == nil { + watcher, err := v.client.NewLifetimeWatcher(&vault.LifetimeWatcherInput{Secret: authInfo}) + if err != nil { + return fmt.Errorf("unable to initialize Vault lifetime watcher: %w", err) + } + go watcher.Start() + } + + return nil +} + +func (v *Vault) validateAuth() error { + if v.AppRole != nil { + if v.auth != nil { + return errors.New("must only specify one authentication method") + } + v.auth = v.AppRole + } + if v.AwsEC2 != nil { + if v.auth != nil { + return errors.New("must only specify one authentication method") + } + v.auth = v.AwsEC2 + } + if v.AwsIAM != nil { + if v.auth != nil { + return errors.New("must only specify one authentication method") + } + v.auth = v.AwsIAM + } + if v.Azure != nil { + if v.auth != nil { + return errors.New("must only specify one authentication method") + } + v.auth = v.Azure + } + if v.Kubernetes != nil { + if v.auth != nil { + return errors.New("must only specify one authentication method") + } + v.auth = v.Kubernetes + } + if v.UserPass != nil { + if v.auth != nil { + return errors.New("must only specify one authentication method") + } + v.auth = v.UserPass + } + + if v.auth == nil { + return errors.New("no auth method set") + } + return v.auth.Validate() } func (v *Vault) Get(key string) ([]byte, error) { @@ -131,41 +189,6 @@ func (v *Vault) GetResolver(key string) (telegraf.ResolveFunc, error) { return resolver, nil } -func (v *Vault) authenticate() error { - secret, err := v.AppRole.Secret.Get() - if err != nil { - return fmt.Errorf("getting secret failed: %w", err) - } - secretID := &approle.SecretID{FromString: secret.String()} - defer secret.Destroy() - - opts := make([]approle.LoginOption, 0) - if v.AppRole.ResponseWrapped { - opts = append(opts, approle.WithWrappingToken()) - } - - appRoleAuth, err := approle.NewAppRoleAuth(v.AppRole.RoleID, secretID, opts...) - if err != nil { - return fmt.Errorf("unable to initialize AppRole auth method: %w", err) - } - - authInfo, err := v.client.Auth().Login(context.Background(), appRoleAuth) - if err != nil { - return fmt.Errorf("unable to login to AppRole auth method: %w", err) - } - if authInfo == nil { - return errors.New("no auth info was returned after login") - } - - watcher, err := v.client.NewLifetimeWatcher(&vault.LifetimeWatcherInput{Secret: authInfo}) - if err != nil { - return fmt.Errorf("unable to initialize Vault lifetime watcher: %w", err) - } - go watcher.Start() - - return nil -} - func (v *Vault) getSecret() (*vault.KVSecret, error) { if v.Engine == "kv-v1" { return v.client.KVv1(v.MountPath).Get(context.Background(), v.SecretPath) diff --git a/plugins/secretstores/vault/vault_test.go b/plugins/secretstores/vault/vault_test.go index 74ba7bdfeaca9..3450348cea0b5 100644 --- a/plugins/secretstores/vault/vault_test.go +++ b/plugins/secretstores/vault/vault_test.go @@ -14,6 +14,7 @@ import ( "github.com/testcontainers/testcontainers-go/modules/vault" "github.com/influxdata/telegraf/config" + "github.com/influxdata/telegraf/plugins/secretstores/vault/auth" ) func createContainer(t *testing.T, initCommands []string) (*vault.VaultContainer, func()) { @@ -155,7 +156,7 @@ func TestIntegrationKVv1(t *testing.T) { MountPath: mountPath, SecretPath: secretPath, Engine: "kv-v1", - AppRole: &appRole{ + AppRole: &auth.AppRole{ RoleID: getRoleID(t, container), Secret: config.NewSecret([]byte(getSecretID(t, container))), }, @@ -192,7 +193,7 @@ func TestIntegrationKVv2(t *testing.T) { Address: addr, MountPath: mountPath, SecretPath: secretPath, - AppRole: &appRole{ + AppRole: &auth.AppRole{ RoleID: getRoleID(t, container), Secret: config.NewSecret([]byte(getSecretID(t, container))), }, @@ -229,7 +230,7 @@ func TestIntegrationAppRoleSecretWrapped(t *testing.T) { Address: addr, MountPath: mountPath, SecretPath: secretPath, - AppRole: &appRole{ + AppRole: &auth.AppRole{ RoleID: getRoleID(t, container), Secret: config.NewSecret([]byte(getWrappedSecretID(t, container))), ResponseWrapped: true, From d07bedf27157597495bd759110164e52b267f7ae Mon Sep 17 00:00:00 2001 From: Maya Strandboge Date: Mon, 2 Mar 2026 17:29:45 -0700 Subject: [PATCH 2/5] run make docs --- plugins/secretstores/vault/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/secretstores/vault/README.md b/plugins/secretstores/vault/README.md index 628bc2f497ba6..399e96821df3a 100644 --- a/plugins/secretstores/vault/README.md +++ b/plugins/secretstores/vault/README.md @@ -104,11 +104,12 @@ store usage. # secret = "" # [secretstores.vault.userpass] - # ## The Vault UserPass username + # ## The Vault Userpass username # username = "" # - # ## The Vault UserPass password + # ## The Vault Userpass password # password = "" + ``` [vault]: https://www.hashicorp.com/en/products/vault From 644e6db63a02f06a6a3697e50c08b8b9d7e4b5e1 Mon Sep 17 00:00:00 2001 From: Maya Strandboge Date: Mon, 2 Mar 2026 17:35:05 -0700 Subject: [PATCH 3/5] update license of dependencies --- docs/LICENSE_OF_DEPENDENCIES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/LICENSE_OF_DEPENDENCIES.md b/docs/LICENSE_OF_DEPENDENCIES.md index e4c6247d2e1d7..b4f6ff543001b 100644 --- a/docs/LICENSE_OF_DEPENDENCIES.md +++ b/docs/LICENSE_OF_DEPENDENCIES.md @@ -71,6 +71,7 @@ following works: - github.com/awnumar/memcall [Apache License 2.0](https://github.com/awnumar/memcall/blob/master/LICENSE) - github.com/awnumar/memguard [Apache License 2.0](https://github.com/awnumar/memguard/blob/master/LICENSE) - github.com/aws/aws-msk-iam-sasl-signer-go [Apache License 2.0](https://github.com/aws/aws-msk-iam-sasl-signer-go/blob/main/LICENSE) +- github.com/aws/aws-sdk-go [Apache License 2.0](https://github.com/aws/aws-sdk-go/blob/main/LICENSE.txt) - github.com/aws/aws-sdk-go-v2 [Apache License 2.0](https://github.com/aws/aws-sdk-go-v2/blob/main/LICENSE.txt) - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream [Apache License 2.0](https://github.com/aws/aws-sdk-go-v2/blob/main/aws/protocol/eventstream/LICENSE.txt) - github.com/aws/aws-sdk-go-v2/config [Apache License 2.0](https://github.com/aws/aws-sdk-go-v2/blob/main/config/LICENSE.txt) @@ -231,6 +232,7 @@ following works: - github.com/hashicorp/go-multierror [Mozilla Public License 2.0](https://github.com/hashicorp/go-multierror/blob/master/LICENSE) - github.com/hashicorp/go-retryablehttp [Mozilla Public License 2.0](https://github.com/hashicorp/go-retryablehttp/blob/main/LICENSE) - github.com/hashicorp/go-rootcerts [Mozilla Public License 2.0](https://github.com/hashicorp/go-rootcerts/blob/master/LICENSE) +- github.com/hashicorp/go-secure-stdlib/awsutil [Mozilla Public License 2.0](https://github.com/hashicorp/go-secure-stdlib/blob/main/awsutil/LICENSE) - github.com/hashicorp/go-secure-stdlib/parseutil [Mozilla Public License 2.0](https://github.com/hashicorp/go-secure-stdlib/blob/main/parseutil/LICENSE) - github.com/hashicorp/go-secure-stdlib/strutil [Mozilla Public License 2.0](https://github.com/hashicorp/go-secure-stdlib/blob/main/strutil/LICENSE) - github.com/hashicorp/go-sockaddr [Mozilla Public License 2.0](https://github.com/hashicorp/go-sockaddr/blob/master/LICENSE) @@ -242,6 +244,10 @@ following works: - github.com/hashicorp/serf [Mozilla Public License 2.0](https://github.com/hashicorp/serf/blob/master/LICENSE) - github.com/hashicorp/vault/api [Mozilla Public License 2.0](https://github.com/hashicorp/vault/blob/main/api/LICENSE) - github.com/hashicorp/vault/api/auth/approle [Mozilla Public License 2.0](https://github.com/hashicorp/vault/blob/main/api/auth/approle/LICENSE) +- github.com/hashicorp/vault/api/auth/aws [Mozilla Public License 2.0](https://github.com/hashicorp/vault/blob/main/api/auth/aws/LICENSE) +- github.com/hashicorp/vault/api/auth/azure [Mozilla Public License 2.0](https://github.com/hashicorp/vault/blob/main/api/auth/azure/LICENSE) +- github.com/hashicorp/vault/api/auth/kubernetes [Mozilla Public License 2.0](https://github.com/hashicorp/vault/blob/main/api/auth/kubernetes/LICENSE) +- github.com/hashicorp/vault/api/auth/userpass [Mozilla Public License 2.0](https://github.com/hashicorp/vault/blob/main/api/auth/userpass/LICENSE) - github.com/huandu/xstrings [MIT License](https://github.com/huandu/xstrings/blob/master/LICENSE) - github.com/icholy/digest [MIT License](https://github.com/icholy/digest/blob/master/LICENSE) - github.com/imdario/mergo [BSD 3-Clause "New" or "Revised" License](https://github.com/imdario/mergo/blob/master/LICENSE) From 5927d5a80a8cd16021394373d6bb5415f2c888e2 Mon Sep 17 00:00:00 2001 From: Maya Strandboge Date: Wed, 4 Mar 2026 13:01:18 -0700 Subject: [PATCH 4/5] reviews --- plugins/secretstores/vault/README.md | 2 +- plugins/secretstores/vault/auth/approle.go | 6 +- plugins/secretstores/vault/auth/auth.go | 5 +- plugins/secretstores/vault/auth/aws.go | 8 +- plugins/secretstores/vault/auth/azure.go | 4 +- plugins/secretstores/vault/auth/kubernetes.go | 14 +-- plugins/secretstores/vault/auth/userpass.go | 4 +- plugins/secretstores/vault/vault.go | 89 +++++++++---------- 8 files changed, 63 insertions(+), 69 deletions(-) diff --git a/plugins/secretstores/vault/README.md b/plugins/secretstores/vault/README.md index 399e96821df3a..5bf91fb59271c 100644 --- a/plugins/secretstores/vault/README.md +++ b/plugins/secretstores/vault/README.md @@ -2,7 +2,7 @@ The `vault` plugin allows to utilize secrets stored in a [HashiCorp Vault][vault] server via the Vault API. It supports authentication -via AppRole. +via AppRole, Userpass, AWS IAM, AWS EC2, Azure and Kubernetes. ⭐ Telegraf v1.37.0 🏷️ secrets diff --git a/plugins/secretstores/vault/auth/approle.go b/plugins/secretstores/vault/auth/approle.go index afcc2e078cf0b..5982acb681c3c 100644 --- a/plugins/secretstores/vault/auth/approle.go +++ b/plugins/secretstores/vault/auth/approle.go @@ -17,8 +17,8 @@ type AppRole struct { Secret config.Secret `toml:"secret"` } -// Validate checks if the provided configuration fields are valid -func (a *AppRole) Validate() error { +// Init validates the auth method options and sets any necessary defaults +func (a *AppRole) Init() error { if a.RoleID == "" { return errors.New("approle role_id missing") } @@ -37,7 +37,7 @@ func (a *AppRole) Authenticate(v *vault.Client) (*vault.Secret, error) { secretID := &approle.SecretID{FromString: secret.String()} defer secret.Destroy() - opts := make([]approle.LoginOption, 0) + var opts []approle.LoginOption if a.ResponseWrapped { opts = append(opts, approle.WithWrappingToken()) } diff --git a/plugins/secretstores/vault/auth/auth.go b/plugins/secretstores/vault/auth/auth.go index a926de274d2d8..08722702c020f 100644 --- a/plugins/secretstores/vault/auth/auth.go +++ b/plugins/secretstores/vault/auth/auth.go @@ -3,9 +3,8 @@ package auth import vault "github.com/hashicorp/vault/api" type VaultAuth interface { - - // Validate checks if the provided configuration fields are valid - Validate() error + // Init validates the auth method options and sets any necessary defaults + Init() error // Authenticate uses the provided configuration to authenticate to Vault Authenticate(*vault.Client) (*vault.Secret, error) diff --git a/plugins/secretstores/vault/auth/aws.go b/plugins/secretstores/vault/auth/aws.go index f662422949d88..0f7d0b8b0cc25 100644 --- a/plugins/secretstores/vault/auth/aws.go +++ b/plugins/secretstores/vault/auth/aws.go @@ -15,8 +15,8 @@ type AwsIAM struct { ServerIDHeader string `toml:"server_id_header"` } -// Validate checks if the provided configuration fields are valid -func (a *AwsIAM) Validate() error { +// Init validates the auth method options and sets any necessary defaults +func (a *AwsIAM) Init() error { if a.RoleName == "" { return errors.New("aws iam role_name missing") } @@ -61,8 +61,8 @@ type AwsEC2 struct { SignatureType string `toml:"signature_type"` } -// Validate checks if the provided configuration fields are valid -func (a *AwsEC2) Validate() error { +// Init validates the auth method options and sets any necessary defaults +func (a *AwsEC2) Init() error { if a.RoleName == "" { return errors.New("aws ec2 role_name missing") } diff --git a/plugins/secretstores/vault/auth/azure.go b/plugins/secretstores/vault/auth/azure.go index 289dc70503496..3e79d8f5caa70 100644 --- a/plugins/secretstores/vault/auth/azure.go +++ b/plugins/secretstores/vault/auth/azure.go @@ -14,8 +14,8 @@ type Azure struct { ResourceURL string `toml:"resource_url"` } -// Validate checks if the provided configuration fields are valid -func (a *Azure) Validate() error { +// Init validates the auth method options and sets any necessary defaults +func (a *Azure) Init() error { if a.RoleName == "" { return errors.New("azure role_name missing") } diff --git a/plugins/secretstores/vault/auth/kubernetes.go b/plugins/secretstores/vault/auth/kubernetes.go index 13cbf1e3dc2a6..cfc3f34918434 100644 --- a/plugins/secretstores/vault/auth/kubernetes.go +++ b/plugins/secretstores/vault/auth/kubernetes.go @@ -6,18 +6,18 @@ import ( "fmt" vault "github.com/hashicorp/vault/api" - auth "github.com/hashicorp/vault/api/auth/kubernetes" + "github.com/hashicorp/vault/api/auth/kubernetes" "github.com/influxdata/telegraf/config" ) type Kubernetes struct { - RoleName string - Secret config.Secret + RoleName string `toml:"role_name"` + Secret config.Secret `toml:"secret"` } -// Validate checks if the provided configuration fields are valid -func (k *Kubernetes) Validate() error { +// Init validates the auth method options and sets any necessary defaults +func (k *Kubernetes) Init() error { if k.RoleName == "" { return errors.New("kubernetes role_name missing") } @@ -33,10 +33,10 @@ func (k *Kubernetes) Authenticate(client *vault.Client) (*vault.Secret, error) { if err != nil { return nil, fmt.Errorf("getting secret failed: %w", err) } - opt := auth.WithServiceAccountToken(secret.String()) + opt := kubernetes.WithServiceAccountToken(secret.String()) defer secret.Destroy() - kubernetesAuth, err := auth.NewKubernetesAuth(k.RoleName, opt) + kubernetesAuth, err := kubernetes.NewKubernetesAuth(k.RoleName, opt) if err != nil { return nil, fmt.Errorf("unable to initialize Kubernetes auth method: %w", err) } diff --git a/plugins/secretstores/vault/auth/userpass.go b/plugins/secretstores/vault/auth/userpass.go index 2b20cf974afcb..074d9202519f3 100644 --- a/plugins/secretstores/vault/auth/userpass.go +++ b/plugins/secretstores/vault/auth/userpass.go @@ -16,8 +16,8 @@ type UserPass struct { Password config.Secret `toml:"password"` } -// Validate checks if the provided configuration fields are valid -func (u *UserPass) Validate() error { +// Init validates the auth method options and sets any necessary defaults +func (u *UserPass) Init() error { if u.Username == "" { return errors.New("userpass username missing") } diff --git a/plugins/secretstores/vault/vault.go b/plugins/secretstores/vault/vault.go index be640d0031ef5..f9604d235f3c0 100644 --- a/plugins/secretstores/vault/vault.go +++ b/plugins/secretstores/vault/vault.go @@ -33,6 +33,8 @@ type Vault struct { Kubernetes *auth.Kubernetes `toml:"kubernetes"` UserPass *auth.UserPass `toml:"userpass"` + Log telegraf.Logger `toml:"-"` + auth auth.VaultAuth client *vault.Client } @@ -81,61 +83,22 @@ func (v *Vault) Init() error { return err } - if renewable, err := authInfo.TokenIsRenewable(); renewable && err == nil { + renewable, err := authInfo.TokenIsRenewable() + if err != nil { + v.Log.Errorf("failed to check if auth token is renewable: %v", err) + } + if renewable { watcher, err := v.client.NewLifetimeWatcher(&vault.LifetimeWatcherInput{Secret: authInfo}) if err != nil { - return fmt.Errorf("unable to initialize Vault lifetime watcher: %w", err) + v.Log.Errorf("unable to initialize Vault lifetime watcher: %v", err) + } else { + go watcher.Start() } - go watcher.Start() } return nil } -func (v *Vault) validateAuth() error { - if v.AppRole != nil { - if v.auth != nil { - return errors.New("must only specify one authentication method") - } - v.auth = v.AppRole - } - if v.AwsEC2 != nil { - if v.auth != nil { - return errors.New("must only specify one authentication method") - } - v.auth = v.AwsEC2 - } - if v.AwsIAM != nil { - if v.auth != nil { - return errors.New("must only specify one authentication method") - } - v.auth = v.AwsIAM - } - if v.Azure != nil { - if v.auth != nil { - return errors.New("must only specify one authentication method") - } - v.auth = v.Azure - } - if v.Kubernetes != nil { - if v.auth != nil { - return errors.New("must only specify one authentication method") - } - v.auth = v.Kubernetes - } - if v.UserPass != nil { - if v.auth != nil { - return errors.New("must only specify one authentication method") - } - v.auth = v.UserPass - } - - if v.auth == nil { - return errors.New("no auth method set") - } - return v.auth.Validate() -} - func (v *Vault) Get(key string) ([]byte, error) { secret, err := v.getSecret() if err != nil { @@ -189,6 +152,38 @@ func (v *Vault) GetResolver(key string) (telegraf.ResolveFunc, error) { return resolver, nil } +func (v *Vault) validateAuth() error { + var methods []auth.VaultAuth + if v.AppRole != nil { + methods = append(methods, v.AppRole) + } + if v.AwsEC2 != nil { + methods = append(methods, v.AwsEC2) + } + if v.AwsIAM != nil { + methods = append(methods, v.AwsIAM) + } + if v.Azure != nil { + methods = append(methods, v.Azure) + } + if v.Kubernetes != nil { + methods = append(methods, v.Kubernetes) + } + if v.UserPass != nil { + methods = append(methods, v.UserPass) + } + + if len(methods) == 0 { + return errors.New("no auth method set") + } + if len(methods) > 1 { + return errors.New("must only specify one authentication method") + } + + v.auth = methods[0] + return v.auth.Init() +} + func (v *Vault) getSecret() (*vault.KVSecret, error) { if v.Engine == "kv-v1" { return v.client.KVv1(v.MountPath).Get(context.Background(), v.SecretPath) From 5c5dfc0e6e851125d7ffd742500c79820cd47131 Mon Sep 17 00:00:00 2001 From: Maya Strandboge Date: Wed, 4 Mar 2026 14:37:13 -0700 Subject: [PATCH 5/5] reviews --- plugins/secretstores/vault/README.md | 2 +- plugins/secretstores/vault/auth/aws.go | 2 +- plugins/secretstores/vault/auth/kubernetes.go | 10 +++++----- plugins/secretstores/vault/sample.conf | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/secretstores/vault/README.md b/plugins/secretstores/vault/README.md index 5bf91fb59271c..3d05dc228d188 100644 --- a/plugins/secretstores/vault/README.md +++ b/plugins/secretstores/vault/README.md @@ -101,7 +101,7 @@ store usage. # role_name = "" # # ## The Kubernetes service account token - # secret = "" + # service_account_token = "" # [secretstores.vault.userpass] # ## The Vault Userpass username diff --git a/plugins/secretstores/vault/auth/aws.go b/plugins/secretstores/vault/auth/aws.go index 0f7d0b8b0cc25..05a30187e6980 100644 --- a/plugins/secretstores/vault/auth/aws.go +++ b/plugins/secretstores/vault/auth/aws.go @@ -72,7 +72,7 @@ func (a *AwsEC2) Init() error { a.SignatureType = "pkcs7" case "pkcs7", "identity", "rsa2048": default: - return errors.New("unknown signature type: " + a.SignatureType) + return fmt.Errorf("unknown signature type: %q", a.SignatureType) } if a.Region == "" { diff --git a/plugins/secretstores/vault/auth/kubernetes.go b/plugins/secretstores/vault/auth/kubernetes.go index cfc3f34918434..31caad2139568 100644 --- a/plugins/secretstores/vault/auth/kubernetes.go +++ b/plugins/secretstores/vault/auth/kubernetes.go @@ -12,8 +12,8 @@ import ( ) type Kubernetes struct { - RoleName string `toml:"role_name"` - Secret config.Secret `toml:"secret"` + RoleName string `toml:"role_name"` + ServiceAccountToken config.Secret `toml:"service_account_token"` } // Init validates the auth method options and sets any necessary defaults @@ -21,15 +21,15 @@ func (k *Kubernetes) Init() error { if k.RoleName == "" { return errors.New("kubernetes role_name missing") } - if k.Secret.Empty() { - return errors.New("kubernetes secret missing") + if k.ServiceAccountToken.Empty() { + return errors.New("kubernetes service_account_token missing") } return nil } // Authenticate uses the provided configuration to authenticate to Vault func (k *Kubernetes) Authenticate(client *vault.Client) (*vault.Secret, error) { - secret, err := k.Secret.Get() + secret, err := k.ServiceAccountToken.Get() if err != nil { return nil, fmt.Errorf("getting secret failed: %w", err) } diff --git a/plugins/secretstores/vault/sample.conf b/plugins/secretstores/vault/sample.conf index b1d6ae9cfd5a3..0e42c44ac431e 100644 --- a/plugins/secretstores/vault/sample.conf +++ b/plugins/secretstores/vault/sample.conf @@ -78,7 +78,7 @@ # role_name = "" # # ## The Kubernetes service account token - # secret = "" + # service_account_token = "" # [secretstores.vault.userpass] # ## The Vault Userpass username