Use new auth modules + Login method in Go client docs (#13189)

This commit is contained in:
VAL 2021-11-17 11:52:38 -08:00 committed by GitHub
parent e83953b563
commit 8ee5aba343
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 179 additions and 327 deletions

View file

@ -256,16 +256,17 @@ wrapping.
package main
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
vault "github.com/hashicorp/vault/api"
auth "github.com/hashicorp/vault/api/auth/approle"
)
// Fetches a key-value secret (kv-v2) after authenticating via AppRole,
// an auth method used by machines that are unable to use platform-based authentication mechanisms like AWS Auth, Kubernetes Auth, etc.
// an auth method used by machines that are unable to use platform-based
// authentication mechanisms like AWS Auth, Kubernetes Auth, etc.
func getSecretWithAppRole() (string, error) {
config := vault.DefaultConfig() // modify for more granular configuration
@ -274,40 +275,38 @@ func getSecretWithAppRole() (string, error) {
return "", fmt.Errorf("unable to initialize Vault client: %w", err)
}
// A combination of a Role ID and Secret ID is required to log in to Vault with an AppRole.
// The Secret ID is a value that needs to be protected, so instead of the app having knowledge of the secret ID directly,
// we have a trusted orchestrator (https://learn.hashicorp.com/tutorials/vault/secure-introduction?in=vault/app-integration#trusted-orchestrator)
// give the app access to a short-lived response-wrapping token (https://www.vaultproject.io/docs/concepts/response-wrapping).
// Read more at: https://learn.hashicorp.com/tutorials/vault/approle-best-practices?in=vault/auth-methods#secretid-delivery-best-practices
wrappingToken, err := ioutil.ReadFile("path/to/wrapping-token") // placed here by a trusted orchestrator
if err != nil {
return "", fmt.Errorf("unable to read file containing wrapping token: %w", err)
}
unwrappedToken, err := client.Logical().Unwrap(strings.TrimSuffix(string(wrappingToken), "\n"))
if err != nil {
// a good opportunity to alert, in case the one-time use wrapping token appears to have already been used
return "", fmt.Errorf("unable to unwrap token: %w", err)
}
secretID := unwrappedToken.Data["secret_id"]
// the role ID given to you by your administrator
// A combination of a Role ID and Secret ID is required to log in to Vault
// with an AppRole.
// First, let's get the role ID given to us by our Vault administrator.
roleID := os.Getenv("APPROLE_ROLE_ID")
if roleID == "" {
return "", fmt.Errorf("no role ID was provided in APPROLE_ROLE_ID env var")
}
params := map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
}
resp, err := client.Logical().Write("auth/approle/login", params)
if err != nil {
return "", fmt.Errorf("unable to log in with approle: %w", err)
}
client.SetToken(resp.Auth.ClientToken)
// The Secret ID is a value that needs to be protected, so instead of the
// app having knowledge of the secret ID directly, we have a trusted orchestrator (https://learn.hashicorp.com/tutorials/vault/secure-introduction?in=vault/app-integration#trusted-orchestrator)
// give the app access to a short-lived response-wrapping token (https://www.vaultproject.io/docs/concepts/response-wrapping).
// Read more at: https://learn.hashicorp.com/tutorials/vault/approle-best-practices?in=vault/auth-methods#secretid-delivery-best-practices
secretID := &auth.SecretID{FromFile: "path/to/wrapping-token"}
appRoleAuth, err := auth.NewAppRoleAuth(
roleID,
secretID,
auth.WithWrappingToken(), // Only required if the secret ID is response-wrapped.
)
if err != nil {
return "", fmt.Errorf("unable to initialize AppRole auth method: %w", err)
}
authInfo, err := client.Auth().Login(context.TODO(), appRoleAuth)
if err != nil {
return "", fmt.Errorf("unable to login to AppRole auth method: %w", err)
}
if authInfo == nil {
return "", fmt.Errorf("no auth info was returned after login")
}
// get secret
secret, err := client.Logical().Read("kv-v2/data/creds")
if err != nil {
return "", fmt.Errorf("unable to read secret: %w", err)
@ -318,6 +317,8 @@ func getSecretWithAppRole() (string, error) {
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 {

View file

@ -746,7 +746,7 @@ details.
## Code Example
The following code snippet uses the AWS auth method to authenticate with Vault.
The following code snippet uses the AWS (IAM) auth method to authenticate with Vault.
<CodeTabs heading="AWS auth example">
@ -756,18 +756,18 @@ The following code snippet uses the AWS auth method to authenticate with Vault.
package main
import (
"context"
"fmt"
"os"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-secure-stdlib/awsutil"
vault "github.com/hashicorp/vault/api"
auth "github.com/hashicorp/vault/api/auth/aws"
)
// Fetches a key-value secret (kv-v2) after authenticating to Vault via AWS IAM,
// one of two auth methods used to authenticate with AWS (the other is EC2 auth).
// A role must first be created in Vault bound to the IAM ARN you wish to authenticate with, like so:
// vault write auth/aws/role/dev-role-iam \
// A role must first be created in Vault bound to the IAM ARN you wish to
// authenticate with, like so:
// vault write auth/aws/role/dev-role-iam \
// auth_type=iam \
// bound_iam_principal_arn="arn:aws:iam::AWS-ACCOUNT-NUMBER:role/AWS-IAM-ROLE-NAME" \
// ttl=24h
@ -780,32 +780,22 @@ func getSecretWithAWSAuthIAM() (string, error) {
return "", fmt.Errorf("unable to initialize Vault client: %w", err)
}
logger := hclog.Default()
// If environment variables are empty, will fall back on other AWS-provided mechanisms to retrieve credentials.
creds, err := awsutil.RetrieveCreds(os.Getenv("AWS_ACCESS_KEY_ID"), os.Getenv("AWS_SECRET_ACCESS_KEY"), os.Getenv("AWS_SESSION_TOKEN"), logger)
awsAuth, err := auth.NewAWSAuth(
auth.WithRole("dev-role-iam"), // if not provided, Vault will fall back on looking for a role with the IAM role name if you're using the iam auth type, or the EC2 instance's AMI id if using the ec2 auth type
)
if err != nil {
return "", fmt.Errorf("unable to retrieve creds from STS: %w", err)
return "", fmt.Errorf("unable to initialize AWS auth method: %w", err)
}
// the optional second parameter can be used to help mitigate replay attacks,
// when the role in Vault is configured with resolve_aws_unique_ids = true: https://www.vaultproject.io/docs/auth/aws#iam-auth-method
params, err := awsutil.GenerateLoginData(creds, "Replace-With-IAM-Server-Id", os.Getenv("AWS_DEFAULT_REGION"), logger)
authInfo, err := client.Auth().Login(context.TODO(), awsAuth)
if err != nil {
return "", err
return "", fmt.Errorf("unable to login to AWS auth method: %w", err)
}
if params == nil {
return "", fmt.Errorf("got nil response from GenerateLoginData")
}
params["role"] = "dev-role-iam" // the name of the role in Vault that was created with this IAM principal ARN bound to it
resp, err := client.Logical().Write("auth/aws/login", params)
if err != nil {
return "", fmt.Errorf("unable to log in with AWS IAM auth: %w", err)
if authInfo == nil {
return "", fmt.Errorf("no auth info was returned after login")
}
client.SetToken(resp.Auth.ClientToken)
// get secret
secret, err := client.Logical().Read("kv-v2/data/creds")
if err != nil {
return "", fmt.Errorf("unable to read secret: %w", err)
@ -816,7 +806,8 @@ func getSecretWithAWSAuthIAM() (string, error) {
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
// 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 {

View file

@ -209,35 +209,13 @@ with Vault.
package main
import (
"encoding/json"
"context"
"fmt"
"io/ioutil"
"net/http"
"net/url"
vault "github.com/hashicorp/vault/api"
auth "github.com/hashicorp/vault/api/auth/azure"
)
type responseJson struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn string `json:"expires_in"`
ExpiresOn string `json:"expires_on"`
NotBefore string `json:"not_before"`
Resource string `json:"resource"`
TokenType string `json:"token_type"`
}
type metadataJson struct {
Compute computeJson `json:"compute"`
}
type computeJson struct {
VirtualMachineName string `json:"name"`
SubscriptionId string `json:"subscriptionId"`
ResourceGroupName string `json:"resourceGroupName"`
}
// Fetches a key-value secret (kv-v2) after authenticating to Vault via Azure authentication.
// This example assumes you have a configured Azure AD Application.
// Learn more about Azure authentication prerequisites: https://www.vaultproject.io/docs/auth/azure
@ -249,147 +227,48 @@ type computeJson struct {
// bound_resource_groups=test-rg \
// ttl=24h
func getSecretWithAzureAuth() (string, error) {
config := vault.DefaultConfig() // modify for more granular configuration
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)
}
// Get AccessToken
jwtResp, err := getJWT()
if err != nil {
return "", fmt.Errorf("unable to get access token: %w", err)
}
// Get metadata for Azure instance
metadataRespJson, err := getMetadata()
if err != nil {
return "", fmt.Errorf("unable to get instance metadata: %w", err)
}
// log in to Vault's auth method with signed JWT token
params := map[string]interface{}{
"role": "dev-role-azure", // the name of the role in Vault w/ bound subscription id and resource group
"jwt": jwtResp,
"vm_name": metadataRespJson.Compute.VirtualMachineName,
"subscription_id": metadataRespJson.Compute.SubscriptionId,
"resource_group_name": metadataRespJson.Compute.ResourceGroupName,
}
// log in to Vault's Azure auth method
resp, err := client.Logical().Write("auth/azure/login", params) // confirm with your Vault administrator that "azure" is the correct mount name
if err != nil {
return "", fmt.Errorf("unable to log in with Azure auth: %w", err)
}
if resp == nil || resp.Auth == nil || resp.Auth.ClientToken == "" {
return "", fmt.Errorf("login response did not return client token")
}
client.SetToken(resp.Auth.ClientToken)
// get secret
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
}
// Retrieve instance metadata from Azure
func getMetadata() (metadataJson, error) {
metadataEndpoint, err := url.Parse("http://169.254.169.254/metadata/instance")
if err != nil {
fmt.Println("Error creating URL: ", err)
return metadataJson{}, err
client, err := vault.NewClient(config)
if err != nil {
return "", fmt.Errorf("unable to initialize Vault client: %w", err)
}
metadataParameters := metadataEndpoint.Query()
metadataParameters.Add("api-version", "2018-02-01")
metadataEndpoint.RawQuery = metadataParameters.Encode()
req, err := http.NewRequest("GET", metadataEndpoint.String(), nil)
if err != nil {
return metadataJson{}, fmt.Errorf("Error creating HTTP Request: %w", err)
}
req.Header.Add("Metadata", "true")
azureAuth, err := auth.NewAzureAuth(
"dev-role-azure",
)
if err != nil {
return "", fmt.Errorf("unable to initialize Azure auth method: %w", err)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error calling token endpoint: ", err)
return metadataJson{}, fmt.Errorf("Error calling token endpoint: %w", err)
}
authInfo, err := client.Auth().Login(context.TODO(), azureAuth)
if err != nil {
return "", fmt.Errorf("unable to login to Azure auth method: %w", err)
}
if authInfo == nil {
return "", fmt.Errorf("no auth info was returned after login")
}
responseBytes, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return metadataJson{}, fmt.Errorf("Error reading response body: %w", err)
}
// get secret
secret, err := client.Logical().Read("kv-v2/data/creds")
if err != nil {
return "", fmt.Errorf("unable to read secret: %w", err)
}
// Unmarshal response body into metadata struct
var r metadataJson
err = json.Unmarshal(responseBytes, &r)
if err != nil {
return metadataJson{}, fmt.Errorf("Error unmarshalling the response: %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"])
}
return r, nil
}
// 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])
}
// Retrieves an access token from Azure MSI
// Learn more here: https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token
func getJWT() (string, error) {
// Create HTTP request for a managed services for Azure resources token to access Azure Resource Manager
msiEndpoint, err := url.Parse("http://169.254.169.254/metadata/identity/oauth2/token")
if err != nil {
return "", fmt.Errorf("Error creating URL: %w", err)
}
msiParameters := msiEndpoint.Query()
msiParameters.Add("api-version", "2018-02-01")
msiParameters.Add("resource", "https://management.azure.com/")
msiEndpoint.RawQuery = msiParameters.Encode()
req, err := http.NewRequest("GET", msiEndpoint.String(), nil)
if err != nil {
return "", fmt.Errorf("Error creating HTTP request: %w", err)
}
req.Header.Add("Metadata", "true")
// Call managed services for Azure resources token endpoint
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("Error calling token endpoint: %w", err)
}
responseBytes, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return "", fmt.Errorf("Error reading response body: %w", err)
}
// Unmarshal response body into struct
var r responseJson
err = json.Unmarshal(responseBytes, &r)
if err != nil {
return "", fmt.Errorf("Error unmarshalling the response: %w", err)
}
return r.AccessToken, nil
return value, nil
}
```
</CodeBlockConfig>

View file

@ -378,25 +378,25 @@ package main
import (
"context"
"encoding/json"
"fmt"
"os"
"time"
credentials "cloud.google.com/go/iam/credentials/apiv1"
vault "github.com/hashicorp/vault/api"
credentialspb "google.golang.org/genproto/googleapis/iam/credentials/v1"
auth "github.com/hashicorp/vault/api/auth/gcp"
)
// Fetches a key-value secret (kv-v2) after authenticating to Vault via GCP IAM,
// one of two auth methods used to authenticate with GCP (the other is GCE auth).
// Fetches a key-value secret (kv-v2) after authenticating to Vault
// via GCP IAM, one of two auth methods used to authenticate with
// GCP (the other is GCE auth).
//
// A role must first be created in Vault bound to the IAM user's service account you wish to authenticate with, like so:
// A role must first be created in Vault bound to the IAM user's service
// account you wish to authenticate with, like so:
// vault write auth/gcp/role/dev-role-iam \
// type="iam" \
// policies="dev-policy" \
// bound_service_accounts="my-service@my-project.iam.gserviceaccount.com"
// Your Vault instance must also be configured with GCP credentials to perform API calls to IAM, like so:
// Your Vault instance must also be configured with GCP credentials to
// perform API calls to IAM, like so:
// vault write auth/gcp/config credentials=@path/to/server/creds.json
// Learn more at https://www.vaultproject.io/docs/auth/gcp
func getSecretWithGCPAuthIAM() (string, error) {
@ -407,31 +407,36 @@ func getSecretWithGCPAuthIAM() (string, error) {
return "", fmt.Errorf("unable to initialize Vault client: %w", err)
}
// For IAM-style auth, the environment variable GOOGLE_APPLICATION_CREDENTIALS
// must be set with the path to a valid credentials JSON file, otherwise
// Vault will fall back to Google's default instance credentials.
// Learn about authenticating to GCS with service account credentials at https://cloud.google.com/docs/authentication/production
if pathToCreds := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"); pathToCreds == "" {
fmt.Printf("WARNING: Environment variable GOOGLE_APPLICATION_CREDENTIALS was not set. IAM client for JWT signing and Vault server IAM client will both fall back to default instance credentials.\n")
}
jwtResp, err := signJWT()
svcAccountEmail := fmt.Sprintf("%s@%s.iam.gserviceaccount.com", os.Getenv("GCP_SERVICE_ACCOUNT_NAME"), os.Getenv("GOOGLE_CLOUD_PROJECT"))
// We pass the auth.WithIAMAuth option to use the IAM-style authentication
// of the GCP auth backend. Otherwise, we default to using GCE-style
// authentication, which gets its credentials from the metadata server.
gcpAuth, err := auth.NewGCPAuth(
"dev-role-iam",
auth.WithIAMAuth(svcAccountEmail),
)
if err != nil {
return "", fmt.Errorf("unable to sign JWT for authenticating to GCP: %w", err)
return "", fmt.Errorf("unable to initialize GCP auth method: %w", err)
}
// send login request to Vault with signed JWT token
params := map[string]interface{}{
"role": "dev-role-iam", // the name of the role in Vault that was created with this IAM bound to it
"jwt": jwtResp.SignedJwt,
}
// Environment variable GOOGLE_APPLICATION_CREDENTIALS pointing to the path to a valid credentials JSON must be set,
// or Vault will fall back to Google's default instance credentials
resp, err := client.Logical().Write("auth/gcp/login", params)
authInfo, err := client.Auth().Login(context.TODO(), gcpAuth)
if err != nil {
return "", fmt.Errorf("unable to log in with GCP IAM auth: %w", err)
return "", fmt.Errorf("unable to login to GCP auth method: %w", err)
}
if authInfo == nil {
return "", fmt.Errorf("login response did not return client token")
}
client.SetToken(resp.Auth.ClientToken)
// get secret
secret, err := client.Logical().Read("kv-v2/data/creds")
if err != nil {
return "", fmt.Errorf("unable to read secret: %w", err)
@ -442,7 +447,8 @@ func getSecretWithGCPAuthIAM() (string, error) {
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
// 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 {
@ -451,42 +457,6 @@ func getSecretWithGCPAuthIAM() (string, error) {
return value, nil
}
// generate signed JWT token from GCP IAM
func signJWT() (*credentialspb.SignJwtResponse, error) {
svcAccountEmail := fmt.Sprintf("%s@%s.iam.gserviceaccount.com", os.Getenv("GCP_SERVICE_ACCOUNT_NAME"), os.Getenv("GOOGLE_CLOUD_PROJECT"))
ctx := context.Background()
iamClient, err := credentials.NewIamCredentialsClient(ctx) // can pass option.WithCredentialsFile("path/to/creds.json") as second param if GOOGLE_APPLICATION_CREDENTIALS env var not set
if err != nil {
return nil, fmt.Errorf("unable to initialize IAM credentials client: %w", err)
}
defer iamClient.Close()
resourceName := fmt.Sprintf("projects/-/serviceAccounts/%s", svcAccountEmail)
jwtPayload := map[string]interface{}{
"aud": "vault/dev-role-iam", // the name of the role in Vault that was created with this IAM service account bound to it
"sub": svcAccountEmail,
"exp": time.Now().Add(time.Minute * 10).Unix(),
}
payloadBytes, err := json.Marshal(jwtPayload)
if err != nil {
return nil, fmt.Errorf("unable to marshal jwt payload to json: %w", err)
}
signJWTReq := &credentialspb.SignJwtRequest{
Name: resourceName,
Payload: string(payloadBytes),
}
jwtResp, err := iamClient.SignJwt(ctx, signJWTReq)
if err != nil {
return nil, fmt.Errorf("unable to sign JWT: %w", err)
}
return jwtResp, nil
}
```
</CodeBlockConfig>

View file

@ -194,6 +194,7 @@ import (
"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.
@ -202,7 +203,8 @@ import (
//
// 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.
// 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)
@ -210,31 +212,28 @@ func getSecretWithKubernetesAuth() (string, error) {
return "", fmt.Errorf("unable to initialize Vault client: %w", err)
}
// Read the service-account token from the path where the token's Kubernetes Secret is mounted.
// By default, Kubernetes will mount this to /var/run/secrets/kubernetes.io/serviceaccount/token
// but an administrator may have configured it to be mounted elsewhere.
jwt, err := os.ReadFile("path/to/service-account-token")
// 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 read file containing service account token: %w", err)
return "", fmt.Errorf("unable to initialize Kubernetes auth method: %w", err)
}
params := map[string]interface{}{
"jwt": string(jwt),
"role": "dev-role-k8s", // the name of the role in Vault that was created with this app's Kubernetes service account bound to it
}
// log in to Vault's Kubernetes auth method
resp, err := client.Logical().Write("auth/kubernetes/login", params)
authInfo, err := client.Auth().Login(context.TODO(), k8sAuth)
if err != nil {
return "", fmt.Errorf("unable to log in with Kubernetes auth: %w", err)
}
if resp == nil || resp.Auth == nil || resp.Auth.ClientToken == "" {
return "", fmt.Errorf("login response did not return client token")
if authInfo == nil {
return "", fmt.Errorf("no auth info was returned after login")
}
// now you will use the resulting Vault token for making all future calls to Vault
client.SetToken(resp.Auth.ClientToken)
// get secret from Vault
secret, err := client.Logical().Read("kv-v2/data/creds")
if err != nil {
@ -246,7 +245,8 @@ func getSecretWithKubernetesAuth() (string, error) {
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
// 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 {

View file

@ -123,22 +123,27 @@ The following code snippet demonstrates how to renew auth tokens.
package main
import (
"context"
"fmt"
"log"
vault "github.com/hashicorp/vault/api"
auth "github.com/hashicorp/vault/api/auth/userpass"
)
// Once you've set the token for your Vault client, you will need to periodically renew its lease.
// Once you've set the token for your Vault client, you will need to
// periodically renew its lease.
//
// A function like this should be run as a goroutine to avoid blocking.
//
// Production applications may also wish to be more tolerant of failures and retry rather than exiting.
// Production applications may also wish to be more tolerant of failures and
// retry rather than exiting.
//
// Additionally, enterprise Vault users should be aware that due to eventual consistency, the API may return unexpected errors when
// running Vault with performance standbys or performance replication, despite the client having a freshly renewed token.
// See https://www.vaultproject.io/docs/enterprise/consistency#vault-1-7-mitigations for several ways to mitigate this
// which are outside the scope of this code sample.
// Additionally, enterprise Vault users should be aware that due to eventual
// consistency, the API may return unexpected errors when running Vault with
// performance standbys or performance replication, despite the client having
// a freshly renewed token. See https://www.vaultproject.io/docs/enterprise/consistency#vault-1-7-mitigations
// for several ways to mitigate this which are outside the scope of this code sample.
func renewToken(client *vault.Client) {
for {
vaultLoginResp, err := login(client)
@ -152,7 +157,8 @@ func renewToken(client *vault.Client) {
}
}
// Starts token lifecycle management. Returns only fatal errors as errors, otherwise returns nil so we can attempt login again.
// Starts token lifecycle management. Returns only fatal errors as errors,
// otherwise returns nil so we can attempt login again.
func manageTokenLifecycle(client *vault.Client, token *vault.Secret) error {
renew := token.Auth.Renewable // You may notice a different top-level field called Renewable. That one is used for dynamic secrets renewal, not token renewal.
if !renew {
@ -173,40 +179,45 @@ func manageTokenLifecycle(client *vault.Client, token *vault.Secret) error {
for {
select {
// `DoneCh` will return if renewal fails, or if the remaining lease duration is
// under a built-in threshold and either renewing is not extending it or
// renewing is disabled. In any case, the caller needs to attempt to log in again.
case err := <-watcher.DoneCh():
if err != nil {
log.Printf("Failed to renew token: %v. Re-attempting login.", err)
return nil
}
log.Printf("Token can no longer be renewed. Re-attempting login.")
return nil
// `DoneCh` will return if renewal fails, or if the remaining lease
// duration is under a built-in threshold and either renewing is not
// extending it or renewing is disabled. In any case, the caller
// needs to attempt to log in again.
case err := <-watcher.DoneCh():
if err != nil {
log.Printf("Failed to renew token: %v. Re-attempting login.", err)
return nil
}
log.Printf("Token can no longer be renewed. Re-attempting login.")
return nil
// Successfully completed renewal
case renewal := <-watcher.RenewCh():
log.Printf("Successfully renewed: %#v", renewal)
}
}
// Successfully completed renewal
case renewal := <-watcher.RenewCh():
log.Printf("Successfully renewed: %#v", renewal)
}
}
}
func login(client *vault.Client) (*vault.Secret, error) {
// WARNING: A plaintext password like this is obviously insecure.
// See the files starting in auth-* in the hashicorp/vault-examples repo for full examples of how to securely log in to Vault using various auth methods.
// This is just demonstrating the basic idea that a *vault.Secret is returned by a call to an auth method's /login API endpoint.
resp, err := client.Logical().Write("auth/userpass/login/my-user", map[string]interface{}{"password": "my-password"})
if err != nil {
return nil, err
}
if resp == nil || resp.Auth == nil || resp.Auth.ClientToken == "" {
return nil, fmt.Errorf("login response did not return client token")
}
// WARNING: A plaintext password like this is obviously insecure.
// See the files starting in auth-* for full examples of how to securely
// log in to Vault using various auth methods. This function is just
// demonstrating the basic idea that a *vault.Secret is returned by
// the login call.
userpassAuth, err := auth.NewUserpassAuth("my-user", &auth.Password{FromString: "my-password"})
if err != nil {
return nil, fmt.Errorf("unable to initialize userpass auth method: %w", err)
}
// have the client use that token for all Vault calls from now on
client.SetToken(resp.Auth.ClientToken)
authInfo, err := client.Auth().Login(context.TODO(), userpassAuth)
if err != nil {
return nil, fmt.Errorf("unable to login to userpass auth method: %w", err)
}
if authInfo == nil {
return nil, fmt.Errorf("no auth info was returned after login")
}
return resp, nil
return authInfo, nil
}
```