From 4fcae8c0700ad9816b6e30f556370be735e6ac01 Mon Sep 17 00:00:00 2001 From: Sergey Petushkov Date: Fri, 1 Dec 2023 16:30:28 +0100 Subject: [PATCH 1/4] chore(oidc): add docker compose configuration that uses mock oidc provider --- docker/oidc/mock-oidc-provider/.gitignore | 1 + docker/oidc/mock-oidc-provider/Dockerfile | 16 ++++++++ .../mock-oidc-provider/docker-compose.yml | 7 ++++ .../mock-oidc-provider/oidc-mock-provider.js | 41 +++++++++++++++++++ docker/oidc/mock-oidc-provider/proxy.js | 28 +++++++++++++ .../oidc/mock-oidc-provider/start-server.sh | 13 ++++++ 6 files changed, 106 insertions(+) create mode 100644 docker/oidc/mock-oidc-provider/.gitignore create mode 100644 docker/oidc/mock-oidc-provider/Dockerfile create mode 100644 docker/oidc/mock-oidc-provider/docker-compose.yml create mode 100644 docker/oidc/mock-oidc-provider/oidc-mock-provider.js create mode 100644 docker/oidc/mock-oidc-provider/proxy.js create mode 100755 docker/oidc/mock-oidc-provider/start-server.sh diff --git a/docker/oidc/mock-oidc-provider/.gitignore b/docker/oidc/mock-oidc-provider/.gitignore new file mode 100644 index 0000000..8fa9d6f --- /dev/null +++ b/docker/oidc/mock-oidc-provider/.gitignore @@ -0,0 +1 @@ +!*.js \ No newline at end of file diff --git a/docker/oidc/mock-oidc-provider/Dockerfile b/docker/oidc/mock-oidc-provider/Dockerfile new file mode 100644 index 0000000..79f64df --- /dev/null +++ b/docker/oidc/mock-oidc-provider/Dockerfile @@ -0,0 +1,16 @@ +FROM mongodb/mongodb-enterprise-server:latest +USER root +RUN apt-get update && apt-get install -y \ + ca-certificates \ + curl +ARG NODE_VERSION=20.10.0 +ARG NODE_PACKAGE=node-v$NODE_VERSION-linux-arm64 +ARG NODE_HOME=/opt/$NODE_PACKAGE +ENV NODE_PATH $NODE_HOME/lib/node_modules +ENV PATH $NODE_HOME/bin:$PATH +RUN curl https://nodejs.org/dist/v$NODE_VERSION/$NODE_PACKAGE.tar.gz | tar -xzC /opt/ +RUN mkdir -p /tmp/mock-provider && cd /tmp/mock-provider && npm init -y && npm install @mongodb-js/oidc-mock-provider +COPY start-server.sh /start-server.sh +COPY oidc-mock-provider.js /tmp/mock-provider/oidc-mock-provider.js +COPY proxy.js /tmp/mock-provider/proxy.js +ENTRYPOINT ["/start-server.sh"] diff --git a/docker/oidc/mock-oidc-provider/docker-compose.yml b/docker/oidc/mock-oidc-provider/docker-compose.yml new file mode 100644 index 0000000..8ecac18 --- /dev/null +++ b/docker/oidc/mock-oidc-provider/docker-compose.yml @@ -0,0 +1,7 @@ +version: '3' +services: + mongodb-server-with-mock-oidc-provider: + build: . + ports: + - '27017:27017' + - '29091:29091' diff --git a/docker/oidc/mock-oidc-provider/oidc-mock-provider.js b/docker/oidc/mock-oidc-provider/oidc-mock-provider.js new file mode 100644 index 0000000..153229a --- /dev/null +++ b/docker/oidc/mock-oidc-provider/oidc-mock-provider.js @@ -0,0 +1,41 @@ +const { OIDCMockProvider } = require('@mongodb-js/oidc-mock-provider'); + +const DEFAULT_TOKEN_PAYLOAD = { + expires_in: 3600, + payload: { + // Define the user information stored inside the access tokens. + groups: ['testgroup'], + sub: 'testuser', + aud: 'resource-server-audience-value', + }, +}; + +(async () => { + const port = process.argv[2]; + const proxyPort = process.argv[3]; + const provider = await OIDCMockProvider.create({ + port: Number(port), + getTokenPayload() { + return DEFAULT_TOKEN_PAYLOAD; + }, + overrideRequestHandler(_url, req) { + console.log('[OIDC PROVIDER] %s %s', req.method, req.url); + }, + }); + console.log('[OIDC PROVIDER] Listening on %s', provider.issuer); + // To make oidc-mock-provider can be used by the server, we need to make sure + // that it's listening on the localhost and the issuer returned by the various + // mock provider requests is matching for all parts of the OIDC flow. + // + // This is tricky in docker environment on macos where we can't run the + // container attached to the host network. We can't use docker hostnames + // either because then we will not be passing various http localhost checks + // in server or oidc-plugin. + // + // To work around that we set up a proxy (see `./proxy.js`) that actually + // listens on all interfaces (0.0.0.0), while mock provider is only listening + // to localhost. To make sure that all responses returned by mock provider are + // matching the proxy address that is exposed outside, we override instance + // issuer property after we start the mock provider. + provider.issuer = `http://localhost:${proxyPort}`; +})(); diff --git a/docker/oidc/mock-oidc-provider/proxy.js b/docker/oidc/mock-oidc-provider/proxy.js new file mode 100644 index 0000000..5784b6e --- /dev/null +++ b/docker/oidc/mock-oidc-provider/proxy.js @@ -0,0 +1,28 @@ +// see ./oidc-mock-provider.js for why we need this proxy +const http = require('http'); + +const from = process.argv[2]; +const to = process.argv[3]; + +http + .createServer(function (clientReq, clientRes) { + console.log('[OIDC PROVIDER PROXY] %s %s', clientReq.method, clientReq.url); + + const options = { + hostname: 'localhost', + port: to, + path: clientReq.url, + method: clientReq.method, + headers: clientReq.headers, + }; + + const proxy = http.request(options, function (res) { + clientRes.writeHead(res.statusCode, res.headers); + res.pipe(clientRes, { end: true }); + }); + + clientReq.pipe(proxy, { end: true }); + }) + .listen(from, '0.0.0.0', () => { + console.log(`[OIDC PROVIDER PROXY] Listening on http://0.0.0.0:${from}`); + }); diff --git a/docker/oidc/mock-oidc-provider/start-server.sh b/docker/oidc/mock-oidc-provider/start-server.sh new file mode 100755 index 0000000..58b252c --- /dev/null +++ b/docker/oidc/mock-oidc-provider/start-server.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e +OIDC_PROVIDER_PORT=29090 +OIDC_PROVIDER_PROXY_PORT=29091 +node /tmp/mock-provider/oidc-mock-provider.js $OIDC_PROVIDER_PORT $OIDC_PROVIDER_PROXY_PORT & +node /tmp/mock-provider/proxy.js $OIDC_PROVIDER_PROXY_PORT $OIDC_PROVIDER_PORT & +echo Waiting to make sure that oidc mock provider and proxy are running +until $(curl --output /dev/null --silent --head --fail http://localhost:$OIDC_PROVIDER_PROXY_PORT/.well-known/openid-configuration); do + sleep 0.3 +done +echo Starting server +# This is original mongodb/mongodb-enterprise-server entrypoint +python3 /usr/local/bin/docker-entrypoint.py --setParameter authenticationMechanisms="SCRAM-SHA-256,MONGODB-OIDC" --setParameter enableTestCommands="true" --setParameter oidcIdentityProviders="[{\"issuer\":\"http://localhost:$OIDC_PROVIDER_PROXY_PORT\",\"clientId\":\"testServer\",\"requestScopes\":[\"mongodbGroups\"],\"authorizationClaim\":\"groups\",\"audience\":\"resource-server-audience-value\",\"authNamePrefix\":\"dev\"}]" From 2b264563e365eba1033955c167860fde7e8db412 Mon Sep 17 00:00:00 2001 From: Sergey Petushkov Date: Fri, 1 Dec 2023 18:29:43 +0100 Subject: [PATCH 2/4] chore: add readme and allow env configuration --- docker/oidc/mock-oidc-provider/README.md | 20 ++++++++++++++++++ docker/oidc/mock-oidc-provider/config.ts | 21 +++++++++++++++++++ .../mock-oidc-provider/docker-compose.yaml | 13 ++++++++++++ .../mock-oidc-provider/docker-compose.yml | 7 ------- .../mock-oidc-provider/oidc-mock-provider.js | 12 +++++++---- 5 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 docker/oidc/mock-oidc-provider/README.md create mode 100644 docker/oidc/mock-oidc-provider/config.ts create mode 100644 docker/oidc/mock-oidc-provider/docker-compose.yaml delete mode 100644 docker/oidc/mock-oidc-provider/docker-compose.yml diff --git a/docker/oidc/mock-oidc-provider/README.md b/docker/oidc/mock-oidc-provider/README.md new file mode 100644 index 0000000..952557d --- /dev/null +++ b/docker/oidc/mock-oidc-provider/README.md @@ -0,0 +1,20 @@ +### MongoDB enterprise with mock OIDC provider auth enabled + +```sh +docker-compose -f oidc/mock-oidc-provider/docker-compose.yaml up +``` + +#### How to connect + +```sh +mongosh \ + --host localhost \ + --port 27017 \ + --authenticationMechanism MONGODB-OIDC +``` + +Connection string: + +``` +mongodb://localhost:27017/?authMechanism=MONGODB-OIDC +``` diff --git a/docker/oidc/mock-oidc-provider/config.ts b/docker/oidc/mock-oidc-provider/config.ts new file mode 100644 index 0000000..a2afd14 --- /dev/null +++ b/docker/oidc/mock-oidc-provider/config.ts @@ -0,0 +1,21 @@ +import path from 'path'; + +import ConnectionString from 'mongodb-connection-string-url'; + +const port = '27017'; + +const connectionString = new ConnectionString(`mongodb://localhost:${port}`); +connectionString.searchParams.set('authMechanism', 'MONGODB-OIDC'); + +export default { + dockerCompose: { + projectName: path.basename(__dirname), + yamlPath: path.resolve(__dirname, 'docker-compose.yaml'), + }, + waitOn: [`tcp:${port}`], + connections: { + oidc: { + connectionString: connectionString.href, + }, + }, +}; diff --git a/docker/oidc/mock-oidc-provider/docker-compose.yaml b/docker/oidc/mock-oidc-provider/docker-compose.yaml new file mode 100644 index 0000000..027d853 --- /dev/null +++ b/docker/oidc/mock-oidc-provider/docker-compose.yaml @@ -0,0 +1,13 @@ +version: '3' +services: + mongodb-server-with-mock-oidc-provider: + build: . + ports: + - '27017:27017' + - '29091:29091' + environment: + - OIDC_TOKEN_PAYLOAD_EXPIRES_IN + # comma-separated list + - OIDC_TOKEN_PAYLOAD_GROUPS + - OIDC_TOKEN_PAYLOAD_SUB + - OIDC_TOKEN_PAYLOAD_AUD diff --git a/docker/oidc/mock-oidc-provider/docker-compose.yml b/docker/oidc/mock-oidc-provider/docker-compose.yml deleted file mode 100644 index 8ecac18..0000000 --- a/docker/oidc/mock-oidc-provider/docker-compose.yml +++ /dev/null @@ -1,7 +0,0 @@ -version: '3' -services: - mongodb-server-with-mock-oidc-provider: - build: . - ports: - - '27017:27017' - - '29091:29091' diff --git a/docker/oidc/mock-oidc-provider/oidc-mock-provider.js b/docker/oidc/mock-oidc-provider/oidc-mock-provider.js index 153229a..71084aa 100644 --- a/docker/oidc/mock-oidc-provider/oidc-mock-provider.js +++ b/docker/oidc/mock-oidc-provider/oidc-mock-provider.js @@ -1,12 +1,16 @@ const { OIDCMockProvider } = require('@mongodb-js/oidc-mock-provider'); const DEFAULT_TOKEN_PAYLOAD = { - expires_in: 3600, + expires_in: process.env.OIDC_TOKEN_PAYLOAD_EXPIRES_IN + ? Number(process.env.OIDC_TOKEN_PAYLOAD_EXPIRES_IN) + : 3600, payload: { // Define the user information stored inside the access tokens. - groups: ['testgroup'], - sub: 'testuser', - aud: 'resource-server-audience-value', + groups: process.env.OIDC_TOKEN_PAYLOAD_GROUPS + ? process.env.OIDC_TOKEN_PAYLOAD_GROUPS.split(',') + : ['testgroup'], + sub: process.env.OIDC_TOKEN_PAYLOAD_SUB || 'testuser', + aud: process.env.OIDC_TOKEN_PAYLOAD_AUD || 'resource-server-audience-value', }, }; From c82c36baf1dc2e9169be3661851b0bb16f13923e Mon Sep 17 00:00:00 2001 From: Sergey Petushkov Date: Fri, 1 Dec 2023 18:31:39 +0100 Subject: [PATCH 3/4] chore: update gitignore --- docker/oidc/mock-oidc-provider/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/oidc/mock-oidc-provider/.gitignore b/docker/oidc/mock-oidc-provider/.gitignore index 8fa9d6f..d3d84cb 100644 --- a/docker/oidc/mock-oidc-provider/.gitignore +++ b/docker/oidc/mock-oidc-provider/.gitignore @@ -1 +1,2 @@ -!*.js \ No newline at end of file +!proxy.js +!oidc-mock-provider.js From fe78c0de268bcbdb46264a14498931ef1d3c3be5 Mon Sep 17 00:00:00 2001 From: Sergey Petushkov Date: Mon, 4 Dec 2023 09:53:33 +0100 Subject: [PATCH 4/4] chore: choose unique port for mongodb; add env to the start / stop scripts; export env config --- docker/oidc/mock-oidc-provider/README.md | 4 +-- .../mock-oidc-provider/docker-compose.yaml | 2 +- .../mock-oidc-provider/oidc-mock-provider.js | 27 ++++++++++++++----- .../oidc/mock-oidc-provider/start-server.sh | 6 ++++- scripts/ps-all.sh | 1 + scripts/start-all.sh | 1 + scripts/stop-all.sh | 1 + src/index.ts | 2 ++ 8 files changed, 34 insertions(+), 10 deletions(-) diff --git a/docker/oidc/mock-oidc-provider/README.md b/docker/oidc/mock-oidc-provider/README.md index 952557d..7824ae3 100644 --- a/docker/oidc/mock-oidc-provider/README.md +++ b/docker/oidc/mock-oidc-provider/README.md @@ -9,12 +9,12 @@ docker-compose -f oidc/mock-oidc-provider/docker-compose.yaml up ```sh mongosh \ --host localhost \ - --port 27017 \ + --port 27096 \ --authenticationMechanism MONGODB-OIDC ``` Connection string: ``` -mongodb://localhost:27017/?authMechanism=MONGODB-OIDC +mongodb://localhost:27096/?authMechanism=MONGODB-OIDC ``` diff --git a/docker/oidc/mock-oidc-provider/docker-compose.yaml b/docker/oidc/mock-oidc-provider/docker-compose.yaml index 027d853..6127e1b 100644 --- a/docker/oidc/mock-oidc-provider/docker-compose.yaml +++ b/docker/oidc/mock-oidc-provider/docker-compose.yaml @@ -3,7 +3,7 @@ services: mongodb-server-with-mock-oidc-provider: build: . ports: - - '27017:27017' + - '27096:27017' - '29091:29091' environment: - OIDC_TOKEN_PAYLOAD_EXPIRES_IN diff --git a/docker/oidc/mock-oidc-provider/oidc-mock-provider.js b/docker/oidc/mock-oidc-provider/oidc-mock-provider.js index 71084aa..043dd91 100644 --- a/docker/oidc/mock-oidc-provider/oidc-mock-provider.js +++ b/docker/oidc/mock-oidc-provider/oidc-mock-provider.js @@ -17,24 +17,39 @@ const DEFAULT_TOKEN_PAYLOAD = { (async () => { const port = process.argv[2]; const proxyPort = process.argv[3]; + const serverOidcConfig = { + get issuer() { + return provider.issuer; + }, + clientId: 'testServer', + requestScopes: ['mongodbGroups'], + authorizationClaim: 'groups', + audience: 'resource-server-audience-value', + authNamePrefix: 'dev', + }; const provider = await OIDCMockProvider.create({ port: Number(port), getTokenPayload() { return DEFAULT_TOKEN_PAYLOAD; }, - overrideRequestHandler(_url, req) { + overrideRequestHandler(_url, req, res) { console.log('[OIDC PROVIDER] %s %s', req.method, req.url); + if (req.url === '/server-oidc-config') { + res.setHeader('content-type', 'application/json'); + res.write(JSON.stringify(serverOidcConfig)); + res.end(); + } }, }); console.log('[OIDC PROVIDER] Listening on %s', provider.issuer); - // To make oidc-mock-provider can be used by the server, we need to make sure - // that it's listening on the localhost and the issuer returned by the various - // mock provider requests is matching for all parts of the OIDC flow. + // To make sure oidc-mock-provider can be used by the server, we need to make + // sure that it's listening on the localhost and the issuer returned by the + // various mock provider requests is matching for all parts of the OIDC flow. // // This is tricky in docker environment on macos where we can't run the // container attached to the host network. We can't use docker hostnames - // either because then we will not be passing various http localhost checks - // in server or oidc-plugin. + // either because then we will not be passing various http localhost checks in + // server or oidc-plugin. // // To work around that we set up a proxy (see `./proxy.js`) that actually // listens on all interfaces (0.0.0.0), while mock provider is only listening diff --git a/docker/oidc/mock-oidc-provider/start-server.sh b/docker/oidc/mock-oidc-provider/start-server.sh index 58b252c..5878624 100755 --- a/docker/oidc/mock-oidc-provider/start-server.sh +++ b/docker/oidc/mock-oidc-provider/start-server.sh @@ -9,5 +9,9 @@ until $(curl --output /dev/null --silent --head --fail http://localhost:$OIDC_PR sleep 0.3 done echo Starting server +OIDC_IDENTITY_PROVIDERS="[$(curl --fail http://localhost:29091/server-oidc-config)]" # This is original mongodb/mongodb-enterprise-server entrypoint -python3 /usr/local/bin/docker-entrypoint.py --setParameter authenticationMechanisms="SCRAM-SHA-256,MONGODB-OIDC" --setParameter enableTestCommands="true" --setParameter oidcIdentityProviders="[{\"issuer\":\"http://localhost:$OIDC_PROVIDER_PROXY_PORT\",\"clientId\":\"testServer\",\"requestScopes\":[\"mongodbGroups\"],\"authorizationClaim\":\"groups\",\"audience\":\"resource-server-audience-value\",\"authNamePrefix\":\"dev\"}]" +python3 /usr/local/bin/docker-entrypoint.py \ + --setParameter authenticationMechanisms="SCRAM-SHA-256,MONGODB-OIDC" \ + --setParameter enableTestCommands="true" \ + --setParameter oidcIdentityProviders="$OIDC_IDENTITY_PROVIDERS" diff --git a/scripts/ps-all.sh b/scripts/ps-all.sh index 013fd4b..addb9b4 100644 --- a/scripts/ps-all.sh +++ b/scripts/ps-all.sh @@ -7,3 +7,4 @@ docker-compose -f docker/sharded/docker-compose.yaml ps docker-compose -f docker/ssh/docker-compose.yaml ps docker-compose -f docker/tls/docker-compose.yaml ps docker-compose -f docker/kerberos/docker-compose.yaml ps +docker-compose -f docker/oidc/mock-oidc-provider/docker-compose.yaml ps diff --git a/scripts/start-all.sh b/scripts/start-all.sh index dfdc9e4..d665945 100644 --- a/scripts/start-all.sh +++ b/scripts/start-all.sh @@ -7,3 +7,4 @@ docker-compose -f docker/sharded/docker-compose.yaml up -d --force-recreate docker-compose -f docker/ssh/docker-compose.yaml up -d --force-recreate docker-compose -f docker/tls/docker-compose.yaml up -d --force-recreate docker-compose -f docker/kerberos/docker-compose.yaml up -d --force-recreate +docker-compose -f docker/oidc/mock-oidc-provider/docker-compose.yaml up -d --force-recreate diff --git a/scripts/stop-all.sh b/scripts/stop-all.sh index 8876e53..7bbcc0b 100644 --- a/scripts/stop-all.sh +++ b/scripts/stop-all.sh @@ -7,3 +7,4 @@ docker-compose -f docker/sharded/docker-compose.yaml down --remove-orphans --vol docker-compose -f docker/ssh/docker-compose.yaml down --remove-orphans --volumes docker-compose -f docker/tls/docker-compose.yaml down --remove-orphans --volumes docker-compose -f docker/kerberos/docker-compose.yaml down --remove-orphans --volumes +docker-compose -f docker/oidc/mock-oidc-provider/docker-compose.yaml down --remove-orphans --volumes diff --git a/src/index.ts b/src/index.ts index 5069ef9..6f2cb52 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ import scram from '../docker/scram/config'; import sharded from '../docker/sharded/config'; import ssh from '../docker/ssh/config'; import tls from '../docker/tls/config'; +import oidc from '../docker/oidc/mock-oidc-provider/config'; const CONFIGS: Record = { community, @@ -22,6 +23,7 @@ const CONFIGS: Record = { sharded, ssh, tls, + oidc, }; export default function createTestEnvironments(