mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 07:26:45 +00:00
Merge pull request #944 from Infisical/gcp-sm-integration-auth
Add support for service account JSON authentication method for GCP Secret Manager integration
This commit is contained in:
@@ -14,8 +14,10 @@ import {
|
|||||||
INTEGRATION_RAILWAY_API_URL,
|
INTEGRATION_RAILWAY_API_URL,
|
||||||
INTEGRATION_SET,
|
INTEGRATION_SET,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_VERCEL_API_URL,
|
||||||
|
INTEGRATION_GCP_SECRET_MANAGER,
|
||||||
getIntegrationOptions as getIntegrationOptionsFunc
|
getIntegrationOptions as getIntegrationOptionsFunc
|
||||||
} from "../../variables";
|
} from "../../variables";
|
||||||
|
import { exchangeRefresh } from "../../integrations";
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Return integration authorization with id [integrationAuthId]
|
* Return integration authorization with id [integrationAuthId]
|
||||||
@@ -88,7 +90,7 @@ export const oAuthExchange = async (req: Request, res: Response) => {
|
|||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const saveIntegrationAccessToken = async (req: Request, res: Response) => {
|
export const saveIntegrationToken = async (req: Request, res: Response) => {
|
||||||
// TODO: refactor
|
// TODO: refactor
|
||||||
// TODO: check if access token is valid for each integration
|
// TODO: check if access token is valid for each integration
|
||||||
|
|
||||||
@@ -96,14 +98,16 @@ export const saveIntegrationAccessToken = async (req: Request, res: Response) =>
|
|||||||
const {
|
const {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
accessId,
|
accessId,
|
||||||
|
refreshToken,
|
||||||
accessToken,
|
accessToken,
|
||||||
url,
|
url,
|
||||||
namespace,
|
namespace,
|
||||||
integration
|
integration
|
||||||
}: {
|
}: {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
accessId: string | null;
|
accessId: string | undefined;
|
||||||
accessToken: string;
|
refreshToken: string | undefined;
|
||||||
|
accessToken: string | undefined;
|
||||||
url: string;
|
url: string;
|
||||||
namespace: string;
|
namespace: string;
|
||||||
integration: string;
|
integration: string;
|
||||||
@@ -127,21 +131,36 @@ export const saveIntegrationAccessToken = async (req: Request, res: Response) =>
|
|||||||
url,
|
url,
|
||||||
namespace,
|
namespace,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
|
...(integration === INTEGRATION_GCP_SECRET_MANAGER ? {
|
||||||
|
metadata: {
|
||||||
|
authMethod: "serviceAccount"
|
||||||
|
}
|
||||||
|
} : {})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true,
|
new: true,
|
||||||
upsert: true
|
upsert: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// encrypt and save integration access details
|
||||||
|
if (refreshToken) {
|
||||||
|
await exchangeRefresh({
|
||||||
|
integrationAuth,
|
||||||
|
refreshToken
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// encrypt and save integration access details
|
// encrypt and save integration access details
|
||||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
if (accessId || accessToken) {
|
||||||
integrationAuthId: integrationAuth._id.toString(),
|
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
||||||
accessId,
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
accessToken,
|
accessId,
|
||||||
accessExpiresAt: undefined
|
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");
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Bot, IntegrationAuth } from "../models";
|
import { Bot, IIntegrationAuth, IntegrationAuth } from "../models";
|
||||||
import { exchangeCode, exchangeRefresh } from "../integrations";
|
import { exchangeCode, exchangeRefresh } from "../integrations";
|
||||||
import { BotService } from "../services";
|
import { BotService } from "../services";
|
||||||
import {
|
import {
|
||||||
ALGORITHM_AES_256_GCM,
|
ALGORITHM_AES_256_GCM,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_VERCEL
|
INTEGRATION_VERCEL,
|
||||||
|
INTEGRATION_GCP_SECRET_MANAGER,
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import { UnauthorizedRequestError } from "../utils/errors";
|
import { BadRequestError, InternalServerError, UnauthorizedRequestError } from "../utils/errors";
|
||||||
|
import { IntegrationAuthMetadata } from "../models/integrationAuth/types";
|
||||||
|
|
||||||
interface Update {
|
interface Update {
|
||||||
workspace: string;
|
workspace: string;
|
||||||
integration: string;
|
integration: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
|
metadata?: IntegrationAuthMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,6 +67,10 @@ export const handleOAuthExchangeHelper = async ({
|
|||||||
break;
|
break;
|
||||||
case INTEGRATION_NETLIFY:
|
case INTEGRATION_NETLIFY:
|
||||||
update.accountId = res.accountId;
|
update.accountId = res.accountId;
|
||||||
|
case INTEGRATION_GCP_SECRET_MANAGER:
|
||||||
|
update.metadata = {
|
||||||
|
authMethod: "oauth2"
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +100,6 @@ export const handleOAuthExchangeHelper = async ({
|
|||||||
// set integration auth access token
|
// set integration auth access token
|
||||||
await setIntegrationAuthAccessHelper({
|
await setIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: integrationAuth._id.toString(),
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
accessId: null,
|
|
||||||
accessToken: res.accessToken,
|
accessToken: res.accessToken,
|
||||||
accessExpiresAt: res.accessExpiresAt
|
accessExpiresAt: res.accessExpiresAt
|
||||||
});
|
});
|
||||||
@@ -158,22 +164,24 @@ export const getIntegrationAuthAccessHelper = async ({
|
|||||||
message: "Failed to locate Integration Authentication credentials"
|
message: "Failed to locate Integration Authentication credentials"
|
||||||
});
|
});
|
||||||
|
|
||||||
accessToken = await BotService.decryptSymmetric({
|
if (integrationAuth.accessCiphertext && integrationAuth.accessIV && integrationAuth.accessTag) {
|
||||||
workspaceId: integrationAuth.workspace,
|
accessToken = await BotService.decryptSymmetric({
|
||||||
ciphertext: integrationAuth.accessCiphertext as string,
|
workspaceId: integrationAuth.workspace,
|
||||||
iv: integrationAuth.accessIV as string,
|
ciphertext: integrationAuth.accessCiphertext as string,
|
||||||
tag: integrationAuth.accessTag as string
|
iv: integrationAuth.accessIV as string,
|
||||||
});
|
tag: integrationAuth.accessTag as string
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (integrationAuth?.accessExpiresAt && integrationAuth?.refreshCiphertext) {
|
if (integrationAuth?.refreshCiphertext) {
|
||||||
// there is a access token expiration date
|
// there is a access token expiration date
|
||||||
// and refresh token to exchange with the OAuth2 server
|
// and refresh token to exchange with the OAuth2 server
|
||||||
|
const refreshToken = await getIntegrationAuthRefreshHelper({
|
||||||
|
integrationAuthId
|
||||||
|
});
|
||||||
|
|
||||||
if (integrationAuth.accessExpiresAt < new Date()) {
|
if (integrationAuth?.accessExpiresAt && integrationAuth.accessExpiresAt < new Date()) {
|
||||||
// access token is expired
|
// access token is expired
|
||||||
const refreshToken = await getIntegrationAuthRefreshHelper({
|
|
||||||
integrationAuthId
|
|
||||||
});
|
|
||||||
accessToken = await exchangeRefresh({
|
accessToken = await exchangeRefresh({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
refreshToken
|
refreshToken
|
||||||
@@ -194,6 +202,8 @@ export const getIntegrationAuthAccessHelper = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!accessToken) throw InternalServerError();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accessId,
|
accessId,
|
||||||
accessToken
|
accessToken
|
||||||
@@ -214,7 +224,7 @@ export const setIntegrationAuthRefreshHelper = async ({
|
|||||||
}: {
|
}: {
|
||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) => {
|
}): Promise<IIntegrationAuth> => {
|
||||||
let integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
let integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
||||||
|
|
||||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||||
@@ -239,6 +249,8 @@ export const setIntegrationAuthRefreshHelper = async ({
|
|||||||
new: true
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!integrationAuth) throw InternalServerError();
|
||||||
|
|
||||||
return integrationAuth;
|
return integrationAuth;
|
||||||
};
|
};
|
||||||
@@ -259,20 +271,24 @@ export const setIntegrationAuthAccessHelper = async ({
|
|||||||
accessExpiresAt
|
accessExpiresAt
|
||||||
}: {
|
}: {
|
||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
accessId: string | null;
|
accessId?: string;
|
||||||
accessToken: string;
|
accessToken?: string;
|
||||||
accessExpiresAt: Date | undefined;
|
accessExpiresAt: Date | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
let integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
let integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
||||||
|
|
||||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||||
|
|
||||||
const encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
let encryptedAccessTokenObj;
|
||||||
workspaceId: integrationAuth.workspace,
|
|
||||||
plaintext: accessToken
|
|
||||||
});
|
|
||||||
|
|
||||||
let encryptedAccessIdObj;
|
let encryptedAccessIdObj;
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
||||||
|
workspaceId: integrationAuth.workspace,
|
||||||
|
plaintext: accessToken
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (accessId) {
|
if (accessId) {
|
||||||
encryptedAccessIdObj = await BotService.encryptSymmetric({
|
encryptedAccessIdObj = await BotService.encryptSymmetric({
|
||||||
workspaceId: integrationAuth.workspace,
|
workspaceId: integrationAuth.workspace,
|
||||||
@@ -286,11 +302,11 @@ export const setIntegrationAuthAccessHelper = async ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined,
|
accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined,
|
||||||
accessIdIV: encryptedAccessIdObj?.iv ?? undefined,
|
accessIdIV: encryptedAccessIdObj?.iv,
|
||||||
accessIdTag: encryptedAccessIdObj?.tag ?? undefined,
|
accessIdTag: encryptedAccessIdObj?.tag,
|
||||||
accessCiphertext: encryptedAccessTokenObj.ciphertext,
|
accessCiphertext: encryptedAccessTokenObj?.ciphertext,
|
||||||
accessIV: encryptedAccessTokenObj.iv,
|
accessIV: encryptedAccessTokenObj?.iv,
|
||||||
accessTag: encryptedAccessTokenObj.tag,
|
accessTag: encryptedAccessTokenObj?.tag,
|
||||||
accessExpiresAt,
|
accessExpiresAt,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ interface ExchangeCodeAzureResponse {
|
|||||||
id_token: string;
|
id_token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ExchangeCodeGCPResponse {
|
||||||
|
access_token: string;
|
||||||
|
expires_in: number;
|
||||||
|
refresh_token: string;
|
||||||
|
scope: string;
|
||||||
|
token_type: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface ExchangeCodeHerokuResponse {
|
interface ExchangeCodeHerokuResponse {
|
||||||
token_type: string;
|
token_type: string;
|
||||||
access_token: string;
|
access_token: string;
|
||||||
@@ -174,7 +182,7 @@ const exchangeCode = async ({
|
|||||||
const exchangeCodeGCP = async ({ code }: { code: string }) => {
|
const exchangeCodeGCP = async ({ code }: { code: string }) => {
|
||||||
const accessExpiresAt = new Date();
|
const accessExpiresAt = new Date();
|
||||||
|
|
||||||
const res: ExchangeCodeAzureResponse = (
|
const res: ExchangeCodeGCPResponse = (
|
||||||
await standardRequest.post(
|
await standardRequest.post(
|
||||||
INTEGRATION_GCP_TOKEN_URL,
|
INTEGRATION_GCP_TOKEN_URL,
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import jwt from "jsonwebtoken";
|
||||||
import { standardRequest } from "../config/request";
|
import { standardRequest } from "../config/request";
|
||||||
import { IIntegrationAuth } from "../models";
|
import { IIntegrationAuth } from "../models";
|
||||||
import {
|
import {
|
||||||
@@ -6,6 +7,9 @@ import {
|
|||||||
INTEGRATION_BITBUCKET_TOKEN_URL,
|
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
|
INTEGRATION_GCP_SECRET_MANAGER,
|
||||||
|
INTEGRATION_GCP_TOKEN_URL,
|
||||||
|
INTEGRATION_GCP_CLOUD_PLATFORM_SCOPE
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import {
|
import {
|
||||||
INTEGRATION_AZURE_TOKEN_URL,
|
INTEGRATION_AZURE_TOKEN_URL,
|
||||||
@@ -21,6 +25,8 @@ import {
|
|||||||
getClientSecretBitBucket,
|
getClientSecretBitBucket,
|
||||||
getClientSecretGitLab,
|
getClientSecretGitLab,
|
||||||
getClientSecretHeroku,
|
getClientSecretHeroku,
|
||||||
|
getClientIdGCPSecretManager,
|
||||||
|
getClientSecretGCPSecretManager,
|
||||||
getSiteURL,
|
getSiteURL,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
|
|
||||||
@@ -59,6 +65,19 @@ interface RefreshTokenBitBucketResponse {
|
|||||||
state: string;
|
state: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ServiceAccountAccessTokenGCPSecretManagerResponse {
|
||||||
|
access_token: string;
|
||||||
|
expires_in: number;
|
||||||
|
token_type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RefreshTokenGCPSecretManagerResponse {
|
||||||
|
access_token: string;
|
||||||
|
expires_in: number;
|
||||||
|
scope: string;
|
||||||
|
token_type: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return new access token by exchanging refresh token [refreshToken] for integration
|
* Return new access token by exchanging refresh token [refreshToken] for integration
|
||||||
* named [integration]
|
* named [integration]
|
||||||
@@ -101,18 +120,23 @@ const exchangeRefresh = async ({
|
|||||||
refreshToken,
|
refreshToken,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case INTEGRATION_GCP_SECRET_MANAGER:
|
||||||
|
tokenDetails = await exchangeRefreshGCPSecretManager({
|
||||||
|
integrationAuth,
|
||||||
|
refreshToken,
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Failed to exchange token for incompatible integration");
|
throw new Error("Failed to exchange token for incompatible integration");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
tokenDetails?.accessToken &&
|
tokenDetails.accessToken &&
|
||||||
tokenDetails?.refreshToken &&
|
tokenDetails.refreshToken &&
|
||||||
tokenDetails?.accessExpiresAt
|
tokenDetails.accessExpiresAt
|
||||||
) {
|
) {
|
||||||
await IntegrationService.setIntegrationAuthAccess({
|
await IntegrationService.setIntegrationAuthAccess({
|
||||||
integrationAuthId: integrationAuth._id.toString(),
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
accessId: null,
|
|
||||||
accessToken: tokenDetails.accessToken,
|
accessToken: tokenDetails.accessToken,
|
||||||
accessExpiresAt: tokenDetails.accessExpiresAt,
|
accessExpiresAt: tokenDetails.accessExpiresAt,
|
||||||
});
|
});
|
||||||
@@ -278,4 +302,76 @@ const exchangeRefreshBitBucket = async ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export { exchangeRefresh };
|
/**
|
||||||
|
* Return new access token by exchanging refresh token [refreshToken] for the
|
||||||
|
* GCP Secret Manager integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.refreshToken - refresh token to use to get new access token for GCP Secret Manager
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const exchangeRefreshGCPSecretManager = async ({
|
||||||
|
integrationAuth,
|
||||||
|
refreshToken,
|
||||||
|
}: {
|
||||||
|
integrationAuth: IIntegrationAuth;
|
||||||
|
refreshToken: string;
|
||||||
|
}) => {
|
||||||
|
const accessExpiresAt = new Date();
|
||||||
|
|
||||||
|
if (integrationAuth.metadata?.authMethod === "serviceAccount") {
|
||||||
|
const serviceAccount = JSON.parse(refreshToken);
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
iss: serviceAccount.client_email,
|
||||||
|
aud: serviceAccount.token_uri,
|
||||||
|
scope: INTEGRATION_GCP_CLOUD_PLATFORM_SCOPE,
|
||||||
|
iat: Math.floor(Date.now() / 1000),
|
||||||
|
exp: Math.floor(Date.now() / 1000) + 3600,
|
||||||
|
};
|
||||||
|
|
||||||
|
const token = jwt.sign(payload, serviceAccount.private_key, { algorithm: 'RS256' });
|
||||||
|
|
||||||
|
const { data }: { data: ServiceAccountAccessTokenGCPSecretManagerResponse } = await standardRequest.post(
|
||||||
|
INTEGRATION_GCP_TOKEN_URL,
|
||||||
|
new URLSearchParams({
|
||||||
|
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||||
|
assertion: token
|
||||||
|
}).toString(),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken,
|
||||||
|
accessExpiresAt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data }: { data: RefreshTokenGCPSecretManagerResponse } = (
|
||||||
|
await standardRequest.post(
|
||||||
|
INTEGRATION_GCP_TOKEN_URL,
|
||||||
|
new URLSearchParams({
|
||||||
|
client_id: await getClientIdGCPSecretManager(),
|
||||||
|
client_secret: await getClientSecretGCPSecretManager(),
|
||||||
|
refresh_token: refreshToken,
|
||||||
|
grant_type: "refresh_token",
|
||||||
|
} as any)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken,
|
||||||
|
accessExpiresAt,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { exchangeRefresh };
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import jwt from "jsonwebtoken";
|
||||||
import {
|
import {
|
||||||
CreateSecretCommand,
|
CreateSecretCommand,
|
||||||
GetSecretValueCommand,
|
GetSecretValueCommand,
|
||||||
@@ -28,6 +29,8 @@ import {
|
|||||||
INTEGRATION_FLYIO_API_URL,
|
INTEGRATION_FLYIO_API_URL,
|
||||||
INTEGRATION_GCP_SECRET_MANAGER,
|
INTEGRATION_GCP_SECRET_MANAGER,
|
||||||
INTEGRATION_GCP_SECRET_MANAGER_URL,
|
INTEGRATION_GCP_SECRET_MANAGER_URL,
|
||||||
|
INTEGRATION_GCP_TOKEN_URL,
|
||||||
|
INTEGRATION_GCP_CLOUD_PLATFORM_SCOPE,
|
||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
INTEGRATION_GITLAB_API_URL,
|
INTEGRATION_GITLAB_API_URL,
|
||||||
|
|||||||
@@ -1,199 +0,0 @@
|
|||||||
import {
|
|
||||||
ALGORITHM_AES_256_GCM,
|
|
||||||
ENCODING_SCHEME_BASE64,
|
|
||||||
ENCODING_SCHEME_UTF8,
|
|
||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
|
||||||
INTEGRATION_AWS_SECRET_MANAGER,
|
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
|
||||||
INTEGRATION_BITBUCKET,
|
|
||||||
INTEGRATION_CIRCLECI,
|
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
|
||||||
INTEGRATION_CLOUD_66,
|
|
||||||
INTEGRATION_CODEFRESH,
|
|
||||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
|
||||||
INTEGRATION_FLYIO,
|
|
||||||
INTEGRATION_GCP_SECRET_MANAGER,
|
|
||||||
INTEGRATION_GITHUB,
|
|
||||||
INTEGRATION_GITLAB,
|
|
||||||
INTEGRATION_HASHICORP_VAULT,
|
|
||||||
INTEGRATION_HEROKU,
|
|
||||||
INTEGRATION_LARAVELFORGE,
|
|
||||||
INTEGRATION_NETLIFY,
|
|
||||||
INTEGRATION_NORTHFLANK,
|
|
||||||
INTEGRATION_RAILWAY,
|
|
||||||
INTEGRATION_RENDER,
|
|
||||||
INTEGRATION_SUPABASE,
|
|
||||||
INTEGRATION_TEAMCITY,
|
|
||||||
INTEGRATION_TERRAFORM_CLOUD,
|
|
||||||
INTEGRATION_TRAVISCI,
|
|
||||||
INTEGRATION_VERCEL,
|
|
||||||
INTEGRATION_WINDMILL
|
|
||||||
} from "../variables";
|
|
||||||
import { Document, Schema, Types, model } from "mongoose";
|
|
||||||
|
|
||||||
export interface IIntegrationAuth extends Document {
|
|
||||||
_id: Types.ObjectId;
|
|
||||||
workspace: Types.ObjectId;
|
|
||||||
integration:
|
|
||||||
| "heroku"
|
|
||||||
| "vercel"
|
|
||||||
| "netlify"
|
|
||||||
| "github"
|
|
||||||
| "gitlab"
|
|
||||||
| "render"
|
|
||||||
| "railway"
|
|
||||||
| "flyio"
|
|
||||||
| "azure-key-vault"
|
|
||||||
| "laravel-forge"
|
|
||||||
| "circleci"
|
|
||||||
| "travisci"
|
|
||||||
| "supabase"
|
|
||||||
| "aws-parameter-store"
|
|
||||||
| "aws-secret-manager"
|
|
||||||
| "checkly"
|
|
||||||
| "cloudflare-pages"
|
|
||||||
| "codefresh"
|
|
||||||
| "digital-ocean-app-platform"
|
|
||||||
| "bitbucket"
|
|
||||||
| "cloud-66"
|
|
||||||
| "terraform-cloud"
|
|
||||||
| "teamcity"
|
|
||||||
| "northflank"
|
|
||||||
| "windmill"
|
|
||||||
| "gcp-secret-manager";
|
|
||||||
teamId: string;
|
|
||||||
accountId: string;
|
|
||||||
url: string;
|
|
||||||
namespace: string;
|
|
||||||
refreshCiphertext?: string;
|
|
||||||
refreshIV?: string;
|
|
||||||
refreshTag?: string;
|
|
||||||
accessIdCiphertext?: string;
|
|
||||||
accessIdIV?: string;
|
|
||||||
accessIdTag?: string;
|
|
||||||
accessCiphertext?: string;
|
|
||||||
accessIV?: string;
|
|
||||||
accessTag?: string;
|
|
||||||
algorithm?: "aes-256-gcm";
|
|
||||||
keyEncoding?: "utf8" | "base64";
|
|
||||||
accessExpiresAt?: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
|
||||||
{
|
|
||||||
workspace: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: "Workspace",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
integration: {
|
|
||||||
type: String,
|
|
||||||
enum: [
|
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
|
||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
|
||||||
INTEGRATION_AWS_SECRET_MANAGER,
|
|
||||||
INTEGRATION_HEROKU,
|
|
||||||
INTEGRATION_VERCEL,
|
|
||||||
INTEGRATION_NETLIFY,
|
|
||||||
INTEGRATION_GITHUB,
|
|
||||||
INTEGRATION_GITLAB,
|
|
||||||
INTEGRATION_RENDER,
|
|
||||||
INTEGRATION_RAILWAY,
|
|
||||||
INTEGRATION_FLYIO,
|
|
||||||
INTEGRATION_CIRCLECI,
|
|
||||||
INTEGRATION_LARAVELFORGE,
|
|
||||||
INTEGRATION_TRAVISCI,
|
|
||||||
INTEGRATION_TEAMCITY,
|
|
||||||
INTEGRATION_SUPABASE,
|
|
||||||
INTEGRATION_TERRAFORM_CLOUD,
|
|
||||||
INTEGRATION_HASHICORP_VAULT,
|
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
|
||||||
INTEGRATION_CODEFRESH,
|
|
||||||
INTEGRATION_WINDMILL,
|
|
||||||
INTEGRATION_BITBUCKET,
|
|
||||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
|
||||||
INTEGRATION_CLOUD_66,
|
|
||||||
INTEGRATION_NORTHFLANK,
|
|
||||||
INTEGRATION_GCP_SECRET_MANAGER
|
|
||||||
],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
teamId: {
|
|
||||||
// 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,
|
|
||||||
},
|
|
||||||
refreshCiphertext: {
|
|
||||||
type: String,
|
|
||||||
select: false,
|
|
||||||
},
|
|
||||||
refreshIV: {
|
|
||||||
type: String,
|
|
||||||
select: false,
|
|
||||||
},
|
|
||||||
refreshTag: {
|
|
||||||
type: String,
|
|
||||||
select: false,
|
|
||||||
},
|
|
||||||
accessIdCiphertext: {
|
|
||||||
type: String,
|
|
||||||
select: false,
|
|
||||||
},
|
|
||||||
accessIdIV: {
|
|
||||||
type: String,
|
|
||||||
select: false,
|
|
||||||
},
|
|
||||||
accessIdTag: {
|
|
||||||
type: String,
|
|
||||||
select: false,
|
|
||||||
},
|
|
||||||
accessCiphertext: {
|
|
||||||
type: String,
|
|
||||||
select: false,
|
|
||||||
},
|
|
||||||
accessIV: {
|
|
||||||
type: String,
|
|
||||||
select: false,
|
|
||||||
},
|
|
||||||
accessTag: {
|
|
||||||
type: String,
|
|
||||||
select: false,
|
|
||||||
},
|
|
||||||
accessExpiresAt: {
|
|
||||||
type: Date,
|
|
||||||
select: false,
|
|
||||||
},
|
|
||||||
algorithm: { // the encryption algorithm used
|
|
||||||
type: String,
|
|
||||||
enum: [ALGORITHM_AES_256_GCM],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
keyEncoding: {
|
|
||||||
type: String,
|
|
||||||
enum: [
|
|
||||||
ENCODING_SCHEME_UTF8,
|
|
||||||
ENCODING_SCHEME_BASE64,
|
|
||||||
],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timestamps: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const IntegrationAuth = model<IIntegrationAuth>(
|
|
||||||
"IntegrationAuth",
|
|
||||||
integrationAuthSchema
|
|
||||||
);
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./integrationAuth";
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
import {
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_BASE64,
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
INTEGRATION_AWS_PARAMETER_STORE,
|
||||||
|
INTEGRATION_AWS_SECRET_MANAGER,
|
||||||
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
|
INTEGRATION_CIRCLECI,
|
||||||
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
|
INTEGRATION_CLOUD_66,
|
||||||
|
INTEGRATION_CODEFRESH,
|
||||||
|
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||||
|
INTEGRATION_FLYIO,
|
||||||
|
INTEGRATION_GCP_SECRET_MANAGER,
|
||||||
|
INTEGRATION_GITHUB,
|
||||||
|
INTEGRATION_GITLAB,
|
||||||
|
INTEGRATION_HASHICORP_VAULT,
|
||||||
|
INTEGRATION_HEROKU,
|
||||||
|
INTEGRATION_LARAVELFORGE,
|
||||||
|
INTEGRATION_NETLIFY,
|
||||||
|
INTEGRATION_NORTHFLANK,
|
||||||
|
INTEGRATION_RAILWAY,
|
||||||
|
INTEGRATION_RENDER,
|
||||||
|
INTEGRATION_SUPABASE,
|
||||||
|
INTEGRATION_TEAMCITY,
|
||||||
|
INTEGRATION_TERRAFORM_CLOUD,
|
||||||
|
INTEGRATION_TRAVISCI,
|
||||||
|
INTEGRATION_VERCEL,
|
||||||
|
INTEGRATION_WINDMILL
|
||||||
|
} from "../../variables";
|
||||||
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
import { IntegrationAuthMetadata } from "./types";
|
||||||
|
|
||||||
|
export interface IIntegrationAuth extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
workspace: Types.ObjectId;
|
||||||
|
integration:
|
||||||
|
| "heroku"
|
||||||
|
| "vercel"
|
||||||
|
| "netlify"
|
||||||
|
| "github"
|
||||||
|
| "gitlab"
|
||||||
|
| "render"
|
||||||
|
| "railway"
|
||||||
|
| "flyio"
|
||||||
|
| "azure-key-vault"
|
||||||
|
| "laravel-forge"
|
||||||
|
| "circleci"
|
||||||
|
| "travisci"
|
||||||
|
| "supabase"
|
||||||
|
| "aws-parameter-store"
|
||||||
|
| "aws-secret-manager"
|
||||||
|
| "checkly"
|
||||||
|
| "cloudflare-pages"
|
||||||
|
| "codefresh"
|
||||||
|
| "digital-ocean-app-platform"
|
||||||
|
| "bitbucket"
|
||||||
|
| "cloud-66"
|
||||||
|
| "terraform-cloud"
|
||||||
|
| "teamcity"
|
||||||
|
| "northflank"
|
||||||
|
| "windmill"
|
||||||
|
| "gcp-secret-manager";
|
||||||
|
teamId: string;
|
||||||
|
accountId: string;
|
||||||
|
url: string;
|
||||||
|
namespace: string;
|
||||||
|
refreshCiphertext?: string;
|
||||||
|
refreshIV?: string;
|
||||||
|
refreshTag?: string;
|
||||||
|
accessIdCiphertext?: string;
|
||||||
|
accessIdIV?: string;
|
||||||
|
accessIdTag?: string;
|
||||||
|
accessCiphertext?: string;
|
||||||
|
accessIV?: string;
|
||||||
|
accessTag?: string;
|
||||||
|
algorithm?: "aes-256-gcm";
|
||||||
|
keyEncoding?: "utf8" | "base64";
|
||||||
|
accessExpiresAt?: Date;
|
||||||
|
metadata?: IntegrationAuthMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||||
|
{
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Workspace",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
integration: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_AWS_PARAMETER_STORE,
|
||||||
|
INTEGRATION_AWS_SECRET_MANAGER,
|
||||||
|
INTEGRATION_HEROKU,
|
||||||
|
INTEGRATION_VERCEL,
|
||||||
|
INTEGRATION_NETLIFY,
|
||||||
|
INTEGRATION_GITHUB,
|
||||||
|
INTEGRATION_GITLAB,
|
||||||
|
INTEGRATION_RENDER,
|
||||||
|
INTEGRATION_RAILWAY,
|
||||||
|
INTEGRATION_FLYIO,
|
||||||
|
INTEGRATION_CIRCLECI,
|
||||||
|
INTEGRATION_LARAVELFORGE,
|
||||||
|
INTEGRATION_TRAVISCI,
|
||||||
|
INTEGRATION_TEAMCITY,
|
||||||
|
INTEGRATION_SUPABASE,
|
||||||
|
INTEGRATION_TERRAFORM_CLOUD,
|
||||||
|
INTEGRATION_HASHICORP_VAULT,
|
||||||
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
|
INTEGRATION_CODEFRESH,
|
||||||
|
INTEGRATION_WINDMILL,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
|
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||||
|
INTEGRATION_CLOUD_66,
|
||||||
|
INTEGRATION_NORTHFLANK,
|
||||||
|
INTEGRATION_GCP_SECRET_MANAGER
|
||||||
|
],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
teamId: {
|
||||||
|
// 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,
|
||||||
|
},
|
||||||
|
refreshCiphertext: {
|
||||||
|
type: String,
|
||||||
|
select: false,
|
||||||
|
},
|
||||||
|
refreshIV: {
|
||||||
|
type: String,
|
||||||
|
select: false,
|
||||||
|
},
|
||||||
|
refreshTag: {
|
||||||
|
type: String,
|
||||||
|
select: false,
|
||||||
|
},
|
||||||
|
accessIdCiphertext: {
|
||||||
|
type: String,
|
||||||
|
select: false,
|
||||||
|
},
|
||||||
|
accessIdIV: {
|
||||||
|
type: String,
|
||||||
|
select: false,
|
||||||
|
},
|
||||||
|
accessIdTag: {
|
||||||
|
type: String,
|
||||||
|
select: false,
|
||||||
|
},
|
||||||
|
accessCiphertext: {
|
||||||
|
type: String,
|
||||||
|
select: false,
|
||||||
|
},
|
||||||
|
accessIV: {
|
||||||
|
type: String,
|
||||||
|
select: false,
|
||||||
|
},
|
||||||
|
accessTag: {
|
||||||
|
type: String,
|
||||||
|
select: false,
|
||||||
|
},
|
||||||
|
accessExpiresAt: {
|
||||||
|
type: Date,
|
||||||
|
select: false,
|
||||||
|
},
|
||||||
|
algorithm: { // the encryption algorithm used
|
||||||
|
type: String,
|
||||||
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64,
|
||||||
|
],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
type: Schema.Types.Mixed
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const IntegrationAuth = model<IIntegrationAuth>(
|
||||||
|
"IntegrationAuth",
|
||||||
|
integrationAuthSchema
|
||||||
|
);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
interface GCPIntegrationAuthMetadata {
|
||||||
|
authMethod: "oauth2" | "serviceAccount"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntegrationAuthMetadata = GCPIntegrationAuthMetadata;
|
||||||
@@ -22,7 +22,6 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
|
|||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
isActive: true,
|
isActive: true,
|
||||||
app: { $ne: null }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// for each workspace integration, sync/push secrets
|
// for each workspace integration, sync/push secrets
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ router.post(
|
|||||||
location: "body",
|
location: "body",
|
||||||
}),
|
}),
|
||||||
body("integrationAuthId").exists().isString().trim(),
|
body("integrationAuthId").exists().isString().trim(),
|
||||||
body("app").trim(),
|
|
||||||
body("isActive").exists().isBoolean(),
|
body("isActive").exists().isBoolean(),
|
||||||
body("appId").trim(),
|
body("app").optional().isString().trim(),
|
||||||
|
body("appId").optional().isString().trim(),
|
||||||
body("secretPath").default("/").isString().trim(),
|
body("secretPath").default("/").isString().trim(),
|
||||||
body("sourceEnvironment").trim(),
|
body("sourceEnvironment").trim(),
|
||||||
body("targetEnvironment").trim(),
|
body("targetEnvironment").trim(),
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ router.get(
|
|||||||
}),
|
}),
|
||||||
requireIntegrationAuthorizationAuth({
|
requireIntegrationAuthorizationAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
attachAccessToken: false
|
||||||
}),
|
}),
|
||||||
param("integrationAuthId"),
|
param("integrationAuthId"),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -54,8 +55,9 @@ router.post(
|
|||||||
router.post(
|
router.post(
|
||||||
"/access-token",
|
"/access-token",
|
||||||
body("workspaceId").exists().trim().notEmpty(),
|
body("workspaceId").exists().trim().notEmpty(),
|
||||||
body("accessId").trim(),
|
body("refreshToken").optional().isString().trim().notEmpty(),
|
||||||
body("accessToken").exists().trim().notEmpty(),
|
body("accessId").optional().isString().trim(),
|
||||||
|
body("accessToken").optional().isString().trim().notEmpty(),
|
||||||
body("url").trim(),
|
body("url").trim(),
|
||||||
body("namespace").trim(),
|
body("namespace").trim(),
|
||||||
body("integration").exists().trim().notEmpty(),
|
body("integration").exists().trim().notEmpty(),
|
||||||
@@ -67,7 +69,7 @@ router.post(
|
|||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
locationWorkspaceId: "body",
|
locationWorkspaceId: "body",
|
||||||
}),
|
}),
|
||||||
integrationAuthController.saveIntegrationAccessToken
|
integrationAuthController.saveIntegrationToken
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
setIntegrationAuthRefreshHelper,
|
setIntegrationAuthRefreshHelper,
|
||||||
} from "../helpers/integration";
|
} from "../helpers/integration";
|
||||||
import { syncSecretsToActiveIntegrationsQueue } from "../queues/integrations/syncSecretsToThirdPartyServices";
|
import { syncSecretsToActiveIntegrationsQueue } from "../queues/integrations/syncSecretsToThirdPartyServices";
|
||||||
|
import { IIntegrationAuth } from "../models";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to handle integrations
|
* Class to handle integrations
|
||||||
@@ -102,7 +103,7 @@ class IntegrationService {
|
|||||||
}: {
|
}: {
|
||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) {
|
}): Promise<IIntegrationAuth> {
|
||||||
return await setIntegrationAuthRefreshHelper({
|
return await setIntegrationAuthRefreshHelper({
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
refreshToken,
|
refreshToken,
|
||||||
@@ -127,8 +128,8 @@ class IntegrationService {
|
|||||||
accessExpiresAt,
|
accessExpiresAt,
|
||||||
}: {
|
}: {
|
||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
accessId: string | null;
|
accessId?: string;
|
||||||
accessToken: string;
|
accessToken?: string;
|
||||||
accessExpiresAt: Date | undefined;
|
accessExpiresAt: Date | undefined;
|
||||||
}) {
|
}) {
|
||||||
return await setIntegrationAuthAccessHelper({
|
return await setIntegrationAuthAccessHelper({
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export const INTEGRATION_SET = new Set([
|
|||||||
export const INTEGRATION_OAUTH2 = "oauth2";
|
export const INTEGRATION_OAUTH2 = "oauth2";
|
||||||
|
|
||||||
// integration oauth endpoints
|
// integration oauth endpoints
|
||||||
export const INTEGRATION_GCP_TOKEN_URL = "https://accounts.google.com/o/oauth2/token";
|
export const INTEGRATION_GCP_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
||||||
export const INTEGRATION_AZURE_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
|
export const INTEGRATION_AZURE_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
|
||||||
export const INTEGRATION_HEROKU_TOKEN_URL = "https://id.heroku.com/oauth/token";
|
export const INTEGRATION_HEROKU_TOKEN_URL = "https://id.heroku.com/oauth/token";
|
||||||
export const INTEGRATION_VERCEL_TOKEN_URL =
|
export const INTEGRATION_VERCEL_TOKEN_URL =
|
||||||
@@ -105,6 +105,7 @@ export const INTEGRATION_NORTHFLANK_API_URL = "https://api.northflank.com";
|
|||||||
export const INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com"
|
export const INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com"
|
||||||
export const INTEGRATION_GCP_SECRET_MANAGER_URL = `https://${INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME}`;
|
export const INTEGRATION_GCP_SECRET_MANAGER_URL = `https://${INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME}`;
|
||||||
export const INTEGRATION_GCP_SERVICE_USAGE_URL = "https://serviceusage.googleapis.com";
|
export const INTEGRATION_GCP_SERVICE_USAGE_URL = "https://serviceusage.googleapis.com";
|
||||||
|
export const INTEGRATION_GCP_CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
|
||||||
|
|
||||||
export const getIntegrationOptions = async () => {
|
export const getIntegrationOptions = async () => {
|
||||||
const INTEGRATION_OPTIONS = [
|
const INTEGRATION_OPTIONS = [
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 893 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 992 KiB |
@@ -16,7 +16,7 @@ Navigate to your IAM user permissions and add a permission policy to grant acces
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
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:
|
For enhanced 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
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,17 +5,24 @@ description: "How to sync secrets from Infisical to GCP Secret Manager"
|
|||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="Usage">
|
<Tab title="Usage">
|
||||||
|
|
||||||
|
<AccordionGroup>
|
||||||
|
<Accordion title="Connect with OAuth2">
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
## Navigate to your project's integrations tab
|
||||||
## Navigate to your project's integrations tab
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Authorize Infisical for GCP
|
## Authorize Infisical for GCP
|
||||||
|
|
||||||
Press on the GCP Secret Manager tile and grant Infisical access to GCP.
|
Press on the GCP Secret Manager tile and select **Continue with OAuth**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Grant Infisical access to GCP.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -37,9 +44,61 @@ Select which Infisical environment secrets you want to sync to which GCP secret
|
|||||||
Using Infisical to sync secrets to GCP Secret Manager requires that you enable
|
Using Infisical to sync secrets to GCP Secret Manager requires that you enable
|
||||||
the Service Usage API in the Google Cloud project you want to sync secrets to. More on that [here](https://cloud.google.com/service-usage/docs/set-up-development-environment).
|
the Service Usage API in the Google Cloud project you want to sync secrets to. More on that [here](https://cloud.google.com/service-usage/docs/set-up-development-environment).
|
||||||
</Warning>
|
</Warning>
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="Connect with Service Account JSON">
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- Have a GCP project and have/create a [service account](https://cloud.google.com/iam/docs/service-account-overview) in it
|
||||||
|
|
||||||
|
## Grant the service account permissions for GCP Secret Manager
|
||||||
|
|
||||||
|
Navigate to **IAM & Admin** page in GCP and add the **Secret Manager Admin** and **Service Usage Admin** roles to the service account.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Info>
|
||||||
|
For enhanced security, you may want to assign more granular permissions to the service account. At minimum,
|
||||||
|
the service account should be able to read/write secrets from/to GCP Secret Manager (e.g. **Secret Manager Admin** role)
|
||||||
|
and list which GCP services are enabled/disabled (e.g. **Service Usage Admin** role).
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
## Navigate to your project's integrations tab
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Authorize Infisical for GCP
|
||||||
|
|
||||||
|
Press on the GCP Secret Manager tile and paste in your **GCP Service Account JSON** (you can create and download the JSON for your
|
||||||
|
service account in IAM & Admin > Service Accounts > Service Account > Keys).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<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 the GCP secret manager project. Lastly, press create integration to start syncing secrets to GCP secret manager.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Using Infisical to sync secrets to GCP Secret Manager requires that you enable
|
||||||
|
the Service Usage API in the Google Cloud project you want to sync secrets to. More on that [here](https://cloud.google.com/service-usage/docs/set-up-development-environment).
|
||||||
|
</Warning>
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Self-Hosted Setup">
|
<Tab title="Self-Hosted Setup">
|
||||||
Using the GCP Secret Manager integration on a self-hosted instance of Infisical requires configuring an OAuth2 application in GCP
|
Using the GCP Secret Manager integration (via the OAuth2 method) on a self-hosted instance of Infisical requires configuring an OAuth2 application in GCP
|
||||||
and registering your instance with it.
|
and registering your instance with it.
|
||||||
|
|
||||||
## Create an OAuth2 application in GCP
|
## Create an OAuth2 application in GCP
|
||||||
|
|||||||
@@ -394,6 +394,7 @@ export const useSaveIntegrationAccessToken = () => {
|
|||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
integration,
|
integration,
|
||||||
|
refreshToken,
|
||||||
accessId,
|
accessId,
|
||||||
accessToken,
|
accessToken,
|
||||||
url,
|
url,
|
||||||
@@ -401,14 +402,16 @@ export const useSaveIntegrationAccessToken = () => {
|
|||||||
}: {
|
}: {
|
||||||
workspaceId: string | null;
|
workspaceId: string | null;
|
||||||
integration: string | undefined;
|
integration: string | undefined;
|
||||||
accessId: string | null;
|
refreshToken?: string;
|
||||||
accessToken: string;
|
accessId?: string;
|
||||||
|
accessToken?: string;
|
||||||
url: string | null;
|
url: string | null;
|
||||||
namespace: string | null;
|
namespace: string | null;
|
||||||
}) => {
|
}) => {
|
||||||
const { data: { integrationAuth } } = await apiRequest.post("/api/v1/integration-auth/access-token", {
|
const { data: { integrationAuth } } = await apiRequest.post("/api/v1/integration-auth/access-token", {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
integration,
|
integration,
|
||||||
|
refreshToken,
|
||||||
accessId,
|
accessId,
|
||||||
accessToken,
|
accessToken,
|
||||||
url,
|
url,
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ export const useCreateIntegration = () => {
|
|||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
app: string | null;
|
app?: string | null;
|
||||||
appId: string | null;
|
appId?: string | null;
|
||||||
sourceEnvironment: string;
|
sourceEnvironment: string;
|
||||||
targetEnvironment: string | null;
|
targetEnvironment: string | null;
|
||||||
targetEnvironmentId: string | null;
|
targetEnvironmentId: string | null;
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export default function ChecklyCreateIntegrationPage() {
|
|||||||
const integrationAuth = await mutateAsync({
|
const integrationAuth = await mutateAsync({
|
||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "checkly",
|
integration: "checkly",
|
||||||
accessId: null,
|
|
||||||
accessToken,
|
accessToken,
|
||||||
url: null,
|
url: null,
|
||||||
namespace: null
|
namespace: null
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "circleci",
|
integration: "circleci",
|
||||||
accessToken: apiKey,
|
accessToken: apiKey,
|
||||||
accessId: null,
|
|
||||||
url: null,
|
url: null,
|
||||||
namespace:null
|
namespace:null
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export default function Cloud66CreateIntegrationPage() {
|
|||||||
const integrationAuth = await mutateAsync({
|
const integrationAuth = await mutateAsync({
|
||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "cloud-66",
|
integration: "cloud-66",
|
||||||
accessId: null,
|
|
||||||
accessToken: apiKey,
|
accessToken: apiKey,
|
||||||
url: null,
|
url: null,
|
||||||
namespace: null
|
namespace: null
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export default function CodefreshCreateIntegrationPage() {
|
|||||||
const integrationAuth = await mutateAsync({
|
const integrationAuth = await mutateAsync({
|
||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "codefresh",
|
integration: "codefresh",
|
||||||
accessId: null,
|
|
||||||
accessToken: apiKey,
|
accessToken: apiKey,
|
||||||
url: null,
|
url: null,
|
||||||
namespace: null
|
namespace: null
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export default function DigitalOceanAppPlatformCreateIntegrationPage() {
|
|||||||
const integrationAuth = await mutateAsync({
|
const integrationAuth = await mutateAsync({
|
||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "digital-ocean-app-platform",
|
integration: "digital-ocean-app-platform",
|
||||||
accessId: null,
|
|
||||||
accessToken: apiKey,
|
accessToken: apiKey,
|
||||||
url: null,
|
url: null,
|
||||||
namespace: null
|
namespace: null
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export default function FlyioCreateIntegrationPage() {
|
|||||||
const integrationAuth = await mutateAsync({
|
const integrationAuth = await mutateAsync({
|
||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "flyio",
|
integration: "flyio",
|
||||||
accessId: null,
|
|
||||||
accessToken,
|
accessToken,
|
||||||
url: null,
|
url: null,
|
||||||
namespace: null
|
namespace: null
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { faGoogle } from "@fortawesome/free-brands-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import {
|
||||||
|
useGetCloudIntegrations,
|
||||||
|
useSaveIntegrationAccessToken} from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { Button, Card, CardTitle, FormControl, TextArea } from "../../../components/v2";
|
||||||
|
|
||||||
|
export default function GCPSecretManagerAuthorizeIntegrationPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const { data: cloudIntegrations } = useGetCloudIntegrations();
|
||||||
|
|
||||||
|
const { mutateAsync } = useSaveIntegrationAccessToken();
|
||||||
|
|
||||||
|
const [accessToken, setAccessToken] = useState("");
|
||||||
|
const [accessTokenErrorText, setAccessTokenErrorText] = useState("");
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleIntegrateWithPAT = async () => {
|
||||||
|
try {
|
||||||
|
setAccessTokenErrorText("");
|
||||||
|
if (accessToken.length === 0) {
|
||||||
|
setAccessTokenErrorText("Service account JSON cannot be blank");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const integrationAuth = await mutateAsync({
|
||||||
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
|
integration: "gcp-secret-manager",
|
||||||
|
refreshToken: accessToken,
|
||||||
|
url: null,
|
||||||
|
namespace: null
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
|
||||||
|
router.push(`/integrations/gcp-secret-manager/pat/create?integrationAuthId=${integrationAuth._id}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleIntegrateWithOAuth = () => {
|
||||||
|
if (!cloudIntegrations) return;
|
||||||
|
const integrationOption = cloudIntegrations.find((integration) => integration.slug === "gcp-secret-manager");
|
||||||
|
|
||||||
|
if (!integrationOption) return;
|
||||||
|
|
||||||
|
const state = crypto.randomBytes(16).toString("hex");
|
||||||
|
localStorage.setItem("latestCSRFToken", state);
|
||||||
|
|
||||||
|
const link = `https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/cloud-platform&response_type=code&access_type=offline&state=${state}&redirect_uri=${window.location.origin}/integrations/gcp-secret-manager/oauth2/callback&client_id=${integrationOption.clientId}`;
|
||||||
|
window.location.assign(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<Card className="max-w-md rounded-md p-8">
|
||||||
|
<CardTitle className="text-center">GCP Secret Manager Integration</CardTitle>
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={handleIntegrateWithOAuth}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
||||||
|
className="h-11 w-full mx-0 mt-4"
|
||||||
|
>
|
||||||
|
Continue with OAuth
|
||||||
|
</Button>
|
||||||
|
<div className='w-full flex flex-row items-center my-4 py-2'>
|
||||||
|
<div className='w-full border-t border-mineshaft-400/60' />
|
||||||
|
<span className="mx-2 text-mineshaft-200 text-xs">or</span>
|
||||||
|
<div className='w-full border-t border-mineshaft-400/60' />
|
||||||
|
</div>
|
||||||
|
<FormControl
|
||||||
|
label="GCP Service Account JSON"
|
||||||
|
errorText={accessTokenErrorText}
|
||||||
|
isError={accessTokenErrorText !== "" ?? false}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
value={accessToken}
|
||||||
|
onChange={(e) => setAccessToken(e.target.value)}
|
||||||
|
placeholder=""
|
||||||
|
className="h-48"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<Button
|
||||||
|
onClick={handleIntegrateWithPAT}
|
||||||
|
color="mineshaft"
|
||||||
|
className="mt-4"
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
Connect to GCP Secret Manager
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GCPSecretManagerAuthorizeIntegrationPage.requireAuth = true;
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import queryString from "query-string";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardTitle,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
useCreateIntegration
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { useGetIntegrationAuthApps,useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth";
|
||||||
|
import { useGetWorkspaceById } from "@app/hooks/api/workspace";
|
||||||
|
|
||||||
|
export default function GCPSecretManagerCreateIntegrationPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const { mutateAsync } = useCreateIntegration();
|
||||||
|
|
||||||
|
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||||
|
|
||||||
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||||
|
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||||
|
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||||
|
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||||
|
});
|
||||||
|
|
||||||
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||||
|
const [targetAppId, setTargetAppId] = useState("");
|
||||||
|
const [secretPath, setSecretPath] = useState("/");
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (workspace) {
|
||||||
|
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||||
|
}
|
||||||
|
}, [workspace]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (integrationAuthApps) {
|
||||||
|
if (integrationAuthApps.length > 0) {
|
||||||
|
setTargetAppId(integrationAuthApps[0].appId as string);
|
||||||
|
} else {
|
||||||
|
setTargetAppId("none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [integrationAuthApps]);
|
||||||
|
|
||||||
|
const handleButtonClick = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
if (!integrationAuth?._id) return;
|
||||||
|
|
||||||
|
await mutateAsync({
|
||||||
|
integrationAuthId: integrationAuth?._id,
|
||||||
|
isActive: true,
|
||||||
|
app: integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId)?.name ?? null,
|
||||||
|
appId: targetAppId,
|
||||||
|
sourceEnvironment: selectedSourceEnvironment,
|
||||||
|
targetEnvironment: null,
|
||||||
|
targetEnvironmentId: null,
|
||||||
|
targetService: null,
|
||||||
|
targetServiceId: null,
|
||||||
|
owner: null,
|
||||||
|
path: null,
|
||||||
|
region: null,
|
||||||
|
secretPath
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return integrationAuth &&
|
||||||
|
workspace &&
|
||||||
|
selectedSourceEnvironment &&
|
||||||
|
integrationAuthApps
|
||||||
|
? (
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<Card className="max-w-md rounded-md p-8">
|
||||||
|
<CardTitle className="text-center">GCP 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={`source-environment-${sourceEnvironment.slug}`}
|
||||||
|
>
|
||||||
|
{sourceEnvironment.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl label="Secrets Path">
|
||||||
|
<Input
|
||||||
|
value={secretPath}
|
||||||
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
|
placeholder="Provide a path, default is /"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl label="GCP Project">
|
||||||
|
<Select
|
||||||
|
value={targetAppId}
|
||||||
|
onValueChange={(val) => setTargetAppId(val)}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
|
>
|
||||||
|
{integrationAuthApps.length > 0 ? (
|
||||||
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
|
<SelectItem
|
||||||
|
value={integrationAuthApp.appId as string}
|
||||||
|
key={`target-app-${integrationAuthApp.appId}`}
|
||||||
|
>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No projects found
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<Button
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
color="mineshaft"
|
||||||
|
className="mt-4"
|
||||||
|
isLoading={isLoading}
|
||||||
|
// isDisabled={integrationAuthApps.length === 0}
|
||||||
|
>
|
||||||
|
Create Integration
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GCPSecretManagerCreateIntegrationPage.requireAuth = true;
|
||||||
@@ -29,7 +29,6 @@ export default function NorthflankCreateIntegrationPage() {
|
|||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "northflank",
|
integration: "northflank",
|
||||||
accessToken: apiKey,
|
accessToken: apiKey,
|
||||||
accessId: null,
|
|
||||||
url: null,
|
url: null,
|
||||||
namespace: null
|
namespace: null
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export default function RailwayAuthorizeIntegrationPage() {
|
|||||||
const integrationAuth = await mutateAsync({
|
const integrationAuth = await mutateAsync({
|
||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "railway",
|
integration: "railway",
|
||||||
accessId: null,
|
|
||||||
accessToken: apiKey,
|
accessToken: apiKey,
|
||||||
url: null,
|
url: null,
|
||||||
namespace: null
|
namespace: null
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export default function RenderCreateIntegrationPage() {
|
|||||||
const integrationAuth = await mutateAsync({
|
const integrationAuth = await mutateAsync({
|
||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "render",
|
integration: "render",
|
||||||
accessId: null,
|
|
||||||
accessToken: apiKey,
|
accessToken: apiKey,
|
||||||
url: null,
|
url: null,
|
||||||
namespace: null
|
namespace: null
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export default function SupabaseCreateIntegrationPage() {
|
|||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "supabase",
|
integration: "supabase",
|
||||||
accessToken: apiKey,
|
accessToken: apiKey,
|
||||||
accessId: null,
|
|
||||||
url: null,
|
url: null,
|
||||||
namespace: null
|
namespace: null
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export default function TeamCityCreateIntegrationPage() {
|
|||||||
const integrationAuth = await mutateAsync({
|
const integrationAuth = await mutateAsync({
|
||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "teamcity",
|
integration: "teamcity",
|
||||||
accessId: null,
|
|
||||||
accessToken: apiKey,
|
accessToken: apiKey,
|
||||||
url: serverUrl,
|
url: serverUrl,
|
||||||
namespace: null
|
namespace: null
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export default function TravisCICreateIntegrationPage() {
|
|||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "travisci",
|
integration: "travisci",
|
||||||
accessToken: apiKey,
|
accessToken: apiKey,
|
||||||
accessId: null,
|
|
||||||
url: null,
|
url: null,
|
||||||
namespace: null
|
namespace: null
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ export default function WindmillCreateIntegrationPage() {
|
|||||||
workspaceId: localStorage.getItem("projectData.id"),
|
workspaceId: localStorage.getItem("projectData.id"),
|
||||||
integration: "windmill",
|
integration: "windmill",
|
||||||
accessToken: apiKey,
|
accessToken: apiKey,
|
||||||
accessId: null,
|
|
||||||
url: null,
|
url: null,
|
||||||
namespace: null
|
namespace: null
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
|
|||||||
let link = "";
|
let link = "";
|
||||||
switch (integrationOption.slug) {
|
switch (integrationOption.slug) {
|
||||||
case "gcp-secret-manager":
|
case "gcp-secret-manager":
|
||||||
link = `https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/cloud-platform&response_type=code&access_type=offline&state=${state}&redirect_uri=${window.location.origin}/integrations/gcp-secret-manager/oauth2/callback&client_id=${integrationOption.clientId}`;
|
link = `${window.location.origin}/integrations/gcp-secret-manager/authorize`;
|
||||||
break;
|
break;
|
||||||
case "azure-key-vault":
|
case "azure-key-vault":
|
||||||
link = `https://login.microsoftonline.com/common/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}`;
|
link = `https://login.microsoftonline.com/common/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}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user