Skip to content

Commit

Permalink
VAULT-14644 Add support for Azure WIF auth to auto-auth (for Agent an…
Browse files Browse the repository at this point in the history
…d Proxy) (#22264)

* VAULT-14644 first draft of changes for WIF support

* VAULT-14644 Potentially finalize Agent work for WIF support

* VAULT-14644 finishing touches

* VAULT-14644 finishing touches

* VAULT-14644 remove extra log

* VAULT-14644 better docs

* VAULT-14644 changelog

* VAULT-14644 review feedback
  • Loading branch information
VioletHynes committed Aug 10, 2023
1 parent 8f646d5 commit 7e5f2ce
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 38 deletions.
3 changes: 3 additions & 0 deletions changelog/22264.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
auto-auth/azure: Added Azure Workload Identity Federation support to auto-auth (for Vault Agent and Vault Proxy).
```
116 changes: 93 additions & 23 deletions command/agentproxyshared/auth/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import (
"context"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"

policy "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
az "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
cleanhttp "github.com/hashicorp/go-cleanhttp"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api"
Expand All @@ -31,10 +33,12 @@ type azureMethod struct {
logger hclog.Logger
mountPath string

role string
resource string
objectID string
clientID string
authenticateFromEnvironment bool
role string
scope string
resource string
objectID string
clientID string
}

func NewAzureAuthMethod(conf *auth.AuthConfig) (auth.AuthMethod, error) {
Expand Down Expand Up @@ -84,6 +88,25 @@ func NewAzureAuthMethod(conf *auth.AuthConfig) (auth.AuthMethod, error) {
}
}

scopeRaw, ok := conf.Config["scope"]
if ok {
a.scope, ok = scopeRaw.(string)
if !ok {
return nil, errors.New("could not convert 'scope' config value to string")
}
}
if a.scope == "" {
a.scope = fmt.Sprintf("%s/.default", a.resource)
}

authenticateFromEnvironmentRaw, ok := conf.Config["authenticate_from_environment"]
if ok {
a.authenticateFromEnvironment, ok = authenticateFromEnvironmentRaw.(bool)
if !ok {
return nil, errors.New("could not convert 'authenticate_from_environment' config value to bool")
}
}

switch {
case a.role == "":
return nil, errors.New("'role' value is empty")
Expand All @@ -106,10 +129,11 @@ func (a *azureMethod) Authenticate(ctx context.Context, client *api.Client) (ret
ResourceGroupName string
SubscriptionID string
VMScaleSetName string
ResourceID string
}
}

body, err := getMetadataInfo(ctx, instanceEndpoint, "", "", "")
body, err := getInstanceMetadataInfo(ctx)
if err != nil {
retErr = err
return
Expand All @@ -121,21 +145,19 @@ func (a *azureMethod) Authenticate(ctx context.Context, client *api.Client) (ret
return
}

// Fetch JWT
var identity struct {
AccessToken string `json:"access_token"`
}

body, err = getMetadataInfo(ctx, identityEndpoint, a.resource, a.objectID, a.clientID)
if err != nil {
retErr = err
return
}

err = jsonutil.DecodeJSON(body, &identity)
if err != nil {
retErr = fmt.Errorf("error parsing identity metadata response: %w", err)
return
token := ""
if a.authenticateFromEnvironment {
token, err = getAzureTokenFromEnvironment(ctx, a.scope)
if err != nil {
retErr = err
return
}
} else {
token, err = getTokenFromIdentityEndpoint(ctx, a.resource, a.objectID, a.clientID)
if err != nil {
retErr = err
return
}
}

// Attempt login
Expand All @@ -145,7 +167,7 @@ func (a *azureMethod) Authenticate(ctx context.Context, client *api.Client) (ret
"vmss_name": instance.Compute.VMScaleSetName,
"resource_group_name": instance.Compute.ResourceGroupName,
"subscription_id": instance.Compute.SubscriptionID,
"jwt": identity.AccessToken,
"jwt": token,
}

return fmt.Sprintf("%s/login", a.mountPath), nil, data, nil
Expand All @@ -161,6 +183,54 @@ func (a *azureMethod) CredSuccess() {
func (a *azureMethod) Shutdown() {
}

// getAzureTokenFromEnvironment Is Azure's preferred way for authentication, and takes values
// from environment variables to form a credential.
// It uses a DefaultAzureCredential:
// https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#readme-defaultazurecredential
// Environment variables are taken into account in the following order:
// https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#readme-environment-variables
func getAzureTokenFromEnvironment(ctx context.Context, scope string) (string, error) {
cred, err := az.NewDefaultAzureCredential(nil)
if err != nil {
return "", err
}

tokenOpts := policy.TokenRequestOptions{Scopes: []string{scope}}
tk, err := cred.GetToken(ctx, tokenOpts)
if err != nil {
return "", err
}
return tk.Token, nil
}

// getInstanceMetadataInfo calls the Azure Instance Metadata endpoint to get
// information about the Azure environment it's running in.
func getInstanceMetadataInfo(ctx context.Context) ([]byte, error) {
return getMetadataInfo(ctx, instanceEndpoint, "", "", "")
}

// getTokenFromIdentityEndpoint is kept for backwards compatibility purposes. Using the
// newer APIs and the Azure SDK should be preferred over this mechanism.
func getTokenFromIdentityEndpoint(ctx context.Context, resource, objectID, clientID string) (string, error) {
var identity struct {
AccessToken string `json:"access_token"`
}

body, err := getMetadataInfo(ctx, identityEndpoint, resource, objectID, clientID)
if err != nil {
return "", err
}

err = jsonutil.DecodeJSON(body, &identity)
if err != nil {
return "", fmt.Errorf("error parsing identity metadata response: %w", err)
}

return identity.AccessToken, nil
}

// getMetadataInfo calls the Azure metadata endpoint with the given parameters.
// An empty resource, objectID and clientID will return metadata information.
func getMetadataInfo(ctx context.Context, endpoint, resource, objectID, clientID string) ([]byte, error) {
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
Expand Down Expand Up @@ -194,7 +264,7 @@ func getMetadataInfo(ctx context.Context, endpoint, resource, objectID, clientID
}

defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading metadata from %s: %w", endpoint, err)
}
Expand Down
35 changes: 23 additions & 12 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ require (
cloud.google.com/go/monitoring v1.13.0
cloud.google.com/go/spanner v1.45.0
cloud.google.com/go/storage v1.28.1
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
github.com/Azure/azure-storage-blob-go v0.15.0
github.com/Azure/go-autorest/autorest v0.11.29
github.com/Azure/go-autorest/autorest/adal v0.9.22
Expand Down Expand Up @@ -122,7 +124,7 @@ require (
github.com/hashicorp/raft-boltdb/v2 v2.0.0-20210421194847-a7e34179d62c
github.com/hashicorp/raft-snapshot v1.0.4
github.com/hashicorp/vault-plugin-auth-alicloud v0.15.0
github.com/hashicorp/vault-plugin-auth-azure v0.15.1
github.com/hashicorp/vault-plugin-auth-azure v0.15.2-0.20230808174847-9dfb4f4a5ba7
github.com/hashicorp/vault-plugin-auth-centrify v0.15.1
github.com/hashicorp/vault-plugin-auth-cf v0.15.0
github.com/hashicorp/vault-plugin-auth-gcp v0.16.0
Expand Down Expand Up @@ -200,19 +202,19 @@ require (
go.etcd.io/etcd/client/v3 v3.5.7
go.mongodb.org/atlas v0.28.0
go.mongodb.org/mongo-driver v1.11.6
go.opentelemetry.io/otel v1.14.0
go.opentelemetry.io/otel v1.16.0
go.opentelemetry.io/otel/sdk v1.14.0
go.opentelemetry.io/otel/trace v1.14.0
go.opentelemetry.io/otel/trace v1.16.0
go.uber.org/atomic v1.11.0
go.uber.org/goleak v1.2.1
golang.org/x/crypto v0.9.0
golang.org/x/crypto v0.10.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/net v0.10.0
golang.org/x/oauth2 v0.8.0
golang.org/x/net v0.11.0
golang.org/x/oauth2 v0.9.0
golang.org/x/sync v0.2.0
golang.org/x/sys v0.8.0
golang.org/x/term v0.8.0
golang.org/x/text v0.9.0
golang.org/x/sys v0.9.0
golang.org/x/term v0.9.0
golang.org/x/text v0.10.0
golang.org/x/tools v0.7.0
google.golang.org/api v0.124.0
google.golang.org/grpc v1.55.0
Expand All @@ -238,8 +240,6 @@ require (
github.com/99designs/keyring v1.2.2 // indirect
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
github.com/Azure/azure-sdk-for-go v67.2.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.1.0 // indirect
Expand Down Expand Up @@ -298,6 +298,7 @@ require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible // indirect
github.com/circonus-labs/circonusllhist v0.1.3 // indirect
github.com/cjlapao/common-go v0.0.39 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/cloudfoundry-community/go-cfclient v0.0.0-20210823134051-721f0e559306 // indirect
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect
Expand Down Expand Up @@ -338,7 +339,7 @@ require (
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.4.1 // indirect
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/analysis v0.20.0 // indirect
Expand Down Expand Up @@ -418,6 +419,14 @@ require (
github.com/mattn/go-ieproxy v0.0.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mediocregopher/radix/v4 v4.1.2 // indirect
github.com/microsoft/kiota-abstractions-go v1.1.0 // indirect
github.com/microsoft/kiota-authentication-azure-go v1.0.0 // indirect
github.com/microsoft/kiota-http-go v1.0.0 // indirect
github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect
github.com/microsoft/kiota-serialization-json-go v1.0.4 // indirect
github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect
github.com/microsoftgraph/msgraph-sdk-go v1.12.0 // indirect
github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0 // indirect
github.com/miekg/dns v1.1.43 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/hashstructure v1.1.0 // indirect
Expand Down Expand Up @@ -477,12 +486,14 @@ require (
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
github.com/zclconf/go-cty v1.12.1 // indirect
go.etcd.io/etcd/api/v3 v3.5.7 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel/metric v1.16.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.19.1 // indirect
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
Expand Down
Loading

0 comments on commit 7e5f2ce

Please sign in to comment.