Skip to content

Commit

Permalink
WIF support for GCP auth engine (#204)
Browse files Browse the repository at this point in the history
  • Loading branch information
vinay-gopalan committed May 9, 2024
1 parent 13c73ea commit bd3bf14
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,8 @@
## Next

IMPROVEMENTS:
* Added support for Workload Identity Federation [GH-204](https://github.com/hashicorp/vault-plugin-auth-gcp/pull/204)

## v0.16.3

IMPROVEMENTS:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -9,7 +9,7 @@ require (
github.com/go-jose/go-jose/v4 v4.0.1
github.com/golang/mock v1.6.0
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-gcp-common v0.8.0
github.com/hashicorp/go-gcp-common v0.9.0
github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Expand Up @@ -118,8 +118,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-gcp-common v0.8.0 h1:/2vGAbCU1v+BZ3YHXTCzTvxqma9WOJHYtADTfhZixLo=
github.com/hashicorp/go-gcp-common v0.8.0/go.mod h1:Q7zYRy9ue9SuaEN2s9YLIQs4SoKHdoRmKRcImY3SLgs=
github.com/hashicorp/go-gcp-common v0.9.0 h1:dabqPrA+vlNWcyQV/3yOI6WCmQGFJgwyztDEsqDp+Q0=
github.com/hashicorp/go-gcp-common v0.9.0/go.mod h1:aZnN6BVMqryPo4vIy97ZAYSoREnJWilLMmaOmi5P7vY=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
Expand Down
55 changes: 54 additions & 1 deletion plugin/backend.go
Expand Up @@ -10,16 +10,21 @@ import (
"time"

"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/vault-plugin-auth-gcp/plugin/cache"
"github.com/hashicorp/go-gcp-common/gcputil"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/helper/useragent"
"github.com/hashicorp/vault/sdk/logical"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/google/externalaccount"
"google.golang.org/api/cloudresourcemanager/v1"
"google.golang.org/api/compute/v1"
"google.golang.org/api/iam/v1"
"google.golang.org/api/option"

"github.com/hashicorp/vault-plugin-auth-gcp/plugin/cache"
)

const (
Expand Down Expand Up @@ -238,6 +243,15 @@ func (b *GcpAuthBackend) credentials(ctx context.Context, s logical.Storage) (*g
if err != nil {
return nil, fmt.Errorf("failed to parse credentials: %w", err)
}
} else if config.IdentityTokenAudience != "" {
ts := &PluginIdentityTokenSupplier{
sys: b.System(),
logger: b.Logger(),
audience: config.IdentityTokenAudience,
ttl: config.IdentityTokenTTL,
}

creds, err = b.GetExternalAccountConfig(config, ts).GetExternalAccountCredentials(ctx)
} else {
creds, err = google.FindDefaultCredentials(ctx, iam.CloudPlatformScope)
if err != nil {
Expand All @@ -253,6 +267,45 @@ func (b *GcpAuthBackend) credentials(ctx context.Context, s logical.Storage) (*g
return creds.(*google.Credentials), nil
}

func (b *GcpAuthBackend) GetExternalAccountConfig(c *gcpConfig, ts *PluginIdentityTokenSupplier) *gcputil.ExternalAccountConfig {
b.Logger().Debug("adding web identity token fetcher")
cfg := &gcputil.ExternalAccountConfig{
ServiceAccountEmail: c.ServiceAccountEmail,
Audience: c.IdentityTokenAudience,
TTL: c.IdentityTokenTTL,
TokenSupplier: ts,
}

return cfg
}

type PluginIdentityTokenSupplier struct {
sys logical.SystemView
logger hclog.Logger
audience string
ttl time.Duration
}

var _ externalaccount.SubjectTokenSupplier = (*PluginIdentityTokenSupplier)(nil)

func (p *PluginIdentityTokenSupplier) SubjectToken(ctx context.Context, opts externalaccount.SupplierOptions) (string, error) {
p.logger.Info("fetching new plugin identity token")
resp, err := p.sys.GenerateIdentityToken(ctx, &pluginutil.IdentityTokenRequest{
Audience: p.audience,
TTL: p.ttl,
})
if err != nil {
return "", fmt.Errorf("failed to generate plugin identity token: %w", err)
}

if resp.TTL < p.ttl {
p.logger.Debug("generated plugin identity token has shorter TTL than requested",
"requested", p.ttl.Seconds(), "actual", resp.TTL)
}

return resp.Token.Token(), nil
}

// ClearCaches deletes all cached clients and credentials.
func (b *GcpAuthBackend) ClearCaches() {
b.cache.Clear()
Expand Down
12 changes: 9 additions & 3 deletions plugin/cli.go
Expand Up @@ -104,9 +104,15 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro
mount = "gcp"
}

loginToken, err := getSignedJwt(role, m)
if err != nil {
return nil, err
var loginToken string
var err error
if v, ok := m["jwt"]; ok {
loginToken = v
} else {
loginToken, err = getSignedJwt(role, m)
if err != nil {
return nil, err
}
}

path := fmt.Sprintf("auth/%s/login", mount)
Expand Down
23 changes: 23 additions & 0 deletions plugin/gcp_config.go
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/hashicorp/go-gcp-common/gcputil"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/authmetadata"
"github.com/hashicorp/vault/sdk/helper/pluginidentityutil"
"google.golang.org/api/compute/v1"
"google.golang.org/api/iam/v1"
)
Expand All @@ -32,6 +33,9 @@ type gcpConfig struct {
CRMCustomEndpoint string `json:"crm_custom_endpoint"`
// ComputeCustomEndpoint overrides the service endpoint for compute.googleapis.com
ComputeCustomEndpoint string `json:"compute_custom_endpoint"`

pluginidentityutil.PluginIdentityTokenParams
ServiceAccountEmail string `json:"service_account_email"`
}

// standardizedCreds wraps gcputil.GcpCredentials with a type to allow
Expand Down Expand Up @@ -120,6 +124,25 @@ func (c *gcpConfig) Update(d *framework.FieldData) error {
}
}

// set plugin identity token fields
if err := c.ParsePluginIdentityTokenFields(d); err != nil {
return err
}

// set Service Account email
saEmail, ok := d.GetOk("service_account_email")
if ok {
c.ServiceAccountEmail = saEmail.(string)
}

if c.IdentityTokenAudience != "" && c.Credentials != nil {
return fmt.Errorf("only one of 'credentials' or 'identity_token_audience' can be set")
}

if c.IdentityTokenAudience != "" && c.ServiceAccountEmail == "" {
return fmt.Errorf("missing required 'service_account_email' when 'identity_token_audience' is set")
}

return nil
}

Expand Down
32 changes: 31 additions & 1 deletion plugin/path_config.go
Expand Up @@ -5,11 +5,14 @@ package gcpauth

import (
"context"
"errors"
"fmt"
"net/http"

"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/authmetadata"
"github.com/hashicorp/vault/sdk/helper/pluginidentityutil"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
)

Expand Down Expand Up @@ -49,7 +52,7 @@ var (
)

func pathConfig(b *GcpAuthBackend) *framework.Path {
return &framework.Path{
p := &framework.Path{
Pattern: "config",

DisplayAttrs: &framework.DisplayAttributes{
Expand Down Expand Up @@ -89,6 +92,10 @@ If not specified, will use application default credentials`,
Deprecated. This field does nothing and be removed in a future release`,
Deprecated: true,
},
"service_account_email": {
Type: framework.TypeString,
Description: `Email ID for the Service Account to impersonate for Workload Identity Federation.`,
},
},

Operations: map[logical.Operation]framework.OperationHandler{
Expand Down Expand Up @@ -118,6 +125,10 @@ iam AUTH:
* iam.serviceAccountKeys.get
`,
}

pluginidentityutil.AddPluginIdentityTokenFields(p.Fields)

return p
}

func (b *GcpAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
Expand All @@ -134,6 +145,19 @@ func (b *GcpAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Reque
return nil, logical.CodedError(http.StatusBadRequest, err.Error())
}

// generate token to check if WIF is enabled on this edition of Vault
if c.IdentityTokenAudience != "" {
_, err := b.System().GenerateIdentityToken(ctx, &pluginutil.IdentityTokenRequest{
Audience: c.IdentityTokenAudience,
})
if err != nil {
if errors.Is(err, pluginidentityutil.ErrPluginWorkloadIdentityUnsupported) {
return logical.ErrorResponse(err.Error()), nil
}
return nil, err
}
}

// Create/update the storage entry
entry, err := logical.StorageEntryJSON("config", c)
if err != nil {
Expand Down Expand Up @@ -205,6 +229,12 @@ func (b *GcpAuthBackend) pathConfigRead(ctx context.Context, req *logical.Reques
resp["custom_endpoint"] = endpoints
}

if v := config.ServiceAccountEmail; v != "" {
resp["service_account_email"] = v
}

config.PopulatePluginIdentityTokenData(resp)

return &logical.Response{
Data: resp,
}, nil
Expand Down

0 comments on commit bd3bf14

Please sign in to comment.