From adf92d63e2bffb3c0eebf41fb55429596aaf463f Mon Sep 17 00:00:00 2001 From: Valerie Conklin Date: Wed, 17 Nov 2021 11:06:34 -0800 Subject: [PATCH] Use new auth modules + Login method in Go client docs --- website/content/docs/auth/approle.mdx | 55 +++---- website/content/docs/auth/aws.mdx | 43 ++--- website/content/docs/auth/azure.mdx | 193 +++++------------------ website/content/docs/auth/gcp.mdx | 88 ++++------- website/content/docs/auth/kubernetes.mdx | 38 ++--- website/content/docs/concepts/auth.mdx | 87 +++++----- 6 files changed, 178 insertions(+), 326 deletions(-) diff --git a/website/content/docs/auth/approle.mdx b/website/content/docs/auth/approle.mdx index dfeb37224aa..89861493e3d 100644 --- a/website/content/docs/auth/approle.mdx +++ b/website/content/docs/auth/approle.mdx @@ -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) + // 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") + } + + // 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"} - wrappingToken, err := ioutil.ReadFile("path/to/wrapping-token") // placed here by a trusted orchestrator + 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 read file containing wrapping token: %w", err) + return "", fmt.Errorf("unable to initialize AppRole auth method: %w", err) } - unwrappedToken, err := client.Logical().Unwrap(strings.TrimSuffix(string(wrappingToken), "\n")) + authInfo, err := client.Auth().Login(context.TODO(), appRoleAuth) 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) + return "", fmt.Errorf("unable to login to AppRole auth method: %w", err) } - secretID := unwrappedToken.Data["secret_id"] - - // the role ID given to you by your 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) + 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) @@ -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 { diff --git a/website/content/docs/auth/aws.mdx b/website/content/docs/auth/aws.mdx index 980507b7575..ce8e1601322 100644 --- a/website/content/docs/auth/aws.mdx +++ b/website/content/docs/auth/aws.mdx @@ -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. @@ -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") + if authInfo == nil { + return "", fmt.Errorf("no auth info was returned after login") } - 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) - } - - 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 { diff --git a/website/content/docs/auth/azure.mdx b/website/content/docs/auth/azure.mdx index 0a1e82f4f96..00309764387 100644 --- a/website/content/docs/auth/azure.mdx +++ b/website/content/docs/auth/azure.mdx @@ -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 - - 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") -} + config := vault.DefaultConfig() // modify for more granular configuration -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) -} + client, err := vault.NewClient(config) + if err != nil { + return "", fmt.Errorf("unable to initialize Vault client: %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"]) -} + azureAuth, err := auth.NewAzureAuth( + "dev-role-azure", + ) + if err != nil { + return "", fmt.Errorf("unable to initialize Azure auth method: %w", err) + } -// 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]) -} + 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") + } - return value, nil -} + // get secret + secret, err := client.Logical().Read("kv-v2/data/creds") + if err != nil { + return "", fmt.Errorf("unable to read secret: %w", err) + } -// 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 + 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"]) } - 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") - - 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) - } - - responseBytes, err := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() - if err != nil { - return metadataJson{}, fmt.Errorf("Error reading response body: %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) - } - - 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 } ``` diff --git a/website/content/docs/auth/gcp.mdx b/website/content/docs/auth/gcp.mdx index fc63bf08eb0..402b25dec8d 100644 --- a/website/content/docs/auth/gcp.mdx +++ b/website/content/docs/auth/gcp.mdx @@ -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() - if err != nil { - return "", fmt.Errorf("unable to sign JWT for authenticating to GCP: %w", err) - } + svcAccountEmail := fmt.Sprintf("%s@%s.iam.gserviceaccount.com", os.Getenv("GCP_SERVICE_ACCOUNT_NAME"), os.Getenv("GOOGLE_CLOUD_PROJECT")) - // 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, + // 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 initialize GCP auth method: %w", err) } - // 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 -} ``` diff --git a/website/content/docs/auth/kubernetes.mdx b/website/content/docs/auth/kubernetes.mdx index 05b302591ae..3f273f81ab0 100644 --- a/website/content/docs/auth/kubernetes.mdx +++ b/website/content/docs/auth/kubernetes.mdx @@ -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 { diff --git a/website/content/docs/concepts/auth.mdx b/website/content/docs/concepts/auth.mdx index f1ff02b3105..42fe1c242e6 100644 --- a/website/content/docs/concepts/auth.mdx +++ b/website/content/docs/concepts/auth.mdx @@ -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 - - // Successfully completed renewal - case renewal := <-watcher.RenewCh(): - log.Printf("Successfully renewed: %#v", renewal) - } - } + // `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) + } + } } 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") - } - - // have the client use that token for all Vault calls from now on - client.SetToken(resp.Auth.ClientToken) - - return resp, nil + // 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) + } + + 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 authInfo, nil } ```