Skip to content

Add support for custom group claims #133

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,11 @@ sudo opkssh add root [email protected] google
To allow a group, `ssh-users`, to ssh to your server as `root`, run:

```bash
sudo opkssh add root oidc:groups:ssh-users google
sudo opkssh add root oidc|groups|ssh-users google

or

sudo opkssh add root oidc|custom-groups-field|ssh-users google
```

## How it works
Expand Down Expand Up @@ -219,7 +223,7 @@ Linux user accounts are typically referred to in SSH as *principals* and we cont
- Email - the email of the identity
- Subject ID - an unique ID for the user set by the OP. This is the `sub` claim in the ID Token.
- Group - the name of the group that the user is part of. This uses the `groups` claim which is presumed to
be an array. The group identifier uses a structured identifier. I.e. `oidc:groups:{groupId}`. Replace the `groupId`
be an array. The group identifier uses a structured identifier. I.e. `oidc|groups|{groupId}`. Replace the `groupId`
with the id of your group.
- Column 3: Issuer URI

Expand All @@ -231,7 +235,7 @@ root [email protected] https://accounts.google.com
dev [email protected] https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0

# Group identifier
dev oidc:groups:developer https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0
dev oidc|groups|developer https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0
```

To add new rule run:
Expand Down Expand Up @@ -264,7 +268,7 @@ That is, if it is in `/home/alice/.opk/auth_id` it can only specify who can assu
alice [email protected] https://accounts.google.com

# Group identifier
dev oidc:groups:developer https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0
dev oidc|groups|developer https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0
```

It requires the following permissions:
Expand Down
64 changes: 60 additions & 4 deletions policy/enforcer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,58 @@ type Enforcer struct {
PolicyLoader Loader
}

// type for Identity Token requiredClaims
type requiredClaims struct {
Email string `json:"email"`
Sub string `json:"sub"`
}

// type for Identity Token checkedClaims
type checkedClaims struct {
Email string `json:"email"`
Sub string `json:"sub"`
Groups []string `json:"groups"`
Email string `json:"email"`
Sub string `json:"sub"`
PotentialGroupClaims map[string][]string `json:"potentialGroupClaims,omitempty"`
}

func (s *checkedClaims) UnmarshalJSON(data []byte) error {
// First unmarshal the static fields
var claims requiredClaims
err := json.Unmarshal(data, &claims)
if err != nil {
return err
}

// Unmarshal the rest
var schema map[string]interface{}
err = json.Unmarshal([]byte(data), &schema)
if err != nil {
return err
}

s.Email = claims.Email
s.Sub = claims.Sub
s.PotentialGroupClaims = make(map[string][]string)
// Add all []string types as potential group claims
for key, value := range schema {
groups, ok := value.([]interface{})
if ok {
isPotentialGroup := true
strGroups := make([]string, len(groups))
for i, v := range groups {
str, ok := v.(string)
if !ok {
isPotentialGroup = false
break
}
strGroups[i] = str
}
if isPotentialGroup {
s.PotentialGroupClaims[key] = strGroups
}
}
}

return nil
}

// Validates that the server defined identity attribute matches the
Expand All @@ -44,7 +91,16 @@ func validateClaim(claims *checkedClaims, user *User) bool {
if strings.HasPrefix(user.IdentityAttribute, "oidc:groups") {
oidcGroupSections := strings.Split(user.IdentityAttribute, ":")

return slices.Contains(claims.Groups, oidcGroupSections[len(oidcGroupSections)-1])
return slices.Contains(claims.PotentialGroupClaims["groups"], oidcGroupSections[len(oidcGroupSections)-1])
}
if strings.HasPrefix(user.IdentityAttribute, "oidc") {
oidcGroupSections := strings.Split(user.IdentityAttribute, "|")
oidcGroupsName := oidcGroupSections[1]

return slices.Contains(
claims.PotentialGroupClaims[oidcGroupsName],
oidcGroupSections[len(oidcGroupSections)-1],
)
}

// email should be a case-insensitive check
Expand Down
Loading