Skip to content

Feature/openid connect #3093

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

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d189ffc
added openid auth support
Apr 9, 2021
5f59e99
merged in changes from main
dylanturn Apr 10, 2021
e1abd86
scrubbed some small changes that I didn't mean to include
dylanturn Apr 10, 2021
906e7b6
Updated the yarn lockfile. Updated the OIDC user console log with debug.
dylanturn Apr 10, 2021
12c813f
ran yarn fmt, and made sure yarn lint, _audit, test:unit would pass.
dylanturn Apr 10, 2021
0440796
made the oidc group claim optional
dylanturn Apr 15, 2021
4fcfab2
Added documentation that explains how to configure and OpenID-Connect…
dylanturn Apr 15, 2021
a11d9a9
added markdown to reduce the image sizes
dylanturn Apr 15, 2021
1532cb5
resized the images
dylanturn Apr 15, 2021
2a0c448
updated the doc images
dylanturn Apr 15, 2021
6add98b
updated the doc images
dylanturn Apr 15, 2021
3ac81d9
updated the doc images
dylanturn Apr 15, 2021
aabfded
Updated the documentation with a couple extra steps after testing it out
dylanturn Apr 15, 2021
c3c305c
updated the screenshot to make sure the code-server endpoint url is c…
dylanturn Apr 15, 2021
153e89b
Merge pull request #4 from turnbros/feature/openid-connect-remove-cla…
dylanturn Apr 15, 2021
d5ffe24
Merge pull request #5 from turnbros/documentation/openid-setup-guides
dylanturn Apr 15, 2021
9e2b4f3
replace console.debug with logger.debug
dylanturn Apr 16, 2021
af31604
updated the logger.debug and ran yarn fmt
dylanturn Apr 16, 2021
793817b
Merge pull request #6 from turnbros/fix/openid-connect-logging
dylanturn Apr 16, 2021
d503138
updated the logger format to match what was actually requested
dylanturn Apr 16, 2021
f2f731a
Merge pull request #7 from turnbros/fix/openid-connect-logging
dylanturn Apr 16, 2021
becd7da
Merge branch 'main' into feature/openid-connect
dylanturn Apr 22, 2021
ed29b32
bumped the jose version to appease the audit gods
dylanturn Apr 22, 2021
cf1ad0d
Apply suggestions from code review
dylanturn Apr 23, 2021
ab718f1
update the documentation to better conform to the style guide
dylanturn Apr 23, 2021
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
152 changes: 152 additions & 0 deletions docs/openid-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Configuring OpenID authentication

The purpose of this guide is to demonstrate the process of configuring an OpenID application as an authentication and authorization source for code-server.

More information regarding how OpenID Connect works can be found here:

- <https://openid.net/connect/faq/>
- <https://auth0.com/docs/protocols/openid-connect-protocol>
- <https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc>

## Prerequisites

- Either Keycloak has been deployed or an existing Auth0/Okta account exists.
- Auth0 Signup: <https://auth0.com/signup>
- Okta Signup: <https://developer.okta.com/signup/>
- Getting started with Keycloak: <https://www.keycloak.org/docs/latest/getting_started/>
- A value needs to be generated for `openid-secret`. This value will be used to encrypt the OpenID Connect session token.
- MacOS/Linux: `openssl rand -base64 25`
- Windows: <https://activedirectoryfaq.com/2017/08/creating-individual-random-passwords/>

## Auth0 OpenID Connect application setup

### Creating the application

1. Navigate to the application management section of your Auth0 dashboard at `https://manage.auth0.com/dashboard/us/{{auth0_account_name}}/applications`
2. Click the **Create Application** button in the top right of the page.
3. Either provide a name for this application or use the default, then select **Regular Web Application** as the application type and click the blue **Create** button.
<kbd>
<img src="assets/openid-connect/auth0/create-application.png" />
</kbd>

### Gather the client ID

1. Make note of the `Client ID` value. This value will be used in the code-server `openid-client-id` configuration variable.
<kbd>
<img src="assets/openid-connect/auth0/application-client-id.png" />
</kbd>

### Update the application URLs

1. Update the **_Allowed Callback URL_** and **_Allowed Logout URLs_** fields so that they point to the correct code-server endpoint.
<kbd>
<img src="assets/openid-connect/auth0/update-application.png" />
</kbd>

### Example Auth0 code-server configuration

---

**NOTE:** The `openid-issuer-base-url` should be set to the value of the **Domain** field of the Okta application.

---

```yml
bind-addr: 127.0.0.1:8080
cert: false

auth: openid
openid-issuer-base-url: "https://cdr.auth0.com"
openid-client-id: "yg3IIz2alcikRqjqOsnRJABn8Uthfrtv"
openid-base-url: "http://localhost:8080"
openid-secret: "{prerequisites_openid-secret_value}"
```

## Okta OpenID Connect application setup

### Creating the application

1. Navigate to the **Add Application** page of your Okta dashboard at `https://{{okta_account_name}}-admin.okta.com/admin/apps/add-app`
2. Click the **Create New App** button located in the upper right of the page.
3. Set the **Platform** to **Web** and **Sign on method** to **OpenID Connect**. Then click **Create**
<kbd>
<img src="assets/openid-connect/okta/create-application.png" />
</kbd>

### Update the application

1. Update the **Application name** field with the desired name for this application.
2. Update the **Login redirect URIs** and **Logout redirect URIs (Optional)** fields so that they point to the correct code-server endpoint then click **Save**
<kbd><img src="assets/openid-connect/okta/update-application.png"/></kbd>
3. To update the **Allowed grant types** start by navigating to the **General Settings** section and clicking the **Edit** link in the top right corner of the section card.
4. Once in the edit view, locate the **Allowed grant types** checkbox list and make sure the boxes for **Implicit (Hybrid)** and **Allow ID Token with implicit grant type** are checked, then scroll to the bottom of the page and click **Save**.
<kbd><img src="assets/openid-connect/okta/update-application-grants.png"/></kbd>
5. Lastly, ensure a user is assigned to this application by navigating to the **Assignments** tab, clicking on **Assign**, then selecting **Assign to People**.
<kbd><img src="assets/openid-connect/okta/update-application-assignments.png"/></kbd>

### Gather the client ID

1. Make note of the `Client ID` value. This value will be used in the code-server `openid-client-id` configuration variable.
<kbd>
<img src="assets/openid-connect/okta/application-client-id.png" />
</kbd>

### Example code-server configuration

---

**NOTE:** The `openid-issuer-base-url` should be set to the value of the **Okta domain** field of the Okta application.

---

```yml
bind-addr: 127.0.0.1:8080
cert: false

auth: openid
openid-issuer-base-url: "https://cdr.okta.com"
openid-client-id: "0oal8azjyekLwJXan5d6"
openid-base-url: "http://localhost:8080"
openid-secret: "{prerequisites_openid-secret_value}"
```

## Keycloak OpenID Connect client setup

### Creating the client

1. Once logged into the Keycloak instance, navigate to the **Clients** page by clicking the **Clients** link in the navigation menu on the left side of the page.
2. Begin the client creation process by clicking the **Create** button located in the top right corner of the client list.
3. Fill out the client creation fields then click **Save** - Client ID**: This is the value that will later be populated in `openid-client-id`. This value is entirely up the the user or process creating the client. - **Client Protocol**: This value should be set to **openid-connect** - **Root URL**: This field should be populated with the code-server base URL.
<kbd>
<img src="assets/openid-connect/keycloak/create-client.png" />
</kbd>

### Update the client name

1. Once the **Save** button in the **Add Client** window has been clicked, the client will be created and the page will be redirected to the client settings view. From inside that view proceed to name the newly create client by populating the **Name** field.
2. Enable implicit flow by changing **Implicit Flow Enabled** from **OFF** to **ON**.
3. Scroll to the bottom of the page and click **Save**.
<kbd>
<img src="assets/openid-connect/keycloak/update-client.png" />
</kbd>

### Example code-server configuration

---

**NOTE:** The `openid-issuer-base-url` will depend on the Keycloak realm. In the example below the realm is called `master`.

**NOTE:** The `openid-client-id` value should be set to the value given to the **Client ID** when the client was first created.

---

```yml
bind-addr: 127.0.0.1:8080
cert: false

auth: openid
openid-issuer-base-url: "https://keycloak.local/auth/realms/master/.well-known/openid-configuration"
openid-client-id: "example-code-server"
openid-base-url: "http://localhost:8080"
openid-secret: "{prerequisites_openid-secret_value}"
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"cookie-parser": "^1.4.5",
"env-paths": "^2.2.0",
"express": "^5.0.0-alpha.8",
"express-openid-connect": "^2.3.1",
"http-proxy": "^1.18.0",
"httpolyglot": "^0.1.2",
"js-yaml": "^4.0.0",
Expand Down
16 changes: 15 additions & 1 deletion src/node/app.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { logger } from "@coder/logger"
import compression from "compression"
import express, { Express } from "express"
import { auth } from "express-openid-connect"
import { promises as fs } from "fs"
import http from "http"
import * as httpolyglot from "httpolyglot"
import * as util from "../common/util"
import { DefaultedArgs } from "./cli"
import { AuthType, DefaultedArgs } from "./cli"
import { handleUpgrade } from "./wsRouter"

/**
Expand Down Expand Up @@ -59,6 +60,19 @@ export const createApp = async (args: DefaultedArgs): Promise<[Express, Express,
const wsApp = express()
handleUpgrade(wsApp, server)

if (args.auth === AuthType.Openid) {
const openidConfig = {
authRequired: true,
auth0Logout: true,
issuerBaseURL: args["openid-issuer-base-url"],
clientID: args["openid-client-id"],
baseURL: args["openid-base-url"],
secret: args["openid-secret"],
}
app.use(auth(openidConfig))
wsApp.use(auth(openidConfig))
}

return [app, wsApp, server]
}

Expand Down
31 changes: 31 additions & 0 deletions src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { canConnect, generateCertificate, generatePassword, humanPath, paths } f
export enum AuthType {
Password = "password",
None = "none",
Openid = "openid",
}

export class Optional<T> {
Expand All @@ -29,6 +30,12 @@ export interface Args extends VsArgs {
config?: string
auth?: AuthType
password?: string
"openid-issuer-base-url"?: string
"openid-client-id"?: string
"openid-base-url"?: string
"openid-secret"?: string
"openid-group-claim"?: OptionalString
"openid-user-group"?: string
"hashed-password"?: string
cert?: OptionalString
"cert-host"?: string
Expand Down Expand Up @@ -105,6 +112,30 @@ const options: Options<Required<Args>> = {
type: "string",
description: "The password for password authentication (can only be passed in via $PASSWORD or the config file).",
},
"openid-issuer-base-url": {
type: "string",
description: "The base url for providers .well-known endpoint",
},
"openid-client-id": {
type: "string",
description: "The client ID of this OpenID application",
},
"openid-base-url": {
type: "string",
description: "The base URL for code-server",
},
"openid-secret": {
type: "string",
description: "A long secret used to encrypt the session cookie",
},
"openid-group-claim": {
type: OptionalString,
description: "A claim that contains the authenticated users group assignments",
},
"openid-user-group": {
type: "string",
description: "The group that a user will need to be a member of to access this instance",
},
"hashed-password": {
type: "string",
description:
Expand Down
35 changes: 35 additions & 0 deletions src/node/http.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { field, logger } from "@coder/logger"
import * as express from "express"
import { RequestContext } from "express-openid-connect"
import * as expressCore from "express-serve-static-core"
import qs from "qs"
import safeCompare from "safe-compare"
Expand All @@ -16,6 +17,7 @@ declare global {
export interface Request {
args: DefaultedArgs
heart: Heart
oidc: RequestContext
}
}
}
Expand Down Expand Up @@ -69,6 +71,39 @@ export const authenticated = (req: express.Request): boolean => {
? safeCompare(req.cookies.key, req.args["hashed-password"])
: req.args.password && safeCompare(req.cookies.key, hash(req.args.password)))
)
case AuthType.Openid:
if (req.oidc.isAuthenticated()) {
logger.debug("User authenticated with OpenID Connect", field("user", req.oidc.user))

// Check to see if a group claim was specified.
// If there was no group claim specified the user will be considered authorized.
if (!req.args["openid-group-claim"]) {
return true
}

// With the user authenticated we need to check the group claims to make sure they're authorized.
for (const key in req.oidc.idTokenClaims) {
const claims = <string[]>req.oidc.idTokenClaims[key]
if (key === req.args["openid-group-claim"] && req.args["openid-group-claim"].value) {
for (const value in claims) {
if (req.args["openid-user-group"] === claims[value]) {
logger.debug("User authorized", field("user", req.oidc.user))
return true
}
}
}
}

// Throw an error informing the user that they're unauthorized.
logger.debug("User not authorized", field("user", req.oidc.user))
throw new HttpError("Unauthorized", HttpCode.Unauthorized)
}

// Returning false means the user isn't authenticated.
// This should trigger a redirect so the user can get authenticated.
logger.debug("User not authenticated using OpenID Connect", field("user", req.oidc.user))
return false

default:
throw new Error(`Unsupported auth type ${req.args.auth}`)
}
Expand Down
2 changes: 1 addition & 1 deletion test/unit/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ describe("parser", () => {

it("should error if value is invalid", () => {
expect(() => parse(["--port", "foo"])).toThrowError(/--port must be a number/)
expect(() => parse(["--auth", "invalid"])).toThrowError(/--auth valid values: \[password, none\]/)
expect(() => parse(["--auth", "invalid"])).toThrowError(/--auth valid values: \[password, none, openid\]/)
expect(() => parse(["--log", "invalid"])).toThrowError(/--log valid values: \[trace, debug, info, warn, error\]/)
})

Expand Down
Loading