mirror of
https://github.com/hashicorp/vault.git
synced 2026-06-11 09:51:16 -04:00
Use new auth modules + Login method in Go client docs (#13189)
This commit is contained in:
parent
e83953b563
commit
8ee5aba343
6 changed files with 179 additions and 327 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue