The Vault Kubernetes Auth method’s service account token validation is where Kubernetes service accounts are used to authenticate with Vault. This means your applications running in Kubernetes can get Vault tokens without needing to manage separate Vault credentials. Vault validates the service account token against the Kubernetes API server to prove the identity of the requesting pod.
Let’s see Vault Kubernetes Auth in action. Imagine a simple Go application running in a Kubernetes pod that needs to retrieve a secret from Vault.
First, we configure the Kubernetes auth method in Vault:
vault auth enable kubernetes
vault write auth/kubernetes/config \
host="https://10.0.0.1:6443" \
kubernetes_ca_cert=@/path/to/ca.crt \
issuer="https://kubernetes.default.svc.cluster.local"
Here, host is your Kubernetes API server endpoint, kubernetes_ca_cert is the CA certificate for your Kubernetes cluster, and issuer is the expected issuer of the service account tokens.
Next, we create a Vault role that maps Kubernetes service accounts to Vault policies:
vault write auth/kubernetes/role/my-app-role \
bound_service_account_names=my-app-sa \
bound_service_account_namespaces=default \
policies=my-app-policy \
ttl=24h
This role, my-app-role, will allow service accounts named my-app-sa in the default namespace to authenticate and will grant them the my-app-policy for 24 hours.
Now, in our Kubernetes pod, the application will fetch its service account token and send it to Vault:
// Example Go code snippet
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
vault "github.com/hashicorp/vault/api"
)
func main() {
vaultAddr := "http://vault.default.svc.cluster.local:8200" // Vault service address
// Read the service account token and path
token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
if err != nil {
log.Fatalf("failed to read service account token: %v", err)
}
// KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT are injected by Kubernetes
// We use these to construct the Kubernetes API host if not explicitly provided in Vault config
k8sHost := fmt.Sprintf("https://%s:%s", os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT"))
// Configure Vault client
config := vault.DefaultConfig()
config.Address = vaultAddr
client, err := vault.NewClient(config)
if err != nil {
log.Fatalf("failed to create Vault client: %v", err)
}
// Authenticate with Vault using Kubernetes auth
authPath := "auth/kubernetes/login"
loginData := map[string]interface{}{
"role": "my-app-role",
"jwt": string(token),
}
secret, err := client.Logical.Write(authPath, loginData)
if err != nil {
log.Fatalf("failed to authenticate with Vault: %v", err)
}
vaultToken := secret.Auth.ClientToken
client.SetToken(vaultToken)
// Now you can use the client to interact with Vault
fmt.Println("Successfully authenticated with Vault. Vault token:", vaultToken)
// Example: Read a secret
data, err := client.Logical.Read("secret/my-data")
if err != nil {
log.Fatalf("failed to read secret: %v", err)
}
fmt.Printf("Secret data: %+v\n", data.Data)
}
In this Go code, the application reads its service account token from /var/run/secrets/kubernetes.io/serviceaccount/token, which is automatically mounted by Kubernetes. It then sends this token along with the Vault role name (my-app-role) to Vault’s Kubernetes auth login endpoint. Vault verifies the token with the Kubernetes API server. If valid, Vault issues a Vault token to the application.
The bound_service_account_names and bound_service_account_namespaces in the Vault role are crucial. They ensure that only specific service accounts within specific namespaces can authenticate using that role. The policies field dictates what actions the authenticated application can perform within Vault.
The core of the validation process lies in Vault’s interaction with the Kubernetes API. When a service account token is presented, Vault uses the configured host and kubernetes_ca_cert to connect to the Kubernetes API server. It then calls an endpoint like /api/v1/namespaces/default/serviceaccounts/my-app-sa (or similar, depending on Vault’s internal logic) to verify the token’s validity and extract information like the service account’s name, namespace, and UID. The issuer field in the Vault config is used to ensure the token is actually issued by the expected Kubernetes cluster. If all checks pass, Vault considers the identity of the service account to be proven.
This mechanism effectively decouples your application’s authentication from managing static Vault tokens. The lifecycle of the service account token is managed by Kubernetes, and Vault leverages this for secure, dynamic authentication.
One aspect that often trips people up is the audience field, which is a JWT claim. When Vault is configured with an issuer that matches the Kubernetes API server’s iss claim, it implicitly expects the aud claim in the token to contain the Vault’s issuer URL. If your Kubernetes cluster’s tokens don’t have an audience claim that includes the Vault issuer URL (which is common if you haven’t explicitly configured it in the Kubernetes API server), Vault will reject the token. You often need to ensure the Kubernetes API server is configured to issue tokens with an appropriate audience, or configure Vault’s kubernetes_auth_audience parameter to match what your Kubernetes cluster is issuing.
The next step after successfully authenticating your applications is to explore how to manage secrets dynamically based on these authenticated identities.