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

GCP auth metadata config #92

Merged
merged 4 commits into from Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
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
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
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
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
118 changes: 75 additions & 43 deletions plugin/path_config.go
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 {
tyrannosaurus-becks marked this conversation as resolved.
Show resolved Hide resolved
c = &gcpConfig{}
}

changed, err := c.Update(d)
if err != nil {
if err := c.Update(d); err != nil {
tyrannosaurus-becks marked this conversation as resolved.
Show resolved Hide resolved
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 {
tyrannosaurus-becks marked this conversation as resolved.
Show resolved Hide resolved
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,18 +172,18 @@ 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
}

if err := entry.DecodeJSON(config); err != nil {
return nil, err
if entry != nil {
if err := entry.DecodeJSON(config); err != nil {
tyrannosaurus-becks marked this conversation as resolved.
Show resolved Hide resolved
return nil, err
}
}
return config, nil
}