Support adding own certs (#715)

* Support adding own cert

* Update the env and decode the keys before using it

* Drop the JACKSON_ prefix

* Tweaks to the getDefaultCertificate

* Remove the console.log
This commit is contained in:
Kiran K 2022-12-07 00:31:01 +05:30 committed by GitHub
parent 8a0a9bf3c5
commit cae8741307
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 52 additions and 18 deletions

View File

@ -48,4 +48,11 @@ OPENID_JWS_ALG=
OPENID_RSA_PRIVATE_KEY=
# openssl rsa -in private_key.pem -pubout -out public_key.pem
# cat public_key.pem | base64
OPENID_RSA_PUBLIC_KEY=
OPENID_RSA_PUBLIC_KEY=
# You can use `openssl req -x509 -newkey rsa:2048 -keyout key.pem -out public.crt -sha256 -days 365000 -nodes` to generate one
# Base64 encoded value of public key `cat public.crt | base64`
PUBLIC_KEY=
# Base64 encoded value of private key `cat key.pem | base64`
PRIVATE_KEY=

View File

@ -1,11 +1,11 @@
import env from '@lib/env';
import { apiKeys } from '@lib/env';
export const validateApiKey = (token: string | null) => {
if (!token) {
return false;
}
return env.apiKeys.includes(token);
return apiKeys.includes(token);
};
export const extractAuthToken = (req): string | null => {

View File

@ -1,4 +1,4 @@
import { DatabaseEngine, DatabaseType } from '@boxyhq/saml-jackson';
import type { DatabaseEngine, DatabaseType, JacksonOption } from '@boxyhq/saml-jackson';
const hostUrl = process.env.HOST_URL || 'localhost';
const hostPort = Number(process.env.PORT || '5225');
@ -40,18 +40,22 @@ const jwtSigningKeys = {
};
const openid = { jwsAlg, jwtSigningKeys };
export default {
hostUrl,
hostPort,
const jacksonOptions: JacksonOption = {
externalUrl,
samlPath,
oidcPath,
idpDiscoveryPath,
samlAudience,
preLoadedConnection,
apiKeys,
idpEnabled,
db,
clientSecretVerifier,
openid,
certs: {
publicKey: process.env.PUBLIC_KEY || '',
privateKey: process.env.PRIVATE_KEY || '',
},
};
export { apiKeys };
export { jacksonOptions };

View File

@ -22,7 +22,7 @@ import type {
} from '@boxyhq/saml-jackson';
import jackson from '@boxyhq/saml-jackson';
import env from '@lib/env';
import { jacksonOptions } from '@lib/env';
import '@lib/metrics';
let connectionAPIController: IConnectionAPIController;
@ -47,7 +47,7 @@ export default async function init() {
!g.oidcDiscoveryController ||
!g.spConfig
) {
const ret = await jackson(env);
const ret = await jackson(jacksonOptions);
connectionAPIController = ret.connectionAPIController;
oauthController = ret.oauthController;
adminController = ret.adminController;

View File

@ -1,6 +1,6 @@
import { Storable } from '@boxyhq/saml-jackson';
import DB from 'npm/src/db/db';
import opts from './env';
import { jacksonOptions } from './env';
import type { AdapterUser, VerificationToken } from 'next-auth/adapters';
import { validateEmailWithACL } from './utils';
import defaultDb from 'npm/src/db/defaultDb';
@ -9,7 +9,7 @@ const g = global as any;
export async function initNextAuthDB(): Promise<Storable> {
if (!g.adminAuthStore) {
const _opts = defaultDb(opts);
const _opts = defaultDb(jacksonOptions);
const db = await DB.new(_opts.db);
g.adminAuthStore = db.store('admin:auth');
}

View File

@ -3,10 +3,11 @@ import { marked } from 'marked';
import saml20 from '@boxyhq/saml20';
import xmlbuilder from 'xmlbuilder';
import { getDefaultCertificate } from '../saml/x509';
// Service Provider SAML Configuration
export class SPSAMLConfig {
constructor(private opts: JacksonOption, private getDefaultCertificate: any) {}
constructor(private opts: JacksonOption) {}
private get acsUrl(): string {
return `${this.opts.externalUrl}${this.opts.samlPath}`;
@ -37,7 +38,7 @@ export class SPSAMLConfig {
publicKey: string;
publicKeyString: string;
}> {
const cert = await this.getDefaultCertificate();
const cert = await getDefaultCertificate();
return {
acsUrl: this.acsUrl,

View File

@ -80,7 +80,7 @@ export const controllers = async (
await healthCheckController.init();
// Create default certificate if it doesn't exist.
await x509.init(certificateStore);
await x509.init(certificateStore, opts);
const oauthController = new OAuthController({
connectionStore,
@ -100,7 +100,7 @@ export const controllers = async (
const oidcDiscoveryController = new OidcDiscoveryController({ opts });
const spConfig = new SPSAMLConfig(opts, x509.getDefaultCertificate);
const spConfig = new SPSAMLConfig(opts);
// write pre-loaded connections if present
const preLoadedConnection = opts.preLoadedConnection || opts.preLoadedConfig;

View File

@ -1,14 +1,16 @@
import * as forge from 'node-forge';
import crypto from 'crypto';
import type { Storable } from '../typings';
import type { JacksonOption, Storable } from '../typings';
const pki = forge.pki;
let certificateStore: Storable;
let cachedCertificate: { publicKey: string; privateKey: string };
let jacksonOption: JacksonOption;
export const init = async (store: Storable) => {
export const init = async (store: Storable, opts: JacksonOption) => {
certificateStore = store;
jacksonOption = opts;
return await getDefaultCertificate();
};
@ -65,6 +67,22 @@ export const getDefaultCertificate = async (): Promise<{ publicKey: string; priv
throw new Error('Certificate store not initialized');
}
if (!jacksonOption) {
throw new Error('Jackson option not initialized');
}
// If the user has provided a certificate, use that instead of the default.
// We expect the developer to provide base64 encoded keys, so we need to decode them.
if (jacksonOption.certs?.privateKey && jacksonOption.certs?.publicKey) {
cachedCertificate = {
publicKey: Buffer.from(jacksonOption.certs.publicKey, 'base64').toString('utf-8'),
privateKey: Buffer.from(jacksonOption.certs.privateKey, 'base64').toString('utf-8'),
};
return cachedCertificate;
}
// Otherwise, use the default certificate.
cachedCertificate = await certificateStore.get('default');
// If certificate is expired let it drop through so it creates a new cert

View File

@ -323,6 +323,10 @@ export interface JacksonOption {
public: string;
};
};
certs?: {
publicKey: string;
privateKey: string;
};
}
export interface SLORequestParams {