Compare commits
2 Commits
misc/add-c
...
cli-ssh-ag
Author | SHA1 | Date | |
---|---|---|---|
f5a0641671 | |||
f97f98b2c3 |
17
.env.example
@ -88,20 +88,3 @@ PLAIN_WISH_LABEL_IDS=
|
|||||||
SSL_CLIENT_CERTIFICATE_HEADER_KEY=
|
SSL_CLIENT_CERTIFICATE_HEADER_KEY=
|
||||||
|
|
||||||
ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT=true
|
ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT=true
|
||||||
|
|
||||||
# App Connections
|
|
||||||
|
|
||||||
# aws assume-role
|
|
||||||
INF_APP_CONNECTION_AWS_ACCESS_KEY_ID=
|
|
||||||
INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY=
|
|
||||||
|
|
||||||
# github oauth
|
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID=
|
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET=
|
|
||||||
|
|
||||||
#github app
|
|
||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID=
|
|
||||||
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=
|
|
2
backend/src/@types/fastify.d.ts
vendored
@ -36,7 +36,6 @@ import { TSshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-cert
|
|||||||
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
|
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
|
||||||
import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
|
import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
|
||||||
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||||
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
|
|
||||||
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
|
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
|
||||||
import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
|
import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
|
||||||
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
|
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
|
||||||
@ -209,7 +208,6 @@ declare module "fastify" {
|
|||||||
externalGroupOrgRoleMapping: TExternalGroupOrgRoleMappingServiceFactory;
|
externalGroupOrgRoleMapping: TExternalGroupOrgRoleMappingServiceFactory;
|
||||||
projectTemplate: TProjectTemplateServiceFactory;
|
projectTemplate: TProjectTemplateServiceFactory;
|
||||||
totp: TTotpServiceFactory;
|
totp: TTotpServiceFactory;
|
||||||
appConnection: TAppConnectionServiceFactory;
|
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
6
backend/src/@types/knex.d.ts
vendored
@ -363,7 +363,6 @@ import {
|
|||||||
TWorkflowIntegrationsInsert,
|
TWorkflowIntegrationsInsert,
|
||||||
TWorkflowIntegrationsUpdate
|
TWorkflowIntegrationsUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TAppConnections, TAppConnectionsInsert, TAppConnectionsUpdate } from "@app/db/schemas/app-connections";
|
|
||||||
import {
|
import {
|
||||||
TExternalGroupOrgRoleMappings,
|
TExternalGroupOrgRoleMappings,
|
||||||
TExternalGroupOrgRoleMappingsInsert,
|
TExternalGroupOrgRoleMappingsInsert,
|
||||||
@ -887,10 +886,5 @@ declare module "knex/types/tables" {
|
|||||||
TProjectSplitBackfillIdsInsert,
|
TProjectSplitBackfillIdsInsert,
|
||||||
TProjectSplitBackfillIdsUpdate
|
TProjectSplitBackfillIdsUpdate
|
||||||
>;
|
>;
|
||||||
[TableName.AppConnection]: KnexOriginal.CompositeTableType<
|
|
||||||
TAppConnections,
|
|
||||||
TAppConnectionsInsert,
|
|
||||||
TAppConnectionsUpdate
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { TableName } from "@app/db/schemas";
|
|
||||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "@app/db/utils";
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
if (!(await knex.schema.hasTable(TableName.AppConnection))) {
|
|
||||||
await knex.schema.createTable(TableName.AppConnection, (t) => {
|
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
|
||||||
t.string("name", 32).notNullable();
|
|
||||||
t.string("description");
|
|
||||||
t.string("app").notNullable();
|
|
||||||
t.string("method").notNullable();
|
|
||||||
t.binary("encryptedCredentials").notNullable();
|
|
||||||
t.integer("version").defaultTo(1).notNullable();
|
|
||||||
t.uuid("orgId").notNullable();
|
|
||||||
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
|
||||||
t.timestamps(true, true, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
await createOnUpdateTrigger(knex, TableName.AppConnection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
await knex.schema.dropTableIfExists(TableName.AppConnection);
|
|
||||||
await dropOnUpdateTrigger(knex, TableName.AppConnection);
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
// Code generated by automation script, DO NOT EDIT.
|
|
||||||
// Automated by pulling database and generating zod schema
|
|
||||||
// To update. Just run npm run generate:schema
|
|
||||||
// Written by akhilmhdh.
|
|
||||||
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { zodBuffer } from "@app/lib/zod";
|
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
|
||||||
|
|
||||||
export const AppConnectionsSchema = z.object({
|
|
||||||
id: z.string().uuid(),
|
|
||||||
name: z.string(),
|
|
||||||
description: z.string().nullable().optional(),
|
|
||||||
app: z.string(),
|
|
||||||
method: z.string(),
|
|
||||||
encryptedCredentials: zodBuffer,
|
|
||||||
version: z.number().default(1),
|
|
||||||
orgId: z.string().uuid(),
|
|
||||||
createdAt: z.date(),
|
|
||||||
updatedAt: z.date()
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TAppConnections = z.infer<typeof AppConnectionsSchema>;
|
|
||||||
export type TAppConnectionsInsert = Omit<z.input<typeof AppConnectionsSchema>, TImmutableDBKeys>;
|
|
||||||
export type TAppConnectionsUpdate = Partial<Omit<z.input<typeof AppConnectionsSchema>, TImmutableDBKeys>>;
|
|
@ -129,8 +129,7 @@ export enum TableName {
|
|||||||
KmsKeyVersion = "kms_key_versions",
|
KmsKeyVersion = "kms_key_versions",
|
||||||
WorkflowIntegrations = "workflow_integrations",
|
WorkflowIntegrations = "workflow_integrations",
|
||||||
SlackIntegrations = "slack_integrations",
|
SlackIntegrations = "slack_integrations",
|
||||||
ProjectSlackConfigs = "project_slack_configs",
|
ProjectSlackConfigs = "project_slack_configs"
|
||||||
AppConnection = "app_connections"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
||||||
|
@ -23,7 +23,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
"Please choose a different slug, the slug you have entered is reserved"
|
"Please choose a different slug, the slug you have entered is reserved"
|
||||||
),
|
),
|
||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
description: z.string().trim().nullish(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array()
|
permissions: z.any().array()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@ -95,7 +95,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
name: z.string().trim().optional(),
|
name: z.string().trim().optional(),
|
||||||
description: z.string().trim().nullish(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array().optional()
|
permissions: z.any().array().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
@ -39,7 +39,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
)
|
)
|
||||||
.describe(PROJECT_ROLE.CREATE.slug),
|
.describe(PROJECT_ROLE.CREATE.slug),
|
||||||
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
||||||
description: z.string().trim().nullish().describe(PROJECT_ROLE.CREATE.description),
|
description: z.string().trim().optional().describe(PROJECT_ROLE.CREATE.description),
|
||||||
permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.CREATE.permissions)
|
permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.CREATE.permissions)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@ -95,7 +95,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
.describe(PROJECT_ROLE.UPDATE.slug)
|
.describe(PROJECT_ROLE.UPDATE.slug)
|
||||||
.optional(),
|
.optional(),
|
||||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||||
description: z.string().trim().nullish().describe(PROJECT_ROLE.UPDATE.description),
|
description: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.description),
|
||||||
permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
@ -36,7 +36,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
)
|
)
|
||||||
.describe(PROJECT_ROLE.CREATE.slug),
|
.describe(PROJECT_ROLE.CREATE.slug),
|
||||||
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
||||||
description: z.string().trim().nullish().describe(PROJECT_ROLE.CREATE.description),
|
description: z.string().trim().optional().describe(PROJECT_ROLE.CREATE.description),
|
||||||
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_ROLE.CREATE.permissions)
|
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_ROLE.CREATE.permissions)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@ -91,7 +91,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional()
|
.optional()
|
||||||
.describe(PROJECT_ROLE.UPDATE.slug),
|
.describe(PROJECT_ROLE.UPDATE.slug),
|
||||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||||
description: z.string().trim().nullish().describe(PROJECT_ROLE.UPDATE.description),
|
description: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.description),
|
||||||
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
@ -6,8 +6,6 @@ import { SshCaStatus, SshCertType } from "@app/ee/services/ssh/ssh-certificate-a
|
|||||||
import { SshCertTemplateStatus } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-types";
|
import { SshCertTemplateStatus } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-types";
|
||||||
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
|
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
import { TCreateAppConnectionDTO, TUpdateAppConnectionDTO } from "@app/services/app-connection/app-connection-types";
|
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
||||||
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
@ -224,12 +222,7 @@ export enum EventType {
|
|||||||
CREATE_PROJECT_TEMPLATE = "create-project-template",
|
CREATE_PROJECT_TEMPLATE = "create-project-template",
|
||||||
UPDATE_PROJECT_TEMPLATE = "update-project-template",
|
UPDATE_PROJECT_TEMPLATE = "update-project-template",
|
||||||
DELETE_PROJECT_TEMPLATE = "delete-project-template",
|
DELETE_PROJECT_TEMPLATE = "delete-project-template",
|
||||||
APPLY_PROJECT_TEMPLATE = "apply-project-template",
|
APPLY_PROJECT_TEMPLATE = "apply-project-template"
|
||||||
GET_APP_CONNECTIONS = "get-app-connections",
|
|
||||||
GET_APP_CONNECTION = "get-app-connection",
|
|
||||||
CREATE_APP_CONNECTION = "create-app-connection",
|
|
||||||
UPDATE_APP_CONNECTION = "update-app-connection",
|
|
||||||
DELETE_APP_CONNECTION = "delete-app-connection"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
@ -1874,39 +1867,6 @@ interface ApplyProjectTemplateEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetAppConnectionsEvent {
|
|
||||||
type: EventType.GET_APP_CONNECTIONS;
|
|
||||||
metadata: {
|
|
||||||
app?: AppConnection;
|
|
||||||
count: number;
|
|
||||||
connectionIds: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GetAppConnectionEvent {
|
|
||||||
type: EventType.GET_APP_CONNECTION;
|
|
||||||
metadata: {
|
|
||||||
connectionId: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CreateAppConnectionEvent {
|
|
||||||
type: EventType.CREATE_APP_CONNECTION;
|
|
||||||
metadata: Omit<TCreateAppConnectionDTO, "credentials"> & { connectionId: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UpdateAppConnectionEvent {
|
|
||||||
type: EventType.UPDATE_APP_CONNECTION;
|
|
||||||
metadata: Omit<TUpdateAppConnectionDTO, "credentials"> & { connectionId: string; credentialsUpdated: boolean };
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DeleteAppConnectionEvent {
|
|
||||||
type: EventType.DELETE_APP_CONNECTION;
|
|
||||||
metadata: {
|
|
||||||
connectionId: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
@ -2078,9 +2038,4 @@ export type Event =
|
|||||||
| CreateProjectTemplateEvent
|
| CreateProjectTemplateEvent
|
||||||
| UpdateProjectTemplateEvent
|
| UpdateProjectTemplateEvent
|
||||||
| DeleteProjectTemplateEvent
|
| DeleteProjectTemplateEvent
|
||||||
| ApplyProjectTemplateEvent
|
| ApplyProjectTemplateEvent;
|
||||||
| GetAppConnectionsEvent
|
|
||||||
| GetAppConnectionEvent
|
|
||||||
| CreateAppConnectionEvent
|
|
||||||
| UpdateAppConnectionEvent
|
|
||||||
| DeleteAppConnectionEvent;
|
|
||||||
|
@ -49,8 +49,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
},
|
},
|
||||||
pkiEst: false,
|
pkiEst: false,
|
||||||
enforceMfa: false,
|
enforceMfa: false,
|
||||||
projectTemplates: false,
|
projectTemplates: false
|
||||||
appConnections: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
||||||
|
@ -67,7 +67,6 @@ export type TFeatureSet = {
|
|||||||
pkiEst: boolean;
|
pkiEst: boolean;
|
||||||
enforceMfa: boolean;
|
enforceMfa: boolean;
|
||||||
projectTemplates: false;
|
projectTemplates: false;
|
||||||
appConnections: false; // TODO: remove once live
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgPlansTableDTO = {
|
export type TOrgPlansTableDTO = {
|
||||||
|
@ -27,8 +27,7 @@ export enum OrgPermissionSubjects {
|
|||||||
Kms = "kms",
|
Kms = "kms",
|
||||||
AdminConsole = "organization-admin-console",
|
AdminConsole = "organization-admin-console",
|
||||||
AuditLogs = "audit-logs",
|
AuditLogs = "audit-logs",
|
||||||
ProjectTemplates = "project-templates",
|
ProjectTemplates = "project-templates"
|
||||||
AppConnections = "app-connections"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OrgPermissionSet =
|
export type OrgPermissionSet =
|
||||||
@ -47,7 +46,6 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.AppConnections]
|
|
||||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
|
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
|
||||||
|
|
||||||
const buildAdminPermission = () => {
|
const buildAdminPermission = () => {
|
||||||
@ -125,11 +123,6 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.AppConnections);
|
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.AppConnections);
|
|
||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.AppConnections);
|
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.AppConnections);
|
|
||||||
|
|
||||||
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
@ -160,8 +153,6 @@ const buildMemberPermission = () => {
|
|||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.AppConnections);
|
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import { ProjectType } from "@app/db/schemas";
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { TSshCertificateAuthorityDALFactory } from "../ssh/ssh-certificate-authority-dal";
|
import { TSshCertificateAuthorityDALFactory } from "../ssh/ssh-certificate-authority-dal";
|
||||||
import { TSshCertificateTemplateDALFactory } from "./ssh-certificate-template-dal";
|
import { TSshCertificateTemplateDALFactory } from "./ssh-certificate-template-dal";
|
||||||
import {
|
import {
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
import { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps";
|
|
||||||
|
|
||||||
export const GROUPS = {
|
export const GROUPS = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
name: "The name of the group to create.",
|
name: "The name of the group to create.",
|
||||||
@ -1608,34 +1605,3 @@ export const ProjectTemplates = {
|
|||||||
templateId: "The ID of the project template to be deleted."
|
templateId: "The ID of the project template to be deleted."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AppConnections = {
|
|
||||||
GET_BY_ID: (app: AppConnection) => ({
|
|
||||||
connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} Connection to retrieve.`
|
|
||||||
}),
|
|
||||||
GET_BY_NAME: (app: AppConnection) => ({
|
|
||||||
connectionName: `The name of the ${APP_CONNECTION_NAME_MAP[app]} Connection to retrieve.`
|
|
||||||
}),
|
|
||||||
CREATE: (app: AppConnection) => {
|
|
||||||
const appName = APP_CONNECTION_NAME_MAP[app];
|
|
||||||
return {
|
|
||||||
name: `The name of the ${appName} Connection to create. Must be slug-friendly.`,
|
|
||||||
description: `An optional description for the ${appName} Connection.`,
|
|
||||||
credentials: `The credentials used to connect with ${appName}.`,
|
|
||||||
method: `The method used to authenticate with ${appName}.`
|
|
||||||
};
|
|
||||||
},
|
|
||||||
UPDATE: (app: AppConnection) => {
|
|
||||||
const appName = APP_CONNECTION_NAME_MAP[app];
|
|
||||||
return {
|
|
||||||
connectionId: `The ID of the ${appName} Connection to be updated.`,
|
|
||||||
name: `The updated name of the ${appName} Connection. Must be slug-friendly.`,
|
|
||||||
description: `The updated description of the ${appName} Connection.`,
|
|
||||||
credentials: `The credentials used to connect with ${appName}.`,
|
|
||||||
method: `The method used to authenticate with ${appName}.`
|
|
||||||
};
|
|
||||||
},
|
|
||||||
DELETE: (app: AppConnection) => ({
|
|
||||||
connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} connection to be deleted.`
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
@ -180,24 +180,7 @@ const envSchema = z
|
|||||||
HSM_SLOT: z.coerce.number().optional().default(0),
|
HSM_SLOT: z.coerce.number().optional().default(0),
|
||||||
|
|
||||||
USE_PG_QUEUE: zodStrBool.default("false"),
|
USE_PG_QUEUE: zodStrBool.default("false"),
|
||||||
SHOULD_INIT_PG_QUEUE: zodStrBool.default("false"),
|
SHOULD_INIT_PG_QUEUE: zodStrBool.default("false")
|
||||||
|
|
||||||
/* App Connections ----------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
// aws
|
|
||||||
INF_APP_CONNECTION_AWS_ACCESS_KEY_ID: zpStr(z.string().optional()),
|
|
||||||
INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY: zpStr(z.string().optional()),
|
|
||||||
|
|
||||||
// github oauth
|
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID: zpStr(z.string().optional()),
|
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET: zpStr(z.string().optional()),
|
|
||||||
|
|
||||||
// github app
|
|
||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID: zpStr(z.string().optional()),
|
|
||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET: zpStr(z.string().optional()),
|
|
||||||
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY: zpStr(z.string().optional()),
|
|
||||||
INF_APP_CONNECTION_GITHUB_APP_SLUG: zpStr(z.string().optional()),
|
|
||||||
INF_APP_CONNECTION_GITHUB_APP_ID: zpStr(z.string().optional())
|
|
||||||
})
|
})
|
||||||
// To ensure that basic encryption is always possible.
|
// To ensure that basic encryption is always possible.
|
||||||
.refine(
|
.refine(
|
||||||
|
@ -14,5 +14,3 @@ export const prefixWithSlash = (str: string) => {
|
|||||||
if (str.startsWith("/")) return str;
|
if (str.startsWith("/")) return str;
|
||||||
return `/${str}`;
|
return `/${str}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const startsWithVowel = (str: string) => /^[aeiou]/i.test(str);
|
|
||||||
|
@ -43,8 +43,6 @@ export type RequiredKeys<T> = {
|
|||||||
|
|
||||||
export type PickRequired<T> = Pick<T, RequiredKeys<T>>;
|
export type PickRequired<T> = Pick<T, RequiredKeys<T>>;
|
||||||
|
|
||||||
export type DiscriminativePick<T, K extends keyof T> = T extends unknown ? Pick<T, K> : never;
|
|
||||||
|
|
||||||
export enum EnforcementLevel {
|
export enum EnforcementLevel {
|
||||||
Hard = "hard",
|
Hard = "hard",
|
||||||
Soft = "soft"
|
Soft = "soft"
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { ForbiddenError, PureAbility } from "@casl/ability";
|
import { ForbiddenError, PureAbility } from "@casl/ability";
|
||||||
import opentelemetry from "@opentelemetry/api";
|
|
||||||
import fastifyPlugin from "fastify-plugin";
|
import fastifyPlugin from "fastify-plugin";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import {
|
import {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
DatabaseError,
|
DatabaseError,
|
||||||
@ -37,30 +35,8 @@ enum HttpStatusCodes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
|
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
|
||||||
const appCfg = getConfig();
|
|
||||||
|
|
||||||
const apiMeter = opentelemetry.metrics.getMeter("API");
|
|
||||||
const errorHistogram = apiMeter.createHistogram("API_errors", {
|
|
||||||
description: "API errors by type, status code, and name",
|
|
||||||
unit: "1"
|
|
||||||
});
|
|
||||||
|
|
||||||
server.setErrorHandler((error, req, res) => {
|
server.setErrorHandler((error, req, res) => {
|
||||||
req.log.error(error);
|
req.log.error(error);
|
||||||
if (appCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) {
|
|
||||||
const { method } = req;
|
|
||||||
const route = req.routerPath;
|
|
||||||
const errorType =
|
|
||||||
error instanceof jwt.JsonWebTokenError ? "TokenError" : error.constructor.name || "UnknownError";
|
|
||||||
|
|
||||||
errorHistogram.record(1, {
|
|
||||||
route,
|
|
||||||
method,
|
|
||||||
type: errorType,
|
|
||||||
name: error.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof BadRequestError) {
|
if (error instanceof BadRequestError) {
|
||||||
void res
|
void res
|
||||||
.status(HttpStatusCodes.BadRequest)
|
.status(HttpStatusCodes.BadRequest)
|
||||||
@ -76,20 +52,13 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
|||||||
message: error.message,
|
message: error.message,
|
||||||
error: error.name
|
error: error.name
|
||||||
});
|
});
|
||||||
} else if (error instanceof DatabaseError) {
|
} else if (error instanceof DatabaseError || error instanceof InternalServerError) {
|
||||||
void res.status(HttpStatusCodes.InternalServerError).send({
|
void res.status(HttpStatusCodes.InternalServerError).send({
|
||||||
reqId: req.id,
|
reqId: req.id,
|
||||||
statusCode: HttpStatusCodes.InternalServerError,
|
statusCode: HttpStatusCodes.InternalServerError,
|
||||||
message: "Something went wrong",
|
message: "Something went wrong",
|
||||||
error: error.name
|
error: error.name
|
||||||
});
|
});
|
||||||
} else if (error instanceof InternalServerError) {
|
|
||||||
void res.status(HttpStatusCodes.InternalServerError).send({
|
|
||||||
reqId: req.id,
|
|
||||||
statusCode: HttpStatusCodes.InternalServerError,
|
|
||||||
message: error.message ?? "Something went wrong",
|
|
||||||
error: error.name
|
|
||||||
});
|
|
||||||
} else if (error instanceof GatewayTimeoutError) {
|
} else if (error instanceof GatewayTimeoutError) {
|
||||||
void res.status(HttpStatusCodes.GatewayTimeout).send({
|
void res.status(HttpStatusCodes.GatewayTimeout).send({
|
||||||
reqId: req.id,
|
reqId: req.id,
|
||||||
|
@ -91,8 +91,6 @@ import { readLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { accessTokenQueueServiceFactory } from "@app/services/access-token-queue/access-token-queue";
|
import { accessTokenQueueServiceFactory } from "@app/services/access-token-queue/access-token-queue";
|
||||||
import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal";
|
import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal";
|
||||||
import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||||
import { appConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
|
||||||
import { appConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
|
|
||||||
import { authDALFactory } from "@app/services/auth/auth-dal";
|
import { authDALFactory } from "@app/services/auth/auth-dal";
|
||||||
import { authLoginServiceFactory } from "@app/services/auth/auth-login-service";
|
import { authLoginServiceFactory } from "@app/services/auth/auth-login-service";
|
||||||
import { authPaswordServiceFactory } from "@app/services/auth/auth-password-service";
|
import { authPaswordServiceFactory } from "@app/services/auth/auth-password-service";
|
||||||
@ -316,7 +314,6 @@ export const registerRoutes = async (
|
|||||||
const auditLogStreamDAL = auditLogStreamDALFactory(db);
|
const auditLogStreamDAL = auditLogStreamDALFactory(db);
|
||||||
const trustedIpDAL = trustedIpDALFactory(db);
|
const trustedIpDAL = trustedIpDALFactory(db);
|
||||||
const telemetryDAL = telemetryDALFactory(db);
|
const telemetryDAL = telemetryDALFactory(db);
|
||||||
const appConnectionDAL = appConnectionDALFactory(db);
|
|
||||||
|
|
||||||
// ee db layer ops
|
// ee db layer ops
|
||||||
const permissionDAL = permissionDALFactory(db);
|
const permissionDAL = permissionDALFactory(db);
|
||||||
@ -1355,13 +1352,6 @@ export const registerRoutes = async (
|
|||||||
externalGroupOrgRoleMappingDAL
|
externalGroupOrgRoleMappingDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const appConnectionService = appConnectionServiceFactory({
|
|
||||||
appConnectionDAL,
|
|
||||||
permissionService,
|
|
||||||
kmsService,
|
|
||||||
licenseService
|
|
||||||
});
|
|
||||||
|
|
||||||
await superAdminService.initServerCfg();
|
await superAdminService.initServerCfg();
|
||||||
|
|
||||||
// setup the communication with license key server
|
// setup the communication with license key server
|
||||||
@ -1458,8 +1448,7 @@ export const registerRoutes = async (
|
|||||||
migration: migrationService,
|
migration: migrationService,
|
||||||
externalGroupOrgRoleMapping: externalGroupOrgRoleMappingService,
|
externalGroupOrgRoleMapping: externalGroupOrgRoleMappingService,
|
||||||
projectTemplate: projectTemplateService,
|
projectTemplate: projectTemplateService,
|
||||||
totp: totpService,
|
totp: totpService
|
||||||
appConnection: appConnectionService
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const cronJobs: CronJob[] = [];
|
const cronJobs: CronJob[] = [];
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
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 { 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
|
|
||||||
]);
|
|
||||||
|
|
||||||
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|
||||||
AwsConnectionListItemSchema,
|
|
||||||
GitHubConnectionListItemSchema
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: "/options",
|
|
||||||
config: {
|
|
||||||
rateLimit: readLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
description: "List the available App Connection Options.",
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
appConnectionOptions: AppConnectionOptionsSchema.array()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
||||||
handler: () => {
|
|
||||||
const appConnectionOptions = server.services.appConnection.listAppConnectionOptions();
|
|
||||||
return { appConnectionOptions };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: "/",
|
|
||||||
config: {
|
|
||||||
rateLimit: readLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
description: "List all the App Connections for the current organization.",
|
|
||||||
response: {
|
|
||||||
200: z.object({ appConnections: SanitizedAppConnectionSchema.array() })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const appConnections = await server.services.appConnection.listAppConnectionsByOrg(req.permission);
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
|
||||||
...req.auditLogInfo,
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
event: {
|
|
||||||
type: EventType.GET_APP_CONNECTIONS,
|
|
||||||
metadata: {
|
|
||||||
count: appConnections.length,
|
|
||||||
connectionIds: appConnections.map((connection) => connection.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { appConnections };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,274 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
|
||||||
import { AppConnections } from "@app/lib/api-docs";
|
|
||||||
import { startsWithVowel } from "@app/lib/fn";
|
|
||||||
import { readLimit, writeLimit } 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 { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps";
|
|
||||||
import { TAppConnection, TAppConnectionInput } from "@app/services/app-connection/app-connection-types";
|
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
|
||||||
|
|
||||||
export const registerAppConnectionEndpoints = <T extends TAppConnection, I extends TAppConnectionInput>({
|
|
||||||
server,
|
|
||||||
app,
|
|
||||||
createSchema,
|
|
||||||
updateSchema,
|
|
||||||
responseSchema
|
|
||||||
}: {
|
|
||||||
app: AppConnection;
|
|
||||||
server: FastifyZodProvider;
|
|
||||||
createSchema: z.ZodType<{
|
|
||||||
name: string;
|
|
||||||
method: I["method"];
|
|
||||||
credentials: I["credentials"];
|
|
||||||
description?: string | null;
|
|
||||||
}>;
|
|
||||||
updateSchema: z.ZodType<{ name?: string; credentials?: I["credentials"]; description?: string | null }>;
|
|
||||||
responseSchema: z.ZodTypeAny;
|
|
||||||
}) => {
|
|
||||||
const appName = APP_CONNECTION_NAME_MAP[app];
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: `/`,
|
|
||||||
config: {
|
|
||||||
rateLimit: readLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
description: `List the ${appName} Connections for the current organization.`,
|
|
||||||
response: {
|
|
||||||
200: z.object({ appConnections: responseSchema.array() })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const appConnections = (await server.services.appConnection.listAppConnectionsByOrg(req.permission, app)) as T[];
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
|
||||||
...req.auditLogInfo,
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
event: {
|
|
||||||
type: EventType.GET_APP_CONNECTIONS,
|
|
||||||
metadata: {
|
|
||||||
app,
|
|
||||||
count: appConnections.length,
|
|
||||||
connectionIds: appConnections.map((connection) => connection.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { appConnections };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: "/:connectionId",
|
|
||||||
config: {
|
|
||||||
rateLimit: readLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
description: `Get the specified ${appName} Connection by ID.`,
|
|
||||||
params: z.object({
|
|
||||||
connectionId: z.string().uuid().describe(AppConnections.GET_BY_ID(app).connectionId)
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({ appConnection: responseSchema })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const { connectionId } = req.params;
|
|
||||||
|
|
||||||
const appConnection = (await server.services.appConnection.findAppConnectionById(
|
|
||||||
app,
|
|
||||||
connectionId,
|
|
||||||
req.permission
|
|
||||||
)) as T;
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
|
||||||
...req.auditLogInfo,
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
event: {
|
|
||||||
type: EventType.GET_APP_CONNECTION,
|
|
||||||
metadata: {
|
|
||||||
connectionId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { appConnection };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: `/name/:connectionName`,
|
|
||||||
config: {
|
|
||||||
rateLimit: readLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
description: `Get the specified ${appName} Connection by name.`,
|
|
||||||
params: z.object({
|
|
||||||
connectionName: z
|
|
||||||
.string()
|
|
||||||
.min(0, "Connection name required")
|
|
||||||
.describe(AppConnections.GET_BY_NAME(app).connectionName)
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({ appConnection: responseSchema })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const { connectionName } = req.params;
|
|
||||||
|
|
||||||
const appConnection = (await server.services.appConnection.findAppConnectionByName(
|
|
||||||
app,
|
|
||||||
connectionName,
|
|
||||||
req.permission
|
|
||||||
)) as T;
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
|
||||||
...req.auditLogInfo,
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
event: {
|
|
||||||
type: EventType.GET_APP_CONNECTION,
|
|
||||||
metadata: {
|
|
||||||
connectionId: appConnection.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { appConnection };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "POST",
|
|
||||||
url: "/",
|
|
||||||
config: {
|
|
||||||
rateLimit: writeLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
description: `Create ${
|
|
||||||
startsWithVowel(appName) ? "an" : "a"
|
|
||||||
} ${appName} Connection for the current organization.`,
|
|
||||||
body: createSchema,
|
|
||||||
response: {
|
|
||||||
200: z.object({ appConnection: responseSchema })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const { name, method, credentials, description } = req.body;
|
|
||||||
|
|
||||||
const appConnection = (await server.services.appConnection.createAppConnection(
|
|
||||||
{ name, method, app, credentials, description },
|
|
||||||
req.permission
|
|
||||||
)) as TAppConnection;
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
|
||||||
...req.auditLogInfo,
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
event: {
|
|
||||||
type: EventType.CREATE_APP_CONNECTION,
|
|
||||||
metadata: {
|
|
||||||
name,
|
|
||||||
method,
|
|
||||||
app,
|
|
||||||
connectionId: appConnection.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { appConnection };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "PATCH",
|
|
||||||
url: "/:connectionId",
|
|
||||||
config: {
|
|
||||||
rateLimit: writeLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
description: `Update the specified ${appName} Connection.`,
|
|
||||||
params: z.object({
|
|
||||||
connectionId: z.string().uuid().describe(AppConnections.UPDATE(app).connectionId)
|
|
||||||
}),
|
|
||||||
body: updateSchema,
|
|
||||||
response: {
|
|
||||||
200: z.object({ appConnection: responseSchema })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const { name, credentials, description } = req.body;
|
|
||||||
const { connectionId } = req.params;
|
|
||||||
|
|
||||||
const appConnection = (await server.services.appConnection.updateAppConnection(
|
|
||||||
{ name, credentials, connectionId, description },
|
|
||||||
req.permission
|
|
||||||
)) as T;
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
|
||||||
...req.auditLogInfo,
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
event: {
|
|
||||||
type: EventType.UPDATE_APP_CONNECTION,
|
|
||||||
metadata: {
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
credentialsUpdated: Boolean(credentials),
|
|
||||||
connectionId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { appConnection };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "DELETE",
|
|
||||||
url: `/:connectionId`,
|
|
||||||
config: {
|
|
||||||
rateLimit: writeLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
description: `Delete the specified ${appName} Connection.`,
|
|
||||||
params: z.object({
|
|
||||||
connectionId: z.string().uuid().describe(AppConnections.DELETE(app).connectionId)
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({ appConnection: responseSchema })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const { connectionId } = req.params;
|
|
||||||
|
|
||||||
const appConnection = (await server.services.appConnection.deleteAppConnection(
|
|
||||||
app,
|
|
||||||
connectionId,
|
|
||||||
req.permission
|
|
||||||
)) as T;
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
|
||||||
...req.auditLogInfo,
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
event: {
|
|
||||||
type: EventType.DELETE_APP_CONNECTION,
|
|
||||||
metadata: {
|
|
||||||
connectionId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { appConnection };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
import {
|
|
||||||
CreateAwsConnectionSchema,
|
|
||||||
SanitizedAwsConnectionSchema,
|
|
||||||
UpdateAwsConnectionSchema
|
|
||||||
} from "@app/services/app-connection/aws";
|
|
||||||
|
|
||||||
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
|
||||||
|
|
||||||
export const registerAwsConnectionRouter = async (server: FastifyZodProvider) =>
|
|
||||||
registerAppConnectionEndpoints({
|
|
||||||
app: AppConnection.AWS,
|
|
||||||
server,
|
|
||||||
responseSchema: SanitizedAwsConnectionSchema,
|
|
||||||
createSchema: CreateAwsConnectionSchema,
|
|
||||||
updateSchema: UpdateAwsConnectionSchema
|
|
||||||
});
|
|
@ -1,17 +0,0 @@
|
|||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
import {
|
|
||||||
CreateGitHubConnectionSchema,
|
|
||||||
SanitizedGitHubConnectionSchema,
|
|
||||||
UpdateGitHubConnectionSchema
|
|
||||||
} from "@app/services/app-connection/github";
|
|
||||||
|
|
||||||
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
|
||||||
|
|
||||||
export const registerGitHubConnectionRouter = async (server: FastifyZodProvider) =>
|
|
||||||
registerAppConnectionEndpoints({
|
|
||||||
app: AppConnection.GitHub,
|
|
||||||
server,
|
|
||||||
responseSchema: SanitizedGitHubConnectionSchema,
|
|
||||||
createSchema: CreateGitHubConnectionSchema,
|
|
||||||
updateSchema: UpdateGitHubConnectionSchema
|
|
||||||
});
|
|
@ -1,8 +0,0 @@
|
|||||||
import { registerAwsConnectionRouter } from "@app/server/routes/v1/app-connection-routers/apps/aws-connection-router";
|
|
||||||
import { registerGitHubConnectionRouter } from "@app/server/routes/v1/app-connection-routers/apps/github-connection-router";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
|
|
||||||
export const APP_CONNECTION_REGISTER_MAP: Record<AppConnection, (server: FastifyZodProvider) => Promise<void>> = {
|
|
||||||
[AppConnection.AWS]: registerAwsConnectionRouter,
|
|
||||||
[AppConnection.GitHub]: registerGitHubConnectionRouter
|
|
||||||
};
|
|
@ -1,2 +0,0 @@
|
|||||||
export * from "./app-connection-router";
|
|
||||||
export * from "./apps";
|
|
@ -1,4 +1,3 @@
|
|||||||
import { APP_CONNECTION_REGISTER_MAP, registerAppConnectionRouter } from "@app/server/routes/v1/app-connection-routers";
|
|
||||||
import { registerCmekRouter } from "@app/server/routes/v1/cmek-router";
|
import { registerCmekRouter } from "@app/server/routes/v1/cmek-router";
|
||||||
import { registerDashboardRouter } from "@app/server/routes/v1/dashboard-router";
|
import { registerDashboardRouter } from "@app/server/routes/v1/dashboard-router";
|
||||||
|
|
||||||
@ -111,14 +110,4 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerDashboardRouter, { prefix: "/dashboard" });
|
await server.register(registerDashboardRouter, { prefix: "/dashboard" });
|
||||||
await server.register(registerCmekRouter, { prefix: "/kms" });
|
await server.register(registerCmekRouter, { prefix: "/kms" });
|
||||||
await server.register(registerExternalGroupOrgRoleMappingRouter, { prefix: "/external-group-mappings" });
|
await server.register(registerExternalGroupOrgRoleMappingRouter, { prefix: "/external-group-mappings" });
|
||||||
|
|
||||||
await server.register(
|
|
||||||
async (appConnectionsRouter) => {
|
|
||||||
await appConnectionsRouter.register(registerAppConnectionRouter);
|
|
||||||
for await (const [app, router] of Object.entries(APP_CONNECTION_REGISTER_MAP)) {
|
|
||||||
await appConnectionsRouter.register(router, { prefix: `/${app}` });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ prefix: "/app-connections" }
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import { TDbClient } from "@app/db";
|
|
||||||
import { TableName } from "@app/db/schemas";
|
|
||||||
import { ormify } from "@app/lib/knex";
|
|
||||||
|
|
||||||
export type TAppConnectionDALFactory = ReturnType<typeof appConnectionDALFactory>;
|
|
||||||
|
|
||||||
export const appConnectionDALFactory = (db: TDbClient) => {
|
|
||||||
const appConnectionOrm = ormify(db, TableName.AppConnection);
|
|
||||||
|
|
||||||
return { ...appConnectionOrm };
|
|
||||||
};
|
|
@ -1,4 +0,0 @@
|
|||||||
export enum AppConnection {
|
|
||||||
GitHub = "github",
|
|
||||||
AWS = "aws"
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
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";
|
|
||||||
import {
|
|
||||||
AwsConnectionMethod,
|
|
||||||
getAwsAppConnectionListItem,
|
|
||||||
validateAwsConnectionCredentials
|
|
||||||
} from "@app/services/app-connection/aws";
|
|
||||||
import {
|
|
||||||
getGitHubConnectionListItem,
|
|
||||||
GitHubConnectionMethod,
|
|
||||||
validateGitHubConnectionCredentials
|
|
||||||
} from "@app/services/app-connection/github";
|
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
|
||||||
|
|
||||||
export const listAppConnectionOptions = () => {
|
|
||||||
return [getAwsAppConnectionListItem(), getGitHubConnectionListItem()].sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const encryptAppConnectionCredentials = async ({
|
|
||||||
orgId,
|
|
||||||
credentials,
|
|
||||||
kmsService
|
|
||||||
}: {
|
|
||||||
orgId: string;
|
|
||||||
credentials: TAppConnection["credentials"];
|
|
||||||
kmsService: TAppConnectionServiceFactoryDep["kmsService"];
|
|
||||||
}) => {
|
|
||||||
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
|
||||||
type: KmsDataKey.Organization,
|
|
||||||
orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
const { cipherTextBlob: encryptedCredentialsBlob } = encryptor({
|
|
||||||
plainText: Buffer.from(JSON.stringify(credentials))
|
|
||||||
});
|
|
||||||
|
|
||||||
return encryptedCredentialsBlob;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const decryptAppConnectionCredentials = async ({
|
|
||||||
orgId,
|
|
||||||
encryptedCredentials,
|
|
||||||
kmsService
|
|
||||||
}: {
|
|
||||||
orgId: string;
|
|
||||||
encryptedCredentials: Buffer;
|
|
||||||
kmsService: TAppConnectionServiceFactoryDep["kmsService"];
|
|
||||||
}) => {
|
|
||||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
|
||||||
type: KmsDataKey.Organization,
|
|
||||||
orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
const decryptedPlainTextBlob = decryptor({
|
|
||||||
cipherTextBlob: encryptedCredentials
|
|
||||||
});
|
|
||||||
|
|
||||||
return JSON.parse(decryptedPlainTextBlob.toString()) as TAppConnection["credentials"];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const validateAppConnectionCredentials = async (
|
|
||||||
appConnection: TAppConnectionConfig
|
|
||||||
): Promise<TAppConnection["credentials"]> => {
|
|
||||||
const { app } = appConnection;
|
|
||||||
switch (app) {
|
|
||||||
case AppConnection.AWS: {
|
|
||||||
return validateAwsConnectionCredentials(appConnection);
|
|
||||||
}
|
|
||||||
case AppConnection.GitHub:
|
|
||||||
return validateGitHubConnectionCredentials(appConnection);
|
|
||||||
default:
|
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
||||||
throw new Error(`Unhandled App Connection ${app}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAppConnectionMethodName = (method: TAppConnection["method"]) => {
|
|
||||||
switch (method) {
|
|
||||||
case GitHubConnectionMethod.App:
|
|
||||||
return "GitHub App";
|
|
||||||
case GitHubConnectionMethod.OAuth:
|
|
||||||
return "OAuth";
|
|
||||||
case AwsConnectionMethod.AccessKey:
|
|
||||||
return "Access Key";
|
|
||||||
case AwsConnectionMethod.AssumeRole:
|
|
||||||
return "Assume Role";
|
|
||||||
default:
|
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
||||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,6 +0,0 @@
|
|||||||
import { AppConnection } from "./app-connection-enums";
|
|
||||||
|
|
||||||
export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|
||||||
[AppConnection.AWS]: "AWS",
|
|
||||||
[AppConnection.GitHub]: "GitHub"
|
|
||||||
};
|
|
@ -1,35 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { AppConnectionsSchema } from "@app/db/schemas/app-connections";
|
|
||||||
import { AppConnections } from "@app/lib/api-docs";
|
|
||||||
import { slugSchema } from "@app/server/lib/schemas";
|
|
||||||
|
|
||||||
import { AppConnection } from "./app-connection-enums";
|
|
||||||
|
|
||||||
export const BaseAppConnectionSchema = AppConnectionsSchema.omit({
|
|
||||||
encryptedCredentials: true,
|
|
||||||
app: true,
|
|
||||||
method: true
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GenericCreateAppConnectionFieldsSchema = (app: AppConnection) =>
|
|
||||||
z.object({
|
|
||||||
name: slugSchema({ field: "name" }).describe(AppConnections.CREATE(app).name),
|
|
||||||
description: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.max(256, "Description cannot exceed 256 characters")
|
|
||||||
.nullish()
|
|
||||||
.describe(AppConnections.CREATE(app).description)
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GenericUpdateAppConnectionFieldsSchema = (app: AppConnection) =>
|
|
||||||
z.object({
|
|
||||||
name: slugSchema({ field: "name" }).describe(AppConnections.UPDATE(app).name).optional(),
|
|
||||||
description: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.max(256, "Description cannot exceed 256 characters")
|
|
||||||
.nullish()
|
|
||||||
.describe(AppConnections.UPDATE(app).description)
|
|
||||||
});
|
|
@ -1,360 +0,0 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
|
||||||
|
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
|
||||||
import { DiscriminativePick, OrgServiceActor } from "@app/lib/types";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
import {
|
|
||||||
decryptAppConnectionCredentials,
|
|
||||||
encryptAppConnectionCredentials,
|
|
||||||
getAppConnectionMethodName,
|
|
||||||
listAppConnectionOptions,
|
|
||||||
validateAppConnectionCredentials
|
|
||||||
} from "@app/services/app-connection/app-connection-fns";
|
|
||||||
import { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps";
|
|
||||||
import {
|
|
||||||
TAppConnection,
|
|
||||||
TAppConnectionConfig,
|
|
||||||
TCreateAppConnectionDTO,
|
|
||||||
TUpdateAppConnectionDTO,
|
|
||||||
TValidateAppConnectionCredentials
|
|
||||||
} from "@app/services/app-connection/app-connection-types";
|
|
||||||
import { ValidateAwsConnectionCredentialsSchema } from "@app/services/app-connection/aws";
|
|
||||||
import { ValidateGitHubConnectionCredentialsSchema } from "@app/services/app-connection/github";
|
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
|
||||||
|
|
||||||
import { TAppConnectionDALFactory } from "./app-connection-dal";
|
|
||||||
|
|
||||||
export type TAppConnectionServiceFactoryDep = {
|
|
||||||
appConnectionDAL: TAppConnectionDALFactory;
|
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">; // TODO: remove once launched
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServiceFactory>;
|
|
||||||
|
|
||||||
const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAppConnectionCredentials> = {
|
|
||||||
[AppConnection.AWS]: ValidateAwsConnectionCredentialsSchema,
|
|
||||||
[AppConnection.GitHub]: ValidateGitHubConnectionCredentialsSchema
|
|
||||||
};
|
|
||||||
|
|
||||||
export const appConnectionServiceFactory = ({
|
|
||||||
appConnectionDAL,
|
|
||||||
permissionService,
|
|
||||||
kmsService,
|
|
||||||
licenseService
|
|
||||||
}: TAppConnectionServiceFactoryDep) => {
|
|
||||||
// app connections are disabled for public until launch
|
|
||||||
const checkAppServicesAvailability = async (orgId: string) => {
|
|
||||||
const subscription = await licenseService.getPlan(orgId);
|
|
||||||
|
|
||||||
if (!subscription.appConnections) throw new BadRequestError({ message: "App Connections are not available yet." });
|
|
||||||
};
|
|
||||||
|
|
||||||
const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => {
|
|
||||||
await checkAppServicesAvailability(actor.orgId);
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
|
||||||
actor.type,
|
|
||||||
actor.id,
|
|
||||||
actor.orgId,
|
|
||||||
actor.authMethod,
|
|
||||||
actor.orgId
|
|
||||||
);
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AppConnections);
|
|
||||||
|
|
||||||
const appConnections = await appConnectionDAL.find(
|
|
||||||
app
|
|
||||||
? { orgId: actor.orgId, app }
|
|
||||||
: {
|
|
||||||
orgId: actor.orgId
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return Promise.all(
|
|
||||||
appConnections
|
|
||||||
.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()))
|
|
||||||
.map(async ({ encryptedCredentials, ...connection }) => {
|
|
||||||
const credentials = await decryptAppConnectionCredentials({
|
|
||||||
encryptedCredentials,
|
|
||||||
kmsService,
|
|
||||||
orgId: connection.orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...connection,
|
|
||||||
credentials
|
|
||||||
} as TAppConnection;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const findAppConnectionById = async (app: AppConnection, connectionId: string, actor: OrgServiceActor) => {
|
|
||||||
await checkAppServicesAvailability(actor.orgId);
|
|
||||||
|
|
||||||
const appConnection = await appConnectionDAL.findById(connectionId);
|
|
||||||
|
|
||||||
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
|
||||||
actor.type,
|
|
||||||
actor.id,
|
|
||||||
actor.orgId,
|
|
||||||
actor.authMethod,
|
|
||||||
appConnection.orgId
|
|
||||||
);
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AppConnections);
|
|
||||||
|
|
||||||
if (appConnection.app !== app)
|
|
||||||
throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` });
|
|
||||||
|
|
||||||
return {
|
|
||||||
...appConnection,
|
|
||||||
credentials: await decryptAppConnectionCredentials({
|
|
||||||
encryptedCredentials: appConnection.encryptedCredentials,
|
|
||||||
orgId: appConnection.orgId,
|
|
||||||
kmsService
|
|
||||||
})
|
|
||||||
} as TAppConnection;
|
|
||||||
};
|
|
||||||
|
|
||||||
const findAppConnectionByName = async (app: AppConnection, connectionName: string, actor: OrgServiceActor) => {
|
|
||||||
await checkAppServicesAvailability(actor.orgId);
|
|
||||||
|
|
||||||
const appConnection = await appConnectionDAL.findOne({ name: connectionName, orgId: actor.orgId });
|
|
||||||
|
|
||||||
if (!appConnection)
|
|
||||||
throw new NotFoundError({ message: `Could not find App Connection with name ${connectionName}` });
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
|
||||||
actor.type,
|
|
||||||
actor.id,
|
|
||||||
actor.orgId,
|
|
||||||
actor.authMethod,
|
|
||||||
appConnection.orgId
|
|
||||||
);
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AppConnections);
|
|
||||||
|
|
||||||
if (appConnection.app !== app)
|
|
||||||
throw new BadRequestError({ message: `App Connection with name ${connectionName} is not for App "${app}"` });
|
|
||||||
|
|
||||||
return {
|
|
||||||
...appConnection,
|
|
||||||
credentials: await decryptAppConnectionCredentials({
|
|
||||||
encryptedCredentials: appConnection.encryptedCredentials,
|
|
||||||
orgId: appConnection.orgId,
|
|
||||||
kmsService
|
|
||||||
})
|
|
||||||
} as TAppConnection;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createAppConnection = async (
|
|
||||||
{ method, app, credentials, ...params }: TCreateAppConnectionDTO,
|
|
||||||
actor: OrgServiceActor
|
|
||||||
) => {
|
|
||||||
await checkAppServicesAvailability(actor.orgId);
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
|
||||||
actor.type,
|
|
||||||
actor.id,
|
|
||||||
actor.orgId,
|
|
||||||
actor.authMethod,
|
|
||||||
actor.orgId
|
|
||||||
);
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.AppConnections);
|
|
||||||
|
|
||||||
const appConnection = await appConnectionDAL.transaction(async (tx) => {
|
|
||||||
const isConflictingName = Boolean(
|
|
||||||
await appConnectionDAL.findOne(
|
|
||||||
{
|
|
||||||
name: params.name,
|
|
||||||
orgId: actor.orgId
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isConflictingName)
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `An App Connection with the name "${params.name}" already exists`
|
|
||||||
});
|
|
||||||
|
|
||||||
const validatedCredentials = await validateAppConnectionCredentials({
|
|
||||||
app,
|
|
||||||
credentials,
|
|
||||||
method,
|
|
||||||
orgId: actor.orgId
|
|
||||||
} as TAppConnectionConfig);
|
|
||||||
|
|
||||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
|
||||||
credentials: validatedCredentials,
|
|
||||||
orgId: actor.orgId,
|
|
||||||
kmsService
|
|
||||||
});
|
|
||||||
|
|
||||||
const connection = await appConnectionDAL.create(
|
|
||||||
{
|
|
||||||
orgId: actor.orgId,
|
|
||||||
encryptedCredentials,
|
|
||||||
method,
|
|
||||||
app,
|
|
||||||
...params
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...connection,
|
|
||||||
credentials: validatedCredentials
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return appConnection;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateAppConnection = async (
|
|
||||||
{ connectionId, credentials, ...params }: TUpdateAppConnectionDTO,
|
|
||||||
actor: OrgServiceActor
|
|
||||||
) => {
|
|
||||||
await checkAppServicesAvailability(actor.orgId);
|
|
||||||
|
|
||||||
const appConnection = await appConnectionDAL.findById(connectionId);
|
|
||||||
|
|
||||||
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
|
||||||
actor.type,
|
|
||||||
actor.id,
|
|
||||||
actor.orgId,
|
|
||||||
actor.authMethod,
|
|
||||||
appConnection.orgId
|
|
||||||
);
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.AppConnections);
|
|
||||||
|
|
||||||
const updatedAppConnection = await appConnectionDAL.transaction(async (tx) => {
|
|
||||||
if (params.name && appConnection.name !== params.name) {
|
|
||||||
const isConflictingName = Boolean(
|
|
||||||
await appConnectionDAL.findOne(
|
|
||||||
{
|
|
||||||
name: params.name,
|
|
||||||
orgId: appConnection.orgId
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isConflictingName)
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `An App Connection with the name "${params.name}" already exists`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let encryptedCredentials: undefined | Buffer;
|
|
||||||
|
|
||||||
if (credentials) {
|
|
||||||
const { app, method } = appConnection as DiscriminativePick<TAppConnectionConfig, "app" | "method">;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[app].safeParse({
|
|
||||||
method,
|
|
||||||
credentials
|
|
||||||
}).success
|
|
||||||
)
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Invalid credential format for ${
|
|
||||||
APP_CONNECTION_NAME_MAP[app]
|
|
||||||
} Connection with method ${getAppConnectionMethodName(method)}`
|
|
||||||
});
|
|
||||||
|
|
||||||
const validatedCredentials = await validateAppConnectionCredentials({
|
|
||||||
app,
|
|
||||||
orgId: actor.orgId,
|
|
||||||
credentials,
|
|
||||||
method
|
|
||||||
} as TAppConnectionConfig);
|
|
||||||
|
|
||||||
if (!validatedCredentials)
|
|
||||||
throw new BadRequestError({ message: "Unable to validate connection - check credentials" });
|
|
||||||
|
|
||||||
encryptedCredentials = await encryptAppConnectionCredentials({
|
|
||||||
credentials: validatedCredentials,
|
|
||||||
orgId: actor.orgId,
|
|
||||||
kmsService
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedConnection = await appConnectionDAL.updateById(
|
|
||||||
connectionId,
|
|
||||||
{
|
|
||||||
orgId: actor.orgId,
|
|
||||||
encryptedCredentials,
|
|
||||||
...params
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
return updatedConnection;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...updatedAppConnection,
|
|
||||||
credentials: await decryptAppConnectionCredentials({
|
|
||||||
encryptedCredentials: updatedAppConnection.encryptedCredentials,
|
|
||||||
orgId: updatedAppConnection.orgId,
|
|
||||||
kmsService
|
|
||||||
})
|
|
||||||
} as TAppConnection;
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteAppConnection = async (app: AppConnection, connectionId: string, actor: OrgServiceActor) => {
|
|
||||||
await checkAppServicesAvailability(actor.orgId);
|
|
||||||
|
|
||||||
const appConnection = await appConnectionDAL.findById(connectionId);
|
|
||||||
|
|
||||||
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
|
||||||
actor.type,
|
|
||||||
actor.id,
|
|
||||||
actor.orgId,
|
|
||||||
actor.authMethod,
|
|
||||||
appConnection.orgId
|
|
||||||
);
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.AppConnections);
|
|
||||||
|
|
||||||
if (appConnection.app !== app)
|
|
||||||
throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` });
|
|
||||||
|
|
||||||
// TODO: specify delete error message if due to existing dependencies
|
|
||||||
|
|
||||||
const deletedAppConnection = await appConnectionDAL.deleteById(connectionId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...deletedAppConnection,
|
|
||||||
credentials: await decryptAppConnectionCredentials({
|
|
||||||
encryptedCredentials: deletedAppConnection.encryptedCredentials,
|
|
||||||
orgId: deletedAppConnection.orgId,
|
|
||||||
kmsService
|
|
||||||
})
|
|
||||||
} as TAppConnection;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
listAppConnectionOptions,
|
|
||||||
listAppConnectionsByOrg,
|
|
||||||
findAppConnectionById,
|
|
||||||
findAppConnectionByName,
|
|
||||||
createAppConnection,
|
|
||||||
updateAppConnection,
|
|
||||||
deleteAppConnection
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,31 +0,0 @@
|
|||||||
import {
|
|
||||||
TAwsConnection,
|
|
||||||
TAwsConnectionConfig,
|
|
||||||
TAwsConnectionInput,
|
|
||||||
TValidateAwsConnectionCredentials
|
|
||||||
} from "@app/services/app-connection/aws";
|
|
||||||
import {
|
|
||||||
TGitHubConnection,
|
|
||||||
TGitHubConnectionConfig,
|
|
||||||
TGitHubConnectionInput,
|
|
||||||
TValidateGitHubConnectionCredentials
|
|
||||||
} from "@app/services/app-connection/github";
|
|
||||||
|
|
||||||
export type TAppConnection = { id: string } & (TAwsConnection | TGitHubConnection);
|
|
||||||
|
|
||||||
export type TAppConnectionInput = { id: string } & (TAwsConnectionInput | TGitHubConnectionInput);
|
|
||||||
|
|
||||||
export type TCreateAppConnectionDTO = Pick<
|
|
||||||
TAppConnectionInput,
|
|
||||||
"credentials" | "method" | "name" | "app" | "description"
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type TUpdateAppConnectionDTO = Partial<Omit<TCreateAppConnectionDTO, "method" | "app">> & {
|
|
||||||
connectionId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TAppConnectionConfig = TAwsConnectionConfig | TGitHubConnectionConfig;
|
|
||||||
|
|
||||||
export type TValidateAppConnectionCredentials =
|
|
||||||
| TValidateAwsConnectionCredentials
|
|
||||||
| TValidateGitHubConnectionCredentials;
|
|
@ -1,4 +0,0 @@
|
|||||||
export enum AwsConnectionMethod {
|
|
||||||
AssumeRole = "assume-role",
|
|
||||||
AccessKey = "access-key"
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
|
|
||||||
import AWS from "aws-sdk";
|
|
||||||
import { randomUUID } from "crypto";
|
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
|
|
||||||
import { AwsConnectionMethod } from "./aws-connection-enums";
|
|
||||||
import { TAwsConnectionConfig } from "./aws-connection-types";
|
|
||||||
|
|
||||||
export const getAwsAppConnectionListItem = () => {
|
|
||||||
const { INF_APP_CONNECTION_AWS_ACCESS_KEY_ID } = getConfig();
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "AWS" as const,
|
|
||||||
app: AppConnection.AWS as const,
|
|
||||||
methods: Object.values(AwsConnectionMethod) as [AwsConnectionMethod.AssumeRole, AwsConnectionMethod.AccessKey],
|
|
||||||
accessKeyId: INF_APP_CONNECTION_AWS_ACCESS_KEY_ID
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAwsConnectionConfig = async (appConnection: TAwsConnectionConfig, region = "us-east-1") => {
|
|
||||||
const appCfg = getConfig();
|
|
||||||
|
|
||||||
let accessKeyId: string;
|
|
||||||
let secretAccessKey: string;
|
|
||||||
let sessionToken: undefined | string;
|
|
||||||
|
|
||||||
const { method, credentials, orgId } = appConnection;
|
|
||||||
|
|
||||||
switch (method) {
|
|
||||||
case AwsConnectionMethod.AssumeRole: {
|
|
||||||
const client = new STSClient({
|
|
||||||
region,
|
|
||||||
credentials:
|
|
||||||
appCfg.INF_APP_CONNECTION_AWS_ACCESS_KEY_ID && appCfg.INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY
|
|
||||||
? {
|
|
||||||
accessKeyId: appCfg.INF_APP_CONNECTION_AWS_ACCESS_KEY_ID,
|
|
||||||
secretAccessKey: appCfg.INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY
|
|
||||||
}
|
|
||||||
: undefined // if hosting on AWS
|
|
||||||
});
|
|
||||||
|
|
||||||
const command = new AssumeRoleCommand({
|
|
||||||
RoleArn: credentials.roleArn,
|
|
||||||
RoleSessionName: `infisical-app-connection-${randomUUID()}`,
|
|
||||||
DurationSeconds: 900, // 15 mins
|
|
||||||
ExternalId: orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
const assumeRes = await client.send(command);
|
|
||||||
|
|
||||||
if (!assumeRes.Credentials?.AccessKeyId || !assumeRes.Credentials?.SecretAccessKey) {
|
|
||||||
throw new BadRequestError({ message: "Failed to assume role - verify credentials and role configuration" });
|
|
||||||
}
|
|
||||||
|
|
||||||
accessKeyId = assumeRes.Credentials.AccessKeyId;
|
|
||||||
secretAccessKey = assumeRes.Credentials.SecretAccessKey;
|
|
||||||
sessionToken = assumeRes.Credentials?.SessionToken;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case AwsConnectionMethod.AccessKey: {
|
|
||||||
accessKeyId = credentials.accessKeyId;
|
|
||||||
secretAccessKey = credentials.secretAccessKey;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
||||||
throw new InternalServerError({ message: `Unsupported AWS connection method: ${method}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AWS.Config({
|
|
||||||
region,
|
|
||||||
credentials: {
|
|
||||||
accessKeyId,
|
|
||||||
secretAccessKey,
|
|
||||||
sessionToken
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const validateAwsConnectionCredentials = async (appConnection: TAwsConnectionConfig) => {
|
|
||||||
const awsConfig = await getAwsConnectionConfig(appConnection);
|
|
||||||
const sts = new AWS.STS(awsConfig);
|
|
||||||
let resp: Awaited<ReturnType<ReturnType<typeof sts.getCallerIdentity>["promise"]>>;
|
|
||||||
|
|
||||||
try {
|
|
||||||
resp = await sts.getCallerIdentity().promise();
|
|
||||||
} catch (e: unknown) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Unable to validate connection - verify credentials`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resp.$response.httpResponse.statusCode !== 200)
|
|
||||||
throw new InternalServerError({
|
|
||||||
message: `Unable to validate credentials: ${
|
|
||||||
resp.$response.error?.message ??
|
|
||||||
`AWS responded with a status code of ${resp.$response.httpResponse.statusCode}. Verify credentials and try again.`
|
|
||||||
}`
|
|
||||||
});
|
|
||||||
|
|
||||||
return appConnection.credentials;
|
|
||||||
};
|
|
@ -1,82 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { AppConnections } from "@app/lib/api-docs";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
import {
|
|
||||||
BaseAppConnectionSchema,
|
|
||||||
GenericCreateAppConnectionFieldsSchema,
|
|
||||||
GenericUpdateAppConnectionFieldsSchema
|
|
||||||
} from "@app/services/app-connection/app-connection-schemas";
|
|
||||||
|
|
||||||
import { AwsConnectionMethod } from "./aws-connection-enums";
|
|
||||||
|
|
||||||
export const AwsConnectionAssumeRoleCredentialsSchema = z.object({
|
|
||||||
roleArn: z.string().trim().min(1, "Role ARN required")
|
|
||||||
});
|
|
||||||
|
|
||||||
export const AwsConnectionAccessTokenCredentialsSchema = z.object({
|
|
||||||
accessKeyId: z.string().trim().min(1, "Access Key ID required"),
|
|
||||||
secretAccessKey: z.string().trim().min(1, "Secret Access Key required")
|
|
||||||
});
|
|
||||||
|
|
||||||
const BaseAwsConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.AWS) });
|
|
||||||
|
|
||||||
export const AwsConnectionSchema = z.intersection(
|
|
||||||
BaseAwsConnectionSchema,
|
|
||||||
z.discriminatedUnion("method", [
|
|
||||||
z.object({
|
|
||||||
method: z.literal(AwsConnectionMethod.AssumeRole),
|
|
||||||
credentials: AwsConnectionAssumeRoleCredentialsSchema
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
method: z.literal(AwsConnectionMethod.AccessKey),
|
|
||||||
credentials: AwsConnectionAccessTokenCredentialsSchema
|
|
||||||
})
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
export const SanitizedAwsConnectionSchema = z.discriminatedUnion("method", [
|
|
||||||
BaseAwsConnectionSchema.extend({
|
|
||||||
method: z.literal(AwsConnectionMethod.AssumeRole),
|
|
||||||
credentials: AwsConnectionAssumeRoleCredentialsSchema.omit({ roleArn: true })
|
|
||||||
}),
|
|
||||||
BaseAwsConnectionSchema.extend({
|
|
||||||
method: z.literal(AwsConnectionMethod.AccessKey),
|
|
||||||
credentials: AwsConnectionAccessTokenCredentialsSchema.omit({ secretAccessKey: true })
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const ValidateAwsConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
|
||||||
z.object({
|
|
||||||
method: z.literal(AwsConnectionMethod.AssumeRole).describe(AppConnections?.CREATE(AppConnection.AWS).method),
|
|
||||||
credentials: AwsConnectionAssumeRoleCredentialsSchema.describe(AppConnections.CREATE(AppConnection.AWS).credentials)
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
method: z.literal(AwsConnectionMethod.AccessKey).describe(AppConnections?.CREATE(AppConnection.AWS).method),
|
|
||||||
credentials: AwsConnectionAccessTokenCredentialsSchema.describe(
|
|
||||||
AppConnections.CREATE(AppConnection.AWS).credentials
|
|
||||||
)
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const CreateAwsConnectionSchema = ValidateAwsConnectionCredentialsSchema.and(
|
|
||||||
GenericCreateAppConnectionFieldsSchema(AppConnection.AWS)
|
|
||||||
);
|
|
||||||
|
|
||||||
export const UpdateAwsConnectionSchema = z
|
|
||||||
.object({
|
|
||||||
credentials: z
|
|
||||||
.union([AwsConnectionAccessTokenCredentialsSchema, AwsConnectionAssumeRoleCredentialsSchema])
|
|
||||||
.optional()
|
|
||||||
.describe(AppConnections.UPDATE(AppConnection.AWS).credentials)
|
|
||||||
})
|
|
||||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AWS));
|
|
||||||
|
|
||||||
export const AwsConnectionListItemSchema = z.object({
|
|
||||||
name: z.literal("AWS"),
|
|
||||||
app: z.literal(AppConnection.AWS),
|
|
||||||
// the below is preferable but currently breaks mintlify
|
|
||||||
// methods: z.tuple([z.literal(AwsConnectionMethod.AssumeRole), z.literal(AwsConnectionMethod.AccessKey)]),
|
|
||||||
methods: z.nativeEnum(AwsConnectionMethod).array(),
|
|
||||||
accessKeyId: z.string().optional()
|
|
||||||
});
|
|
@ -1,22 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { DiscriminativePick } from "@app/lib/types";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
|
|
||||||
import {
|
|
||||||
AwsConnectionSchema,
|
|
||||||
CreateAwsConnectionSchema,
|
|
||||||
ValidateAwsConnectionCredentialsSchema
|
|
||||||
} from "./aws-connection-schemas";
|
|
||||||
|
|
||||||
export type TAwsConnection = z.infer<typeof AwsConnectionSchema>;
|
|
||||||
|
|
||||||
export type TAwsConnectionInput = z.infer<typeof CreateAwsConnectionSchema> & {
|
|
||||||
app: AppConnection.AWS;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TValidateAwsConnectionCredentials = typeof ValidateAwsConnectionCredentialsSchema;
|
|
||||||
|
|
||||||
export type TAwsConnectionConfig = DiscriminativePick<TAwsConnectionInput, "method" | "app" | "credentials"> & {
|
|
||||||
orgId: string;
|
|
||||||
};
|
|
@ -1,4 +0,0 @@
|
|||||||
export * from "./aws-connection-enums";
|
|
||||||
export * from "./aws-connection-fns";
|
|
||||||
export * from "./aws-connection-schemas";
|
|
||||||
export * from "./aws-connection-types";
|
|
@ -1,4 +0,0 @@
|
|||||||
export enum GitHubConnectionMethod {
|
|
||||||
OAuth = "oauth",
|
|
||||||
App = "github-app"
|
|
||||||
}
|
|
@ -1,129 +0,0 @@
|
|||||||
import { AxiosResponse } from "axios";
|
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import { request } from "@app/lib/config/request";
|
|
||||||
import { BadRequestError, ForbiddenRequestError, InternalServerError } from "@app/lib/errors";
|
|
||||||
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
|
|
||||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
|
||||||
|
|
||||||
import { AppConnection } from "../app-connection-enums";
|
|
||||||
import { GitHubConnectionMethod } from "./github-connection-enums";
|
|
||||||
import { TGitHubConnectionConfig } from "./github-connection-types";
|
|
||||||
|
|
||||||
export const getGitHubConnectionListItem = () => {
|
|
||||||
const { INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID, INF_APP_CONNECTION_GITHUB_APP_SLUG } = getConfig();
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "GitHub" as const,
|
|
||||||
app: AppConnection.GitHub as const,
|
|
||||||
methods: Object.values(GitHubConnectionMethod) as [GitHubConnectionMethod.App, GitHubConnectionMethod.OAuth],
|
|
||||||
oauthClientId: INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID,
|
|
||||||
appClientSlug: INF_APP_CONNECTION_GITHUB_APP_SLUG
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type TokenRespData = {
|
|
||||||
access_token: string;
|
|
||||||
scope: string;
|
|
||||||
token_type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const validateGitHubConnectionCredentials = async (config: TGitHubConnectionConfig) => {
|
|
||||||
const { credentials, method } = config;
|
|
||||||
|
|
||||||
const {
|
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID,
|
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET,
|
|
||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID,
|
|
||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET,
|
|
||||||
SITE_URL
|
|
||||||
} = getConfig();
|
|
||||||
|
|
||||||
const { clientId, clientSecret } =
|
|
||||||
method === GitHubConnectionMethod.App
|
|
||||||
? {
|
|
||||||
clientId: INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID,
|
|
||||||
clientSecret: INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET
|
|
||||||
}
|
|
||||||
: // oauth
|
|
||||||
{
|
|
||||||
clientId: INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID,
|
|
||||||
clientSecret: INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!clientId || !clientSecret) {
|
|
||||||
throw new InternalServerError({
|
|
||||||
message: `GitHub ${getAppConnectionMethodName(method)} environment variables have not been configured`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokenResp: AxiosResponse<TokenRespData>;
|
|
||||||
|
|
||||||
try {
|
|
||||||
tokenResp = await request.get<TokenRespData>("https://github.com/login/oauth/access_token", {
|
|
||||||
params: {
|
|
||||||
client_id: clientId,
|
|
||||||
client_secret: clientSecret,
|
|
||||||
code: credentials.code,
|
|
||||||
redirect_uri: `${SITE_URL}/app-connections/github/oauth/callback`
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
"Accept-Encoding": "application/json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e: unknown) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Unable to validate connection - verify credentials`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokenResp.status !== 200) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Unable to validate credentials: GitHub responded with a status code of ${tokenResp.status} (${tokenResp.statusText}). Verify credentials and try again.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (method === GitHubConnectionMethod.App) {
|
|
||||||
const installationsResp = await request.get<{
|
|
||||||
installations: {
|
|
||||||
id: number;
|
|
||||||
account: {
|
|
||||||
login: string;
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
}>(IntegrationUrls.GITHUB_USER_INSTALLATIONS, {
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
Authorization: `Bearer ${tokenResp.data.access_token}`,
|
|
||||||
"Accept-Encoding": "application/json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const matchingInstallation = installationsResp.data.installations.find(
|
|
||||||
(installation) => installation.id === +credentials.installationId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!matchingInstallation) {
|
|
||||||
throw new ForbiddenRequestError({
|
|
||||||
message: "User does not have access to the provided installation"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (method) {
|
|
||||||
case GitHubConnectionMethod.App:
|
|
||||||
return {
|
|
||||||
// access token not needed for GitHub App
|
|
||||||
installationId: credentials.installationId
|
|
||||||
};
|
|
||||||
case GitHubConnectionMethod.OAuth:
|
|
||||||
return {
|
|
||||||
accessToken: tokenResp.data.access_token
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
throw new InternalServerError({
|
|
||||||
message: `Unhandled GitHub connection method: ${method as GitHubConnectionMethod}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,93 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { AppConnections } from "@app/lib/api-docs";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
import {
|
|
||||||
BaseAppConnectionSchema,
|
|
||||||
GenericCreateAppConnectionFieldsSchema,
|
|
||||||
GenericUpdateAppConnectionFieldsSchema
|
|
||||||
} from "@app/services/app-connection/app-connection-schemas";
|
|
||||||
|
|
||||||
import { GitHubConnectionMethod } from "./github-connection-enums";
|
|
||||||
|
|
||||||
export const GitHubConnectionOAuthInputCredentialsSchema = z.object({
|
|
||||||
code: z.string().trim().min(1, "OAuth code required")
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubConnectionAppInputCredentialsSchema = z.object({
|
|
||||||
code: z.string().trim().min(1, "GitHub App code required"),
|
|
||||||
installationId: z.string().min(1, "GitHub App Installation ID required")
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubConnectionOAuthOutputCredentialsSchema = z.object({
|
|
||||||
accessToken: z.string()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const GitHubConnectionAppOutputCredentialsSchema = z.object({
|
|
||||||
installationId: z.string()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ValidateGitHubConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
|
||||||
z.object({
|
|
||||||
method: z.literal(GitHubConnectionMethod.App).describe(AppConnections.CREATE(AppConnection.GitHub).method),
|
|
||||||
credentials: GitHubConnectionAppInputCredentialsSchema.describe(
|
|
||||||
AppConnections.CREATE(AppConnection.GitHub).credentials
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
method: z.literal(GitHubConnectionMethod.OAuth).describe(AppConnections.CREATE(AppConnection.GitHub).method),
|
|
||||||
credentials: GitHubConnectionOAuthInputCredentialsSchema.describe(
|
|
||||||
AppConnections.CREATE(AppConnection.GitHub).credentials
|
|
||||||
)
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const CreateGitHubConnectionSchema = ValidateGitHubConnectionCredentialsSchema.and(
|
|
||||||
GenericCreateAppConnectionFieldsSchema(AppConnection.GitHub)
|
|
||||||
);
|
|
||||||
|
|
||||||
export const UpdateGitHubConnectionSchema = z
|
|
||||||
.object({
|
|
||||||
credentials: z
|
|
||||||
.union([GitHubConnectionAppInputCredentialsSchema, GitHubConnectionOAuthInputCredentialsSchema])
|
|
||||||
.optional()
|
|
||||||
.describe(AppConnections.UPDATE(AppConnection.GitHub).credentials)
|
|
||||||
})
|
|
||||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.GitHub));
|
|
||||||
|
|
||||||
const BaseGitHubConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.GitHub) });
|
|
||||||
|
|
||||||
export const GitHubAppConnectionSchema = z.intersection(
|
|
||||||
BaseGitHubConnectionSchema,
|
|
||||||
z.discriminatedUnion("method", [
|
|
||||||
z.object({
|
|
||||||
method: z.literal(GitHubConnectionMethod.App),
|
|
||||||
credentials: GitHubConnectionAppOutputCredentialsSchema
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
method: z.literal(GitHubConnectionMethod.OAuth),
|
|
||||||
credentials: GitHubConnectionOAuthOutputCredentialsSchema
|
|
||||||
})
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [
|
|
||||||
BaseGitHubConnectionSchema.extend({
|
|
||||||
method: z.literal(GitHubConnectionMethod.App),
|
|
||||||
credentials: GitHubConnectionAppOutputCredentialsSchema.omit({ installationId: true })
|
|
||||||
}),
|
|
||||||
BaseGitHubConnectionSchema.extend({
|
|
||||||
method: z.literal(GitHubConnectionMethod.OAuth),
|
|
||||||
credentials: GitHubConnectionOAuthOutputCredentialsSchema.omit({ accessToken: true })
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const GitHubConnectionListItemSchema = z.object({
|
|
||||||
name: z.literal("GitHub"),
|
|
||||||
app: z.literal(AppConnection.GitHub),
|
|
||||||
// the below is preferable but currently breaks mintlify
|
|
||||||
// methods: z.tuple([z.literal(GitHubConnectionMethod.GitHubApp), z.literal(GitHubConnectionMethod.OAuth)]),
|
|
||||||
methods: z.nativeEnum(GitHubConnectionMethod).array(),
|
|
||||||
oauthClientId: z.string().optional(),
|
|
||||||
appClientSlug: z.string().optional()
|
|
||||||
});
|
|
@ -1,20 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { DiscriminativePick } from "@app/lib/types";
|
|
||||||
|
|
||||||
import { AppConnection } from "../app-connection-enums";
|
|
||||||
import {
|
|
||||||
CreateGitHubConnectionSchema,
|
|
||||||
GitHubAppConnectionSchema,
|
|
||||||
ValidateGitHubConnectionCredentialsSchema
|
|
||||||
} from "./github-connection-schemas";
|
|
||||||
|
|
||||||
export type TGitHubConnection = z.infer<typeof GitHubAppConnectionSchema>;
|
|
||||||
|
|
||||||
export type TGitHubConnectionInput = z.infer<typeof CreateGitHubConnectionSchema> & {
|
|
||||||
app: AppConnection.GitHub;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TValidateGitHubConnectionCredentials = typeof ValidateGitHubConnectionCredentialsSchema;
|
|
||||||
|
|
||||||
export type TGitHubConnectionConfig = DiscriminativePick<TGitHubConnectionInput, "method" | "app" | "credentials">;
|
|
@ -1,4 +0,0 @@
|
|||||||
export * from "./github-connection-enums";
|
|
||||||
export * from "./github-connection-fns";
|
|
||||||
export * from "./github-connection-schemas";
|
|
||||||
export * from "./github-connection-types";
|
|
@ -1397,24 +1397,14 @@ const syncSecretsHeroku = async ({
|
|||||||
* Sync/push [secrets] to Vercel project named [integration.app]
|
* Sync/push [secrets] to Vercel project named [integration.app]
|
||||||
*/
|
*/
|
||||||
const syncSecretsVercel = async ({
|
const syncSecretsVercel = async ({
|
||||||
createManySecretsRawFn,
|
|
||||||
integration,
|
integration,
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
secrets: infisicalSecrets,
|
secrets,
|
||||||
accessToken
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<{ id: string }>>;
|
integration: TIntegrations;
|
||||||
integration: TIntegrations & {
|
|
||||||
projectId: string;
|
|
||||||
environment: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
};
|
|
||||||
secretPath: string;
|
|
||||||
};
|
|
||||||
integrationAuth: TIntegrationAuths;
|
integrationAuth: TIntegrationAuths;
|
||||||
secrets: Record<string, { value: string; comment?: string } | null>;
|
secrets: Record<string, { value: string; comment?: string }>;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
interface VercelSecret {
|
interface VercelSecret {
|
||||||
@ -1487,119 +1477,80 @@ const syncSecretsVercel = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = IntegrationMetadataSchema.parse(integration.metadata);
|
const updateSecrets: VercelSecret[] = [];
|
||||||
|
const deleteSecrets: VercelSecret[] = [];
|
||||||
|
const newSecrets: VercelSecret[] = [];
|
||||||
|
|
||||||
// Default to overwrite target for old integrations that doesn't have a initial sync behavior set.
|
// Identify secrets to create
|
||||||
if (!metadata.initialSyncBehavior) {
|
Object.keys(secrets).forEach((key) => {
|
||||||
metadata.initialSyncBehavior = IntegrationInitialSyncBehavior.OVERWRITE_TARGET;
|
if (!(key in res)) {
|
||||||
}
|
// case: secret has been created
|
||||||
|
newSecrets.push({
|
||||||
const secretsToAddToInfisical: { [key: string]: VercelSecret } = {};
|
key,
|
||||||
|
value: secrets[key].value,
|
||||||
Object.keys(res).forEach((vercelKey) => {
|
type: "encrypted",
|
||||||
if (!integration.lastUsed) {
|
target: [integration.targetEnvironment as string],
|
||||||
// first time using integration
|
...(integration.path
|
||||||
// -> apply initial sync behavior
|
? {
|
||||||
switch (metadata.initialSyncBehavior) {
|
gitBranch: integration.path
|
||||||
// Override all the secrets in Vercel
|
}
|
||||||
case IntegrationInitialSyncBehavior.OVERWRITE_TARGET: {
|
: {})
|
||||||
if (!(vercelKey in infisicalSecrets)) infisicalSecrets[vercelKey] = null;
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
case IntegrationInitialSyncBehavior.PREFER_SOURCE: {
|
|
||||||
// if the vercel secret is not in infisical, we need to add it to infisical
|
|
||||||
if (!(vercelKey in infisicalSecrets)) {
|
|
||||||
infisicalSecrets[vercelKey] = {
|
|
||||||
value: res[vercelKey].value
|
|
||||||
};
|
|
||||||
secretsToAddToInfisical[vercelKey] = res[vercelKey];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new Error(`Invalid initial sync behavior: ${metadata.initialSyncBehavior}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (!(vercelKey in infisicalSecrets)) {
|
|
||||||
infisicalSecrets[vercelKey] = null;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Object.keys(secretsToAddToInfisical).length) {
|
// Identify secrets to update and delete
|
||||||
await createManySecretsRawFn({
|
Object.keys(res).forEach((key) => {
|
||||||
projectId: integration.projectId,
|
if (key in secrets) {
|
||||||
environment: integration.environment.slug,
|
if (res[key].value !== secrets[key].value) {
|
||||||
path: integration.secretPath,
|
// case: secret value has changed
|
||||||
secrets: Object.keys(secretsToAddToInfisical).map((key) => ({
|
updateSecrets.push({
|
||||||
secretName: key,
|
id: res[key].id,
|
||||||
secretValue: secretsToAddToInfisical[key].value,
|
key,
|
||||||
type: SecretType.Shared,
|
value: secrets[key].value,
|
||||||
secretComment: ""
|
type: res[key].type,
|
||||||
}))
|
target: res[key].target.includes(integration.targetEnvironment as string)
|
||||||
|
? [...res[key].target]
|
||||||
|
: [...res[key].target, integration.targetEnvironment as string],
|
||||||
|
...(integration.path
|
||||||
|
? {
|
||||||
|
gitBranch: integration.path
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// case: secret has been deleted
|
||||||
|
deleteSecrets.push({
|
||||||
|
id: res[key].id,
|
||||||
|
key,
|
||||||
|
value: res[key].value,
|
||||||
|
type: "encrypted", // value doesn't matter
|
||||||
|
target: [integration.targetEnvironment as string],
|
||||||
|
...(integration.path
|
||||||
|
? {
|
||||||
|
gitBranch: integration.path
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sync/push new secrets
|
||||||
|
if (newSecrets.length > 0) {
|
||||||
|
await request.post(`${IntegrationUrls.VERCEL_API_URL}/v10/projects/${integration.app}/env`, newSecrets, {
|
||||||
|
params,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// update and create logic
|
for await (const secret of updateSecrets) {
|
||||||
for await (const key of Object.keys(infisicalSecrets)) {
|
if (secret.type !== "sensitive") {
|
||||||
if (!(key in res) || infisicalSecrets[key]?.value !== res[key].value) {
|
const { id, ...updatedSecret } = secret;
|
||||||
// if the key is not in the vercel res, we need to create it
|
await request.patch(`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${id}`, updatedSecret, {
|
||||||
if (!(key in res)) {
|
|
||||||
await request.post(
|
|
||||||
`${IntegrationUrls.VERCEL_API_URL}/v10/projects/${integration.app}/env`,
|
|
||||||
{
|
|
||||||
key,
|
|
||||||
value: infisicalSecrets[key]?.value,
|
|
||||||
type: "encrypted",
|
|
||||||
target: [integration.targetEnvironment as string],
|
|
||||||
...(integration.path
|
|
||||||
? {
|
|
||||||
gitBranch: integration.path
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
params,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
"Accept-Encoding": "application/json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Else if the key already exists and its not sensitive, we need to update it
|
|
||||||
} else if (res[key].type !== "sensitive") {
|
|
||||||
await request.patch(
|
|
||||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${res[key].id}`,
|
|
||||||
{
|
|
||||||
key,
|
|
||||||
value: infisicalSecrets[key]?.value,
|
|
||||||
type: res[key].type,
|
|
||||||
target: res[key].target.includes(integration.targetEnvironment as string)
|
|
||||||
? [...res[key].target]
|
|
||||||
: [...res[key].target, integration.targetEnvironment as string],
|
|
||||||
...(integration.path
|
|
||||||
? {
|
|
||||||
gitBranch: integration.path
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
params,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
"Accept-Encoding": "application/json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete logic
|
|
||||||
for await (const key of Object.keys(res)) {
|
|
||||||
if (infisicalSecrets[key] === null) {
|
|
||||||
// case: delete secret
|
|
||||||
await request.delete(`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${res[key].id}`, {
|
|
||||||
params,
|
params,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
@ -1608,6 +1559,16 @@ const syncSecretsVercel = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for await (const secret of deleteSecrets) {
|
||||||
|
await request.delete(`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, {
|
||||||
|
params,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -4510,8 +4471,7 @@ export const syncIntegrationSecrets = async ({
|
|||||||
integration,
|
integration,
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken,
|
accessToken
|
||||||
createManySecretsRawFn
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case Integrations.NETLIFY:
|
case Integrations.NETLIFY:
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import opentelemetry from "@opentelemetry/api";
|
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -159,12 +158,6 @@ export const secretQueueFactory = ({
|
|||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
projectKeyDAL
|
projectKeyDAL
|
||||||
}: TSecretQueueFactoryDep) => {
|
}: TSecretQueueFactoryDep) => {
|
||||||
const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
|
|
||||||
const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", {
|
|
||||||
description: "Integration secret sync errors",
|
|
||||||
unit: "1"
|
|
||||||
});
|
|
||||||
|
|
||||||
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
|
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
await queueService.stopRepeatableJob(
|
await queueService.stopRepeatableJob(
|
||||||
@ -940,19 +933,6 @@ export const secretQueueFactory = ({
|
|||||||
`Secret integration sync error [projectId=${job.data.projectId}] [environment=${environment}] [secretPath=${job.data.secretPath}]`
|
`Secret integration sync error [projectId=${job.data.projectId}] [environment=${environment}] [secretPath=${job.data.secretPath}]`
|
||||||
);
|
);
|
||||||
|
|
||||||
const appCfg = getConfig();
|
|
||||||
if (appCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) {
|
|
||||||
errorHistogram.record(1, {
|
|
||||||
version: 1,
|
|
||||||
integration: integration.integration,
|
|
||||||
integrationId: integration.id,
|
|
||||||
type: err instanceof AxiosError ? "AxiosError" : err?.constructor?.name || "UnknownError",
|
|
||||||
status: err instanceof AxiosError ? err.response?.status : undefined,
|
|
||||||
name: err instanceof Error ? err.name : undefined,
|
|
||||||
projectId: integration.projectId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const message =
|
const message =
|
||||||
// eslint-disable-next-line no-nested-ternary
|
// eslint-disable-next-line no-nested-ternary
|
||||||
(err instanceof AxiosError
|
(err instanceof AxiosError
|
||||||
|
10
cli/go.mod
@ -23,8 +23,8 @@ require (
|
|||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
github.com/spf13/viper v1.8.1
|
github.com/spf13/viper v1.8.1
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
golang.org/x/crypto v0.25.0
|
golang.org/x/crypto v0.31.0
|
||||||
golang.org/x/term v0.22.0
|
golang.org/x/term v0.27.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -93,9 +93,9 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||||
golang.org/x/net v0.27.0 // indirect
|
golang.org/x/net v0.27.0 // indirect
|
||||||
golang.org/x/oauth2 v0.21.0 // indirect
|
golang.org/x/oauth2 v0.21.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/api v0.188.0 // indirect
|
google.golang.org/api v0.188.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||||
|
20
cli/go.sum
@ -453,8 +453,8 @@ golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5
|
|||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -564,8 +564,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -620,16 +620,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -643,8 +643,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
@ -6,9 +6,11 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Infisical/infisical-merge/packages/api"
|
"github.com/Infisical/infisical-merge/packages/api"
|
||||||
"github.com/Infisical/infisical-merge/packages/config"
|
"github.com/Infisical/infisical-merge/packages/config"
|
||||||
@ -16,6 +18,8 @@ import (
|
|||||||
infisicalSdk "github.com/infisical/go-sdk"
|
infisicalSdk "github.com/infisical/go-sdk"
|
||||||
infisicalSdkUtil "github.com/infisical/go-sdk/packages/util"
|
infisicalSdkUtil "github.com/infisical/go-sdk/packages/util"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sshCmd = &cobra.Command{
|
var sshCmd = &cobra.Command{
|
||||||
@ -52,8 +56,8 @@ var algoToFileName = map[infisicalSdkUtil.CertKeyAlgorithm]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isValidKeyAlgorithm(algo infisicalSdkUtil.CertKeyAlgorithm) bool {
|
func isValidKeyAlgorithm(algo infisicalSdkUtil.CertKeyAlgorithm) bool {
|
||||||
_, exists := algoToFileName[algo]
|
_, exists := algoToFileName[algo]
|
||||||
return exists
|
return exists
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidCertType(certType infisicalSdkUtil.SshCertType) bool {
|
func isValidCertType(certType infisicalSdkUtil.SshCertType) bool {
|
||||||
@ -81,6 +85,71 @@ func writeToFile(filePath string, content string, perm os.FileMode) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addCredentialsToAgent(privateKeyContent, certContent string) error {
|
||||||
|
// Parse the private key
|
||||||
|
privateKey, err := ssh.ParseRawPrivateKey([]byte(privateKeyContent))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse private key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the certificate
|
||||||
|
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(certContent))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, ok := pubKey.(*ssh.Certificate)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("parsed key is not a certificate")
|
||||||
|
}
|
||||||
|
// Calculate LifetimeSecs based on certificate's valid-to time
|
||||||
|
validUntil := time.Unix(int64(cert.ValidBefore), 0)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
// Handle ValidBefore as either a timestamp or an enumeration
|
||||||
|
// SSH certificates use ValidBefore as a timestamp unless set to 0 or ~0
|
||||||
|
if cert.ValidBefore == ssh.CertTimeInfinity {
|
||||||
|
// If certificate never expires, set default lifetime to 1 year (can adjust as needed)
|
||||||
|
validUntil = now.Add(365 * 24 * time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the duration until expiration
|
||||||
|
lifetime := validUntil.Sub(now)
|
||||||
|
if lifetime <= 0 {
|
||||||
|
return fmt.Errorf("certificate is already expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert duration to seconds
|
||||||
|
lifetimeSecs := uint32(lifetime.Seconds())
|
||||||
|
|
||||||
|
// Connect to the SSH agent
|
||||||
|
socket := os.Getenv("SSH_AUTH_SOCK")
|
||||||
|
if socket == "" {
|
||||||
|
return fmt.Errorf("SSH_AUTH_SOCK not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.Dial("unix", socket)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to SSH agent: %w", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
agentClient := agent.NewClient(conn)
|
||||||
|
|
||||||
|
// Add the key with certificate to the agent
|
||||||
|
err = agentClient.Add(agent.AddedKey{
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
Certificate: cert,
|
||||||
|
Comment: "Added via Infisical CLI",
|
||||||
|
LifetimeSecs: lifetimeSecs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add key to agent: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func issueCredentials(cmd *cobra.Command, args []string) {
|
func issueCredentials(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
token, err := util.GetInfisicalToken(cmd)
|
token, err := util.GetInfisicalToken(cmd)
|
||||||
@ -166,6 +235,15 @@ func issueCredentials(cmd *cobra.Command, args []string) {
|
|||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addToAgent, err := cmd.Flags().GetBool("addToAgent")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse addToAgent flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
if outFilePath == "" && addToAgent == false {
|
||||||
|
util.PrintErrorMessageAndExit("You must provide either --outFilePath or --addToAgent flag to use this command")
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
outputDir string
|
outputDir string
|
||||||
privateKeyPath string
|
privateKeyPath string
|
||||||
@ -173,14 +251,7 @@ func issueCredentials(cmd *cobra.Command, args []string) {
|
|||||||
signedKeyPath string
|
signedKeyPath string
|
||||||
)
|
)
|
||||||
|
|
||||||
if outFilePath == "" {
|
if outFilePath != "" {
|
||||||
// Use current working directory
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Failed to get current working directory")
|
|
||||||
}
|
|
||||||
outputDir = cwd
|
|
||||||
} else {
|
|
||||||
// Expand ~ to home directory if present
|
// Expand ~ to home directory if present
|
||||||
if strings.HasPrefix(outFilePath, "~") {
|
if strings.HasPrefix(outFilePath, "~") {
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
@ -264,34 +335,47 @@ func issueCredentials(cmd *cobra.Command, args []string) {
|
|||||||
util.HandleError(err, "Failed to issue SSH credentials")
|
util.HandleError(err, "Failed to issue SSH credentials")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If signedKeyPath wasn't set in the directory scenario, set it now
|
if outFilePath != "" {
|
||||||
if signedKeyPath == "" {
|
// If signedKeyPath wasn't set in the directory scenario, set it now
|
||||||
fileName := algoToFileName[infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)]
|
if signedKeyPath == "" {
|
||||||
signedKeyPath = filepath.Join(outputDir, fileName+"-cert.pub")
|
fileName := algoToFileName[infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)]
|
||||||
|
signedKeyPath = filepath.Join(outputDir, fileName+"-cert.pub")
|
||||||
|
}
|
||||||
|
|
||||||
|
if privateKeyPath == "" {
|
||||||
|
privateKeyPath = filepath.Join(outputDir, algoToFileName[infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)])
|
||||||
|
}
|
||||||
|
err = writeToFile(privateKeyPath, creds.PrivateKey, 0600)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Failed to write Private Key to file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if publicKeyPath == "" {
|
||||||
|
publicKeyPath = privateKeyPath + ".pub"
|
||||||
|
}
|
||||||
|
err = writeToFile(publicKeyPath, creds.PublicKey, 0644)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Failed to write Public Key to file")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeToFile(signedKeyPath, creds.SignedKey, 0644)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Failed to write Signed Key to file")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Successfully wrote SSH certificate to:", signedKeyPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if privateKeyPath == "" {
|
// Add SSH credentials to the SSH agent if needed
|
||||||
privateKeyPath = filepath.Join(outputDir, algoToFileName[infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)])
|
if addToAgent {
|
||||||
|
// Call the helper function to handle add-to-agent flow
|
||||||
|
err := addCredentialsToAgent(creds.PrivateKey, creds.SignedKey)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Failed to add keys to SSH agent")
|
||||||
|
} else {
|
||||||
|
fmt.Println("The SSH key and certificate have been successfully added to your ssh-agent.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
err = writeToFile(privateKeyPath, creds.PrivateKey, 0600)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Failed to write Private Key to file")
|
|
||||||
}
|
|
||||||
|
|
||||||
if publicKeyPath == "" {
|
|
||||||
publicKeyPath = privateKeyPath + ".pub"
|
|
||||||
}
|
|
||||||
err = writeToFile(publicKeyPath, creds.PublicKey, 0644)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Failed to write Public Key to file")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = writeToFile(signedKeyPath, creds.SignedKey, 0644)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Failed to write Signed Key to file")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Successfully wrote SSH certificate to:", signedKeyPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func signKey(cmd *cobra.Command, args []string) {
|
func signKey(cmd *cobra.Command, args []string) {
|
||||||
@ -519,6 +603,7 @@ func init() {
|
|||||||
sshIssueCredentialsCmd.Flags().String("ttl", "", "The ttl to issue SSH credentials for")
|
sshIssueCredentialsCmd.Flags().String("ttl", "", "The ttl to issue SSH credentials for")
|
||||||
sshIssueCredentialsCmd.Flags().String("keyId", "", "The keyId to issue SSH credentials for")
|
sshIssueCredentialsCmd.Flags().String("keyId", "", "The keyId to issue SSH credentials for")
|
||||||
sshIssueCredentialsCmd.Flags().String("outFilePath", "", "The path to write the SSH credentials to such as ~/.ssh, ./some_folder, ./some_folder/id_rsa-cert.pub. If not provided, the credentials will be saved to the current working directory")
|
sshIssueCredentialsCmd.Flags().String("outFilePath", "", "The path to write the SSH credentials to such as ~/.ssh, ./some_folder, ./some_folder/id_rsa-cert.pub. If not provided, the credentials will be saved to the current working directory")
|
||||||
|
sshIssueCredentialsCmd.Flags().Bool("addToAgent", false, "Whether to add issued SSH credentials to the SSH agent")
|
||||||
sshCmd.AddCommand(sshIssueCredentialsCmd)
|
sshCmd.AddCommand(sshIssueCredentialsCmd)
|
||||||
rootCmd.AddCommand(sshCmd)
|
rootCmd.AddCommand(sshCmd)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Create"
|
|
||||||
openapi: "POST /api/v1/app-connections/aws"
|
|
||||||
---
|
|
||||||
|
|
||||||
<Note>
|
|
||||||
Check out the configuration docs for [AWS Connections](/integrations/app-connections/aws) to learn how to obtain
|
|
||||||
the required credentials.
|
|
||||||
</Note>
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Delete"
|
|
||||||
openapi: "DELETE /api/v1/app-connections/aws/{connectionId}"
|
|
||||||
---
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Get by ID"
|
|
||||||
openapi: "GET /api/v1/app-connections/aws/{connectionId}"
|
|
||||||
---
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Get by Name"
|
|
||||||
openapi: "GET /api/v1/app-connections/aws/name/{connectionName}"
|
|
||||||
---
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "List"
|
|
||||||
openapi: "GET /api/v1/app-connections/aws"
|
|
||||||
---
|
|
@ -1,9 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Update"
|
|
||||||
openapi: "PATCH /api/v1/app-connections/aws/{connectionId}"
|
|
||||||
---
|
|
||||||
|
|
||||||
<Note>
|
|
||||||
Check out the configuration docs for [AWS Connections](/integrations/app-connections/aws) to learn how to obtain
|
|
||||||
the required credentials.
|
|
||||||
</Note>
|
|
@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Create"
|
|
||||||
openapi: "POST /api/v1/app-connections/github"
|
|
||||||
---
|
|
||||||
|
|
||||||
<Note>
|
|
||||||
GitHub Connections must be created through the Infisical UI.
|
|
||||||
Check out the configuration docs for [GitHub Connections](/integrations/app-connections/github) for a step-by-step
|
|
||||||
guide.
|
|
||||||
</Note>
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Delete"
|
|
||||||
openapi: "DELETE /api/v1/app-connections/github/{connectionId}"
|
|
||||||
---
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Get by ID"
|
|
||||||
openapi: "GET /api/v1/app-connections/github/{connectionId}"
|
|
||||||
---
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Get by Name"
|
|
||||||
openapi: "GET /api/v1/app-connections/github/name/{connectionName}"
|
|
||||||
---
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "List"
|
|
||||||
openapi: "GET /api/v1/app-connections/github"
|
|
||||||
---
|
|
@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Update"
|
|
||||||
openapi: "PATCH /api/v1/app-connections/github/{connectionId}"
|
|
||||||
---
|
|
||||||
|
|
||||||
<Note>
|
|
||||||
GitHub Connections must be updated through the Infisical UI.
|
|
||||||
Check out the configuration docs for [GitHub Connections](/integrations/app-connections/github) for a step-by-step
|
|
||||||
guide.
|
|
||||||
</Note>
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "List"
|
|
||||||
openapi: "GET /api/v1/app-connections"
|
|
||||||
---
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Options"
|
|
||||||
openapi: "GET /api/v1/app-connections/options"
|
|
||||||
---
|
|
@ -159,7 +159,19 @@ as part of the SSH operation.
|
|||||||
|
|
||||||
## Guide to Using Infisical SSH to Access a Host
|
## Guide to Using Infisical SSH to Access a Host
|
||||||
|
|
||||||
We show how to obtain a SSH certificate (and optionally a new SSH key pair) for a client to access a host via CLI:
|
We show how to obtain a SSH certificate and use it for a client to access a host via CLI:
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
The subsequent guide assumes the following prerequisites:
|
||||||
|
|
||||||
|
- SSH Agent is running: The `ssh-agent` must be actively running on the host machine.
|
||||||
|
- OpenSSH is installed: The system should have OpenSSH installed; this includes
|
||||||
|
both the `ssh` client and `ssh-agent`.
|
||||||
|
- `SSH_AUTH_SOCK` environment variable
|
||||||
|
is set; the `SSH_AUTH_SOCK` variable should point to the UNIX socket that
|
||||||
|
`ssh-agent` uses for communication.
|
||||||
|
|
||||||
|
</Note>
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Authenticate with Infisical">
|
<Step title="Authenticate with Infisical">
|
||||||
@ -169,70 +181,25 @@ infisical login
|
|||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Obtain a SSH certificate (and optionally new key-pair)">
|
<Step title="Obtain a SSH certificate and load it into the SSH agent">
|
||||||
Depending on the use-case, a client may either request a SSH certificate along with a new SSH key pair or obtain a SSH certificate for an existing SSH key pair to access a host.
|
Run the `infisical ssh issue-credentials` command, specifying the `--addToAgent` flag to automatically load the SSH certificate into the SSH agent.
|
||||||
|
```bash
|
||||||
|
infisical ssh issue-credentials --certificateTemplateId=<certificate-template-id> --principals=<username> --addToAgent
|
||||||
|
```
|
||||||
|
|
||||||
<AccordionGroup>
|
Here's some guidance on each flag:
|
||||||
<Accordion title="Using New Key Pair (Recommended)">
|
|
||||||
If you wish to obtain a new SSH key pair in conjunction with the SSH certificate, then you can use the `infisical ssh issue-credentials` command.
|
|
||||||
|
|
||||||
```bash
|
- `certificateTemplateId`: The ID of the certificate template to use for issuing the SSH certificate.
|
||||||
infisical ssh issue-credentials --certificateTemplateId=<certificate-template-id> --principals=<username>
|
- `principals`: The comma-delimited username(s) or hostname(s) to include in the SSH certificate.
|
||||||
```
|
|
||||||
|
|
||||||
The following flags may be relevant:
|
|
||||||
|
|
||||||
- `certificateTemplateId`: The ID of the certificate template to use for issuing the SSH certificate.
|
|
||||||
- `principals`: The comma-delimited username(s) or hostname(s) to include in the SSH certificate.
|
|
||||||
- `outFilePath` (optional): The path to the file to write the SSH certificate to.
|
|
||||||
|
|
||||||
<Note>
|
|
||||||
If `outFilePath` is not specified, the SSH certificate will be written to the current working directory where the command is run.
|
|
||||||
</Note>
|
|
||||||
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="Using Existing Key Pair">
|
|
||||||
If you have an existing SSH key pair, then you can use the `infisical ssh sign-key` command with either
|
|
||||||
the `--publicKey` flag or the `--publicKeyFilePath` flag to obtain a SSH certificate corresponding to
|
|
||||||
the existing credential.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
infisical ssh sign-key --publicKeyFilePath=<public-key-file-path> --certificateTemplateId=<certificate-template-id> --principals=<username>
|
|
||||||
```
|
|
||||||
|
|
||||||
The following flags may be relevant:
|
|
||||||
|
|
||||||
- `publicKey`: The public key to sign.
|
|
||||||
- `publicKeyFilePath`: The path to the public key file to sign.
|
|
||||||
- `certificateTemplateId`: The ID of the certificate template to use for issuing the SSH certificate.
|
|
||||||
- `principals`: The comma-delimited username(s) or hostname(s) to include in the SSH certificate.
|
|
||||||
- `outFilePath` (optional): The path to the file to write the SSH certificate to.
|
|
||||||
|
|
||||||
<Note>
|
|
||||||
If `outFilePath` is not specified but `publicKeyFilePath` is then the SSH certificate will be written to the directory of the public key file; if the public key file is called `id_rsa.pub`, then the file containing the SSH certificate will be called `id_rsa-cert.pub`.
|
|
||||||
|
|
||||||
Otherwise, if `outFilePath` is not specified, the SSH certificate will be written to the current working directory where the command is run.
|
|
||||||
</Note>
|
|
||||||
|
|
||||||
</Accordion>
|
|
||||||
</AccordionGroup>
|
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="SSH into the host">
|
<Step title="SSH into the host">
|
||||||
Once you have obtained the SSH certificate, you can use it to SSH into the desired host.
|
Finally, SSH into the desired host; the SSH operation will be performed using the SSH certificate loaded into the SSH agent.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh -i /path/to/private_key.pem \
|
ssh username@hostname
|
||||||
-o CertificateFile=/path/to/ssh-cert.pub \
|
|
||||||
username@hostname
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<Note>
|
|
||||||
We recommend setting up aliases so you can more easily SSH into the desired host.
|
|
||||||
|
|
||||||
For example, you may set up an SSH alias using the SSH client configuration file (usually `~/.ssh/config`), defining a host alias including the file path to the issued SSH credential(s).
|
|
||||||
</Note>
|
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 957 KiB |
Before Width: | Height: | Size: 957 KiB |
Before Width: | Height: | Size: 598 KiB |
Before Width: | Height: | Size: 584 KiB |
Before Width: | Height: | Size: 306 KiB |
Before Width: | Height: | Size: 311 KiB |
Before Width: | Height: | Size: 584 KiB |
Before Width: | Height: | Size: 974 KiB |
Before Width: | Height: | Size: 586 KiB |
Before Width: | Height: | Size: 580 KiB |
Before Width: | Height: | Size: 974 KiB |
Before Width: | Height: | Size: 352 KiB |
Before Width: | Height: | Size: 974 KiB |
Before Width: | Height: | Size: 562 KiB |
@ -1,354 +0,0 @@
|
|||||||
---
|
|
||||||
title: "AWS Connection"
|
|
||||||
description: "Learn how to configure an AWS Connection for Infisical."
|
|
||||||
---
|
|
||||||
|
|
||||||
Infisical supports two methods for connecting to AWS.
|
|
||||||
|
|
||||||
<Tabs>
|
|
||||||
<Tab title="Assume Role (Recommended)">
|
|
||||||
Infisical will assume the provided role in your AWS account securely, without the need to share any credentials.
|
|
||||||
|
|
||||||
**Prerequisites:**
|
|
||||||
|
|
||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
|
||||||
|
|
||||||
<Accordion title="Self-Hosted Instance">
|
|
||||||
To connect your self-hosted Infisical instance with AWS, you need to set up an AWS IAM User account that can assume the configured AWS IAM Role.
|
|
||||||
|
|
||||||
If your instance is deployed on AWS, the aws-sdk will automatically retrieve the credentials. Ensure that you assign the provided permission policy to your deployed instance, such as ECS or EC2.
|
|
||||||
|
|
||||||
The following steps are for instances not deployed on AWS:
|
|
||||||
<Steps>
|
|
||||||
<Step title="Create an IAM User">
|
|
||||||
Navigate to [Create IAM User](https://console.aws.amazon.com/iamv2/home#/users/create) in your AWS Console.
|
|
||||||
</Step>
|
|
||||||
<Step title="Create an Inline Policy">
|
|
||||||
Attach the following inline permission policy to the IAM User to allow it to assume any IAM Roles:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": [
|
|
||||||
{
|
|
||||||
"Sid": "AllowAssumeAnyRole",
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Action": "sts:AssumeRole",
|
|
||||||
"Resource": "arn:aws:iam::*:role/*"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</Step>
|
|
||||||
<Step title="Obtain the IAM User Credentials">
|
|
||||||
Obtain the AWS access key ID and secret access key for your IAM User by navigating to **IAM > Users > [Your User] > Security credentials > Access keys**.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Set Up Connection Keys">
|
|
||||||
1. Set the access key as **INF_APP_CONNECTION_AWS_CLIENT_ID**.
|
|
||||||
2. Set the secret key as **INF_APP_CONNECTION_AWS_CLIENT_SECRET**.
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step title="Create the Managing User IAM Role for Infisical">
|
|
||||||
1. Navigate to the [Create IAM Role](https://console.aws.amazon.com/iamv2/home#/roles/create?step=selectEntities) page in your AWS Console.
|
|
||||||

|
|
||||||
|
|
||||||
2. Select **AWS Account** as the **Trusted Entity Type**.
|
|
||||||
3. Choose **Another AWS Account** and enter **381492033652** (Infisical AWS Account ID). This restricts the role to be assumed only by Infisical. If self-hosting, provide your AWS account number instead.
|
|
||||||
4. Optionally, enable **Require external ID** and enter your **Organization ID** to further enhance security.
|
|
||||||
</Step>
|
|
||||||
|
|
||||||
<Step title="Add Required Permissions for the IAM Role">
|
|
||||||
Depending on your use case, add one or more of the following policies to your IAM Role:
|
|
||||||
|
|
||||||
<Tabs>
|
|
||||||
<Tab title="Secrets Sync">
|
|
||||||
<AccordionGroup>
|
|
||||||
<Accordion title="AWS Secrets Manager">
|
|
||||||
Use the following custom policy to grant the minimum permissions required by Infisical to sync secrets to AWS Secrets Manager:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": [
|
|
||||||
{
|
|
||||||
"Sid": "AllowSecretsManagerAccess",
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Action": [
|
|
||||||
"secretsmanager:GetSecretValue",
|
|
||||||
"secretsmanager:CreateSecret",
|
|
||||||
"secretsmanager:UpdateSecret",
|
|
||||||
"secretsmanager:DescribeSecret",
|
|
||||||
"secretsmanager:TagResource",
|
|
||||||
"secretsmanager:UntagResource",
|
|
||||||
"kms:ListKeys",
|
|
||||||
"kms:ListAliases",
|
|
||||||
"kms:Encrypt",
|
|
||||||
"kms:Decrypt"
|
|
||||||
],
|
|
||||||
"Resource": "*"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="AWS Parameter Store">
|
|
||||||
Use the following custom policy to grant the minimum permissions required by Infisical to sync secrets to AWS Parameter Store:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": [
|
|
||||||
{
|
|
||||||
"Sid": "AllowSSMAccess",
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Action": [
|
|
||||||
"ssm:PutParameter",
|
|
||||||
"ssm:DeleteParameter",
|
|
||||||
"ssm:GetParameters",
|
|
||||||
"ssm:GetParametersByPath",
|
|
||||||
"ssm:DescribeParameters",
|
|
||||||
"ssm:DeleteParameters",
|
|
||||||
"ssm:AddTagsToResource", // if you need to add tags to secrets
|
|
||||||
"kms:ListKeys", // if you need to specify the KMS key
|
|
||||||
"kms:ListAliases", // if you need to specify the KMS key
|
|
||||||
"kms:Encrypt", // if you need to specify the KMS key
|
|
||||||
"kms:Decrypt" // if you need to specify the KMS key
|
|
||||||
],
|
|
||||||
"Resource": "*"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
</AccordionGroup>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
</Step>
|
|
||||||
|
|
||||||
<Step title="Copy the AWS IAM Role ARN">
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
|
|
||||||
<Step title="Setup AWS Connection in Infisical">
|
|
||||||
<Tabs>
|
|
||||||
<Tab title="Infisical UI">
|
|
||||||
1. Navigate to the App Connections tab on the Organization Settings page.
|
|
||||||

|
|
||||||
|
|
||||||
2. Select the **AWS Connection** option.
|
|
||||||

|
|
||||||
|
|
||||||
3. Select the **Assume Role** method option and provide the **AWS IAM Role ARN** obtained from the previous step and press **Connect to AWS**.
|
|
||||||

|
|
||||||
|
|
||||||
4. Your **AWS Connection** is now available for use.
|
|
||||||

|
|
||||||
</Tab>
|
|
||||||
<Tab title="API">
|
|
||||||
To create an AWS Connection, make an API request to the [Create AWS
|
|
||||||
Connection](/api-reference/endpoints/app-connections/aws/create) API endpoint.
|
|
||||||
|
|
||||||
### Sample request
|
|
||||||
|
|
||||||
```bash Request
|
|
||||||
curl --request POST \
|
|
||||||
--url https://app.infisical.com/api/v1/app-connections/aws \
|
|
||||||
--header 'Content-Type: application/json' \
|
|
||||||
--data '{
|
|
||||||
"name": "my-aws-connection",
|
|
||||||
"method": "assume-role",
|
|
||||||
"credentials": {
|
|
||||||
"roleArn": "...",
|
|
||||||
}
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Sample response
|
|
||||||
|
|
||||||
```bash Response
|
|
||||||
{
|
|
||||||
"appConnection": {
|
|
||||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
|
||||||
"name": "my-aws-connection",
|
|
||||||
"version": 123,
|
|
||||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
|
||||||
"createdAt": "2023-11-07T05:31:56Z",
|
|
||||||
"updatedAt": "2023-11-07T05:31:56Z",
|
|
||||||
"app": "aws",
|
|
||||||
"method": "assume-role",
|
|
||||||
"credentials": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
<Tab title="Access Key">
|
|
||||||
Infisical will use the provided **Access Key ID** and **Secret Key** to connect to your AWS instance.
|
|
||||||
|
|
||||||
**Prerequisites:**
|
|
||||||
|
|
||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step title="Create the Managing User IAM Role for Infisical">
|
|
||||||
1. Navigate to the [Create IAM Role](https://console.aws.amazon.com/iamv2/home#/roles/create?step=selectEntities) page in your AWS Console.
|
|
||||||

|
|
||||||
|
|
||||||
2. Select **AWS Account** as the **Trusted Entity Type**.
|
|
||||||
3. Choose **Another AWS Account** and enter **381492033652** (Infisical AWS Account ID). This restricts the role to be assumed only by Infisical. If self-hosting, provide your AWS account number instead.
|
|
||||||
4. Optionally, enable **Require external ID** and enter your **Organization ID** to further enhance security.
|
|
||||||
</Step>
|
|
||||||
|
|
||||||
<Step title="Add Required Permissions for the IAM Role">
|
|
||||||
Depending on your use case, add one or more of the following policies to your IAM Role:
|
|
||||||
|
|
||||||
<Tabs>
|
|
||||||
<Tab title="Secrets Sync">
|
|
||||||
<AccordionGroup>
|
|
||||||
<Accordion title="AWS Secrets Manager">
|
|
||||||
Use the following custom policy to grant the minimum permissions required by Infisical to sync secrets to AWS Secrets Manager:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": [
|
|
||||||
{
|
|
||||||
"Sid": "AllowSecretsManagerAccess",
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Action": [
|
|
||||||
"secretsmanager:GetSecretValue",
|
|
||||||
"secretsmanager:CreateSecret",
|
|
||||||
"secretsmanager:UpdateSecret",
|
|
||||||
"secretsmanager:DescribeSecret",
|
|
||||||
"secretsmanager:TagResource",
|
|
||||||
"secretsmanager:UntagResource",
|
|
||||||
"kms:ListKeys",
|
|
||||||
"kms:ListAliases",
|
|
||||||
"kms:Encrypt",
|
|
||||||
"kms:Decrypt"
|
|
||||||
],
|
|
||||||
"Resource": "*"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="AWS Parameter Store">
|
|
||||||
Use the following custom policy to grant the minimum permissions required by Infisical to sync secrets to AWS Parameter Store:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": [
|
|
||||||
{
|
|
||||||
"Sid": "AllowSSMAccess",
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Action": [
|
|
||||||
"ssm:PutParameter",
|
|
||||||
"ssm:DeleteParameter",
|
|
||||||
"ssm:GetParameters",
|
|
||||||
"ssm:GetParametersByPath",
|
|
||||||
"ssm:DescribeParameters",
|
|
||||||
"ssm:DeleteParameters",
|
|
||||||
"ssm:AddTagsToResource", // if you need to add tags to secrets
|
|
||||||
"kms:ListKeys", // if you need to specify the KMS key
|
|
||||||
"kms:ListAliases", // if you need to specify the KMS key
|
|
||||||
"kms:Encrypt", // if you need to specify the KMS key
|
|
||||||
"kms:Decrypt" // if you need to specify the KMS key
|
|
||||||
],
|
|
||||||
"Resource": "*"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
</AccordionGroup>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
</Step>
|
|
||||||
<Step title="Obtain Access Key ID and Secret Access Key">
|
|
||||||
Retrieve an AWS **Access Key ID** and a **Secret Key** for your IAM user in **IAM > Users > User > Security credentials > Access keys**.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Setup AWS Connection in Infisical">
|
|
||||||
<Tabs>
|
|
||||||
<Tab title="Infisical UI">
|
|
||||||
1. Navigate to the App Connections tab on the Organization Settings page.
|
|
||||||

|
|
||||||
|
|
||||||
2. Select the **AWS Connection** option.
|
|
||||||

|
|
||||||
|
|
||||||
3. Select the **Access Key** method option and provide the **Access Key ID** and **Secret Key** obtained from the previous step and press **Connect to AWS**.
|
|
||||||

|
|
||||||
|
|
||||||
4. Your **AWS Connection** is now available for use.
|
|
||||||

|
|
||||||
</Tab>
|
|
||||||
<Tab title="API">
|
|
||||||
To create an AWS Connection, make an API request to the [Create AWS
|
|
||||||
Connection](/api-reference/endpoints/app-connections/aws/create) API endpoint.
|
|
||||||
|
|
||||||
### Sample request
|
|
||||||
|
|
||||||
```bash Request
|
|
||||||
curl --request POST \
|
|
||||||
--url https://app.infisical.com/api/v1/app-connections/aws \
|
|
||||||
--header 'Content-Type: application/json' \
|
|
||||||
--data '{
|
|
||||||
"name": "my-aws-connection",
|
|
||||||
"method": "access-key",
|
|
||||||
"credentials": {
|
|
||||||
"accessKeyId": "...",
|
|
||||||
"secretKey": "..."
|
|
||||||
}
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Sample response
|
|
||||||
|
|
||||||
```bash Response
|
|
||||||
{
|
|
||||||
"appConnection": {
|
|
||||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
|
||||||
"name": "my-aws-connection",
|
|
||||||
"version": 123,
|
|
||||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
|
||||||
"createdAt": "2023-11-07T05:31:56Z",
|
|
||||||
"updatedAt": "2023-11-07T05:31:56Z",
|
|
||||||
"app": "aws",
|
|
||||||
"method": "access-key",
|
|
||||||
"credentials": {
|
|
||||||
"accessKeyId": "..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
@ -1,169 +0,0 @@
|
|||||||
---
|
|
||||||
title: "GitHub Connection"
|
|
||||||
description: "Learn how to configure a GitHub Connection for Infisical."
|
|
||||||
---
|
|
||||||
|
|
||||||
Infisical supports two methods for connecting to GitHub.
|
|
||||||
|
|
||||||
<Tabs>
|
|
||||||
<Tab title="GitHub App (Recommended)">
|
|
||||||
Infisical will use a GitHub App with finely grained permissions to connect to GitHub.
|
|
||||||
|
|
||||||
**Prerequisites:**
|
|
||||||
|
|
||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
|
||||||
|
|
||||||
<Accordion title="Self-Hosted Instance">
|
|
||||||
Using the GitHub integration with app authentication on a self-hosted instance of Infisical requires configuring an application on GitHub
|
|
||||||
and registering your instance with it.
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step title="Create an application on GitHub">
|
|
||||||
Navigate to the GitHub app settings [here](https://github.com/settings/apps). Click **New GitHub App**.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Give the application a name, a homepage URL (your self-hosted domain i.e. `https://your-domain.com`), and a callback URL (i.e. `https://your-domain.com/app-connections/github/oauth/callback`).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Enable request user authorization during app installation.
|
|
||||||

|
|
||||||
|
|
||||||
Disable webhook by unchecking the Active checkbox.
|
|
||||||

|
|
||||||
|
|
||||||
Set the repository permissions as follows: Metadata: Read-only, Secrets: Read and write, Environments: Read and write, Actions: Read.
|
|
||||||

|
|
||||||
|
|
||||||
Similarly, set the organization permissions as follows: Secrets: Read and write.
|
|
||||||

|
|
||||||
|
|
||||||
Create the Github application.
|
|
||||||

|
|
||||||
|
|
||||||
<Note>
|
|
||||||
If you have a GitHub organization, you can create an application under it
|
|
||||||
in your organization Settings > Developer settings > GitHub Apps > New GitHub App.
|
|
||||||
</Note>
|
|
||||||
</Step>
|
|
||||||
<Step title="Add your application credentials to Infisical">
|
|
||||||
Generate a new **Client Secret** for your GitHub application.
|
|
||||||

|
|
||||||
|
|
||||||
Generate a new **Private Key** for your Github application.
|
|
||||||

|
|
||||||
|
|
||||||
Obtain the necessary Github application credentials. This would be the application slug, client ID, app ID, client secret, and private key.
|
|
||||||

|
|
||||||
|
|
||||||
Back in your Infisical instance, add the five new environment variables for the credentials of your GitHub application:
|
|
||||||
|
|
||||||
- `INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID`: The **Client ID** of your GitHub application.
|
|
||||||
- `INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET`: The **Client Secret** of your GitHub application.
|
|
||||||
- `INF_APP_CONNECTION_GITHUB_APP_CLIENT_SLUG`: The **Slug** of your GitHub application. This is the one found in the URL.
|
|
||||||
- `INF_APP_CONNECTION_GITHUB_APP_CLIENT_APP_ID`: The **App ID** of your GitHub application.
|
|
||||||
- `INF_APP_CONNECTION_GITHUB_APP_CLIENT_PRIVATE_KEY`: The **Private Key** of your GitHub application.
|
|
||||||
|
|
||||||
Once added, restart your Infisical instance and use the GitHub integration via app authentication.
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
## Setup GitHub Connection in Infisical
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step title="Navigate to the App Connections">
|
|
||||||
Navigate to the **App Connections** tab on the **Organization Settings** page.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Add Connection">
|
|
||||||
Select the **GitHub Connection** option from the connection options modal.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Authorize Connection">
|
|
||||||
Select the **GitHub App** method and click **Connect to GitHub**.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Install GitHub App">
|
|
||||||
You will then be redirected to the GitHub app installation page.
|
|
||||||
|
|
||||||
Install and authorize the GitHub application. This will redirect you back to Infisical's App Connections page.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Connection Created">
|
|
||||||
Your **GitHub Connection** is now available for use.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
</Tab>
|
|
||||||
<Tab title="OAuth">
|
|
||||||
Infisical will use an OAuth App to connect to GitHub.
|
|
||||||
|
|
||||||
**Prerequisites:**
|
|
||||||
|
|
||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
|
||||||
|
|
||||||
<Accordion title="Self-Hosted Instance">
|
|
||||||
Using the GitHub integration on a self-hosted instance of Infisical requires configuring an OAuth application in GitHub
|
|
||||||
and registering your instance with it.
|
|
||||||
<Steps>
|
|
||||||
<Step title="Create an OAuth application in GitHub">
|
|
||||||
Navigate to your user Settings > Developer settings > OAuth Apps to create a new GitHub OAuth application.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Create the OAuth application. As part of the form, set the **Homepage URL** to your self-hosted domain `https://your-domain.com`
|
|
||||||
and the **Authorization callback URL** to `https://your-domain.com/app-connections/github/oauth/callback`.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<Note>
|
|
||||||
If you have a GitHub organization, you can create an OAuth application under it
|
|
||||||
in your organization Settings > Developer settings > OAuth Apps > New Org OAuth App.
|
|
||||||
</Note>
|
|
||||||
</Step>
|
|
||||||
<Step title="Add your OAuth application credentials to Infisical">
|
|
||||||
Obtain the **Client ID** and generate a new **Client Secret** for your GitHub OAuth application.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Back in your Infisical instance, add two new environment variables for the credentials of your GitHub OAuth application:
|
|
||||||
|
|
||||||
- `INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID`: The **Client ID** of your GitHub OAuth application.
|
|
||||||
- `INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET`: The **Client Secret** of your GitHub OAuth application.
|
|
||||||
|
|
||||||
Once added, restart your Infisical instance and use the GitHub integration.
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
## Setup GitHub Connection in Infisical
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step title="Navigate to the App Connections">
|
|
||||||
Navigate to the **App Connections** tab on the **Organization Settings** page.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Add Connection">
|
|
||||||
Select the **GitHub Connection** option from the connection options modal.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Authorize Connection">
|
|
||||||
Select the **OAuth** method and click **Connect to GitHub**.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Grant Access">
|
|
||||||
You will then be redirected to the GitHub to grant Infisical access to your GitHub account (organization and repo privileges).
|
|
||||||
Once granted, you will redirect you back to Infisical's App Connections page.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Connection Created">
|
|
||||||
Your **GitHub Connection** is now available for use.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
@ -1,77 +0,0 @@
|
|||||||
---
|
|
||||||
sidebarTitle: "Overview"
|
|
||||||
description: "Learn how to manage and configure third-party app connections with Infisical."
|
|
||||||
---
|
|
||||||
|
|
||||||
App Connections enable your organization to integrate Infisical with third-party services in a secure and versatile way.
|
|
||||||
|
|
||||||
## Concept
|
|
||||||
|
|
||||||
App Connections are an organization-level resource used to establish connections with third-party applications
|
|
||||||
that can be used across Infisical projects. Example use cases include syncing secrets, generating dynamic secrets, and more.
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
%%{init: {'flowchart': {'curve': 'linear'} } }%%
|
|
||||||
graph TD
|
|
||||||
A[AWS]
|
|
||||||
B[AWS Connection]
|
|
||||||
C[Project 1 Secret Sync]
|
|
||||||
D[Project 2 Secret Sync]
|
|
||||||
E[Project 3 Generate Dynamic Secret]
|
|
||||||
|
|
||||||
B --> A
|
|
||||||
C --> B
|
|
||||||
D --> B
|
|
||||||
E --> B
|
|
||||||
|
|
||||||
classDef default fill:#ffffff,stroke:#666,stroke-width:2px,rx:10px,color:black
|
|
||||||
classDef aws fill:#FFF2B2,stroke:#E6C34A,stroke-width:2px,color:black,rx:15px
|
|
||||||
classDef project fill:#E6F4FF,stroke:#0096D6,stroke-width:2px,color:black,rx:15px
|
|
||||||
classDef connection fill:#F4FFE6,stroke:#96D600,stroke-width:2px,color:black,rx:15px
|
|
||||||
|
|
||||||
class A aws
|
|
||||||
class B connection
|
|
||||||
class C,D,E project
|
|
||||||
```
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Workflow
|
|
||||||
|
|
||||||
App Connections require initial setup in both your third-party application and Infisical. Follow these steps to establish a secure connection:
|
|
||||||
|
|
||||||
<Note>
|
|
||||||
For step-by-step guides specific to each application, refer to the App Connections section in the Navigation Bar.
|
|
||||||
</Note>
|
|
||||||
|
|
||||||
1. <strong>Create Access Entity:</strong> If necessary, create an entity such as a service account or role within the third-party application you want to connect to. Be sure
|
|
||||||
to limit the access of this entity to the minimal permission set required to perform the operations you need. For example:
|
|
||||||
- For secret syncing: Read/write permissions to specific secret stores
|
|
||||||
- For dynamic secrets: Permissions to create temporary credentials
|
|
||||||
|
|
||||||
<Tip>
|
|
||||||
Whenever possible, Infisical encourages creating a designated service account for your App Connection to limit the scope of permissions based on your use-case.
|
|
||||||
</Tip>
|
|
||||||
|
|
||||||
2. <strong>Generate Authentication Credentials:</strong> Obtain the required credentials from your third-party application. These can vary between applications and might be:
|
|
||||||
- an API key or access token
|
|
||||||
- A client ID and secret pair
|
|
||||||
- other credentials, etc.
|
|
||||||
|
|
||||||
3. <strong>Create App Connection:</strong> Configure the connection in Infisical using your generated credentials through either the UI or API.
|
|
||||||
|
|
||||||
<Info>
|
|
||||||
Some App Connections can only be created via the UI such as connections using OAuth.
|
|
||||||
</Info>
|
|
||||||
|
|
||||||
4. <strong>Utilize the Connection:</strong> Use your App Connection for various features across Infisical such as our Secrets Sync by selecting it via the dropdown menu
|
|
||||||
in the UI or by passing the associated `connectionId` when generating resources via the API.
|
|
||||||
|
|
||||||
<Note>
|
|
||||||
Infisical is continuously expanding its third-party application support. If your desired application isn't listed,
|
|
||||||
you can still use previous methods of connecting to it such as our Native Integrations.
|
|
||||||
</Note>
|
|
@ -341,14 +341,6 @@
|
|||||||
"cli/faq"
|
"cli/faq"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"group": "App Connections",
|
|
||||||
"pages": [
|
|
||||||
"integrations/app-connections/overview",
|
|
||||||
"integrations/app-connections/aws",
|
|
||||||
"integrations/app-connections/github"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"group": "Infrastructure Integrations",
|
"group": "Infrastructure Integrations",
|
||||||
"pages": [
|
"pages": [
|
||||||
@ -765,33 +757,6 @@
|
|||||||
"api-reference/endpoints/identity-specific-privilege/list"
|
"api-reference/endpoints/identity-specific-privilege/list"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"group": "App Connections",
|
|
||||||
"pages": [
|
|
||||||
"api-reference/endpoints/app-connections/list",
|
|
||||||
"api-reference/endpoints/app-connections/options",
|
|
||||||
{ "group": "AWS",
|
|
||||||
"pages": [
|
|
||||||
"api-reference/endpoints/app-connections/aws/list",
|
|
||||||
"api-reference/endpoints/app-connections/aws/get-by-id",
|
|
||||||
"api-reference/endpoints/app-connections/aws/get-by-name",
|
|
||||||
"api-reference/endpoints/app-connections/aws/create",
|
|
||||||
"api-reference/endpoints/app-connections/aws/update",
|
|
||||||
"api-reference/endpoints/app-connections/aws/delete"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{ "group": "GitHub",
|
|
||||||
"pages": [
|
|
||||||
"api-reference/endpoints/app-connections/github/list",
|
|
||||||
"api-reference/endpoints/app-connections/github/get-by-id",
|
|
||||||
"api-reference/endpoints/app-connections/github/get-by-name",
|
|
||||||
"api-reference/endpoints/app-connections/github/create",
|
|
||||||
"api-reference/endpoints/app-connections/github/update",
|
|
||||||
"api-reference/endpoints/app-connections/github/delete"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"group": "Integrations",
|
"group": "Integrations",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
@ -418,53 +418,7 @@ When set, all visits to the Infisical login page will automatically redirect use
|
|||||||
information.
|
information.
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
## App Connections
|
## Native secret integrations
|
||||||
|
|
||||||
You can configure third-party app connections for re-use across Infisical Projects.
|
|
||||||
|
|
||||||
<Accordion title="AWS Assume Role Connection">
|
|
||||||
<ParamField query="INF_APP_CONNECTION_AWS_ACCESS_KEY_ID" type="string" default="none" optional>
|
|
||||||
The AWS IAM User access key ID for assuming roles
|
|
||||||
</ParamField>
|
|
||||||
|
|
||||||
<ParamField query="INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY" type="string" default="none" optional>
|
|
||||||
The AWS IAM User secret key for assuming roles
|
|
||||||
</ParamField>
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="GitHub App Connection">
|
|
||||||
<ParamField query="INF_APP_CONNECTION_GITHUB_APP_ID" type="string" default="none" optional>
|
|
||||||
The ID of the GitHub App
|
|
||||||
</ParamField>
|
|
||||||
|
|
||||||
<ParamField query="INF_APP_CONNECTION_GITHUB_APP_SLUG" type="string" default="none" optional>
|
|
||||||
The slug of the GitHub App
|
|
||||||
</ParamField>
|
|
||||||
|
|
||||||
<ParamField query="INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID" type="string" default="none" optional>
|
|
||||||
The client ID for the GitHub App
|
|
||||||
</ParamField>
|
|
||||||
|
|
||||||
<ParamField query="INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET" type="string" default="none" optional>
|
|
||||||
The client secret for the GitHub App
|
|
||||||
</ParamField>
|
|
||||||
|
|
||||||
<ParamField query="INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY" type="string" default="none" optional>
|
|
||||||
The private key for the GitHub App
|
|
||||||
</ParamField>
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="GitHub OAuth Connection">
|
|
||||||
<ParamField query="INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID" type="string" default="none" optional>
|
|
||||||
The OAuth2 client ID for GitHub OAuth Connection
|
|
||||||
</ParamField>
|
|
||||||
|
|
||||||
<ParamField query="INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET" type="string" default="none" optional>
|
|
||||||
The OAuth2 client secret for GitHub OAuth Connection
|
|
||||||
</ParamField>
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
## Native Secret Integrations
|
|
||||||
|
|
||||||
To help you sync secrets from Infisical to services such as Github and Gitlab, Infisical provides native integrations out of the box.
|
To help you sync secrets from Infisical to services such as Github and Gitlab, Infisical provides native integrations out of the box.
|
||||||
|
|
||||||
@ -538,7 +492,7 @@ To help you sync secrets from Infisical to services such as Github and Gitlab, I
|
|||||||
</ParamField>
|
</ParamField>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="AWS Integration">
|
<Accordion title="AWS">
|
||||||
<ParamField query="CLIENT_ID_AWS_INTEGRATION" type="string" default="none" optional>
|
<ParamField query="CLIENT_ID_AWS_INTEGRATION" type="string" default="none" optional>
|
||||||
The AWS IAM User access key for assuming roles.
|
The AWS IAM User access key for assuming roles.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 15 KiB |
@ -3,7 +3,6 @@ import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { Modal, ModalContent, ModalTrigger, Select, SelectItem } from "@app/components/v2";
|
import { Modal, ModalContent, ModalTrigger, Select, SelectItem } from "@app/components/v2";
|
||||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
|
||||||
|
|
||||||
enum Region {
|
enum Region {
|
||||||
US = "us",
|
US = "us",
|
||||||
@ -80,7 +79,10 @@ export const RegionSelect = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const shouldDisplay =
|
const shouldDisplay =
|
||||||
isInfisicalCloud() || window.location.origin.includes("http://localhost:8080");
|
window.location.origin.includes("https://app.infisical.com") ||
|
||||||
|
window.location.origin.includes("https://us.infisical.com") ||
|
||||||
|
window.location.origin.includes("https://eu.infisical.com") ||
|
||||||
|
window.location.origin.includes("http://localhost:8080");
|
||||||
|
|
||||||
// only display region select for cloud
|
// only display region select for cloud
|
||||||
if (!shouldDisplay) return null;
|
if (!shouldDisplay) return null;
|
||||||
|
@ -44,7 +44,7 @@ export const FormLabel = ({
|
|||||||
)}
|
)}
|
||||||
{tooltipText && (
|
{tooltipText && (
|
||||||
<Tooltip content={tooltipText} className={tooltipClassName}>
|
<Tooltip content={tooltipText} className={tooltipClassName}>
|
||||||
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
|
<FontAwesomeIcon icon={faQuestionCircle} size="1x" className="ml-2" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Label.Root>
|
</Label.Root>
|
||||||
|
@ -23,8 +23,7 @@ export enum OrgPermissionSubjects {
|
|||||||
Kms = "kms",
|
Kms = "kms",
|
||||||
AdminConsole = "organization-admin-console",
|
AdminConsole = "organization-admin-console",
|
||||||
AuditLogs = "audit-logs",
|
AuditLogs = "audit-logs",
|
||||||
ProjectTemplates = "project-templates",
|
ProjectTemplates = "project-templates"
|
||||||
AppConnections = "app-connections"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OrgPermissionAdminConsoleAction {
|
export enum OrgPermissionAdminConsoleAction {
|
||||||
@ -48,7 +47,6 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
||||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates];
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.AppConnections];
|
|
||||||
|
|
||||||
export type TOrgPermission = MongoAbility<OrgPermissionSet>;
|
export type TOrgPermission = MongoAbility<OrgPermissionSet>;
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import { faGithub } from "@fortawesome/free-brands-svg-icons";
|
|
||||||
import { faKey, faPassport, faUser } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
|
|
||||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
|
||||||
import {
|
|
||||||
AwsConnectionMethod,
|
|
||||||
GitHubConnectionMethod,
|
|
||||||
TAppConnection
|
|
||||||
} from "@app/hooks/api/appConnections/types";
|
|
||||||
|
|
||||||
export const APP_CONNECTION_MAP: Record<AppConnection, { name: string; image: string }> = {
|
|
||||||
[AppConnection.AWS]: { name: "AWS", image: "Amazon Web Services.png" },
|
|
||||||
[AppConnection.GitHub]: { name: "GitHub", image: "GitHub.png" }
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
|
|
||||||
switch (method) {
|
|
||||||
case GitHubConnectionMethod.App:
|
|
||||||
return { name: "GitHub App", icon: faGithub };
|
|
||||||
case GitHubConnectionMethod.OAuth:
|
|
||||||
return { name: "OAuth", icon: faPassport };
|
|
||||||
case AwsConnectionMethod.AccessKey:
|
|
||||||
return { name: "Access Key", icon: faKey };
|
|
||||||
case AwsConnectionMethod.AssumeRole:
|
|
||||||
return { name: "Assume Role", icon: faUser };
|
|
||||||
default:
|
|
||||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,4 +0,0 @@
|
|||||||
export const isInfisicalCloud = () =>
|
|
||||||
window.location.origin.includes("https://app.infisical.com") ||
|
|
||||||
window.location.origin.includes("https://us.infisical.com") ||
|
|
||||||
window.location.origin.includes("https://eu.infisical.com");
|
|
@ -1,4 +0,0 @@
|
|||||||
export enum AppConnection {
|
|
||||||
AWS = "aws",
|
|
||||||
GitHub = "github"
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export * from "./mutations";
|
|
||||||
export * from "./queries";
|
|
||||||
export * from "./types";
|
|
@ -1,58 +0,0 @@
|
|||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
|
||||||
import { appConnectionKeys } from "@app/hooks/api/appConnections/queries";
|
|
||||||
import {
|
|
||||||
TAppConnectionResponse,
|
|
||||||
TCreateAppConnectionDTO,
|
|
||||||
TDeleteAppConnectionDTO,
|
|
||||||
TUpdateAppConnectionDTO
|
|
||||||
} from "@app/hooks/api/appConnections/types";
|
|
||||||
|
|
||||||
export const useCreateAppConnection = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async ({ app, ...params }: TCreateAppConnectionDTO) => {
|
|
||||||
const { data } = await apiRequest.post<TAppConnectionResponse>(
|
|
||||||
`/api/v1/app-connections/${app}`,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.appConnection;
|
|
||||||
},
|
|
||||||
onSuccess: () => queryClient.invalidateQueries(appConnectionKeys.list())
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useUpdateAppConnection = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async ({ connectionId, app, ...params }: TUpdateAppConnectionDTO) => {
|
|
||||||
const { data } = await apiRequest.patch<TAppConnectionResponse>(
|
|
||||||
`/api/v1/app-connections/${app}/${connectionId}`,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.appConnection;
|
|
||||||
},
|
|
||||||
onSuccess: (_, { connectionId, app }) => {
|
|
||||||
queryClient.invalidateQueries(appConnectionKeys.list());
|
|
||||||
queryClient.invalidateQueries(appConnectionKeys.byId(app, connectionId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useDeleteAppConnection = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async ({ connectionId, app }: TDeleteAppConnectionDTO) => {
|
|
||||||
const { data } = await apiRequest.delete(`/api/v1/app-connections/${app}/${connectionId}`);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
onSuccess: (_, { connectionId, app }) => {
|
|
||||||
queryClient.invalidateQueries(appConnectionKeys.list());
|
|
||||||
queryClient.invalidateQueries(appConnectionKeys.byId(app, connectionId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,136 +0,0 @@
|
|||||||
import { useMemo } from "react";
|
|
||||||
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
|
||||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
|
||||||
import {
|
|
||||||
TAppConnection,
|
|
||||||
TAppConnectionMap,
|
|
||||||
TAppConnectionOptions,
|
|
||||||
TGetAppConnection,
|
|
||||||
TListAppConnections
|
|
||||||
} from "@app/hooks/api/appConnections/types";
|
|
||||||
import {
|
|
||||||
TAppConnectionOption,
|
|
||||||
TAppConnectionOptionMap
|
|
||||||
} from "@app/hooks/api/appConnections/types/app-options";
|
|
||||||
|
|
||||||
export const appConnectionKeys = {
|
|
||||||
all: ["app-connection"] as const,
|
|
||||||
options: () => [...appConnectionKeys.all, "options"] as const,
|
|
||||||
list: () => [...appConnectionKeys.all, "list"] as const,
|
|
||||||
listByApp: (app: AppConnection) => [...appConnectionKeys.list(), app],
|
|
||||||
byId: (app: AppConnection, templateId: string) =>
|
|
||||||
[...appConnectionKeys.all, app, "by-id", templateId] as const
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useAppConnectionOptions = (
|
|
||||||
options?: Omit<
|
|
||||||
UseQueryOptions<
|
|
||||||
TAppConnectionOption[],
|
|
||||||
unknown,
|
|
||||||
TAppConnectionOption[],
|
|
||||||
ReturnType<typeof appConnectionKeys.options>
|
|
||||||
>,
|
|
||||||
"queryKey" | "queryFn"
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: appConnectionKeys.options(),
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data } = await apiRequest.get<TAppConnectionOptions>(
|
|
||||||
"/api/v1/app-connections/options"
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.appConnectionOptions;
|
|
||||||
},
|
|
||||||
...options
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetAppConnectionOption = <T extends AppConnection>(app: T) => {
|
|
||||||
const { data: options = [], isLoading } = useAppConnectionOptions();
|
|
||||||
|
|
||||||
return useMemo(
|
|
||||||
() => ({
|
|
||||||
option: (options.find((opt) => opt.app === app) as TAppConnectionOptionMap[T]) ?? {},
|
|
||||||
isLoading
|
|
||||||
}),
|
|
||||||
[options, app]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useListAppConnections = (
|
|
||||||
options?: Omit<
|
|
||||||
UseQueryOptions<
|
|
||||||
TAppConnection[],
|
|
||||||
unknown,
|
|
||||||
TAppConnection[],
|
|
||||||
ReturnType<typeof appConnectionKeys.list>
|
|
||||||
>,
|
|
||||||
"queryKey" | "queryFn"
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: appConnectionKeys.list(),
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data } = await apiRequest.get<TListAppConnections<TAppConnection>>(
|
|
||||||
"/api/v1/app-connections"
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.appConnections;
|
|
||||||
},
|
|
||||||
...options
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useListAppConnectionsByApp = <T extends AppConnection>(
|
|
||||||
app: T,
|
|
||||||
options?: Omit<
|
|
||||||
UseQueryOptions<
|
|
||||||
TAppConnectionMap[T][],
|
|
||||||
unknown,
|
|
||||||
TAppConnectionMap[T][],
|
|
||||||
ReturnType<typeof appConnectionKeys.listByApp>
|
|
||||||
>,
|
|
||||||
"queryKey" | "queryFn"
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: appConnectionKeys.listByApp(app),
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data } = await apiRequest.get<TListAppConnections<TAppConnectionMap[T]>>(
|
|
||||||
`/api/v1/app-connections/${app}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.appConnections;
|
|
||||||
},
|
|
||||||
...options
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetAppConnectionById = <T extends AppConnection>(
|
|
||||||
app: T,
|
|
||||||
connectionId: string,
|
|
||||||
options?: Omit<
|
|
||||||
UseQueryOptions<
|
|
||||||
TAppConnectionMap[T],
|
|
||||||
unknown,
|
|
||||||
TAppConnectionMap[T],
|
|
||||||
ReturnType<typeof appConnectionKeys.byId>
|
|
||||||
>,
|
|
||||||
"queryKey" | "queryFn"
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: appConnectionKeys.byId(app, connectionId),
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data } = await apiRequest.get<TGetAppConnection<TAppConnectionMap[T]>>(
|
|
||||||
`/api/v1/app-connections/${app}/${connectionId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.appConnection;
|
|
||||||
},
|
|
||||||
...options
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
|
||||||
|
|
||||||
export type TAppConnectionOptionBase = {
|
|
||||||
name: string;
|
|
||||||
methods: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TAwsConnectionOption = TAppConnectionOptionBase & {
|
|
||||||
app: AppConnection.AWS;
|
|
||||||
accessKeyId?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TGitHubConnectionOption = TAppConnectionOptionBase & {
|
|
||||||
app: AppConnection.GitHub;
|
|
||||||
oauthClientId?: string;
|
|
||||||
appClientSlug?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TAppConnectionOption = TAwsConnectionOption | TGitHubConnectionOption;
|
|
||||||
|
|
||||||
export type TAppConnectionOptionMap = {
|
|
||||||
[AppConnection.AWS]: TAwsConnectionOption;
|
|
||||||
[AppConnection.GitHub]: TGitHubConnectionOption;
|
|
||||||
};
|
|
@ -1,23 +0,0 @@
|
|||||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
|
||||||
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
|
|
||||||
|
|
||||||
export enum AwsConnectionMethod {
|
|
||||||
AssumeRole = "assume-role",
|
|
||||||
AccessKey = "access-key"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TAwsConnection = TRootAppConnection & { app: AppConnection.AWS } & (
|
|
||||||
| {
|
|
||||||
method: AwsConnectionMethod.AccessKey;
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: string;
|
|
||||||
secretAccessKey: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
method: AwsConnectionMethod.AssumeRole;
|
|
||||||
credentials: {
|
|
||||||
roleArn: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,23 +0,0 @@
|
|||||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
|
||||||
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
|
|
||||||
|
|
||||||
export enum GitHubConnectionMethod {
|
|
||||||
App = "github-app",
|
|
||||||
OAuth = "oauth"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub } & (
|
|
||||||
| {
|
|
||||||
method: GitHubConnectionMethod.OAuth;
|
|
||||||
credentials: {
|
|
||||||
code: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
method: GitHubConnectionMethod.App;
|
|
||||||
credentials: {
|
|
||||||
code: string;
|
|
||||||
installationId: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,36 +0,0 @@
|
|||||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
|
||||||
import { TAppConnectionOption } from "@app/hooks/api/appConnections/types/app-options";
|
|
||||||
import { TAwsConnection } from "@app/hooks/api/appConnections/types/aws-connection";
|
|
||||||
import { TGitHubConnection } from "@app/hooks/api/appConnections/types/github-connection";
|
|
||||||
|
|
||||||
export * from "./aws-connection";
|
|
||||||
export * from "./github-connection";
|
|
||||||
|
|
||||||
export type TAppConnection = TAwsConnection | TGitHubConnection;
|
|
||||||
|
|
||||||
export type TListAppConnections<T extends TAppConnection> = { appConnections: T[] };
|
|
||||||
export type TGetAppConnection<T extends TAppConnection> = { appConnection: T };
|
|
||||||
export type TAppConnectionOptions = { appConnectionOptions: TAppConnectionOption[] };
|
|
||||||
export type TAppConnectionResponse = { appConnection: TAppConnection };
|
|
||||||
|
|
||||||
export type TCreateAppConnectionDTO = Pick<
|
|
||||||
TAppConnection,
|
|
||||||
"name" | "credentials" | "method" | "app" | "description"
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type TUpdateAppConnectionDTO = Partial<
|
|
||||||
Pick<TAppConnection, "name" | "credentials" | "description">
|
|
||||||
> & {
|
|
||||||
connectionId: string;
|
|
||||||
app: AppConnection;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TDeleteAppConnectionDTO = {
|
|
||||||
app: AppConnection;
|
|
||||||
connectionId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TAppConnectionMap = {
|
|
||||||
[AppConnection.AWS]: TAwsConnection;
|
|
||||||
[AppConnection.GitHub]: TGitHubConnection;
|
|
||||||
};
|
|
@ -1,9 +0,0 @@
|
|||||||
export type TRootAppConnection = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description?: string | null;
|
|
||||||
version: number;
|
|
||||||
orgId: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
};
|
|
@ -56,7 +56,7 @@ export type TGetUserProjectPermissionDTO = {
|
|||||||
export type TCreateOrgRoleDTO = {
|
export type TCreateOrgRoleDTO = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string | null;
|
description?: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
permissions: TPermission[];
|
permissions: TPermission[];
|
||||||
};
|
};
|
||||||
@ -74,7 +74,7 @@ export type TDeleteOrgRoleDTO = {
|
|||||||
export type TCreateProjectRoleDTO = {
|
export type TCreateProjectRoleDTO = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string | null;
|
description?: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
permissions: TProjectPermission[];
|
permissions: TProjectPermission[];
|
||||||
};
|
};
|
||||||
|
@ -45,5 +45,4 @@ export type SubscriptionPlan = {
|
|||||||
pkiEst: boolean;
|
pkiEst: boolean;
|
||||||
enforceMfa: boolean;
|
enforceMfa: boolean;
|
||||||
projectTemplates: boolean;
|
projectTemplates: boolean;
|
||||||
appConnections: boolean; // TODO: remove once released
|
|
||||||
};
|
};
|
||||||
|