Update plugins
This commit is contained in:
parent
6e3d8311a1
commit
2b2b69cf0b
|
@ -3,32 +3,26 @@ package gcpauth
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"github.com/hashicorp/errwrap"
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-cleanhttp"
|
"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"
|
||||||
"github.com/hashicorp/vault/logical/framework"
|
"github.com/hashicorp/vault/logical/framework"
|
||||||
"github.com/hashicorp/vault/version"
|
|
||||||
"golang.org/x/oauth2"
|
"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/compute/v1"
|
||||||
"google.golang.org/api/iam/v1"
|
"google.golang.org/api/iam/v1"
|
||||||
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultCloudScope = "https://www.googleapis.com/auth/cloud-platform"
|
||||||
|
|
||||||
type GcpAuthBackend struct {
|
type GcpAuthBackend struct {
|
||||||
*framework.Backend
|
*framework.Backend
|
||||||
|
|
||||||
// OAuth scopes for generating HTTP and GCP service clients.
|
// OAuth scopes for generating HTTP and GCP service clients.
|
||||||
oauthScopes []string
|
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.
|
// 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 {
|
func Backend() *GcpAuthBackend {
|
||||||
b := &GcpAuthBackend{
|
b := &GcpAuthBackend{
|
||||||
oauthScopes: []string{
|
oauthScopes: []string{defaultCloudScope},
|
||||||
iam.CloudPlatformScope,
|
|
||||||
compute.ComputeReadonlyScope,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Backend = &framework.Backend{
|
b.Backend = &framework.Backend{
|
||||||
AuthRenew: b.pathLoginRenew,
|
AuthRenew: b.pathLoginRenew,
|
||||||
BackendType: logical.TypeCredential,
|
BackendType: logical.TypeCredential,
|
||||||
Invalidate: b.invalidate,
|
|
||||||
Help: backendHelp,
|
Help: backendHelp,
|
||||||
PathsSpecial: &logical.Paths{
|
PathsSpecial: &logical.Paths{
|
||||||
Unauthenticated: []string{
|
Unauthenticated: []string{
|
||||||
|
@ -72,106 +62,77 @@ func Backend() *GcpAuthBackend {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *GcpAuthBackend) invalidate(_ context.Context, key string) {
|
func (b *GcpAuthBackend) httpClient(ctx context.Context, s logical.Storage) (*http.Client, error) {
|
||||||
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) {
|
|
||||||
config, err := b.config(ctx, s)
|
config, err := b.config(ctx, s)
|
||||||
if err != nil {
|
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
|
credsBytes, err := config.formatAndMarshalCredentials()
|
||||||
if config == nil || config.Credentials == nil {
|
if err != nil {
|
||||||
_, tknSrc, err := gcputil.FindCredentials("", ctx, b.oauthScopes...)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("credentials were not configured and fallback to application default credentials failed: %v", err)
|
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 {
|
} else {
|
||||||
httpClient, err = gcputil.GetHttpClient(config.Credentials, b.oauthScopes...)
|
creds, err = google.FindDefaultCredentials(ctx, b.oauthScopes...)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
b.Close()
|
return nil, errwrap.Wrapf("could not obtain HTTP client: {{err}}", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
b.iamClient.UserAgent = userAgentStr
|
|
||||||
|
|
||||||
b.gceClient, err = compute.New(httpClient)
|
iamClient, err := iam.New(httpC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Close()
|
return nil, fmt.Errorf(clientErrorTemplate, "IAM", err)
|
||||||
return 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 = `
|
const backendHelp = `
|
||||||
|
|
|
@ -163,8 +163,8 @@ Configuration:
|
||||||
permissions on this service account.
|
permissions on this service account.
|
||||||
|
|
||||||
project=<string>
|
project=<string>
|
||||||
Project the service account belongs to. Defaults to credentials
|
Project for the service account who will be authenticating to Vault.
|
||||||
"project_id" if "credentials" specified and this value is not.
|
Defaults to the credential's "project_id" (if credentials are specified)."
|
||||||
`
|
`
|
||||||
|
|
||||||
return strings.TrimSpace(help)
|
return strings.TrimSpace(help)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||||
"github.com/hashicorp/vault/logical"
|
"github.com/hashicorp/vault/logical"
|
||||||
"github.com/hashicorp/vault/logical/framework"
|
"github.com/hashicorp/vault/logical/framework"
|
||||||
|
@ -23,8 +24,7 @@ If not specified, will use application default credentials`,
|
||||||
"google_certs_endpoint": {
|
"google_certs_endpoint": {
|
||||||
Type: framework.TypeString,
|
Type: framework.TypeString,
|
||||||
Description: `
|
Description: `
|
||||||
Base endpoint url that Vault will use to get Google certificates.
|
Deprecated. This field does nothing and be removed in a future release`,
|
||||||
If not specified, will use the OAuth2 library default. Useful for testing.`,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||||
|
@ -65,9 +65,6 @@ func (b *GcpAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Reque
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate exisitng clients so they read the new configuration
|
|
||||||
b.Close()
|
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,9 +91,6 @@ func (b *GcpAuthBackend) pathConfigRead(ctx context.Context, req *logical.Reques
|
||||||
if v := config.Credentials.ProjectId; v != "" {
|
if v := config.Credentials.ProjectId; v != "" {
|
||||||
resp["project_id"] = v
|
resp["project_id"] = v
|
||||||
}
|
}
|
||||||
if v := config.GoogleCertsEndpoint; v != "" {
|
|
||||||
resp["google_certs_endpoint"] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
return &logical.Response{
|
return &logical.Response{
|
||||||
Data: resp,
|
Data: resp,
|
||||||
|
@ -115,8 +109,30 @@ iam AUTH:
|
||||||
|
|
||||||
// gcpConfig contains all config required for the GCP backend.
|
// gcpConfig contains all config required for the GCP backend.
|
||||||
type gcpConfig struct {
|
type gcpConfig struct {
|
||||||
Credentials *gcputil.GcpCredentials `json:"credentials"`
|
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.
|
// Update sets gcpConfig values parsed from the FieldData.
|
||||||
|
@ -132,12 +148,6 @@ func (config *gcpConfig) Update(data *framework.FieldData) error {
|
||||||
}
|
}
|
||||||
config.Credentials = creds
|
config.Credentials = creds
|
||||||
}
|
}
|
||||||
|
|
||||||
certsEndpoint := data.Get("google_certs_endpoint").(string)
|
|
||||||
if len(certsEndpoint) > 0 {
|
|
||||||
config.GoogleCertsEndpoint = certsEndpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/hashicorp/vault/helper/strutil"
|
"github.com/hashicorp/vault/helper/strutil"
|
||||||
"github.com/hashicorp/vault/logical"
|
"github.com/hashicorp/vault/logical"
|
||||||
"github.com/hashicorp/vault/logical/framework"
|
"github.com/hashicorp/vault/logical/framework"
|
||||||
|
"google.golang.org/api/cloudresourcemanager/v1"
|
||||||
"google.golang.org/api/compute/v1"
|
"google.golang.org/api/compute/v1"
|
||||||
"google.golang.org/api/iam/v1"
|
"google.golang.org/api/iam/v1"
|
||||||
"gopkg.in/square/go-jose.v2/jwt"
|
"gopkg.in/square/go-jose.v2/jwt"
|
||||||
|
@ -118,7 +119,7 @@ type gcpLoginInfo struct {
|
||||||
Role *gcpRole
|
Role *gcpRole
|
||||||
|
|
||||||
// ID or email of an IAM service account or that inferred for a GCE VM.
|
// 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)
|
// Base JWT Claims (registered claims such as 'exp', 'iss', etc)
|
||||||
JWTClaims *jwt.Claims
|
JWTClaims *jwt.Claims
|
||||||
|
@ -177,7 +178,7 @@ func (b *GcpAuthBackend) parseAndValidateJwt(ctx context.Context, req *logical.R
|
||||||
if len(baseClaims.Subject) == 0 {
|
if len(baseClaims.Subject) == 0 {
|
||||||
return nil, errors.New("expected JWT to have non-empty 'sub' claim")
|
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 {
|
if customClaims.Google != nil && customClaims.Google.Compute != nil && len(customClaims.Google.Compute.InstanceId) > 0 {
|
||||||
loginInfo.GceMetadata = customClaims.Google.Compute
|
loginInfo.GceMetadata = customClaims.Google.Compute
|
||||||
|
@ -199,24 +200,23 @@ func (b *GcpAuthBackend) getSigningKey(ctx context.Context, token *jwt.JSONWebTo
|
||||||
|
|
||||||
switch role.RoleType {
|
switch role.RoleType {
|
||||||
case iamRoleType:
|
case iamRoleType:
|
||||||
iamClient, err := b.IAM(ctx, s)
|
clients, err := b.newGcpClients(ctx, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceAccountId, err := parseServiceAccountFromIAMJWT(rawToken)
|
serviceAccountId, err := parseServiceAccountFromIAMJWT(rawToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accountKey, err := gcputil.ServiceAccountKey(iamClient, &gcputil.ServiceAccountKeyId{
|
accountKey, err := gcputil.ServiceAccountKey(clients.iam, &gcputil.ServiceAccountKeyId{
|
||||||
Project: role.ProjectId,
|
Project: "-",
|
||||||
EmailOrId: serviceAccountId,
|
EmailOrId: serviceAccountId,
|
||||||
Key: keyId,
|
Key: keyId,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Attempt to get a normal Google Oauth cert in case of GCE inferrence.
|
// 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 {
|
if err != nil {
|
||||||
return nil, errwrap.Wrapf(
|
return nil, errwrap.Wrapf(
|
||||||
fmt.Sprintf("could not find service account key or Google Oauth cert with given 'kid' id %s: {{err}}", keyId),
|
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)
|
return gcputil.PublicKey(accountKey.PublicKeyData)
|
||||||
case gceRoleType:
|
case gceRoleType:
|
||||||
return b.getGoogleOauthCert(ctx, keyId, s)
|
return gcputil.OAuth2RSAPublicKey(keyId, "")
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unexpected role type %s", role.RoleType)
|
return nil, fmt.Errorf("unexpected role type %s", role.RoleType)
|
||||||
}
|
}
|
||||||
|
@ -245,17 +245,8 @@ func parseServiceAccountFromIAMJWT(signedJwt string) (string, error) {
|
||||||
return accountId, nil
|
return accountId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *GcpAuthBackend) getGoogleOauthCert(ctx context.Context, keyId string, s logical.Storage) (interface{}, error) {
|
func (b *GcpAuthBackend) getGoogleOauthCert(ctx context.Context, keyId string) (interface{}, error) {
|
||||||
var certsEndpoint string
|
key, err := gcputil.OAuth2RSAPublicKey(keyId, "")
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -288,9 +279,9 @@ func validateBaseJWTClaims(c *jwt.Claims, roleName string) error {
|
||||||
// ---- IAM login domain ----
|
// ---- IAM login domain ----
|
||||||
// pathIamLogin attempts a login operation using the parsed login info.
|
// 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) {
|
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 {
|
if err != nil {
|
||||||
return logical.ErrorResponse(fmt.Sprintf(clientErrorTemplate, "IAM", err)), nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
role := loginInfo.Role
|
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.
|
// Get service account and make sure it still exists.
|
||||||
accountId := &gcputil.ServiceAccountId{
|
accountId := &gcputil.ServiceAccountId{
|
||||||
Project: role.ProjectId,
|
Project: "-",
|
||||||
EmailOrId: loginInfo.ServiceAccountId,
|
EmailOrId: loginInfo.EmailOrId,
|
||||||
}
|
}
|
||||||
serviceAccount, err := gcputil.ServiceAccount(iamClient, accountId)
|
serviceAccount, err := gcputil.ServiceAccount(clients.iam, accountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
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
|
// pathIamRenew returns an error if the service account referenced in the auth token metadata cannot renew the
|
||||||
// auth token for the given role.
|
// auth token for the given role.
|
||||||
func (b *GcpAuthBackend) pathIamRenew(ctx context.Context, req *logical.Request, roleName string, role *gcpRole) error {
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf(clientErrorTemplate, "IAM", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceAccountId, ok := req.Auth.Metadata["service_account_id"]
|
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")
|
return errors.New("service account id metadata not associated with auth token, invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceAccount, err := gcputil.ServiceAccount(iamClient, &gcputil.ServiceAccountId{
|
// This project is the service account's project.
|
||||||
Project: role.ProjectId,
|
project, ok := req.Auth.Metadata["project_id"]
|
||||||
|
if !ok {
|
||||||
|
project = "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceAccount, err := gcputil.ServiceAccount(clients.iam, &gcputil.ServiceAccountId{
|
||||||
|
Project: project,
|
||||||
EmailOrId: serviceAccountId,
|
EmailOrId: serviceAccountId,
|
||||||
})
|
})
|
||||||
if err != nil {
|
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.
|
// 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 {
|
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 len(role.BoundProjects) > 0 && !strutil.StrListContains(role.BoundProjects, serviceAccount.ProjectId) {
|
||||||
if role.ProjectId != serviceAccount.ProjectId {
|
return fmt.Errorf("service account %q not in bound projects %+v", serviceAccount.Email, role.BoundProjects)
|
||||||
return fmt.Errorf("service account %s does not belong to project %s", serviceAccount.Email, role.ProjectId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if role has the wildcard as the only service account.
|
// 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
|
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(
|
return logical.ErrorResponse(fmt.Sprintf(
|
||||||
"GCE instance must belong to project %s; metadata given has project %s",
|
"instance %q (project %q) not in bound projects %+v", metadata.InstanceId, metadata.ProjectId, role.BoundProjects)), nil
|
||||||
role.ProjectId, metadata.ProjectId)), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify instance exists.
|
// Verify instance exists.
|
||||||
gceClient, err := b.GCE(ctx, req.Storage)
|
clients, err := b.newGcpClients(ctx, req.Storage)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
return logical.ErrorResponse(fmt.Sprintf(
|
return logical.ErrorResponse(fmt.Sprintf(
|
||||||
"error when attempting to find instance (project %s, zone: %s, instance: %s) :%v",
|
"error when attempting to find instance (project %s, zone: %s, instance: %s) :%v",
|
||||||
metadata.ProjectId, metadata.Zone, metadata.InstanceName, err)), nil
|
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
|
return logical.ErrorResponse(err.Error()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
iamClient, err := b.IAM(ctx, req.Storage)
|
if req.Operation == logical.AliasLookaheadOperation {
|
||||||
if err != nil {
|
return &logical.Response{
|
||||||
return logical.ErrorResponse(fmt.Sprintf(clientErrorTemplate, "IAM", err)), nil
|
Auth: &logical.Auth{
|
||||||
|
Alias: &logical.Alias{
|
||||||
|
Name: fmt.Sprintf("gce-%s", strconv.FormatUint(instance.Id, 10)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceAccount, err := gcputil.ServiceAccount(iamClient, &gcputil.ServiceAccountId{
|
serviceAccount, err := gcputil.ServiceAccount(clients.iam, &gcputil.ServiceAccountId{
|
||||||
Project: loginInfo.Role.ProjectId,
|
Project: "-",
|
||||||
EmailOrId: loginInfo.ServiceAccountId,
|
EmailOrId: loginInfo.EmailOrId,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logical.ErrorResponse(fmt.Sprintf(
|
return logical.ErrorResponse(fmt.Sprintf(
|
||||||
"Could not find service account '%s' used for GCE metadata token: %s",
|
"Could not find service account '%s' used for GCE metadata token: %s",
|
||||||
loginInfo.ServiceAccountId, err)), nil
|
loginInfo.EmailOrId, err)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := &logical.Response{
|
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
|
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 {
|
func authMetadata(loginInfo *gcpLoginInfo, serviceAccount *iam.ServiceAccount) map[string]string {
|
||||||
metadata := map[string]string{
|
metadata := map[string]string{
|
||||||
"role": loginInfo.RoleName,
|
"role": loginInfo.RoleName,
|
||||||
"service_account_id": serviceAccount.UniqueId,
|
"service_account_id": serviceAccount.UniqueId,
|
||||||
"service_account_email": serviceAccount.Email,
|
"service_account_email": serviceAccount.Email,
|
||||||
|
"project_id": serviceAccount.ProjectId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if loginInfo.GceMetadata != nil {
|
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
|
// pathGceRenew returns an error if the instance referenced in the auth token metadata cannot renew the
|
||||||
// auth token for the given role.
|
// auth token for the given role.
|
||||||
func (b *GcpAuthBackend) pathGceRenew(ctx context.Context, req *logical.Request, roleName string, role *gcpRole) error {
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf(clientErrorTemplate, "GCE", err)
|
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
|
// authorizeGCEInstance returns an error if the given GCE instance is not
|
||||||
// authorized for the role.
|
// authorized for the role.
|
||||||
func (b *GcpAuthBackend) authorizeGCEInstance(ctx context.Context, instance *compute.Instance, s logical.Storage, role *gcpRole, serviceAccountId string) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
iamSvc, err := b.IAM(ctx, s)
|
iamClient, err := iam.New(httpC)
|
||||||
if err != nil {
|
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{
|
return AuthorizeGCE(ctx, &AuthorizeGCEInput{
|
||||||
client: &gcpClient{
|
client: &gcpClient{
|
||||||
computeSvc: computeSvc,
|
computeSvc: gceClient,
|
||||||
iamSvc: iamSvc,
|
iamSvc: iamClient,
|
||||||
},
|
},
|
||||||
|
|
||||||
project: role.ProjectId,
|
|
||||||
serviceAccount: serviceAccountId,
|
serviceAccount: serviceAccountId,
|
||||||
|
|
||||||
instanceLabels: instance.Labels,
|
instanceLabels: instance.Labels,
|
||||||
|
|
|
@ -23,7 +23,6 @@ const (
|
||||||
// Errors
|
// Errors
|
||||||
errEmptyRoleName = "role name is required"
|
errEmptyRoleName = "role name is required"
|
||||||
errEmptyRoleType = "role type cannot be empty"
|
errEmptyRoleType = "role type cannot be empty"
|
||||||
errEmptyProjectId = "project id cannot be empty"
|
|
||||||
errEmptyIamServiceAccounts = "IAM role type must have at least one service account"
|
errEmptyIamServiceAccounts = "IAM role type must have at least one service account"
|
||||||
|
|
||||||
errTemplateEditListWrongType = "role is type '%s', cannot edit attribute '%s' (expected role type: '%s')"
|
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.`,
|
duration specified by this value. At each renewal, the token's TTL will be set to the value of this parameter.`,
|
||||||
},
|
},
|
||||||
// -- GCP Information
|
// -- GCP Information
|
||||||
"project_id": {
|
"bound_projects": {
|
||||||
Type: framework.TypeString,
|
Type: framework.TypeCommaStringSlice,
|
||||||
Description: `The id of the project that authorized instances must belong to for this role.`,
|
Description: `GCP Projects that authenticating entities must belong to.`,
|
||||||
},
|
},
|
||||||
"bound_service_accounts": {
|
"bound_service_accounts": {
|
||||||
Type: framework.TypeCommaStringSlice,
|
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
|
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.`,
|
is set on a GCE role, the inferred service account from the instance metadata token will be used.`,
|
||||||
},
|
},
|
||||||
"service_accounts": {
|
"add_group_aliases": {
|
||||||
Type: framework.TypeCommaStringSlice,
|
Type: framework.TypeBool,
|
||||||
Description: `Deprecated, use bound_service_accounts instead.`,
|
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 " +
|
"\"key:value\" strings that must be present on the GCE instance " +
|
||||||
"in order to authenticate. This option only applies to \"gce\" roles.",
|
"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.
|
// pathsRole creates paths for listing roles and CRUD operations.
|
||||||
|
@ -160,6 +149,9 @@ func pathsRole(b *GcpAuthBackend) []*framework.Path {
|
||||||
for k, v := range gceOnlyFieldSchema {
|
for k, v := range gceOnlyFieldSchema {
|
||||||
roleFieldSchema[k] = v
|
roleFieldSchema[k] = v
|
||||||
}
|
}
|
||||||
|
for k, v := range deprecatedFieldSchema {
|
||||||
|
roleFieldSchema[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
paths := []*framework.Path{
|
paths := []*framework.Path{
|
||||||
{
|
{
|
||||||
|
@ -291,9 +283,6 @@ func (b *GcpAuthBackend) pathRoleRead(ctx context.Context, req *logical.Request,
|
||||||
if role.RoleType != "" {
|
if role.RoleType != "" {
|
||||||
resp["type"] = role.RoleType
|
resp["type"] = role.RoleType
|
||||||
}
|
}
|
||||||
if role.ProjectId != "" {
|
|
||||||
resp["project_id"] = role.ProjectId
|
|
||||||
}
|
|
||||||
if len(role.Policies) > 0 {
|
if len(role.Policies) > 0 {
|
||||||
resp["policies"] = role.Policies
|
resp["policies"] = role.Policies
|
||||||
}
|
}
|
||||||
|
@ -309,6 +298,10 @@ func (b *GcpAuthBackend) pathRoleRead(ctx context.Context, req *logical.Request,
|
||||||
if len(role.BoundServiceAccounts) > 0 {
|
if len(role.BoundServiceAccounts) > 0 {
|
||||||
resp["bound_service_accounts"] = role.BoundServiceAccounts
|
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 {
|
switch role.RoleType {
|
||||||
case iamRoleType:
|
case iamRoleType:
|
||||||
|
@ -509,6 +502,12 @@ func (b *GcpAuthBackend) role(ctx context.Context, s logical.Storage, name strin
|
||||||
modified := false
|
modified := false
|
||||||
|
|
||||||
// Move old bindings to new fields.
|
// 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 {
|
if role.BoundRegion != "" && len(role.BoundRegions) == 0 {
|
||||||
role.BoundRegions = []string{role.BoundRegion}
|
role.BoundRegions = []string{role.BoundRegion}
|
||||||
role.BoundRegion = ""
|
role.BoundRegion = ""
|
||||||
|
@ -576,9 +575,6 @@ type gcpRole struct {
|
||||||
// Type of this role. See path_role constants for currently supported types.
|
// Type of this role. See path_role constants for currently supported types.
|
||||||
RoleType string `json:"role_type,omitempty"`
|
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 for Vault to assign to authorized entities.
|
||||||
Policies []string `json:"policies,omitempty"`
|
Policies []string `json:"policies,omitempty"`
|
||||||
|
|
||||||
|
@ -593,6 +589,9 @@ type gcpRole struct {
|
||||||
// with TTL equal to this value.
|
// with TTL equal to this value.
|
||||||
Period time.Duration `json:"period,omitempty"`
|
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.
|
// Service accounts allowed to login under this role.
|
||||||
BoundServiceAccounts []string `json:"bound_service_accounts,omitempty"`
|
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 that instances must currently have set in order to login under this role.
|
||||||
BoundLabels map[string]string `json:"bound_labels,omitempty"`
|
BoundLabels map[string]string `json:"bound_labels,omitempty"`
|
||||||
|
|
||||||
|
AddGroupAliases bool `json:"add_group_aliases,omitempty"`
|
||||||
|
|
||||||
// Deprecated fields
|
// Deprecated fields
|
||||||
// TODO: Remove in 0.5.0+
|
// TODO: Remove in 0.5.0+
|
||||||
|
ProjectId string `json:"project_id,omitempty"`
|
||||||
BoundRegion string `json:"bound_region,omitempty"`
|
BoundRegion string `json:"bound_region,omitempty"`
|
||||||
BoundZone string `json:"bound_zone,omitempty"`
|
BoundZone string `json:"bound_zone,omitempty"`
|
||||||
BoundInstanceGroup string `json:"bound_instance_group,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)
|
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.
|
// Update token TTL.
|
||||||
if ttl, ok := data.GetOk("ttl"); ok {
|
if ttl, ok := data.GetOk("ttl"); ok {
|
||||||
role.TTL = time.Duration(ttl.(int)) * time.Second
|
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)
|
role.BoundServiceAccounts = sa.([]string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(role.BoundServiceAccounts) > 0 {
|
if len(role.BoundServiceAccounts) > 0 {
|
||||||
role.BoundServiceAccounts = strutil.TrimStrings(role.BoundServiceAccounts)
|
role.BoundServiceAccounts = strutil.TrimStrings(role.BoundServiceAccounts)
|
||||||
role.BoundServiceAccounts = strutil.RemoveDuplicates(role.BoundServiceAccounts, false)
|
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
|
// Update fields specific to this type
|
||||||
switch role.RoleType {
|
switch role.RoleType {
|
||||||
case iamRoleType:
|
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)
|
return warnings, fmt.Errorf("role type '%s' is invalid", role.RoleType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if role.ProjectId == "" {
|
|
||||||
return warnings, errors.New(errEmptyProjectId)
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultLeaseTTL := sys.DefaultLeaseTTL()
|
defaultLeaseTTL := sys.DefaultLeaseTTL()
|
||||||
if role.TTL > defaultLeaseTTL {
|
if role.TTL > defaultLeaseTTL {
|
||||||
warnings = append(warnings, fmt.Sprintf(
|
warnings = append(warnings, fmt.Sprintf(
|
||||||
|
@ -960,3 +976,27 @@ func checkInvalidRoleTypeArgs(data *framework.FieldData, invalidSchema map[strin
|
||||||
}
|
}
|
||||||
return nil
|
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.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
2054
vendor/google.golang.org/api/cloudresourcemanager/v1/cloudresourcemanager-api.json
generated
vendored
Normal file
2054
vendor/google.golang.org/api/cloudresourcemanager/v1/cloudresourcemanager-api.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
8294
vendor/google.golang.org/api/cloudresourcemanager/v1/cloudresourcemanager-gen.go
generated
vendored
Normal file
8294
vendor/google.golang.org/api/cloudresourcemanager/v1/cloudresourcemanager-gen.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1425,10 +1425,10 @@
|
||||||
"revisionTime": "2018-08-16T20:11:31Z"
|
"revisionTime": "2018-08-16T20:11:31Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "pJC3V+AR+Khd/TpvekLf1ZRU7rA=",
|
"checksumSHA1": "h7m+nCb1vdvcy5cUkCarix08yJI=",
|
||||||
"path": "github.com/hashicorp/vault-plugin-auth-gcp/plugin",
|
"path": "github.com/hashicorp/vault-plugin-auth-gcp/plugin",
|
||||||
"revision": "86f7837fd8102347f7a10e3f0ad275e5a256863c",
|
"revision": "0a3edf071747fcb84aa4f9d08a34d2c249e6228d",
|
||||||
"revisionTime": "2018-10-25T21:28:40Z"
|
"revisionTime": "2018-11-15T01:46:55Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "tt3FtyjXgdBI9Mb43UL4LtOZmAk=",
|
"checksumSHA1": "tt3FtyjXgdBI9Mb43UL4LtOZmAk=",
|
||||||
|
@ -2808,6 +2808,12 @@
|
||||||
"revision": "cc9bd73d51b4c5610e35bcb01f15368185cfaab3",
|
"revision": "cc9bd73d51b4c5610e35bcb01f15368185cfaab3",
|
||||||
"revisionTime": "2018-10-16T15:55:58Z"
|
"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=",
|
"checksumSHA1": "QkzppExyctFz029dvXzKzh6UHgU=",
|
||||||
"path": "google.golang.org/api/compute/v1",
|
"path": "google.golang.org/api/compute/v1",
|
||||||
|
|
Loading…
Reference in New Issue