Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,14 @@ import (
"github.com/gravitational/teleport/lib/devicetrust/assertserver"
dtconfig "github.com/gravitational/teleport/lib/devicetrust/config"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/gcp"
"github.com/gravitational/teleport/lib/integrations/awsra/createsession"
"github.com/gravitational/teleport/lib/inventory"
iterstream "github.com/gravitational/teleport/lib/itertools/stream"
"github.com/gravitational/teleport/lib/join"
joinboundkeypair "github.com/gravitational/teleport/lib/join/boundkeypair"
"github.com/gravitational/teleport/lib/join/ec2join"
"github.com/gravitational/teleport/lib/join/env0"
"github.com/gravitational/teleport/lib/join/gcp"
"github.com/gravitational/teleport/lib/join/githubactions"
"github.com/gravitational/teleport/lib/join/gitlab"
"github.com/gravitational/teleport/lib/join/tpmjoin"
Expand Down Expand Up @@ -1314,7 +1314,7 @@ type Server struct {

// gcpIDTokenValidator allows ID tokens from GCP to be validated by the auth
// server. It can be overridden for the purpose of tests.
gcpIDTokenValidator gcpIDTokenValidator
gcpIDTokenValidator gcp.Validator

// terraformIDTokenValidator allows JWTs from Terraform Cloud to be
// validated by the auth server using a known JWKS. It can be overridden for
Expand Down
8 changes: 0 additions & 8 deletions lib/auth/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,6 @@ func (a *Server) SetCircleCITokenValidate(validator func(ctx context.Context, or
a.circleCITokenValidate = validator
}

func (a *Server) SetGCPIDTokenValidator(validator gcpIDTokenValidator) {
a.gcpIDTokenValidator = validator
}

func (a *Server) SetK8sTokenReviewValidator(validator k8sTokenReviewValidator) {
a.k8sTokenReviewValidator = validator
}
Expand Down Expand Up @@ -360,10 +356,6 @@ func ValidateGithubAuthCallbackHelper(ctx context.Context, m GitHubManager, diag
return validateGithubAuthCallbackHelper(ctx, m, diagCtx, q, emitter, logger)
}

func IsGCPZoneInLocation(rawLocation, rawZone string) bool {
return isGCPZoneInLocation(rawLocation, rawZone)
}

func FormatHeaderFromMap(m map[string]string) http.Header {
return formatHeaderFromMap(m)
}
Expand Down
8 changes: 5 additions & 3 deletions lib/auth/join/join.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,11 @@ func Register(ctx context.Context, params RegisterParams) (result *RegisterResul
return nil, trace.Wrap(err)
}
case types.JoinMethodGCP:
params.IDToken, err = gcp.GetIDToken(ctx)
if err != nil {
return nil, trace.Wrap(err)
if params.IDToken == "" {
params.IDToken, err = gcp.GetIDToken(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
}
case types.JoinMethodSpacelift:
params.IDToken, err = spacelift.NewIDTokenSource(os.Getenv).GetIDToken()
Expand Down
132 changes: 19 additions & 113 deletions lib/auth/join_gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,130 +20,36 @@ package auth

import (
"context"
"slices"
"strings"

"github.com/gravitational/trace"

"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/gcp"
"github.com/gravitational/teleport/lib/join/gcp"
)

type gcpIDTokenValidator interface {
Validate(ctx context.Context, token string) (*gcp.IDTokenClaims, error)
// GetGCPIDTokenValidator returns the server's configured GCP ID token
// validator.
func (a *Server) GetGCPIDTokenValidator() gcp.Validator {
return a.gcpIDTokenValidator
}

// SetGCPIDTokenValidator sets a new GCP ID token validator, used in tests.
func (a *Server) SetGCPIDTokenValidator(validator gcp.Validator) {
a.gcpIDTokenValidator = validator
}

func (a *Server) checkGCPJoinRequest(
ctx context.Context,
req *types.RegisterUsingTokenRequest,
pt types.ProvisionToken,
) (*gcp.IDTokenClaims, error) {
if req.IDToken == "" {
return nil, trace.BadParameter("IDToken not provided for GCP join request")
}
token, ok := pt.(*types.ProvisionTokenV2)
if !ok {
return nil, trace.BadParameter("gcp join method only supports ProvisionTokenV2, '%T' was provided", pt)
}

claims, err := a.gcpIDTokenValidator.Validate(ctx, req.IDToken)
if err != nil {
a.logger.WarnContext(ctx, "Unable to validate GCP IDToken",
"error", err,
"claims", claims,
"token", pt.GetName(),
)
return nil, trace.Wrap(err)
}

a.logger.InfoContext(ctx, "GCP VM trying to join cluster",
"claims", claims,
"token", pt.GetName(),
)

if err := checkGCPAllowRules(token, claims); err != nil {
return nil, trace.Wrap(err)
}

return claims, nil
}

func checkGCPAllowRules(token *types.ProvisionTokenV2, claims *gcp.IDTokenClaims) error {
compute := claims.Google.ComputeEngine
// unmatchedLocation is true if the location restriction is set and the "google.compute_engine.zone"
// claim is not present in the IDToken. This happens when the joining node is not a GCE VM.
unmatchedLocation := false
// If a single rule passes, accept the IDToken.
for _, rule := range token.Spec.GCP.Allow {
if !slices.Contains(rule.ProjectIDs, compute.ProjectID) {
continue
}

if len(rule.ServiceAccounts) > 0 && !slices.Contains(rule.ServiceAccounts, claims.Email) {
continue
}

if len(rule.Locations) > 0 && !slices.ContainsFunc(rule.Locations, func(location string) bool {
return isGCPZoneInLocation(location, compute.Zone)
}) {
unmatchedLocation = true
continue
}

// All provided rules met.
return nil
}

// If the location restriction is set and the "google.compute_engine.zone" claim is not present in the IDToken,
// return a more specific error message.
if unmatchedLocation && compute.Zone == "" {
return trace.CompareFailed("id token %q claim is empty and didn't match the %q. "+
"Services running outside of GCE VM instances are incompatible with %q restriction.", "google.compute_engine.zone", "locations", "location")
}
return trace.AccessDenied("id token claims did not match any allow rules")
}

type gcpLocation struct {
globalLocation string
region string
zone string
}

func parseGCPLocation(location string) (*gcpLocation, error) {
parts := strings.Split(location, "-")
if len(parts) < 2 || len(parts) > 3 {
return nil, trace.BadParameter("location %q is not a valid GCP region or zone", location)
}
globalLocation, region := parts[0], parts[1]
var zone string
if len(parts) == 3 {
zone = parts[2]
}
return &gcpLocation{
globalLocation: globalLocation,
region: region,
zone: zone,
}, nil
}

// isGCPZoneInLocation checks if a zone belongs to a location, which can be
// either a zone or region.
func isGCPZoneInLocation(rawLocation, rawZone string) bool {
location, err := parseGCPLocation(rawLocation)
if err != nil {
return false
}
zone, err := parseGCPLocation(rawZone)
if err != nil {
return false
}
// Make sure zone is, in fact, a zone.
if zone.zone == "" {
return false
}

if location.globalLocation != zone.globalLocation || location.region != zone.region {
return false
}
return location.zone == "" || location.zone == zone.zone
claims, err := gcp.CheckIDToken(ctx, &gcp.CheckIDTokenParams{
ProvisionToken: pt,
IDToken: []byte(req.IDToken),
Validator: a.gcpIDTokenValidator,
})

// Where possible, try to return any extracted claims along with the error
// to improve audit logs for failed join attempts.
return claims, trace.Wrap(err)
}
74 changes: 0 additions & 74 deletions lib/gcp/gcp.go

This file was deleted.

Loading
Loading