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
This commit is contained in:
parent
e74f80742e
commit
e116941750
|
@ -62,16 +62,31 @@ spec:
|
||||||
{{ end }}
|
{{ end }}
|
||||||
- name: ENABLE_ANALYTICS
|
- name: ENABLE_ANALYTICS
|
||||||
value: {{ .Values.globals.enableAnalytics | quote }}
|
value: {{ .Values.globals.enableAnalytics | quote }}
|
||||||
|
- name: API_ENCRYPTION_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: budibase-budibase {{ template "budibase.fullname" . }}
|
||||||
|
key: apiEncryptionKey
|
||||||
- name: INTERNAL_API_KEY
|
- name: INTERNAL_API_KEY
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: {{ template "budibase.fullname" . }}
|
name: {{ template "budibase.fullname" . }}
|
||||||
key: internalApiKey
|
key: internalApiKey
|
||||||
|
- name: INTERNAL_API_KEY_FALLBACK
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ template "budibase.fullname" . }}
|
||||||
|
key: internalApiKeyFallback
|
||||||
- name: JWT_SECRET
|
- name: JWT_SECRET
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: {{ template "budibase.fullname" . }}
|
name: {{ template "budibase.fullname" . }}
|
||||||
key: jwtSecret
|
key: jwtSecret
|
||||||
|
- name: JWT_SECRET_FALLBACK
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ template "budibase.fullname" . }}
|
||||||
|
key: jwtSecretFallback
|
||||||
{{ if .Values.services.objectStore.region }}
|
{{ if .Values.services.objectStore.region }}
|
||||||
- name: AWS_REGION
|
- name: AWS_REGION
|
||||||
value: {{ .Values.services.objectStore.region }}
|
value: {{ .Values.services.objectStore.region }}
|
||||||
|
|
|
@ -10,8 +10,14 @@ metadata:
|
||||||
heritage: "{{ .Release.Service }}"
|
heritage: "{{ .Release.Service }}"
|
||||||
type: Opaque
|
type: Opaque
|
||||||
data:
|
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 }}
|
internalApiKey: {{ template "budibase.defaultsecret" .Values.globals.internalApiKey }}
|
||||||
|
{{/* Fallback value auto generated */}}
|
||||||
|
internalApiKeyFallback: {{ .Values.globals.internalApiKeyFallback }}
|
||||||
jwtSecret: {{ template "budibase.defaultsecret" .Values.globals.jwtSecret }}
|
jwtSecret: {{ template "budibase.defaultsecret" .Values.globals.jwtSecret }}
|
||||||
|
{{/* Falback value never auto generated */}}
|
||||||
|
jwtSecretFallback: {{ .Values.globals.jwtSecretFallback }}
|
||||||
objectStoreAccess: {{ template "budibase.defaultsecret" .Values.services.objectStore.accessKey }}
|
objectStoreAccess: {{ template "budibase.defaultsecret" .Values.services.objectStore.accessKey }}
|
||||||
objectStoreSecret: {{ template "budibase.defaultsecret" .Values.services.objectStore.secretKey }}
|
objectStoreSecret: {{ template "budibase.defaultsecret" .Values.services.objectStore.secretKey }}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
|
@ -62,16 +62,31 @@ spec:
|
||||||
{{ else }}
|
{{ else }}
|
||||||
value: http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }}
|
value: http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
- name: API_ENCRYPTION_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: budibase-budibase {{ template "budibase.fullname" . }}
|
||||||
|
key: apiEncryptionKey
|
||||||
- name: INTERNAL_API_KEY
|
- name: INTERNAL_API_KEY
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: {{ template "budibase.fullname" . }}
|
name: {{ template "budibase.fullname" . }}
|
||||||
key: internalApiKey
|
key: internalApiKey
|
||||||
|
- name: INTERNAL_API_KEY_FALLBACK
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ template "budibase.fullname" . }}
|
||||||
|
key: internalApiKeyFallback
|
||||||
- name: JWT_SECRET
|
- name: JWT_SECRET
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: {{ template "budibase.fullname" . }}
|
name: {{ template "budibase.fullname" . }}
|
||||||
key: jwtSecret
|
key: jwtSecret
|
||||||
|
- name: JWT_SECRET_FALLBACK
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ template "budibase.fullname" . }}
|
||||||
|
key: jwtSecretFallback
|
||||||
{{ if .Values.services.objectStore.region }}
|
{{ if .Values.services.objectStore.region }}
|
||||||
- name: AWS_REGION
|
- name: AWS_REGION
|
||||||
value: {{ .Values.services.objectStore.region }}
|
value: {{ .Values.services.objectStore.region }}
|
||||||
|
|
|
@ -96,9 +96,13 @@ globals:
|
||||||
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
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
|
# if createSecrets is set to false, you can hard-code your secrets here
|
||||||
|
apiEncryptionKey: ""
|
||||||
internalApiKey: ""
|
internalApiKey: ""
|
||||||
jwtSecret: ""
|
jwtSecret: ""
|
||||||
cdnUrl: ""
|
cdnUrl: ""
|
||||||
|
# fallback values used during live rotation
|
||||||
|
internalApiKeyFallback: ""
|
||||||
|
jwtSecretFallback: ""
|
||||||
|
|
||||||
smtp:
|
smtp:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
|
@ -3,6 +3,7 @@ MAIN_PORT=10000
|
||||||
|
|
||||||
# This section contains all secrets pertaining to the system
|
# This section contains all secrets pertaining to the system
|
||||||
# These should be updated
|
# These should be updated
|
||||||
|
API_ENCRYPTION_KEY=testsecret
|
||||||
JWT_SECRET=testsecret
|
JWT_SECRET=testsecret
|
||||||
MINIO_ACCESS_KEY=budibase
|
MINIO_ACCESS_KEY=budibase
|
||||||
MINIO_SECRET_KEY=budibase
|
MINIO_SECRET_KEY=budibase
|
||||||
|
|
|
@ -17,6 +17,7 @@ services:
|
||||||
INTERNAL_API_KEY: ${INTERNAL_API_KEY}
|
INTERNAL_API_KEY: ${INTERNAL_API_KEY}
|
||||||
BUDIBASE_ENVIRONMENT: ${BUDIBASE_ENVIRONMENT}
|
BUDIBASE_ENVIRONMENT: ${BUDIBASE_ENVIRONMENT}
|
||||||
PORT: 4002
|
PORT: 4002
|
||||||
|
API_ENCRYPTION_KEY: ${API_ENCRYPTION_KEY}
|
||||||
JWT_SECRET: ${JWT_SECRET}
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
LOG_LEVEL: info
|
LOG_LEVEL: info
|
||||||
SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131
|
SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131
|
||||||
|
@ -40,6 +41,7 @@ services:
|
||||||
SELF_HOSTED: 1
|
SELF_HOSTED: 1
|
||||||
PORT: 4003
|
PORT: 4003
|
||||||
CLUSTER_PORT: ${MAIN_PORT}
|
CLUSTER_PORT: ${MAIN_PORT}
|
||||||
|
API_ENCRYPTION_KEY: ${API_ENCRYPTION_KEY}
|
||||||
JWT_SECRET: ${JWT_SECRET}
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
|
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
|
||||||
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
|
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
|
||||||
|
|
|
@ -3,6 +3,7 @@ MAIN_PORT=10000
|
||||||
|
|
||||||
# This section contains all secrets pertaining to the system
|
# This section contains all secrets pertaining to the system
|
||||||
# These should be updated
|
# These should be updated
|
||||||
|
API_ENCRYPTION_KEY=testsecret
|
||||||
JWT_SECRET=testsecret
|
JWT_SECRET=testsecret
|
||||||
MINIO_ACCESS_KEY=budibase
|
MINIO_ACCESS_KEY=budibase
|
||||||
MINIO_SECRET_KEY=budibase
|
MINIO_SECRET_KEY=budibase
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
const _passport = require("koa-passport")
|
const _passport = require("koa-passport")
|
||||||
const LocalStrategy = require("passport-local").Strategy
|
const LocalStrategy = require("passport-local").Strategy
|
||||||
const JwtStrategy = require("passport-jwt").Strategy
|
|
||||||
import { getGlobalDB } from "../context"
|
import { getGlobalDB } from "../context"
|
||||||
import { Cookie } from "../constants"
|
import { Cookie } from "../constants"
|
||||||
import { getSessionsForUser, invalidateSessions } from "../security/sessions"
|
import { getSessionsForUser, invalidateSessions } from "../security/sessions"
|
||||||
|
@ -8,7 +7,6 @@ import {
|
||||||
authenticated,
|
authenticated,
|
||||||
csrf,
|
csrf,
|
||||||
google,
|
google,
|
||||||
jwt as jwtPassport,
|
|
||||||
local,
|
local,
|
||||||
oidc,
|
oidc,
|
||||||
tenancy,
|
tenancy,
|
||||||
|
@ -21,14 +19,11 @@ import {
|
||||||
OIDCInnerConfig,
|
OIDCInnerConfig,
|
||||||
PlatformLogoutOpts,
|
PlatformLogoutOpts,
|
||||||
SSOProviderType,
|
SSOProviderType,
|
||||||
User,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { logAlert } from "../logging"
|
|
||||||
import * as events from "../events"
|
import * as events from "../events"
|
||||||
import * as configs from "../configs"
|
import * as configs from "../configs"
|
||||||
import { clearCookie, getCookie } from "../utils"
|
import { clearCookie, getCookie } from "../utils"
|
||||||
import { ssoSaveUserNoOp } from "../middleware/passport/sso/sso"
|
import { ssoSaveUserNoOp } from "../middleware/passport/sso/sso"
|
||||||
import env from "../environment"
|
|
||||||
|
|
||||||
const refresh = require("passport-oauth2-refresh")
|
const refresh = require("passport-oauth2-refresh")
|
||||||
export {
|
export {
|
||||||
|
@ -51,25 +46,6 @@ export const jwt = require("jsonwebtoken")
|
||||||
|
|
||||||
// Strategies
|
// Strategies
|
||||||
_passport.use(new LocalStrategy(local.options, local.authenticate))
|
_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(
|
async function refreshOIDCAccessToken(
|
||||||
chosenConfig: OIDCInnerConfig,
|
chosenConfig: OIDCInnerConfig,
|
||||||
|
|
|
@ -30,6 +30,12 @@ const DefaultBucketName = {
|
||||||
|
|
||||||
const selfHosted = !!parseInt(process.env.SELF_HOSTED || "")
|
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 = {
|
const environment = {
|
||||||
isTest,
|
isTest,
|
||||||
isJest,
|
isJest,
|
||||||
|
@ -39,7 +45,9 @@ const environment = {
|
||||||
},
|
},
|
||||||
JS_BCRYPT: process.env.JS_BCRYPT,
|
JS_BCRYPT: process.env.JS_BCRYPT,
|
||||||
JWT_SECRET: process.env.JWT_SECRET,
|
JWT_SECRET: process.env.JWT_SECRET,
|
||||||
|
JWT_SECRET_FALLBACK: process.env.JWT_SECRET_FALLBACK,
|
||||||
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
|
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
|
||||||
|
API_ENCRYPTION_KEY: getAPIEncryptionKey(),
|
||||||
COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005",
|
COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005",
|
||||||
COUCH_DB_USERNAME: process.env.COUCH_DB_USER,
|
COUCH_DB_USERNAME: process.env.COUCH_DB_USER,
|
||||||
COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD,
|
COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD,
|
||||||
|
@ -55,6 +63,7 @@ const environment = {
|
||||||
MINIO_URL: process.env.MINIO_URL,
|
MINIO_URL: process.env.MINIO_URL,
|
||||||
MINIO_ENABLED: process.env.MINIO_ENABLED || 1,
|
MINIO_ENABLED: process.env.MINIO_ENABLED || 1,
|
||||||
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
|
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
|
||||||
|
INTERNAL_API_KEY_FALLBACK: process.env.INTERNAL_API_KEY_FALLBACK,
|
||||||
MULTI_TENANCY: process.env.MULTI_TENANCY,
|
MULTI_TENANCY: process.env.MULTI_TENANCY,
|
||||||
ACCOUNT_PORTAL_URL:
|
ACCOUNT_PORTAL_URL:
|
||||||
process.env.ACCOUNT_PORTAL_URL || "https://account.budibase.app",
|
process.env.ACCOUNT_PORTAL_URL || "https://account.budibase.app",
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { Cookie, Header } from "../constants"
|
import { Cookie, Header } from "../constants"
|
||||||
import { getCookie, clearCookie, openJwt } from "../utils"
|
import {
|
||||||
|
getCookie,
|
||||||
|
clearCookie,
|
||||||
|
openJwt,
|
||||||
|
isValidInternalAPIKey,
|
||||||
|
} from "../utils"
|
||||||
import { getUser } from "../cache/user"
|
import { getUser } from "../cache/user"
|
||||||
import { getSession, updateSessionTTL } from "../security/sessions"
|
import { getSession, updateSessionTTL } from "../security/sessions"
|
||||||
import { buildMatcherRegex, matches } from "./matchers"
|
import { buildMatcherRegex, matches } from "./matchers"
|
||||||
|
@ -35,7 +40,9 @@ function finalise(ctx: any, opts: FinaliseOpts = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkApiKey(apiKey: string, populateUser?: Function) {
|
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 }
|
return { valid: true }
|
||||||
}
|
}
|
||||||
const decrypted = decrypt(apiKey)
|
const decrypted = decrypt(apiKey)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
export * as jwt from "./passport/jwt"
|
|
||||||
export * as local from "./passport/local"
|
export * as local from "./passport/local"
|
||||||
export * as google from "./passport/sso/google"
|
export * as google from "./passport/sso/google"
|
||||||
export * as oidc from "./passport/sso/oidc"
|
export * as oidc from "./passport/sso/oidc"
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
import env from "../environment"
|
|
||||||
import { Header } from "../constants"
|
import { Header } from "../constants"
|
||||||
import { BBContext } from "@budibase/types"
|
import { BBContext } from "@budibase/types"
|
||||||
|
import { isValidInternalAPIKey } from "../utils"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API Key only endpoint.
|
* API Key only endpoint.
|
||||||
*/
|
*/
|
||||||
export default async (ctx: BBContext, next: any) => {
|
export default async (ctx: BBContext, next: any) => {
|
||||||
const apiKey = ctx.request.headers[Header.API_KEY]
|
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")
|
ctx.throw(403, "Unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,7 @@ const RANDOM_BYTES = 16
|
||||||
const STRETCH_LENGTH = 32
|
const STRETCH_LENGTH = 32
|
||||||
|
|
||||||
export enum SecretOption {
|
export enum SecretOption {
|
||||||
JWT = "jwt",
|
API = "api",
|
||||||
ENCRYPTION = "encryption",
|
ENCRYPTION = "encryption",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@ function getSecret(secretOption: SecretOption): string {
|
||||||
secret = env.ENCRYPTION_KEY
|
secret = env.ENCRYPTION_KEY
|
||||||
secretName = "ENCRYPTION_KEY"
|
secretName = "ENCRYPTION_KEY"
|
||||||
break
|
break
|
||||||
case SecretOption.JWT:
|
case SecretOption.API:
|
||||||
default:
|
default:
|
||||||
secret = env.JWT_SECRET
|
secret = env.API_ENCRYPTION_KEY
|
||||||
secretName = "JWT_SECRET"
|
secretName = "API_ENCRYPTION_KEY"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (!secret) {
|
if (!secret) {
|
||||||
|
@ -37,7 +37,7 @@ function stretchString(string: string, salt: Buffer) {
|
||||||
|
|
||||||
export function encrypt(
|
export function encrypt(
|
||||||
input: string,
|
input: string,
|
||||||
secretOption: SecretOption = SecretOption.JWT
|
secretOption: SecretOption = SecretOption.API
|
||||||
) {
|
) {
|
||||||
const salt = crypto.randomBytes(RANDOM_BYTES)
|
const salt = crypto.randomBytes(RANDOM_BYTES)
|
||||||
const stretched = stretchString(getSecret(secretOption), salt)
|
const stretched = stretchString(getSecret(secretOption), salt)
|
||||||
|
@ -50,7 +50,7 @@ export function encrypt(
|
||||||
|
|
||||||
export function decrypt(
|
export function decrypt(
|
||||||
input: string,
|
input: string,
|
||||||
secretOption: SecretOption = SecretOption.JWT
|
secretOption: SecretOption = SecretOption.API
|
||||||
) {
|
) {
|
||||||
const [salt, encrypted] = input.split(SEPARATOR)
|
const [salt, encrypted] = input.split(SEPARATOR)
|
||||||
const saltBuffer = Buffer.from(salt, "hex")
|
const saltBuffer = Buffer.from(salt, "hex")
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { getAllApps, queryGlobalView } from "../db"
|
import { getAllApps, queryGlobalView } from "../db"
|
||||||
import { options } from "../middleware/passport/jwt"
|
|
||||||
import {
|
import {
|
||||||
Header,
|
Header,
|
||||||
MAX_VALID_DATE,
|
MAX_VALID_DATE,
|
||||||
|
@ -133,7 +132,30 @@ export function openJwt(token: string) {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return 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 }
|
opts = { sign: true }
|
||||||
) {
|
) {
|
||||||
if (value && opts && opts.sign) {
|
if (value && opts && opts.sign) {
|
||||||
value = jwt.sign(value, options.secretOrKey)
|
value = jwt.sign(value, env.JWT_SECRET)
|
||||||
}
|
}
|
||||||
|
|
||||||
const config: SetOption = {
|
const config: SetOption = {
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const ENV_PATH = path.resolve("./.env")
|
||||||
|
|
||||||
function getSecrets(opts = { single: false }) {
|
function getSecrets(opts = { single: false }) {
|
||||||
const secrets = [
|
const secrets = [
|
||||||
|
"API_ENCRYPTION_KEY",
|
||||||
"JWT_SECRET",
|
"JWT_SECRET",
|
||||||
"MINIO_ACCESS_KEY",
|
"MINIO_ACCESS_KEY",
|
||||||
"MINIO_SECRET_KEY",
|
"MINIO_SECRET_KEY",
|
||||||
|
|
|
@ -29,13 +29,6 @@ router
|
||||||
br: false,
|
br: false,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.use(async (ctx, next) => {
|
|
||||||
ctx.config = {
|
|
||||||
jwtSecret: env.JWT_SECRET,
|
|
||||||
useAppRootPath: true,
|
|
||||||
}
|
|
||||||
await next()
|
|
||||||
})
|
|
||||||
// re-direct before any middlewares occur
|
// re-direct before any middlewares occur
|
||||||
.redirect("/", "/builder")
|
.redirect("/", "/builder")
|
||||||
.use(
|
.use(
|
||||||
|
|
|
@ -39,7 +39,6 @@ let inThread = false
|
||||||
const environment = {
|
const environment = {
|
||||||
// important - prefer app port to generic port
|
// important - prefer app port to generic port
|
||||||
PORT: process.env.APP_PORT || process.env.PORT,
|
PORT: process.env.APP_PORT || process.env.PORT,
|
||||||
JWT_SECRET: process.env.JWT_SECRET,
|
|
||||||
COUCH_DB_URL: process.env.COUCH_DB_URL,
|
COUCH_DB_URL: process.env.COUCH_DB_URL,
|
||||||
MINIO_URL: process.env.MINIO_URL,
|
MINIO_URL: process.env.MINIO_URL,
|
||||||
WORKER_URL: process.env.WORKER_URL,
|
WORKER_URL: process.env.WORKER_URL,
|
||||||
|
@ -48,7 +47,6 @@ const environment = {
|
||||||
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
||||||
REDIS_URL: process.env.REDIS_URL,
|
REDIS_URL: process.env.REDIS_URL,
|
||||||
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
|
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
|
||||||
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
|
|
||||||
HTTP_MIGRATIONS: process.env.HTTP_MIGRATIONS,
|
HTTP_MIGRATIONS: process.env.HTTP_MIGRATIONS,
|
||||||
API_REQ_LIMIT_PER_SEC: process.env.API_REQ_LIMIT_PER_SEC,
|
API_REQ_LIMIT_PER_SEC: process.env.API_REQ_LIMIT_PER_SEC,
|
||||||
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
|
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
|
||||||
|
|
|
@ -205,7 +205,6 @@ class TestConfiguration {
|
||||||
request.appId = appId
|
request.appId = appId
|
||||||
// fake cookies, we don't need them
|
// fake cookies, we don't need them
|
||||||
request.cookies = { set: () => {}, get: () => {} }
|
request.cookies = { set: () => {}, get: () => {} }
|
||||||
request.config = { jwtSecret: env.JWT_SECRET }
|
|
||||||
request.user = { appId, tenantId: this.getTenantId() }
|
request.user = { appId, tenantId: this.getTenantId() }
|
||||||
request.query = {}
|
request.query = {}
|
||||||
request.request = {
|
request.request = {
|
||||||
|
@ -332,8 +331,8 @@ class TestConfiguration {
|
||||||
roleId: roleId,
|
roleId: roleId,
|
||||||
appId,
|
appId,
|
||||||
}
|
}
|
||||||
const authToken = auth.jwt.sign(authObj, env.JWT_SECRET)
|
const authToken = auth.jwt.sign(authObj, coreEnv.JWT_SECRET)
|
||||||
const appToken = auth.jwt.sign(app, env.JWT_SECRET)
|
const appToken = auth.jwt.sign(app, coreEnv.JWT_SECRET)
|
||||||
|
|
||||||
// returning necessary request headers
|
// returning necessary request headers
|
||||||
await cache.user.invalidateUser(userId)
|
await cache.user.invalidateUser(userId)
|
||||||
|
@ -361,8 +360,8 @@ class TestConfiguration {
|
||||||
roleId: roles.BUILTIN_ROLE_IDS.ADMIN,
|
roleId: roles.BUILTIN_ROLE_IDS.ADMIN,
|
||||||
appId: this.appId,
|
appId: this.appId,
|
||||||
}
|
}
|
||||||
const authToken = auth.jwt.sign(authObj, env.JWT_SECRET)
|
const authToken = auth.jwt.sign(authObj, coreEnv.JWT_SECRET)
|
||||||
const appToken = auth.jwt.sign(app, env.JWT_SECRET)
|
const appToken = auth.jwt.sign(app, coreEnv.JWT_SECRET)
|
||||||
const headers: any = {
|
const headers: any = {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
Cookie: [
|
Cookie: [
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
constants,
|
constants,
|
||||||
tenancy,
|
tenancy,
|
||||||
logging,
|
logging,
|
||||||
|
env as coreEnv,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { updateAppRole } from "./global"
|
import { updateAppRole } from "./global"
|
||||||
import { BBContext, User } from "@budibase/types"
|
import { BBContext, User } from "@budibase/types"
|
||||||
|
@ -15,7 +16,7 @@ export function request(ctx?: BBContext, request?: any) {
|
||||||
request.headers = {}
|
request.headers = {}
|
||||||
}
|
}
|
||||||
if (!ctx) {
|
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()) {
|
if (tenancy.isTenantIdSet()) {
|
||||||
request.headers[constants.Header.TENANT_ID] = tenancy.getTenantId()
|
request.headers[constants.Header.TENANT_ID] = tenancy.getTenantId()
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,10 +30,8 @@ const environment = {
|
||||||
// auth
|
// auth
|
||||||
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
|
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
|
||||||
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
||||||
JWT_SECRET: process.env.JWT_SECRET,
|
|
||||||
SALT_ROUNDS: process.env.SALT_ROUNDS,
|
SALT_ROUNDS: process.env.SALT_ROUNDS,
|
||||||
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
|
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
|
||||||
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
|
|
||||||
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
||||||
// urls
|
// urls
|
||||||
MINIO_URL: process.env.MINIO_URL,
|
MINIO_URL: process.env.MINIO_URL,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { constants } from "@budibase/backend-core"
|
import { constants, utils } from "@budibase/backend-core"
|
||||||
import { BBContext } from "@budibase/types"
|
import { BBContext } from "@budibase/types"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,7 +9,15 @@ import { BBContext } from "@budibase/types"
|
||||||
export default async (ctx: BBContext, next: any) => {
|
export default async (ctx: BBContext, next: any) => {
|
||||||
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
||||||
const apiKey = ctx.request.headers[constants.Header.API_KEY]
|
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")
|
ctx.throw(403, "Unauthorized")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,10 @@ import {
|
||||||
sessions,
|
sessions,
|
||||||
events,
|
events,
|
||||||
HTTPError,
|
HTTPError,
|
||||||
|
env as coreEnv,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { PlatformLogoutOpts, User } from "@budibase/types"
|
import { PlatformLogoutOpts, User } from "@budibase/types"
|
||||||
import jwt from "jsonwebtoken"
|
import jwt from "jsonwebtoken"
|
||||||
import env from "../../environment"
|
|
||||||
import * as userSdk from "../users"
|
import * as userSdk from "../users"
|
||||||
import * as emails from "../../utilities/email"
|
import * as emails from "../../utilities/email"
|
||||||
import * as redis from "../../utilities/redis"
|
import * as redis from "../../utilities/redis"
|
||||||
|
@ -26,7 +26,7 @@ export async function loginUser(user: User) {
|
||||||
sessionId,
|
sessionId,
|
||||||
tenantId,
|
tenantId,
|
||||||
},
|
},
|
||||||
env.JWT_SECRET!
|
coreEnv.JWT_SECRET!
|
||||||
)
|
)
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,6 @@ class TestConfiguration {
|
||||||
const request: any = {}
|
const request: any = {}
|
||||||
// fake cookies, we don't need them
|
// fake cookies, we don't need them
|
||||||
request.cookies = { set: () => {}, get: () => {} }
|
request.cookies = { set: () => {}, get: () => {} }
|
||||||
request.config = { jwtSecret: env.JWT_SECRET }
|
|
||||||
request.user = { tenantId: this.getTenantId() }
|
request.user = { tenantId: this.getTenantId() }
|
||||||
request.query = {}
|
request.query = {}
|
||||||
request.request = {
|
request.request = {
|
||||||
|
@ -180,7 +179,7 @@ class TestConfiguration {
|
||||||
sessionId: "sessionid",
|
sessionId: "sessionid",
|
||||||
tenantId: user.tenantId,
|
tenantId: user.tenantId,
|
||||||
}
|
}
|
||||||
const authCookie = auth.jwt.sign(authToken, env.JWT_SECRET)
|
const authCookie = auth.jwt.sign(authToken, coreEnv.JWT_SECRET)
|
||||||
return {
|
return {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
...this.cookieHeader([`${constants.Cookie.Auth}=${authCookie}`]),
|
...this.cookieHeader([`${constants.Cookie.Auth}=${authCookie}`]),
|
||||||
|
@ -197,7 +196,7 @@ class TestConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
internalAPIHeaders() {
|
internalAPIHeaders() {
|
||||||
return { [constants.Header.API_KEY]: env.INTERNAL_API_KEY }
|
return { [constants.Header.API_KEY]: coreEnv.INTERNAL_API_KEY }
|
||||||
}
|
}
|
||||||
|
|
||||||
adminOnlyResponse = () => {
|
adminOnlyResponse = () => {
|
||||||
|
@ -277,7 +276,7 @@ class TestConfiguration {
|
||||||
// CONFIGS - OIDC
|
// CONFIGS - OIDC
|
||||||
|
|
||||||
getOIDConfigCookie(configId: string) {
|
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}`]])
|
return this.cookieHeader([[`${constants.Cookie.OIDC_CONFIG}=${token}`]])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import fetch from "node-fetch"
|
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 { checkSlashesInUrl } from "../utilities"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { SyncUserRequest, User } from "@budibase/types"
|
import { SyncUserRequest, User } from "@budibase/types"
|
||||||
|
@ -9,7 +14,7 @@ async function makeAppRequest(url: string, method: string, body: any) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const request: any = { headers: {} }
|
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()) {
|
if (tenancy.isTenantIdSet()) {
|
||||||
request.headers[constants.Header.TENANT_ID] = tenancy.getTenantId()
|
request.headers[constants.Header.TENANT_ID] = tenancy.getTenantId()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue