Skip to content

Commit

Permalink
Use new auth modules + Login method in Go client docs (#13189)
Browse files Browse the repository at this point in the history
  • Loading branch information
digivava authored Nov 17, 2021
1 parent e83953b commit 8ee5aba
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 326 deletions.
55 changes: 28 additions & 27 deletions website/content/docs/auth/approle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
Expand All @@ -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 {
Expand Down
43 changes: 17 additions & 26 deletions website/content/docs/auth/aws.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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">

Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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 {
Expand Down
193 changes: 36 additions & 157 deletions website/content/docs/auth/azure.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
```
</CodeBlockConfig>
Expand Down
Loading

0 comments on commit 8ee5aba

Please sign in to comment.