Skip to content

Commit

Permalink
Merge pull request #927 from gravitational/rjones/acr-values
Browse files Browse the repository at this point in the history
Added support for ACR values for OIDC connectors.
  • Loading branch information
russjones authored Apr 13, 2017
2 parents d28fe6c + 9df3b48 commit 415dea4
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 12 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Naming convention:
# for stable releases we use "1.0.0" format
# for pre-releases, we use "1.0.0-beta.2" format
VERSION=2.0.0-rc.4
VERSION=2.1.0-alpha.2

# These are standard autotools variables, don't change them please
BUILDDIR ?= build
Expand Down
5 changes: 5 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,8 @@ const (
// CertExtensionPermitPortForwarding allows user to request port forwarding
CertExtensionPermitPortForwarding = "permit-port-forwarding"
)

const (
// NetIQ is an identity provider.
NetIQ = "netiq"
)
96 changes: 86 additions & 10 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,9 +749,25 @@ func (s *AuthServer) CreateOIDCAuthRequest(req services.OIDCAuthRequest) (*servi
if err != nil {
return nil, trace.Wrap(err)
}

// online is OIDC online scope, "select_account" forces user to always select account
redirectURL := oauthClient.AuthCodeURL(req.StateToken, "online", "select_account")
req.RedirectURL = redirectURL
req.RedirectURL = oauthClient.AuthCodeURL(req.StateToken, "online", "select_account")

// if the connector has an Authentication Context Class Reference (ACR) value set,
// update redirect url and add it as a query value.
acrValue := connector.GetACR()
if acrValue != "" {
u, err := url.Parse(req.RedirectURL)
if err != nil {
return nil, trace.Wrap(err)
}
q := u.Query()
q.Set("acr_values", acrValue)
u.RawQuery = q.Encode()
req.RedirectURL = u.String()
}

log.Debugf("[OIDC] Redirect URL: %v", req.RedirectURL)

err = s.Identity.CreateOIDCAuthRequest(req, defaults.OIDCAuthRequestTTL)
if err != nil {
Expand Down Expand Up @@ -904,15 +920,16 @@ func claimsFromUserInfo(oidcClient *oidc.Client, issuerURL string, accessToken s
}
hc := oac.HttpClient()

// go get the provider config so we can find out where the UserInfo endpoint is
// go get the provider config so we can find out where the UserInfo endpoint
// is. if the provider doesn't offer a UserInfo endpoint return not found.
pc, err := oidc.FetchProviderConfig(oac.HttpClient(), issuerURL)
if err != nil {
return nil, trace.Wrap(err)
}
// If the provider doesn't offer a UserInfo endpoint don't err.
if pc.UserInfoEndpoint == nil {
return nil, nil
return nil, trace.NotFound("UserInfo endpoint not found")
}

endpoint := pc.UserInfoEndpoint.String()
err = isHTTPS(endpoint)
if err != nil {
Expand Down Expand Up @@ -980,14 +997,13 @@ func (a *AuthServer) getClaims(oidcClient *oidc.Client, issuerURL string, code s

userInfoClaims, err := claimsFromUserInfo(oidcClient, issuerURL, t.AccessToken)
if err != nil {
if trace.IsNotFound(err) {
log.Debugf("[OIDC] Provider doesn't offer UserInfo endpoint. Returning token claims: %v", idTokenClaims)
return idTokenClaims, nil
}
log.Debugf("[OIDC] Unable to fetch UserInfo claims: %v", err)
return nil, trace.Wrap(err)
}
if userInfoClaims == nil {
log.Warn("[OIDC] Provider doesn't offer UserInfo endpoint. Only token claims will be used.")
return idTokenClaims, nil
}

log.Debugf("[OIDC] UserInfo claims: %v", userInfoClaims)

// make sure that the subject in the userinfo claim matches the subject in
Expand Down Expand Up @@ -1018,6 +1034,56 @@ func (a *AuthServer) getClaims(oidcClient *oidc.Client, issuerURL string, code s
return claims, nil
}

// validateACRValues validates that we get an appropriate response for acr values. By default
// we expect the same value we send, but this function also handles Identity Provider specific
// forms of validation.
func (a *AuthServer) validateACRValues(acrValue string, identityProvider string, claims jose.Claims) error {
switch identityProvider {
case teleport.NetIQ:
log.Debugf("[OIDC] Validating ACR values with %q rules", identityProvider)

tokenAcr, ok := claims["acr"]
if !ok {
return trace.BadParameter("acr claim does not exist")
}
tokenAcrMap, ok := tokenAcr.(map[string][]string)
if !ok {
return trace.BadParameter("acr unknown type: %T", tokenAcr)
}
tokenAcrValues, ok := tokenAcrMap["values"]
if !ok {
return trace.BadParameter("acr.values not found in claims")
}
acrValueMatched := false
for _, v := range tokenAcrValues {
if acrValue == v {
acrValueMatched = true
break
}
}
if !acrValueMatched {
log.Debugf("[OIDC] No ACR match found for %q in %q", acrValue, tokenAcrValues)
return trace.BadParameter("acr claim does not match")
}
default:
log.Debugf("[OIDC] Validating ACR values with default rules")

claimValue, exists, err := claims.StringClaim("acr")
if !exists {
return trace.BadParameter("acr claim does not exist")
}
if err != nil {
return trace.Wrap(err)
}
if claimValue != acrValue {
log.Debugf("[OIDC] No ACR match found %q != %q", acrValue, claimValue)
return trace.BadParameter("acr claim does not match")
}
}

return nil
}

// ValidateOIDCAuthCallback is called by the proxy to check OIDC query parameters
// returned by OIDC Provider, if everything checks out, auth server
// will respond with OIDCAuthResponse, otherwise it will return error
Expand Down Expand Up @@ -1061,6 +1127,16 @@ func (a *AuthServer) ValidateOIDCAuthCallback(q url.Values) (*OIDCAuthResponse,
}
log.Debugf("[OIDC] Claims: %v", claims)

// if we are sending acr values, make sure we also validate them
acrValue := connector.GetACR()
if acrValue != "" {
err := a.validateACRValues(acrValue, connector.GetProvider(), claims)
if err != nil {
return nil, trace.Wrap(err)
}
log.Debugf("[OIDC] ACR values %q successfully validated", acrValue)
}

ident, err := oidc.IdentityFromClaims(claims)
if err != nil {
return nil, trace.OAuth2(
Expand Down
9 changes: 9 additions & 0 deletions lib/config/fileconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ var (
"client_id": true,
"client_secret": true,
"redirect_url": true,
"acr_values": true,
"provider": true,
"tokens": true,
"region": true,
"table_name": true,
Expand Down Expand Up @@ -695,6 +697,11 @@ type OIDCConnector struct {
// client's browser back to it after successfull authentication
// Should match the URL on Provider's side
RedirectURL string `yaml:"redirect_url"`
// ACR is the acr_values parameter to be sent with an authorization request.
ACR string `yaml:"acr_values,omitempty"`
// Provider is the identity provider we connect to. This field is
// only required if using acr_values.
Provider string `yaml:"provider,omitempty"`
// Display controls how this connector is displayed
Display string `yaml:"display"`
// Scope is a list of additional scopes to request from OIDC
Expand Down Expand Up @@ -736,6 +743,8 @@ func (o *OIDCConnector) Parse() (services.OIDCConnector, error) {
ClaimsToRoles: mappings,
}
v2 := other.V2()
v2.SetACR(o.ACR)
v2.SetProvider(o.Provider)
if err := v2.Check(); err != nil {
return nil, trace.Wrap(err)
}
Expand Down
35 changes: 35 additions & 0 deletions lib/services/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ type OIDCConnector interface {
// client's browser back to it after successfull authentication
// Should match the URL on Provider's side
GetRedirectURL() string
// GetACR returns the Authentication Context Class Reference (ACR) value.
GetACR() string
// GetProvider returns the identity provider.
GetProvider() string
// Display - Friendly name for this provider.
GetDisplay() string
// Scope is additional scopes set by provder
Expand All @@ -70,6 +74,10 @@ type OIDCConnector interface {
SetIssuerURL(string)
// SetRedirectURL sets RedirectURL
SetRedirectURL(string)
// SetACR sets the Authentication Context Class Reference (ACR) value.
SetACR(string)
// SetProvider sets the identity provider.
SetProvider(string)
// SetScope sets additional scopes set by provider
SetScope([]string)
// SetClaimsToRoles sets dynamic mapping from claims to roles
Expand Down Expand Up @@ -257,6 +265,16 @@ func (o *OIDCConnectorV2) SetRedirectURL(redirectURL string) {
o.Spec.RedirectURL = redirectURL
}

// SetACR sets the Authentication Context Class Reference (ACR) value.
func (o *OIDCConnectorV2) SetACR(acrValue string) {
o.Spec.ACR = acrValue
}

// SetProvider sets the identity provider.
func (o *OIDCConnectorV2) SetProvider(identityProvider string) {
o.Spec.Provider = identityProvider
}

// SetScope sets additional scopes set by provider
func (o *OIDCConnectorV2) SetScope(scope []string) {
o.Spec.Scope = scope
Expand Down Expand Up @@ -300,6 +318,16 @@ func (o *OIDCConnectorV2) GetRedirectURL() string {
return o.Spec.RedirectURL
}

// GetACR returns the Authentication Context Class Reference (ACR) value.
func (o *OIDCConnectorV2) GetACR() string {
return o.Spec.ACR
}

// GetProvider returns the identity provider.
func (o *OIDCConnectorV2) GetProvider() string {
return o.Spec.Provider
}

// Display - Friendly name for this provider.
func (o *OIDCConnectorV2) GetDisplay() string {
if o.Spec.Display != "" {
Expand Down Expand Up @@ -497,6 +525,11 @@ type OIDCConnectorSpecV2 struct {
// client's browser back to it after successfull authentication
// Should match the URL on Provider's side
RedirectURL string `json:"redirect_url"`
// ACR is an Authentication Context Class Reference value. The meaning of the ACR
// value is context-specific and varies for identity providers.
ACR string `json:"acr_values,omitempty"`
// Provider is the external identity provider.
Provider string `json:"provider,omitempty"`
// Display - Friendly name for this provider.
Display string `json:"display,omitempty"`
// Scope is additional scopes set by provder
Expand All @@ -515,6 +548,8 @@ var OIDCConnectorSpecV2Schema = fmt.Sprintf(`{
"client_id": {"type": "string"},
"client_secret": {"type": "string"},
"redirect_url": {"type": "string"},
"acr_values": {"type": "string"},
"provider": {"type": "string"},
"display": {"type": "string"},
"scope": {
"type": "array",
Expand Down
2 changes: 1 addition & 1 deletion version.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
package teleport

const (
Version = "2.0.0-rc.4"
Version = "2.1.0-alpha.2"
)

// Gitref variable is automatically set to the output of git-describe
Expand Down

0 comments on commit 415dea4

Please sign in to comment.