Skip to content

Commit

Permalink
GCP auth metadata config (#92)
Browse files Browse the repository at this point in the history
* add ability to configure auth metadata

* return early when entry nil

* fix typo in log msg

* compare metadata in expected obj
  • Loading branch information
tyrannosaurus-becks authored Apr 28, 2020
1 parent 8126125 commit 0090835
Show file tree
Hide file tree
Showing 22 changed files with 1,536 additions and 118 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/hashicorp/go-hclog v0.12.0
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820
github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820
github.com/hashicorp/vault/sdk v0.1.14-0.20200427170607-03332aaf8d18
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/stretchr/testify v1.3.0
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820 h1:biZidYDDE
github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o=
github.com/hashicorp/vault/sdk v0.1.14-0.20200215195600-2ca765f0a500 h1:tiMX2ewq4ble+e2zENzBvaH2dMoFHe80NbnrF5Ir9Kk=
github.com/hashicorp/vault/sdk v0.1.14-0.20200215195600-2ca765f0a500/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820 h1:TmDZ1sS6gU0hFeFlFuyJVUwRPEzifZIHCBeS2WF2uSc=
github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
github.com/hashicorp/vault/sdk v0.1.14-0.20200427170607-03332aaf8d18 h1:xnQPngs9nbaMNx7keJMa1ccVAvs97ZHTno9o1Iz5x8Y=
github.com/hashicorp/vault/sdk v0.1.14-0.20200427170607-03332aaf8d18/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
Expand Down
5 changes: 4 additions & 1 deletion plugin/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/hashicorp/go-gcp-common/gcputil"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/sdk/helper/authmetadata"
"github.com/hashicorp/vault/sdk/logical"
)

Expand Down Expand Up @@ -40,7 +41,9 @@ func testBackendWithCreds(tb testing.TB) (*GcpAuthBackend, logical.Storage, *gcp
ctx := context.Background()

entry, err := logical.StorageEntryJSON("config", &gcpConfig{
Credentials: creds,
Credentials: creds,
GCEAuthMetadata: authmetadata.NewHandler(gceAuthMetadataFields),
IAMAuthMetadata: authmetadata.NewHandler(iamAuthMetadataFields),
})
if err != nil {
tb.Fatal(err)
Expand Down
31 changes: 17 additions & 14 deletions plugin/gcp_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-gcp-common/gcputil"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/authmetadata"
"google.golang.org/api/compute/v1"
"google.golang.org/api/iam/v1"
)

// gcpConfig contains all config required for the GCP backend.
type gcpConfig struct {
Credentials *gcputil.GcpCredentials `json:"credentials"`
IAMAliasType string `json:"iam_alias"`
GCEAliasType string `json:"gce_alias"`
Credentials *gcputil.GcpCredentials `json:"credentials"`
IAMAliasType string `json:"iam_alias"`
IAMAuthMetadata *authmetadata.Handler `json:"iam_auth_metadata_handler"`
GCEAliasType string `json:"gce_alias"`
GCEAuthMetadata *authmetadata.Handler `json:"gce_auth_metadata_handler"`
}

// standardizedCreds wraps gcputil.GcpCredentials with a type to allow
Expand All @@ -44,46 +47,46 @@ func (c *gcpConfig) formatAndMarshalCredentials() ([]byte, error) {
}

// Update sets gcpConfig values parsed from the FieldData.
func (c *gcpConfig) Update(d *framework.FieldData) (bool, error) {
func (c *gcpConfig) Update(d *framework.FieldData) error {
if d == nil {
return false, nil
return nil
}

changed := false

if v, ok := d.GetOk("credentials"); ok {
creds, err := gcputil.Credentials(v.(string))
if err != nil {
return false, errwrap.Wrapf("failed to read credentials: {{err}}", err)
return errwrap.Wrapf("failed to read credentials: {{err}}", err)
}

if len(creds.PrivateKeyId) == 0 {
return false, errors.New("missing private key in credentials")
return errors.New("missing private key in credentials")
}

c.Credentials = creds
changed = true
}

rawIamAlias, exists := d.GetOk("iam_alias")
if exists {
iamAlias := rawIamAlias.(string)
if iamAlias != c.IAMAliasType {
c.IAMAliasType = iamAlias
changed = true
}
}
if err := c.IAMAuthMetadata.ParseAuthMetadata(d); err != nil {
return errwrap.Wrapf("failed to parse iam metadata: {{err}}", err)
}

rawGceAlias, exists := d.GetOk("gce_alias")
if exists {
gceAlias := rawGceAlias.(string)
if gceAlias != c.GCEAliasType {
c.GCEAliasType = gceAlias
changed = true
}
}

return changed, nil
if err := c.GCEAuthMetadata.ParseAuthMetadata(d); err != nil {
return errwrap.Wrapf("failed to parse gce metadata: {{err}}", err)
}
return nil
}

func (c *gcpConfig) getIAMAlias(role *gcpRole, svcAccount *iam.ServiceAccount) (alias string, err error) {
Expand Down
111 changes: 72 additions & 39 deletions plugin/path_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,45 @@ import (

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

var (
// The default gce_alias is "instance_id". The default fields
// below are selected because they're unlikely to change often
// for a particular instance ID.
gceAuthMetadataFields = &authmetadata.Fields{
FieldName: "gce_metadata",
Default: []string{
"instance_creation_timestamp",
"instance_id",
"instance_name",
"project_id",
"project_number",
"role",
"service_account_id",
"service_account_email",
"zone",
},
AvailableToAdd: []string{},
}

// The default iam_alias is "unique_id". The default fields
// below are selected because they're unlikely to change often
// for a particular instance ID.
iamAuthMetadataFields = &authmetadata.Fields{
FieldName: "iam_metadata",
Default: []string{
"project_id",
"role",
"service_account_id",
"service_account_email",
},
AvailableToAdd: []string{},
}
)

func pathConfig(b *GcpAuthBackend) *framework.Path {
return &framework.Path{
Pattern: "config",
Expand All @@ -27,11 +63,13 @@ If not specified, will use application default credentials`,
Default: defaultIAMAlias,
Description: "Indicates what value to use when generating an alias for IAM authentications.",
},
iamAuthMetadataFields.FieldName: authmetadata.FieldSchema(iamAuthMetadataFields),
"gce_alias": {
Type: framework.TypeString,
Default: defaultGCEAlias,
Description: "Indicates what value to use when generating an alias for GCE authentications.",
},
gceAuthMetadataFields.FieldName: authmetadata.FieldSchema(gceAuthMetadataFields),

// Deprecated
"google_certs_endpoint": {
Expand Down Expand Up @@ -64,36 +102,28 @@ func (b *GcpAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Reque
}

c, err := b.config(ctx, req.Storage)

if err != nil {
return nil, err
}
if c == nil {
c = &gcpConfig{}
}

changed, err := c.Update(d)
if err != nil {
if err := c.Update(d); err != nil {
return nil, logical.CodedError(http.StatusBadRequest, err.Error())
}

// Only do the following if the config is different
if changed {
// Generate a new storage entry
entry, err := logical.StorageEntryJSON("config", c)
if err != nil {
return nil, errwrap.Wrapf("failed to generate JSON configuration: {{err}}", err)
}

// Save the storage entry
if err := req.Storage.Put(ctx, entry); err != nil {
return nil, errwrap.Wrapf("failed to persist configuration to storage: {{err}}", err)
}
// Create/update the storage entry
entry, err := logical.StorageEntryJSON("config", c)
if err != nil {
return nil, errwrap.Wrapf("failed to generate JSON configuration: {{err}}", err)
}

// Invalidate existing client so it reads the new configuration
b.ClearCaches()
// Save the storage entry
if err := req.Storage.Put(ctx, entry); err != nil {
return nil, errwrap.Wrapf("failed to persist configuration to storage: {{err}}", err)
}

// Invalidate existing client so it reads the new configuration
b.ClearCaches()

return nil, nil
}

Expand All @@ -106,23 +136,25 @@ func (b *GcpAuthBackend) pathConfigRead(ctx context.Context, req *logical.Reques
if err != nil {
return nil, err
}
if config == nil {
return nil, nil
}

resp := make(map[string]interface{})

if v := config.Credentials.ClientEmail; v != "" {
resp["client_email"] = v
}
if v := config.Credentials.ClientId; v != "" {
resp["client_id"] = v
resp := map[string]interface{}{
gceAuthMetadataFields.FieldName: config.GCEAuthMetadata.AuthMetadata(),
iamAuthMetadataFields.FieldName: config.IAMAuthMetadata.AuthMetadata(),
}
if v := config.Credentials.PrivateKeyId; v != "" {
resp["private_key_id"] = v
}
if v := config.Credentials.ProjectId; v != "" {
resp["project_id"] = v

if config.Credentials != nil {
if v := config.Credentials.ClientEmail; v != "" {
resp["client_email"] = v
}
if v := config.Credentials.ClientId; v != "" {
resp["client_id"] = v
}
if v := config.Credentials.PrivateKeyId; v != "" {
resp["private_key_id"] = v
}
if v := config.Credentials.ProjectId; v != "" {
resp["project_id"] = v
}
}

if v := config.IAMAliasType; v != "" {
Expand All @@ -140,16 +172,17 @@ func (b *GcpAuthBackend) pathConfigRead(ctx context.Context, req *logical.Reques
// config reads the backend's gcpConfig from storage.
// This assumes the caller has already obtained the backend's config lock.
func (b *GcpAuthBackend) config(ctx context.Context, s logical.Storage) (*gcpConfig, error) {
config := &gcpConfig{}
config := &gcpConfig{
GCEAuthMetadata: authmetadata.NewHandler(gceAuthMetadataFields),
IAMAuthMetadata: authmetadata.NewHandler(iamAuthMetadataFields),
}
entry, err := s.Get(ctx, "config")

if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
return config, nil
}

if err := entry.DecodeJSON(config); err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 0090835

Please sign in to comment.