From 5a76dea6fabf19b49ff9bf833d090352260e4160 Mon Sep 17 00:00:00 2001 From: Chris Turner <23338096+christopherjturner@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:12:15 +0000 Subject: [PATCH] ECR blobs and disable login ECR admin endpoint will add a new service to all repos if it doesnt exist. new OIDC_SHOW_LOGIN flag to disable login prompt --- src/api/admin/controllers/trigger-ecr-push.js | 20 +++++++++++++ src/api/ecr/controllers/blobs.js | 26 +++++++++-------- .../oidc/controllers/authorize-controller.js | 12 ++++++-- src/api/oidc/helpers/validate-scope.js | 5 +++- src/api/oidc/helpers/validate-scope.test.js | 28 +++++++++++++++++++ src/config/index.js | 12 ++++---- 6 files changed, 82 insertions(+), 21 deletions(-) create mode 100644 src/api/oidc/helpers/validate-scope.test.js diff --git a/src/api/admin/controllers/trigger-ecr-push.js b/src/api/admin/controllers/trigger-ecr-push.js index faa609d..80e543e 100644 --- a/src/api/admin/controllers/trigger-ecr-push.js +++ b/src/api/admin/controllers/trigger-ecr-push.js @@ -1,11 +1,31 @@ // Simulates a new image being pushed to the ECR registry import { config } from '~/src/config' import { SendMessageCommand } from '@aws-sdk/client-sqs' +import { + allServices, + protectedServices, + publicServices +} from '~/src/config/services' const triggerEcrPush = { handler: async (request, h) => { const repo = request.params.repo const tag = request.params.tag + const zone = request.query.zone + + if (!allServices().includes(repo)) { + if (zone === 'public') { + publicServices.push(repo) + } else if (zone === 'protected') { + protectedServices.push(repo) + } else { + return h.response( + `unknown service, use the zone=public/private param, or use a service from ${allServices().join( + ', ' + )}` + ) + } + } const payload = JSON.stringify(generateMessage(repo, tag)) const msg = { diff --git a/src/api/ecr/controllers/blobs.js b/src/api/ecr/controllers/blobs.js index b74a6fe..ba83b0b 100644 --- a/src/api/ecr/controllers/blobs.js +++ b/src/api/ecr/controllers/blobs.js @@ -6,16 +6,24 @@ const blobController = { handler: async (request, h) => { const blobId = request.params.blobId - const blobs = configBlobs() - if (blobs[blobId]) { - return h.response(JSON.stringify(blobs[blobId])).code(200) + const accept = request.headers.accept + + if (accept === 'application/vnd.docker.container.image.v1+json') { + const blobs = configBlobs() + if (blobs[blobId]) { + return h.response(JSON.stringify(blobs[blobId])).code(200) + } } - if (fileBlobs[blobId]) { - return h.file(fileBlobs[blobId]) + if (accept === 'application/vnd.docker.image.rootfs.diff.tar.gzip') { + if (fileBlobs[blobId]) { + return h.file(fileBlobs[blobId]) + } + + return h.response().code(404) } - return h.response().code(404) + return h.response(`unsupported accept header ${accept}`).code(400) } } @@ -30,13 +38,9 @@ const fileBlobs = { const configBlobs = () => { const configBlobs = {} allServices().forEach((s) => { - // note: this isn't actually what docker is hashing, we're just generating it this way since its - // derministic and reasonably representative of the real thing. const org = config.get('githubOrg') - const hash = crypto.createHash('sha256') - hash.update(s) - const sha256 = hash.digest('hex') + const sha256 = crypto.createHash('sha256').update(s).digest('hex') configBlobs[`sha256:${sha256}`] = generateConfig(org, s) }) return configBlobs diff --git a/src/api/oidc/controllers/authorize-controller.js b/src/api/oidc/controllers/authorize-controller.js index d4a1336..62af381 100644 --- a/src/api/oidc/controllers/authorize-controller.js +++ b/src/api/oidc/controllers/authorize-controller.js @@ -3,13 +3,19 @@ import { validateScope } from '~/src/api/oidc/helpers/validate-scope' import { newSession } from '~/src/api/oidc/helpers/session-store' import { allUsers } from '~/src/api/oidc/helpers/users' import { renderLoginPage } from '~/src/api/oidc/helpers/render-login-page' +import { config } from '~/src/config' const authorizeController = { handler: (request, h) => { // a bit of a hack, but basically if the user param hasn't been set // show a 'login' page where they can select which fake user they want - if (request.query.user === undefined) { - return renderLoginPage(request.url, h) + + let loginUser = 'admin' + if (config.get('oidcShowLogin')) { + if (request.query.user === undefined) { + return renderLoginPage(request.url, h) + } + loginUser = request.query.user } // TODO check these are all set, use joi or something @@ -45,7 +51,7 @@ const authorizeController = { .code(400) } - const user = allUsers[request.query.user] + const user = allUsers[loginUser] if (user === undefined) { request.logger.error(`Invalid user selected ${request.query.user}`) return h.response(`Invalid user selection!`).code(400) diff --git a/src/api/oidc/helpers/validate-scope.js b/src/api/oidc/helpers/validate-scope.js index a437825..69c2880 100644 --- a/src/api/oidc/helpers/validate-scope.js +++ b/src/api/oidc/helpers/validate-scope.js @@ -2,7 +2,10 @@ import { oidcConfig } from '~/src/api/oidc/oidc-config' const validateScope = (scope) => { const scopes = scope.split(' ') - return scopes.filter((s) => !oidcConfig.scopesSupported.includes(s)) + + return scopes + .filter((s) => s !== '') + .filter((s) => !oidcConfig.scopesSupported.includes(s)) } export { validateScope } diff --git a/src/api/oidc/helpers/validate-scope.test.js b/src/api/oidc/helpers/validate-scope.test.js new file mode 100644 index 0000000..fbc6650 --- /dev/null +++ b/src/api/oidc/helpers/validate-scope.test.js @@ -0,0 +1,28 @@ +import { validateScope } from '~/src/api/oidc/helpers/validate-scope' + +describe('validateScope function', () => { + it('should return an empty array for valid scopes', () => { + const validScope = 'profile email' + expect(validateScope(validScope)).toEqual([]) + }) + + it('should return unsupported scopes', () => { + const invalidScope = 'profile address' + expect(validateScope(invalidScope)).toEqual(['address']) + }) + + it('should handle empty input', () => { + const emptyScope = '' + expect(validateScope(emptyScope)).toEqual([]) + }) + + it('should handle single valid scope', () => { + const singleValidScope = 'openid' + expect(validateScope(singleValidScope)).toEqual([]) + }) + + it('should handle single invalid scope', () => { + const singleInvalidScope = 'address' + expect(validateScope(singleInvalidScope)).toEqual(['address']) + }) +}) diff --git a/src/config/index.js b/src/config/index.js index 26e5f5c..d6efcab 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -128,12 +128,6 @@ const config = convict({ default: 'test_value', env: 'OIDC_CLIENT_SECRET' }, - oidcServerHost: { - doc: 'hostname of the stub server, used in wellknown urls endpoint', - format: String, - default: 'http://localhost', - env: 'OIDC_SERVER_HOST' - }, oidcPublicKeyBase64: { doc: 'base 64 encoded public pem', format: String, @@ -145,6 +139,12 @@ const config = convict({ format: String, default: undefined, env: 'OIDC_PRIVATE_KEY_B64' + }, + oidcShowLogin: { + doc: 'if set, shows login page, else it auto logs in as admin', + format: Boolean, + default: true, + env: 'OIDC_SHOW_LOGIN' } })