Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

backport branch/v6.0: Add google_service_account inline field option for Google Workspace/GSuite OIDC #5964

Merged
merged 4 commits into from
Mar 12, 2021
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
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