Add support and docs for AWS parameter store and secret manager
3308
backend/package-lock.json
generated
@@ -1,11 +1,18 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.267.0",
|
||||
"@godaddy/terminus": "^4.11.2",
|
||||
"@sentry/node": "^7.21.1",
|
||||
"@sentry/tracing": "^7.21.1",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/node": "^7.14.0",
|
||||
"@sentry/tracing": "^7.19.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"axios": "^1.2.0",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
"await-to-js": "^3.0.0",
|
||||
"aws-sdk": "^2.1311.0",
|
||||
"axios": "^1.1.3",
|
||||
"bcrypt": "^5.1.0",
|
||||
"bigint-conversion": "^2.2.2",
|
||||
"builder-pattern": "^2.2.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"crypto-js": "^4.1.1",
|
||||
@@ -15,17 +22,26 @@
|
||||
"express-validator": "^6.14.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"helmet": "^5.1.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jsrp": "^0.2.4",
|
||||
"mongoose": "^6.7.3",
|
||||
"libsodium-wrappers": "^0.7.10",
|
||||
"lodash": "^4.17.21",
|
||||
"mongoose": "^6.7.2",
|
||||
"nodemailer": "^6.8.0",
|
||||
"posthog-node": "^2.1.0",
|
||||
"posthog-node": "^2.2.2",
|
||||
"query-string": "^7.1.3",
|
||||
"request-ip": "^3.3.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"stripe": "^10.7.0",
|
||||
"swagger-autogen": "^2.22.0",
|
||||
"swagger-ui-express": "^4.6.0",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"tweetnacl-util": "^0.15.1",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^4.9.3",
|
||||
"utility-types": "^3.10.0",
|
||||
"winston": "^3.8.2",
|
||||
"winston-loki": "^6.0.6"
|
||||
},
|
||||
"name": "infisical-api",
|
||||
"version": "1.0.0",
|
||||
@@ -102,47 +118,5 @@
|
||||
"suiteNameTemplate": "{filepath}",
|
||||
"classNameTemplate": "{classname}",
|
||||
"titleTemplate": "{title}"
|
||||
},
|
||||
"dependencies": {
|
||||
"@godaddy/terminus": "^4.11.2",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/node": "^7.14.0",
|
||||
"@sentry/tracing": "^7.19.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
"await-to-js": "^3.0.0",
|
||||
"axios": "^1.1.3",
|
||||
"bcrypt": "^5.1.0",
|
||||
"bigint-conversion": "^2.2.2",
|
||||
"builder-pattern": "^2.2.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"express": "^4.18.1",
|
||||
"express-rate-limit": "^6.7.0",
|
||||
"express-validator": "^6.14.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"helmet": "^5.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jsrp": "^0.2.4",
|
||||
"libsodium-wrappers": "^0.7.10",
|
||||
"lodash": "^4.17.21",
|
||||
"mongoose": "^6.7.2",
|
||||
"nodemailer": "^6.8.0",
|
||||
"posthog-node": "^2.2.2",
|
||||
"query-string": "^7.1.3",
|
||||
"request-ip": "^3.3.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"stripe": "^10.7.0",
|
||||
"swagger-autogen": "^2.22.0",
|
||||
"swagger-ui-express": "^4.6.0",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"tweetnacl-util": "^0.15.1",
|
||||
"typescript": "^4.9.3",
|
||||
"utility-types": "^3.10.0",
|
||||
"winston": "^3.8.2",
|
||||
"winston-loki": "^6.0.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,8 @@ export const oAuthExchange = async (
|
||||
};
|
||||
|
||||
/**
|
||||
* Save integration access token as part of integration [integration] for workspace with id [workspaceId]
|
||||
* Save integration access token and (optionally) access id as part of integration
|
||||
* [integration] for workspace with id [workspaceId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
@@ -93,14 +94,18 @@ export const saveIntegrationAccessToken = async (
|
||||
res: Response
|
||||
) => {
|
||||
// TODO: refactor
|
||||
// TODO: check if access token is valid for each integration
|
||||
|
||||
let integrationAuth;
|
||||
try {
|
||||
const {
|
||||
workspaceId,
|
||||
accessId,
|
||||
accessToken,
|
||||
integration
|
||||
}: {
|
||||
workspaceId: string;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
integration: string;
|
||||
} = req.body;
|
||||
@@ -123,9 +128,10 @@ export const saveIntegrationAccessToken = async (
|
||||
upsert: true
|
||||
});
|
||||
|
||||
// encrypt and save integration access token
|
||||
// encrypt and save integration access details
|
||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId,
|
||||
accessToken,
|
||||
accessExpiresAt: undefined
|
||||
});
|
||||
|
||||
@@ -26,7 +26,9 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
isActive,
|
||||
sourceEnvironment,
|
||||
targetEnvironment,
|
||||
owner
|
||||
owner,
|
||||
path,
|
||||
region
|
||||
} = req.body;
|
||||
|
||||
// TODO: validate [sourceEnvironment] and [targetEnvironment]
|
||||
@@ -40,6 +42,8 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
appId,
|
||||
targetEnvironment,
|
||||
owner,
|
||||
path,
|
||||
region,
|
||||
integration: req.integrationAuth.integration,
|
||||
integrationAuth: new Types.ObjectId(integrationAuthId)
|
||||
}).save();
|
||||
|
||||
@@ -94,6 +94,7 @@ const handleOAuthExchangeHelper = async ({
|
||||
// set integration auth access token
|
||||
await setIntegrationAuthAccessHelper({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId: null,
|
||||
accessToken: res.accessToken,
|
||||
accessExpiresAt: res.accessExpiresAt
|
||||
});
|
||||
@@ -138,7 +139,7 @@ const syncIntegrationsHelper = async ({
|
||||
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
||||
|
||||
// get integration auth access token
|
||||
const accessToken = await getIntegrationAuthAccessHelper({
|
||||
const access = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: integration.integrationAuth.toString()
|
||||
});
|
||||
|
||||
@@ -147,7 +148,8 @@ const syncIntegrationsHelper = async ({
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets,
|
||||
accessToken
|
||||
accessId: access.accessId,
|
||||
accessToken: access.accessToken
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -203,12 +205,12 @@ const syncIntegrationsHelper = async ({
|
||||
* @returns {String} accessToken - decrypted access token
|
||||
*/
|
||||
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
|
||||
let accessId;
|
||||
let accessToken;
|
||||
|
||||
try {
|
||||
const integrationAuth = await IntegrationAuth
|
||||
.findById(integrationAuthId)
|
||||
.select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext');
|
||||
.select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext +accessIdCiphertext +accessIdIV +accessIdTag');
|
||||
|
||||
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
|
||||
|
||||
@@ -232,6 +234,15 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (integrationAuth?.accessIdCiphertext && integrationAuth?.accessIdIV && integrationAuth?.accessIdTag) {
|
||||
accessId = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace.toString(),
|
||||
ciphertext: integrationAuth.accessIdCiphertext as string,
|
||||
iv: integrationAuth.accessIdIV as string,
|
||||
tag: integrationAuth.accessIdTag as string
|
||||
});
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
@@ -242,7 +253,10 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
|
||||
throw new Error('Failed to get integration access token');
|
||||
}
|
||||
|
||||
return accessToken;
|
||||
return ({
|
||||
accessId,
|
||||
accessToken
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,9 +306,9 @@ const setIntegrationAuthRefreshHelper = async ({
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt access token [accessToken] using the bot's copy
|
||||
* of the workspace key for workspace belonging to integration auth
|
||||
* with id [integrationAuthId] and store it along with [accessExpiresAt]
|
||||
* Encrypt access token [accessToken] and (optionally) access id [accessId]
|
||||
* using the bot's copy of the workspace key for workspace belonging to
|
||||
* integration auth with id [integrationAuthId] and store it along with [accessExpiresAt]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @param {String} obj.accessToken - access token
|
||||
@@ -302,10 +316,12 @@ const setIntegrationAuthRefreshHelper = async ({
|
||||
*/
|
||||
const setIntegrationAuthAccessHelper = async ({
|
||||
integrationAuthId,
|
||||
accessId,
|
||||
accessToken,
|
||||
accessExpiresAt
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
accessExpiresAt: Date | undefined;
|
||||
}) => {
|
||||
@@ -315,17 +331,28 @@ const setIntegrationAuthAccessHelper = async ({
|
||||
|
||||
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
||||
|
||||
const obj = await BotService.encryptSymmetric({
|
||||
const encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace.toString(),
|
||||
plaintext: accessToken
|
||||
});
|
||||
|
||||
let encryptedAccessIdObj;
|
||||
if (accessId) {
|
||||
encryptedAccessIdObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace.toString(),
|
||||
plaintext: accessId
|
||||
});
|
||||
}
|
||||
|
||||
integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
||||
_id: integrationAuthId
|
||||
}, {
|
||||
accessCiphertext: obj.ciphertext,
|
||||
accessIV: obj.iv,
|
||||
accessTag: obj.tag,
|
||||
accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined,
|
||||
accessIdIV: encryptedAccessIdObj?.iv ?? undefined,
|
||||
accessIdTag: encryptedAccessIdObj?.tag ?? undefined,
|
||||
accessCiphertext: encryptedAccessTokenObj.ciphertext,
|
||||
accessIV: encryptedAccessTokenObj.iv,
|
||||
accessTag: encryptedAccessTokenObj.tag,
|
||||
accessExpiresAt
|
||||
}, {
|
||||
new: true
|
||||
|
||||
@@ -4,6 +4,8 @@ import { Octokit } from '@octokit/rest';
|
||||
import { IIntegrationAuth } from '../models';
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -42,10 +44,14 @@ const getApps = async ({
|
||||
try {
|
||||
switch (integrationAuth.integration) {
|
||||
case INTEGRATION_AZURE_KEY_VAULT:
|
||||
apps = await getAppsAzureKeyVault({
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
apps = [];
|
||||
break;
|
||||
case INTEGRATION_AWS_PARAMETER_STORE:
|
||||
apps = [];
|
||||
break;
|
||||
case INTEGRATION_AWS_SECRET_MANAGER:
|
||||
apps = [];
|
||||
break;
|
||||
case INTEGRATION_HEROKU:
|
||||
apps = await getAppsHeroku({
|
||||
accessToken
|
||||
@@ -87,15 +93,6 @@ const getApps = async ({
|
||||
return apps;
|
||||
};
|
||||
|
||||
const getAppsAzureKeyVault = async ({
|
||||
accessToken
|
||||
}: {
|
||||
accessToken: string;
|
||||
}) => {
|
||||
// TODO
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of apps for Heroku integration
|
||||
* @param {Object} obj
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
import axios from 'axios';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import _ from 'lodash';
|
||||
import AWS from 'aws-sdk';
|
||||
import {
|
||||
SecretsManagerClient,
|
||||
UpdateSecretCommand,
|
||||
CreateSecretCommand,
|
||||
GetSecretValueCommand,
|
||||
ResourceNotFoundException
|
||||
} from '@aws-sdk/client-secrets-manager';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import sodium from 'libsodium-wrappers';
|
||||
import { IIntegration, IIntegrationAuth } from '../models';
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -24,17 +35,20 @@ import {
|
||||
* @param {IIntegration} obj.integration - integration details
|
||||
* @param {IIntegrationAuth} obj.integrationAuth - integration auth details
|
||||
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||
* @param {String} obj.accessId - access id for integration
|
||||
* @param {String} obj.accessToken - access token for integration
|
||||
*/
|
||||
const syncSecrets = async ({
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets,
|
||||
accessId,
|
||||
accessToken
|
||||
}: {
|
||||
integration: IIntegration;
|
||||
integrationAuth: IIntegrationAuth;
|
||||
secrets: any;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
try {
|
||||
@@ -46,6 +60,22 @@ const syncSecrets = async ({
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_AWS_PARAMETER_STORE:
|
||||
await syncSecretsAWSParameterStore({
|
||||
integration,
|
||||
secrets,
|
||||
accessId,
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_AWS_SECRET_MANAGER:
|
||||
await syncSecretsAWSSecretManager({
|
||||
integration,
|
||||
secrets,
|
||||
accessId,
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_HEROKU:
|
||||
await syncSecretsHeroku({
|
||||
integration,
|
||||
@@ -243,6 +273,188 @@ const syncSecretsAzureKeyVault = async ({
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to AWS parameter store
|
||||
* @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.accessId - access id for AWS parameter store integration
|
||||
* @param {String} obj.accessToken - access token for AWS parameter store integration
|
||||
*/
|
||||
const syncSecretsAWSParameterStore = async ({
|
||||
integration,
|
||||
secrets,
|
||||
accessId,
|
||||
accessToken
|
||||
}: {
|
||||
integration: IIntegration;
|
||||
secrets: any;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
try {
|
||||
if (!accessId) return;
|
||||
|
||||
AWS.config.update({
|
||||
region: integration.region,
|
||||
accessKeyId: accessId,
|
||||
secretAccessKey: accessToken
|
||||
});
|
||||
|
||||
const ssm = new AWS.SSM({
|
||||
apiVersion: '2014-11-06',
|
||||
region: integration.region
|
||||
});
|
||||
|
||||
const params = {
|
||||
Path: integration.path,
|
||||
Recursive: true,
|
||||
WithDecryption: true
|
||||
};
|
||||
|
||||
const parameterList = (await ssm.getParametersByPath(params).promise()).Parameters
|
||||
|
||||
let awsParameterStoreSecretsObj: {
|
||||
[key: string]: any // TODO: fix type
|
||||
} = {};
|
||||
|
||||
if (parameterList) {
|
||||
awsParameterStoreSecretsObj = parameterList.reduce((obj: any, secret: any) => ({
|
||||
...obj,
|
||||
[secret.Name.split("/").pop()]: secret
|
||||
}), {});
|
||||
}
|
||||
|
||||
// Identify secrets to create
|
||||
Object.keys(secrets).map(async (key) => {
|
||||
if (!(key in awsParameterStoreSecretsObj)) {
|
||||
// case: secret does not exist in AWS parameter store
|
||||
// -> create secret
|
||||
await ssm.putParameter({
|
||||
Name: `${integration.path}${key}`,
|
||||
Type: 'SecureString',
|
||||
Value: secrets[key],
|
||||
Overwrite: true
|
||||
}).promise();
|
||||
} else {
|
||||
// case: secret exists in AWS parameter store
|
||||
|
||||
if (awsParameterStoreSecretsObj[key].Value !== secrets[key]) {
|
||||
// case: secret value doesn't match one in AWS parameter store
|
||||
// -> update secret
|
||||
await ssm.putParameter({
|
||||
Name: `${integration.path}${key}`,
|
||||
Type: 'SecureString',
|
||||
Value: secrets[key],
|
||||
Overwrite: true
|
||||
}).promise();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Identify secrets to delete
|
||||
Object.keys(awsParameterStoreSecretsObj).map(async (key) => {
|
||||
if (!(key in secrets)) {
|
||||
// case:
|
||||
// -> delete secret
|
||||
await ssm.deleteParameter({
|
||||
Name: awsParameterStoreSecretsObj[key].Name
|
||||
}).promise();
|
||||
}
|
||||
});
|
||||
|
||||
AWS.config.update({
|
||||
region: undefined,
|
||||
accessKeyId: undefined,
|
||||
secretAccessKey: undefined
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed to sync secrets to AWS Parameter Store');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to AWS secret manager
|
||||
* @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.accessId - access id for AWS secret manager integration
|
||||
* @param {String} obj.accessToken - access token for AWS secret manager integration
|
||||
*/
|
||||
const syncSecretsAWSSecretManager = async ({
|
||||
integration,
|
||||
secrets,
|
||||
accessId,
|
||||
accessToken
|
||||
}: {
|
||||
integration: IIntegration;
|
||||
secrets: any;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
let secretsManager;
|
||||
try {
|
||||
if (!accessId) return;
|
||||
|
||||
AWS.config.update({
|
||||
region: integration.region,
|
||||
accessKeyId: accessId,
|
||||
secretAccessKey: accessToken
|
||||
});
|
||||
|
||||
secretsManager = new SecretsManagerClient({
|
||||
region: integration.region,
|
||||
credentials: {
|
||||
accessKeyId: accessId,
|
||||
secretAccessKey: accessToken
|
||||
}
|
||||
});
|
||||
|
||||
const awsSecretManagerSecret = await secretsManager.send(
|
||||
new GetSecretValueCommand({
|
||||
SecretId: integration.app
|
||||
})
|
||||
);
|
||||
|
||||
let awsSecretManagerSecretObj: { [key: string]: any } = {};
|
||||
|
||||
if (awsSecretManagerSecret?.SecretString) {
|
||||
awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString);
|
||||
}
|
||||
|
||||
if (!_.isEqual(awsSecretManagerSecretObj, secrets)) {
|
||||
await secretsManager.send(new UpdateSecretCommand({
|
||||
SecretId: integration.app,
|
||||
SecretString: JSON.stringify(secrets)
|
||||
}));
|
||||
}
|
||||
|
||||
AWS.config.update({
|
||||
region: undefined,
|
||||
accessKeyId: undefined,
|
||||
secretAccessKey: undefined
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof ResourceNotFoundException && secretsManager) {
|
||||
await secretsManager.send(new CreateSecretCommand({
|
||||
Name: integration.app,
|
||||
SecretString: JSON.stringify(secrets)
|
||||
}));
|
||||
} else {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed to sync secrets to AWS Secret Manager');
|
||||
}
|
||||
AWS.config.update({
|
||||
region: undefined,
|
||||
accessKeyId: undefined,
|
||||
secretAccessKey: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to Heroku app named [integration.app]
|
||||
* @param {Object} obj
|
||||
|
||||
@@ -45,9 +45,10 @@ const requireIntegrationAuthorizationAuth = ({
|
||||
|
||||
req.integrationAuth = integrationAuth;
|
||||
if (attachAccessToken) {
|
||||
req.accessToken = await IntegrationService.getIntegrationAuthAccess({
|
||||
const access = await IntegrationService.getIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString()
|
||||
});
|
||||
req.accessToken = access.accessToken;
|
||||
}
|
||||
|
||||
return next();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Schema, model, Types } from 'mongoose';
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -18,7 +20,18 @@ export interface IIntegration {
|
||||
owner: string;
|
||||
targetEnvironment: string;
|
||||
appId: string;
|
||||
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'render' | 'flyio' | 'azure-key-vault';
|
||||
path: string;
|
||||
region: string;
|
||||
integration:
|
||||
| 'azure-key-vault'
|
||||
| 'aws-parameter-store'
|
||||
| 'aws-secret-manager'
|
||||
| 'heroku'
|
||||
| 'vercel'
|
||||
| 'netlify'
|
||||
| 'github'
|
||||
| 'render'
|
||||
| 'flyio';
|
||||
integrationAuth: Types.ObjectId;
|
||||
}
|
||||
|
||||
@@ -57,10 +70,22 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
path: {
|
||||
// aws-parameter-store-specific path
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
region: {
|
||||
// aws-parameter-store-specific path
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
integration: {
|
||||
type: String,
|
||||
enum: [
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
|
||||
@@ -1,21 +1,37 @@
|
||||
import { Schema, model, Types } from 'mongoose';
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_FLYIO
|
||||
} from '../variables';
|
||||
|
||||
export interface IIntegrationAuth {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'render' | 'flyio' | 'azure-key-vault';
|
||||
teamId: string;
|
||||
accountId: string;
|
||||
integration:
|
||||
| 'azure-key-vault'
|
||||
| 'aws-parameter-store'
|
||||
| 'aws-secret-manager'
|
||||
| 'heroku'
|
||||
| 'vercel'
|
||||
| 'netlify'
|
||||
| 'github'
|
||||
| 'render'
|
||||
| 'flyio';
|
||||
teamId: string; // TODO: deprecate (vercel) -> move to accessId
|
||||
accountId: string; // TODO: deprecate (netlify) -> move to accessId
|
||||
refreshCiphertext?: string;
|
||||
refreshIV?: string;
|
||||
refreshTag?: string;
|
||||
accessIdCiphertext?: string; // new
|
||||
accessIdIV?: string; // new
|
||||
accessIdTag?: string; // new
|
||||
accessCiphertext?: string;
|
||||
accessIV?: string;
|
||||
accessTag?: string;
|
||||
@@ -33,10 +49,14 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
type: String,
|
||||
enum: [
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_FLYIO
|
||||
],
|
||||
required: true
|
||||
},
|
||||
@@ -60,6 +80,18 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessIdCiphertext: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessIdIV: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessIdTag: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessCiphertext: {
|
||||
type: String,
|
||||
select: false
|
||||
|
||||
@@ -20,12 +20,14 @@ router.post( // new: add new integration for integration auth
|
||||
location: 'body'
|
||||
}),
|
||||
body('integrationAuthId').exists().isString().trim(),
|
||||
body('app').isString().trim(),
|
||||
body('app').trim(),
|
||||
body('isActive').exists().isBoolean(),
|
||||
body('appId').trim(),
|
||||
body('sourceEnvironment').trim(),
|
||||
body('targetEnvironment').trim(),
|
||||
body('owner').trim(),
|
||||
body('path').trim(),
|
||||
body('region').trim(),
|
||||
validateRequest,
|
||||
integrationController.createIntegration
|
||||
);
|
||||
|
||||
@@ -57,6 +57,7 @@ router.post(
|
||||
location: 'body'
|
||||
}),
|
||||
body('workspaceId').exists().trim().notEmpty(),
|
||||
body('accessId').trim(),
|
||||
body('accessToken').exists().trim().notEmpty(),
|
||||
body('integration').exists().trim().notEmpty(),
|
||||
validateRequest,
|
||||
|
||||
@@ -112,26 +112,30 @@ class IntegrationService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt access token [accessToken] using the bot's copy
|
||||
* of the workspace key for workspace belonging to integration auth
|
||||
* Encrypt access token [accessToken] and (optionally) access id using the
|
||||
* bot's copy of the workspace key for workspace belonging to integration auth
|
||||
* with id [integrationAuthId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @param {String} obj.accessId - access id
|
||||
* @param {String} obj.accessToken - access token
|
||||
* @param {Date} obj.accessExpiresAt - expiration date of access token
|
||||
* @returns {IntegrationAuth} - updated integration auth
|
||||
*/
|
||||
static async setIntegrationAuthAccess({
|
||||
integrationAuthId,
|
||||
accessId,
|
||||
accessToken,
|
||||
accessExpiresAt
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
accessExpiresAt: Date | undefined;
|
||||
}) {
|
||||
return await setIntegrationAuthAccessHelper({
|
||||
integrationAuthId,
|
||||
accessId,
|
||||
accessToken,
|
||||
accessExpiresAt
|
||||
});
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
} from './environment';
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -61,6 +63,8 @@ export {
|
||||
ENV_PROD,
|
||||
ENV_SET,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
|
||||
// integrations
|
||||
const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault';
|
||||
const INTEGRATION_AWS_PARAMETER_STORE = 'aws-parameter-store';
|
||||
const INTEGRATION_AWS_SECRET_MANAGER = 'aws-secret-manager';
|
||||
const INTEGRATION_HEROKU = 'heroku';
|
||||
const INTEGRATION_VERCEL = 'vercel';
|
||||
const INTEGRATION_NETLIFY = 'netlify';
|
||||
@@ -47,16 +49,6 @@ const INTEGRATION_RENDER_API_URL = 'https://api.render.com';
|
||||
const INTEGRATION_FLYIO_API_URL = 'https://api.fly.io/graphql';
|
||||
|
||||
const INTEGRATION_OPTIONS = [
|
||||
{
|
||||
name: 'Azure Key Vault',
|
||||
slug: 'azure-key-vault',
|
||||
image: 'Microsoft Azure.png',
|
||||
isAvailable: false,
|
||||
type: 'oauth',
|
||||
clientId: CLIENT_ID_AZURE,
|
||||
tenantId: TENANT_ID_AZURE,
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
name: 'Heroku',
|
||||
slug: 'heroku',
|
||||
@@ -112,6 +104,34 @@ const INTEGRATION_OPTIONS = [
|
||||
clientId: '',
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
name: 'AWS Parameter Store',
|
||||
slug: 'aws-parameter-store',
|
||||
image: 'Amazon Web Services.png',
|
||||
isAvailable: true,
|
||||
type: 'custom',
|
||||
clientId: '',
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
name: 'AWS Secret Manager',
|
||||
slug: 'aws-secret-manager',
|
||||
image: 'Amazon Web Services.png',
|
||||
isAvailable: true,
|
||||
type: 'custom',
|
||||
clientId: '',
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
name: 'Azure Key Vault',
|
||||
slug: 'azure-key-vault',
|
||||
image: 'Microsoft Azure.png',
|
||||
isAvailable: false,
|
||||
type: 'oauth',
|
||||
clientId: CLIENT_ID_AZURE,
|
||||
tenantId: TENANT_ID_AZURE,
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
name: 'Google Cloud Platform',
|
||||
slug: 'gcp',
|
||||
@@ -121,24 +141,6 @@ const INTEGRATION_OPTIONS = [
|
||||
clientId: '',
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
name: 'Amazon Web Services',
|
||||
slug: 'aws',
|
||||
image: 'Amazon Web Services.png',
|
||||
isAvailable: false,
|
||||
type: '',
|
||||
clientId: '',
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
name: 'Microsoft Azure',
|
||||
slug: 'azure',
|
||||
image: 'Microsoft Azure.png',
|
||||
isAvailable: false,
|
||||
type: '',
|
||||
clientId: '',
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
name: 'Travis CI',
|
||||
slug: 'travisci',
|
||||
@@ -161,6 +163,8 @@ const INTEGRATION_OPTIONS = [
|
||||
|
||||
export {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
|
||||
BIN
docs/images/integrations-aws-access-key-1.png
Normal file
|
After Width: | Height: | Size: 374 KiB |
BIN
docs/images/integrations-aws-access-key-2.png
Normal file
|
After Width: | Height: | Size: 364 KiB |
BIN
docs/images/integrations-aws-access-key-3.png
Normal file
|
After Width: | Height: | Size: 290 KiB |
BIN
docs/images/integrations-aws-iam-1.png
Normal file
|
After Width: | Height: | Size: 323 KiB |
BIN
docs/images/integrations-aws-parameter-store-auth.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
docs/images/integrations-aws-parameter-store-create.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
docs/images/integrations-aws-parameter-store-iam-2.png
Normal file
|
After Width: | Height: | Size: 343 KiB |
BIN
docs/images/integrations-aws-parameter-store-iam-3.png
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
docs/images/integrations-aws-parameter-store.png
Normal file
|
After Width: | Height: | Size: 377 KiB |
BIN
docs/images/integrations-aws-secret-manager-auth.png
Normal file
|
After Width: | Height: | Size: 180 KiB |
BIN
docs/images/integrations-aws-secret-manager-create.png
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
docs/images/integrations-aws-secret-manager-iam-2.png
Normal file
|
After Width: | Height: | Size: 343 KiB |
BIN
docs/images/integrations-aws-secret-manager-iam-3.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
docs/images/integrations-aws-secret-manager.png
Normal file
|
After Width: | Height: | Size: 377 KiB |
|
Before Width: | Height: | Size: 350 KiB After Width: | Height: | Size: 162 KiB |
BIN
docs/images/integrations-flyio-create.png
Normal file
|
After Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 399 KiB After Width: | Height: | Size: 373 KiB |
BIN
docs/images/integrations-heroku-create.png
Normal file
|
After Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 402 KiB After Width: | Height: | Size: 371 KiB |
BIN
docs/images/integrations-netlify-create.png
Normal file
|
After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 404 KiB After Width: | Height: | Size: 380 KiB |
|
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 165 KiB |
BIN
docs/images/integrations-render-create.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 398 KiB After Width: | Height: | Size: 371 KiB |
BIN
docs/images/integrations-vercel-create.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 403 KiB After Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 418 KiB After Width: | Height: | Size: 421 KiB |
75
docs/integrations/cloud/aws-parameter-store.mdx
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
title: "AWS Parameter Store"
|
||||
description: "How to automatically sync secrets from Infisical to your AWS Parameter Store."
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- Set up AWS and have/create an IAM user
|
||||
|
||||
## Grant the IAM user permissions to access AWS Parameter Store
|
||||
|
||||
Navigate to your IAM user permissions and add a permission policy to grant access to AWS Parameter Store.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
For better security, here's a custom policy containing the minimum permissions required by Infisical to sync secrets to AWS Parameter Store for the IAM user that you can use:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "AllowSSMAccess",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ssm:PutParameter",
|
||||
"ssm:DeleteParameter",
|
||||
"ssm:GetParametersByPath",
|
||||
"ssm:DeleteParameters"
|
||||
],
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Navigate to your project's integrations tab
|
||||
|
||||

|
||||
|
||||
## Authorize Infisical for AWS Parameter store
|
||||
|
||||
Obtain a AWS access key ID and secret access key for your IAM user in IAM > Users > User > Security credentials > Access keys
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
Press on the AWS Parameter Store tile and input your AWS access key ID and secret access key from the previous step.
|
||||
|
||||

|
||||
|
||||
<Info>
|
||||
If this is your project's first cloud integration, then you'll have to grant
|
||||
Infisical access to your project's environment variables. Although this step
|
||||
breaks E2EE, it's necessary for Infisical to sync the environment variables to
|
||||
the cloud platform.
|
||||
</Info>
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to which AWS Parameter Store region and indicate the path for your secrets. Then, press create integration to start syncing secrets to AWS Parameter Store.
|
||||
|
||||

|
||||
|
||||
<Tip>
|
||||
Infisical requires you to add a path for your secrets to be stored in AWS
|
||||
Parameter Store and recommends setting the path structure to
|
||||
`/[project_name]/[environment]/` according to best practices. This enables a
|
||||
secret like `TEST` to be stored as `/[project_name]/[environment]/TEST` in AWS
|
||||
Parameter Store.
|
||||
</Tip>
|
||||
73
docs/integrations/cloud/aws-secret-manager.mdx
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "AWS Secret Manager"
|
||||
description: "How to automatically sync secrets from Infisical to your AWS Secret Manager."
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- Set up AWS and have/create an IAM user
|
||||
|
||||
## Grant the IAM user permissions to access AWS Secret Manager
|
||||
|
||||
Navigate to your IAM user permissions and add a permission policy to grant access to AWS Secret Manager.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
For better security, here's a custom policy containing the minimum permissions required by Infisical to sync secrets to AWS Secret Manager for the IAM user that you can use:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "AllowSecretsManagerAccess",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"secretsmanager:GetSecretValue",
|
||||
"secretsmanager:CreateSecret",
|
||||
"secretsmanager:UpdateSecret"
|
||||
],
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Navigate to your project's integrations tab
|
||||
|
||||

|
||||
|
||||
## Authorize Infisical for AWS Secret Manager
|
||||
|
||||
Obtain a AWS access key ID and secret access key for your IAM user in IAM > Users > User > Security credentials > Access keys
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
Press on the AWS Secret Manager tile and input your AWS access key ID and secret access key from the previous step.
|
||||
|
||||

|
||||
|
||||
<Info>
|
||||
If this is your project's first cloud integration, then you'll have to grant
|
||||
Infisical access to your project's environment variables. Although this step
|
||||
breaks E2EE, it's necessary for Infisical to sync the environment variables to
|
||||
the cloud platform.
|
||||
</Info>
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to which AWS Secret Manager region and under which secret name. Then, press create integration to start syncing secrets to AWS Secret Manager.
|
||||
|
||||

|
||||
|
||||
<Info>
|
||||
Infisical currently syncs environment variables to AWS Secret Manager as
|
||||
key-value pairs under one secret. We're actively exploring ways to help users
|
||||
group environment variable key-pairs under multiple secrets for greater
|
||||
control.
|
||||
</Info>
|
||||
@@ -31,6 +31,7 @@ Press on the Fly.io tile and input your Fly.io access token to grant Infisical a
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to which Fly.io app and press start integration to start syncing secrets to Fly.io.
|
||||
Select which Infisical environment secrets you want to sync to which Fly.io app and press create integration to start syncing secrets to Fly.io.
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -18,13 +18,15 @@ Press on the Heroku tile and grant Infisical access to your Heroku account.
|
||||

|
||||
|
||||
<Info>
|
||||
If this is your project's first cloud integration, then you'll have to grant Infisical access to your project's environment variables.
|
||||
Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform.
|
||||
If this is your project's first cloud integration, then you'll have to grant
|
||||
Infisical access to your project's environment variables. Although this step
|
||||
breaks E2EE, it's necessary for Infisical to sync the environment variables to
|
||||
the cloud platform.
|
||||
</Info>
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to which Heroku app and press start integration to start syncing secrets to Heroku.
|
||||
Select which Infisical environment secrets you want to sync to which Heroku app and press create integration to start syncing secrets to Heroku.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ description: "How to automatically sync secrets from Infisical into your Netlify
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Infisical integrates with Netlify's new environment variable experience. If your site uses Netlify's old environment variable experience, you'll have to upgrade it to the new one to use this integration.
|
||||
Infisical integrates with Netlify's new environment variable experience. If
|
||||
your site uses Netlify's old environment variable experience, you'll have to
|
||||
upgrade it to the new one to use this integration.
|
||||
</Warning>
|
||||
|
||||
Prerequisites:
|
||||
@@ -22,12 +24,15 @@ Press on the Netlify tile and grant Infisical access to your Netlify account.
|
||||

|
||||
|
||||
<Info>
|
||||
If this is your project's first cloud integration, then you'll have to grant Infisical access to your project's environment variables.
|
||||
Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform.
|
||||
If this is your project's first cloud integration, then you'll have to grant
|
||||
Infisical access to your project's environment variables. Although this step
|
||||
breaks E2EE, it's necessary for Infisical to sync the environment variables to
|
||||
the cloud platform.
|
||||
</Info>
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to which Netlify app and context. Lastly, press start integration to start syncing secrets to Netlify.
|
||||
Select which Infisical environment secrets you want to sync to which Netlify app and context. Lastly, press create integration to start syncing secrets to Netlify.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
@@ -31,6 +31,7 @@ Press on the Render tile and input your Render API Key to grant Infisical access
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to which Render service and press start integration to start syncing secrets to Render.
|
||||
Select which Infisical environment secrets you want to sync to which Render service and press create integration to start syncing secrets to Render.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
@@ -17,8 +17,16 @@ Press on the Vercel tile and grant Infisical access to your Vercel account.
|
||||
|
||||

|
||||
|
||||
<Info>
|
||||
If this is your project's first cloud integration, then you'll have to grant
|
||||
Infisical access to your project's environment variables. Although this step
|
||||
breaks E2EE, it's necessary for Infisical to sync the environment variables to
|
||||
the cloud platform.
|
||||
</Info>
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to which Vercel app and environment. Lastly, press start integration to start syncing secrets to Vercel.
|
||||
Select which Infisical environment secrets you want to sync to which Vercel app and environment. Lastly, press create integration to start syncing secrets to Vercel.
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -7,38 +7,40 @@ Integrations allow environment variables to be synced from Infisical into your l
|
||||
|
||||
Missing an integration? Throw in a [request](https://github.com/Infisical/infisical/issues).
|
||||
|
||||
| Integration | Type | Status |
|
||||
| -------------------------------------------------------- | --------- | ----------- |
|
||||
| [Docker](/integrations/platforms/docker) | Platform | Available |
|
||||
| [Docker-Compose](/integrations/platforms/docker-compose) | Platform | Available |
|
||||
| [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available |
|
||||
| [PM2](/integrations/platforms/pm2) | Platform | Available |
|
||||
| [Heroku](/integrations/cloud/heroku) | Cloud | Available |
|
||||
| [Vercel](/integrations/cloud/vercel) | Cloud | Available |
|
||||
| [Netlify](/integrations/cloud/netlify) | Cloud | Available |
|
||||
| [Render](/integrations/cloud/render) | Cloud | Available |
|
||||
| [Fly.io](/integrations/cloud/flyio) | Cloud | Available |
|
||||
| [GitHub Actions](/integrations/cicd/githubactions) | CI/CD | Available |
|
||||
| [React](/integrations/frameworks/react) | Framework | Available |
|
||||
| [Vue](/integrations/frameworks/vue) | Framework | Available |
|
||||
| [Express](/integrations/frameworks/express) | Framework | Available |
|
||||
| [Next.js](/integrations/frameworks/nextjs) | Framework | Available |
|
||||
| [NestJS](/integrations/frameworks/nestjs) | Framework | Available |
|
||||
| [Nuxt](/integrations/frameworks/nuxt) | Framework | Available |
|
||||
| [Gatsby](/integrations/frameworks/gatsby) | Framework | Available |
|
||||
| [Remix](/integrations/frameworks/remix) | Framework | Available |
|
||||
| [Vite](/integrations/frameworks/vite) | Framework | Available |
|
||||
| [Fiber](/integrations/frameworks/fiber) | Framework | Available |
|
||||
| [Django](/integrations/frameworks/django) | Framework | Available |
|
||||
| [Flask](/integrations/frameworks/flask) | Framework | Available |
|
||||
| [Laravel](/integrations/frameworks/laravel) | Framework | Available |
|
||||
| [Ruby on Rails](/integrations/frameworks/rails) | Framework | Available |
|
||||
| AWS | Cloud | Coming soon |
|
||||
| GCP | Cloud | Coming soon |
|
||||
| Azure | Cloud | Coming soon |
|
||||
| DigitalOcean | Cloud | Coming soon |
|
||||
| [GitLab Pipeline](/integrations/cicd/gitlab) | CI/CD | Available |
|
||||
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Coming soon |
|
||||
| TravisCI | CI/CD | Coming soon |
|
||||
| GitHub Actions | CI/CD | Coming soon |
|
||||
| Jenkins | CI/CD | Coming soon |
|
||||
| Integration | Type | Status |
|
||||
| -------------------------------------------------------------- | --------- | ----------- |
|
||||
| [Docker](/integrations/platforms/docker) | Platform | Available |
|
||||
| [Docker-Compose](/integrations/platforms/docker-compose) | Platform | Available |
|
||||
| [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available |
|
||||
| [PM2](/integrations/platforms/pm2) | Platform | Available |
|
||||
| [Heroku](/integrations/cloud/heroku) | Cloud | Available |
|
||||
| [Vercel](/integrations/cloud/vercel) | Cloud | Available |
|
||||
| [Netlify](/integrations/cloud/netlify) | Cloud | Available |
|
||||
| [Render](/integrations/cloud/render) | Cloud | Available |
|
||||
| [Fly.io](/integrations/cloud/flyio) | Cloud | Available |
|
||||
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
|
||||
| [AWS Secret Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
||||
| [GitHub Actions](/integrations/cicd/githubactions) | CI/CD | Available |
|
||||
| [React](/integrations/frameworks/react) | Framework | Available |
|
||||
| [Vue](/integrations/frameworks/vue) | Framework | Available |
|
||||
| [Express](/integrations/frameworks/express) | Framework | Available |
|
||||
| [Next.js](/integrations/frameworks/nextjs) | Framework | Available |
|
||||
| [NestJS](/integrations/frameworks/nestjs) | Framework | Available |
|
||||
| [Nuxt](/integrations/frameworks/nuxt) | Framework | Available |
|
||||
| [Gatsby](/integrations/frameworks/gatsby) | Framework | Available |
|
||||
| [Remix](/integrations/frameworks/remix) | Framework | Available |
|
||||
| [Vite](/integrations/frameworks/vite) | Framework | Available |
|
||||
| [Fiber](/integrations/frameworks/fiber) | Framework | Available |
|
||||
| [Django](/integrations/frameworks/django) | Framework | Available |
|
||||
| [Flask](/integrations/frameworks/flask) | Framework | Available |
|
||||
| [Laravel](/integrations/frameworks/laravel) | Framework | Available |
|
||||
| [Ruby on Rails](/integrations/frameworks/rails) | Framework | Available |
|
||||
| AWS | Cloud | Coming soon |
|
||||
| GCP | Cloud | Coming soon |
|
||||
| Azure | Cloud | Coming soon |
|
||||
| DigitalOcean | Cloud | Coming soon |
|
||||
| [GitLab Pipeline](/integrations/cicd/gitlab) | CI/CD | Available |
|
||||
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Coming soon |
|
||||
| TravisCI | CI/CD | Coming soon |
|
||||
| GitHub Actions | CI/CD | Coming soon |
|
||||
| Jenkins | CI/CD | Coming soon |
|
||||
|
||||
@@ -220,7 +220,9 @@
|
||||
"integrations/cloud/vercel",
|
||||
"integrations/cloud/netlify",
|
||||
"integrations/cloud/render",
|
||||
"integrations/cloud/flyio"
|
||||
"integrations/cloud/flyio",
|
||||
"integrations/cloud/aws-parameter-store",
|
||||
"integrations/cloud/aws-secret-manager"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,6 +4,8 @@ interface Mapping {
|
||||
|
||||
const integrationSlugNameMapping: Mapping = {
|
||||
'azure-key-vault': 'Azure Key Vault',
|
||||
'aws-parameter-store': 'AWS Parameter Store',
|
||||
'aws-secret-manager': 'AWS Secret Manager',
|
||||
'heroku': 'Heroku',
|
||||
'vercel': 'Vercel',
|
||||
'netlify': 'Netlify',
|
||||
|
||||
@@ -18,6 +18,8 @@ interface Integration {
|
||||
isActive: boolean;
|
||||
app: string | null;
|
||||
appId: string | null;
|
||||
path: string | null;
|
||||
region: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
environment: string;
|
||||
@@ -76,6 +78,8 @@ const IntegrationTile = ({
|
||||
|
||||
if (integration?.app) {
|
||||
setIntegrationApp(integration.app);
|
||||
} else if (integration?.path && integration?.region) {
|
||||
setIntegrationApp(`${integration.path} (${integration.region})`);
|
||||
} else if (tempApps.length > 0) {
|
||||
setIntegrationApp(tempApps[0].name)
|
||||
} else {
|
||||
|
||||
@@ -8,6 +8,8 @@ interface Props {
|
||||
sourceEnvironment: string;
|
||||
targetEnvironment: string | null;
|
||||
owner: string | null;
|
||||
path: string | null;
|
||||
region: string | null;
|
||||
}
|
||||
/**
|
||||
* This route creates a new integration based on the integration authorization with id [integrationAuthId]
|
||||
@@ -22,7 +24,9 @@ const createIntegration = ({
|
||||
appId,
|
||||
sourceEnvironment,
|
||||
targetEnvironment,
|
||||
owner
|
||||
owner,
|
||||
path,
|
||||
region
|
||||
}: Props) =>
|
||||
SecurityClient.fetchCall('/api/v1/integration', {
|
||||
method: 'POST',
|
||||
@@ -36,7 +40,9 @@ const createIntegration = ({
|
||||
appId,
|
||||
sourceEnvironment,
|
||||
targetEnvironment,
|
||||
owner
|
||||
owner,
|
||||
path,
|
||||
region
|
||||
})
|
||||
}).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
interface Props {
|
||||
workspaceId: string | null;
|
||||
integration: string | undefined;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
}
|
||||
/**
|
||||
@@ -19,6 +20,7 @@ interface Props {
|
||||
const saveIntegrationAccessToken = ({
|
||||
workspaceId,
|
||||
integration,
|
||||
accessId,
|
||||
accessToken
|
||||
}: Props) =>
|
||||
SecurityClient.fetchCall(`/api/v1/integration-auth/access-token`, {
|
||||
@@ -29,13 +31,14 @@ const saveIntegrationAccessToken = ({
|
||||
body: JSON.stringify({
|
||||
workspaceId,
|
||||
integration,
|
||||
accessId,
|
||||
accessToken
|
||||
})
|
||||
}).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
return (await res.json()).integrationAuth;
|
||||
}
|
||||
console.log('Failed to save integration access token');
|
||||
console.log('Failed to save integration access details');
|
||||
return undefined;
|
||||
});
|
||||
|
||||
|
||||
@@ -174,6 +174,12 @@ export default function Integrations() {
|
||||
case 'azure-key-vault':
|
||||
link = `https://login.microsoftonline.com/${integrationOption.tenantId}/oauth2/v2.0/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/azure-key-vault/oauth2/callback&response_mode=query&scope=https://vault.azure.net/.default openid offline_access&state=${state}`;
|
||||
break;
|
||||
case 'aws-parameter-store':
|
||||
link = `${window.location.origin}/integrations/aws-parameter-store/authorize`;
|
||||
break;
|
||||
case 'aws-secret-manager':
|
||||
link = `${window.location.origin}/integrations/aws-secret-manager/authorize`;
|
||||
break;
|
||||
case 'heroku':
|
||||
link = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`;
|
||||
break;
|
||||
@@ -211,6 +217,12 @@ export default function Integrations() {
|
||||
case 'azure-key-vault':
|
||||
link = `${window.location.origin}/integrations/azure-key-vault/create?integrationAuthId=${integrationAuth._id}`;
|
||||
break;
|
||||
case 'aws-parameter-store':
|
||||
link = `${window.location.origin}/integrations/aws-parameter-store/create?integrationAuthId=${integrationAuth._id}`;
|
||||
break;
|
||||
case 'aws-secret-manager':
|
||||
link = `${window.location.origin}/integrations/aws-secret-manager/create?integrationAuthId=${integrationAuth._id}`;
|
||||
break;
|
||||
case 'heroku':
|
||||
link = `${window.location.origin}/integrations/heroku/create?integrationAuthId=${integrationAuth._id}`;
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import { getTranslatedServerSideProps } from '../../../components/utilities/withTranslateProps';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
} from '../../../components/v2';
|
||||
import saveIntegrationAccessToken from "../../api/integrations/saveIntegrationAccessToken";
|
||||
|
||||
export default function AWSParameterStoreAuthorizeIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [accessKey, setAccessKey] = useState('');
|
||||
const [accessKeyErrorText, setAccessKeyErrorText] = useState('');
|
||||
const [accessSecretKey, setAccessSecretKey] = useState('');
|
||||
const [accessSecretKeyErrorText, setAccessSecretKeyErrorText] = useState('');
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
setAccessKeyErrorText('');
|
||||
setAccessSecretKeyErrorText('');
|
||||
|
||||
if (accessKey.length === 0) {
|
||||
setAccessKeyErrorText('Access key cannot be blank');
|
||||
return;
|
||||
}
|
||||
|
||||
if (accessSecretKey.length === 0) {
|
||||
setAccessSecretKeyErrorText('Secret access key cannot be blank');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const integrationAuth = await saveIntegrationAccessToken({
|
||||
workspaceId: localStorage.getItem('projectData.id'),
|
||||
integration: 'aws-parameter-store',
|
||||
accessId: accessKey,
|
||||
accessToken: accessSecretKey
|
||||
});
|
||||
|
||||
setAccessKey('');
|
||||
setAccessSecretKey('');
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(
|
||||
`/integrations/aws-parameter-store/create?integrationAuthId=${integrationAuth._id}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full w-full flex justify-center items-center">
|
||||
<Card className="max-w-md p-8 rounded-md">
|
||||
<CardTitle className='text-center mb-4'>AWS Parameter Store Integration</CardTitle>
|
||||
<FormControl
|
||||
label="Access Key ID"
|
||||
errorText={accessKeyErrorText}
|
||||
isError={accessKeyErrorText !== '' ?? false}
|
||||
>
|
||||
<Input
|
||||
placeholder=''
|
||||
value={accessKey}
|
||||
onChange={(e) => setAccessKey(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="Secret Access Key"
|
||||
errorText={accessSecretKeyErrorText}
|
||||
isError={accessSecretKeyErrorText !== '' ?? false}
|
||||
>
|
||||
<Input
|
||||
placeholder=''
|
||||
value={accessSecretKey}
|
||||
onChange={(e) => setAccessSecretKey(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to AWS Parameter Store
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
AWSParameterStoreAuthorizeIntegrationPage.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps(['integrations']);
|
||||
178
frontend/src/pages/integrations/aws-parameter-store/create.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import { getTranslatedServerSideProps } from '../../../components/utilities/withTranslateProps';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem
|
||||
} from '../../../components/v2';
|
||||
import { useGetIntegrationAuthById } from '../../../hooks/api/integrationAuth';
|
||||
import { useGetWorkspaceById } from '../../../hooks/api/workspace';
|
||||
import createIntegration from "../../api/integrations/createIntegration";
|
||||
|
||||
const awsRegions = [
|
||||
{ name: 'US East (Ohio)', slug: 'us-east-2' },
|
||||
{ name: 'US East (N. Virginia)', slug: 'us-east-1' },
|
||||
{ name: 'US West (N. California)', slug: 'us-west-1' },
|
||||
{ name: 'US West (Oregon)', slug: 'us-west-2' },
|
||||
{ name: 'Africa (Cape Town)', slug: 'af-south-1' },
|
||||
{ name: 'Asia Pacific (Hong Kong)', slug: 'ap-east-1' },
|
||||
{ name: 'Asia Pacific (Hyderabad)', slug: 'ap-south-2' },
|
||||
{ name: 'Asia Pacific (Jakarta)', slug: 'ap-southeast-3' },
|
||||
{ name: 'Asia Pacific (Melbourne)', slug: 'ap-southeast-4' },
|
||||
{ name: 'Asia Pacific (Mumbai)', slug: 'ap-south-1' },
|
||||
{ name: 'Asia Pacific (Osaka)', slug: 'ap-northeast-3' },
|
||||
{ name: 'Asia Pacific (Seoul)', slug: 'ap-northeast-2' },
|
||||
{ name: 'Asia Pacific (Singapore)', slug: 'ap-southeast-1' },
|
||||
{ name: 'Asia Pacific (Sydney)', slug: 'ap-southeast-2' },
|
||||
{ name: 'Asia Pacific (Tokyo)', slug: 'ap-northeast-1' },
|
||||
{ name: 'Canada (Central)', slug: 'ca-central-1' },
|
||||
{ name: 'Europe (Frankfurt)', slug: 'eu-central-1' },
|
||||
{ name: 'Europe (Ireland)', slug: 'eu-west-1' },
|
||||
{ name: 'Europe (London)', slug: 'eu-west-2' },
|
||||
{ name: 'Europe (Milan)', slug: 'eu-south-1' },
|
||||
{ name: 'Europe (Paris)', slug: 'eu-west-3' },
|
||||
{ name: 'Europe (Spain)', slug: 'eu-south-2' },
|
||||
{ name: 'Europe (Stockholm)', slug: 'eu-north-1' },
|
||||
{ name: 'Europe (Zurich)', slug: 'eu-central-2' },
|
||||
{ name: 'Middle East (Bahrain)', slug: 'me-south-1' },
|
||||
{ name: 'Middle East (UAE)', slug: 'me-central-1' },
|
||||
{ name: 'South America (Sao Paulo)', slug: 'sa-east-1' },
|
||||
{ name: 'AWS GovCloud (US-East)', slug: 'us-gov-east-1' },
|
||||
{ name: 'AWS GovCloud (US-West)', slug: 'us-gov-west-1' },
|
||||
]
|
||||
|
||||
export default function AWSParameterStoreCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split('?')[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||
const [selectedAWSRegion, setSelectedAWSRegion] = useState('');
|
||||
const [path, setPath] = useState('');
|
||||
const [pathErrorText, setPathErrorText] = useState('');
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
setSelectedAWSRegion(awsRegions[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
const isValidAWSParameterStorePath = (secretPath: string) => {
|
||||
const pattern = /^\/([\w-]+\/)*[\w-]+\/$/;
|
||||
return pattern.test(secretPath) && secretPath.length <= 2048;
|
||||
}
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
|
||||
if (path !== '') {
|
||||
// case: path is not empty
|
||||
if (!isValidAWSParameterStorePath(path)) {
|
||||
// case: path is not valid for aws parameter store
|
||||
setPathErrorText('Path must be a valid path for SSM like /project/env/');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!integrationAuth?._id) return;
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
await createIntegration({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: null,
|
||||
appId: null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: null,
|
||||
owner: null,
|
||||
path,
|
||||
region: selectedAWSRegion
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
setPathErrorText('');
|
||||
|
||||
router.push(
|
||||
`/integrations/${localStorage.getItem('projectData.id')}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return (integrationAuth && workspace && selectedSourceEnvironment) ? (
|
||||
<div className="h-full w-full flex justify-center items-center">
|
||||
<Card className="max-w-md p-8 rounded-md">
|
||||
<CardTitle className='text-center'>AWS Parameter Store Integration</CardTitle>
|
||||
<FormControl
|
||||
label="Project Environment"
|
||||
className='mt-4'
|
||||
>
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem value={sourceEnvironment.slug} key={`flyio-environment-${sourceEnvironment.slug}`}>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="AWS Region"
|
||||
>
|
||||
<Select
|
||||
value={selectedAWSRegion}
|
||||
onValueChange={(val) => setSelectedAWSRegion(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{awsRegions.map((awsRegion) => (
|
||||
<SelectItem value={awsRegion.slug} key={`flyio-environment-${awsRegion.slug}`}>
|
||||
{awsRegion.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="Path"
|
||||
errorText={pathErrorText}
|
||||
isError={pathErrorText !== '' ?? false}
|
||||
>
|
||||
<Input
|
||||
placeholder={`/${workspace.name.toLowerCase().replace(/ /g, "-")}/${selectedSourceEnvironment}/`}
|
||||
value={path}
|
||||
onChange={(e) => setPath(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
) : <div />
|
||||
}
|
||||
|
||||
AWSParameterStoreCreateIntegrationPage.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps(['integrations']);
|
||||
100
frontend/src/pages/integrations/aws-secret-manager/authorize.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import { getTranslatedServerSideProps } from '../../../components/utilities/withTranslateProps';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
} from '../../../components/v2';
|
||||
import saveIntegrationAccessToken from "../../api/integrations/saveIntegrationAccessToken";
|
||||
|
||||
export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [accessKey, setAccessKey] = useState('');
|
||||
const [accessKeyErrorText, setAccessKeyErrorText] = useState('');
|
||||
const [accessSecretKey, setAccessSecretKey] = useState('');
|
||||
const [accessSecretKeyErrorText, setAccessSecretKeyErrorText] = useState('');
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
setAccessKeyErrorText('');
|
||||
setAccessSecretKeyErrorText('');
|
||||
|
||||
if (accessKey.length === 0) {
|
||||
setAccessKeyErrorText('Access key cannot be blank');
|
||||
return;
|
||||
}
|
||||
|
||||
if (accessSecretKey.length === 0) {
|
||||
setAccessSecretKeyErrorText('Secret access key cannot be blank');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const integrationAuth = await saveIntegrationAccessToken({
|
||||
workspaceId: localStorage.getItem('projectData.id'),
|
||||
integration: 'aws-secret-manager',
|
||||
accessId: accessKey,
|
||||
accessToken: accessSecretKey
|
||||
});
|
||||
|
||||
setAccessKey('');
|
||||
setAccessSecretKey('');
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(
|
||||
`/integrations/aws-secret-manager/create?integrationAuthId=${integrationAuth._id}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full w-full flex justify-center items-center">
|
||||
<Card className="max-w-md p-8 rounded-md">
|
||||
<CardTitle className='text-center mb-4'>AWS Secret Manager Integration</CardTitle>
|
||||
<FormControl
|
||||
label="Access Key ID"
|
||||
errorText={accessKeyErrorText}
|
||||
isError={accessKeyErrorText !== '' ?? false}
|
||||
>
|
||||
<Input
|
||||
placeholder=''
|
||||
value={accessKey}
|
||||
onChange={(e) => setAccessKey(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="Secret Access Key"
|
||||
errorText={accessSecretKeyErrorText}
|
||||
isError={accessSecretKeyErrorText !== '' ?? false}
|
||||
>
|
||||
<Input
|
||||
placeholder=''
|
||||
value={accessSecretKey}
|
||||
onChange={(e) => setAccessSecretKey(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to AWS Secret Manager
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
AWSSecretManagerCreateIntegrationPage.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps(['integrations']);
|
||||
177
frontend/src/pages/integrations/aws-secret-manager/create.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import { getTranslatedServerSideProps } from '../../../components/utilities/withTranslateProps';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem
|
||||
} from '../../../components/v2';
|
||||
import { useGetIntegrationAuthById } from '../../../hooks/api/integrationAuth';
|
||||
import { useGetWorkspaceById } from '../../../hooks/api/workspace';
|
||||
import createIntegration from "../../api/integrations/createIntegration";
|
||||
|
||||
const awsRegions = [
|
||||
{ name: 'US East (Ohio)', slug: 'us-east-2' },
|
||||
{ name: 'US East (N. Virginia)', slug: 'us-east-1' },
|
||||
{ name: 'US West (N. California)', slug: 'us-west-1' },
|
||||
{ name: 'US West (Oregon)', slug: 'us-west-2' },
|
||||
{ name: 'Africa (Cape Town)', slug: 'af-south-1' },
|
||||
{ name: 'Asia Pacific (Hong Kong)', slug: 'ap-east-1' },
|
||||
{ name: 'Asia Pacific (Hyderabad)', slug: 'ap-south-2' },
|
||||
{ name: 'Asia Pacific (Jakarta)', slug: 'ap-southeast-3' },
|
||||
{ name: 'Asia Pacific (Melbourne)', slug: 'ap-southeast-4' },
|
||||
{ name: 'Asia Pacific (Mumbai)', slug: 'ap-south-1' },
|
||||
{ name: 'Asia Pacific (Osaka)', slug: 'ap-northeast-3' },
|
||||
{ name: 'Asia Pacific (Seoul)', slug: 'ap-northeast-2' },
|
||||
{ name: 'Asia Pacific (Singapore)', slug: 'ap-southeast-1' },
|
||||
{ name: 'Asia Pacific (Sydney)', slug: 'ap-southeast-2' },
|
||||
{ name: 'Asia Pacific (Tokyo)', slug: 'ap-northeast-1' },
|
||||
{ name: 'Canada (Central)', slug: 'ca-central-1' },
|
||||
{ name: 'Europe (Frankfurt)', slug: 'eu-central-1' },
|
||||
{ name: 'Europe (Ireland)', slug: 'eu-west-1' },
|
||||
{ name: 'Europe (London)', slug: 'eu-west-2' },
|
||||
{ name: 'Europe (Milan)', slug: 'eu-south-1' },
|
||||
{ name: 'Europe (Paris)', slug: 'eu-west-3' },
|
||||
{ name: 'Europe (Spain)', slug: 'eu-south-2' },
|
||||
{ name: 'Europe (Stockholm)', slug: 'eu-north-1' },
|
||||
{ name: 'Europe (Zurich)', slug: 'eu-central-2' },
|
||||
{ name: 'Middle East (Bahrain)', slug: 'me-south-1' },
|
||||
{ name: 'Middle East (UAE)', slug: 'me-central-1' },
|
||||
{ name: 'South America (Sao Paulo)', slug: 'sa-east-1' },
|
||||
{ name: 'AWS GovCloud (US-East)', slug: 'us-gov-east-1' },
|
||||
{ name: 'AWS GovCloud (US-West)', slug: 'us-gov-west-1' },
|
||||
]
|
||||
|
||||
export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split('?')[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||
const [selectedAWSRegion, setSelectedAWSRegion] = useState('');
|
||||
const [targetSecretName, setTargetSecretName] = useState('');
|
||||
const [targetSecretNameErrorText, setTargetSecretNameErrorText] = useState('');
|
||||
|
||||
// const [path, setPath] = useState('');
|
||||
// const [pathErrorText, setPathErrorText] = useState('');
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
setSelectedAWSRegion(awsRegions[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
// const isValidAWSPath = (path: string) => {
|
||||
// const pattern = /^\/[\w./]+\/$/;
|
||||
// return pattern.test(path) && path.length <= 2048;
|
||||
// }
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
|
||||
if (targetSecretName.trim() === '') {
|
||||
setTargetSecretName('Secret name cannot be blank');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!integrationAuth?._id) return;
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
await createIntegration({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetSecretName.trim(),
|
||||
appId: null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: null,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: selectedAWSRegion
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
setTargetSecretNameErrorText('');
|
||||
|
||||
router.push(
|
||||
`/integrations/${localStorage.getItem('projectData.id')}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return (integrationAuth && workspace && selectedSourceEnvironment) ? (
|
||||
<div className="h-full w-full flex justify-center items-center">
|
||||
<Card className="max-w-md p-8 rounded-md">
|
||||
<CardTitle className='text-center'>AWS Secret Manager Integration</CardTitle>
|
||||
<FormControl
|
||||
label="Project Environment"
|
||||
className='mt-4'
|
||||
>
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem value={sourceEnvironment.slug} key={`flyio-environment-${sourceEnvironment.slug}`}>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="AWS Region"
|
||||
>
|
||||
<Select
|
||||
value={selectedAWSRegion}
|
||||
onValueChange={(val) => setSelectedAWSRegion(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{awsRegions.map((awsRegion) => (
|
||||
<SelectItem value={awsRegion.slug} key={`flyio-environment-${awsRegion.slug}`}>
|
||||
{awsRegion.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="AWS SM Secret Name"
|
||||
errorText={targetSecretNameErrorText}
|
||||
isError={targetSecretNameErrorText !== '' ?? false}
|
||||
>
|
||||
<Input
|
||||
placeholder={`${workspace.name.toLowerCase().replace(/ /g, "-")}/${selectedSourceEnvironment}`}
|
||||
value={targetSecretName}
|
||||
onChange={(e) => setTargetSecretName(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
) : <div />
|
||||
}
|
||||
|
||||
AWSSecretManagerCreateIntegrationPage.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps(['integrations']);
|
||||
@@ -56,13 +56,15 @@ export default function AzureKeyVaultCreateIntegrationPage() {
|
||||
|
||||
setIsLoading(true);
|
||||
await createIntegration({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: vaultBaseUrl,
|
||||
appId: null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: null,
|
||||
owner: null
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: vaultBaseUrl,
|
||||
appId: null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: null,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: null
|
||||
});
|
||||
setIsLoading(false);
|
||||
|
||||
|
||||
@@ -49,13 +49,15 @@ export default function FlyioCreateIntegrationPage() {
|
||||
setIsLoading(true);
|
||||
|
||||
await createIntegration({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: null,
|
||||
owner: null
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: null,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: null
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -57,7 +57,9 @@ export default function GitHubCreateIntegrationPage() {
|
||||
appId: null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: null,
|
||||
owner
|
||||
owner,
|
||||
path: null,
|
||||
region: null
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -49,13 +49,15 @@ export default function HerokuCreateIntegrationPage() {
|
||||
if (!integrationAuth?._id) return;
|
||||
|
||||
await createIntegration({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: null,
|
||||
owner: null
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: null,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: null
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -58,13 +58,15 @@ export default function NetlifyCreateIntegrationPage() {
|
||||
if (!integrationAuth?._id) return;
|
||||
|
||||
await createIntegration({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: (integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.name === targetApp))?.appId ?? null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment,
|
||||
owner: null
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: (integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.name === targetApp))?.appId ?? null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: null
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -49,13 +49,15 @@ export default function RenderCreateIntegrationPage() {
|
||||
setIsLoading(true);
|
||||
|
||||
await createIntegration({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: (integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.name === targetApp))?.appId ?? null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: null,
|
||||
owner: null
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: (integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.name === targetApp))?.appId ?? null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment: null,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: null
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -56,13 +56,15 @@ export default function VercelCreateIntegrationPage() {
|
||||
|
||||
setIsLoading(true);
|
||||
await createIntegration({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment,
|
||||
owner: null
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: null,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: null
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||