Skip to content

Commit

Permalink
backport branch/v6.0: Add google_service_account inline field option …
Browse files Browse the repository at this point in the history
…for Google Workspace/GSuite OIDC (#5964)

Backport of #5563
  • Loading branch information
stevenGravy authored Mar 12, 2021
1 parent d5bb09f commit b169431
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 23 deletions.
22 changes: 20 additions & 2 deletions api/types/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,13 @@ type OIDCConnector interface {
SetDisplay(string)
// GetGoogleServiceAccountURI returns path to google service account URI
GetGoogleServiceAccountURI() string
// GetGoogleServiceAccount returns google service account json for Google
GetGoogleServiceAccount() string
// SetGoogleServiceAccount sets the google service account json contents
SetGoogleServiceAccount(string)
// GetGoogleAdminEmail returns a google admin user email
// https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority
// "Note: Although you can use service accounts in applications that run from a G Suite domain, service accounts are not members of your G Suite account and aren’t subject to domain policies set by G Suite administrators. For example, a policy set in the G Suite admin console to restrict the ability of G Suite end users to share documents outside of the domain would not apply to service accounts."
// "Note: Although you can use service accounts in applications that run from a Google Workspace (formerly G Suite) domain, service accounts are not members of your Google Workspace account and aren’t subject to domain policies set by administrators. For example, a policy set in the Google Workspace admin console to restrict the ability of end users to share documents outside of the domain would not apply to service accounts."
GetGoogleAdminEmail() string
}

Expand Down Expand Up @@ -139,6 +143,16 @@ func (o *OIDCConnectorV2) GetGoogleServiceAccountURI() string {
return o.Spec.GoogleServiceAccountURI
}

// GetGoogleServiceAccount returns a string representing a Google service account
func (o *OIDCConnectorV2) GetGoogleServiceAccount() string {
return o.Spec.GoogleServiceAccount
}

// SetGoogleServiceAccount sets a string representing a Google service account
func (o *OIDCConnectorV2) SetGoogleServiceAccount(s string) {
o.Spec.GoogleServiceAccount = s
}

// GetGoogleAdminEmail returns a google admin user email
func (o *OIDCConnectorV2) GetGoogleAdminEmail() string {
return o.Spec.GoogleAdminEmail
Expand Down Expand Up @@ -176,11 +190,13 @@ func (o *OIDCConnectorV2) SetResourceID(id int64) {

// WithoutSecrets returns an instance of resource without secrets.
func (o *OIDCConnectorV2) WithoutSecrets() Resource {
if o.GetClientSecret() == "" {
if o.GetClientSecret() == "" && o.GetGoogleServiceAccount() == "" {
return o
}
o2 := *o

o2.SetClientSecret("")
o2.SetGoogleServiceAccount("")
return &o2
}

Expand Down Expand Up @@ -410,6 +426,8 @@ type OIDCConnectorSpecV2 struct {
ClaimsToRoles []ClaimMapping `json:"claims_to_roles,omitempty"`
// GoogleServiceAccountURI is a path to google service account uri
GoogleServiceAccountURI string `json:"google_service_account_uri,omitempty"`
// GoogleServiceAccount is a string containing the google service account credentials
GoogleServiceAccount string `json:"google_service_account,omitempty"`
// GoogleAdminEmail is email of google admin to impersonate
GoogleAdminEmail string `json:"google_admin_email,omitempty"`
}
Expand Down
49 changes: 28 additions & 21 deletions lib/auth/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ collect:
for {
if count > MaxPages {
warningMessage := "Truncating list of teams used to populate claims: " +
"hit maximum number pages that can be fetched from GSuite."
"hit maximum number pages that can be fetched from Google Workspace."

// Print warning to Teleport logs as well as the Audit Log.
log.Warnf(warningMessage)
Expand Down Expand Up @@ -714,7 +714,7 @@ func (g *gsuiteClient) fetchGroupsPage(pageToken string) (*gsuiteGroups, error)
u.RawQuery = q.Encode()
endpoint := u.String()

log.Debugf("Fetching OIDC claims from GSuite groups endpoint: %q.", endpoint)
log.Debugf("Fetching OIDC claims from Google Workspace groups endpoint: %q.", endpoint)

req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
Expand Down Expand Up @@ -835,32 +835,39 @@ func (a *Server) getClaims(oidcClient *oidc.Client, connector services.OIDCConne
return nil, trace.Wrap(err)
}

serviceAccountURI := connector.GetGoogleServiceAccountURI()
if serviceAccountURI == "" {
return nil, trace.NotFound(
"the gsuite connector requires google_service_account_uri parameter to be specified and pointing to a valid google service account file with credentials, read this article for more details https://developers.google.com/admin-sdk/directory/v1/guides/delegation")
var jsonCredentials []byte
var credentialLoadingMethod string
if connector.GetGoogleServiceAccountURI() != "" {
// load the google service account from URI
credentialLoadingMethod = "google_service_account_uri"

uri, err := utils.ParseSessionsURI(connector.GetGoogleServiceAccountURI())
if err != nil {
return nil, trace.BadParameter("failed to parse google_service_account_uri: %v", err)
}
jsonCredentials, err = ioutil.ReadFile(uri.Path)
if err != nil {
return nil, trace.Wrap(err)
}
} else if connector.GetGoogleServiceAccount() != "" {
// load the google service account from string
credentialLoadingMethod = "google_service_account"
jsonCredentials = []byte(connector.GetGoogleServiceAccount())
} else {
return nil, trace.NotFound("the google workspace connector requires google_service_account parameter with JSON-formatted credentials or google_service_account_uri parameter pointing to a valid google service account file with credentials to be specified, read this article for more details https://developers.google.com/admin-sdk/directory/v1/guides/delegation")
}

uri, err := utils.ParseSessionsURI(serviceAccountURI)
config, err := google.JWTConfigFromJSON(jsonCredentials, teleport.GSuiteGroupsScope)
if err != nil {
return nil, trace.BadParameter("failed to parse google_service_account_uri: %v", err)
return nil, trace.BadParameter("unable to parse google service account from %v: %v", credentialLoadingMethod, err)
}

impersonateAdmin := connector.GetGoogleAdminEmail()
if impersonateAdmin == "" {
return nil, trace.NotFound(
"the gsuite connector requires google_admin_email user to impersonate, as service accounts can not be used directly https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority")
}

jsonCredentials, err := ioutil.ReadFile(uri.Path)
if err != nil {
return nil, trace.Wrap(err)
"the google workspace connector requires google_admin_email user to impersonate, as service accounts can not be used directly https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority")
}

config, err := google.JWTConfigFromJSON(jsonCredentials, teleport.GSuiteGroupsScope)
if err != nil {
return nil, trace.BadParameter("unable to parse client secret file to config: %v", err)
}
// User should impersonate admin user, otherwise it won't work:
//
// https://developers.google.com/admin-sdk/directory/v1/guides/delegation
Expand All @@ -869,7 +876,7 @@ func (a *Server) getClaims(oidcClient *oidc.Client, connector services.OIDCConne
//
domain, exists, err := userInfoClaims.StringClaim(teleport.GSuiteDomainClaim)
if err != nil || !exists {
return nil, trace.BadParameter("hd is the required claim for GSuite")
return nil, trace.BadParameter("hd is the required claim for Google Workspace")
}
config.Subject = impersonateAdmin

Expand All @@ -878,10 +885,10 @@ func (a *Server) getClaims(oidcClient *oidc.Client, connector services.OIDCConne
if !trace.IsNotFound(err) {
return nil, trace.Wrap(err)
}
log.Debugf("Found no GSuite claims.")
log.Debugf("Found no Google Workspace claims.")
} else {
if gsuiteClaims != nil {
log.Debugf("Got GSuiteclaims: %v.", gsuiteClaims)
log.Debugf("Got gsuiteClaims claims from Google Workspace: %v.", gsuiteClaims)
}
claims, err = mergeClaims(claims, gsuiteClaims)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions lib/services/local/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ func (s *IdentityService) GetOIDCConnector(name string, withSecrets bool) (servi
}
if !withSecrets {
conn.SetClientSecret("")
conn.SetGoogleServiceAccount("")
}
return conn, nil
}
Expand All @@ -738,6 +739,7 @@ func (s *IdentityService) GetOIDCConnectors(withSecrets bool) ([]services.OIDCCo
}
if !withSecrets {
conn.SetClientSecret("")
conn.SetGoogleServiceAccount("")
}
connectors[i] = conn
}
Expand Down
11 changes: 11 additions & 0 deletions lib/services/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func ValidateOIDCConnector(oc types.OIDCConnector) error {
if _, err := url.Parse(oc.GetRedirectURL()); err != nil {
return trace.BadParameter("RedirectURL: bad url: '%v'", oc.GetRedirectURL())
}

if oc.GetGoogleServiceAccountURI() != "" && oc.GetGoogleServiceAccount() != "" {
return trace.BadParameter("one of either google_service_account_uri or google_service_account is supported, not both")
}

if oc.GetGoogleServiceAccountURI() != "" {
uri, err := utils.ParseSessionsURI(oc.GetGoogleServiceAccountURI())
if err != nil {
Expand All @@ -50,6 +55,11 @@ func ValidateOIDCConnector(oc types.OIDCConnector) error {
return trace.BadParameter("whenever google_service_account_uri is specified, google_admin_email should be set as well, read https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority for more details")
}
}
if oc.GetGoogleServiceAccount() != "" {
if oc.GetGoogleAdminEmail() == "" {
return trace.BadParameter("whenever google_service_account is specified, google_admin_email should be set as well, read https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority for more details")
}
}
return nil
}

Expand Down Expand Up @@ -95,6 +105,7 @@ var OIDCConnectorSpecV2Schema = fmt.Sprintf(`{
"display": {"type": "string"},
"prompt": {"type": "string"},
"google_service_account_uri": {"type": "string"},
"google_service_account": {"type": "string"},
"google_admin_email": {"type": "string"},
"scope": {
"type": "array",
Expand Down

0 comments on commit b169431

Please sign in to comment.