Skip to content

Latest commit

 

History

History
409 lines (329 loc) · 15.4 KB

File metadata and controls

409 lines (329 loc) · 15.4 KB
layout docs
page_title Kubernetes - Auth Methods
description The Kubernetes auth method allows automated authentication of Kubernetes Service Accounts.

Kubernetes Auth Method

The kubernetes auth method can be used to authenticate with Vault using a Kubernetes Service Account Token. This method of authentication makes it easy to introduce a Vault token into a Kubernetes Pod.

Authentication

Via the CLI

The default path is /kubernetes. If this auth method was enabled at a different path, specify -path=/my-path in the CLI.

$ vault write auth/kubernetes/login role=demo jwt=...

Via the API

The default endpoint is auth/kubernetes/login. If this auth method was enabled at a different path, use that value instead of kubernetes.

$ curl \
    --request POST \
    --data '{"jwt": "<your service account jwt>", "role": "demo"}' \
    http://127.0.0.1:8200/v1/auth/kubernetes/login

The response will contain a token at auth.client_token:

{
  "auth": {
    "client_token": "38fe9691-e623-7238-f618-c94d4e7bc674",
    "accessor": "78e87a38-84ed-2692-538f-ca8b9f400ab3",
    "policies": ["default"],
    "metadata": {
      "role": "demo",
      "service_account_name": "vault-auth",
      "service_account_namespace": "default",
      "service_account_secret_name": "vault-auth-token-pd21c",
      "service_account_uid": "aa9aa8ff-98d0-11e7-9bb7-0800276d99bf"
    },
    "lease_duration": 2764800,
    "renewable": true
  }
}

Configuration

Auth methods must be configured in advance before users or machines can authenticate. These steps are usually completed by an operator or configuration management tool.

  1. Enable the Kubernetes auth method:

    $ vault auth enable kubernetes
    
  2. Use the /config endpoint to configure Vault to talk to Kubernetes. Use kubectl cluster-info to validate the Kubernetes host address and TCP port. Kubernetes 1.21+ clusters may require setting the service account issuer, as described here. For the list of available configuration options, please see the API documentation.

    $ vault write auth/kubernetes/config \
        token_reviewer_jwt="<your reviewer service account JWT>" \
        kubernetes_host=https://192.168.99.100:<your TCP port or blank for 443> \
        kubernetes_ca_cert=@ca.crt
    

    !> NOTE: The pattern Vault uses to authenticate Pods depends on sharing the JWT token over the network. Given the security model of Vault, this is allowable because Vault is part of the trusted compute base. In general, Kubernetes applications should not share this JWT with other applications, as it allows API calls to be made on behalf of the Pod and can result in unintended access being granted to 3rd parties.

  3. Create a named role:

    vault write auth/kubernetes/role/demo \
        bound_service_account_names=vault-auth \
        bound_service_account_namespaces=default \
        policies=default \
        ttl=1h
    

    This role authorizes the "vault-auth" service account in the default namespace and it gives it the default policy.

    For the complete list of configuration options, please see the API documentation.

How to work with short-lived Kubernetes tokens

Starting in version 1.21, the Kubernetes BoundServiceAccountTokenVolume feature defaults to enabled. This means the token mounted into containers is short-lived and auto-refreshed by default. It is also invalidated when the pod is deleted. These tokens are therefore unsuitable to be used as the token_reviewer_jwt parameter when configuring Kubernetes auth, because Vault stores that value in its own storage and does not automatically refresh it. There are a few different ways to configure Kubernetes auth in this scenario, each with their own tradeoffs.

Use the Vault client's JWT as the reviewer JWT

When configuring Kubernetes auth, you can omit the token_reviewer_jwt, and Vault will use the Vault client's JWT as its own auth token when communicating with the Kubernetes TokenReview API.

This means Vault does not store any JWTs and allows you to use short-lived tokens everywhere but adds some operational overhead to maintain the cluster role binding on the set of service accounts you want to be able to authenticate with Vault.

Continue using long-lived tokens

The default Kubernetes secret created for a service account is still long lived, and can be used as the token_reviewer_jwt without needing to refresh it. To find the secret, run:

kubectl get secret "$(kubectl get serviceaccount default -o jsonpath='{.secrets[0].name}')"

Using this maintains previous workflows but does not fully take advantage of the new default short-lived tokens.

Use JWT auth

Kubernetes auth is specialized to use Kubernetes' TokenReview API. However, the JWT tokens Kubernetes generates can also be verified using Kubernetes as an OIDC provider. The JWT auth method documentation has instructions for setting up JWT auth with Kubernetes as the OIDC provider.

This solution allows you to use short-lived tokens for all clients and removes the need for a reviewer JWT. However, the client tokens cannot be revoked before their TTL expires, so it is recommended to keep the TTL short with that limitation in mind.

Summary of options

Option | All tokens are short-lived | Can revoke tokens early | Other considerations

Use client JWT as reviewer JWT | Yes | Yes | Operational overhead Use long-lived token as reviewer JWT | No | Yes | Use JWT auth | Yes | No |

Discovering the service account issuer

-> Note: From Vault 1.9, disable_iss_validation and issuer are deprecated and the default for disable_iss_validation has changed to true. The following section only applies if you set disable_iss_validation=false. The Kubernetes API does the same validation when reviewing tokens, so enabling issuer validation is duplicated work.

Kubernetes 1.21+ clusters may require setting the service account issuer to the same value as kube-apiserver's --service-account-issuer flag. This is because the service account JWTs for these clusters may have an issuer specific to the cluster itself, instead of the old default of kubernetes/serviceaccount. If you are unable to check this value directly, you can run the following and look for the "iss" field to find the required value:

kubectl proxy &
curl --silent http://127.0.0.1:8001/api/v1/namespaces/default/serviceaccounts/default/token \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{"apiVersion": "authentication.k8s.io/v1", "kind": "TokenRequest"}' \
  | jq -r '.status.token' \
  | cut -d . -f2 \
  | base64 -D

Most clusters will also have that information available at the .well-known/openid-configuration endpoint:

kubectl proxy &
curl --silent http://127.0.0.1:8001/.well-known/openid-configuration | jq -r .issuer

This value is then used when configuring Kubernetes auth, e.g.:

vault write auth/kubernetes/config \
  token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
  kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  issuer="\"test-aks-cluster-dns-d6cbb78e.hcp.uksouth.azmk8s.io\""

Configuring Kubernetes

This auth method accesses the Kubernetes TokenReview API to validate the provided JWT is still valid. Kubernetes should be running with --service-account-lookup. This is defaulted to true from Kubernetes 1.7. Otherwise deleted tokens in Kubernetes will not be properly revoked and will be able to authenticate to this auth method.

Service Accounts used in this auth method will need to have access to the TokenReview API. If Kubernetes is configured to use RBAC roles, the Service Account should be granted permissions to access this API. The following example ClusterRoleBinding could be used to grant these permissions:

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: role-tokenreview-binding
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:auth-delegator
subjects:
  - kind: ServiceAccount
    name: vault-auth
    namespace: default

API

The Kubernetes Auth Plugin has a full HTTP API. Please see the API docs for more details.

Code Example

The following code snippet demonstrates the Kubernetes auth method to authenticate with Vault.

package main

import (
	"fmt"
	"os"

	vault "github.com/hashicorp/vault/api"
  auth "github.com/hashicorp/vault/api/auth/kubernetes"
)

// Fetches a key-value secret (kv-v2) after authenticating to Vault with a Kubernetes service account.
//
// As the client, all we need to do is pass along the JWT token representing our application's Kubernetes Service Account in our login request to Vault.
//
// For a more in-depth setup explanation, please see the full version of this code in the hashicorp/vault-examples repo.
func getSecretWithKubernetesAuth() (string, error) {
	// If set, the VAULT_ADDR environment variable will be the address that
	// your pod uses to communicate with Vault.
	config := vault.DefaultConfig() // modify for more granular configuration

	client, err := vault.NewClient(config)
	if err != nil {
		return "", fmt.Errorf("unable to initialize Vault client: %w", err)
	}

	// The service-account token will be read from the path where the token's
	// Kubernetes Secret is mounted. By default, Kubernetes will mount it to
	// /var/run/secrets/kubernetes.io/serviceaccount/token, but an administrator
	// may have configured it to be mounted elsewhere.
	// In that case, we'll use the option WithServiceAccountTokenPath to look
	// for the token there.
	k8sAuth, err := auth.NewKubernetesAuth(
		"dev-role-k8s",
		auth.WithServiceAccountTokenPath("path/to/service-account-token"),
	)
	if err != nil {
		return "", fmt.Errorf("unable to initialize Kubernetes auth method: %w", err)
	}

	authInfo, err := client.Auth().Login(context.TODO(), k8sAuth)
	if err != nil {
		return "", fmt.Errorf("unable to log in with Kubernetes auth: %w", err)
	}
	if authInfo == nil {
		return "", fmt.Errorf("no auth info was returned after login")
	}

	// get secret from Vault
	secret, err := client.Logical().Read("kv-v2/data/creds")
	if err != nil {
		return "", fmt.Errorf("unable to read secret: %w", err)
	}

	data, ok := secret.Data["data"].(map[string]interface{})
	if !ok {
		return "", fmt.Errorf("data type assertion failed: %T %#v", secret.Data["data"], secret.Data["data"])
	}

	// data map can contain more than one key-value pair,
	// in this case we're just grabbing one of them
	key := "password"
	value, ok := data[key].(string)
	if !ok {
		return "", fmt.Errorf("value type assertion failed: %T %#v", data[key], data[key])
	}

	return value, nil
}
using System;
using System.IO;
using VaultSharp;
using VaultSharp.V1.AuthMethods;
using VaultSharp.V1.AuthMethods.Kubernetes;
using VaultSharp.V1.Commons;

namespace Examples
{
    public class KubernetesAuthExample
    {
        const string DefaultTokenPath = "path/to/service-account-token";

        // Fetches a key-value secret (kv-v2) after authenticating to Vault with a Kubernetes service account.
        //
        // As the client, all we need to do is pass along the JWT token representing our application's Kubernetes Service Account in our login request to Vault.
        // This token is automatically mounted to your application's container by Kubernetes. Read more at https://www.vaultproject.io/docs/auth/kubernetes
        //
        // SETUP NOTES: If an operator has not already set up Kubernetes auth in Vault for you, then you must also first configure the Vault server with its own Service Account token to be able to communicate with the Kubernetes API
        // so it can verify that the client's service-account token is valid. The service account that will be performing that verification needs the ClusterRole system:auth-delegator.
        //
        //    export TOKEN_REVIEW_JWT=$(kubectl get secret $TOKEN_REVIEWER_SECRET --output='go-template={{ .data.token }}' | base64 --decode)
        //    export KUBE_HOST=$(kubectl config view --raw --minify --flatten --output='jsonpath={.clusters[].cluster.server}')
        //    kubectl config view --raw --minify --flatten --output='jsonpath={.clusters[].cluster.certificate-authority-data}' | base64 --decode > path/to/kube_ca_cert
        //
        //    vault write auth/kubernetes/config \
        //  	token_reviewer_jwt=${TOKEN_REVIEW_JWT} \
        //      kubernetes_host=${KUBE_HOST} \
        //      kubernetes_ca_cert=@path/to/kube_ca_cert \
        //      issuer="kubernetes/serviceaccount"
        //
        // The "issuer" field is normally only required when running Kubernetes 1.21 or above, and may differ from the default value above:
        // https://www.vaultproject.io/docs/auth/kubernetes#discovering-the-service-account-issuer.
        //
        // Finally, make sure to create a role in Vault bound to your pod's service account:
        //
        // 	vault write auth/kubernetes/role/dev-role-k8s \
        //     	policies="dev-policy" \
        //     	bound_service_account_names="my-app" \
        //		bound_service_account_namespaces="default"
        public string GetSecretWithK8s()
        {
            var vaultAddr = Environment.GetEnvironmentVariable("VAULT_ADDR");
            if(String.IsNullOrEmpty(vaultAddr))
            {
                throw new System.ArgumentNullException("Vault Address");
            }

            var roleName = Environment.GetEnvironmentVariable("VAULT_ROLE");
            if(String.IsNullOrEmpty(roleName))
            {
                throw new System.ArgumentNullException("Vault Role Name");
            }

            // Get the path to service account token or fall back on default path
            string pathToToken = String.IsNullOrEmpty(Environment.GetEnvironmentVariable("SA_TOKEN_PATH")) ? DefaultTokenPath : Environment.GetEnvironmentVariable("SA_TOKEN_PATH");
            string jwt = File.ReadAllText(pathToToken);

            IAuthMethodInfo authMethod = new KubernetesAuthMethodInfo(roleName, jwt);
            var vaultClientSettings = new VaultClientSettings(vaultAddr, authMethod);

            IVaultClient vaultClient = new VaultClient(vaultClientSettings);

            // We can retrieve the secret after creating our VaultClient object
            Secret<SecretData> kv2Secret = null;
            kv2Secret = vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(path: "/creds").Result;

            var password = kv2Secret.Data.Data["password"];

            return password.ToString();
        }
    }
}