Finish preliminary Vault integration, made docs for Vault and Checkly

This commit is contained in:
Tuan Dang
2023-06-09 15:36:37 +01:00
parent c51b194ba6
commit 00dfcfcf4e
48 changed files with 680 additions and 63 deletions

View File

@@ -86,47 +86,53 @@ export const saveIntegrationAccessToken = async (
// TODO: check if access token is valid for each integration
let integrationAuth;
const {
workspaceId,
accessId,
accessToken,
integration
}: {
workspaceId: string;
accessId: string | null;
accessToken: string;
integration: string;
} = req.body;
const {
workspaceId,
accessId,
accessToken,
url,
namespace,
integration
}: {
workspaceId: string;
accessId: string | null;
accessToken: string;
url: string;
namespace: string;
integration: string;
} = req.body;
const bot = await Bot.findOne({
workspace: new Types.ObjectId(workspaceId),
isActive: true
});
const bot = await Bot.findOne({
workspace: new Types.ObjectId(workspaceId),
isActive: true
});
if (!bot) throw new Error('Bot must be enabled to save integration access token');
if (!bot) throw new Error('Bot must be enabled to save integration access token');
integrationAuth = await IntegrationAuth.findOneAndUpdate({
workspace: new Types.ObjectId(workspaceId),
integration
}, {
workspace: new Types.ObjectId(workspaceId),
integration,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8
}, {
new: true,
upsert: true
});
integrationAuth = await IntegrationAuth.findOneAndUpdate({
workspace: new Types.ObjectId(workspaceId),
integration
}, {
workspace: new Types.ObjectId(workspaceId),
integration,
url,
namespace,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8
}, {
new: true,
upsert: true
});
// encrypt and save integration access details
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(),
accessId,
accessToken,
accessExpiresAt: undefined
});
// encrypt and save integration access details
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(),
accessId,
accessToken,
accessExpiresAt: undefined
});
if (!integrationAuth) throw new Error('Failed to save integration access token');
if (!integrationAuth) throw new Error('Failed to save integration access token');
return res.status(200).send({
integrationAuth

View File

@@ -57,6 +57,7 @@ export const createIntegration = async (req: Request, res: Response) => {
})
});
}
return res.status(200).send({
integration,
});

View File

@@ -36,7 +36,8 @@ import {
INTEGRATION_TRAVISCI_API_URL,
INTEGRATION_SUPABASE_API_URL,
INTEGRATION_CHECKLY,
INTEGRATION_CHECKLY_API_URL
INTEGRATION_CHECKLY_API_URL,
INTEGRATION_HASHICORP_VAULT
} from "../variables";
import { standardRequest} from '../config/request';
@@ -200,6 +201,15 @@ const syncSecrets = async ({
accessToken,
});
break;
case INTEGRATION_HASHICORP_VAULT:
await syncSecretsHashiCorpVault({
integration,
integrationAuth,
secrets,
accessId,
accessToken
});
break;
}
};
@@ -1762,5 +1772,65 @@ const syncSecretsCheckly = async ({
}
};
/**
* Sync/push [secrets] to HashiCorp Vault path
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for HashiCorp Vault integration
*/
const syncSecretsHashiCorpVault = async ({
integration,
integrationAuth,
secrets,
accessId,
accessToken,
}: {
integration: IIntegration;
integrationAuth: IIntegrationAuth;
secrets: any;
accessId: string | null;
accessToken: string;
}) => {
if (!accessId) return;
interface LoginAppRoleRes {
auth: {
client_token: string;
}
}
// get Vault client token (could be optimized)
const { data }: { data: LoginAppRoleRes } = await standardRequest.post(
`${integrationAuth.url}/v1/auth/approle/login`,
{
"role_id": accessId,
"secret_id": accessToken
},
{
headers: {
"X-Vault-Namespace": integrationAuth.namespace
}
}
);
const clientToken = data.auth.client_token;
await standardRequest.post(
`${integrationAuth.url}/v1/${integration.app}/data/${integration.path}`,
{
data: secrets
},
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept": "application/json",
"Content-Type": "application/json",
"X-Vault-Token": clientToken,
"X-Vault-Namespace": integrationAuth.namespace
},
}
);
};
export { syncSecrets };

View File

@@ -3,6 +3,7 @@ import { ErrorRequestHandler } from 'express';
import { InternalServerError } from '../utils/errors';
import { getLogger } from '../utils/logger';
import RequestError, { LogLevel } from '../utils/requestError';
import { getNodeEnv } from '../config';
export const requestErrorHandler: ErrorRequestHandler = async (
error: RequestError | Error,
@@ -12,6 +13,11 @@ export const requestErrorHandler: ErrorRequestHandler = async (
) => {
if (res.headersSent) return next();
if (await getNodeEnv() !== "production") {
/* eslint-disable no-console */
console.error(error);
}
//TODO: Find better way to type check for error. In current setting you need to cast type to get the functions and variables from RequestError
if (!(error instanceof RequestError)) {
error = InternalServerError({

View File

@@ -14,7 +14,8 @@ import {
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE,
INTEGRATION_CHECKLY
INTEGRATION_CHECKLY,
INTEGRATION_HASHICORP_VAULT
} from "../variables";
export interface IIntegration {
@@ -47,7 +48,8 @@ export interface IIntegration {
| 'circleci'
| 'travisci'
| 'supabase'
| 'checkly';
| 'checkly'
| 'hashicorp-vault';
integrationAuth: Types.ObjectId;
}
@@ -133,7 +135,8 @@ const integrationSchema = new Schema<IIntegration>(
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE,
INTEGRATION_CHECKLY
INTEGRATION_CHECKLY,
INTEGRATION_HASHICORP_VAULT
],
required: true,
},

View File

@@ -14,6 +14,7 @@ import {
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE,
INTEGRATION_HASHICORP_VAULT,
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
ENCODING_SCHEME_BASE64
@@ -25,6 +26,8 @@ export interface IIntegrationAuth extends Document {
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager' | 'checkly';
teamId: string;
accountId: string;
url: string;
namespace: string;
refreshCiphertext?: string;
refreshIV?: string;
refreshTag?: string;
@@ -62,7 +65,8 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE
INTEGRATION_SUPABASE,
INTEGRATION_HASHICORP_VAULT
],
required: true,
},
@@ -70,6 +74,14 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
// vercel-specific integration param
type: String,
},
url: {
// for any self-hosted integrations (e.g. self-hosted hashicorp-vault)
type: String
},
namespace: {
// hashicorp-vault-specific integration param
type: String
},
accountId: {
// netlify-specific integration param
type: String,

View File

@@ -15,7 +15,7 @@ import {
import { body, param } from 'express-validator';
import { integrationController } from '../../controllers/v1';
router.post( // new: add new integration for integration auth
router.post(
'/',
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]

View File

@@ -57,6 +57,8 @@ router.post(
body('workspaceId').exists().trim().notEmpty(),
body('accessId').trim(),
body('accessToken').exists().trim().notEmpty(),
body('url').trim(),
body('namespace').trim(),
body('integration').exists().trim().notEmpty(),
validateRequest,
requireAuth({

View File

@@ -23,6 +23,7 @@ export const INTEGRATION_CIRCLECI = "circleci";
export const INTEGRATION_TRAVISCI = "travisci";
export const INTEGRATION_SUPABASE = 'supabase';
export const INTEGRATION_CHECKLY = 'checkly';
export const INTEGRATION_HASHICORP_VAULT = 'hashicorp-vault';
export const INTEGRATION_SET = new Set([
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU,
@@ -35,7 +36,8 @@ export const INTEGRATION_SET = new Set([
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE,
INTEGRATION_CHECKLY
INTEGRATION_CHECKLY,
INTEGRATION_HASHICORP_VAULT
]);
// integration types
@@ -202,6 +204,15 @@ export const getIntegrationOptions = async () => {
clientId: '',
docsLink: ''
},
{
name: 'HashiCorp Vault',
slug: 'hashicorp-vault',
image: 'Vault.png',
isAvailable: true,
type: 'pat',
clientId: '',
docsLink: ''
},
{
name: 'Google Cloud Platform',
slug: 'gcp',