mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 03:27:54 +00:00
Add GCP auth helper (#4654)
* update auth plugin vendoring * add GCP auth helper and docs
This commit is contained in:
@@ -201,6 +201,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
"aws": &credAws.CLIHandler{},
|
||||
"centrify": &credCentrify.CLIHandler{},
|
||||
"cert": &credCert.CLIHandler{},
|
||||
"gcp": &credGcp.CLIHandler{},
|
||||
"github": &credGitHub.CLIHandler{},
|
||||
"ldap": &credLdap.CLIHandler{},
|
||||
"okta": &credOkta.CLIHandler{},
|
||||
|
||||
13
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/backend.go
generated
vendored
13
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/backend.go
generated
vendored
@@ -7,13 +7,11 @@ import (
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/vault-plugin-auth-gcp/plugin/util"
|
||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||
"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/compute/v1"
|
||||
"google.golang.org/api/iam/v1"
|
||||
)
|
||||
@@ -143,15 +141,14 @@ func (b *GcpAuthBackend) initClients(ctx context.Context, s logical.Storage) (er
|
||||
|
||||
var httpClient *http.Client
|
||||
if config == nil || config.Credentials == nil {
|
||||
// Use Application Default Credentials
|
||||
reqCtx := context.WithValue(ctx, oauth2.HTTPClient, cleanhttp.DefaultClient())
|
||||
|
||||
httpClient, err = google.DefaultClient(reqCtx, b.oauthScopes...)
|
||||
_, tknSrc, err := gcputil.FindCredentials("", ctx, b.oauthScopes...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("credentials were not configured and fallback to application default credentials failed: %v", err)
|
||||
}
|
||||
|
||||
httpClient = oauth2.NewClient(ctx, tknSrc)
|
||||
} else {
|
||||
httpClient, err = util.GetHttpClient(config.Credentials, b.oauthScopes...)
|
||||
httpClient, err = gcputil.GetHttpClient(config.Credentials, b.oauthScopes...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
172
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/cli.go
generated
vendored
Normal file
172
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/cli.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
package gcpauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/api/iam/v1"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CLIHandler struct{}
|
||||
|
||||
func getSignedJwt(role string, m map[string]string) (string, error) {
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, cleanhttp.DefaultClient())
|
||||
|
||||
credentials, tokenSource, err := gcputil.FindCredentials(m["credentials"], ctx, iam.CloudPlatformScope)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not obtain credentials: %v", err)
|
||||
}
|
||||
|
||||
httpClient := oauth2.NewClient(ctx, tokenSource)
|
||||
|
||||
serviceAccount, ok := m["service_account"]
|
||||
if !ok && credentials != nil {
|
||||
serviceAccount = credentials.ClientEmail
|
||||
}
|
||||
if serviceAccount == "" {
|
||||
return "", errors.New("could not obtain service account from credentials (are you using Application Default Credentials?). You must provide a service account to authenticate as")
|
||||
}
|
||||
|
||||
project, ok := m["project"]
|
||||
if !ok {
|
||||
if credentials != nil {
|
||||
project = credentials.ProjectId
|
||||
} else {
|
||||
project = "-"
|
||||
}
|
||||
}
|
||||
|
||||
var ttlMin = int64(defaultIamMaxJwtExpMinutes)
|
||||
jwtExpStr, ok := m["jwt_exp"]
|
||||
if ok {
|
||||
ttlMin, err = strconv.ParseInt(jwtExpStr, 10, 64)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not parse jwt_exp '%s' into integer value", jwtExpStr)
|
||||
}
|
||||
}
|
||||
ttl := time.Minute * time.Duration(ttlMin)
|
||||
|
||||
jwtPayload := map[string]interface{}{
|
||||
"aud": fmt.Sprintf("http://vault/%s", role),
|
||||
"sub": serviceAccount,
|
||||
"exp": time.Now().Add(ttl).Unix(),
|
||||
}
|
||||
payloadBytes, err := json.Marshal(jwtPayload)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not convert JWT payload to JSON string: %v", err)
|
||||
}
|
||||
|
||||
jwtReq := &iam.SignJwtRequest{
|
||||
Payload: string(payloadBytes),
|
||||
}
|
||||
|
||||
iamClient, err := iam.New(httpClient)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not create IAM client: %v", err)
|
||||
}
|
||||
|
||||
resourceName := fmt.Sprintf("projects/%s/serviceAccounts/%s", project, serviceAccount)
|
||||
resp, err := iamClient.Projects.ServiceAccounts.SignJwt(resourceName, jwtReq).Do()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to sign JWT for %s using given Vault credentials: %v", resourceName, err)
|
||||
}
|
||||
|
||||
return resp.SignedJwt, nil
|
||||
}
|
||||
|
||||
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, error) {
|
||||
role, ok := m["role"]
|
||||
if !ok {
|
||||
return nil, errors.New("role is required")
|
||||
}
|
||||
|
||||
mount, ok := m["mount"]
|
||||
if !ok {
|
||||
mount = "gcp"
|
||||
}
|
||||
|
||||
loginToken, err := getSignedJwt(role, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("auth/%s/login", mount)
|
||||
secret, err := c.Logical().Write(
|
||||
path,
|
||||
map[string]interface{}{
|
||||
"role": role,
|
||||
"jwt": loginToken,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, fmt.Errorf("empty response from credential provider")
|
||||
}
|
||||
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func (h *CLIHandler) Help() string {
|
||||
help := `
|
||||
Usage: vault login -method=gcp [CONFIG K=V...]
|
||||
|
||||
The GCP credential provider allows you to authenticate with
|
||||
a GCP IAM service account key. To use it, you provide a valid GCP IAM
|
||||
credential JSON either explicitly on the command line (not recommended),
|
||||
or through any GCP Application Default Credentials.
|
||||
|
||||
Authenticate using Application Default Credentials:
|
||||
|
||||
Example: vault login -method=gcp role=my-iam-role
|
||||
|
||||
Authenticate using explicitly passed-in credentials:
|
||||
|
||||
Example:
|
||||
vault login -method=gcp role=my-iam-role -credentials=@path/to/creds role=my-iam-role
|
||||
|
||||
This tool generates a signed JWT signed using the given credentials.
|
||||
|
||||
Configuration:
|
||||
|
||||
role
|
||||
Required. The name of the role you're requesting a token for.
|
||||
|
||||
mount=gcp
|
||||
This is usually provided via the -path flag in the "vault login" command,
|
||||
but it can be specified here as well. If specified here, it takes
|
||||
precedence over the value for -path.
|
||||
|
||||
credentials=<string>
|
||||
Explicitly specified GCP credentials in JSON string format (not recommended)
|
||||
|
||||
jwt_exp=<minutes>
|
||||
Time until the generated JWT expires in minutes.
|
||||
The given IAM role will have a max_jwt_exp field, the
|
||||
time in minutes that all valid authentication JWTs
|
||||
must expire within (from time of authentication).
|
||||
Defaults to 15 minutes, the default max_jwt_exp for a role.
|
||||
Must be less than an hour.
|
||||
|
||||
service_account=<string>
|
||||
Service account to generate a JWT for. Defaults to credentials
|
||||
"client_email" if "credentials" specified and this value is not.
|
||||
The actual credential must have the "iam.serviceAccounts.signJWT"
|
||||
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.
|
||||
`
|
||||
|
||||
return strings.TrimSpace(help)
|
||||
}
|
||||
27
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/login_util.go
generated
vendored
Normal file
27
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/login_util.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package gcpauth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||
"google.golang.org/api/iam/v1"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ServiceAccountLoginJwt(
|
||||
iamClient *iam.Service, exp time.Time, aud, project, serviceAccount string) (*iam.SignJwtResponse, error) {
|
||||
accountResource := fmt.Sprintf(gcputil.ServiceAccountTemplate, project, serviceAccount)
|
||||
|
||||
payload, err := json.Marshal(map[string]interface{}{
|
||||
"sub": serviceAccount,
|
||||
"aud": aud,
|
||||
"exp": exp.Unix(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jwtReq := &iam.SignJwtRequest{
|
||||
Payload: string(payload),
|
||||
}
|
||||
return iamClient.Projects.ServiceAccounts.SignJwt(accountResource, jwtReq).Do()
|
||||
}
|
||||
6
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/path_config.go
generated
vendored
6
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/path_config.go
generated
vendored
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault-plugin-auth-gcp/plugin/util"
|
||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
@@ -100,7 +100,7 @@ iam AUTH:
|
||||
|
||||
// gcpConfig contains all config required for the GCP backend.
|
||||
type gcpConfig struct {
|
||||
Credentials *util.GcpCredentials `json:"credentials" structs:"credentials" mapstructure:"credentials"`
|
||||
Credentials *gcputil.GcpCredentials `json:"credentials" structs:"credentials" mapstructure:"credentials"`
|
||||
GoogleCertsEndpoint string `json:"google_certs_endpoint" structs:"google_certs_endpoint" mapstructure:"google_certs_endpoint"`
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ type gcpConfig struct {
|
||||
func (config *gcpConfig) Update(data *framework.FieldData) error {
|
||||
credentialsJson := data.Get("credentials").(string)
|
||||
if credentialsJson != "" {
|
||||
creds, err := util.Credentials(credentialsJson)
|
||||
creds, err := gcputil.Credentials(credentialsJson)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading google credentials from given JSON: %v", err)
|
||||
}
|
||||
|
||||
132
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/path_login.go
generated
vendored
132
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/path_login.go
generated
vendored
@@ -8,7 +8,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault-plugin-auth-gcp/plugin/util"
|
||||
"github.com/SermoDigital/jose/jws"
|
||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||
"github.com/hashicorp/vault/helper/policyutil"
|
||||
"github.com/hashicorp/vault/helper/strutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
@@ -117,7 +118,7 @@ type gcpLoginInfo struct {
|
||||
JWTClaims *jwt.Claims
|
||||
|
||||
// Metadata from a GCE instance identity token.
|
||||
GceMetadata *util.GCEIdentityMetadata
|
||||
GceMetadata *gcputil.GCEIdentityMetadata
|
||||
}
|
||||
|
||||
func (b *GcpAuthBackend) parseAndValidateJwt(ctx context.Context, req *logical.Request, data *framework.FieldData) (*gcpLoginInfo, error) {
|
||||
@@ -156,7 +157,7 @@ func (b *GcpAuthBackend) parseAndValidateJwt(ctx context.Context, req *logical.R
|
||||
|
||||
// Parse claims and verify signature.
|
||||
baseClaims := &jwt.Claims{}
|
||||
customClaims := &util.CustomJWTClaims{}
|
||||
customClaims := &gcputil.CustomJWTClaims{}
|
||||
|
||||
if err = jwtVal.Claims(key, baseClaims, customClaims); err != nil {
|
||||
return nil, err
|
||||
@@ -197,12 +198,16 @@ func (b *GcpAuthBackend) getSigningKey(ctx context.Context, token *jwt.JSONWebTo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serviceAccountId, err := util.ParseServiceAccountFromIAMJWT(rawToken)
|
||||
serviceAccountId, err := parseServiceAccountFromIAMJWT(rawToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accountKey, err := util.ServiceAccountKey(iamClient, keyId, serviceAccountId, role.ProjectId)
|
||||
accountKey, err := gcputil.ServiceAccountKey(iamClient, &gcputil.ServiceAccountKeyId{
|
||||
Project: role.ProjectId,
|
||||
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)
|
||||
@@ -211,7 +216,7 @@ func (b *GcpAuthBackend) getSigningKey(ctx context.Context, token *jwt.JSONWebTo
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
return util.PublicKey(accountKey.PublicKeyData)
|
||||
return gcputil.PublicKey(accountKey.PublicKeyData)
|
||||
case gceRoleType:
|
||||
return b.getGoogleOauthCert(ctx, keyId, s)
|
||||
default:
|
||||
@@ -219,6 +224,19 @@ func (b *GcpAuthBackend) getSigningKey(ctx context.Context, token *jwt.JSONWebTo
|
||||
}
|
||||
}
|
||||
|
||||
// ParseServiceAccountFromIAMJWT parses the service account from the 'sub' claim given a serialized signed JWT.
|
||||
func parseServiceAccountFromIAMJWT(signedJwt string) (string, error) {
|
||||
jwtVal, err := jws.ParseJWT([]byte(signedJwt))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not parse service account from JWT 'sub' claim: %v", err)
|
||||
}
|
||||
accountId, ok := jwtVal.Claims().Subject()
|
||||
if !ok {
|
||||
return "", errors.New("expected 'sub' claim with service account ID or name")
|
||||
}
|
||||
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)
|
||||
@@ -229,7 +247,7 @@ func (b *GcpAuthBackend) getGoogleOauthCert(ctx context.Context, keyId string, s
|
||||
certsEndpoint = conf.GoogleCertsEndpoint
|
||||
}
|
||||
|
||||
key, err := util.OAuth2RSAPublicKey(keyId, certsEndpoint)
|
||||
key, err := gcputil.OAuth2RSAPublicKey(keyId, certsEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -279,7 +297,11 @@ func (b *GcpAuthBackend) pathIamLogin(ctx context.Context, req *logical.Request,
|
||||
}
|
||||
|
||||
// Get service account and make sure it still exists.
|
||||
serviceAccount, err := util.ServiceAccount(iamClient, loginInfo.ServiceAccountId, role.ProjectId)
|
||||
accountId := &gcputil.ServiceAccountId{
|
||||
Project: role.ProjectId,
|
||||
EmailOrId: loginInfo.ServiceAccountId,
|
||||
}
|
||||
serviceAccount, err := gcputil.ServiceAccount(iamClient, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -335,7 +357,10 @@ 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 := util.ServiceAccount(iamClient, serviceAccountId, role.ProjectId)
|
||||
serviceAccount, err := gcputil.ServiceAccount(iamClient, &gcputil.ServiceAccountId{
|
||||
Project: role.ProjectId,
|
||||
EmailOrId: serviceAccountId,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot find service account %s", serviceAccountId)
|
||||
}
|
||||
@@ -411,7 +436,10 @@ func (b *GcpAuthBackend) pathGceLogin(ctx context.Context, req *logical.Request,
|
||||
return logical.ErrorResponse(fmt.Sprintf(clientErrorTemplate, "IAM", err)), nil
|
||||
}
|
||||
|
||||
serviceAccount, err := util.ServiceAccount(iamClient, loginInfo.ServiceAccountId, loginInfo.Role.ProjectId)
|
||||
serviceAccount, err := gcputil.ServiceAccount(iamClient, &gcputil.ServiceAccountId{
|
||||
Project: loginInfo.Role.ProjectId,
|
||||
EmailOrId: loginInfo.ServiceAccountId,
|
||||
})
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf(
|
||||
"Could not find service account '%s' used for GCE metadata token: %s",
|
||||
@@ -466,7 +494,7 @@ func (b *GcpAuthBackend) pathGceRenew(ctx context.Context, req *logical.Request,
|
||||
return fmt.Errorf(clientErrorTemplate, "GCE", err)
|
||||
}
|
||||
|
||||
meta, err := util.GetInstanceMetadataFromAuth(req.Auth.Metadata)
|
||||
meta, err := getInstanceMetadataFromAuth(req.Auth.Metadata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid auth metadata: %v", err)
|
||||
}
|
||||
@@ -487,6 +515,53 @@ func (b *GcpAuthBackend) pathGceRenew(ctx context.Context, req *logical.Request,
|
||||
return nil
|
||||
}
|
||||
|
||||
func getInstanceMetadataFromAuth(authMetadata map[string]string) (*gcputil.GCEIdentityMetadata, error) {
|
||||
meta := &gcputil.GCEIdentityMetadata{}
|
||||
var ok bool
|
||||
var err error
|
||||
|
||||
meta.ProjectId, ok = authMetadata["project_id"]
|
||||
if !ok {
|
||||
return nil, errors.New("expected 'project_id' field")
|
||||
}
|
||||
|
||||
meta.Zone, ok = authMetadata["zone"]
|
||||
if !ok {
|
||||
return nil, errors.New("expected 'zone' field")
|
||||
}
|
||||
|
||||
meta.InstanceId, ok = authMetadata["instance_id"]
|
||||
if !ok {
|
||||
return nil, errors.New("expected 'instance_id' field")
|
||||
}
|
||||
|
||||
meta.InstanceName, ok = authMetadata["instance_name"]
|
||||
if !ok {
|
||||
return nil, errors.New("expected 'instance_name' field")
|
||||
}
|
||||
|
||||
// Parse numbers back into int values.
|
||||
projectNumber, ok := authMetadata["project_number"]
|
||||
if !ok {
|
||||
return nil, errors.New("expected 'project_number' field, got %v")
|
||||
}
|
||||
meta.ProjectNumber, err = strconv.ParseInt(projectNumber, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("expected 'project_number' value '%s' to be a int64", projectNumber)
|
||||
}
|
||||
|
||||
createdAt, ok := authMetadata["instance_creation_timestamp"]
|
||||
if !ok {
|
||||
return nil, errors.New("expected 'instance_creation_timestamp' field")
|
||||
}
|
||||
meta.CreatedAt, err = strconv.ParseInt(createdAt, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("expected 'instance_creation_timestamp' value '%s' to be int64", createdAt)
|
||||
}
|
||||
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
// validateGCEInstance 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, zone, serviceAccountId string) error {
|
||||
gceClient, err := b.GCE(ctx, s)
|
||||
@@ -504,27 +579,16 @@ func (b *GcpAuthBackend) authorizeGCEInstance(ctx context.Context, instance *com
|
||||
|
||||
// Verify that instance is in zone or region if given.
|
||||
if len(role.BoundZone) > 0 {
|
||||
var zone string
|
||||
idx := strings.LastIndex(instance.Zone, "zones/")
|
||||
if idx > 0 {
|
||||
// Parse zone name from full zone self-link URL.
|
||||
idx += len("zones/")
|
||||
zone = instance.Zone[idx:len(instance.Zone)]
|
||||
} else {
|
||||
// Expect full zone name to be set as instance zone.
|
||||
zone = instance.Zone
|
||||
}
|
||||
|
||||
if zone != role.BoundZone {
|
||||
return fmt.Errorf("instance is not in role zone '%s'", role.BoundZone)
|
||||
if !compareResourceNameOrSelfLink(role.BoundZone, instance.Zone, "zones") {
|
||||
return fmt.Errorf("instance zone %s is not role zone '%s'", instance.Zone, role.BoundZone)
|
||||
}
|
||||
} else if len(role.BoundRegion) > 0 {
|
||||
zone, err := gceClient.Zones.Get(role.ProjectId, zone).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not verify instance zone '%s' is available for project '%s': %v", zone.Name, role.ProjectId, err)
|
||||
}
|
||||
if zone.Region != role.BoundRegion {
|
||||
return fmt.Errorf("zone '%s' is not in region '%s'", zone.Name, zone.Region)
|
||||
if !compareResourceNameOrSelfLink(role.BoundRegion, zone.Region, "regions") {
|
||||
return fmt.Errorf("instance zone %s is not in role region '%s'", zone.Name, role.BoundRegion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,7 +633,10 @@ func (b *GcpAuthBackend) authorizeGCEInstance(ctx context.Context, instance *com
|
||||
return err
|
||||
}
|
||||
|
||||
serviceAccount, err := util.ServiceAccount(iamClient, serviceAccountId, role.ProjectId)
|
||||
serviceAccount, err := gcputil.ServiceAccount(iamClient, &gcputil.ServiceAccountId{
|
||||
Project: role.ProjectId,
|
||||
EmailOrId: serviceAccountId,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not find service account with id '%s': %v", serviceAccountId, err)
|
||||
}
|
||||
@@ -584,6 +651,17 @@ func (b *GcpAuthBackend) authorizeGCEInstance(ctx context.Context, instance *com
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareResourceNameOrSelfLink(expected, actual, collectionId string) bool {
|
||||
sep := fmt.Sprintf("%s/", collectionId)
|
||||
if strings.Contains(expected, sep) {
|
||||
return expected == actual
|
||||
}
|
||||
|
||||
actTkns := strings.SplitAfter(actual, sep)
|
||||
actualName := actTkns[len(actTkns)-1]
|
||||
return expected == actualName
|
||||
}
|
||||
|
||||
const pathLoginHelpSyn = `Authenticates Google Cloud Platform entities with Vault.`
|
||||
const pathLoginHelpDesc = `
|
||||
Authenticate Google Cloud Platform (GCP) entities.
|
||||
|
||||
17
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/path_role.go
generated
vendored
17
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/path_role.go
generated
vendored
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault-plugin-auth-gcp/plugin/util"
|
||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||
"github.com/hashicorp/vault/helper/policyutil"
|
||||
"github.com/hashicorp/vault/helper/strutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
@@ -107,13 +107,14 @@ var gceOnlyFieldSchema map[string]*framework.FieldSchema = map[string]*framework
|
||||
Type: framework.TypeString,
|
||||
Description: `
|
||||
"gce" roles only. If set, determines the zone that a GCE instance must belong to. If a group is provided, it is assumed
|
||||
to be a zonal group and the group must belong to this zone.`,
|
||||
to be a zonal group and the group must belong to this zone. Accepts self-link or zone name.`,
|
||||
},
|
||||
"bound_region": {
|
||||
Type: framework.TypeString,
|
||||
Description: `
|
||||
"gce" roles only. If set, determines the region that a GCE instance must belong to. If a group is provided, it is
|
||||
assumed to be a regional group and the group must belong to this region. If zone is provided, region will be ignored`,
|
||||
assumed to be a regional group and the group must belong to this region. If zone is provided, region will be ignored.
|
||||
Either self-link or region name are accepted.`,
|
||||
},
|
||||
"bound_instance_group": {
|
||||
Type: framework.TypeString,
|
||||
@@ -401,7 +402,7 @@ func (b *GcpAuthBackend) pathRoleEditGceLabels(ctx context.Context, req *logical
|
||||
return logical.ErrorResponse(fmt.Sprintf(errTemplateEditListWrongType, role.RoleType, "labels", gceRoleType)), nil
|
||||
}
|
||||
|
||||
labelsToAdd, invalidLabels := util.ParseGcpLabels(toAdd)
|
||||
labelsToAdd, invalidLabels := gcputil.ParseGcpLabels(toAdd)
|
||||
if len(invalidLabels) > 0 {
|
||||
return logical.ErrorResponse(fmt.Sprintf("given invalid labels to add: %q", invalidLabels)), nil
|
||||
}
|
||||
@@ -669,23 +670,23 @@ func (role *gcpRole) updateIamFields(data *framework.FieldData, op logical.Opera
|
||||
func (role *gcpRole) updateGceFields(data *framework.FieldData, op logical.Operation) error {
|
||||
region, hasRegion := data.GetOk("bound_region")
|
||||
if hasRegion {
|
||||
role.BoundRegion = region.(string)
|
||||
role.BoundRegion = strings.TrimSpace(region.(string))
|
||||
}
|
||||
|
||||
zone, hasZone := data.GetOk("bound_zone")
|
||||
if hasZone {
|
||||
role.BoundZone = zone.(string)
|
||||
role.BoundZone = strings.TrimSpace(zone.(string))
|
||||
}
|
||||
|
||||
instanceGroup, ok := data.GetOk("bound_instance_group")
|
||||
if ok {
|
||||
role.BoundInstanceGroup = instanceGroup.(string)
|
||||
role.BoundInstanceGroup = strings.TrimSpace(instanceGroup.(string))
|
||||
}
|
||||
|
||||
labels, ok := data.GetOk("bound_labels")
|
||||
if ok {
|
||||
var invalidLabels []string
|
||||
role.BoundLabels, invalidLabels = util.ParseGcpLabels(labels.([]string))
|
||||
role.BoundLabels, invalidLabels = gcputil.ParseGcpLabels(labels.([]string))
|
||||
if len(invalidLabels) > 0 {
|
||||
return fmt.Errorf("invalid labels given: %q", invalidLabels)
|
||||
}
|
||||
|
||||
6
vendor/vendor.json
vendored
6
vendor/vendor.json
vendored
@@ -1303,10 +1303,10 @@
|
||||
"revisionTime": "2018-04-03T19:19:30Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "klHF0u2D5rTw60ZcNufHHtO8nSk=",
|
||||
"checksumSHA1": "CidzvD2Z5a68j+AfUatPOUlHZT4=",
|
||||
"path": "github.com/hashicorp/vault-plugin-auth-gcp/plugin",
|
||||
"revision": "c1f38c311636440ff37e1f655f9722d3d9c1c0cc",
|
||||
"revisionTime": "2018-04-08T01:06:05Z"
|
||||
"revision": "c6d50df183e9848729b204364e1436e85bf7fb26",
|
||||
"revisionTime": "2018-05-29T23:55:28Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "ffJQvzbQvmCG/PdaElGSfGnDgNM=",
|
||||
|
||||
@@ -19,181 +19,143 @@ Currently supports authentication for:
|
||||
* GCP IAM service accounts (`iam`)
|
||||
* GCE IAM service accounts (`gce`)
|
||||
|
||||
## Precursors:
|
||||
## Quick Links
|
||||
* [API documentation](/api/auth/gcp/index.html)
|
||||
* [Usage: Setup](#setup): How to set up the auth backend on the server
|
||||
* [Usage: Authentication](#authentication): How to authenticate to Vault using this backend
|
||||
* [Workflows](#authentication-workflow): Authentication and authorization workflows in more detail
|
||||
|
||||
## Setup
|
||||
|
||||
The following documentation assumes that the method has been
|
||||
[mounted](/docs/plugin/index.html) at `auth/gcp`.
|
||||
|
||||
```sh
|
||||
$ vault auth enable 'gcp'
|
||||
Success! Enabled gcp auth method at: gcp/
|
||||
```
|
||||
|
||||
You must also [enable the following GCP APIs](https://support.google.com/cloud/answer/6158841?hl=en)
|
||||
for your GCP project:
|
||||
|
||||
* IAM API for both `iam` service accounts and `gce` instances
|
||||
* GCE API for just `gce` instances
|
||||
|
||||
The next sections review how the authN/Z workflows work. If you
|
||||
have already reviewed these sections, here are some quick links to:
|
||||
There are generally two setup steps:
|
||||
|
||||
* [Usage](#usage)
|
||||
* [API documentation](/api/auth/gcp/index.html) docs.
|
||||
### 1. Config
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
$ vault write auth/gcp/config credentials=@/path/to/creds.json
|
||||
```
|
||||
|
||||
This step allows you to explicitly set GCP credentials that Vault (this auth backend) uses.
|
||||
If credentials are not configured or if the user explicitly sets the config with no credentials,
|
||||
the Vault server will attempt to infer credentials. In order of preference, the auth method will use
|
||||
the first credentials it finds from the following:
|
||||
|
||||
* Provided JSON as `credentials` arg
|
||||
* Environment variables `GOOGLE_CREDENTIALS` or `GOOGLE_CLOUD_KEYFILE_JSON` set to the credentials JSON string
|
||||
* JSON file `~/.gcp/credentials`
|
||||
* [Google Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials)
|
||||
|
||||
For the complete list of configuration options, see the [API documentation](/api/auth/gcp/index.html#configure).
|
||||
|
||||
### 2. Roles
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
$ vault write auth/gcp/role/dev-iam-role \
|
||||
type="iam" \
|
||||
project_id="project-123456" \
|
||||
policies="dev" \
|
||||
bound_service_accounts="serviceaccount1@project1234.iam.gserviceaccount.com,uuid123,..."
|
||||
|
||||
$ vault write auth/gcp/role/prod-gce-role \
|
||||
type="gce" \
|
||||
project_id="project-123456" \
|
||||
policies="prod" \
|
||||
bound_zone="us-central1-a" \
|
||||
bound_instance_group="my-gce-group"
|
||||
```
|
||||
|
||||
Users will login to Vault under roles (Vault-specific, unrelated to GCP IAM roles).
|
||||
Roles are associated with an authentication type using the `type` parameter. Currently
|
||||
`iam` or `gce` are acceptable values. The role also manages the [policies](/docs/concepts/policies.html)
|
||||
the logged-in user will have and a set of constraints on authenticated users.
|
||||
These are determined by the authenticating entity type and confirmed with GCP.
|
||||
|
||||
For the complete list of role options, please see the [API documentation](/api/auth/gcp/index.html#create-role).
|
||||
|
||||
|
||||
## Authentication
|
||||
These following examples assume the auth method is enabled at `/auth/gcp`
|
||||
and use the CLI. For more complete documentation of parameters, see the
|
||||
[HTTP API documentation](/api/auth/gcp/index.html#login).
|
||||
|
||||
### Via the CLI
|
||||
There are two ways to login using the CLI:
|
||||
|
||||
The default path is `/gcp`. If this auth method was enabled at a different
|
||||
path, specify `auth/my-path/login` instead.
|
||||
### Client-provided JWT
|
||||
|
||||
```text
|
||||
In this case, you already have the GCE VM identity metadata token
|
||||
or have obtained a IAM service account JWT, self-signed
|
||||
or Google-signed using the GCP IAM API method
|
||||
[projects.serviceAccounts.signJwt](https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts/signJwt).
|
||||
|
||||
```sh
|
||||
$ vault write auth/gcp/login \
|
||||
role="dev-role" \
|
||||
jwt="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
The `role` and `jwt` parameters are required. These map to the name of the role
|
||||
to login against, and the signed JWT token for authenticating a role
|
||||
respectively. The format of the provided JWT differs depending on the
|
||||
authenticating entity.
|
||||
* `role` is the role to login under
|
||||
* `jwt` is the GCE metadata token or IAM service account token
|
||||
|
||||
See docs on the [IAM workflow](#iam) or [GCE workflow](#gce) to see how to obtain this JWT.
|
||||
|
||||
### Via the API
|
||||
|
||||
The default endpoint is `auth/gcp/login`. If this auth method was enabled
|
||||
at a different path, use that value instead of `gcp`.
|
||||
### (IAM ONLY) Client-side helper for service account JWT
|
||||
Because the process to sign a service account JWT can be tedious,
|
||||
we implemented a helper that can be run from client-side.
|
||||
|
||||
```sh
|
||||
$ curl \
|
||||
--request POST \
|
||||
--data '{"role": "dev-role", "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}' \
|
||||
http://127.0.0.1:8200/v1/auth/gcp/login
|
||||
$ vault login -method=gcp \
|
||||
mount="gcp"
|
||||
role="dev-role" \
|
||||
jwt_exp
|
||||
credentials="@/path/to/creds.json" \
|
||||
project="project-for-authenticating-service-account" \
|
||||
service-account="authenticating-service-account@project.iam.gserviceaccounts.com"
|
||||
```
|
||||
|
||||
The response will contain the token at `auth.client_token`:
|
||||
This signs a properly formatted service account JWT for you and logs into Vault
|
||||
directly. For more information, run `vault auth help gcp`.
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"client_token": "f33f8c72-924e-11f8-cb43-ac59d697597c",
|
||||
"accessor": "0e9e354a-520f-df04-6867-ee81cae3d42d",
|
||||
"policies": [
|
||||
"default",
|
||||
"dev",
|
||||
"prod"
|
||||
],
|
||||
"metadata": {
|
||||
"role": "dev-role",
|
||||
"service_account_email": "dev1@project-123456.iam.gserviceaccount.com",
|
||||
"service_account_id": "111111111111111111111"
|
||||
},
|
||||
"lease_duration": 2764800,
|
||||
"renewable": true
|
||||
}
|
||||
}
|
||||
```
|
||||
Required Parameters:
|
||||
|
||||
## Configuration
|
||||
- `role` `(string: <required>)` - The role with type=`iam` to login under.
|
||||
|
||||
Auth methods must be configured in advance before users or machines can
|
||||
authenticate. These steps are usually completed by an operator or configuration
|
||||
management tool.
|
||||
Optional Parameters:
|
||||
|
||||
### Via the CLI
|
||||
|
||||
1. Enable GCP authentication in Vault:
|
||||
|
||||
```text
|
||||
$ vault auth enable gcp
|
||||
```
|
||||
|
||||
1. Configure the GCP auth method:
|
||||
|
||||
```text
|
||||
$ vault write auth/gcp/config credentials=@/path/to/creds.json
|
||||
```
|
||||
|
||||
Configuration includes GCP credentials Vault will use these to make calls to
|
||||
GCP APIs. If credentials are not configured or if the user explicitly sets
|
||||
the config with no credentials, the Vault server will attempt to use
|
||||
[Application Default
|
||||
Credentials](https://developers.google.com/identity/protocols/application-default-credentials)
|
||||
as set on the Vault server.
|
||||
|
||||
For the complete list of configuration options, please see the API
|
||||
documentation.
|
||||
|
||||
1. Create a role:
|
||||
|
||||
```text
|
||||
$ vault write auth/gcp/role/dev-role \
|
||||
type="iam" \
|
||||
project_id="project-123456" \
|
||||
policies="prod,dev" \
|
||||
service_accounts="serviceaccount1@project1234.iam.gserviceaccount.com,uuid123,..."
|
||||
```
|
||||
|
||||
Roles are associated with an authentication type/entity and a set of Vault
|
||||
policies. Roles are configured with constraints specific to the
|
||||
authentication type, as well as overall constraints and configuration for
|
||||
the generated auth tokens.
|
||||
|
||||
For the complete list of role options, please see the API documentation.
|
||||
|
||||
### Via the API
|
||||
|
||||
1. Enable GCP authentication in Vault:
|
||||
|
||||
```sh
|
||||
$ curl \
|
||||
--header "X-Vault-Token: ..." \
|
||||
--request POST \
|
||||
--data '{"type": "gcp"}' \
|
||||
http://127.0.0.1:8200/v1/sys/auth/gcp
|
||||
```
|
||||
|
||||
1. Configure the GCP auth method:
|
||||
|
||||
```sh
|
||||
$ curl \
|
||||
--header "X-Vault-Token: ..." \
|
||||
--request POST \
|
||||
--data '{"credentials": "{...}"}' \
|
||||
http://127.0.0.1:8200/v1/auth/gcp/config
|
||||
```
|
||||
|
||||
1. Create a role:
|
||||
|
||||
```sh
|
||||
$ curl \
|
||||
--header "X-Vault-Token: ..." \
|
||||
--request POST \
|
||||
--data '{"type": "iam", "project_id": "project-123456", ...}' \
|
||||
http://127.0.0.1:8200/v1/auth/gcp/role/dev-role
|
||||
```
|
||||
|
||||
### Plugin Setup
|
||||
|
||||
~> The following section is only relevant if you decide to enable the gcp auth
|
||||
method as an external plugin. The gcp plugin method is integrated into Vault as
|
||||
a builtin method by default.
|
||||
|
||||
Assuming you have saved the binary `vault-plugin-auth-gcp` to some folder and
|
||||
configured the [plugin directory](/docs/internals/plugins.html#plugin-directory)
|
||||
for your server at `path/to/plugins`:
|
||||
|
||||
|
||||
1. Enable the plugin in the catalog:
|
||||
|
||||
```text
|
||||
$ vault write sys/plugins/catalog/gcp-auth \
|
||||
command="vault-plugin-auth-gcp" \
|
||||
sha_256="..."
|
||||
```
|
||||
|
||||
1. Enable the gcp auth method as a plugin:
|
||||
|
||||
```text
|
||||
$ vault auth enable -path=gcp -plugin-name=gcp-auth plugin
|
||||
* `mount` (`string`: ``"gcp"`) - Name of the role.
|
||||
* `jwt_exp` (`int`: `15`) - Number of minutes within which the JWT should expire.
|
||||
This defaults to the default value used when creating a role, and should only be set
|
||||
if set differently in the role (i.e. if the admin setting up your Vault server only accepts
|
||||
JWTs that expire within < 15 min).
|
||||
* `credentials`: If not provided, attempts to use Application Default Credentials.
|
||||
These credentials are ONLY used to sign the service account JWT, i.e. call API
|
||||
method [projects.serviceAccounts.signJwt](https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts/signJwt),
|
||||
and thus must have IAM permission `iam.serviceAccounts.signJWT` or role `iam.serviceAccountTokenCreator`
|
||||
on the `service_account`, even if they are the same service account (**NOTE** this permission is not enabled by default).
|
||||
```
|
||||
* `service_account` `(string: `""`)`: Service account to sign JWT for i.e. login to Vault as
|
||||
and is listed under roles `bound_service_accounts`. If not provided, uses `client_email`
|
||||
from the credentials JSON. Only provide if the credentials are for a different account, acting as a "signer".
|
||||
* `project`: Project of service account. If not provided, attempts to use `project_id` from the credentials JSON.
|
||||
Fails if not found and not provided by Application Default Credentials. Only provide if the credentials are for a
|
||||
different account, acting as a "signer".
|
||||
|
||||
## Authentication Workflow
|
||||
|
||||
@@ -263,6 +225,8 @@ for the identity metadata token. The params the user provides are:
|
||||
|
||||
#### Generating IAM Token
|
||||
|
||||
If you don't want to use the CLI helper, you can generate the JWT using the following methods.
|
||||
|
||||
**HTTP Request Example**
|
||||
|
||||
This uses [Google API HTTP annotation](https://github.com/googleapis/googleapis/blob/master/google/api/http.proto).
|
||||
|
||||
Reference in New Issue
Block a user