Skip to content

Commit

Permalink
Prepare JWT CI
Browse files Browse the repository at this point in the history
  • Loading branch information
slvrtrn committed Dec 13, 2024
1 parent 7cc3d73 commit 6456a34
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 43 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ jobs:
env:
CLICKHOUSE_CLOUD_HOST: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_HOST_SMT }}
CLICKHOUSE_CLOUD_PASSWORD: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_PASSWORD_SMT }}
CLICKHOUSE_CLOUD_JWT_SECRET: ${{ secrets.CI_JWT_SIGNING_PRIVATE_KEY }}
run: |
npm run test:node:integration:cloud_smt
Expand All @@ -242,6 +243,14 @@ jobs:
run: |
npm run test:web:integration:cloud_smt
- name: Run JWT auth integration tests
env:
CLICKHOUSE_CLOUD_HOST: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_HOST_SMT }}
CLICKHOUSE_CLOUD_PASSWORD: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_PASSWORD_SMT }}
CLICKHOUSE_CLOUD_JWT_SECRET: ${{ secrets.CI_JWT_SIGNING_PRIVATE_KEY }}
run: |
npm run test:web:integration:cloud_smt:jwt
# With unit + integration + TLS tests + coverage + SonarCloud report, after the rest of the tests.
# Needs all integration tests on all environments to pass.
# Should use only the current LTS version of Node.js.
Expand Down
7 changes: 7 additions & 0 deletions .scripts/generate_cloud_jwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { makeJWT } from '../packages/client-node/__tests__/utils/jwt'

/** Used to generate a JWT token for web testing (can't use `jsonwebtoken` library directly there)
* See `package.json` -> `scripts` -> `test:web:integration:cloud_smt:jwt` */
;(() => {
console.log(makeJWT())
})()
40 changes: 40 additions & 0 deletions karma.config.jwt.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const webpackConfig = require('./webpack.dev.js')

const TEST_TIMEOUT_MS = 400_000

module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (e.g. files, exclude)
basePath: '',
frameworks: ['webpack', 'jasmine'],
// list of files / patterns to load in the browser
files: ['packages/client-web/__tests__/jwt/*.test.ts'],
exclude: [],
webpack: webpackConfig,
preprocessors: {
'packages/client-common/**/*.ts': ['webpack', 'sourcemap'],
'packages/client-web/**/*.ts': ['webpack', 'sourcemap'],
'packages/client-common/__tests__/jwt/*.ts': ['webpack', 'sourcemap'],
'packages/client-common/__tests__/utils/*.ts': ['webpack', 'sourcemap'],
},
reporters: ['mocha'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['ChromeHeadless', 'FirefoxHeadless'],
browserNoActivityTimeout: TEST_TIMEOUT_MS,
browserDisconnectTimeout: TEST_TIMEOUT_MS,
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
client: {
jasmine: {
random: false,
stopOnSpecFailure: false,
stopSpecOnExpectationFailure: true,
timeoutInterval: TEST_TIMEOUT_MS,
},
},
})
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@
"test:web:integration:local_cluster": "CLICKHOUSE_TEST_ENVIRONMENT=local_cluster npm run test:web",
"test:web:integration:cloud": "CLICKHOUSE_TEST_ENVIRONMENT=cloud npm run test:web",
"test:web:integration:cloud_smt": "CLICKHOUSE_TEST_ENVIRONMENT=cloud_smt npm run test:web",
"test:web:integration:cloud_smt:jwt": "CLICKHOUSE_CLOUD_JWT_ACCESS_TOKEN=`ts-node .scripts/generate_cloud_jwt.ts` karma start karma.config.jwt.cjs",
"prepare": "husky"
},
"devDependencies": {
"@faker-js/faker": "^9.0.2",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/jasmine": "^5.1.4",
"@types/jsonwebtoken": "^9.0.7",
"@types/node": "^22.7.0",
"@types/sinon": "^17.0.3",
"@types/split2": "^4.2.3",
Expand All @@ -61,6 +63,7 @@
"jasmine": "^5.3.0",
"jasmine-core": "^5.3.0",
"jasmine-expect": "^5.0.0",
"jsonwebtoken": "^9.0.2",
"karma": "^6.4.4",
"karma-chrome-launcher": "^3.2.0",
"karma-firefox-launcher": "^2.1.3",
Expand Down
42 changes: 1 addition & 41 deletions packages/client-common/__tests__/integration/auth.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type ClickHouseClient } from '@clickhouse/client-common'
import { createSimpleTable } from '@test/fixtures/simple_table'
import { EnvKeys, getAuthFromEnv, getFromEnv } from '@test/utils/env'
import { getAuthFromEnv } from '@test/utils/env'
import { createTestClient, guid } from '../utils'

describe('authentication', () => {
Expand Down Expand Up @@ -96,44 +96,4 @@ describe('authentication', () => {
expect(result).toEqual('42,144\n')
})
})

// FIXME
xdescribe('JWT auth', () => {
let jwtClient: ClickHouseClient
afterEach(async () => {
await jwtClient.close()
})

it('should work with client configuration', async () => {
jwtClient = createTestClient({
url: `https://${getFromEnv(EnvKeys.host)}:8443`,
auth: {
access_token: getFromEnv(EnvKeys.jwt_access_token),
},
})
const rs = await jwtClient.query({
query: 'SELECT 42 AS result',
format: 'JSONEachRow',
})
expect(await rs.json()).toEqual([{ result: 42 }])
})

it('should override the client instance auth', async () => {
jwtClient = createTestClient({
url: `https://${getFromEnv(EnvKeys.host)}:8443`,
auth: {
username: 'gibberish',
password: 'gibberish',
},
})
const rs = await jwtClient.query({
query: 'SELECT 42 AS result',
format: 'JSONEachRow',
auth: {
access_token: getFromEnv(EnvKeys.jwt_access_token),
},
})
expect(await rs.json()).toEqual([{ result: 42 }])
})
})
})
3 changes: 2 additions & 1 deletion packages/client-common/__tests__/utils/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ export const EnvKeys = {
host: 'CLICKHOUSE_CLOUD_HOST',
username: 'CLICKHOUSE_CLOUD_USERNAME',
password: 'CLICKHOUSE_CLOUD_PASSWORD',
jwt_access_token: 'CLICKHOUSE_JWT_ACCESS_TOKEN',
jwt_access_token: 'CLICKHOUSE_CLOUD_JWT_ACCESS_TOKEN',
jwt_secret: 'CLICKHOUSE_CLOUD_JWT_SECRET',
}

export function getFromEnv(key: string): string {
Expand Down
50 changes: 50 additions & 0 deletions packages/client-node/__tests__/integration/node_jwt_auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { ClickHouseClient } from '@clickhouse/client-common'
import { createTestClient, TestEnv, whenOnEnv } from '@test/utils'
import { EnvKeys, getFromEnv } from '@test/utils/env'
import { makeJWT } from '../utils/jwt'

whenOnEnv(TestEnv.CloudSMT).describe('[Node.js] JWT auth', () => {
let jwtClient: ClickHouseClient
let url: string
let jwt: string

beforeAll(() => {
url = `https://${getFromEnv(EnvKeys.host)}:8443`
jwt = makeJWT()
})
afterEach(async () => {
await jwtClient.close()
})

it('should work with client configuration', async () => {
jwtClient = createTestClient({
url,
auth: {
access_token: jwt,
},
})
const rs = await jwtClient.query({
query: 'SELECT 42 AS result',
format: 'JSONEachRow',
})
expect(await rs.json()).toEqual([{ result: 42 }])
})

it('should override the client instance auth', async () => {
jwtClient = createTestClient({
url,
auth: {
username: 'gibberish',
password: 'gibberish',
},
})
const rs = await jwtClient.query({
query: 'SELECT 42 AS result',
format: 'JSONEachRow',
auth: {
access_token: jwt,
},
})
expect(await rs.json()).toEqual([{ result: 42 }])
})
})
18 changes: 18 additions & 0 deletions packages/client-node/__tests__/utils/jwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import jwt from 'jsonwebtoken'

export function makeJWT(): string {
const secret = process.env['CLICKHOUSE_CLOUD_JWT_SECRET']
if (secret === undefined) {
throw new Error(
'Environment variable CLICKHOUSE_CLOUD_JWT_SECRET is not set',
)
}
const payload = {
iss: 'ClickHouse',
sub: 'CI_Test',
aud: '1f7f78b8-da67-480b-8913-726fdd31d2fc',
'clickhouse:roles': ['default'],
'clickhouse:grants': [],
}
return jwt.sign(payload, secret, { expiresIn: '15m', algorithm: 'RS256' })
}
52 changes: 52 additions & 0 deletions packages/client-web/__tests__/jwt/web_jwt_auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { ClickHouseClient } from '@clickhouse/client-common'
import { createTestClient } from '@test/utils'
import { EnvKeys, getFromEnv } from '@test/utils/env'

/** Cannot use the jsonwebtoken library to generate the token: it is Node.js only.
* The access token should be generated externally before running the test,
* and set as the CLICKHOUSE_JWT_ACCESS_TOKEN environment variable */
describe('[Web] JWT auth', () => {
let client: ClickHouseClient
let url: string
let jwt: string

beforeAll(() => {
url = `https://${getFromEnv(EnvKeys.host)}:8443`
jwt = getFromEnv(EnvKeys.jwt_access_token)
})
afterEach(async () => {
await client.close()
})

it('should work with client configuration', async () => {
client = createTestClient({
url,
auth: {
access_token: jwt,
},
})
const rs = await client.query({
query: 'SELECT 42 AS result',
format: 'JSONEachRow',
})
expect(await rs.json()).toEqual([{ result: 42 }])
})

it('should override the client instance auth', async () => {
client = createTestClient({
url,
auth: {
username: 'gibberish',
password: 'gibberish',
},
})
const rs = await client.query({
query: 'SELECT 42 AS result',
format: 'JSONEachRow',
auth: {
access_token: jwt,
},
})
expect(await rs.json()).toEqual([{ result: 42 }])
})
})
2 changes: 1 addition & 1 deletion webpack.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = merge(common, {
CLICKHOUSE_TEST_ENVIRONMENT: process.env.CLICKHOUSE_TEST_ENVIRONMENT,
CLICKHOUSE_CLOUD_HOST: process.env.CLICKHOUSE_CLOUD_HOST,
CLICKHOUSE_CLOUD_PASSWORD: process.env.CLICKHOUSE_CLOUD_PASSWORD,
CLICKHOUSE_JWT_ACCESS_TOKEN: process.env.CLICKHOUSE_JWT_ACCESS_TOKEN,
CLICKHOUSE_CLOUD_JWT_ACCESS_TOKEN: process.env.CLICKHOUSE_CLOUD_JWT_ACCESS_TOKEN,
}),
}),
],
Expand Down

0 comments on commit 6456a34

Please sign in to comment.