Skip to content

Commit

Permalink
Allow roles to be specified for Keycloak
Browse files Browse the repository at this point in the history
  • Loading branch information
khawaga committed Sep 8, 2020
1 parent ef08d01 commit 148c308
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 1 deletion.
2 changes: 1 addition & 1 deletion contrib/oauth2-proxy_autocomplete.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ _oauth2_proxy() {
COMPREPLY=( $(compgen -W 'X-Real-IP X-Forwarded-For X-ProxyUser-IP' -- ${cur}) )
return 0
;;
--@(http-address|https-address|redirect-url|upstream|basic-auth-password|skip-auth-regex|flush-interval|extra-jwt-issuers|email-domain|whitelist-domain|trusted-ip|keycloak-group|azure-tenant|bitbucket-team|bitbucket-repository|github-org|github-team|github-repo|github-token|gitlab-group|github-user|google-group|google-admin-email|google-service-account-json|client-id|client_secret|banner|footer|proxy-prefix|ping-path|cookie-name|cookie-secret|cookie-domain|cookie-path|cookie-expire|cookie-refresh|cookie-samesite|redist-sentinel-master-name|redist-sentinel-connection-urls|redist-cluster-connection-urls|logging-max-size|logging-max-age|logging-max-backups|standard-logging-format|request-logging-format|exclude-logging-paths|auth-logging-format|oidc-issuer-url|oidc-jwks-url|login-url|redeem-url|profile-url|resource|validate-url|scope|approval-prompt|signature-key|acr-values|jwt-key|pubjwk-url))
--@(http-address|https-address|redirect-url|upstream|basic-auth-password|skip-auth-regex|flush-interval|extra-jwt-issuers|email-domain|whitelist-domain|trusted-ip|keycloak-group|keycloak-roles|azure-tenant|bitbucket-team|bitbucket-repository|github-org|github-team|github-repo|github-token|gitlab-group|github-user|google-group|google-admin-email|google-service-account-json|client-id|client_secret|banner|footer|proxy-prefix|ping-path|cookie-name|cookie-secret|cookie-domain|cookie-path|cookie-expire|cookie-refresh|cookie-samesite|redist-sentinel-master-name|redist-sentinel-connection-urls|redist-cluster-connection-urls|logging-max-size|logging-max-age|logging-max-backups|standard-logging-format|request-logging-format|exclude-logging-paths|auth-logging-format|oidc-issuer-url|oidc-jwks-url|login-url|redeem-url|profile-url|resource|validate-url|scope|approval-prompt|signature-key|acr-values|jwt-key|pubjwk-url))
return 0
;;
esac
Expand Down
3 changes: 3 additions & 0 deletions docs/2_auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,12 @@ Make sure you set the following to the appropriate url:
-redeem-url="http(s)://<keycloak host>/realms/<your realm>/protocol/openid-connect/token"
-validate-url="http(s)://<keycloak host>/realms/<your realm>/protocol/openid-connect/userinfo"
-keycloak-group=<user_group>
-keycloak-roles=<user_roles>

The group management in keycloak is using a tree. If you create a group named admin in keycloak you should define the 'keycloak-group' value to /admin.

You can restrict logins to users with specific roles by passing a comma-separated list of roles to 'keycloak-roles', the users must have all of the specified roles in order to be granted access. Both realm and client roles can be used, client roles should be in the following format 'client:role'.

### GitLab Auth Provider

Whether you are using GitLab.com or self-hosting GitLab, follow [these steps to add an application](https://docs.gitlab.com/ce/integration/oauth_provider.html). Make sure to enable at least the `openid`, `profile` and `email` scopes.
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Options struct {

AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group"`
KeycloakRoles []string `flag:"keycloak-roles" cfg:"keycloak_roles"`
AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant"`
BitbucketTeam string `flag:"bitbucket-team" cfg:"bitbucket_team"`
BitbucketRepository string `flag:"bitbucket-repository" cfg:"bitbucket_repository"`
Expand Down Expand Up @@ -203,6 +204,7 @@ func NewFlagSet() *pflag.FlagSet {
flagSet.StringSlice("email-domain", []string{}, "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email")
flagSet.StringSlice("whitelist-domain", []string{}, "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)")
flagSet.String("keycloak-group", "", "restrict login to members of this group.")
flagSet.StringSlice("keycloak-roles", []string{}, "restrict login to members with these roles")
flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.")
flagSet.String("bitbucket-team", "", "restrict logins to members of this team")
flagSet.String("bitbucket-repository", "", "restrict logins to user with access to this repository")
Expand Down
1 change: 1 addition & 0 deletions pkg/validation/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
p.SetUsers(o.GitHubUsers)
case *providers.KeycloakProvider:
p.SetGroup(o.KeycloakGroup)
p.SetRoles(o.KeycloakRoles)
case *providers.GoogleProvider:
if o.GoogleServiceAccountJSON != "" {
file, err := os.Open(o.GoogleServiceAccountJSON)
Expand Down
59 changes: 59 additions & 0 deletions providers/keycloak.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ package providers

import (
"context"
"fmt"
"net/url"

"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests"
"gopkg.in/square/go-jose.v2/jwt"
)

type KeycloakProvider struct {
*ProviderData
Group string
Roles []string
}

var _ Provider = (*KeycloakProvider)(nil)
Expand Down Expand Up @@ -63,12 +66,17 @@ func (p *KeycloakProvider) SetGroup(group string) {
p.Group = group
}

func (p *KeycloakProvider) SetRoles(roles []string) {
p.Roles = roles
}

func (p *KeycloakProvider) GetEmailAddress(ctx context.Context, s *sessions.SessionState) (string, error) {
json, err := requests.New(p.ValidateURL.String()).
WithContext(ctx).
SetHeader("Authorization", "Bearer "+s.AccessToken).
Do().
UnmarshalJSON()

if err != nil {
logger.Errorf("failed making request %s", err)
return "", err
Expand All @@ -95,5 +103,56 @@ func (p *KeycloakProvider) GetEmailAddress(ctx context.Context, s *sessions.Sess
}
}

if len(p.Roles) > 0 {
claims := make(map[string]interface{})

// Decode JWT token without verifying the signature
token, err := jwt.ParseSigned(s.AccessToken)

if err != nil {
logger.Printf("failed to parse token %s", err)
return "", nil
}

// Parse claims
if err := token.UnsafeClaimsWithoutVerification(&claims); err != nil {
logger.Printf("failed to parse claims %s", err)
}

var roleList []string

if realmRoles, found := claims["realm_access"].(map[string]interface{}); found {
if roles, found := realmRoles["roles"]; found {
for _, r := range roles.([]interface{}) {
roleList = append(roleList, fmt.Sprintf("%s", r))
}
}
}

if clientRoles, found := claims["resource_access"].(map[string]interface{}); found {
for name, list := range clientRoles {
scopes := list.(map[string]interface{})
if roles, found := scopes["roles"]; found {
for _, r := range roles.([]interface{}) {
roleList = append(roleList, fmt.Sprintf("%s:%s", name, r))
}
}
}
}

roleSet := make(map[string]bool)

for _, role := range roleList {
roleSet[role] = true
}

for _, role := range p.Roles {
if !roleSet[role] {
logger.Printf("one or more roles not found, access denied")
return "", nil
}
}
}

return json.Get("email").String()
}

0 comments on commit 148c308

Please sign in to comment.