Skip to content

Commit

Permalink
Merge pull request Infisical#305 from Infisical/azure
Browse files Browse the repository at this point in the history
Finish v1 Azure Key Vault integration
  • Loading branch information
dangtony98 authored Feb 6, 2023
2 parents 56da34d + 09c6032 commit 1383886
Show file tree
Hide file tree
Showing 20 changed files with 559 additions and 48 deletions.
6 changes: 6 additions & 0 deletions backend/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ const MONGO_URL = process.env.MONGO_URL!;
const NODE_ENV = process.env.NODE_ENV! || 'production';
const VERBOSE_ERROR_OUTPUT = process.env.VERBOSE_ERROR_OUTPUT! === 'true' && true;
const LOKI_HOST = process.env.LOKI_HOST || undefined;
const CLIENT_ID_AZURE = process.env.CLIENT_ID_AZURE!;
const TENANT_ID_AZURE = process.env.TENANT_ID_AZURE!;
const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!;
const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!;
const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!;
const CLIENT_ID_GITHUB = process.env.CLIENT_ID_GITHUB!;
const CLIENT_SECRET_AZURE = process.env.CLIENT_SECRET_AZURE!;
const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!;
const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!;
const CLIENT_SECRET_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!;
Expand Down Expand Up @@ -60,10 +63,13 @@ export {
NODE_ENV,
VERBOSE_ERROR_OUTPUT,
LOKI_HOST,
CLIENT_ID_AZURE,
TENANT_ID_AZURE,
CLIENT_ID_HEROKU,
CLIENT_ID_VERCEL,
CLIENT_ID_NETLIFY,
CLIENT_ID_GITHUB,
CLIENT_SECRET_AZURE,
CLIENT_SECRET_HEROKU,
CLIENT_SECRET_VERCEL,
CLIENT_SECRET_NETLIFY,
Expand Down
10 changes: 4 additions & 6 deletions backend/src/controllers/v1/integrationAuthController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const oAuthExchange = async (
) => {
try {
const { workspaceId, code, integration } = req.body;

if (!INTEGRATION_SET.has(integration))
throw new Error('Failed to validate integration');

Expand All @@ -40,23 +40,21 @@ export const oAuthExchange = async (
throw new Error("Failed to get environments")
}

await IntegrationService.handleOAuthExchange({
const integrationDetails = await IntegrationService.handleOAuthExchange({
workspaceId,
integration,
code,
environment: environments[0].slug,
});

return res.status(200).send(integrationDetails);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get OAuth2 code-token exchange'
});
}

return res.status(200).send({
message: 'Successfully enabled integration authorization'
});
};

/**
Expand Down
3 changes: 3 additions & 0 deletions backend/src/controllers/v1/integrationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { eventPushSecrets } from '../../events';
* @returns
*/
export const createIntegration = async (req: Request, res: Response) => {

// TODO: make this more versatile

let integration;
try {
// initialize new integration after saving integration access token
Expand Down
8 changes: 7 additions & 1 deletion backend/src/helpers/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const handleOAuthExchangeHelper = async ({
}) => {
let action;
let integrationAuth;
let newIntegration;
try {
const bot = await Bot.findOne({
workspace: workspaceId,
Expand Down Expand Up @@ -100,7 +101,7 @@ const handleOAuthExchangeHelper = async ({
}

// initialize new integration after exchange
await new Integration({
newIntegration = await new Integration({
workspace: workspaceId,
isActive: false,
app: null,
Expand All @@ -113,6 +114,11 @@ const handleOAuthExchangeHelper = async ({
Sentry.captureException(err);
throw new Error('Failed to handle OAuth2 code-token exchange')
}

return ({
integrationAuth,
integration: newIntegration
});
}
/**
* Sync/push environment variables in workspace with id [workspaceId] to
Expand Down
15 changes: 15 additions & 0 deletions backend/src/integrations/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as Sentry from '@sentry/node';
import { Octokit } from '@octokit/rest';
import { IIntegrationAuth } from '../models';
import {
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
Expand Down Expand Up @@ -40,6 +41,11 @@ const getApps = async ({
let apps: App[];
try {
switch (integrationAuth.integration) {
case INTEGRATION_AZURE_KEY_VAULT:
apps = await getAppsAzureKeyVault({
accessToken
});
break;
case INTEGRATION_HEROKU:
apps = await getAppsHeroku({
accessToken
Expand Down Expand Up @@ -81,6 +87,15 @@ const getApps = async ({
return apps;
};

const getAppsAzureKeyVault = async ({
accessToken
}: {
accessToken: string;
}) => {
// TODO
return [];
}

/**
* Return list of apps for Heroku integration
* @param {Object} obj
Expand Down
59 changes: 59 additions & 0 deletions backend/src/integrations/exchange.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
import axios from 'axios';
import * as Sentry from '@sentry/node';
import {
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_AZURE_TOKEN_URL,
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL
} from '../variables';
import {
SITE_URL,
CLIENT_ID_AZURE,
CLIENT_ID_VERCEL,
CLIENT_ID_NETLIFY,
CLIENT_ID_GITHUB,
CLIENT_SECRET_AZURE,
CLIENT_SECRET_HEROKU,
CLIENT_SECRET_VERCEL,
CLIENT_SECRET_NETLIFY,
CLIENT_SECRET_GITHUB
} from '../config';

interface ExchangeCodeAzureResponse {
token_type: string;
scope: string;
expires_in: number;
ext_expires_in: number;
access_token: string;
refresh_token: string;
id_token: string;
}

interface ExchangeCodeHerokuResponse {
token_type: string;
access_token: string;
Expand Down Expand Up @@ -75,6 +89,11 @@ const exchangeCode = async ({

try {
switch (integration) {
case INTEGRATION_AZURE_KEY_VAULT:
obj = await exchangeCodeAzure({
code
});
break;
case INTEGRATION_HEROKU:
obj = await exchangeCodeHeroku({
code
Expand Down Expand Up @@ -105,6 +124,46 @@ const exchangeCode = async ({
return obj;
};

/**
* Return [accessToken] for Azure OAuth2 code-token exchange
* @param param0
*/
const exchangeCodeAzure = async ({
code
}: {
code: string;
}) => {
const accessExpiresAt = new Date();
let res: ExchangeCodeAzureResponse;
try {
res = (await axios.post(
INTEGRATION_AZURE_TOKEN_URL,
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
scope: 'https://vault.azure.net/.default openid offline_access', // TODO: do we need all these permissions?
client_id: CLIENT_ID_AZURE,
client_secret: CLIENT_SECRET_AZURE,
redirect_uri: `${SITE_URL}/azure-key-vault`
} as any)
)).data;

accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err: any) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Azure');
}

return ({
accessToken: res.access_token,
refreshToken: res.refresh_token,
accessExpiresAt
});
}

/**
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Heroku
* OAuth2 code-token exchange
Expand Down
80 changes: 65 additions & 15 deletions backend/src/integrations/refresh.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import axios from 'axios';
import * as Sentry from '@sentry/node';
import { INTEGRATION_HEROKU } from '../variables';
import { INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU } from '../variables';
import {
CLIENT_SECRET_HEROKU
SITE_URL,
CLIENT_ID_AZURE,
CLIENT_SECRET_AZURE,
CLIENT_SECRET_HEROKU
} from '../config';
import {
INTEGRATION_HEROKU_TOKEN_URL
INTEGRATION_AZURE_TOKEN_URL,
INTEGRATION_HEROKU_TOKEN_URL
} from '../variables';

interface RefreshTokenAzureResponse {
token_type: string;
scope: string;
expires_in: number;
ext_expires_in: 4871;
access_token: string;
refresh_token: string;
}

/**
* Return new access token by exchanging refresh token [refreshToken] for integration
* named [integration]
Expand All @@ -25,6 +38,11 @@ const exchangeRefresh = async ({
let accessToken;
try {
switch (integration) {
case INTEGRATION_AZURE_KEY_VAULT:
accessToken = await exchangeRefreshAzure({
refreshToken
});
break;
case INTEGRATION_HEROKU:
accessToken = await exchangeRefreshHeroku({
refreshToken
Expand All @@ -40,6 +58,38 @@ const exchangeRefresh = async ({
return accessToken;
};

/**
* Return new access token by exchanging refresh token [refreshToken] for the
* Azure integration
* @param {Object} obj
* @param {String} obj.refreshToken - refresh token to use to get new access token for Azure
* @returns
*/
const exchangeRefreshAzure = async ({
refreshToken
}: {
refreshToken: string;
}) => {
try {
const res: RefreshTokenAzureResponse = (await axios.post(
INTEGRATION_AZURE_TOKEN_URL,
new URLSearchParams({
client_id: CLIENT_ID_AZURE,
scope: 'openid offline_access',
refresh_token: refreshToken,
grant_type: 'refresh_token',
client_secret: CLIENT_SECRET_AZURE
} as any)
)).data;

return res.access_token;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get refresh OAuth2 access token for Azure');
}
}

/**
* Return new access token by exchanging refresh token [refreshToken] for the
* Heroku integration
Expand All @@ -52,23 +102,23 @@ const exchangeRefreshHeroku = async ({
}: {
refreshToken: string;
}) => {
let accessToken;
//TODO: Refactor code to take advantage of using RequestError. It's possible to create new types of errors for more detailed errors
try {
const res = await axios.post(
INTEGRATION_HEROKU_TOKEN_URL,
new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_secret: CLIENT_SECRET_HEROKU
} as any)
);

let accessToken;
try {
const res = await axios.post(
INTEGRATION_HEROKU_TOKEN_URL,
new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_secret: CLIENT_SECRET_HEROKU
} as any)
);

accessToken = res.data.access_token;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get new OAuth2 access token for Heroku');
throw new Error('Failed to refresh OAuth2 access token for Heroku');
}

return accessToken;
Expand Down
Loading

0 comments on commit 1383886

Please sign in to comment.