mirror of
https://github.com/Infisical/infisical.git
synced 2025-04-17 19:37:38 +00:00
Finished adding support for service account JSON auth method for GCP secret manager integration
This commit is contained in:
@ -14,8 +14,10 @@ import {
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
INTEGRATION_SET,
|
||||
INTEGRATION_VERCEL_API_URL,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
getIntegrationOptions as getIntegrationOptionsFunc
|
||||
} from "../../variables";
|
||||
import { exchangeRefresh } from "../../integrations";
|
||||
|
||||
/***
|
||||
* Return integration authorization with id [integrationAuthId]
|
||||
@ -88,7 +90,7 @@ export const oAuthExchange = async (req: Request, res: Response) => {
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const saveIntegrationAccessToken = async (req: Request, res: Response) => {
|
||||
export const saveIntegrationToken = async (req: Request, res: Response) => {
|
||||
// TODO: refactor
|
||||
// TODO: check if access token is valid for each integration
|
||||
|
||||
@ -96,14 +98,16 @@ export const saveIntegrationAccessToken = async (req: Request, res: Response) =>
|
||||
const {
|
||||
workspaceId,
|
||||
accessId,
|
||||
refreshToken,
|
||||
accessToken,
|
||||
url,
|
||||
namespace,
|
||||
integration
|
||||
}: {
|
||||
workspaceId: string;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
accessId: string | undefined;
|
||||
refreshToken: string | undefined;
|
||||
accessToken: string | undefined;
|
||||
url: string;
|
||||
namespace: string;
|
||||
integration: string;
|
||||
@ -127,21 +131,36 @@ export const saveIntegrationAccessToken = async (req: Request, res: Response) =>
|
||||
url,
|
||||
namespace,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
...(integration === INTEGRATION_GCP_SECRET_MANAGER ? {
|
||||
metadata: {
|
||||
authMethod: "serviceAccount"
|
||||
}
|
||||
} : {})
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
upsert: true
|
||||
}
|
||||
);
|
||||
|
||||
// encrypt and save integration access details
|
||||
if (refreshToken) {
|
||||
await exchangeRefresh({
|
||||
integrationAuth,
|
||||
refreshToken
|
||||
});
|
||||
}
|
||||
|
||||
// encrypt and save integration access details
|
||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId,
|
||||
accessToken,
|
||||
accessExpiresAt: undefined
|
||||
});
|
||||
if (accessId || accessToken) {
|
||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId,
|
||||
accessToken,
|
||||
accessExpiresAt: undefined
|
||||
});
|
||||
}
|
||||
|
||||
if (!integrationAuth) throw new Error("Failed to save integration access token");
|
||||
|
||||
|
@ -1,20 +1,23 @@
|
||||
import { Types } from "mongoose";
|
||||
import { Bot, IntegrationAuth } from "../models";
|
||||
import { Bot, IIntegrationAuth, IntegrationAuth } from "../models";
|
||||
import { exchangeCode, exchangeRefresh } from "../integrations";
|
||||
import { BotService } from "../services";
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_VERCEL
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
} from "../variables";
|
||||
import { UnauthorizedRequestError } from "../utils/errors";
|
||||
import { BadRequestError, InternalServerError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { IntegrationAuthMetadata } from "../models/integrationAuth/types";
|
||||
|
||||
interface Update {
|
||||
workspace: string;
|
||||
integration: string;
|
||||
teamId?: string;
|
||||
accountId?: string;
|
||||
metadata?: IntegrationAuthMetadata
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,6 +67,10 @@ export const handleOAuthExchangeHelper = async ({
|
||||
break;
|
||||
case INTEGRATION_NETLIFY:
|
||||
update.accountId = res.accountId;
|
||||
case INTEGRATION_GCP_SECRET_MANAGER:
|
||||
update.metadata = {
|
||||
authMethod: "oauth2"
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -93,7 +100,6 @@ export const handleOAuthExchangeHelper = async ({
|
||||
// set integration auth access token
|
||||
await setIntegrationAuthAccessHelper({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId: null,
|
||||
accessToken: res.accessToken,
|
||||
accessExpiresAt: res.accessExpiresAt
|
||||
});
|
||||
@ -158,22 +164,24 @@ export const getIntegrationAuthAccessHelper = async ({
|
||||
message: "Failed to locate Integration Authentication credentials"
|
||||
});
|
||||
|
||||
accessToken = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.accessCiphertext as string,
|
||||
iv: integrationAuth.accessIV as string,
|
||||
tag: integrationAuth.accessTag as string
|
||||
});
|
||||
if (integrationAuth.accessCiphertext && integrationAuth.accessIV && integrationAuth.accessTag) {
|
||||
accessToken = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.accessCiphertext 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
|
||||
// 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
|
||||
const refreshToken = await getIntegrationAuthRefreshHelper({
|
||||
integrationAuthId
|
||||
});
|
||||
accessToken = await exchangeRefresh({
|
||||
integrationAuth,
|
||||
refreshToken
|
||||
@ -194,6 +202,8 @@ export const getIntegrationAuthAccessHelper = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (!accessToken) throw InternalServerError();
|
||||
|
||||
return {
|
||||
accessId,
|
||||
accessToken
|
||||
@ -214,7 +224,7 @@ export const setIntegrationAuthRefreshHelper = async ({
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
refreshToken: string;
|
||||
}) => {
|
||||
}): Promise<IIntegrationAuth> => {
|
||||
let integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
||||
|
||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||
@ -239,6 +249,8 @@ export const setIntegrationAuthRefreshHelper = async ({
|
||||
new: true
|
||||
}
|
||||
);
|
||||
|
||||
if (!integrationAuth) throw InternalServerError();
|
||||
|
||||
return integrationAuth;
|
||||
};
|
||||
@ -259,20 +271,24 @@ export const setIntegrationAuthAccessHelper = async ({
|
||||
accessExpiresAt
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
accessId?: string;
|
||||
accessToken?: string;
|
||||
accessExpiresAt: Date | undefined;
|
||||
}) => {
|
||||
let integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
||||
|
||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||
|
||||
const encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: accessToken
|
||||
});
|
||||
|
||||
|
||||
let encryptedAccessTokenObj;
|
||||
let encryptedAccessIdObj;
|
||||
|
||||
if (accessToken) {
|
||||
encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: accessToken
|
||||
});
|
||||
}
|
||||
|
||||
if (accessId) {
|
||||
encryptedAccessIdObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
@ -286,11 +302,11 @@ export const setIntegrationAuthAccessHelper = async ({
|
||||
},
|
||||
{
|
||||
accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined,
|
||||
accessIdIV: encryptedAccessIdObj?.iv ?? undefined,
|
||||
accessIdTag: encryptedAccessIdObj?.tag ?? undefined,
|
||||
accessCiphertext: encryptedAccessTokenObj.ciphertext,
|
||||
accessIV: encryptedAccessTokenObj.iv,
|
||||
accessTag: encryptedAccessTokenObj.tag,
|
||||
accessIdIV: encryptedAccessIdObj?.iv,
|
||||
accessIdTag: encryptedAccessIdObj?.tag,
|
||||
accessCiphertext: encryptedAccessTokenObj?.ciphertext,
|
||||
accessIV: encryptedAccessTokenObj?.iv,
|
||||
accessTag: encryptedAccessTokenObj?.tag,
|
||||
accessExpiresAt,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
|
@ -46,6 +46,14 @@ interface ExchangeCodeAzureResponse {
|
||||
id_token: string;
|
||||
}
|
||||
|
||||
interface ExchangeCodeGCPResponse {
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
scope: string;
|
||||
token_type: string;
|
||||
}
|
||||
|
||||
interface ExchangeCodeHerokuResponse {
|
||||
token_type: string;
|
||||
access_token: string;
|
||||
@ -174,7 +182,7 @@ const exchangeCode = async ({
|
||||
const exchangeCodeGCP = async ({ code }: { code: string }) => {
|
||||
const accessExpiresAt = new Date();
|
||||
|
||||
const res: ExchangeCodeAzureResponse = (
|
||||
const res: ExchangeCodeGCPResponse = (
|
||||
await standardRequest.post(
|
||||
INTEGRATION_GCP_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
|
@ -1,3 +1,4 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import { standardRequest } from "../config/request";
|
||||
import { IIntegrationAuth } from "../models";
|
||||
import {
|
||||
@ -6,6 +7,9 @@ import {
|
||||
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_GCP_TOKEN_URL,
|
||||
INTEGRATION_GCP_CLOUD_PLATFORM_SCOPE
|
||||
} from "../variables";
|
||||
import {
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
@ -21,6 +25,8 @@ import {
|
||||
getClientSecretBitBucket,
|
||||
getClientSecretGitLab,
|
||||
getClientSecretHeroku,
|
||||
getClientIdGCPSecretManager,
|
||||
getClientSecretGCPSecretManager,
|
||||
getSiteURL,
|
||||
} from "../config";
|
||||
|
||||
@ -59,6 +65,19 @@ interface RefreshTokenBitBucketResponse {
|
||||
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
|
||||
* named [integration]
|
||||
@ -101,18 +120,23 @@ const exchangeRefresh = async ({
|
||||
refreshToken,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_GCP_SECRET_MANAGER:
|
||||
tokenDetails = await exchangeRefreshGCPSecretManager({
|
||||
integrationAuth,
|
||||
refreshToken,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error("Failed to exchange token for incompatible integration");
|
||||
}
|
||||
|
||||
if (
|
||||
tokenDetails?.accessToken &&
|
||||
tokenDetails?.refreshToken &&
|
||||
tokenDetails?.accessExpiresAt
|
||||
tokenDetails.accessToken &&
|
||||
tokenDetails.refreshToken &&
|
||||
tokenDetails.accessExpiresAt
|
||||
) {
|
||||
await IntegrationService.setIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId: null,
|
||||
accessToken: tokenDetails.accessToken,
|
||||
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 {
|
||||
CreateSecretCommand,
|
||||
GetSecretValueCommand,
|
||||
@ -28,6 +29,8 @@ import {
|
||||
INTEGRATION_FLYIO_API_URL,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_GCP_SECRET_MANAGER_URL,
|
||||
INTEGRATION_GCP_TOKEN_URL,
|
||||
INTEGRATION_GCP_CLOUD_PLATFORM_SCOPE,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
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
|
||||
);
|
1
backend/src/models/integrationAuth/index.ts
Normal file
1
backend/src/models/integrationAuth/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./integrationAuth";
|
204
backend/src/models/integrationAuth/integrationAuth.ts
Normal file
204
backend/src/models/integrationAuth/integrationAuth.ts
Normal file
@ -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
|
||||
);
|
5
backend/src/models/integrationAuth/types.ts
Normal file
5
backend/src/models/integrationAuth/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
interface GCPIntegrationAuthMetadata {
|
||||
authMethod: "oauth2" | "serviceAccount"
|
||||
}
|
||||
|
||||
export type IntegrationAuthMetadata = GCPIntegrationAuthMetadata;
|
@ -22,7 +22,6 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
|
||||
}
|
||||
: {}),
|
||||
isActive: true,
|
||||
app: { $ne: null }
|
||||
});
|
||||
|
||||
// for each workspace integration, sync/push secrets
|
||||
|
@ -25,9 +25,9 @@ router.post(
|
||||
location: "body",
|
||||
}),
|
||||
body("integrationAuthId").exists().isString().trim(),
|
||||
body("app").trim(),
|
||||
body("isActive").exists().isBoolean(),
|
||||
body("appId").trim(),
|
||||
body("app").optional().isString().trim(),
|
||||
body("appId").optional().isString().trim(),
|
||||
body("secretPath").default("/").isString().trim(),
|
||||
body("sourceEnvironment").trim(),
|
||||
body("targetEnvironment").trim(),
|
||||
|
@ -29,6 +29,7 @@ router.get(
|
||||
}),
|
||||
requireIntegrationAuthorizationAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
attachAccessToken: false
|
||||
}),
|
||||
param("integrationAuthId"),
|
||||
validateRequest,
|
||||
@ -54,8 +55,9 @@ router.post(
|
||||
router.post(
|
||||
"/access-token",
|
||||
body("workspaceId").exists().trim().notEmpty(),
|
||||
body("accessId").trim(),
|
||||
body("accessToken").exists().trim().notEmpty(),
|
||||
body("refreshToken").optional().isString().trim().notEmpty(),
|
||||
body("accessId").optional().isString().trim(),
|
||||
body("accessToken").optional().isString().trim().notEmpty(),
|
||||
body("url").trim(),
|
||||
body("namespace").trim(),
|
||||
body("integration").exists().trim().notEmpty(),
|
||||
@ -67,7 +69,7 @@ router.post(
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "body",
|
||||
}),
|
||||
integrationAuthController.saveIntegrationAccessToken
|
||||
integrationAuthController.saveIntegrationToken
|
||||
);
|
||||
|
||||
router.get(
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
setIntegrationAuthRefreshHelper,
|
||||
} from "../helpers/integration";
|
||||
import { syncSecretsToActiveIntegrationsQueue } from "../queues/integrations/syncSecretsToThirdPartyServices";
|
||||
import { IIntegrationAuth } from "../models";
|
||||
|
||||
/**
|
||||
* Class to handle integrations
|
||||
@ -102,7 +103,7 @@ class IntegrationService {
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
refreshToken: string;
|
||||
}) {
|
||||
}): Promise<IIntegrationAuth> {
|
||||
return await setIntegrationAuthRefreshHelper({
|
||||
integrationAuthId,
|
||||
refreshToken,
|
||||
@ -127,8 +128,8 @@ class IntegrationService {
|
||||
accessExpiresAt,
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
accessId?: string;
|
||||
accessToken?: string;
|
||||
accessExpiresAt: Date | undefined;
|
||||
}) {
|
||||
return await setIntegrationAuthAccessHelper({
|
||||
|
@ -68,7 +68,7 @@ export const INTEGRATION_SET = new Set([
|
||||
export const INTEGRATION_OAUTH2 = "oauth2";
|
||||
|
||||
// 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_HEROKU_TOKEN_URL = "https://id.heroku.com/oauth/token";
|
||||
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_URL = `https://${INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME}`;
|
||||
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 () => {
|
||||
const INTEGRATION_OPTIONS = [
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 1.0 MiB |
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
|
||||
{
|
||||
|
@ -5,17 +5,24 @@ description: "How to sync secrets from Infisical to GCP Secret Manager"
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Connect with OAuth2">
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- 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
|
||||
|
||||
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
|
||||
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>
|
||||
<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 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.
|
||||
|
||||
## Create an OAuth2 application in GCP
|
||||
|
@ -394,6 +394,7 @@ export const useSaveIntegrationAccessToken = () => {
|
||||
mutationFn: async ({
|
||||
workspaceId,
|
||||
integration,
|
||||
refreshToken,
|
||||
accessId,
|
||||
accessToken,
|
||||
url,
|
||||
@ -401,14 +402,16 @@ export const useSaveIntegrationAccessToken = () => {
|
||||
}: {
|
||||
workspaceId: string | null;
|
||||
integration: string | undefined;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
refreshToken?: string;
|
||||
accessId?: string;
|
||||
accessToken?: string;
|
||||
url: string | null;
|
||||
namespace: string | null;
|
||||
}) => {
|
||||
const { data: { integrationAuth } } = await apiRequest.post("/api/v1/integration-auth/access-token", {
|
||||
workspaceId,
|
||||
integration,
|
||||
refreshToken,
|
||||
accessId,
|
||||
accessToken,
|
||||
url,
|
||||
|
@ -46,8 +46,8 @@ export const useCreateIntegration = () => {
|
||||
integrationAuthId: string;
|
||||
isActive: boolean;
|
||||
secretPath: string;
|
||||
app: string | null;
|
||||
appId: string | null;
|
||||
app?: string | null;
|
||||
appId?: string | null;
|
||||
sourceEnvironment: string;
|
||||
targetEnvironment: string | null;
|
||||
targetEnvironmentId: string | null;
|
||||
|
@ -1,51 +1,64 @@
|
||||
import crypto from "crypto";
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faGoogle } from "@fortawesome/free-brands-svg-icons";
|
||||
|
||||
import {
|
||||
useSaveIntegrationAccessToken
|
||||
useSaveIntegrationAccessToken,
|
||||
useGetCloudIntegrations
|
||||
} from "@app/hooks/api";
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, Input, 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 handleButtonClick = async () => {
|
||||
const handleIntegrateWithPAT = async () => {
|
||||
try {
|
||||
setAccessTokenErrorText("");
|
||||
if (accessToken.length === 0) {
|
||||
setAccessTokenErrorText("Access token cannot be blank");
|
||||
setAccessTokenErrorText("Service account JSON cannot be blank");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("setAccessTokenErrorText");
|
||||
console.log("accessToken: ", accessToken);
|
||||
|
||||
// setIsLoading(true);
|
||||
setIsLoading(true);
|
||||
|
||||
// const integrationAuth = await mutateAsync({
|
||||
// workspaceId: localStorage.getItem("projectData.id"),
|
||||
// integration: "flyio",
|
||||
// accessId: null,
|
||||
// accessToken,
|
||||
// url: null,
|
||||
// namespace: null
|
||||
// });
|
||||
const integrationAuth = await mutateAsync({
|
||||
workspaceId: localStorage.getItem("projectData.id"),
|
||||
integration: "gcp-secret-manager",
|
||||
refreshToken: accessToken,
|
||||
url: null,
|
||||
namespace: null
|
||||
});
|
||||
|
||||
// setIsLoading(false);
|
||||
setIsLoading(false);
|
||||
|
||||
// router.push(`/integrations/flyio/create?integrationAuthId=${integrationAuth._id}`);
|
||||
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">
|
||||
@ -54,10 +67,7 @@ export default function GCPSecretManagerAuthorizeIntegrationPage() {
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
// TODO: somehow get client ID
|
||||
// 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}`;
|
||||
}}
|
||||
onClick={handleIntegrateWithOAuth}
|
||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
||||
className="h-11 w-full mx-0 mt-4"
|
||||
>
|
||||
@ -74,13 +84,14 @@ export default function GCPSecretManagerAuthorizeIntegrationPage() {
|
||||
isError={accessTokenErrorText !== "" ?? false}
|
||||
>
|
||||
<TextArea
|
||||
value={accessToken}
|
||||
onChange={(e) => setAccessToken(e.target.value)}
|
||||
placeholder=""
|
||||
value={accessToken}
|
||||
onChange={(e) => setAccessToken(e.target.value)}
|
||||
placeholder=""
|
||||
className="h-48"
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
onClick={handleIntegrateWithPAT}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
isLoading={isLoading}
|
||||
|
@ -0,0 +1,154 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
useCreateIntegration
|
||||
} from "@app/hooks/api";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { useGetIntegrationAuthById, useGetIntegrationAuthApps } 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;
|
@ -40,7 +40,6 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
|
||||
let link = "";
|
||||
switch (integrationOption.slug) {
|
||||
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;
|
||||
case "azure-key-vault":
|
||||
|
Reference in New Issue
Block a user