Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Auth config block supports common arguments from env and flags #577

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ Changes:
* `github.com/hashicorp/go-secure-stdlib/tlsutil` v0.1.2 => v0.1.3
* `github.com/prometheus/client_golang` v1.17.0 => v1.18.0

Improvements:
* Auth config block can support common arguments from env and flags [GH-577](https://github.com/hashicorp/vault-k8s/pull/577)

Bugs:
* Prevent incorrect k8s serviceaccount volume mount to be picked when multiple serviceaccount volumes are present [GH-577](https://github.com/hashicorp/vault-k8s/pull/577)

## 1.3.1 (October 25, 2023)

Changes:
Expand Down
3 changes: 2 additions & 1 deletion agent-inject/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,8 @@ func serviceaccount(pod *corev1.Pod) (*ServiceAccountTokenVolume, error) {
// Fallback to searching for normal service account token
for _, container := range pod.Spec.Containers {
for _, volumes := range container.VolumeMounts {
if strings.Contains(volumes.MountPath, "serviceaccount") {
// Must find only kubernetes serviceaccount token
if strings.Contains(volumes.MountPath, "kubernetes.io/serviceaccount") {
return &ServiceAccountTokenVolume{
Name: volumes.Name,
MountPath: volumes.MountPath,
Expand Down
61 changes: 59 additions & 2 deletions agent-inject/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func testPod(annotations map[string]string) *corev1.Pod {
VolumeMounts: []corev1.VolumeMount{
{
Name: "foobar",
MountPath: "serviceaccount/somewhere",
MountPath: "kubernetes.io/serviceaccount/somewhere",
},
{
Name: "tobecopied",
Expand Down Expand Up @@ -70,7 +70,7 @@ func testPodIRSA(annotations map[string]string) *corev1.Pod {
VolumeMounts: []corev1.VolumeMount{
{
Name: "foobar",
MountPath: "serviceaccount/somewhere",
MountPath: "kubernetes.io/serviceaccount/somewhere",
},
},
},
Expand Down Expand Up @@ -564,6 +564,63 @@ func Test_serviceaccount(t *testing.T) {
},
},
},
"multiple service account tokens from different sources": {
expected: &ServiceAccountTokenVolume{
Name: "kube-api-access-4bfzq",
MountPath: "/var/run/secrets/kubernetes.io/serviceaccount",
TokenPath: "token",
},
expectedError: "",
pod: &corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
VolumeMounts: []corev1.VolumeMount{
{
Name: "aws-iam-token",
MountPath: "/var/run/secrets/eks.amazonaws.com/serviceaccount",
},
{
Name: "kube-api-access-4bfzq",
MountPath: "/var/run/secrets/kubernetes.io/serviceaccount",
},
},
},
},
Volumes: []corev1.Volume{
{
Name: "aws-iam-token",
VolumeSource: corev1.VolumeSource{
Projected: &corev1.ProjectedVolumeSource{
Sources: []corev1.VolumeProjection{
{
ServiceAccountToken: &corev1.ServiceAccountTokenProjection{
Path: "token",
Audience: "sts.amazonaws.com",
},
},
},
},
},
},
{
Name: "kube-api-access-4bfzq",
VolumeSource: corev1.VolumeSource{
Projected: &corev1.ProjectedVolumeSource{
Sources: []corev1.VolumeProjection{
{
ServiceAccountToken: &corev1.ServiceAccountTokenProjection{
Path: "token",
},
},
},
},
},
},
},
},
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
Expand Down
12 changes: 11 additions & 1 deletion agent-inject/agent/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ type AgentConfig struct {
Address string
AuthType string
AuthPath string
AuthConfigExtraArgs map[string]string
VaultNamespace string
Namespace string
RevokeOnShutdown bool
Expand Down Expand Up @@ -385,6 +386,14 @@ func Init(pod *corev1.Pod, cfg AgentConfig) error {
pod.ObjectMeta.Annotations[AnnotationVaultAuthPath] = cfg.AuthPath
}

// Set auth config extra args to annotations
for key, val := range cfg.AuthConfigExtraArgs {
annotationKey := fmt.Sprintf("%s-%s", AnnotationVaultAuthConfig, key)
if _, ok := pod.ObjectMeta.Annotations[annotationKey]; !ok {
pod.ObjectMeta.Annotations[annotationKey] = val
}
}

if _, ok := pod.ObjectMeta.Annotations[AnnotationVaultNamespace]; !ok {
pod.ObjectMeta.Annotations[AnnotationVaultNamespace] = cfg.VaultNamespace
}
Expand Down Expand Up @@ -877,7 +886,8 @@ func (a *Agent) authConfig() map[string]interface{} {

// set token_path parameter from the Agent prior to assignment from annotations
// so that annotations can override the value assigned in agent.go https://github.com/hashicorp/vault-k8s/issues/456
if a.ServiceAccountTokenVolume.MountPath != "" && a.ServiceAccountTokenVolume.TokenPath != "" {
// Also the token_path parameter must be set iff AuthType is "kubernetes". Other auth types doesn't require this attribute
if a.Vault.AuthType == DefaultVaultAuthType && a.ServiceAccountTokenVolume.MountPath != "" && a.ServiceAccountTokenVolume.TokenPath != "" {
authConfig["token_path"] = path.Join(a.ServiceAccountTokenVolume.MountPath, a.ServiceAccountTokenVolume.TokenPath)
}

Expand Down
6 changes: 3 additions & 3 deletions agent-inject/agent/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,7 @@ func TestAuthConfigAnnotations(t *testing.T) {
},
map[string]interface{}{
"role": "backwardscompat",
"token_path": "serviceaccount/somewhere/token",
"token_path": "kubernetes.io/serviceaccount/somewhere/token",
},
},
{
Expand All @@ -983,7 +983,7 @@ func TestAuthConfigAnnotations(t *testing.T) {
},
map[string]interface{}{
"role": "backwardscompat",
"token_path": "serviceaccount/somewhere/token",
"token_path": "kubernetes.io/serviceaccount/somewhere/token",
},
},
{
Expand All @@ -1008,7 +1008,7 @@ func TestAuthConfigAnnotations(t *testing.T) {
"client_cert": "baz",
"credential_poll_interval": "1", // string->int conversion left up to consuming app HCL parser
"remove_secret_id_file_after_reading": "false", // string->bool, same as above
"token_path": "serviceaccount/somewhere/token",
"token_path": "kubernetes.io/serviceaccount/somewhere/token",
},
},
}
Expand Down
8 changes: 4 additions & 4 deletions agent-inject/agent/container_sidecar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1289,14 +1289,14 @@ func TestAgentJsonPatch(t *testing.T) {
baseContainerEnvVars,
corev1.EnvVar{Name: "VAULT_LOG_LEVEL", Value: "info"},
corev1.EnvVar{Name: "VAULT_LOG_FORMAT", Value: "standard"},
corev1.EnvVar{Name: "VAULT_CONFIG", Value: "eyJhdXRvX2F1dGgiOnsibWV0aG9kIjp7InR5cGUiOiJrdWJlcm5ldGVzIiwibW91bnRfcGF0aCI6InRlc3QiLCJjb25maWciOnsicm9sZSI6InJvbGUiLCJ0b2tlbl9wYXRoIjoic2VydmljZWFjY291bnQvc29tZXdoZXJlL3Rva2VuIn19LCJzaW5rIjpbeyJ0eXBlIjoiZmlsZSIsImNvbmZpZyI6eyJwYXRoIjoiL2hvbWUvdmF1bHQvLnZhdWx0LXRva2VuIn19XX0sImV4aXRfYWZ0ZXJfYXV0aCI6ZmFsc2UsInBpZF9maWxlIjoiL2hvbWUvdmF1bHQvLnBpZCIsInZhdWx0Ijp7ImFkZHJlc3MiOiJodHRwOi8vZm9vYmFyOjEyMzQifSwidGVtcGxhdGVfY29uZmlnIjp7ImV4aXRfb25fcmV0cnlfZmFpbHVyZSI6dHJ1ZX19"},
corev1.EnvVar{Name: "VAULT_CONFIG", Value: "eyJhdXRvX2F1dGgiOnsibWV0aG9kIjp7InR5cGUiOiJrdWJlcm5ldGVzIiwibW91bnRfcGF0aCI6InRlc3QiLCJjb25maWciOnsicm9sZSI6InJvbGUiLCJ0b2tlbl9wYXRoIjoia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zb21ld2hlcmUvdG9rZW4ifX0sInNpbmsiOlt7InR5cGUiOiJmaWxlIiwiY29uZmlnIjp7InBhdGgiOiIvaG9tZS92YXVsdC8udmF1bHQtdG9rZW4ifX1dfSwiZXhpdF9hZnRlcl9hdXRoIjpmYWxzZSwicGlkX2ZpbGUiOiIvaG9tZS92YXVsdC8ucGlkIiwidmF1bHQiOnsiYWRkcmVzcyI6Imh0dHA6Ly9mb29iYXI6MTIzNCJ9LCJ0ZW1wbGF0ZV9jb25maWciOnsiZXhpdF9vbl9yZXRyeV9mYWlsdXJlIjp0cnVlfX0="},
),
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{"cpu": resource.MustParse("500m"), "memory": resource.MustParse("128Mi")},
Requests: v1.ResourceList{"cpu": resource.MustParse("250m"), "memory": resource.MustParse("64Mi")},
},
VolumeMounts: []v1.VolumeMount{
{Name: "foobar", ReadOnly: true, MountPath: "serviceaccount/somewhere"},
{Name: "foobar", ReadOnly: true, MountPath: "kubernetes.io/serviceaccount/somewhere"},
{Name: "home-sidecar", MountPath: "/home/vault"},
{Name: "vault-secrets", MountPath: "/vault/secrets"},
},
Expand Down Expand Up @@ -1325,11 +1325,11 @@ func TestAgentJsonPatch(t *testing.T) {
baseContainerEnvVars,
corev1.EnvVar{Name: "VAULT_LOG_LEVEL", Value: "info"},
corev1.EnvVar{Name: "VAULT_LOG_FORMAT", Value: "standard"},
corev1.EnvVar{Name: "VAULT_CONFIG", Value: "eyJhdXRvX2F1dGgiOnsibWV0aG9kIjp7InR5cGUiOiJrdWJlcm5ldGVzIiwibW91bnRfcGF0aCI6InRlc3QiLCJjb25maWciOnsicm9sZSI6InJvbGUiLCJ0b2tlbl9wYXRoIjoic2VydmljZWFjY291bnQvc29tZXdoZXJlL3Rva2VuIn19LCJzaW5rIjpbeyJ0eXBlIjoiZmlsZSIsImNvbmZpZyI6eyJwYXRoIjoiL2hvbWUvdmF1bHQvLnZhdWx0LXRva2VuIn19XX0sImV4aXRfYWZ0ZXJfYXV0aCI6dHJ1ZSwicGlkX2ZpbGUiOiIvaG9tZS92YXVsdC8ucGlkIiwidmF1bHQiOnsiYWRkcmVzcyI6Imh0dHA6Ly9mb29iYXI6MTIzNCJ9LCJ0ZW1wbGF0ZV9jb25maWciOnsiZXhpdF9vbl9yZXRyeV9mYWlsdXJlIjp0cnVlfX0="},
corev1.EnvVar{Name: "VAULT_CONFIG", Value: "eyJhdXRvX2F1dGgiOnsibWV0aG9kIjp7InR5cGUiOiJrdWJlcm5ldGVzIiwibW91bnRfcGF0aCI6InRlc3QiLCJjb25maWciOnsicm9sZSI6InJvbGUiLCJ0b2tlbl9wYXRoIjoia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zb21ld2hlcmUvdG9rZW4ifX0sInNpbmsiOlt7InR5cGUiOiJmaWxlIiwiY29uZmlnIjp7InBhdGgiOiIvaG9tZS92YXVsdC8udmF1bHQtdG9rZW4ifX1dfSwiZXhpdF9hZnRlcl9hdXRoIjp0cnVlLCJwaWRfZmlsZSI6Ii9ob21lL3ZhdWx0Ly5waWQiLCJ2YXVsdCI6eyJhZGRyZXNzIjoiaHR0cDovL2Zvb2JhcjoxMjM0In0sInRlbXBsYXRlX2NvbmZpZyI6eyJleGl0X29uX3JldHJ5X2ZhaWx1cmUiOnRydWV9fQ=="},
)
baseInitContainer.VolumeMounts = []v1.VolumeMount{
{Name: "home-init", MountPath: "/home/vault"},
{Name: "foobar", ReadOnly: true, MountPath: "serviceaccount/somewhere"},
{Name: "foobar", ReadOnly: true, MountPath: "kubernetes.io/serviceaccount/somewhere"},
{Name: "vault-secrets", MountPath: "/vault/secrets"},
}
baseInitContainer.Lifecycle = nil
Expand Down
2 changes: 2 additions & 0 deletions agent-inject/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type Handler struct {
VaultCACertBytes string
VaultAuthType string
VaultAuthPath string
VaultAuthConfigExtraArgs map[string]string
VaultNamespace string
ProxyAddress string
ImageVault string
Expand Down Expand Up @@ -192,6 +193,7 @@ func (h *Handler) Mutate(req *admissionv1.AdmissionRequest) *admissionv1.Admissi
Address: h.VaultAddress,
AuthType: h.VaultAuthType,
AuthPath: h.VaultAuthPath,
AuthConfigExtraArgs: h.VaultAuthConfigExtraArgs,
VaultNamespace: h.VaultNamespace,
ProxyAddress: h.ProxyAddress,
Namespace: req.Namespace,
Expand Down
4 changes: 2 additions & 2 deletions agent-inject/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestHandlerHandle(t *testing.T) {
VolumeMounts: []corev1.VolumeMount{
{
Name: "foobar",
MountPath: "serviceaccount/somewhere",
MountPath: "kubernetes.io/serviceaccount/somewhere",
},
},
},
Expand All @@ -49,7 +49,7 @@ func TestHandlerHandle(t *testing.T) {
VolumeMounts: []corev1.VolumeMount{
{
Name: "foobar",
MountPath: "serviceaccount/somewhere",
MountPath: "kubernetes.io/serviceaccount/somewhere",
},
},
},
Expand Down
23 changes: 23 additions & 0 deletions subcommand/injector/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type Command struct {
flagVaultImage string // Name of the Vault Image to use
flagVaultAuthType string // Type of Vault Auth Method to use
flagVaultAuthPath string // Mount path of the Vault Auth Method
flagVaultAuthConfigExtraArgs string // Extra arguments for the Vault Auth Method Config block
flagVaultNamespace string // Vault enterprise namespace
flagRevokeOnShutdown bool // Revoke Vault Token on pod shutdown
flagRunAsUser string // User (uid) to run Vault agent as
Expand Down Expand Up @@ -192,11 +193,17 @@ func (c *Command) Run(args []string) int {
go c.certWatcher(ctx, certCh, clientset, leaderElector, logger.Named("certwatcher"))

// Build the HTTP handler and server
authConfigExtraArgs, err := c.makeVaultAuthConfigExtraArgs()
if err != nil {
c.UI.Error(fmt.Sprintf("Error reading VaultAuthConfigExtraArgs: %s", err))
return 1
}
injector := agentInject.Handler{
VaultAddress: c.flagVaultService,
VaultCACertBytes: c.flagVaultCACertBytes,
VaultAuthType: c.flagVaultAuthType,
VaultAuthPath: c.flagVaultAuthPath,
VaultAuthConfigExtraArgs: authConfigExtraArgs,
VaultNamespace: c.flagVaultNamespace,
ProxyAddress: c.flagProxyAddress,
ImageVault: c.flagVaultImage,
Expand Down Expand Up @@ -310,6 +317,22 @@ func (c *Command) handleReady(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(204)
}

func (c *Command) makeVaultAuthConfigExtraArgs() (map[string]string, error) {
extraArgs := make(map[string]string)
if c.flagVaultAuthConfigExtraArgs == "" {
return extraArgs, nil
}
for _, extraArg := range strings.Split(c.flagVaultAuthConfigExtraArgs, ",") {
extraArgKey, extraArgVal, found := strings.Cut(extraArg, ":")
if !found {
return nil, fmt.Errorf("invalid auth config extra args: %s",
c.flagVaultAuthConfigExtraArgs)
}
extraArgs[strings.TrimSpace(extraArgKey)] = strings.TrimSpace(extraArgVal)
}
return extraArgs, nil
}

func (c *Command) makeTLSConfig() (*tls.Config, error) {
minTLSVersion, ok := tlsutil.TLSLookup[c.flagTLSMinVersion]
if !ok {
Expand Down
7 changes: 7 additions & 0 deletions subcommand/injector/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ type Specification struct {
// VaultAuthPath is the AGENT_INJECT_VAULT_AUTH_PATH environment variable.
VaultAuthPath string `split_words:"true"`

// VaultAuthConfigExtraArgs is the AGENT_INJECT_VAULT_AUTH_CONFIG_EXTRA_ARGS environment variable.
VaultAuthConfigExtraArgs string `split_words:"true"`

// VaultNamespace is the AGENT_INJECT_VAULT_NAMESPACE environment variable.
VaultNamespace string `split_words:"true"`

Expand Down Expand Up @@ -320,6 +323,10 @@ func (c *Command) parseEnvs() error {
c.flagVaultAuthPath = envs.VaultAuthPath
}

if envs.VaultAuthConfigExtraArgs != "" {
c.flagVaultAuthConfigExtraArgs = envs.VaultAuthConfigExtraArgs
}

if envs.VaultNamespace != "" {
c.flagVaultNamespace = envs.VaultNamespace
}
Expand Down
1 change: 1 addition & 0 deletions subcommand/injector/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func TestCommandEnvs(t *testing.T) {
{env: "AGENT_INJECT_VAULT_CACERT_BYTES", value: "foo", cmdPtr: &cmd.flagVaultCACertBytes},
{env: "AGENT_INJECT_PROXY_ADDR", value: "http://proxy:3128", cmdPtr: &cmd.flagProxyAddress},
{env: "AGENT_INJECT_VAULT_AUTH_PATH", value: "auth-path-test", cmdPtr: &cmd.flagVaultAuthPath},
{env: "AGENT_INJECT_VAULT_AUTH_CONFIG_EXTRA_ARGS", value: "key1:val1", cmdPtr: &cmd.flagVaultAuthConfigExtraArgs},
{env: "AGENT_INJECT_VAULT_IMAGE", value: "hashicorp/vault:1.15.1", cmdPtr: &cmd.flagVaultImage},
{env: "AGENT_INJECT_VAULT_NAMESPACE", value: "test-namespace", cmdPtr: &cmd.flagVaultNamespace},
{env: "AGENT_INJECT_TLS_KEY_FILE", value: "server.key", cmdPtr: &cmd.flagKeyFile},
Expand Down