Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: add group mapping option for group sync #6705

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

Merged
merged 2 commits into from
Mar 21, 2023
Merged
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
1 change: 1 addition & 0 deletions cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ flags, and YAML configuration. The precedence is as follows:
AllowSignups: cfg.OIDC.AllowSignups.Value(),
UsernameField: cfg.OIDC.UsernameField.String(),
GroupField: cfg.OIDC.GroupField.String(),
GroupMapping: cfg.OIDC.GroupMapping.Value,
SignInText: cfg.OIDC.SignInText.String(),
IconURL: cfg.OIDC.IconURL.String(),
IgnoreEmailVerified: cfg.OIDC.IgnoreEmailVerified.Value(),
Expand Down
3 changes: 3 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,12 @@ func New(options *Options) *API {
options.SSHConfig.HostnamePrefix = "coder."
}
if options.SetUserGroups == nil {
options.SetUserGroups = func(context.Context, database.Store, uuid.UUID, []string) error { return nil }
options.SetUserGroups = func(ctx context.Context, _ database.Store, id uuid.UUID, groups []string) error {
options.Logger.Warn(ctx, "attempted to assign OIDC groups without enterprise license",
slog.F("id", id), slog.F("groups", groups),
)
return nil
}
}
if options.TemplateScheduleStore == nil {
options.TemplateScheduleStore = schedule.NewAGPLTemplateScheduleStore()
Expand Down
9 changes: 9 additions & 0 deletions coderd/userauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,10 @@ type OIDCConfig struct {
// groups. If the group field is the empty string, then no group updates
// will ever come from the OIDC provider.
GroupField string
// GroupMapping controls how groups returned by the OIDC provider get mapped
// to groups within Coder.
// map[oidcGroupName]coderGroupName
GroupMapping map[string]string
// SignInText is the text to display on the OIDC login button
SignInText string
// IconURL points to the URL of an icon to display on the OIDC login button
Expand Down Expand Up @@ -651,6 +655,11 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
})
return
}

if mappedGroup, ok := api.OIDCConfig.GroupMapping[group]; ok {
group = mappedGroup
}

groups = append(groups, group)
}
} else {
Expand Down
36 changes: 23 additions & 13 deletions codersdk/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"flag"
"fmt"
"math"
"net/http"
"os"
Expand Down Expand Up @@ -199,7 +198,7 @@ func ParseSSHConfigOption(opt string) (key string, value string, err error) {
return r == ' ' || r == '='
})
if idx == -1 {
return "", "", fmt.Errorf("invalid config-ssh option %q", opt)
return "", "", xerrors.Errorf("invalid config-ssh option %q", opt)
}
return opt[:idx], opt[idx+1:], nil
}
Expand Down Expand Up @@ -248,17 +247,18 @@ type OAuth2GithubConfig struct {
}

type OIDCConfig struct {
AllowSignups clibase.Bool `json:"allow_signups" typescript:",notnull"`
ClientID clibase.String `json:"client_id" typescript:",notnull"`
ClientSecret clibase.String `json:"client_secret" typescript:",notnull"`
EmailDomain clibase.Strings `json:"email_domain" typescript:",notnull"`
IssuerURL clibase.String `json:"issuer_url" typescript:",notnull"`
Scopes clibase.Strings `json:"scopes" typescript:",notnull"`
IgnoreEmailVerified clibase.Bool `json:"ignore_email_verified" typescript:",notnull"`
UsernameField clibase.String `json:"username_field" typescript:",notnull"`
GroupField clibase.String `json:"groups_field" typescript:",notnull"`
SignInText clibase.String `json:"sign_in_text" typescript:",notnull"`
IconURL clibase.URL `json:"icon_url" typescript:",notnull"`
AllowSignups clibase.Bool `json:"allow_signups" typescript:",notnull"`
ClientID clibase.String `json:"client_id" typescript:",notnull"`
ClientSecret clibase.String `json:"client_secret" typescript:",notnull"`
EmailDomain clibase.Strings `json:"email_domain" typescript:",notnull"`
IssuerURL clibase.String `json:"issuer_url" typescript:",notnull"`
Scopes clibase.Strings `json:"scopes" typescript:",notnull"`
IgnoreEmailVerified clibase.Bool `json:"ignore_email_verified" typescript:",notnull"`
UsernameField clibase.String `json:"username_field" typescript:",notnull"`
GroupField clibase.String `json:"groups_field" typescript:",notnull"`
GroupMapping clibase.Struct[map[string]string] `json:"group_mapping" typescript:",notnull"`
SignInText clibase.String `json:"sign_in_text" typescript:",notnull"`
IconURL clibase.URL `json:"icon_url" typescript:",notnull"`
}

type TelemetryConfig struct {
Expand Down Expand Up @@ -875,6 +875,16 @@ when required by your organization's security policy.`,
Group: &deploymentGroupOIDC,
YAML: "groupField",
},
{
Name: "OIDC Group Mapping",
Description: "A map of OIDC group IDs and the group in Coder it should map to. This is useful for when OIDC providers only return group IDs.",
Flag: "oidc-group-mapping",
Env: "OIDC_GROUP_MAPPING",
Default: "{}",
Value: &c.OIDC.GroupMapping,
Group: &deploymentGroupOIDC,
YAML: "groupMapping",
},
{
Name: "OpenID Connect sign in text",
Description: "The text to show on the OpenID Connect sign in button",
Expand Down
16 changes: 16 additions & 0 deletions docs/admin/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,20 @@ CODER_OIDC_SCOPES=openid,profile,email,groups
On login, users will automatically be assigned to groups that have matching
names in Coder and removed from groups that the user no longer belongs to.

For cases when an OIDC provider only returns group IDs ([Azure AD][azure-gids])
or you want to have different group names in Coder than in your OIDC provider,
you can configure mapping between the two.

```console
# as an environment variable
CODER_OIDC_GROUP_MAPPING='{"myOIDCGroupID": "myCoderGroupName"}'
# as a flag
--oidc-group-mapping '{"myOIDCGroupID": "myCoderGroupName"}'
```

From the example above, users that belong to the `myOIDCGroupID` group in your
OIDC provider will be added to the `myCoderGroupName` group in Coder.

> **Note:** Groups are only updated on login.

[azure-gids]: https://github.com/MicrosoftDocs/azure-docs/issues/59766#issuecomment-664387195
1 change: 1 addition & 0 deletions docs/api/general.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
"client_id": "string",
"client_secret": "string",
"email_domain": ["string"],
"group_mapping": {},
"groups_field": "string",
"icon_url": {
"forceQuery": true,
Expand Down
4 changes: 4 additions & 0 deletions docs/api/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -1766,6 +1766,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
"client_id": "string",
"client_secret": "string",
"email_domain": ["string"],
"group_mapping": {},
"groups_field": "string",
"icon_url": {
"forceQuery": true,
Expand Down Expand Up @@ -2110,6 +2111,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
"client_id": "string",
"client_secret": "string",
"email_domain": ["string"],
"group_mapping": {},
"groups_field": "string",
"icon_url": {
"forceQuery": true,
Expand Down Expand Up @@ -2771,6 +2773,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
"client_id": "string",
"client_secret": "string",
"email_domain": ["string"],
"group_mapping": {},
"groups_field": "string",
"icon_url": {
"forceQuery": true,
Expand Down Expand Up @@ -2801,6 +2804,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
| `client_id` | string | false | | |
| `client_secret` | string | false | | |
| `email_domain` | array of string | false | | |
| `group_mapping` | object | false | | |
| `groups_field` | string | false | | |
| `icon_url` | [clibase.URL](#clibaseurl) | false | | |
| `ignore_email_verified` | boolean | false | | |
Expand Down
45 changes: 45 additions & 0 deletions enterprise/coderd/userauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,51 @@ func TestUserOIDC(t *testing.T) {
require.NoError(t, err)
require.Len(t, group.Members, 1)
})
t.Run("AssignsMapped", func(t *testing.T) {
t.Parallel()

ctx, _ := testutil.Context(t)
conf := coderdtest.NewOIDCConfig(t, "")

oidcGroupName := "pingpong"
coderGroupName := "bingbong"
Comment on lines +76 to +77
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

classic

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

classic


config := conf.OIDCConfig(t, jwt.MapClaims{}, func(cfg *coderd.OIDCConfig) {
cfg.GroupMapping = map[string]string{oidcGroupName: coderGroupName}
})
config.AllowSignups = true

client := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
OIDCConfig: config,
},
})
_ = coderdtest.CreateFirstUser(t, client)
coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
AllFeatures: true,
})

admin, err := client.User(ctx, "me")
require.NoError(t, err)
require.Len(t, admin.OrganizationIDs, 1)

group, err := client.CreateGroup(ctx, admin.OrganizationIDs[0], codersdk.CreateGroupRequest{
Name: coderGroupName,
})
require.NoError(t, err)
require.Len(t, group.Members, 0)

resp := oidcCallback(t, client, conf.EncodeClaims(t, jwt.MapClaims{
"email": "[email protected]",
"groups": []string{oidcGroupName},
}))
assert.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)

group, err = client.Group(ctx, group.ID)
require.NoError(t, err)
require.Len(t, group.Members, 1)
})

t.Run("AddThenRemove", func(t *testing.T) {
t.Parallel()

Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -376,10 +376,6 @@ github.com/coder/retry v1.3.1-0.20230210155434-e90a2e1e091d h1:09JG37IgTB6n3ouX9
github.com/coder/retry v1.3.1-0.20230210155434-e90a2e1e091d/go.mod h1:r+1J5i/989wt6CUeNSuvFKKA9hHuKKPMxdzDbTuvwwk=
github.com/coder/ssh v0.0.0-20220811105153-fcea99919338 h1:tN5GKFT68YLVzJoA8AHuiMNJ0qlhoD3pGN3JY9gxSko=
github.com/coder/ssh v0.0.0-20220811105153-fcea99919338/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914=
github.com/coder/tailscale v1.1.1-0.20230314023417-d9efcc0ac972 h1:193YGsJz8hc4yxqAclE36paKl+9CQ6KGLgdleIguCVE=
github.com/coder/tailscale v1.1.1-0.20230314023417-d9efcc0ac972/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA=
github.com/coder/tailscale v1.1.1-0.20230321164649-3362540e3026 h1:6YnWw08eQEGc/7KyweGWP8urOb9TDlo6S35ZqNm8qsQ=
github.com/coder/tailscale v1.1.1-0.20230321164649-3362540e3026/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA=
github.com/coder/tailscale v1.1.1-0.20230321171725-fed359a0cafa h1:EjRGgTz7BUECmbV8jHTi1/rKdDjJESGSlm1Jp7evvCQ=
github.com/coder/tailscale v1.1.1-0.20230321171725-fed359a0cafa/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA=
github.com/coder/terraform-provider-coder v0.6.20 h1:bVyITX9JlbnGzKzTj0qi/JziUCGqD2DiN3cXaWyDcxE=
Expand Down
3 changes: 3 additions & 0 deletions site/src/api/typesGenerated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,9 @@ export interface OIDCConfig {
readonly ignore_email_verified: boolean
readonly username_field: string
readonly groups_field: string
// Named type "github.com/coder/coder/cli/clibase.Struct[map[string]string]" unknown, using "any"
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
readonly group_mapping: any
readonly sign_in_text: string
readonly icon_url: string
}
Expand Down