Skip to content

Commit

Permalink
enable plugin wif on gcp auth
Browse files Browse the repository at this point in the history
  • Loading branch information
vinay-gopalan committed Apr 25, 2024
1 parent 13c73ea commit 98bbbff
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 6 deletions.
41 changes: 40 additions & 1 deletion plugin/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ 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/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"
Expand All @@ -20,6 +21,8 @@ import (
"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 +241,11 @@ 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 != "" {
creds, err = b.GetExternalAccountConfig(config).GetCredentials()
if err != nil {
return nil, fmt.Errorf(fmt.Sprintf("failed to fetch external account credentials: %s", err))
}
} else {
creds, err = google.FindDefaultCredentials(ctx, iam.CloudPlatformScope)
if err != nil {
Expand All @@ -253,6 +261,37 @@ func (b *GcpAuthBackend) credentials(ctx context.Context, s logical.Storage) (*g
return creds.(*google.Credentials), nil
}

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

return cfg
}

func (b *GcpAuthBackend) FetchWorkloadIdentityToken(ctx context.Context, cfg *gcputil.ExternalAccountConfig) (string, error) {
b.Logger().Info("fetching new plugin identity token")
resp, err := b.System().GenerateIdentityToken(ctx, &pluginutil.IdentityTokenRequest{
Audience: cfg.Audience,
TTL: cfg.TTL,
})
if err != nil {
return "", fmt.Errorf("failed to generate plugin identity token: %w", err)
}
b.Logger().Info("fetched new plugin identity token")

if resp.TTL < cfg.TTL {
b.Logger().Debug("generated plugin identity token has shorter TTL than requested",
"requested", cfg.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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
2 changes: 1 addition & 1 deletion plugin/path_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"strings"
"time"

jose "github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt"
"github.com/hashicorp/go-gcp-common/gcputil"
"github.com/hashicorp/go-secure-stdlib/strutil"
Expand Down

0 comments on commit 98bbbff

Please sign in to comment.