Skip to content
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

Basic-auth #167

Merged
merged 2 commits into from
May 24, 2024
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
39 changes: 25 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,34 +61,42 @@ Some number values - for example: "_total returned results_ " - may be specified

For `int64` values whose type is not known ahead of time, such as job variables, you can pass an annotated data transfer object (DTO) to decode them reliably. If no DTO is specified, the default behavior of the SDK is to serialise all numbers to JavaScript `number`, and if a number value is detected at a runtime that cannot be accurately stored as `number`, to throw an exception.

## OAuth
## Authorization

Calls to APIs are authorized using a token that is obtained via a client id/secret pair exchange, and then passes as an authorization header on API calls. The SDK handles this for you.
Calls to APIs can be authorized using basic auth or via OAuth - a token that is obtained via a client id/secret pair exchange.

If your Camunda 8 platform is secured using token exchange, provide the client id and secret to the SDK.
### Disable Auth

### Disable OAuth
To disable OAuth, set the environment variable `CAMUNDA_OAUTH_STRATEGY=NONE`. You can use this when running against a minimal Zeebe broker in a development environment, for example.

To disable OAuth, set the environment variable `CAMUNDA_OAUTH_DISABLED`. You can use this when running against a minimal Zeebe broker in a development environment, for example.
### Basic Auth

With this environment variable set, the SDK will inject a `NullAuthProvider` that does nothing.
To use basic auth, set the following values either via the environment or explicitly in code via the constructor:

### Configuring OAuth
```bash
CAMUNDA_AUTH_STRATEGY=BASIC
CAMUNDA_BASIC_AUTH_USERNAME=....
CAMUNDA_BASIC_AUTH_PASSWORD=...
```

### OAuth

To get a token for use with the application APIs, provide the following configuration fields at a minimum, either via the `Camunda8` constructor or in environment variables:
If your platform is secured with OAuth token exchange (Camunda SaaS or Self-Managed with Identity), provide the following configuration fields at a minimum, either via the `Camunda8` constructor or in environment variables:

```bash
ZEEBE_GRPC_ADDRESS
ZEEBE_CLIENT_ID
ZEEBE_CLIENT_SECRET
CAMUNDA_OAUTH_URL
CAMUNDA_AUTH_STRATEGY=OAUTH
ZEEBE_GRPC_ADDRESS=...
ZEEBE_CLIENT_ID=...
ZEEBE_CLIENT_SECRET=...
CAMUNDA_OAUTH_URL=...
```

To get a token for the Camunda SaaS Administration API or the Camunda SaaS Modeler API, set the following:

```bash
CAMUNDA_CONSOLE_CLIENT_ID
CAMUNDA_CONSOLE_CLIENT_SECRET
CAMUNDA_AUTH_STRATEGY=OAUTH
CAMUNDA_CONSOLE_CLIENT_ID=...
CAMUNDA_CONSOLE_CLIENT_SECRET=...
```

### Token caching
Expand Down Expand Up @@ -123,6 +131,7 @@ export ZEEBE_GRPC_ADDRESS='localhost:26500'
export ZEEBE_REST_ADDRESS='http://localhost:8080'
export ZEEBE_CLIENT_ID='zeebe'
export ZEEBE_CLIENT_SECRET='zecret'
export CAMUNDA_OAUTH_STRATEGY='OAUTH'
export CAMUNDA_OAUTH_URL='http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token'
export CAMUNDA_TASKLIST_BASE_URL='http://localhost:8082'
export CAMUNDA_OPERATE_BASE_URL='http://localhost:8081'
Expand Down Expand Up @@ -153,6 +162,7 @@ const c8 = new Camunda8({
ZEEBE_REST_ADDRESS: 'http://localhost:8080',
ZEEBE_CLIENT_ID: 'zeebe',
ZEEBE_CLIENT_SECRET: 'zecret',
CAMUNDA_OAUTH_STRATEGY: 'OAUTH',
CAMUNDA_OAUTH_URL:
'http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token',
CAMUNDA_TASKLIST_BASE_URL: 'http://localhost:8082',
Expand All @@ -178,6 +188,7 @@ export CAMUNDA_TASKLIST_BASE_URL='https://syd-1.tasklist.camunda.io/5c34c0a7-7f2
export CAMUNDA_OPTIMIZE_BASE_URL='https://syd-1.optimize.camunda.io/5c34c0a7-7f29-4424-8414-125615f7a9b9'
export CAMUNDA_OPERATE_BASE_URL='https://syd-1.operate.camunda.io/5c34c0a7-7f29-4424-8414-125615f7a9b9'
export CAMUNDA_OAUTH_URL='https://login.cloud.camunda.io/oauth/token'
export CAMUNDA_AUTH_STRATEGY='OAUTH'

# This is on by default, but we include it in case it got turned off for local tests
export CAMUNDA_SECURE_CONNECTION=true
Expand Down
23 changes: 23 additions & 0 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@sitapati/testcontainers": "^2.8.1",
"@types/basic-auth": "^1.1.8",
"@types/debug": "^4.1.12",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.11",
Expand All @@ -109,6 +110,7 @@
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"basic-auth": "^2.0.1",
"commitizen": "^4.3.0",
"cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0",
Expand Down
44 changes: 43 additions & 1 deletion src/__tests__/oauth/OAuthProvider.unit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import http from 'http'
import os from 'os'
import path from 'path'

import auth from 'basic-auth'
import got from 'got'
import jwt from 'jsonwebtoken'

import { EnvironmentSetup } from '../../lib'
import { EnvironmentSetup, constructOAuthProvider } from '../../lib'
import { OAuthProvider } from '../../oauth'

jest.setTimeout(10000)
Expand Down Expand Up @@ -573,4 +575,44 @@ describe('OAuthProvider', () => {
expect(thrown).toBe(true)
})
})

it('Can use Basic Auth as a strategy', async () => {
const server = http.createServer((req, res) => {
const credentials = auth(req)

if (
!credentials ||
credentials.name !== 'admin' ||
credentials.pass !== 'supersecret'
) {
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Basic realm="example"')
res.end('Access denied')
} else {
res.end('Access granted')
}
})

server.listen(3033, () => {
console.log('Server running on port 3033')
})

const oAuthProvider = constructOAuthProvider({
CAMUNDA_AUTH_STRATEGY: 'BASIC',
CAMUNDA_BASIC_AUTH_PASSWORD: 'supersecret',
CAMUNDA_BASIC_AUTH_USERNAME: 'admin',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any)
const token = await oAuthProvider.getToken('ZEEBE')
await got
.get('http://localhost:3033', {
headers: {
Authorization: 'Basic ' + token,
},
})
.then((res) => {
server.close()
expect(res).toBeTruthy()
})
})
})
1 change: 0 additions & 1 deletion src/admin/lib/AdminApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ export class AdminApiClient {
body: JSON.stringify(createClusterRequest),
headers,
}
debug(req)
const rest = await this.rest
return rest.post('', req).json()
}
Expand Down
17 changes: 17 additions & 0 deletions src/lib/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,21 @@ const getMainEnv = () =>
type: 'string',
optional: true,
},
/** Username for Basic Auth */
CAMUNDA_BASIC_AUTH_USERNAME: {
type: 'string',
optional: true,
},
/** Username for Basic Auth */
CAMUNDA_BASIC_AUTH_PASSWORD: {
type: 'string',
optional: true,
},
CAMUNDA_AUTH_STRATEGY: {
type: 'string',
choices: ['BASIC', 'OAUTH', 'NONE'],
default: 'OAUTH',
},
})

const getZeebeEnv = () =>
Expand Down Expand Up @@ -377,6 +392,8 @@ export const CamundaEnvironmentVariableDictionary =
'GRPC_KEEPALIVE_TIME_MS',
'ZEEBE_REST_ADDRESS',
'ZEEBE_GRPC_ADDRESS',
'CAMUNDA_BASIC_AUTH_USERNAME',
'CAMUNDA_BASIC_AUTH_PASSWORD',
'ZEEBE_ADDRESS',
'ZEEBE_CLIENT_ID',
'ZEEBE_CLIENT_SECRET',
Expand Down
21 changes: 19 additions & 2 deletions src/lib/ConstructOAuthProvider.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
import debug from 'debug'

import { NullAuthProvider, OAuthProvider } from '../oauth'
import { BasicAuthProvider } from '../oauth/lib/BasicAuthProvider'

import { CamundaPlatform8Configuration } from './Configuration'

const trace = debug('camunda:oauth')

export function constructOAuthProvider(config: CamundaPlatform8Configuration) {
if (config.CAMUNDA_OAUTH_DISABLED) {
trace(`Auth strategy is ${config.CAMUNDA_AUTH_STRATEGY}`)
trace(`OAuth disabled is ${config.CAMUNDA_OAUTH_DISABLED}`)
if (
config.CAMUNDA_OAUTH_DISABLED ||
config.CAMUNDA_AUTH_STRATEGY === 'NONE'
) {
trace(`Disabling Auth`)
return new NullAuthProvider()
} else {
return new OAuthProvider({ config })
if (config.CAMUNDA_AUTH_STRATEGY === 'BASIC') {
trace(`Using Basic Auth`)
return new BasicAuthProvider({ config })
} else {
trace(`Using OAuth`)
return new OAuthProvider({ config })
}
}
}
5 changes: 2 additions & 3 deletions src/modeler/lib/ModelerAPIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,13 @@ export class ModelerApiClient {

private async getHeaders() {
const token = await this.oAuthProvider.getToken('MODELER')
const auth = `Bearer ${token}`
const authorization = `Bearer ${token}`
const headers = {
'content-type': 'application/json',
authorization: auth,
authorization,
'user-agent': this.userAgentString,
accept: '*/*',
}
debug(auth)
return headers
}

Expand Down
39 changes: 39 additions & 0 deletions src/oauth/lib/BasicAuthProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import debug from 'debug'

import {
CamundaEnvironmentConfigurator,
CamundaPlatform8Configuration,
DeepPartial,
RequireConfiguration,
} from '../../lib'

import { IOAuthProvider, TokenGrantAudienceType } from './IOAuthProvider'

const trace = debug('camunda:oauth')

export class BasicAuthProvider implements IOAuthProvider {
private username: string | undefined
private password: string | undefined
constructor(options?: {
config?: DeepPartial<CamundaPlatform8Configuration>
}) {
const config = CamundaEnvironmentConfigurator.mergeConfigWithEnvironment(
options?.config ?? {}
)
this.username = RequireConfiguration(
config.CAMUNDA_BASIC_AUTH_USERNAME,
'CAMUNDA_BASIC_AUTH_USERNAME'
)
this.password = RequireConfiguration(
config.CAMUNDA_BASIC_AUTH_PASSWORD,
'CAMUNDA_BASIC_AUTH_PASSWORD'
)
}
getToken(audience: TokenGrantAudienceType): Promise<string> {
trace(`Requesting token for audience ${audience}`)
const token = Buffer.from(`${this.username}:${this.password}`).toString(
'base64'
)
return Promise.resolve(token)
}
}
Loading