Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport of VAULT-14644 Add support for Azure WIF auth to auto-auth (for Agent and Proxy) into release/1.14.x #22274

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -121,7 +123,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 @@ -199,19 +201,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 @@ -237,8 +239,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 @@ -296,6 +296,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 @@ -333,7 +334,7 @@ require (
github.com/gammazero/workerpool v1.1.3 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4 // 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 @@ -411,6 +412,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 @@ -466,12 +475,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
Loading