From e1169417509d4338581aff8ea644cd7cd22add6e Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 13 Mar 2023 15:02:59 +0000 Subject: [PATCH] Rotatable secrets (#9982) * Rotatable secrets * Set new api encryption key var * Lint * Use fallback keys instead of array * Point api encryption key to dedicated value * Add API_ENCRYPTION_KEY to cli * Lint + add api encryption key to env files --- .../templates/app-service-deployment.yaml | 15 ++++++++++ charts/budibase/templates/secrets.yaml | 6 ++++ .../templates/worker-service-deployment.yaml | 15 ++++++++++ charts/budibase/values.yaml | 4 +++ hosting/.env | 1 + hosting/docker-compose.yaml | 2 ++ hosting/hosting.properties | 1 + packages/backend-core/src/auth/auth.ts | 24 ---------------- packages/backend-core/src/environment.ts | 9 ++++++ .../src/middleware/authenticated.ts | 11 ++++++-- packages/backend-core/src/middleware/index.ts | 1 - .../src/middleware/internalApi.ts | 12 ++++++-- .../src/middleware/passport/jwt.ts | 19 ------------- .../backend-core/src/security/encryption.ts | 12 ++++---- packages/backend-core/src/utils/utils.ts | 28 +++++++++++++++++-- packages/cli/src/hosting/makeFiles.ts | 1 + packages/server/src/api/index.ts | 7 ----- packages/server/src/environment.ts | 2 -- .../src/tests/utilities/TestConfiguration.ts | 9 +++--- .../server/src/utilities/workerRequests.ts | 3 +- packages/worker/src/environment.ts | 2 -- .../worker/src/middleware/cloudRestricted.ts | 12 ++++++-- packages/worker/src/sdk/auth/auth.ts | 4 +-- .../worker/src/tests/TestConfiguration.ts | 7 ++--- packages/worker/src/utilities/appService.ts | 9 ++++-- 25 files changed, 132 insertions(+), 84 deletions(-) delete mode 100644 packages/backend-core/src/middleware/passport/jwt.ts diff --git a/charts/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml index 89e7db7796..4371d27283 100644 --- a/charts/budibase/templates/app-service-deployment.yaml +++ b/charts/budibase/templates/app-service-deployment.yaml @@ -62,16 +62,31 @@ spec: {{ end }} - name: ENABLE_ANALYTICS value: {{ .Values.globals.enableAnalytics | quote }} + - name: API_ENCRYPTION_KEY + valueFrom: + secretKeyRef: + name: budibase-budibase {{ template "budibase.fullname" . }} + key: apiEncryptionKey - name: INTERNAL_API_KEY valueFrom: secretKeyRef: name: {{ template "budibase.fullname" . }} key: internalApiKey + - name: INTERNAL_API_KEY_FALLBACK + valueFrom: + secretKeyRef: + name: {{ template "budibase.fullname" . }} + key: internalApiKeyFallback - name: JWT_SECRET valueFrom: secretKeyRef: name: {{ template "budibase.fullname" . }} key: jwtSecret + - name: JWT_SECRET_FALLBACK + valueFrom: + secretKeyRef: + name: {{ template "budibase.fullname" . }} + key: jwtSecretFallback {{ if .Values.services.objectStore.region }} - name: AWS_REGION value: {{ .Values.services.objectStore.region }} diff --git a/charts/budibase/templates/secrets.yaml b/charts/budibase/templates/secrets.yaml index 1c0a914ed3..3c3ca9bfa8 100644 --- a/charts/budibase/templates/secrets.yaml +++ b/charts/budibase/templates/secrets.yaml @@ -10,8 +10,14 @@ metadata: heritage: "{{ .Release.Service }}" type: Opaque data: + {{/* For new installations this can be any value. For existing installations this must match the first used jwtSecret */}} + apiEncryptionKey: {{ .Values.globals.apiEncryptionKey }} internalApiKey: {{ template "budibase.defaultsecret" .Values.globals.internalApiKey }} + {{/* Fallback value auto generated */}} + internalApiKeyFallback: {{ .Values.globals.internalApiKeyFallback }} jwtSecret: {{ template "budibase.defaultsecret" .Values.globals.jwtSecret }} + {{/* Falback value never auto generated */}} + jwtSecretFallback: {{ .Values.globals.jwtSecretFallback }} objectStoreAccess: {{ template "budibase.defaultsecret" .Values.services.objectStore.accessKey }} objectStoreSecret: {{ template "budibase.defaultsecret" .Values.services.objectStore.secretKey }} {{- end -}} diff --git a/charts/budibase/templates/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml index 0e053dfb5a..f41c7d548b 100644 --- a/charts/budibase/templates/worker-service-deployment.yaml +++ b/charts/budibase/templates/worker-service-deployment.yaml @@ -62,16 +62,31 @@ spec: {{ else }} value: http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }} {{ end }} + - name: API_ENCRYPTION_KEY + valueFrom: + secretKeyRef: + name: budibase-budibase {{ template "budibase.fullname" . }} + key: apiEncryptionKey - name: INTERNAL_API_KEY valueFrom: secretKeyRef: name: {{ template "budibase.fullname" . }} key: internalApiKey + - name: INTERNAL_API_KEY_FALLBACK + valueFrom: + secretKeyRef: + name: {{ template "budibase.fullname" . }} + key: internalApiKeyFallback - name: JWT_SECRET valueFrom: secretKeyRef: name: {{ template "budibase.fullname" . }} key: jwtSecret + - name: JWT_SECRET_FALLBACK + valueFrom: + secretKeyRef: + name: {{ template "budibase.fullname" . }} + key: jwtSecretFallback {{ if .Values.services.objectStore.region }} - name: AWS_REGION value: {{ .Values.services.objectStore.region }} diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 536af8560f..ed4ff014a9 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -96,9 +96,13 @@ globals: createSecrets: true # creates an internal API key, JWT secrets and redis password for you # if createSecrets is set to false, you can hard-code your secrets here + apiEncryptionKey: "" internalApiKey: "" jwtSecret: "" cdnUrl: "" + # fallback values used during live rotation + internalApiKeyFallback: "" + jwtSecretFallback: "" smtp: enabled: false diff --git a/hosting/.env b/hosting/.env index 07b506a6b2..c2b6d55eef 100644 --- a/hosting/.env +++ b/hosting/.env @@ -3,6 +3,7 @@ MAIN_PORT=10000 # This section contains all secrets pertaining to the system # These should be updated +API_ENCRYPTION_KEY=testsecret JWT_SECRET=testsecret MINIO_ACCESS_KEY=budibase MINIO_SECRET_KEY=budibase diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index d36937910f..bad34a20ea 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -17,6 +17,7 @@ services: INTERNAL_API_KEY: ${INTERNAL_API_KEY} BUDIBASE_ENVIRONMENT: ${BUDIBASE_ENVIRONMENT} PORT: 4002 + API_ENCRYPTION_KEY: ${API_ENCRYPTION_KEY} JWT_SECRET: ${JWT_SECRET} LOG_LEVEL: info SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131 @@ -40,6 +41,7 @@ services: SELF_HOSTED: 1 PORT: 4003 CLUSTER_PORT: ${MAIN_PORT} + API_ENCRYPTION_KEY: ${API_ENCRYPTION_KEY} JWT_SECRET: ${JWT_SECRET} MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} diff --git a/hosting/hosting.properties b/hosting/hosting.properties index c5638a266f..6c1d9e5dbd 100644 --- a/hosting/hosting.properties +++ b/hosting/hosting.properties @@ -3,6 +3,7 @@ MAIN_PORT=10000 # This section contains all secrets pertaining to the system # These should be updated +API_ENCRYPTION_KEY=testsecret JWT_SECRET=testsecret MINIO_ACCESS_KEY=budibase MINIO_SECRET_KEY=budibase diff --git a/packages/backend-core/src/auth/auth.ts b/packages/backend-core/src/auth/auth.ts index 7e6fe4bcee..26c7cd4e26 100644 --- a/packages/backend-core/src/auth/auth.ts +++ b/packages/backend-core/src/auth/auth.ts @@ -1,6 +1,5 @@ const _passport = require("koa-passport") const LocalStrategy = require("passport-local").Strategy -const JwtStrategy = require("passport-jwt").Strategy import { getGlobalDB } from "../context" import { Cookie } from "../constants" import { getSessionsForUser, invalidateSessions } from "../security/sessions" @@ -8,7 +7,6 @@ import { authenticated, csrf, google, - jwt as jwtPassport, local, oidc, tenancy, @@ -21,14 +19,11 @@ import { OIDCInnerConfig, PlatformLogoutOpts, SSOProviderType, - User, } from "@budibase/types" -import { logAlert } from "../logging" import * as events from "../events" import * as configs from "../configs" import { clearCookie, getCookie } from "../utils" import { ssoSaveUserNoOp } from "../middleware/passport/sso/sso" -import env from "../environment" const refresh = require("passport-oauth2-refresh") export { @@ -51,25 +46,6 @@ export const jwt = require("jsonwebtoken") // Strategies _passport.use(new LocalStrategy(local.options, local.authenticate)) -if (jwtPassport.options.secretOrKey) { - _passport.use(new JwtStrategy(jwtPassport.options, jwtPassport.authenticate)) -} else if (!env.DISABLE_JWT_WARNING) { - logAlert("No JWT Secret supplied, cannot configure JWT strategy") -} - -_passport.serializeUser((user: User, done: any) => done(null, user)) - -_passport.deserializeUser(async (user: User, done: any) => { - const db = getGlobalDB() - - try { - const dbUser = await db.get(user._id) - return done(null, dbUser) - } catch (err) { - console.error(`User not found`, err) - return done(null, false, { message: "User not found" }) - } -}) async function refreshOIDCAccessToken( chosenConfig: OIDCInnerConfig, diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index 8dc2cce487..f1c96c7fec 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -30,6 +30,12 @@ const DefaultBucketName = { const selfHosted = !!parseInt(process.env.SELF_HOSTED || "") +function getAPIEncryptionKey() { + return process.env.API_ENCRYPTION_KEY + ? process.env.API_ENCRYPTION_KEY + : process.env.JWT_SECRET // fallback to the JWT_SECRET used historically +} + const environment = { isTest, isJest, @@ -39,7 +45,9 @@ const environment = { }, JS_BCRYPT: process.env.JS_BCRYPT, JWT_SECRET: process.env.JWT_SECRET, + JWT_SECRET_FALLBACK: process.env.JWT_SECRET_FALLBACK, ENCRYPTION_KEY: process.env.ENCRYPTION_KEY, + API_ENCRYPTION_KEY: getAPIEncryptionKey(), COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005", COUCH_DB_USERNAME: process.env.COUCH_DB_USER, COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD, @@ -55,6 +63,7 @@ const environment = { MINIO_URL: process.env.MINIO_URL, MINIO_ENABLED: process.env.MINIO_ENABLED || 1, INTERNAL_API_KEY: process.env.INTERNAL_API_KEY, + INTERNAL_API_KEY_FALLBACK: process.env.INTERNAL_API_KEY_FALLBACK, MULTI_TENANCY: process.env.MULTI_TENANCY, ACCOUNT_PORTAL_URL: process.env.ACCOUNT_PORTAL_URL || "https://account.budibase.app", diff --git a/packages/backend-core/src/middleware/authenticated.ts b/packages/backend-core/src/middleware/authenticated.ts index 0708581570..5e546b4c1c 100644 --- a/packages/backend-core/src/middleware/authenticated.ts +++ b/packages/backend-core/src/middleware/authenticated.ts @@ -1,5 +1,10 @@ import { Cookie, Header } from "../constants" -import { getCookie, clearCookie, openJwt } from "../utils" +import { + getCookie, + clearCookie, + openJwt, + isValidInternalAPIKey, +} from "../utils" import { getUser } from "../cache/user" import { getSession, updateSessionTTL } from "../security/sessions" import { buildMatcherRegex, matches } from "./matchers" @@ -35,7 +40,9 @@ function finalise(ctx: any, opts: FinaliseOpts = {}) { } async function checkApiKey(apiKey: string, populateUser?: Function) { - if (apiKey === env.INTERNAL_API_KEY) { + // check both the primary and the fallback internal api keys + // this allows for rotation + if (isValidInternalAPIKey(apiKey)) { return { valid: true } } const decrypted = decrypt(apiKey) diff --git a/packages/backend-core/src/middleware/index.ts b/packages/backend-core/src/middleware/index.ts index addeac6a1a..dce07168d4 100644 --- a/packages/backend-core/src/middleware/index.ts +++ b/packages/backend-core/src/middleware/index.ts @@ -1,4 +1,3 @@ -export * as jwt from "./passport/jwt" export * as local from "./passport/local" export * as google from "./passport/sso/google" export * as oidc from "./passport/sso/oidc" diff --git a/packages/backend-core/src/middleware/internalApi.ts b/packages/backend-core/src/middleware/internalApi.ts index fff761928b..dc73cd6b66 100644 --- a/packages/backend-core/src/middleware/internalApi.ts +++ b/packages/backend-core/src/middleware/internalApi.ts @@ -1,13 +1,21 @@ -import env from "../environment" import { Header } from "../constants" import { BBContext } from "@budibase/types" +import { isValidInternalAPIKey } from "../utils" /** * API Key only endpoint. */ export default async (ctx: BBContext, next: any) => { const apiKey = ctx.request.headers[Header.API_KEY] - if (apiKey !== env.INTERNAL_API_KEY) { + if (!apiKey) { + ctx.throw(403, "Unauthorized") + } + + if (Array.isArray(apiKey)) { + ctx.throw(403, "Unauthorized") + } + + if (!isValidInternalAPIKey(apiKey)) { ctx.throw(403, "Unauthorized") } diff --git a/packages/backend-core/src/middleware/passport/jwt.ts b/packages/backend-core/src/middleware/passport/jwt.ts deleted file mode 100644 index 95dc8f2656..0000000000 --- a/packages/backend-core/src/middleware/passport/jwt.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Cookie } from "../../constants" -import env from "../../environment" -import { authError } from "./utils" -import { BBContext } from "@budibase/types" - -export const options = { - secretOrKey: env.JWT_SECRET, - jwtFromRequest: function (ctx: BBContext) { - return ctx.cookies.get(Cookie.Auth) - }, -} - -export async function authenticate(jwt: Function, done: Function) { - try { - return done(null, jwt) - } catch (err) { - return authError(done, "JWT invalid", err) - } -} diff --git a/packages/backend-core/src/security/encryption.ts b/packages/backend-core/src/security/encryption.ts index d0707cb850..d2c8f18f73 100644 --- a/packages/backend-core/src/security/encryption.ts +++ b/packages/backend-core/src/security/encryption.ts @@ -8,7 +8,7 @@ const RANDOM_BYTES = 16 const STRETCH_LENGTH = 32 export enum SecretOption { - JWT = "jwt", + API = "api", ENCRYPTION = "encryption", } @@ -19,10 +19,10 @@ function getSecret(secretOption: SecretOption): string { secret = env.ENCRYPTION_KEY secretName = "ENCRYPTION_KEY" break - case SecretOption.JWT: + case SecretOption.API: default: - secret = env.JWT_SECRET - secretName = "JWT_SECRET" + secret = env.API_ENCRYPTION_KEY + secretName = "API_ENCRYPTION_KEY" break } if (!secret) { @@ -37,7 +37,7 @@ function stretchString(string: string, salt: Buffer) { export function encrypt( input: string, - secretOption: SecretOption = SecretOption.JWT + secretOption: SecretOption = SecretOption.API ) { const salt = crypto.randomBytes(RANDOM_BYTES) const stretched = stretchString(getSecret(secretOption), salt) @@ -50,7 +50,7 @@ export function encrypt( export function decrypt( input: string, - secretOption: SecretOption = SecretOption.JWT + secretOption: SecretOption = SecretOption.API ) { const [salt, encrypted] = input.split(SEPARATOR) const saltBuffer = Buffer.from(salt, "hex") diff --git a/packages/backend-core/src/utils/utils.ts b/packages/backend-core/src/utils/utils.ts index 3efd40ca80..7c222a9831 100644 --- a/packages/backend-core/src/utils/utils.ts +++ b/packages/backend-core/src/utils/utils.ts @@ -1,5 +1,4 @@ import { getAllApps, queryGlobalView } from "../db" -import { options } from "../middleware/passport/jwt" import { Header, MAX_VALID_DATE, @@ -133,7 +132,30 @@ export function openJwt(token: string) { if (!token) { return token } - return jwt.verify(token, options.secretOrKey) + try { + return jwt.verify(token, env.JWT_SECRET) + } catch (e) { + if (env.JWT_SECRET_FALLBACK) { + // fallback to enable rotation + return jwt.verify(token, env.JWT_SECRET_FALLBACK) + } else { + throw e + } + } +} + +export function isValidInternalAPIKey(apiKey: string) { + if (env.INTERNAL_API_KEY && env.INTERNAL_API_KEY === apiKey) { + return true + } + // fallback to enable rotation + if ( + env.INTERNAL_API_KEY_FALLBACK && + env.INTERNAL_API_KEY_FALLBACK === apiKey + ) { + return true + } + return false } /** @@ -165,7 +187,7 @@ export function setCookie( opts = { sign: true } ) { if (value && opts && opts.sign) { - value = jwt.sign(value, options.secretOrKey) + value = jwt.sign(value, env.JWT_SECRET) } const config: SetOption = { diff --git a/packages/cli/src/hosting/makeFiles.ts b/packages/cli/src/hosting/makeFiles.ts index 9574e107a6..b11a07c107 100644 --- a/packages/cli/src/hosting/makeFiles.ts +++ b/packages/cli/src/hosting/makeFiles.ts @@ -13,6 +13,7 @@ export const ENV_PATH = path.resolve("./.env") function getSecrets(opts = { single: false }) { const secrets = [ + "API_ENCRYPTION_KEY", "JWT_SECRET", "MINIO_ACCESS_KEY", "MINIO_SECRET_KEY", diff --git a/packages/server/src/api/index.ts b/packages/server/src/api/index.ts index 78a6056366..656596a198 100644 --- a/packages/server/src/api/index.ts +++ b/packages/server/src/api/index.ts @@ -29,13 +29,6 @@ router br: false, }) ) - .use(async (ctx, next) => { - ctx.config = { - jwtSecret: env.JWT_SECRET, - useAppRootPath: true, - } - await next() - }) // re-direct before any middlewares occur .redirect("/", "/builder") .use( diff --git a/packages/server/src/environment.ts b/packages/server/src/environment.ts index 715715e42b..058e8bdff8 100644 --- a/packages/server/src/environment.ts +++ b/packages/server/src/environment.ts @@ -39,7 +39,6 @@ let inThread = false const environment = { // important - prefer app port to generic port PORT: process.env.APP_PORT || process.env.PORT, - JWT_SECRET: process.env.JWT_SECRET, COUCH_DB_URL: process.env.COUCH_DB_URL, MINIO_URL: process.env.MINIO_URL, WORKER_URL: process.env.WORKER_URL, @@ -48,7 +47,6 @@ const environment = { MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, REDIS_URL: process.env.REDIS_URL, REDIS_PASSWORD: process.env.REDIS_PASSWORD, - INTERNAL_API_KEY: process.env.INTERNAL_API_KEY, HTTP_MIGRATIONS: process.env.HTTP_MIGRATIONS, API_REQ_LIMIT_PER_SEC: process.env.API_REQ_LIMIT_PER_SEC, GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index 9aab76ba4d..cf0585efd1 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -205,7 +205,6 @@ class TestConfiguration { request.appId = appId // fake cookies, we don't need them request.cookies = { set: () => {}, get: () => {} } - request.config = { jwtSecret: env.JWT_SECRET } request.user = { appId, tenantId: this.getTenantId() } request.query = {} request.request = { @@ -332,8 +331,8 @@ class TestConfiguration { roleId: roleId, appId, } - const authToken = auth.jwt.sign(authObj, env.JWT_SECRET) - const appToken = auth.jwt.sign(app, env.JWT_SECRET) + const authToken = auth.jwt.sign(authObj, coreEnv.JWT_SECRET) + const appToken = auth.jwt.sign(app, coreEnv.JWT_SECRET) // returning necessary request headers await cache.user.invalidateUser(userId) @@ -361,8 +360,8 @@ class TestConfiguration { roleId: roles.BUILTIN_ROLE_IDS.ADMIN, appId: this.appId, } - const authToken = auth.jwt.sign(authObj, env.JWT_SECRET) - const appToken = auth.jwt.sign(app, env.JWT_SECRET) + const authToken = auth.jwt.sign(authObj, coreEnv.JWT_SECRET) + const appToken = auth.jwt.sign(app, coreEnv.JWT_SECRET) const headers: any = { Accept: "application/json", Cookie: [ diff --git a/packages/server/src/utilities/workerRequests.ts b/packages/server/src/utilities/workerRequests.ts index e318b12f82..82e1aac428 100644 --- a/packages/server/src/utilities/workerRequests.ts +++ b/packages/server/src/utilities/workerRequests.ts @@ -6,6 +6,7 @@ import { constants, tenancy, logging, + env as coreEnv, } from "@budibase/backend-core" import { updateAppRole } from "./global" import { BBContext, User } from "@budibase/types" @@ -15,7 +16,7 @@ export function request(ctx?: BBContext, request?: any) { request.headers = {} } if (!ctx) { - request.headers[constants.Header.API_KEY] = env.INTERNAL_API_KEY + request.headers[constants.Header.API_KEY] = coreEnv.INTERNAL_API_KEY if (tenancy.isTenantIdSet()) { request.headers[constants.Header.TENANT_ID] = tenancy.getTenantId() } diff --git a/packages/worker/src/environment.ts b/packages/worker/src/environment.ts index c6618a75df..3f762ea5ab 100644 --- a/packages/worker/src/environment.ts +++ b/packages/worker/src/environment.ts @@ -30,10 +30,8 @@ const environment = { // auth MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, - JWT_SECRET: process.env.JWT_SECRET, SALT_ROUNDS: process.env.SALT_ROUNDS, REDIS_PASSWORD: process.env.REDIS_PASSWORD, - INTERNAL_API_KEY: process.env.INTERNAL_API_KEY, COOKIE_DOMAIN: process.env.COOKIE_DOMAIN, // urls MINIO_URL: process.env.MINIO_URL, diff --git a/packages/worker/src/middleware/cloudRestricted.ts b/packages/worker/src/middleware/cloudRestricted.ts index 5440629de3..f9ab86e2e9 100644 --- a/packages/worker/src/middleware/cloudRestricted.ts +++ b/packages/worker/src/middleware/cloudRestricted.ts @@ -1,5 +1,5 @@ import env from "../environment" -import { constants } from "@budibase/backend-core" +import { constants, utils } from "@budibase/backend-core" import { BBContext } from "@budibase/types" /** @@ -9,7 +9,15 @@ import { BBContext } from "@budibase/types" export default async (ctx: BBContext, next: any) => { if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) { const apiKey = ctx.request.headers[constants.Header.API_KEY] - if (apiKey !== env.INTERNAL_API_KEY) { + if (!apiKey) { + ctx.throw(403, "Unauthorized") + } + + if (Array.isArray(apiKey)) { + ctx.throw(403, "Unauthorized") + } + + if (!utils.isValidInternalAPIKey(apiKey)) { ctx.throw(403, "Unauthorized") } } diff --git a/packages/worker/src/sdk/auth/auth.ts b/packages/worker/src/sdk/auth/auth.ts index 98830c576d..2e716426d5 100644 --- a/packages/worker/src/sdk/auth/auth.ts +++ b/packages/worker/src/sdk/auth/auth.ts @@ -5,10 +5,10 @@ import { sessions, events, HTTPError, + env as coreEnv, } from "@budibase/backend-core" import { PlatformLogoutOpts, User } from "@budibase/types" import jwt from "jsonwebtoken" -import env from "../../environment" import * as userSdk from "../users" import * as emails from "../../utilities/email" import * as redis from "../../utilities/redis" @@ -26,7 +26,7 @@ export async function loginUser(user: User) { sessionId, tenantId, }, - env.JWT_SECRET! + coreEnv.JWT_SECRET! ) return token } diff --git a/packages/worker/src/tests/TestConfiguration.ts b/packages/worker/src/tests/TestConfiguration.ts index 3004d0aed4..e5ed9e8141 100644 --- a/packages/worker/src/tests/TestConfiguration.ts +++ b/packages/worker/src/tests/TestConfiguration.ts @@ -74,7 +74,6 @@ class TestConfiguration { const request: any = {} // fake cookies, we don't need them request.cookies = { set: () => {}, get: () => {} } - request.config = { jwtSecret: env.JWT_SECRET } request.user = { tenantId: this.getTenantId() } request.query = {} request.request = { @@ -180,7 +179,7 @@ class TestConfiguration { sessionId: "sessionid", tenantId: user.tenantId, } - const authCookie = auth.jwt.sign(authToken, env.JWT_SECRET) + const authCookie = auth.jwt.sign(authToken, coreEnv.JWT_SECRET) return { Accept: "application/json", ...this.cookieHeader([`${constants.Cookie.Auth}=${authCookie}`]), @@ -197,7 +196,7 @@ class TestConfiguration { } internalAPIHeaders() { - return { [constants.Header.API_KEY]: env.INTERNAL_API_KEY } + return { [constants.Header.API_KEY]: coreEnv.INTERNAL_API_KEY } } adminOnlyResponse = () => { @@ -277,7 +276,7 @@ class TestConfiguration { // CONFIGS - OIDC getOIDConfigCookie(configId: string) { - const token = auth.jwt.sign(configId, env.JWT_SECRET) + const token = auth.jwt.sign(configId, coreEnv.JWT_SECRET) return this.cookieHeader([[`${constants.Cookie.OIDC_CONFIG}=${token}`]]) } diff --git a/packages/worker/src/utilities/appService.ts b/packages/worker/src/utilities/appService.ts index 478e986fe8..8f411d58fa 100644 --- a/packages/worker/src/utilities/appService.ts +++ b/packages/worker/src/utilities/appService.ts @@ -1,5 +1,10 @@ import fetch from "node-fetch" -import { constants, tenancy, logging } from "@budibase/backend-core" +import { + constants, + tenancy, + logging, + env as coreEnv, +} from "@budibase/backend-core" import { checkSlashesInUrl } from "../utilities" import env from "../environment" import { SyncUserRequest, User } from "@budibase/types" @@ -9,7 +14,7 @@ async function makeAppRequest(url: string, method: string, body: any) { return } const request: any = { headers: {} } - request.headers[constants.Header.API_KEY] = env.INTERNAL_API_KEY + request.headers[constants.Header.API_KEY] = coreEnv.INTERNAL_API_KEY if (tenancy.isTenantIdSet()) { request.headers[constants.Header.TENANT_ID] = tenancy.getTenantId() }