diff --git a/cmd/data/cmd_serve.go b/cmd/data/cmd_serve.go index ef960a6..826ebdd 100644 --- a/cmd/data/cmd_serve.go +++ b/cmd/data/cmd_serve.go @@ -108,10 +108,7 @@ func (s *serveCmd) setup(ctx context.Context, r *mux.Router) (err error) { vc, err := vaulty.NewClient( vaulty.WithContext(ctx), vaulty.WithGeneratedVaultClient(v.GetString("vault.address")), - vaulty.WithUserPassAuth( - v.GetString("vault.auth.username"), - v.GetString("vault.auth.password"), - ), + vaulty.WithKubernetesAuthDefault(), vaulty.WithKvv2Mount(v.GetString("vault.kvv2_mount")), ) if err != nil { diff --git a/go.mod b/go.mod index f2fafc7..4fb20b8 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/jacobbrewer1/pagefilter v0.1.4 github.com/jacobbrewer1/patcher v0.1.10 github.com/jacobbrewer1/uhttp v0.0.4 - github.com/jacobbrewer1/vaulty v0.1.3 + github.com/jacobbrewer1/vaulty v0.1.4 github.com/jacobbrewer1/workerpool v0.0.2 github.com/jmoiron/sqlx v1.4.0 github.com/oapi-codegen/runtime v1.1.1 @@ -55,6 +55,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/vault/api v1.15.0 // indirect github.com/hashicorp/vault/api/auth/approle v0.8.0 // indirect + github.com/hashicorp/vault/api/auth/kubernetes v0.8.0 // indirect github.com/hashicorp/vault/api/auth/userpass v0.8.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/iancoleman/strcase v0.2.0 // indirect diff --git a/go.sum b/go.sum index 920c922..56ffbcf 100644 --- a/go.sum +++ b/go.sum @@ -122,6 +122,8 @@ github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/ github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8= github.com/hashicorp/vault/api/auth/approle v0.8.0 h1:FuVtWZ0xD6+wz1x0l5s0b4852RmVXQNEiKhVXt6lfQY= github.com/hashicorp/vault/api/auth/approle v0.8.0/go.mod h1:NV7O9r5JUtNdVnqVZeMHva81AIdpG0WoIQohNt1VCPM= +github.com/hashicorp/vault/api/auth/kubernetes v0.8.0 h1:6jPcORq7OHwf+MCbaaUmiBvMhETAaZ7+i97WfZtF5kc= +github.com/hashicorp/vault/api/auth/kubernetes v0.8.0/go.mod h1:nfl5sRUUork0ZSfV3xf+pgAFQSD5kSkL0k9axg523DM= github.com/hashicorp/vault/api/auth/userpass v0.8.0 h1:JFFzMld+VO/S1v8HQNJzcy+3o+xfx/iH49dsiQ1G5jk= github.com/hashicorp/vault/api/auth/userpass v0.8.0/go.mod h1:+XbsSnbbyo+yjySfKcIsyl28kO4C/c4Czo7og0XCtUo= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= @@ -137,8 +139,8 @@ github.com/jacobbrewer1/patcher v0.1.10 h1:k0T4/hW6F5r5bQ+WDzeaBg0u5ykAr6shQ7Z1P github.com/jacobbrewer1/patcher v0.1.10/go.mod h1:DbPetwfn5Fgarwn5VOjALRjNflSX0rbkGssZ0cRsOlo= github.com/jacobbrewer1/uhttp v0.0.4 h1:3RrRmz0fjsnLKuAs5fkflOcsjuBFoL0XvC6Xct/TDCk= github.com/jacobbrewer1/uhttp v0.0.4/go.mod h1:qwLAdGw4SLYJY2MS0sE1m/w1ILTr61bjwRtlbGwqo5c= -github.com/jacobbrewer1/vaulty v0.1.3 h1:0L3JnmzqcxL91COVMUkT+HWje3jTy9YjYbpFOyGmHBo= -github.com/jacobbrewer1/vaulty v0.1.3/go.mod h1:c1miXLEnUIejWOZpp2mEl7Jc/0jlVPtcKTrPbmnASXk= +github.com/jacobbrewer1/vaulty v0.1.4 h1:JLcSDX5f1UMBxNNzCxQ0neqV4Mvw6030OgxE44B/pqI= +github.com/jacobbrewer1/vaulty v0.1.4/go.mod h1:BDrZxaM1mMWSqJ3UXHGOQyy6+2l5NxjAjva2V7ijwNI= github.com/jacobbrewer1/workerpool v0.0.2 h1:H5+PRi1JLtOvPXHiER6+MCEfqIMn8d0MyZ0Mi4emYqM= github.com/jacobbrewer1/workerpool v0.0.2/go.mod h1:PKV+PF+dbELi8p+abwzVDRy2FtgpdLJCFKlsz5thaYY= github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg= diff --git a/vendor/github.com/hashicorp/vault/api/auth/kubernetes/LICENSE b/vendor/github.com/hashicorp/vault/api/auth/kubernetes/LICENSE new file mode 100644 index 0000000..fbeca00 --- /dev/null +++ b/vendor/github.com/hashicorp/vault/api/auth/kubernetes/LICENSE @@ -0,0 +1,92 @@ +License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved. +"Business Source License" is a trademark of MariaDB Corporation Ab. + +Parameters + +Licensor: HashiCorp, Inc. +Licensed Work: Vault Version 1.15.0 or later. The Licensed Work is (c) 2024 + HashiCorp, Inc. +Additional Use Grant: You may make production use of the Licensed Work, provided + Your use does not include offering the Licensed Work to third + parties on a hosted or embedded basis in order to compete with + HashiCorp's paid version(s) of the Licensed Work. For purposes + of this license: + + A "competitive offering" is a Product that is offered to third + parties on a paid basis, including through paid support + arrangements, that significantly overlaps with the capabilities + of HashiCorp's paid version(s) of the Licensed Work. If Your + Product is not a competitive offering when You first make it + generally available, it will not become a competitive offering + later due to HashiCorp releasing a new version of the Licensed + Work with additional capabilities. In addition, Products that + are not provided on a paid basis are not competitive. + + "Product" means software that is offered to end users to manage + in their own environments or offered as a service on a hosted + basis. + + "Embedded" means including the source code or executable code + from the Licensed Work in a competitive offering. "Embedded" + also means packaging the competitive offering in such a way + that the Licensed Work must be accessed or downloaded for the + competitive offering to operate. + + Hosting or using the Licensed Work(s) for internal purposes + within an organization is not considered a competitive + offering. HashiCorp considers your organization to include all + of your affiliates under common control. + + For binding interpretive guidance on using HashiCorp products + under the Business Source License, please visit our FAQ. + (https://www.hashicorp.com/license-faq) +Change Date: Four years from the date the Licensed Work is published. +Change License: MPL 2.0 + +For information about alternative licensing arrangements for the Licensed Work, +please contact licensing@hashicorp.com. + +Notice + +Business Source License 1.1 + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. diff --git a/vendor/github.com/hashicorp/vault/api/auth/kubernetes/kubernetes.go b/vendor/github.com/hashicorp/vault/api/auth/kubernetes/kubernetes.go new file mode 100644 index 0000000..f0e38c1 --- /dev/null +++ b/vendor/github.com/hashicorp/vault/api/auth/kubernetes/kubernetes.go @@ -0,0 +1,136 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package kubernetes + +import ( + "context" + "fmt" + "os" + + "github.com/hashicorp/vault/api" +) + +type KubernetesAuth struct { + roleName string + mountPath string + serviceAccountToken string +} + +var _ api.AuthMethod = (*KubernetesAuth)(nil) + +type LoginOption func(a *KubernetesAuth) error + +const ( + defaultMountPath = "kubernetes" + defaultServiceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" +) + +// NewKubernetesAuth creates a KubernetesAuth struct which can be passed to +// the client.Auth().Login method to authenticate to Vault. The roleName +// parameter should be the name of the role in Vault that was created with +// this app's Kubernetes service account bound to it. +// +// The Kubernetes service account token JWT is retrieved from +// /var/run/secrets/kubernetes.io/serviceaccount/token by default. To change this +// path, pass the WithServiceAccountTokenPath option. To instead pass the +// JWT directly as a string, or to read the value from an environment +// variable, use WithServiceAccountToken and WithServiceAccountTokenEnv respectively. +// +// Supported options: WithMountPath, WithServiceAccountTokenPath, WithServiceAccountTokenEnv, WithServiceAccountToken +func NewKubernetesAuth(roleName string, opts ...LoginOption) (*KubernetesAuth, error) { + if roleName == "" { + return nil, fmt.Errorf("no role name was provided") + } + + a := &KubernetesAuth{ + roleName: roleName, + mountPath: defaultMountPath, + } + + // Loop through each option + for _, opt := range opts { + // Call the option giving the instantiated + // *KubernetesAuth as the argument + err := opt(a) + if err != nil { + return nil, fmt.Errorf("error with login option: %w", err) + } + } + + if a.serviceAccountToken == "" { + token, err := readTokenFromFile(defaultServiceAccountTokenPath) + if err != nil { + return nil, fmt.Errorf("error reading service account token from default location: %w", err) + } + a.serviceAccountToken = token + } + + // return the modified auth struct instance + return a, nil +} + +func (a *KubernetesAuth) Login(ctx context.Context, client *api.Client) (*api.Secret, error) { + if ctx == nil { + ctx = context.Background() + } + + loginData := map[string]interface{}{ + "jwt": a.serviceAccountToken, + "role": a.roleName, + } + + path := fmt.Sprintf("auth/%s/login", a.mountPath) + resp, err := client.Logical().WriteWithContext(ctx, path, loginData) + if err != nil { + return nil, fmt.Errorf("unable to log in with Kubernetes auth: %w", err) + } + return resp, nil +} + +func WithMountPath(mountPath string) LoginOption { + return func(a *KubernetesAuth) error { + a.mountPath = mountPath + return nil + } +} + +// WithServiceAccountTokenPath allows you to specify a different path to +// where your application's Kubernetes service account token is mounted, +// instead of the default of /var/run/secrets/kubernetes.io/serviceaccount/token +func WithServiceAccountTokenPath(pathToToken string) LoginOption { + return func(a *KubernetesAuth) error { + token, err := readTokenFromFile(pathToToken) + if err != nil { + return fmt.Errorf("unable to read service account token from file: %w", err) + } + a.serviceAccountToken = token + return nil + } +} + +func WithServiceAccountToken(jwt string) LoginOption { + return func(a *KubernetesAuth) error { + a.serviceAccountToken = jwt + return nil + } +} + +func WithServiceAccountTokenEnv(envVar string) LoginOption { + return func(a *KubernetesAuth) error { + token := os.Getenv(envVar) + if token == "" { + return fmt.Errorf("service account token was specified with an environment variable with an empty value") + } + a.serviceAccountToken = token + return nil + } +} + +func readTokenFromFile(filepath string) (string, error) { + jwt, err := os.ReadFile(filepath) + if err != nil { + return "", fmt.Errorf("unable to read file containing service account token: %w", err) + } + return string(jwt), nil +} diff --git a/vendor/github.com/jacobbrewer1/vaulty/.clog.toml b/vendor/github.com/jacobbrewer1/vaulty/.clog.toml index 54320f4..78294d2 100644 --- a/vendor/github.com/jacobbrewer1/vaulty/.clog.toml +++ b/vendor/github.com/jacobbrewer1/vaulty/.clog.toml @@ -1,7 +1,7 @@ [clog] # A repository link with the trailing '.git' which will be used to generate # all commit and issue links -repository = "https://github.com/Jacobbrewer1/vaulty" +repository = "https://github.com/jacobbrewer1/vaulty" # A constant release title subtitle = "What's Changed" diff --git a/vendor/github.com/jacobbrewer1/vaulty/Makefile b/vendor/github.com/jacobbrewer1/vaulty/Makefile index 65e463e..0535ffb 100644 --- a/vendor/github.com/jacobbrewer1/vaulty/Makefile +++ b/vendor/github.com/jacobbrewer1/vaulty/Makefile @@ -2,6 +2,9 @@ hash = $(shell git rev-parse --short HEAD) DATE = $(shell date -u +'%Y-%m-%dT%H:%M:%SZ') +codegen: + @echo "Generating code" + go generate ./... pr-approval: @echo "Running PR CI" go build ./... diff --git a/vendor/github.com/jacobbrewer1/vaulty/client_options.go b/vendor/github.com/jacobbrewer1/vaulty/client_options.go index 64062d4..4a9e378 100644 --- a/vendor/github.com/jacobbrewer1/vaulty/client_options.go +++ b/vendor/github.com/jacobbrewer1/vaulty/client_options.go @@ -2,10 +2,12 @@ package vaulty import ( "context" + "fmt" "log/slog" "os" hashiVault "github.com/hashicorp/vault/api" + auth "github.com/hashicorp/vault/api/auth/kubernetes" ) type ClientOption func(c *client) @@ -52,3 +54,37 @@ func WithKvv2Mount(mount string) ClientOption { c.kvv2Mount = mount } } + +func WithKubernetesAuthDefault() ClientOption { + return func(c *client) { + c.auth = func(v *hashiVault.Client) (*hashiVault.Secret, error) { + role := os.Getenv(envKubernetesRole) + if role == "" { + return nil, fmt.Errorf("%s environment variable not set", envKubernetesRole) + } + + return kubernetesLogin(v, role, auth.WithServiceAccountTokenPath(kubernetesServiceAccountTokenPath)) + } + } +} + +func WithKubernetesAuthFromEnv() ClientOption { + return func(c *client) { + c.auth = func(v *hashiVault.Client) (*hashiVault.Secret, error) { + role := os.Getenv(envKubernetesRole) + if role == "" { + return nil, fmt.Errorf("%s environment variable not set", envKubernetesRole) + } + + return kubernetesLogin(v, role, auth.WithServiceAccountTokenEnv(envKubernetesToken)) + } + } +} + +func WithKubernetesAuth(role, token string) ClientOption { + return func(c *client) { + c.auth = func(v *hashiVault.Client) (*hashiVault.Secret, error) { + return kubernetesLogin(v, role, auth.WithServiceAccountToken(token)) + } + } +} diff --git a/vendor/github.com/jacobbrewer1/vaulty/consts.go b/vendor/github.com/jacobbrewer1/vaulty/consts.go index 2788566..bd78ebf 100644 --- a/vendor/github.com/jacobbrewer1/vaulty/consts.go +++ b/vendor/github.com/jacobbrewer1/vaulty/consts.go @@ -8,4 +8,9 @@ const ( TransitKeyCipherText = "ciphertext" TransitKeyPlainText = "plaintext" + + envKubernetesRole = "KUBERNETES_ROLE" + envKubernetesToken = "KUBERNETES_TOKEN" + + kubernetesServiceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" ) diff --git a/vendor/github.com/jacobbrewer1/vaulty/kubernetes.go b/vendor/github.com/jacobbrewer1/vaulty/kubernetes.go new file mode 100644 index 0000000..06286dd --- /dev/null +++ b/vendor/github.com/jacobbrewer1/vaulty/kubernetes.go @@ -0,0 +1,35 @@ +package vaulty + +import ( + "context" + "fmt" + "time" + + hashiVault "github.com/hashicorp/vault/api" + auth "github.com/hashicorp/vault/api/auth/kubernetes" +) + +func kubernetesLogin(client *hashiVault.Client, role string, token auth.LoginOption) (*hashiVault.Secret, error) { + k8sAuth, err := auth.NewKubernetesAuth( + role, + token, + ) + if err != nil { + return nil, fmt.Errorf("unable to initialize kubernetes auth method: %w", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + authInfo, err := client.Auth().Login(ctx, k8sAuth) + if err != nil { + return nil, fmt.Errorf("unable to login to kubernetes auth method: %w", err) + } + if authInfo == nil { + return nil, fmt.Errorf("no auth info was returned after login") + } + + client.SetToken(authInfo.Auth.ClientToken) + + return authInfo, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6661caf..b0adf7a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -130,6 +130,9 @@ github.com/hashicorp/vault/api # github.com/hashicorp/vault/api/auth/approle v0.8.0 ## explicit; go 1.21 github.com/hashicorp/vault/api/auth/approle +# github.com/hashicorp/vault/api/auth/kubernetes v0.8.0 +## explicit; go 1.21 +github.com/hashicorp/vault/api/auth/kubernetes # github.com/hashicorp/vault/api/auth/userpass v0.8.0 ## explicit; go 1.21 github.com/hashicorp/vault/api/auth/userpass @@ -154,7 +157,7 @@ github.com/jacobbrewer1/patcher/inserter ## explicit; go 1.23 github.com/jacobbrewer1/uhttp github.com/jacobbrewer1/uhttp/common -# github.com/jacobbrewer1/vaulty v0.1.3 +# github.com/jacobbrewer1/vaulty v0.1.4 ## explicit; go 1.23 github.com/jacobbrewer1/vaulty github.com/jacobbrewer1/vaulty/repositories