Vault
Integrate Vault with Go Applications
In this tutorial, you will use the Vault Go SDK to programmatically integrate Vault into your application. The Vault SDK provides full programmatic control over secret retrieval and management directly within your application code. You will deploy a Go application to Kubernetes that uses the Vault SDK to authenticate with the Kubernetes auth method and retrieve secrets.
Overview
When building applications that need to access secrets from Vault, you have several options:
- Vault Agent: A client-side daemon that handles authentication, caching, and templating secrets to files
- Vault Secrets Operator (VSO): A Kubernetes operator that syncs secrets from Vault to Kubernetes secrets
- Vault SDK: A library you embed in your application for programmatic secret access. Vault supports multiple SDKs for different programming languages, including Go, Python, Java, and more. See Vault Client libraries for the full list.
The strength of the Vault SDK is its flexibility. It gives developers full control over how and when secrets are retrieved, refreshed, and used within the application logic. The SDK approach is ideal when you need flexibility beyond simple secret injection. For example, if your application needs to fetch different secrets based on runtime conditions, implement custom retry logic, or integrate secret retrieval into complex workflows, the SDK provides the necessary tools to do so.
Scenario
After deploying the HashiCups application using Vault Agent, Danielle realized that the application needs more flexibility in how it retrieves and manages secrets. The development team needs to:
- Fetch secrets dynamically based on runtime conditions
- Implement custom retry and error handling logic
- Integrate secret retrieval into existing application workflows
This level of control requires a programmatic method. Danielle decided to use the Vault Go SDK to give the application direct programmatic access to Vault.
Vault Go SDK
The Vault Go SDK is a Go library that provides a client interface for interacting with Vault. Unlike Vault Agent, which runs as a separate process, you embed the SDK directly into your application code.
Use the Vault SDK when you need:
- Programmatic control: Full control over authentication, secret retrieval, and error handling
- Dynamic secret fetching: Ability to fetch different secrets based on application logic
- Complex workflows: Integration of Vault operations into existing application business logic
- Custom retry logic: Implementation of application-specific retry and fallback mechanisms
Prerequisites
To complete this tutorial you need the following:
- Vault binary - Used to deploy a Vault dev server and interact with Vault from the command line
- Kubernetes command-line interface (CLI) - Used to interact with the Kubernetes cluster and manage resources
- Docker and Docker Desktop 4.34 or later for Docker on Linux
- minikube is a CLI tool that provisions and manages single-node Kubernetes clusters on your local system
- git to clone repositories used in the scenario
- jq to parse JSON output
Set up the lab
Danielle needs to set up a development environment to test the application before deploying it to production. They will start a Vault server in dev mode and a local Kubernetes cluster using minikube.
Clone the
learn-vault-golang-sdkrepository.$ git clone https://github.com/hashicorp-education/learn-vault-golang-sdk.gitChange into the
learn-vault-golang-sdkdirectory.$ cd learn-vault-golang-sdk/You need to run commands from the root of the repository, so make sure to stay in this directory for the rest of the tutorial.
Create a
certs/directory to store the TLS certificate generated by the Vault dev server.$ mkdir certsOpen a terminal and start a Vault dev server with the literal string
rootas the root token value, and enable TLS.$ vault server -dev -dev-root-token-id root -dev-tls -dev-tls-san=192.168.65.254 -dev-tls-cert-dir=certsThe dev server listens on the loopback interface at 127.0.0.1 on TCP port 8200 with TLS enabled. The dev server automatically unseals and prints the unseal key and initial root token values to the standard output.
The dev server also generates a self-signed TLS certificate in the
certsdirectory. It also adds the IP address192.168.65.254to the certificate's SANs and prints the path to the CA certificate in the output. You will need this to configure the vault client to trust the dev server's TLS certificate.The IP address
192.168.65.254represents the docker gateway address that minikube uses to access the host machine. This allows applications running in minikube to access the Vault dev server on the host machine using this IP address.In a new terminal, export the
VAULT_TOKEN,VAULT_ADDRandVAULT_CACERTenvironment variables. This configures thevaultCLI to communicate securely with the Vault dev server.$ export VAULT_ADDR='https://127.0.0.1:8200' VAULT_CACERT="$PWD/certs/vault-ca.pem" VAULT_TOKEN=rootStart up minikube in the same terminal window:
$ minikube start 😄 minikube v1.37.0 on Darwin 26.0.1 (arm64) ✨ Automatically selected the docker driver 📌 Using Docker Desktop driver with root privileges 👍 Starting "minikube" primary control-plane node in "minikube" cluster 🚜 Pulling base image v0.0.48 ... 💾 Downloading Kubernetes v1.34.0 preload ... > preloaded-images-k8s-v18-v1...: 332.38 MiB / 332.38 MiB 100.00% 48.22 M > gcr.io/k8s-minikube/kicbase...: 450.06 MiB / 450.06 MiB 100.00% 29.42 M 🔥 Creating docker container (CPUs=2, Memory=4000MB) ... 🐳 Preparing Kubernetes v1.34.0 on Docker 28.4.0 ... 🔗 Configuring bridge CNI (Container Networking Interface) ... 🔎 Verifying Kubernetes components... ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5 🌟 Enabled addons: default-storageclass, storage-provisioner 🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by defaultVerify the status of the minikube cluster:
$ minikube status minikube type: Control Plane host: Running kubelet: Running apiserver: Running kubeconfig: Configured
Configure Kubernetes and Vault resources
Before the application can authenticate to Vault, you need to configure the Kubernetes service account and corresponding Vault resources. The operations team at HashiCups manages this configuration.
The application will use the vault-auth service account to authenticate to Vault using the Kubernetes auth method and retrieve the secret.
Initialize the Terraform configuration.
$ terraform -chdir=terraform/kubernetes/ initThis configuration creates both Kubernetes and Vault resources. On the Kubernetes side, it creates the
vault-authservice account and a Kubernetes secret containing the service account token. On the Vault side, it creates a policy, a secret, and configures the Kubernetes auth method.$ VAULT_CACERT="$PWD/certs/vault-ca.pem" terraform -chdir=terraform/kubernetes/ apply -auto-approveThis creates a service account and binds it to the
system:auth-delegatorcluster role, which allows Vault to verify service account tokens with the Kubernetes API.The Kubernetes secret contains a JWT token and CA certificate. Vault uses these to authenticate requests from pods using this service account.
Verify the creation of the
vault-authservice account.$ kubectl get serviceaccount vault-auth NAME AGE vault-auth 10sVerify the creation of the secret.
$ kubectl get secret vault-auth-secret NAME TYPE DATA AGE vault-auth-secret kubernetes.io/service-account-token 3 70sThere is now a Kubernetes auth method configured in Vault, a role that maps the
vault-authservice account to a policy, and a secret stored in Vault. The application can now authenticate to Vault using the Kubernetes auth method and retrieve the secret.$ vault auth list Path Type Accessor Description Version ---- ---- -------- ----------- ------- kubernetes/ kubernetes auth_kubernetes_698962ea n/a n/a token/ token auth_token_7cc9864f token based credentials n/aA policy grants read and list access to secrets at the
secret/data/myapp/*path.$ vault policy read api-key-policy path "secret/data/myapp/*" { capabilities = ["read", "list"] }Read the API key secret in Vault. The
vault-clientapplication will retrieve this.$ vault kv get secret/myapp/api-key ====== Secret Path ====== secret/data/myapp/api-key ======= Metadata ======= Key Value --- ----- created_time 2026-04-08T18:55:35.620305Z custom_metadata <nil> deletion_time n/a destroyed false version 1 ========== Data ========== Key Value --- ----- access_key appuser secret_access_key Su4t9mBFykMW29LLHsGH5g==The Terraform configuration also set up a Kubernetes auth method in Vault. Verify the configuration of the Kubernetes auth method.
$ vault read auth/kubernetes/configExample output:
Key Value --- ----- disable_iss_validation false disable_local_ca_jwt false issuer https://kubernetes.default.svc.cluster.local kubernetes_ca_cert -----BEGIN CERTIFICATE----- MIIDBjCCAe6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p a3ViZUNBMB4XDTI2MDQwNzE4NTY0OFoXDTM2MDQwNTE4NTY0OFowFTETMBEGA1UE AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMBA b+x+89nsg9a+p9VU0lx9XyP ... yyxGUCH8dVABkg== -----END CERTIFICATE----- kubernetes_host https://127.0.0.1:51446 pem_keys [] token_reviewer_jwt_set true use_annotations_as_alias_metadata falseThe Kubernetes auth method uses the service account JWT token for authentication and sets the Kubernetes API server address and CA certificate for validating tokens.
The configuration also created a role named
vault-kube-auth-rolethat maps thevault-authservice account in thedefaultnamespace to theapi-key-policypolicy.$ vault read auth/kubernetes/role/vault-kube-auth-roleExample output:
Key Value --- ----- alias_name_source serviceaccount_uid audience https://kubernetes.default.svc.cluster.local bound_service_account_names [vault-auth] bound_service_account_namespace_selector n/a bound_service_account_namespaces [default] token_bound_cidrs [] token_explicit_max_ttl 0s token_max_ttl 0s token_no_default_policy false token_num_uses 0 token_period 0s token_policies [api-key-policy] token_ttl 1h token_type defaultThe
api-key-policygrants access to read the secret atsecret/data/myapp/api-key.
Go application and Vault SDK
After the configuration of the Vault and Kubernetes resources, you will review the Go application code that uses the Vault SDK to authenticate and retrieve secrets.
Application overview
The application is a simple web server built with the Gin web framework.
The workflow is the following:
- Pod starts with
vault-authservice account - Application reads service account JWT token from filesystem
- Application calls Vault's Kubernetes auth method with the JWT token
- Vault validates the token with Kubernetes API
- Vault returns a token to the application
- Application uses the token to retrieve secrets from KV v2 secrets engine
You package the docker container and deploy it to Kubernetes, where it authenticates to Vault using the service account and retrieves the secret.
Review the application code in
main.go.$ more main.goThe following code snippet highlights the key sections of the application that interact with Vault using the SDK.
main.go
// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package main import ( "context" "log" "os" "github.com/gin-gonic/gin" vault "github.com/hashicorp/vault/api" auth "github.com/hashicorp/vault/api/auth/kubernetes" ) func main() { config := vault.DefaultConfig() // Configure TLS tlsConfig := &vault.TLSConfig{} err := config.ConfigureTLS(tlsConfig) if err != nil { log.Printf("unable to configure TLS: %v", err) os.Exit(1) } else { log.Printf("TLS configuration successful.") } // initialize Vault client client, err := vault.NewClient(config) if err != nil { log.Printf("unable to initialize Vault client: %v", err) os.Exit(1) } // 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. k8sAuth, err := auth.NewKubernetesAuth( "vault-kube-auth-role", ) if err != nil { log.Printf("unable to initialize Kubernetes auth method: %v", err) os.Exit(1) } authInfo, err := client.Auth().Login(context.Background(), k8sAuth) if err != nil { log.Printf("unable to log in with Kubernetes auth!: %v", err) os.Exit(1) } if authInfo == nil { log.Printf("no auth info was returned after login") os.Exit(1) } // set up Gin router router := gin.Default() router.SetTrustedProxies([]string{"127.0.0.1", "192.168.1.2", "10.0.0.0/8"}) // using the token returned from Vault get secret from the default // mount path for KV v2 secret secret, err := client.KVv2("secret").Get(context.Background(), "myapp/api-key") if err != nil { log.Printf("unable to read secret: %v", err) os.Exit(1) } // data map can contain more than one key-value pair, // in this case we're just grabbing one of them value, ok := secret.Data["access_key"].(string) if !ok { log.Printf("value type assertion failed: %T %#v", secret.Data["access_key"], secret.Data["access_key"]) os.Exit(1) } pass, ok := secret.Data["secret_access_key"].(string) if !ok { log.Printf("value type assertion failed: %T %#v", secret.Data["secret_access_key"], secret.Data["secret_access_key"]) os.Exit(1) } log.Println("Access granted!") log.Printf("Retrieved secret value: %s, %s", value, pass) // Run Gin at the default port of 8080. The application will be accessible at http://localhost:8080 when port forwarding is set up. router.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{ "access_key": value, "secret_access_key": pass, }) }) router.Run(":8080") }The application code performs several key operations:
- The
DefaultConfig()function initializes the Vault client configuration with default settings, including reading environment variables likeVAULT_ADDRandVAULT_CACERTto configure the connection to the Vault server. (line 18) - The line
tlsConfig := &vault.TLSConfig{}configures the TLS settings for the Vault client, which allows the application to trust the Vault dev server's TLS certificate. It reads the trusted CA certs stored in the image, which includes the/usr/local/share/ca-certificates/my-ca.crtfile that the Dockerfile copies into the image. (line 21). - Initialize a new Vault client using the default configuration. (line 31)
- Configure Kubernetes authentication by creating a
NewKubernetesAuthobject that specifies the role name (vault-kube-auth-role). The application reads the service account token from the default path where Kubernetes mounts it in the pod (/var/run/secrets/kubernetes.io/serviceaccount/token). (lines 49-51) - Authenticate to Vault using the Kubernetes auth method by calling
client.Auth().Login(), which returns authentication information including the Vault token. (line 57) - Retrieve the secret from the KV v2 secrets engine at path
secret/myapp/api-keyusing the authenticated client. You can access it from theDatafield of the returned secret object. (line 73) - In this example, the application retrieves both the
access_keyandsecret_access_keyvalues from the same secret. (line 81 and 87).
- The
Build and deploy the application
Now you will build the Docker image and deploy the application to Kubernetes.
Your Docker image requires a CA cert to trust the Vault dev server's TLS certificate. Check the
certs/directory to confirm the presence of thevault-ca.pemfile generated when you started the Vault dev server.$ ls certs/ vault-ca.pem vault-cert.pem vault-key.pemBuild the Docker image for the Vault client.
$ docker build -t vault-sdk-go-app:latest .The build process copies the certificate at
certs/vault-ca.peminto the image. It it copied to/usr/local/share/ca-certificates/my-ca.crt. Then it updates the CA certificates in the image. This allows the application to trust the Vault dev server's TLS certificate when it makes API calls.Load the image into minikube ensures that when you deploy the application to Kubernetes, it can pull the image from the local minikube Docker registry.
$ minikube image load vault-sdk-go-app:latestInitialize the Terraform configuration to create the
vault-clientdeployment for the application.$ terraform -chdir=terraform/app/ initNow apply the Terraform configuration to deploy the application to Kubernetes.
$ VAULT_CACERT="$PWD/certs/vault-ca.pem" terraform -chdir=terraform/app/ apply -auto-approveExample output:
Plan: 1 to add, 0 to change, 0 to destroy. kubernetes_pod_v1.vault-client: Creating... kubernetes_pod_v1.vault-client: Creation complete after 1s Apply complete! Resources: 1 added, 0 changed, 0 destroyed.This configuration creates a Kubernetes pod named
vault-clientthat runs the Go application using the Vault SDK. The pod uses thevault-authservice account, which allows it to authenticate to Vault using the Kubernetes auth method.Verify the pod is running.
$ kubectl get pods NAME READY STATUS RESTARTS AGE vault-client 1/1 Running 0 10sCheck the application logs to verify it authenticated successfully and retrieved the secret.
$ kubectl logs vault-client [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET / --> main.main.func1 (3 handlers) [GIN-debug] Listening and serving HTTP on :8080 2026/03/24 19:36:26 Access granted! 2026/03/24 19:36:26 Retrieved secret value: appuser, Su4t9mBFykMW29LLHsGH5g==The logs show that the application successfully authenticated to Vault and retrieved the secret values.
Verification
In a new terminal, set up port forwarding to access the application.
$ kubectl port-forward pod/vault-client 8080:8080 Forwarding from 127.0.0.1:8080 -> 8080 Forwarding from [::1]:8080 -> 8080In another terminal, test the application endpoint.
$ curl http://localhost:8080 {"access_key":"appuser","secret_access_key":"Su4t9mBFykMW29LLHsGH5g=="}The application returns the
access_keyandsecret_access_keyvalues from the secret, confirming that it retrieved the secret from Vault using the SDK.
Challenge (Optional)
The HashiCups development team needs to expand the API configuration stored in Vault. In addition to the existing access_key and secret_access_key, they need to store the API base URL that the application should use.
Update the secret in Vault and modify the application to retrieve and display this new field.
Complete the following requirements:
- Add a new field
api_urlwith the valuehttps://api.hashicups.com/v1to the existing secret atsecret/myapp/api-key - Update the Go application to retrieve the
api_urlvalue from the secret - Modify the
/endpoint response to include all three fields
When running the application, the output should include the api_url value along with the existing access_key and secret_access_key values.
$ curl http://localhost:8080
{"access_key":"appuser","secret_access_key":"Su4t9mBFykMW29LLHsGH5g==","api_url":"https://api.hashicups.com/v1"}
First, update the secret in Vault with the new field:
$ vault kv put secret/myapp/api-key \ access_key='appuser' \ secret_access_key='Su4t9mBFykMW29LLHsGH5g==' \ api_url=https://api.hashicups.com/v1Update the application code in
main.go. Add the following code after thesecret_access_keyextraction (around 91):apiURL, ok := secret.Data["api_url"].(string) if !ok { log.Printf("value type assertion failed: %T %#v", secret.Data["api_url"], secret.Data["api_url"]) os.Exit(1) } log.Println("Access granted!") log.Printf("Retrieved secret values - Access Key: %s, Secret Key: %s, API URL: %s", value, pass, apiURL)Update the route handler to return all three values:
router.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{ "access_key": value, "secret_access_key": pass, "api_url": apiURL, }) })You need to rebuild the image locally. Remove the local copy of the current image.
$ docker image rm vault-sdk-go-app:latest Untagged: vault-sdk-go-app:latest Deleted: sha256:ca34530d3f642742401fe331418c23cb8d8d12677354ec72cee209355f9d12a5Remove the
vault-clientpod.$ terraform -chdir=terraform/app/ destroy --auto-approveDestroy the Kubernetes resources.
$ terraform -chdir=terraform/kubernetes/ destroy --auto-approveYou might need to delete the image from minikube to force it to pull the updated image.
$ minikube image rm docker.io/library/vault-sdk-go-app:latestRebuild the application image. You should be in the same directory as the
Dockerfilewhen you run this command.$ docker build -t docker.io/library/vault-sdk-go-app:latest .To use the local version you need to load the image into minikube.
$ minikube image load docker.io/library/vault-sdk-go-app:latestRun the Terraform configuration to redeploy the Kubernetes resources.
$ terraform -chdir=terraform/kubernetes/ apply -auto-approveRun the Terraform configuration to redeploy the
vault-clientapplication.$ terraform -chdir=terraform/app/ apply -auto-approveVerify the changes:
$ curl http://localhost:8080 {"access_key":"appuser","api_url":"https://api.hashicups.com/v1","secret_access_key":"Su4t9mBFykMW29LLHsGH5g=="}
This demonstrates how to work with multi-field secrets in Vault and extract multiple values in your application code.
Clean up
Stop the port forwarding process by pressing
Ctrl+Cin the terminal where it's running.Remove the
vault-clientpod.$ terraform -chdir=terraform/app/ destroy --auto-approveDestroy the Kubernetes resources.
$ VAULT_CACERT="$PWD/certs/vault-ca.pem" terraform -chdir=terraform/kubernetes/ destroy --auto-approveStop the minikube cluster.
$ minikube stop ✋ Stopping node "minikube" ... 🛑 Powering off "minikube" via SSH ... 🛑 1 node stopped.Delete the minikube cluster.
$ minikube delete 🔥 Deleting "minikube" in docker ... 🔥 Removing /Users/you/.minikube/machines/minikube ... 💀 Removed all traces of the "minikube" cluster.Stop the Vault server by pressing
Ctrl+Cin the terminal where it's running, or usepkill.$ pkill vault
Knowledge checks
A quiz to test your knowledge.
What is the primary difference between using the Vault SDK versus Vault Agent?
Vault SDK: Embedded in your application code, provides programmatic control over authentication and secret retrieval. Best for dynamic secret fetching and complex workflows.
Vault Agent: Runs as a separate process, handles authentication and writes secrets to files. Best for simple secret injection at startup.
How can you import an auth method into your Go application?
You can import the auth method package from the Vault API library.
import ( "context" "log" "os" "github.com/gin-gonic/gin" vault "github.com/hashicorp/vault/api" auth "github.com/hashicorp/vault/api/auth/kubernetes" )What does the application do after it receives a Vault token in this tutorial?
After receiving a Vault token, the application uses the authenticated Vault client to read the secret from the KV v2 secrets engine at
secret/myapp/api-key. It then extracts theaccess_keyandsecret_access_keyvalues and serves them through the web endpoint.
Summary
The Vault Go SDK provides a programmatic approach to accessing secrets from Vault.
In this tutorial, you learned how to:
- Deploy a Go application to Kubernetes that uses the Vault SDK
- Configure the Kubernetes auth method for pod authentication
- Authenticate to Vault using a service account JWT token
- Retrieve secrets programmatically using the SDK
- Integrate Vault operations directly into application code
SDK vs. Agent trade-offs
When deciding between the Vault SDK and Vault Agent, consider the following:
Use Vault SDK when you need:
- Full programmatic control over authentication and secret retrieval
- To fetch different secrets based on runtime conditions
- Complex error handling and retry logic specific to your application
- Deep integration with application business logic
- Dynamic secret refresh and rotation within your application
Use Vault Agent when you need:
- Simple secret injection at application startup
- To keep Vault logic separate from application code
- Automatic token renewal and secret caching
- To support applications that can't easily embed a library
- Template-based secret rendering to files