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

Add google_service_account inline field option for Google Workspace/GSuite OIDC #5563

Merged
merged 38 commits into from
Mar 12, 2021
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0689666
Add google_service_account field to OIDC for specifying google servic…
stevenGravy Feb 12, 2021
d696f2f
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Feb 12, 2021
6b24cf1
corrected lint issues
stevenGravy Feb 12, 2021
71a6dac
Merge branch 'stevenGravy/sso/gsuite' of https://github.com/gravitati…
stevenGravy Feb 12, 2021
59c37de
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Feb 15, 2021
afe56ab
change comment
stevenGravy Feb 16, 2021
4f23afd
spacing
stevenGravy Feb 16, 2021
b4f1194
change comment for googleserviceaccount
stevenGravy Feb 16, 2021
bd93792
correct comment
stevenGravy Feb 16, 2021
7fd9216
modify comment on GetGoogleServiceAccount
stevenGravy Feb 16, 2021
0eda340
add comment on loading google service account
stevenGravy Feb 16, 2021
fff0251
Moved comments to correct line and modified language
stevenGravy Feb 16, 2021
5a4912b
Merge branch 'stevenGravy/sso/gsuite' of https://github.com/gravitati…
stevenGravy Feb 16, 2021
4d074ed
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Feb 16, 2021
46f089f
lint fix
stevenGravy Feb 16, 2021
6db19d1
added credential loading method variable for better error reporting w…
stevenGravy Feb 16, 2021
829f3dc
Merge branch 'stevenGravy/sso/gsuite' of https://github.com/gravitati…
stevenGravy Feb 16, 2021
5bced25
modified debug message to use variable name while giving google works…
stevenGravy Feb 16, 2021
5bc53c2
take out unnecessary if check
stevenGravy Feb 17, 2021
fe84366
better error message handling
stevenGravy Feb 17, 2021
8ff3630
config assignment
stevenGravy Feb 17, 2021
286e61c
remove unnecessary if check
stevenGravy Feb 17, 2021
59f9396
remove unnecessary parenthesis
stevenGravy Feb 17, 2021
2982395
spacing for variable
stevenGravy Feb 17, 2021
df063a6
remove unnecessary if check
stevenGravy Feb 17, 2021
3853f5f
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Feb 17, 2021
96b20fb
lint fix
stevenGravy Feb 17, 2021
6d6b435
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Feb 17, 2021
41e2e16
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Feb 17, 2021
936ce88
Fix language
stevenGravy Feb 17, 2021
3d08355
language change
stevenGravy Feb 17, 2021
842e8b8
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Feb 17, 2021
0434d70
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Feb 17, 2021
c931331
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Mar 3, 2021
248115f
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Mar 3, 2021
c439a9d
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Mar 4, 2021
f72081a
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Mar 11, 2021
b8003f8
Merge branch 'master' into stevenGravy/sso/gsuite
stevenGravy Mar 12, 2021
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
24 changes: 22 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 Googke Worksapace (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 G Suite end users to share documents outside of the domain would not apply to service accounts."
stevenGravy marked this conversation as resolved.
Show resolved Hide resolved
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,14 @@ 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 +427,9 @@ 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
stevenGravy marked this conversation as resolved.
Show resolved Hide resolved
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() != "" {
stevenGravy marked this conversation as resolved.
Show resolved Hide resolved
// load the google service account from string
credentialLoadingMethod = "google_service_account"
jsonCredentials = []byte(connector.GetGoogleServiceAccount())
stevenGravy marked this conversation as resolved.
Show resolved Hide resolved
} 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("google_service_account_uri or google_service_account is supported, not both")
stevenGravy marked this conversation as resolved.
Show resolved Hide resolved
}

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