Update plugins

This commit is contained in:
Brian Kassouf 2018-11-20 11:43:38 -08:00
parent 6e3d8311a1
commit 2b2b69cf0b
8 changed files with 10640 additions and 213 deletions

View File

@ -3,32 +3,26 @@ package gcpauth
import (
"context"
"fmt"
"net/http"
"runtime"
"sync"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-gcp-common/gcputil"
"github.com/hashicorp/vault/helper/useragent"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
"github.com/hashicorp/vault/version"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/cloudresourcemanager/v1"
"google.golang.org/api/compute/v1"
"google.golang.org/api/iam/v1"
"net/http"
)
const defaultCloudScope = "https://www.googleapis.com/auth/cloud-platform"
type GcpAuthBackend struct {
*framework.Backend
// OAuth scopes for generating HTTP and GCP service clients.
oauthScopes []string
// Locks for guarding service clients
clientMutex sync.RWMutex
// -- GCP service clients --
iamClient *iam.Service
gceClient *compute.Service
}
// Factory returns a new backend as logical.Backend.
@ -42,16 +36,12 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend,
func Backend() *GcpAuthBackend {
b := &GcpAuthBackend{
oauthScopes: []string{
iam.CloudPlatformScope,
compute.ComputeReadonlyScope,
},
oauthScopes: []string{defaultCloudScope},
}
b.Backend = &framework.Backend{
AuthRenew: b.pathLoginRenew,
BackendType: logical.TypeCredential,
Invalidate: b.invalidate,
Help: backendHelp,
PathsSpecial: &logical.Paths{
Unauthenticated: []string{
@ -72,106 +62,77 @@ func Backend() *GcpAuthBackend {
return b
}
func (b *GcpAuthBackend) invalidate(_ context.Context, key string) {
switch key {
case "config":
b.Close()
}
}
// Close deletes created GCP clients in backend.
func (b *GcpAuthBackend) Close() {
b.clientMutex.Lock()
defer b.clientMutex.Unlock()
b.iamClient = nil
b.gceClient = nil
}
func (b *GcpAuthBackend) IAM(ctx context.Context, s logical.Storage) (*iam.Service, error) {
b.clientMutex.RLock()
if b.iamClient != nil {
defer b.clientMutex.RUnlock()
return b.iamClient, nil
}
b.clientMutex.RUnlock()
b.clientMutex.Lock()
defer b.clientMutex.Unlock()
// Check if client was created during lock switch.
if b.iamClient == nil {
err := b.initClients(ctx, s)
if err != nil {
return nil, err
}
}
return b.iamClient, nil
}
func (b *GcpAuthBackend) GCE(ctx context.Context, s logical.Storage) (*compute.Service, error) {
b.clientMutex.RLock()
if b.gceClient != nil {
defer b.clientMutex.RUnlock()
return b.gceClient, nil
}
b.clientMutex.RUnlock()
b.clientMutex.Lock()
defer b.clientMutex.Unlock()
// Check if client was created during lock switch.
if b.gceClient == nil {
err := b.initClients(ctx, s)
if err != nil {
return nil, err
}
}
return b.gceClient, nil
}
// Initialize attempts to create GCP clients from stored config.
// It does not attempt to claim the client lock.
func (b *GcpAuthBackend) initClients(ctx context.Context, s logical.Storage) (err error) {
func (b *GcpAuthBackend) httpClient(ctx context.Context, s logical.Storage) (*http.Client, error) {
config, err := b.config(ctx, s)
if err != nil {
return err
return nil, errwrap.Wrapf(
"could not check to see if GCP credentials were configured, error"+
"reading config: {{err}}", err)
}
var httpClient *http.Client
if config == nil || config.Credentials == nil {
_, tknSrc, err := gcputil.FindCredentials("", ctx, b.oauthScopes...)
credsBytes, err := config.formatAndMarshalCredentials()
if err != nil {
return fmt.Errorf("credentials were not configured and fallback to application default credentials failed: %v", err)
return nil, errwrap.Wrapf(
"unable to marshal given GCP credential JSON: {{err}}", err)
}
var creds *google.Credentials
if config != nil && config.Credentials != nil {
creds, err = google.CredentialsFromJSON(ctx, credsBytes, b.oauthScopes...)
if err != nil {
return nil, errwrap.Wrapf("failed to parse credentials: {{err}}", err)
}
cleanCtx := context.WithValue(context.Background(), oauth2.HTTPClient, cleanhttp.DefaultClient())
httpClient = oauth2.NewClient(cleanCtx, tknSrc)
} else {
httpClient, err = gcputil.GetHttpClient(config.Credentials, b.oauthScopes...)
creds, err = google.FindDefaultCredentials(ctx, b.oauthScopes...)
if err != nil {
return err
return nil, errwrap.Wrapf(
"credentials were not configured and Vault could not find "+
"Application Default Credentials (ADC). Either set ADC or "+
"configure this auth backend at auth/$MOUNT/config "+
"(default auth/gcp/config). Error: {{err}}", err)
}
}
userAgentStr := fmt.Sprintf("(%s %s) Vault/%s", runtime.GOOS, runtime.GOARCH, version.GetVersion().FullVersionNumber(true))
cleanCtx := context.WithValue(ctx, oauth2.HTTPClient, cleanhttp.DefaultClient())
client := oauth2.NewClient(cleanCtx, creds.TokenSource)
return client, nil
}
b.iamClient, err = iam.New(httpClient)
func (b *GcpAuthBackend) newGcpClients(ctx context.Context, s logical.Storage) (*clientHandles, error) {
httpC, err := b.httpClient(ctx, s)
if err != nil {
b.Close()
return err
return nil, errwrap.Wrapf("could not obtain HTTP client: {{err}}", err)
}
b.iamClient.UserAgent = userAgentStr
b.gceClient, err = compute.New(httpClient)
iamClient, err := iam.New(httpC)
if err != nil {
b.Close()
return err
return nil, fmt.Errorf(clientErrorTemplate, "IAM", err)
}
b.gceClient.UserAgent = userAgentStr
iamClient.UserAgent = useragent.String()
return nil
gceClient, err := compute.New(httpC)
if err != nil {
return nil, fmt.Errorf(clientErrorTemplate, "Compute", err)
}
iamClient.UserAgent = useragent.String()
crmClient, err := cloudresourcemanager.New(httpC)
if err != nil {
return nil, fmt.Errorf(clientErrorTemplate, "Cloud Resource Manager", err)
}
crmClient.UserAgent = useragent.String()
return &clientHandles{
iam: iamClient,
gce: gceClient,
resourceManager: crmClient,
}, nil
}
type clientHandles struct {
iam *iam.Service
gce *compute.Service
resourceManager *cloudresourcemanager.Service
}
const backendHelp = `

View File

@ -163,8 +163,8 @@ Configuration:
permissions on this service account.
project=<string>
Project the service account belongs to. Defaults to credentials
"project_id" if "credentials" specified and this value is not.
Project for the service account who will be authenticating to Vault.
Defaults to the credential's "project_id" (if credentials are specified)."
`
return strings.TrimSpace(help)

View File

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"encoding/json"
"github.com/hashicorp/go-gcp-common/gcputil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
@ -23,8 +24,7 @@ If not specified, will use application default credentials`,
"google_certs_endpoint": {
Type: framework.TypeString,
Description: `
Base endpoint url that Vault will use to get Google certificates.
If not specified, will use the OAuth2 library default. Useful for testing.`,
Deprecated. This field does nothing and be removed in a future release`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
@ -65,9 +65,6 @@ func (b *GcpAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Reque
return nil, err
}
// Invalidate exisitng clients so they read the new configuration
b.Close()
return nil, nil
}
@ -94,9 +91,6 @@ func (b *GcpAuthBackend) pathConfigRead(ctx context.Context, req *logical.Reques
if v := config.Credentials.ProjectId; v != "" {
resp["project_id"] = v
}
if v := config.GoogleCertsEndpoint; v != "" {
resp["google_certs_endpoint"] = v
}
return &logical.Response{
Data: resp,
@ -116,7 +110,29 @@ iam AUTH:
// gcpConfig contains all config required for the GCP backend.
type gcpConfig struct {
Credentials *gcputil.GcpCredentials `json:"credentials"`
GoogleCertsEndpoint string `json:"google_certs_endpoint"`
}
// standardizedCreds wraps gcputil.GcpCredentials with a type to allow
// parsing through Google libraries, since the google libraries struct is not
// exposed.
type standardizedCreds struct {
*gcputil.GcpCredentials
CredType string `json:"type"`
}
const serviceAccountCredsType = "service_account"
// formatAsCredentialJSON converts and marshals the config credentials
// into a parsable format by Google libraries.
func (config *gcpConfig) formatAndMarshalCredentials() ([]byte, error) {
if config == nil || config.Credentials == nil {
return []byte{}, nil
}
return json.Marshal(standardizedCreds{
GcpCredentials: config.Credentials,
CredType: serviceAccountCredsType,
})
}
// Update sets gcpConfig values parsed from the FieldData.
@ -132,12 +148,6 @@ func (config *gcpConfig) Update(data *framework.FieldData) error {
}
config.Credentials = creds
}
certsEndpoint := data.Get("google_certs_endpoint").(string)
if len(certsEndpoint) > 0 {
config.GoogleCertsEndpoint = certsEndpoint
}
return nil
}

View File

@ -15,6 +15,7 @@ import (
"github.com/hashicorp/vault/helper/strutil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
"google.golang.org/api/cloudresourcemanager/v1"
"google.golang.org/api/compute/v1"
"google.golang.org/api/iam/v1"
"gopkg.in/square/go-jose.v2/jwt"
@ -118,7 +119,7 @@ type gcpLoginInfo struct {
Role *gcpRole
// ID or email of an IAM service account or that inferred for a GCE VM.
ServiceAccountId string
EmailOrId string
// Base JWT Claims (registered claims such as 'exp', 'iss', etc)
JWTClaims *jwt.Claims
@ -177,7 +178,7 @@ func (b *GcpAuthBackend) parseAndValidateJwt(ctx context.Context, req *logical.R
if len(baseClaims.Subject) == 0 {
return nil, errors.New("expected JWT to have non-empty 'sub' claim")
}
loginInfo.ServiceAccountId = baseClaims.Subject
loginInfo.EmailOrId = baseClaims.Subject
if customClaims.Google != nil && customClaims.Google.Compute != nil && len(customClaims.Google.Compute.InstanceId) > 0 {
loginInfo.GceMetadata = customClaims.Google.Compute
@ -199,24 +200,23 @@ func (b *GcpAuthBackend) getSigningKey(ctx context.Context, token *jwt.JSONWebTo
switch role.RoleType {
case iamRoleType:
iamClient, err := b.IAM(ctx, s)
clients, err := b.newGcpClients(ctx, s)
if err != nil {
return nil, err
}
serviceAccountId, err := parseServiceAccountFromIAMJWT(rawToken)
if err != nil {
return nil, err
}
accountKey, err := gcputil.ServiceAccountKey(iamClient, &gcputil.ServiceAccountKeyId{
Project: role.ProjectId,
accountKey, err := gcputil.ServiceAccountKey(clients.iam, &gcputil.ServiceAccountKeyId{
Project: "-",
EmailOrId: serviceAccountId,
Key: keyId,
})
if err != nil {
// Attempt to get a normal Google Oauth cert in case of GCE inferrence.
key, err := b.getGoogleOauthCert(ctx, keyId, s)
key, err := gcputil.OAuth2RSAPublicKey(keyId, "")
if err != nil {
return nil, errwrap.Wrapf(
fmt.Sprintf("could not find service account key or Google Oauth cert with given 'kid' id %s: {{err}}", keyId),
@ -226,7 +226,7 @@ func (b *GcpAuthBackend) getSigningKey(ctx context.Context, token *jwt.JSONWebTo
}
return gcputil.PublicKey(accountKey.PublicKeyData)
case gceRoleType:
return b.getGoogleOauthCert(ctx, keyId, s)
return gcputil.OAuth2RSAPublicKey(keyId, "")
default:
return nil, fmt.Errorf("unexpected role type %s", role.RoleType)
}
@ -245,17 +245,8 @@ func parseServiceAccountFromIAMJWT(signedJwt string) (string, error) {
return accountId, nil
}
func (b *GcpAuthBackend) getGoogleOauthCert(ctx context.Context, keyId string, s logical.Storage) (interface{}, error) {
var certsEndpoint string
conf, err := b.config(ctx, s)
if err != nil {
return nil, fmt.Errorf("could not read config for backend: %v", err)
}
if conf != nil {
certsEndpoint = conf.GoogleCertsEndpoint
}
key, err := gcputil.OAuth2RSAPublicKey(keyId, certsEndpoint)
func (b *GcpAuthBackend) getGoogleOauthCert(ctx context.Context, keyId string) (interface{}, error) {
key, err := gcputil.OAuth2RSAPublicKey(keyId, "")
if err != nil {
return nil, err
}
@ -288,9 +279,9 @@ func validateBaseJWTClaims(c *jwt.Claims, roleName string) error {
// ---- IAM login domain ----
// pathIamLogin attempts a login operation using the parsed login info.
func (b *GcpAuthBackend) pathIamLogin(ctx context.Context, req *logical.Request, loginInfo *gcpLoginInfo) (*logical.Response, error) {
iamClient, err := b.IAM(ctx, req.Storage)
clients, err := b.newGcpClients(ctx, req.Storage)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(clientErrorTemplate, "IAM", err)), nil
return nil, err
}
role := loginInfo.Role
@ -306,10 +297,10 @@ func (b *GcpAuthBackend) pathIamLogin(ctx context.Context, req *logical.Request,
// Get service account and make sure it still exists.
accountId := &gcputil.ServiceAccountId{
Project: role.ProjectId,
EmailOrId: loginInfo.ServiceAccountId,
Project: "-",
EmailOrId: loginInfo.EmailOrId,
}
serviceAccount, err := gcputil.ServiceAccount(iamClient, accountId)
serviceAccount, err := gcputil.ServiceAccount(clients.iam, accountId)
if err != nil {
return nil, err
}
@ -348,6 +339,18 @@ func (b *GcpAuthBackend) pathIamLogin(ctx context.Context, req *logical.Request,
},
},
}
if role.AddGroupAliases {
clients, err := b.newGcpClients(ctx, req.Storage)
if err != nil {
return nil, err
}
aliases, err := b.groupAliases(clients.resourceManager, ctx, serviceAccount.ProjectId)
if err != nil {
return nil, err
}
resp.Auth.GroupAliases = aliases
}
return resp, nil
}
@ -355,9 +358,9 @@ func (b *GcpAuthBackend) pathIamLogin(ctx context.Context, req *logical.Request,
// pathIamRenew returns an error if the service account referenced in the auth token metadata cannot renew the
// auth token for the given role.
func (b *GcpAuthBackend) pathIamRenew(ctx context.Context, req *logical.Request, roleName string, role *gcpRole) error {
iamClient, err := b.IAM(ctx, req.Storage)
clients, err := b.newGcpClients(ctx, req.Storage)
if err != nil {
return fmt.Errorf(clientErrorTemplate, "IAM", err)
return err
}
serviceAccountId, ok := req.Auth.Metadata["service_account_id"]
@ -365,8 +368,14 @@ func (b *GcpAuthBackend) pathIamRenew(ctx context.Context, req *logical.Request,
return errors.New("service account id metadata not associated with auth token, invalid")
}
serviceAccount, err := gcputil.ServiceAccount(iamClient, &gcputil.ServiceAccountId{
Project: role.ProjectId,
// This project is the service account's project.
project, ok := req.Auth.Metadata["project_id"]
if !ok {
project = "-"
}
serviceAccount, err := gcputil.ServiceAccount(clients.iam, &gcputil.ServiceAccountId{
Project: project,
EmailOrId: serviceAccountId,
})
if err != nil {
@ -387,9 +396,8 @@ func (b *GcpAuthBackend) pathIamRenew(ctx context.Context, req *logical.Request,
// validateAgainstIAMRole returns an error if the given IAM service account is not authorized for the role.
func (b *GcpAuthBackend) authorizeIAMServiceAccount(serviceAccount *iam.ServiceAccount, role *gcpRole) error {
// This is just in case - project should already be used to retrieve service account.
if role.ProjectId != serviceAccount.ProjectId {
return fmt.Errorf("service account %s does not belong to project %s", serviceAccount.Email, role.ProjectId)
if len(role.BoundProjects) > 0 && !strutil.StrListContains(role.BoundProjects, serviceAccount.ProjectId) {
return fmt.Errorf("service account %q not in bound projects %+v", serviceAccount.Email, role.BoundProjects)
}
// Check if role has the wildcard as the only service account.
@ -416,42 +424,46 @@ func (b *GcpAuthBackend) pathGceLogin(ctx context.Context, req *logical.Request,
return logical.ErrorResponse("could not get GCE metadata from given JWT"), nil
}
if role.ProjectId != metadata.ProjectId {
if len(role.BoundProjects) > 0 && !strutil.StrListContains(role.BoundProjects, metadata.ProjectId) {
return logical.ErrorResponse(fmt.Sprintf(
"GCE instance must belong to project %s; metadata given has project %s",
role.ProjectId, metadata.ProjectId)), nil
"instance %q (project %q) not in bound projects %+v", metadata.InstanceId, metadata.ProjectId, role.BoundProjects)), nil
}
// Verify instance exists.
gceClient, err := b.GCE(ctx, req.Storage)
clients, err := b.newGcpClients(ctx, req.Storage)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(clientErrorTemplate, "GCE", err)), nil
return nil, err
}
instance, err := metadata.GetVerifiedInstance(gceClient)
instance, err := metadata.GetVerifiedInstance(clients.gce)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(
"error when attempting to find instance (project %s, zone: %s, instance: %s) :%v",
metadata.ProjectId, metadata.Zone, metadata.InstanceName, err)), nil
}
if err := b.authorizeGCEInstance(ctx, instance, req.Storage, role, loginInfo.ServiceAccountId); err != nil {
if err := b.authorizeGCEInstance(ctx, instance, req.Storage, role, loginInfo.EmailOrId); err != nil {
return logical.ErrorResponse(err.Error()), nil
}
iamClient, err := b.IAM(ctx, req.Storage)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(clientErrorTemplate, "IAM", err)), nil
if req.Operation == logical.AliasLookaheadOperation {
return &logical.Response{
Auth: &logical.Auth{
Alias: &logical.Alias{
Name: fmt.Sprintf("gce-%s", strconv.FormatUint(instance.Id, 10)),
},
},
}, nil
}
serviceAccount, err := gcputil.ServiceAccount(iamClient, &gcputil.ServiceAccountId{
Project: loginInfo.Role.ProjectId,
EmailOrId: loginInfo.ServiceAccountId,
serviceAccount, err := gcputil.ServiceAccount(clients.iam, &gcputil.ServiceAccountId{
Project: "-",
EmailOrId: loginInfo.EmailOrId,
})
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(
"Could not find service account '%s' used for GCE metadata token: %s",
loginInfo.ServiceAccountId, err)), nil
loginInfo.EmailOrId, err)), nil
}
resp := &logical.Response{
@ -472,14 +484,56 @@ func (b *GcpAuthBackend) pathGceLogin(ctx context.Context, req *logical.Request,
},
}
if role.AddGroupAliases {
aliases, err := b.groupAliases(clients.resourceManager, ctx, metadata.ProjectId)
if err != nil {
return nil, err
}
resp.Auth.GroupAliases = aliases
}
return resp, nil
}
// groupAliases will add group aliases for an authenticating GCP entity
// starting at project-level and going up the Cloud Resource Manager
// hierarchy
//
// For example, given a project hierarchy of
// "my-org" --> "my-folder" --> "my-subfolder" --> "my-project",
// this returns the following group aliases:
// [
// "project-my-project"
// "folder-my-subfolder"
// "folder-my-project"
// "organization-my-org"
// ]
func (b *GcpAuthBackend) groupAliases(crmClient *cloudresourcemanager.Service, ctx context.Context, projectId string) ([]*logical.Alias, error) {
ancestry, err := crmClient.Projects.
GetAncestry(projectId, &cloudresourcemanager.GetAncestryRequest{}).
Context(ctx).
Do()
if err != nil {
return nil, err
}
aliases := make([]*logical.Alias, len(ancestry.Ancestor)+1)
aliases[0] = &logical.Alias{
Name: fmt.Sprintf("project-%s", projectId),
}
for i, parent := range ancestry.Ancestor {
aliases[i+1] = &logical.Alias{
Name: fmt.Sprintf("%s-%s", parent.ResourceId.Type, parent.ResourceId.Id),
}
}
return aliases, nil
}
func authMetadata(loginInfo *gcpLoginInfo, serviceAccount *iam.ServiceAccount) map[string]string {
metadata := map[string]string{
"role": loginInfo.RoleName,
"service_account_id": serviceAccount.UniqueId,
"service_account_email": serviceAccount.Email,
"project_id": serviceAccount.ProjectId,
}
if loginInfo.GceMetadata != nil {
@ -497,7 +551,12 @@ func authMetadata(loginInfo *gcpLoginInfo, serviceAccount *iam.ServiceAccount) m
// pathGceRenew returns an error if the instance referenced in the auth token metadata cannot renew the
// auth token for the given role.
func (b *GcpAuthBackend) pathGceRenew(ctx context.Context, req *logical.Request, roleName string, role *gcpRole) error {
gceClient, err := b.GCE(ctx, req.Storage)
httpC, err := b.httpClient(ctx, req.Storage)
if err != nil {
return err
}
gceClient, err := compute.New(httpC)
if err != nil {
return fmt.Errorf(clientErrorTemplate, "GCE", err)
}
@ -573,23 +632,26 @@ func getInstanceMetadataFromAuth(authMetadata map[string]string) (*gcputil.GCEId
// authorizeGCEInstance returns an error if the given GCE instance is not
// authorized for the role.
func (b *GcpAuthBackend) authorizeGCEInstance(ctx context.Context, instance *compute.Instance, s logical.Storage, role *gcpRole, serviceAccountId string) error {
computeSvc, err := b.GCE(ctx, s)
httpC, err := b.httpClient(ctx, s)
if err != nil {
return err
}
iamSvc, err := b.IAM(ctx, s)
iamClient, err := iam.New(httpC)
if err != nil {
return err
return fmt.Errorf(clientErrorTemplate, "IAM", err)
}
gceClient, err := compute.New(httpC)
if err != nil {
return fmt.Errorf(clientErrorTemplate, "GCE", err)
}
return AuthorizeGCE(ctx, &AuthorizeGCEInput{
client: &gcpClient{
computeSvc: computeSvc,
iamSvc: iamSvc,
computeSvc: gceClient,
iamSvc: iamClient,
},
project: role.ProjectId,
serviceAccount: serviceAccountId,
instanceLabels: instance.Labels,

View File

@ -23,7 +23,6 @@ const (
// Errors
errEmptyRoleName = "role name is required"
errEmptyRoleType = "role type cannot be empty"
errEmptyProjectId = "project id cannot be empty"
errEmptyIamServiceAccounts = "IAM role type must have at least one service account"
errTemplateEditListWrongType = "role is type '%s', cannot edit attribute '%s' (expected role type: '%s')"
@ -73,9 +72,9 @@ var baseRoleFieldSchema = map[string]*framework.FieldSchema{
duration specified by this value. At each renewal, the token's TTL will be set to the value of this parameter.`,
},
// -- GCP Information
"project_id": {
Type: framework.TypeString,
Description: `The id of the project that authorized instances must belong to for this role.`,
"bound_projects": {
Type: framework.TypeCommaStringSlice,
Description: `GCP Projects that authenticating entities must belong to.`,
},
"bound_service_accounts": {
Type: framework.TypeCommaStringSlice,
@ -84,9 +83,13 @@ var baseRoleFieldSchema = map[string]*framework.FieldSchema{
If the single value "*" is given, this is assumed to be all service accounts under the role's project. If this
is set on a GCE role, the inferred service account from the instance metadata token will be used.`,
},
"service_accounts": {
Type: framework.TypeCommaStringSlice,
Description: `Deprecated, use bound_service_accounts instead.`,
"add_group_aliases": {
Type: framework.TypeBool,
Default: false,
Description: "If true, will add group aliases to auth tokens generated under this role. " +
"This will add the full list of ancestors (projects, folders, organizations) " +
"for the given entity's project. Requires IAM permission `resourcemanager.projects.get` " +
"on this project.",
},
}
@ -132,20 +135,6 @@ var gceOnlyFieldSchema = map[string]*framework.FieldSchema{
"\"key:value\" strings that must be present on the GCE instance " +
"in order to authenticate. This option only applies to \"gce\" roles.",
},
// Deprecated roles
"bound_zone": {
Type: framework.TypeString,
Description: "Deprecated: use \"bound_zones\" instead.",
},
"bound_region": {
Type: framework.TypeString,
Description: "Deprecated: use \"bound_regions\" instead.",
},
"bound_instance_group": {
Type: framework.TypeString,
Description: "Deprecated: use \"bound_instance_groups\" instead.",
},
}
// pathsRole creates paths for listing roles and CRUD operations.
@ -160,6 +149,9 @@ func pathsRole(b *GcpAuthBackend) []*framework.Path {
for k, v := range gceOnlyFieldSchema {
roleFieldSchema[k] = v
}
for k, v := range deprecatedFieldSchema {
roleFieldSchema[k] = v
}
paths := []*framework.Path{
{
@ -291,9 +283,6 @@ func (b *GcpAuthBackend) pathRoleRead(ctx context.Context, req *logical.Request,
if role.RoleType != "" {
resp["type"] = role.RoleType
}
if role.ProjectId != "" {
resp["project_id"] = role.ProjectId
}
if len(role.Policies) > 0 {
resp["policies"] = role.Policies
}
@ -309,6 +298,10 @@ func (b *GcpAuthBackend) pathRoleRead(ctx context.Context, req *logical.Request,
if len(role.BoundServiceAccounts) > 0 {
resp["bound_service_accounts"] = role.BoundServiceAccounts
}
if len(role.BoundProjects) > 0 {
resp["bound_projects"] = role.BoundProjects
}
resp["add_group_aliases"] = role.AddGroupAliases
switch role.RoleType {
case iamRoleType:
@ -509,6 +502,12 @@ func (b *GcpAuthBackend) role(ctx context.Context, s logical.Storage, name strin
modified := false
// Move old bindings to new fields.
if role.ProjectId != "" && len(role.BoundProjects) == 0 {
role.BoundProjects = []string{role.ProjectId}
role.ProjectId = ""
modified = true
}
if role.BoundRegion != "" && len(role.BoundRegions) == 0 {
role.BoundRegions = []string{role.BoundRegion}
role.BoundRegion = ""
@ -576,9 +575,6 @@ type gcpRole struct {
// Type of this role. See path_role constants for currently supported types.
RoleType string `json:"role_type,omitempty"`
// Project ID in GCP for authorized entities.
ProjectId string `json:"project_id,omitempty"`
// Policies for Vault to assign to authorized entities.
Policies []string `json:"policies,omitempty"`
@ -593,6 +589,9 @@ type gcpRole struct {
// with TTL equal to this value.
Period time.Duration `json:"period,omitempty"`
// Projects that entities must belong to
BoundProjects []string `json:"bound_projects,omitempty"`
// Service accounts allowed to login under this role.
BoundServiceAccounts []string `json:"bound_service_accounts,omitempty"`
@ -618,8 +617,11 @@ type gcpRole struct {
// BoundLabels that instances must currently have set in order to login under this role.
BoundLabels map[string]string `json:"bound_labels,omitempty"`
AddGroupAliases bool `json:"add_group_aliases,omitempty"`
// Deprecated fields
// TODO: Remove in 0.5.0+
ProjectId string `json:"project_id,omitempty"`
BoundRegion string `json:"bound_region,omitempty"`
BoundZone string `json:"bound_zone,omitempty"`
BoundInstanceGroup string `json:"bound_instance_group,omitempty"`
@ -650,13 +652,6 @@ func (role *gcpRole) updateRole(sys logical.SystemView, op logical.Operation, da
role.Policies = policyutil.ParsePolicies(nil)
}
// Update GCP project id.
if projectId, ok := data.GetOk("project_id"); ok {
role.ProjectId = projectId.(string)
} else if op == logical.CreateOperation {
role.ProjectId = data.Get("project_id").(string)
}
// Update token TTL.
if ttl, ok := data.GetOk("ttl"); ok {
role.TTL = time.Duration(ttl.(int)) * time.Second
@ -711,12 +706,37 @@ func (role *gcpRole) updateRole(sys logical.SystemView, op logical.Operation, da
role.BoundServiceAccounts = sa.([]string)
}
}
if len(role.BoundServiceAccounts) > 0 {
role.BoundServiceAccounts = strutil.TrimStrings(role.BoundServiceAccounts)
role.BoundServiceAccounts = strutil.RemoveDuplicates(role.BoundServiceAccounts, false)
}
// Update bound GCP projects.
boundProjects, givenBoundProj := data.GetOk("bound_projects")
if givenBoundProj {
role.BoundProjects = boundProjects.([]string)
}
if projectId, ok := data.GetOk("project_id"); ok {
if givenBoundProj {
return warnings, errors.New("only one of 'bound_projects' or 'project_id' can be given")
}
warnings = append(warnings,
`The "project_id" (singular) field is deprecated. `+
`Please use plural "bound_projects" instead to bind required GCP projects. `+
`The "project_id" field will be removed in a later release, so please update accordingly.`)
role.BoundProjects = []string{projectId.(string)}
}
if len(role.BoundProjects) > 0 {
role.BoundProjects = strutil.TrimStrings(role.BoundProjects)
role.BoundProjects = strutil.RemoveDuplicates(role.BoundProjects, false)
}
// Update bound GCP projects.
addGroupAliases, ok := data.GetOk("add_group_aliases")
if ok {
role.AddGroupAliases = addGroupAliases.(bool)
}
// Update fields specific to this type
switch role.RoleType {
case iamRoleType:
@ -756,10 +776,6 @@ func (role *gcpRole) validate(sys logical.SystemView) (warnings []string, err er
return warnings, fmt.Errorf("role type '%s' is invalid", role.RoleType)
}
if role.ProjectId == "" {
return warnings, errors.New(errEmptyProjectId)
}
defaultLeaseTTL := sys.DefaultLeaseTTL()
if role.TTL > defaultLeaseTTL {
warnings = append(warnings, fmt.Sprintf(
@ -960,3 +976,27 @@ func checkInvalidRoleTypeArgs(data *framework.FieldData, invalidSchema map[strin
}
return nil
}
// deprecatedFieldSchema contains the deprecated role attributes
var deprecatedFieldSchema = map[string]*framework.FieldSchema{
"service_accounts": {
Type: framework.TypeCommaStringSlice,
Description: "Deprecated: use \"bound_service_accounts\" instead.",
},
"project_id": {
Type: framework.TypeString,
Description: "Deprecated: use \"bound_projects\" instead",
},
"bound_zone": {
Type: framework.TypeString,
Description: "Deprecated: use \"bound_zones\" instead.",
},
"bound_region": {
Type: framework.TypeString,
Description: "Deprecated: use \"bound_regions\" instead.",
},
"bound_instance_group": {
Type: framework.TypeString,
Description: "Deprecated: use \"bound_instance_groups\" instead.",
},
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

12
vendor/vendor.json vendored
View File

@ -1425,10 +1425,10 @@
"revisionTime": "2018-08-16T20:11:31Z"
},
{
"checksumSHA1": "pJC3V+AR+Khd/TpvekLf1ZRU7rA=",
"checksumSHA1": "h7m+nCb1vdvcy5cUkCarix08yJI=",
"path": "github.com/hashicorp/vault-plugin-auth-gcp/plugin",
"revision": "86f7837fd8102347f7a10e3f0ad275e5a256863c",
"revisionTime": "2018-10-25T21:28:40Z"
"revision": "0a3edf071747fcb84aa4f9d08a34d2c249e6228d",
"revisionTime": "2018-11-15T01:46:55Z"
},
{
"checksumSHA1": "tt3FtyjXgdBI9Mb43UL4LtOZmAk=",
@ -2808,6 +2808,12 @@
"revision": "cc9bd73d51b4c5610e35bcb01f15368185cfaab3",
"revisionTime": "2018-10-16T15:55:58Z"
},
{
"checksumSHA1": "9Gt9K/Z3m6q4vOJPIm1ynTvgecg=",
"path": "google.golang.org/api/cloudresourcemanager/v1",
"revision": "83a9d304b1e613fc253e1e2710778642fe81af53",
"revisionTime": "2018-11-14T18:14:26Z"
},
{
"checksumSHA1": "QkzppExyctFz029dvXzKzh6UHgU=",
"path": "google.golang.org/api/compute/v1",