Skip to content

Commit

Permalink
Merge pull request #3039 from Infisical/feat/gcp-secret-sync
Browse files Browse the repository at this point in the history
feat: gcp app connections and secret sync
  • Loading branch information
sheensantoscapadngan authored Jan 28, 2025
2 parents 7dc9ea4 + 7f89a7c commit c3d515b
Show file tree
Hide file tree
Showing 94 changed files with 1,555 additions and 41 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,6 @@ INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET=
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY=
INF_APP_CONNECTION_GITHUB_APP_SLUG=
INF_APP_CONNECTION_GITHUB_APP_ID=

#gcp app
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL=
3 changes: 3 additions & 0 deletions backend/src/lib/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ const envSchema = z
INF_APP_CONNECTION_GITHUB_APP_SLUG: zpStr(z.string().optional()),
INF_APP_CONNECTION_GITHUB_APP_ID: zpStr(z.string().optional()),

// gcp app
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL: zpStr(z.string().optional()),

/* CORS ----------------------------------------------------------------------------- */

CORS_ALLOWED_ORIGINS: zpStr(
Expand Down
2 changes: 1 addition & 1 deletion backend/src/lib/crypto/encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const decryptAsymmetric = ({ ciphertext, nonce, publicKey, privateKey }:

export const generateSymmetricKey = (size = 32) => crypto.randomBytes(size).toString("base64");

export const generateHash = (value: string) => crypto.createHash("sha256").update(value).digest("hex");
export const generateHash = (value: string | Buffer) => crypto.createHash("sha256").update(value).digest("hex");

export const generateAsymmetricKeyPair = () => {
const pair = nacl.box.keyPair();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AwsConnectionListItemSchema, SanitizedAwsConnectionSchema } from "@app/services/app-connection/aws";
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
import { AuthMode } from "@app/services/auth/auth-type";

// can't use discriminated due to multiple schemas for certain apps
const SanitizedAppConnectionSchema = z.union([
...SanitizedAwsConnectionSchema.options,
...SanitizedGitHubConnectionSchema.options
...SanitizedGitHubConnectionSchema.options,
...SanitizedGcpConnectionSchema.options
]);

const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
AwsConnectionListItemSchema,
GitHubConnectionListItemSchema
GitHubConnectionListItemSchema,
GcpConnectionListItemSchema
]);

export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import z from "zod";

import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateGcpConnectionSchema,
SanitizedGcpConnectionSchema,
UpdateGcpConnectionSchema
} from "@app/services/app-connection/gcp";
import { AuthMode } from "@app/services/auth/auth-type";

import { registerAppConnectionEndpoints } from "./app-connection-endpoints";

export const registerGcpConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.GCP,
server,
sanitizedResponseSchema: SanitizedGcpConnectionSchema,
createSchema: CreateGcpConnectionSchema,
updateSchema: UpdateGcpConnectionSchema
});

// The below endpoints are not exposed and for Infisical App use
server.route({
method: "GET",
url: `/:connectionId/secret-manager-projects`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z.object({ id: z.string(), name: z.string() }).array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;

const projects = await server.services.appConnection.gcp.listSecretManagerProjects(connectionId, req.permission);

return projects;
}
});
};
4 changes: 3 additions & 1 deletion backend/src/server/routes/v1/app-connection-routers/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";

import { registerAwsConnectionRouter } from "./aws-connection-router";
import { registerGcpConnectionRouter } from "./gcp-connection-router";
import { registerGitHubConnectionRouter } from "./github-connection-router";

export * from "./app-connection-router";

export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server: FastifyZodProvider) => Promise<void>> =
{
[AppConnection.AWS]: registerAwsConnectionRouter,
[AppConnection.GitHub]: registerGitHubConnectionRouter
[AppConnection.GitHub]: registerGitHubConnectionRouter,
[AppConnection.GCP]: registerGcpConnectionRouter
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CreateGcpSyncSchema, GcpSyncSchema, UpdateGcpSyncSchema } from "@app/services/secret-sync/gcp";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";

import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";

export const registerGcpSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.GCPSecretManager,
server,
responseSchema: GcpSyncSchema,
createSchema: CreateGcpSyncSchema,
updateSchema: UpdateGcpSyncSchema
});
4 changes: 3 additions & 1 deletion backend/src/server/routes/v1/secret-sync-routers/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";

import { registerAwsParameterStoreSyncRouter } from "./aws-parameter-store-sync-router";
import { registerGcpSyncRouter } from "./gcp-sync-router";
import { registerGitHubSyncRouter } from "./github-sync-router";

export * from "./secret-sync-router";

export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: FastifyZodProvider) => Promise<void>> = {
[SecretSync.AWSParameterStore]: registerAwsParameterStoreSyncRouter,
[SecretSync.GitHub]: registerGitHubSyncRouter
[SecretSync.GitHub]: registerGitHubSyncRouter,
[SecretSync.GCPSecretManager]: registerGcpSyncRouter
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ import {
AwsParameterStoreSyncListItemSchema,
AwsParameterStoreSyncSchema
} from "@app/services/secret-sync/aws-parameter-store";
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";

const SecretSyncSchema = z.discriminatedUnion("destination", [AwsParameterStoreSyncSchema, GitHubSyncSchema]);
const SecretSyncSchema = z.discriminatedUnion("destination", [
AwsParameterStoreSyncSchema,
GitHubSyncSchema,
GcpSyncSchema
]);

const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
AwsParameterStoreSyncListItemSchema,
GitHubSyncListItemSchema
GitHubSyncListItemSchema,
GcpSyncListItemSchema
]);

export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
Expand Down
3 changes: 2 additions & 1 deletion backend/src/services/app-connection/app-connection-enums.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export enum AppConnection {
GitHub = "github",
AWS = "aws"
AWS = "aws",
GCP = "gcp"
}

export enum AWSRegion {
Expand Down
17 changes: 15 additions & 2 deletions backend/src/services/app-connection/app-connection-fns.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TAppConnections } from "@app/db/schemas/app-connections";
import { generateHash } from "@app/lib/crypto/encryption";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { TAppConnectionServiceFactoryDep } from "@app/services/app-connection/app-connection-service";
import { TAppConnection, TAppConnectionConfig } from "@app/services/app-connection/app-connection-types";
Expand All @@ -7,6 +8,11 @@ import {
getAwsAppConnectionListItem,
validateAwsConnectionCredentials
} from "@app/services/app-connection/aws";
import {
GcpConnectionMethod,
getGcpAppConnectionListItem,
validateGcpConnectionCredentials
} from "@app/services/app-connection/gcp";
import {
getGitHubConnectionListItem,
GitHubConnectionMethod,
Expand All @@ -15,7 +21,9 @@ import {
import { KmsDataKey } from "@app/services/kms/kms-types";

export const listAppConnectionOptions = () => {
return [getAwsAppConnectionListItem(), getGitHubConnectionListItem()].sort((a, b) => a.name.localeCompare(b.name));
return [getAwsAppConnectionListItem(), getGitHubConnectionListItem(), getGcpAppConnectionListItem()].sort((a, b) =>
a.name.localeCompare(b.name)
);
};

export const encryptAppConnectionCredentials = async ({
Expand Down Expand Up @@ -69,6 +77,8 @@ export const validateAppConnectionCredentials = async (
return validateAwsConnectionCredentials(appConnection);
case AppConnection.GitHub:
return validateGitHubConnectionCredentials(appConnection);
case AppConnection.GCP:
return validateGcpConnectionCredentials(appConnection);
default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Unhandled App Connection ${app}`);
Expand All @@ -85,6 +95,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
return "Access Key";
case AwsConnectionMethod.AssumeRole:
return "Assume Role";
case GcpConnectionMethod.ServiceAccountImpersonation:
return "Service Account Impersonation";
default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Unhandled App Connection Method: ${method}`);
Expand All @@ -101,6 +113,7 @@ export const decryptAppConnection = async (
encryptedCredentials: appConnection.encryptedCredentials,
orgId: appConnection.orgId,
kmsService
})
}),
credentialsHash: generateHash(appConnection.encryptedCredentials)
} as TAppConnection;
};
3 changes: 2 additions & 1 deletion backend/src/services/app-connection/app-connection-maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import { AppConnection } from "./app-connection-enums";

export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.AWS]: "AWS",
[AppConnection.GitHub]: "GitHub"
[AppConnection.GitHub]: "GitHub",
[AppConnection.GCP]: "GCP"
};
2 changes: 2 additions & 0 deletions backend/src/services/app-connection/app-connection-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const BaseAppConnectionSchema = AppConnectionsSchema.omit({
encryptedCredentials: true,
app: true,
method: true
}).extend({
credentialsHash: z.string().optional()
});

export const GenericCreateAppConnectionFieldsSchema = (app: AppConnection) =>
Expand Down
10 changes: 8 additions & 2 deletions backend/src/services/app-connection/app-connection-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";

import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { generateHash } from "@app/lib/crypto/encryption";
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
import { DiscriminativePick, OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
Expand All @@ -26,6 +27,8 @@ import { githubConnectionService } from "@app/services/app-connection/github/git
import { TKmsServiceFactory } from "@app/services/kms/kms-service";

import { TAppConnectionDALFactory } from "./app-connection-dal";
import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
import { gcpConnectionService } from "./gcp/gcp-connection-service";

export type TAppConnectionServiceFactoryDep = {
appConnectionDAL: TAppConnectionDALFactory;
Expand All @@ -37,7 +40,8 @@ export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServic

const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAppConnectionCredentials> = {
[AppConnection.AWS]: ValidateAwsConnectionCredentialsSchema,
[AppConnection.GitHub]: ValidateGitHubConnectionCredentialsSchema
[AppConnection.GitHub]: ValidateGitHubConnectionCredentialsSchema,
[AppConnection.GCP]: ValidateGcpConnectionCredentialsSchema
};

export const appConnectionServiceFactory = ({
Expand Down Expand Up @@ -182,6 +186,7 @@ export const appConnectionServiceFactory = ({

return {
...connection,
credentialsHash: generateHash(connection.encryptedCredentials),
credentials: validatedCredentials
};
});
Expand Down Expand Up @@ -382,6 +387,7 @@ export const appConnectionServiceFactory = ({
deleteAppConnection,
connectAppConnectionById,
listAvailableAppConnectionsForUser,
github: githubConnectionService(connectAppConnectionById)
github: githubConnectionService(connectAppConnectionById),
gcp: gcpConnectionService(connectAppConnectionById)
};
};
11 changes: 7 additions & 4 deletions backend/src/services/app-connection/app-connection-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import {
TValidateGitHubConnectionCredentials
} from "@app/services/app-connection/github";

export type TAppConnection = { id: string } & (TAwsConnection | TGitHubConnection);
import { TGcpConnection, TGcpConnectionConfig, TGcpConnectionInput, TValidateGcpConnectionCredentials } from "./gcp";

export type TAppConnectionInput = { id: string } & (TAwsConnectionInput | TGitHubConnectionInput);
export type TAppConnection = { id: string } & (TAwsConnection | TGitHubConnection | TGcpConnection);

export type TAppConnectionInput = { id: string } & (TAwsConnectionInput | TGitHubConnectionInput | TGcpConnectionInput);

export type TCreateAppConnectionDTO = Pick<
TAppConnectionInput,
Expand All @@ -24,8 +26,9 @@ export type TUpdateAppConnectionDTO = Partial<Omit<TCreateAppConnectionDTO, "met
connectionId: string;
};

export type TAppConnectionConfig = TAwsConnectionConfig | TGitHubConnectionConfig;
export type TAppConnectionConfig = TAwsConnectionConfig | TGitHubConnectionConfig | TGcpConnectionConfig;

export type TValidateAppConnectionCredentials =
| TValidateAwsConnectionCredentials
| TValidateGitHubConnectionCredentials;
| TValidateGitHubConnectionCredentials
| TValidateGcpConnectionCredentials;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum GcpConnectionMethod {
ServiceAccountImpersonation = "service-account-impersonation"
}
Loading

0 comments on commit c3d515b

Please sign in to comment.