mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-03-21 21:47:11 +02:00
Extract roles from Keycloak Access Tokens
This commit is contained in:
parent
4c0beb373f
commit
ab54de38cc
@ -508,6 +508,7 @@ type LegacyProvider struct {
|
|||||||
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` // Deprecated by OIDC 1.0
|
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` // Deprecated by OIDC 1.0
|
||||||
UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim"`
|
UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim"`
|
||||||
AllowedGroups []string `flag:"allowed-group" cfg:"allowed_groups"`
|
AllowedGroups []string `flag:"allowed-group" cfg:"allowed_groups"`
|
||||||
|
AllowedRoles []string `flag:"allowed-role" cfg:"allowed_roles"`
|
||||||
|
|
||||||
AcrValues string `flag:"acr-values" cfg:"acr_values"`
|
AcrValues string `flag:"acr-values" cfg:"acr_values"`
|
||||||
JWTKey string `flag:"jwt-key" cfg:"jwt_key"`
|
JWTKey string `flag:"jwt-key" cfg:"jwt_key"`
|
||||||
@ -563,6 +564,7 @@ func legacyProviderFlagSet() *pflag.FlagSet {
|
|||||||
|
|
||||||
flagSet.String("user-id-claim", providers.OIDCEmailClaim, "(DEPRECATED for `oidc-email-claim`) which claim contains the user ID")
|
flagSet.String("user-id-claim", providers.OIDCEmailClaim, "(DEPRECATED for `oidc-email-claim`) which claim contains the user ID")
|
||||||
flagSet.StringSlice("allowed-group", []string{}, "restrict logins to members of this group (may be given multiple times)")
|
flagSet.StringSlice("allowed-group", []string{}, "restrict logins to members of this group (may be given multiple times)")
|
||||||
|
flagSet.StringSlice("allowed-role", []string{}, "(keycloak-oidc) restrict logins to members of these roles (may be given multiple times)")
|
||||||
|
|
||||||
return flagSet
|
return flagSet
|
||||||
}
|
}
|
||||||
@ -659,6 +661,7 @@ func (l *LegacyProvider) convert() (Providers, error) {
|
|||||||
case "keycloak":
|
case "keycloak":
|
||||||
provider.KeycloakConfig = KeycloakOptions{
|
provider.KeycloakConfig = KeycloakOptions{
|
||||||
Groups: l.KeycloakGroups,
|
Groups: l.KeycloakGroups,
|
||||||
|
Roles: l.AllowedRoles,
|
||||||
}
|
}
|
||||||
case "gitlab":
|
case "gitlab":
|
||||||
provider.GitLabConfig = GitLabOptions{
|
provider.GitLabConfig = GitLabOptions{
|
||||||
|
@ -78,6 +78,7 @@ type Provider struct {
|
|||||||
type KeycloakOptions struct {
|
type KeycloakOptions struct {
|
||||||
// Group enables to restrict login to members of indicated group
|
// Group enables to restrict login to members of indicated group
|
||||||
Groups []string `json:"groups,omitempty"`
|
Groups []string `json:"groups,omitempty"`
|
||||||
|
Roles []string `json:"roles,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AzureOptions struct {
|
type AzureOptions struct {
|
||||||
|
@ -251,6 +251,7 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
|
|||||||
if p.Verifier == nil {
|
if p.Verifier == nil {
|
||||||
msgs = append(msgs, "keycloak-oidc provider requires an oidc issuer URL")
|
msgs = append(msgs, "keycloak-oidc provider requires an oidc issuer URL")
|
||||||
}
|
}
|
||||||
|
p.AddAllowedRoles(o.Providers[0].KeycloakConfig.Roles)
|
||||||
case *providers.GoogleProvider:
|
case *providers.GoogleProvider:
|
||||||
if o.Providers[0].GoogleConfig.ServiceAccountJSON != "" {
|
if o.Providers[0].GoogleConfig.ServiceAccountJSON != "" {
|
||||||
file, err := os.Open(o.Providers[0].GoogleConfig.ServiceAccountJSON)
|
file, err := os.Open(o.Providers[0].GoogleConfig.ServiceAccountJSON)
|
||||||
|
@ -2,8 +2,10 @@ package providers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
const keycloakOIDCProviderName = "Keycloak OIDC"
|
const keycloakOIDCProviderName = "Keycloak OIDC"
|
||||||
@ -25,6 +27,15 @@ func NewKeycloakOIDCProvider(p *ProviderData) *KeycloakOIDCProvider {
|
|||||||
|
|
||||||
var _ Provider = (*KeycloakOIDCProvider)(nil)
|
var _ Provider = (*KeycloakOIDCProvider)(nil)
|
||||||
|
|
||||||
|
// AddAllowedRoles sets Keycloak roles that are authorized.
|
||||||
|
// Assumes `SetAllowedGroups` is already called on groups and appends to that
|
||||||
|
// with `role:` prefixed roles.
|
||||||
|
func (p *KeycloakOIDCProvider) AddAllowedRoles(roles []string) {
|
||||||
|
for _, role := range roles {
|
||||||
|
p.AllowedGroups[formatRole(role)] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// EnrichSession is called after Redeem to allow providers to enrich session fields
|
// EnrichSession is called after Redeem to allow providers to enrich session fields
|
||||||
// such as User, Email, Groups with provider specific API calls.
|
// such as User, Email, Groups with provider specific API calls.
|
||||||
func (p *KeycloakOIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
|
func (p *KeycloakOIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
|
||||||
@ -36,6 +47,83 @@ func (p *KeycloakOIDCProvider) EnrichSession(ctx context.Context, s *sessions.Se
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *KeycloakOIDCProvider) extractRoles(ctx context.Context, s *sessions.SessionState) error {
|
func (p *KeycloakOIDCProvider) extractRoles(ctx context.Context, s *sessions.SessionState) error {
|
||||||
// TODO: Implement me with Access Token Role claim extraction logic
|
claims, err := p.getAccessClaims(ctx, s)
|
||||||
return ErrNotImplemented
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var roles []string
|
||||||
|
roles = append(roles, claims.RealmAccess.Roles...)
|
||||||
|
roles = append(roles, getClientRoles(claims)...)
|
||||||
|
|
||||||
|
// Add to groups list with `role:` prefix to distinguish from groups
|
||||||
|
for _, role := range roles {
|
||||||
|
s.Groups = append(s.Groups, formatRole(role))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type realmAccess struct {
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type accessClaims struct {
|
||||||
|
RealmAccess realmAccess `json:"realm_access"`
|
||||||
|
ResourceAccess map[string]interface{} `json:"resource_access"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *KeycloakOIDCProvider) getAccessClaims(ctx context.Context, s *sessions.SessionState) (*accessClaims, error) {
|
||||||
|
// HACK: This isn't an ID Token, but has similar structure & signing
|
||||||
|
token, err := p.Verifier.Verify(ctx, s.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var claims *accessClaims
|
||||||
|
if err = token.Claims(&claims); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getClientRoles extracts client roles from the `resource_access` claim with
|
||||||
|
// the format `client:role`.
|
||||||
|
//
|
||||||
|
// ResourceAccess format:
|
||||||
|
// "resource_access": {
|
||||||
|
// "clientA": {
|
||||||
|
// "roles": [
|
||||||
|
// "roleA"
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// "clientB": {
|
||||||
|
// "roles": [
|
||||||
|
// "roleA",
|
||||||
|
// "roleB",
|
||||||
|
// "roleC"
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
func getClientRoles(claims *accessClaims) []string {
|
||||||
|
var clientRoles []string
|
||||||
|
for clientName, access := range claims.ResourceAccess {
|
||||||
|
accessMap, ok := access.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
logger.Errorf("Unable to parse client roles from claims for client: %v", clientName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var roles interface{}
|
||||||
|
if roles, ok = accessMap["roles"]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, role := range roles.([]interface{}) {
|
||||||
|
clientRoles = append(clientRoles, fmt.Sprintf("%s:%s", clientName, role))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clientRoles
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatRole(role string) string {
|
||||||
|
return fmt.Sprintf("role:%s", role)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user