Merge branch 'main' of https://github.com/Infisical/infisical into feat/adds-expiring-scim-token-notification

This commit is contained in:
Piyush Gupta
2025-11-27 19:31:53 +05:30
83 changed files with 1160 additions and 2693 deletions

View File

@@ -31,25 +31,14 @@ SMTP_FROM_NAME=
SMTP_USERNAME= SMTP_USERNAME=
SMTP_PASSWORD= SMTP_PASSWORD=
# Integration # CICD Integration
# Optional only if integration is used
CLIENT_ID_HEROKU=
CLIENT_ID_VERCEL=
CLIENT_ID_NETLIFY=
CLIENT_ID_GITHUB= CLIENT_ID_GITHUB=
CLIENT_ID_GITHUB_APP= CLIENT_ID_GITHUB_APP=
CLIENT_SLUG_GITHUB_APP= CLIENT_SLUG_GITHUB_APP=
CLIENT_ID_GITLAB=
CLIENT_ID_BITBUCKET=
CLIENT_SECRET_HEROKU=
CLIENT_SECRET_VERCEL=
CLIENT_SECRET_NETLIFY=
CLIENT_SECRET_GITHUB= CLIENT_SECRET_GITHUB=
CLIENT_SECRET_GITHUB_APP= CLIENT_SECRET_GITHUB_APP=
CLIENT_ID_GITLAB=
CLIENT_SECRET_GITLAB= CLIENT_SECRET_GITLAB=
CLIENT_SECRET_BITBUCKET=
CLIENT_SLUG_VERCEL=
CLIENT_PRIVATE_KEY_GITHUB_APP= CLIENT_PRIVATE_KEY_GITHUB_APP=
CLIENT_APP_ID_GITHUB_APP= CLIENT_APP_ID_GITHUB_APP=

View File

@@ -1,10 +1,18 @@
import { ProjectMembershipRole } from "@app/db/schemas";
import { DisableRotationErrors } from "@app/ee/services/secret-rotation/secret-rotation-queue"; import { DisableRotationErrors } from "@app/ee/services/secret-rotation/secret-rotation-queue";
import { getConfig } from "@app/lib/config/env";
import { applyJitter } from "@app/lib/delay";
import { NotFoundError } from "@app/lib/errors"; import { NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { TUserDALFactory } from "@app/services/user/user-dal";
import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal"; import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
import { DynamicSecretStatus } from "../dynamic-secret/dynamic-secret-types"; import { DynamicSecretStatus } from "../dynamic-secret/dynamic-secret-types";
@@ -15,7 +23,12 @@ import { TDynamicSecretLeaseConfig } from "./dynamic-secret-lease-types";
type TDynamicSecretLeaseQueueServiceFactoryDep = { type TDynamicSecretLeaseQueueServiceFactoryDep = {
queueService: TQueueServiceFactory; queueService: TQueueServiceFactory;
dynamicSecretLeaseDAL: Pick<TDynamicSecretLeaseDALFactory, "findById" | "deleteById" | "find" | "updateById">; dynamicSecretLeaseDAL: Pick<TDynamicSecretLeaseDALFactory, "findById" | "deleteById" | "find" | "updateById">;
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findById" | "deleteById" | "updateById">; smtpService: Pick<TSmtpService, "sendMail">;
userDAL: Pick<TUserDALFactory, "findById">;
identityDAL: TIdentityDALFactory;
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findById" | "deleteById" | "updateById" | "findOne">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findAllProjectMembers">;
projectDAL: Pick<TProjectDALFactory, "findById">;
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>; dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">; kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
folderDAL: Pick<TSecretFolderDALFactory, "findById">; folderDAL: Pick<TSecretFolderDALFactory, "findById">;
@@ -23,18 +36,24 @@ type TDynamicSecretLeaseQueueServiceFactoryDep = {
export type TDynamicSecretLeaseQueueServiceFactory = { export type TDynamicSecretLeaseQueueServiceFactory = {
pruneDynamicSecret: (dynamicSecretCfgId: string) => Promise<void>; pruneDynamicSecret: (dynamicSecretCfgId: string) => Promise<void>;
setLeaseRevocation: (leaseId: string, expiryAt: Date) => Promise<void>; setLeaseRevocation: (leaseId: string, dynamicSecretId: string, expiryAt: Date) => Promise<void>;
unsetLeaseRevocation: (leaseId: string) => Promise<void>; unsetLeaseRevocation: (leaseId: string) => Promise<void>;
queueFailedRevocation: (leaseId: string, dynamicSecretId: string) => Promise<void>;
init: () => Promise<void>; init: () => Promise<void>;
}; };
const MAX_REVOCATION_RETRY_COUNT = 10;
export const dynamicSecretLeaseQueueServiceFactory = ({ export const dynamicSecretLeaseQueueServiceFactory = ({
queueService, queueService,
dynamicSecretDAL, dynamicSecretDAL,
dynamicSecretProviders, dynamicSecretProviders,
dynamicSecretLeaseDAL, dynamicSecretLeaseDAL,
kmsService, kmsService,
folderDAL folderDAL,
projectMembershipDAL,
projectDAL,
smtpService
}: TDynamicSecretLeaseQueueServiceFactoryDep): TDynamicSecretLeaseQueueServiceFactory => { }: TDynamicSecretLeaseQueueServiceFactoryDep): TDynamicSecretLeaseQueueServiceFactory => {
const pruneDynamicSecret = async (dynamicSecretCfgId: string) => { const pruneDynamicSecret = async (dynamicSecretCfgId: string) => {
await queueService.queuePg<QueueName.DynamicSecretRevocation>( await queueService.queuePg<QueueName.DynamicSecretRevocation>(
@@ -48,10 +67,10 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
); );
}; };
const setLeaseRevocation = async (leaseId: string, expiryAt: Date) => { const setLeaseRevocation = async (leaseId: string, dynamicSecretId: string, expiryAt: Date) => {
await queueService.queuePg<QueueName.DynamicSecretRevocation>( await queueService.queuePg<QueueName.DynamicSecretRevocation>(
QueueJobs.DynamicSecretRevocation, QueueJobs.DynamicSecretRevocation,
{ leaseId }, { leaseId, dynamicSecretId },
{ {
id: leaseId, id: leaseId,
singletonKey: leaseId, singletonKey: leaseId,
@@ -68,10 +87,53 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
await queueService.stopJobByIdPg(QueueName.DynamicSecretRevocation, leaseId); await queueService.stopJobByIdPg(QueueName.DynamicSecretRevocation, leaseId);
}; };
const queueFailedRevocation = async (leaseId: string, dynamicSecretId: string) => {
const appConfig = getConfig();
const retryDelaySeconds = appConfig.isDevelopmentMode ? 1 : Math.floor(applyJitter(3_600_000 * 4) / 1000); // retry every 4 hours with 20% +- jitter (convert ms to seconds for pgboss)
await queueService.queuePg<QueueName.DynamicSecretRevocation>(
QueueJobs.DynamicSecretRevocation,
{ leaseId, isRetry: true, dynamicSecretId },
{
singletonKey: `${leaseId}-retry`, // avoid conflicts with scheduled revocation
retryDelay: retryDelaySeconds,
retryLimit: MAX_REVOCATION_RETRY_COUNT, // we dont want it to ever hit the limit, we want the expireInHours to take effect.
expireInHours: 23 // if we set it to 24 hours, pgboss will complain that the expireIn is too high
}
);
};
const $queueDynamicSecretLeaseRevocationFailedEmail = async (leaseId: string, dynamicSecretId: string) => {
const appConfig = getConfig();
const delay = appConfig.isDevelopmentMode ? 1_000 * 60 : 1_000 * 60 * 15; // 1 minute in development, 15 minutes in production
await queueService.queue(
QueueName.DynamicSecretLeaseRevocationFailedEmail,
QueueJobs.DynamicSecretLeaseRevocationFailedEmail,
{
leaseId
},
{
jobId: `dynamic-secret-lease-revocation-failed-email-${dynamicSecretId}`,
delay,
attempts: 3,
backoff: {
type: "exponential",
delay: 1000 * 60 // 1 minute
},
removeOnComplete: true,
removeOnFail: true
}
);
};
const $dynamicSecretQueueJob = async ( const $dynamicSecretQueueJob = async (
jobName: string, jobName: string,
jobId: string, jobId: string,
data: { leaseId: string } | { dynamicSecretCfgId: string } data: { leaseId: string; dynamicSecretId: string; isRetry?: boolean } | { dynamicSecretCfgId: string },
retryCount?: number
): Promise<void> => { ): Promise<void> => {
try { try {
if (jobName === QueueJobs.DynamicSecretRevocation) { if (jobName === QueueJobs.DynamicSecretRevocation) {
@@ -79,7 +141,9 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
logger.info("Dynamic secret lease revocation started: ", leaseId, jobId); logger.info("Dynamic secret lease revocation started: ", leaseId, jobId);
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId); const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" }); if (!dynamicSecretLease) {
throw new DisableRotationErrors({ message: "Dynamic secret lease not found" });
}
const folder = await folderDAL.findById(dynamicSecretLease.dynamicSecret.folderId); const folder = await folderDAL.findById(dynamicSecretLease.dynamicSecret.folderId);
if (!folder) if (!folder)
@@ -150,7 +214,7 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
} }
logger.info("Finished dynamic secret job", jobId); logger.info("Finished dynamic secret job", jobId);
} catch (error) { } catch (error) {
logger.error(error); logger.error(error, "Failed to delete dynamic secret");
if (jobName === QueueJobs.DynamicSecretPruning) { if (jobName === QueueJobs.DynamicSecretPruning) {
const { dynamicSecretCfgId } = data as { dynamicSecretCfgId: string }; const { dynamicSecretCfgId } = data as { dynamicSecretCfgId: string };
@@ -161,20 +225,97 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
} }
if (jobName === QueueJobs.DynamicSecretRevocation) { if (jobName === QueueJobs.DynamicSecretRevocation) {
const { leaseId } = data as { leaseId: string }; const { leaseId, isRetry, dynamicSecretId } = data as {
leaseId: string;
isRetry?: boolean;
dynamicSecretId: string;
};
await dynamicSecretLeaseDAL.updateById(leaseId, { await dynamicSecretLeaseDAL.updateById(leaseId, {
status: DynamicSecretStatus.FailedDeletion, status: DynamicSecretStatus.FailedDeletion,
statusDetails: (error as Error)?.message?.slice(0, 255) statusDetails: `${(error as Error)?.message?.slice(0, 255)} - Retrying automatically`
}); });
// only add to retry queue if this is not a retry, and if the error is not a DisableRotationErrors error
if (!isRetry && !(error instanceof DisableRotationErrors)) {
// if revocation fails, we should stop the job and queue a new job to retry the revocation at a later time.
await queueService.stopJobByIdPg(QueueName.DynamicSecretRevocation, jobId);
await queueService.stopRepeatableJobByJobId(QueueName.DynamicSecretRevocation, jobId);
await queueFailedRevocation(leaseId, dynamicSecretId);
// if its the last attempt, and the error isn't a DisableRotationErrors error, send an email to the project admins (debounced)
} else if (isRetry && !(error instanceof DisableRotationErrors)) {
if (retryCount && retryCount === MAX_REVOCATION_RETRY_COUNT) {
// if all retries fail, we should also stop the automatic revocation job.
// the ID of the revocation job is set to the leaseId, so we can use that to stop the job
// we dont have to stop the retry job, because if we hit this point, its the last attempt and the retry job will be stopped by pgboss itself after this point,
await queueService.stopJobByIdPg(QueueName.DynamicSecretRevocation, leaseId);
await queueService.stopRepeatableJobByJobId(QueueName.DynamicSecretRevocation, leaseId);
await $queueDynamicSecretLeaseRevocationFailedEmail(leaseId, dynamicSecretId);
}
}
} }
if (error instanceof DisableRotationErrors) { if (error instanceof DisableRotationErrors) {
if (jobId) { if (jobId) {
await queueService.stopRepeatableJobByJobId(QueueName.DynamicSecretRevocation, jobId); await queueService.stopRepeatableJobByJobId(QueueName.DynamicSecretRevocation, jobId);
await queueService.stopJobByIdPg(QueueName.DynamicSecretRevocation, jobId); await queueService.stopJobByIdPg(QueueName.DynamicSecretRevocation, jobId);
} }
} else {
// propagate to next part
throw error;
}
}
};
// send alert email once all revocation attempts have failed
const $dynamicSecretLeaseRevocationFailedEmailJob = async (jobId: string, data: { leaseId: string }) => {
try {
const appCfg = getConfig();
const { leaseId } = data;
logger.info(
{ leaseId, jobId },
"Dynamic secret revocation failed. Notifying project admins about failed revocation."
);
const lease = await dynamicSecretLeaseDAL.findById(leaseId);
if (!lease) {
throw new DisableRotationErrors({ message: "Dynamic secret lease not found" });
}
const folder = await folderDAL.findById(lease.dynamicSecret.folderId);
if (!folder) throw new NotFoundError({ message: `Failed to find folder with ${lease.dynamicSecret.folderId}` });
const project = await projectDAL.findById(folder.projectId);
const projectMembers = await projectMembershipDAL.findAllProjectMembers(project.id);
const projectAdmins = projectMembers.filter((member) =>
member.roles.some((role) => role.role === ProjectMembershipRole.Admin)
);
await smtpService.sendMail({
recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean),
template: SmtpTemplates.DynamicSecretLeaseRevocationFailed,
subjectLine: "Dynamic Secret Lease Revocation Failed",
substitutions: {
dynamicSecretLeaseUrl: `${appCfg.SITE_URL}/organizations/${project.orgId}/projects/secret-management/${project.id}/secrets/${folder.environment.envSlug}?dynamicSecretId=${lease.dynamicSecret.id}&filterBy=dynamic&search=${lease.dynamicSecret.name}`,
dynamicSecretName: lease.dynamicSecret.name,
projectName: project.name,
environmentSlug: folder.environment.envSlug,
errorMessage: lease.statusDetails || "An unknown error occurred"
}
});
} catch (error) {
logger.error(error, "Failed to send dynamic secret lease revocation failed email");
if (error instanceof DisableRotationErrors) {
if (jobId) {
await queueService.stopRepeatableJobByJobId(QueueName.DynamicSecretLeaseRevocationFailedEmail, jobId);
await queueService.stopJobById(QueueName.DynamicSecretLeaseRevocationFailedEmail, jobId);
}
} else {
throw error;
} }
// propogate to next part
throw error;
} }
}; };
@@ -182,14 +323,21 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
await $dynamicSecretQueueJob(job.name, job.id as string, job.data); await $dynamicSecretQueueJob(job.name, job.id as string, job.data);
}); });
// we use redis for sending the email because:
// 1. we are insensitive to losing the jobs in queue in case of a disaster event
// 2. pgboss does not support exclusive job keys on v0.10.x, and upgrading to v0.11.x which supports exclusive jobs comes with a lot of breaking changes, and we would need to manually migrate our existing jobs to the new version
queueService.start(QueueName.DynamicSecretLeaseRevocationFailedEmail, async (job) => {
await $dynamicSecretLeaseRevocationFailedEmailJob(job.id as string, job.data);
});
const init = async () => { const init = async () => {
await queueService.startPg<QueueName.DynamicSecretRevocation>( await queueService.startPg<QueueName.DynamicSecretRevocation>(
QueueJobs.DynamicSecretRevocation, QueueJobs.DynamicSecretRevocation,
async ([job]) => { async ([job]) => {
await $dynamicSecretQueueJob(job.name, job.id, job.data); await $dynamicSecretQueueJob(job.name, job.id, job.data, job.retryCount);
}, },
{ {
workerCount: 5, workerCount: 10,
pollingIntervalSeconds: 1 pollingIntervalSeconds: 1
} }
); );
@@ -210,6 +358,7 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
pruneDynamicSecret, pruneDynamicSecret,
setLeaseRevocation, setLeaseRevocation,
unsetLeaseRevocation, unsetLeaseRevocation,
queueFailedRevocation,
init init
}; };
}; };

View File

@@ -178,7 +178,7 @@ export const dynamicSecretLeaseServiceFactory = ({
config config
}); });
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, expireAt); await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, dynamicSecretCfg.id, expireAt);
return { lease: dynamicSecretLease, dynamicSecret: dynamicSecretCfg, data }; return { lease: dynamicSecretLease, dynamicSecret: dynamicSecretCfg, data };
}; };
@@ -272,7 +272,7 @@ export const dynamicSecretLeaseServiceFactory = ({
); );
await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id); await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id);
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, expireAt); await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, dynamicSecretCfg.id, expireAt);
const updatedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, { const updatedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
expireAt, expireAt,
externalEntityId: entityId externalEntityId: entityId
@@ -358,11 +358,13 @@ export const dynamicSecretLeaseServiceFactory = ({
if ((revokeResponse as { error?: Error })?.error) { if ((revokeResponse as { error?: Error })?.error) {
const { error } = revokeResponse as { error?: Error }; const { error } = revokeResponse as { error?: Error };
logger.error(error?.message, "Failed to revoke lease"); logger.error(error?.message, "Failed to revoke lease");
const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, { const updatedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
status: DynamicSecretLeaseStatus.FailedDeletion, status: DynamicSecretLeaseStatus.FailedDeletion,
statusDetails: error?.message?.slice(0, 255) statusDetails: error?.message?.slice(0, 255)
}); });
return deletedDynamicSecretLease; // queue a job to retry the revocation at a later time
await dynamicSecretQueueService.queueFailedRevocation(dynamicSecretLease.id, dynamicSecretCfg.id);
return updatedDynamicSecretLease;
} }
await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id); await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id);

View File

@@ -2,3 +2,13 @@ export const delay = (ms: number) =>
new Promise<void>((resolve) => { new Promise<void>((resolve) => {
setTimeout(resolve, ms); setTimeout(resolve, ms);
}); });
export const applyJitter = (delayMs: number) => {
const jitterFactor = 0.2;
// generates random value in [-0.2, +0.2] range
const randomFactor = (Math.random() * 2 - 1) * jitterFactor;
const jitterAmount = randomFactor * delayMs;
return delayMs + jitterAmount;
};

View File

@@ -61,6 +61,7 @@ export enum QueueName {
SecretPushEventScan = "secret-push-event-scan", SecretPushEventScan = "secret-push-event-scan",
UpgradeProjectToGhost = "upgrade-project-to-ghost", UpgradeProjectToGhost = "upgrade-project-to-ghost",
DynamicSecretRevocation = "dynamic-secret-revocation", DynamicSecretRevocation = "dynamic-secret-revocation",
DynamicSecretLeaseRevocationFailedEmail = "dynamic-secret-lease-revocation-failed-email",
CaCrlRotation = "ca-crl-rotation", CaCrlRotation = "ca-crl-rotation",
CaLifecycle = "ca-lifecycle", // parent queue to ca-order-certificate-for-subscriber CaLifecycle = "ca-lifecycle", // parent queue to ca-order-certificate-for-subscriber
SecretReplication = "secret-replication", SecretReplication = "secret-replication",
@@ -120,6 +121,7 @@ export enum QueueJobs {
SecretRotationV2RotateSecrets = "secret-rotation-v2-rotate-secrets", SecretRotationV2RotateSecrets = "secret-rotation-v2-rotate-secrets",
SecretRotationV2SendNotification = "secret-rotation-v2-send-notification", SecretRotationV2SendNotification = "secret-rotation-v2-send-notification",
CreateFolderTreeCheckpoint = "create-folder-tree-checkpoint", CreateFolderTreeCheckpoint = "create-folder-tree-checkpoint",
DynamicSecretLeaseRevocationFailedEmail = "dynamic-secret-lease-revocation-failed-email",
InvalidateCache = "invalidate-cache", InvalidateCache = "invalidate-cache",
SecretScanningV2FullScan = "secret-scanning-v2-full-scan", SecretScanningV2FullScan = "secret-scanning-v2-full-scan",
SecretScanningV2DiffScan = "secret-scanning-v2-diff-scan", SecretScanningV2DiffScan = "secret-scanning-v2-diff-scan",
@@ -219,11 +221,19 @@ export type TQueueJobTypes = {
name: QueueJobs.TelemetryInstanceStats; name: QueueJobs.TelemetryInstanceStats;
payload: undefined; payload: undefined;
}; };
[QueueName.DynamicSecretLeaseRevocationFailedEmail]: {
name: QueueJobs.DynamicSecretLeaseRevocationFailedEmail;
payload: {
leaseId: string;
};
};
[QueueName.DynamicSecretRevocation]: [QueueName.DynamicSecretRevocation]:
| { | {
name: QueueJobs.DynamicSecretRevocation; name: QueueJobs.DynamicSecretRevocation;
payload: { payload: {
isRetry?: boolean;
leaseId: string; leaseId: string;
dynamicSecretId: string;
}; };
} }
| { | {

View File

@@ -1329,7 +1329,8 @@ export const registerRoutes = async (
eventBusService, eventBusService,
licenseService, licenseService,
membershipRoleDAL, membershipRoleDAL,
membershipUserDAL membershipUserDAL,
telemetryService
}); });
const projectService = projectServiceFactory({ const projectService = projectServiceFactory({
@@ -1874,7 +1875,12 @@ export const registerRoutes = async (
dynamicSecretProviders, dynamicSecretProviders,
dynamicSecretDAL, dynamicSecretDAL,
folderDAL, folderDAL,
kmsService kmsService,
smtpService,
userDAL,
identityDAL,
projectMembershipDAL,
projectDAL
}); });
const dynamicSecretService = dynamicSecretServiceFactory({ const dynamicSecretService = dynamicSecretServiceFactory({
projectDAL, projectDAL,

View File

@@ -10,7 +10,12 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { IntegrationMetadataSchema } from "@app/services/integration/integration-schema"; import { IntegrationMetadataSchema } from "@app/services/integration/integration-schema";
import { Integrations } from "@app/services/integration-auth/integration-list"; import { Integrations } from "@app/services/integration-auth/integration-list";
import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telemetry/telemetry-types"; import {
PostHogEventTypes,
TIntegrationCreatedEvent,
TIntegrationDeletedEvent,
TIntegrationSyncedEvent
} from "@app/services/telemetry/telemetry-types";
import {} from "../sanitizedSchemas"; import {} from "../sanitizedSchemas";
@@ -288,31 +293,47 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
shouldDeleteIntegrationSecrets: req.query.shouldDeleteIntegrationSecrets shouldDeleteIntegrationSecrets: req.query.shouldDeleteIntegrationSecrets
}); });
const deleteIntegrationEventProperty = shake({
integrationId: integration.id,
integration: integration.integration,
environment: integration.environment.slug,
secretPath: integration.secretPath,
url: integration.url,
app: integration.app,
appId: integration.appId,
targetEnvironment: integration.targetEnvironment,
targetEnvironmentId: integration.targetEnvironmentId,
targetService: integration.targetService,
targetServiceId: integration.targetServiceId,
path: integration.path,
region: integration.region
}) as TIntegrationDeletedEvent["properties"];
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
projectId: integration.projectId, projectId: integration.projectId,
event: { event: {
type: EventType.DELETE_INTEGRATION, type: EventType.DELETE_INTEGRATION,
// eslint-disable-next-line // eslint-disable-next-line
metadata: shake({ metadata: {
integrationId: integration.id, ...deleteIntegrationEventProperty,
integration: integration.integration,
environment: integration.environment.slug,
secretPath: integration.secretPath,
url: integration.url,
app: integration.app,
appId: integration.appId,
targetEnvironment: integration.targetEnvironment,
targetEnvironmentId: integration.targetEnvironmentId,
targetService: integration.targetService,
targetServiceId: integration.targetServiceId,
path: integration.path,
region: integration.region,
shouldDeleteIntegrationSecrets: req.query.shouldDeleteIntegrationSecrets shouldDeleteIntegrationSecrets: req.query.shouldDeleteIntegrationSecrets
// eslint-disable-next-line // eslint-disable-next-line
}) as any } as any
} }
}); });
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.IntegrationDeleted,
organizationId: req.permission.orgId,
distinctId: getTelemetryDistinctId(req),
properties: {
...deleteIntegrationEventProperty,
projectId: integration.projectId,
...req.auditLogInfo
}
});
return { integration }; return { integration };
} }
}); });
@@ -351,28 +372,41 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
id: req.params.integrationId id: req.params.integrationId
}); });
const syncIntegrationEventProperty = shake({
integrationId: integration.id,
integration: integration.integration,
environment: integration.environment.slug,
secretPath: integration.secretPath,
url: integration.url,
app: integration.app,
appId: integration.appId,
targetEnvironment: integration.targetEnvironment,
targetEnvironmentId: integration.targetEnvironmentId,
targetService: integration.targetService,
targetServiceId: integration.targetServiceId,
path: integration.path,
region: integration.region
}) as TIntegrationSyncedEvent["properties"];
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
projectId: integration.projectId, projectId: integration.projectId,
event: { event: {
type: EventType.MANUAL_SYNC_INTEGRATION, type: EventType.MANUAL_SYNC_INTEGRATION,
// eslint-disable-next-line // eslint-disable-next-line
metadata: shake({ metadata: syncIntegrationEventProperty as any
integrationId: integration.id, }
integration: integration.integration, });
environment: integration.environment.slug,
secretPath: integration.secretPath, await server.services.telemetry.sendPostHogEvents({
url: integration.url, event: PostHogEventTypes.IntegrationSynced,
app: integration.app, organizationId: req.permission.orgId,
appId: integration.appId, distinctId: getTelemetryDistinctId(req),
targetEnvironment: integration.targetEnvironment, properties: {
targetEnvironmentId: integration.targetEnvironmentId, ...syncIntegrationEventProperty,
targetService: integration.targetService, projectId: integration.projectId,
targetServiceId: integration.targetServiceId, isManualSync: true,
path: integration.path, ...req.auditLogInfo
region: integration.region
// eslint-disable-next-line
}) as any
} }
}); });

View File

@@ -99,13 +99,28 @@ export const identityOidcAuthServiceFactory = ({
} }
const requestAgent = new https.Agent({ ca: caCert, rejectUnauthorized: !!caCert }); const requestAgent = new https.Agent({ ca: caCert, rejectUnauthorized: !!caCert });
const { data: discoveryDoc } = await axios.get<{ jwks_uri: string }>(
`${identityOidcAuth.oidcDiscoveryUrl}/.well-known/openid-configuration`, let discoveryDoc: { jwks_uri: string };
{ try {
httpsAgent: identityOidcAuth.oidcDiscoveryUrl.includes("https") ? requestAgent : undefined const response = await axios.get<{ jwks_uri: string }>(
} `${identityOidcAuth.oidcDiscoveryUrl}/.well-known/openid-configuration`,
); {
httpsAgent: identityOidcAuth.oidcDiscoveryUrl.includes("https") ? requestAgent : undefined
}
);
discoveryDoc = response.data;
} catch (error) {
throw new UnauthorizedError({
message: `Access denied: Failed to fetch OIDC discovery document from ${identityOidcAuth.oidcDiscoveryUrl}. ${error instanceof Error ? error.message : String(error)}`
});
}
const jwksUri = discoveryDoc.jwks_uri; const jwksUri = discoveryDoc.jwks_uri;
if (!jwksUri) {
throw new UnauthorizedError({
message: `Access denied: OIDC discovery document does not contain a jwks_uri. The identity provider may be misconfigured.`
});
}
const decodedToken = crypto.jwt().decode(oidcJwt, { complete: true }); const decodedToken = crypto.jwt().decode(oidcJwt, { complete: true });
if (!decodedToken) { if (!decodedToken) {

View File

@@ -64,6 +64,8 @@ import { expandSecretReferencesFactory, getAllSecretReferences } from "../secret
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal"; import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal"; import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service"; import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { TTelemetryServiceFactory } from "../telemetry/telemetry-service";
import { PostHogEventTypes } from "../telemetry/telemetry-types";
import { TUserDALFactory } from "../user/user-dal"; import { TUserDALFactory } from "../user/user-dal";
import { TWebhookDALFactory } from "../webhook/webhook-dal"; import { TWebhookDALFactory } from "../webhook/webhook-dal";
import { fnTriggerWebhook } from "../webhook/webhook-fns"; import { fnTriggerWebhook } from "../webhook/webhook-fns";
@@ -120,6 +122,7 @@ type TSecretQueueFactoryDep = {
reminderService: Pick<TReminderServiceFactory, "createReminderInternal" | "deleteReminderBySecretId">; reminderService: Pick<TReminderServiceFactory, "createReminderInternal" | "deleteReminderBySecretId">;
eventBusService: TEventBusService; eventBusService: TEventBusService;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
telemetryService: Pick<TTelemetryServiceFactory, "sendPostHogEvents">;
}; };
export type TGetSecrets = { export type TGetSecrets = {
@@ -184,7 +187,8 @@ export const secretQueueFactory = ({
eventBusService, eventBusService,
licenseService, licenseService,
membershipUserDAL, membershipUserDAL,
membershipRoleDAL membershipRoleDAL,
telemetryService
}: TSecretQueueFactoryDep) => { }: TSecretQueueFactoryDep) => {
const integrationMeter = opentelemetry.metrics.getMeter("Integrations"); const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", { const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", {
@@ -1029,6 +1033,29 @@ export const secretQueueFactory = ({
isSynced: response?.isSynced ?? true isSynced: response?.isSynced ?? true
}); });
await telemetryService.sendPostHogEvents({
event: PostHogEventTypes.IntegrationSynced,
distinctId: `project/${projectId}`,
organizationId: project.orgId,
properties: {
integrationId: integration.id,
integration: integration.integration,
environment,
secretPath,
projectId,
url: integration.url ?? undefined,
app: integration.app ?? undefined,
appId: integration.appId ?? undefined,
targetEnvironment: integration.targetEnvironment ?? undefined,
targetEnvironmentId: integration.targetEnvironmentId ?? undefined,
targetService: integration.targetService ?? undefined,
targetServiceId: integration.targetServiceId ?? undefined,
path: integration.path ?? undefined,
region: integration.region ?? undefined,
isManualSync: isManual ?? false
}
});
// May be undefined, if it's undefined we assume the sync was successful, hence the strict equality type check. // May be undefined, if it's undefined we assume the sync was successful, hence the strict equality type check.
if (response?.isSynced === false) { if (response?.isSynced === false) {
integrationsFailedToSync.push({ integrationsFailedToSync.push({

View File

@@ -0,0 +1,68 @@
import { Heading, Section, Text } from "@react-email/components";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface DynamicSecretLeaseRevocationFailedTemplateProps
extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
siteUrl: string;
dynamicSecretLeaseUrl: string;
dynamicSecretName: string;
projectName: string;
environmentSlug: string;
errorMessage: string;
}
export const DynamicSecretLeaseRevocationFailedTemplate = ({
siteUrl,
dynamicSecretLeaseUrl,
dynamicSecretName,
projectName,
environmentSlug,
errorMessage
}: DynamicSecretLeaseRevocationFailedTemplateProps) => {
return (
<BaseEmailWrapper
title="Dynamic Secret Lease Revocation Failed"
preview={`Dynamic secret lease revocation failed for dynamic secret ${dynamicSecretName}`}
siteUrl={siteUrl}
>
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
Dynamic Secret Lease Revocation Failed
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-black text-[14px] leading-[24px]">
One or more leases for the dynamic secret <strong>{dynamicSecretName}</strong> in project{" "}
<strong>{projectName}</strong> and environment <strong>{environmentSlug}</strong> have failed to revoke after
multiple attempts.
</Text>
<Text className="text-black text-[14px] leading-[24px]">
Please review the dynamic secret leases and attempt to revoke them again.
</Text>
</Section>
<Section className="mt-[24px] bg-gray-50 pt-[2px] mb-[25px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
<Text className="mb-[0px]">
<strong>Latest error message</strong>
</Text>
<Text className="leading-[24px] text-[14px] text-red-600 mt-[4px]">{errorMessage}</Text>
</Section>
<Section className="text-center">
<BaseButton href={dynamicSecretLeaseUrl}>View Dynamic Secret Leases</BaseButton>
</Section>
</BaseEmailWrapper>
);
};
export default DynamicSecretLeaseRevocationFailedTemplate;
DynamicSecretLeaseRevocationFailedTemplate.PreviewProps = {
errorMessage: 'REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM "[REDACTED]" - tuple concurrently updated.',
dynamicSecretLeaseUrl: "https://infisical.com/test",
leaseId: "717d5013-7194-49d9-b6ac-6192328c2914",
dynamicSecretName: "postgres-prod-db",
projectName: "Development Team",
environmentSlug: "dev",
siteUrl: "https://infisical.com"
} as DynamicSecretLeaseRevocationFailedTemplateProps;

View File

@@ -44,6 +44,7 @@ import {
SubOrganizationInvitationTemplate, SubOrganizationInvitationTemplate,
UnlockAccountTemplate UnlockAccountTemplate
} from "./emails"; } from "./emails";
import DynamicSecretLeaseRevocationFailedTemplate from "./emails/DynamicSecretLeaseRevocationFailedTemplate";
export type TSmtpConfig = SMTPTransport.Options; export type TSmtpConfig = SMTPTransport.Options;
export type TSmtpSendMail = { export type TSmtpSendMail = {
@@ -91,7 +92,8 @@ export enum SmtpTemplates {
SecretScanningV2ScanFailed = "secretScanningV2ScanFailed", SecretScanningV2ScanFailed = "secretScanningV2ScanFailed",
SecretScanningV2SecretsDetected = "secretScanningV2SecretsDetected", SecretScanningV2SecretsDetected = "secretScanningV2SecretsDetected",
AccountDeletionConfirmation = "accountDeletionConfirmation", AccountDeletionConfirmation = "accountDeletionConfirmation",
HealthAlert = "healthAlert" HealthAlert = "healthAlert",
DynamicSecretLeaseRevocationFailed = "dynamicSecretLeaseRevocationFailed"
} }
export enum SmtpHost { export enum SmtpHost {
@@ -140,7 +142,8 @@ const EmailTemplateMap: Record<SmtpTemplates, React.FC<any>> = {
[SmtpTemplates.SecretScanningV2ScanFailed]: SecretScanningScanFailedTemplate, [SmtpTemplates.SecretScanningV2ScanFailed]: SecretScanningScanFailedTemplate,
[SmtpTemplates.SecretScanningV2SecretsDetected]: SecretScanningSecretsDetectedTemplate, [SmtpTemplates.SecretScanningV2SecretsDetected]: SecretScanningSecretsDetectedTemplate,
[SmtpTemplates.AccountDeletionConfirmation]: AccountDeletionConfirmationTemplate, [SmtpTemplates.AccountDeletionConfirmation]: AccountDeletionConfirmationTemplate,
[SmtpTemplates.HealthAlert]: HealthAlertTemplate [SmtpTemplates.HealthAlert]: HealthAlertTemplate,
[SmtpTemplates.DynamicSecretLeaseRevocationFailed]: DynamicSecretLeaseRevocationFailedTemplate
}; };
export const smtpServiceFactory = (cfg: TSmtpConfig) => { export const smtpServiceFactory = (cfg: TSmtpConfig) => {

View File

@@ -21,6 +21,8 @@ export enum PostHogEventTypes {
SecretScannerPush = "cloud secret scan", SecretScannerPush = "cloud secret scan",
ProjectCreated = "Project Created", ProjectCreated = "Project Created",
IntegrationCreated = "Integration Created", IntegrationCreated = "Integration Created",
IntegrationSynced = "Integration Synced",
IntegrationDeleted = "Integration Deleted",
MachineIdentityCreated = "Machine Identity Created", MachineIdentityCreated = "Machine Identity Created",
UserOrgInvitation = "User Org Invitation", UserOrgInvitation = "User Org Invitation",
TelemetryInstanceStats = "Self Hosted Instance Stats", TelemetryInstanceStats = "Self Hosted Instance Stats",
@@ -126,6 +128,47 @@ export type TIntegrationCreatedEvent = {
}; };
}; };
export type TIntegrationSyncedEvent = {
event: PostHogEventTypes.IntegrationSynced;
properties: {
projectId: string;
integrationId: string;
integration: string;
environment: string;
secretPath: string;
isManualSync: boolean;
url?: string;
app?: string;
appId?: string;
targetEnvironment?: string;
targetEnvironmentId?: string;
targetService?: string;
targetServiceId?: string;
path?: string;
region?: string;
};
};
export type TIntegrationDeletedEvent = {
event: PostHogEventTypes.IntegrationDeleted;
properties: {
projectId: string;
integrationId: string;
integration: string;
environment: string;
secretPath: string;
url?: string;
app?: string;
appId?: string;
targetEnvironment?: string;
targetEnvironmentId?: string;
targetService?: string;
targetServiceId?: string;
path?: string;
region?: string;
};
};
export type TUserOrgInvitedEvent = { export type TUserOrgInvitedEvent = {
event: PostHogEventTypes.UserOrgInvitation; event: PostHogEventTypes.UserOrgInvitation;
properties: { properties: {
@@ -249,6 +292,8 @@ export type TPostHogEvent = { distinctId: string; organizationId?: string } & (
| TUserOrgInvitedEvent | TUserOrgInvitedEvent
| TMachineIdentityCreatedEvent | TMachineIdentityCreatedEvent
| TIntegrationCreatedEvent | TIntegrationCreatedEvent
| TIntegrationSyncedEvent
| TIntegrationDeletedEvent
| TProjectCreateEvent | TProjectCreateEvent
| TTelemetryInstanceStatsEvent | TTelemetryInstanceStatsEvent
| TSecretRequestCreatedEvent | TSecretRequestCreatedEvent

View File

@@ -95,12 +95,4 @@ Depending on your use case, it might be helpful to look into some of the resourc
> >
Fetch secrets via HTTP request. Fetch secrets via HTTP request.
</Card> </Card>
<Card
href="/integrations/overview"
title="Native Integrations"
icon="clouds"
color="#000000"
>
Explore integrations for GitHub, Vercel, AWS, and more.
</Card>
</CardGroup> </CardGroup>

View File

@@ -25,22 +25,6 @@ SMTP_FROM_NAME=
SMTP_USERNAME= SMTP_USERNAME=
SMTP_PASSWORD= SMTP_PASSWORD=
# Integration
# Optional only if integration is used
CLIENT_ID_HEROKU=
CLIENT_ID_VERCEL=
CLIENT_ID_NETLIFY=
CLIENT_ID_GITHUB=
CLIENT_ID_GITLAB=
CLIENT_ID_BITBUCKET=
CLIENT_SECRET_HEROKU=
CLIENT_SECRET_VERCEL=
CLIENT_SECRET_NETLIFY=
CLIENT_SECRET_GITHUB=
CLIENT_SECRET_GITLAB=
CLIENT_SECRET_BITBUCKET=
CLIENT_SLUG_VERCEL=
# Sentry (optional) for monitoring errors # Sentry (optional) for monitoring errors
SENTRY_DSN= SENTRY_DSN=

View File

@@ -1,32 +0,0 @@
---
title: "Create Auth"
openapi: "POST /api/v1/integration-auth/access-token"
---
## Integration Authentication Parameters
The integration authentication endpoint is generic and can be used for all native integrations.
For specific integration parameters for a given service, please review the respective documentation below.
<Tabs>
<Tab title="AWS Secrets manager">
<ParamField body="integration" type="string" initialValue="aws-secret-manager" required>
This value must be **aws-secret-manager**.
</ParamField>
<ParamField body="workspaceId" type="string" required>
Infisical project id for the integration.
</ParamField>
<ParamField body="accessId" type="string" required>
The AWS IAM User Access ID.
</ParamField>
<ParamField body="accessToken" type="string" required>
The AWS IAM User Access Secret Key.
</ParamField>
</Tab>
<Tab title="GCP Secrets manager">
Coming Soon
</Tab>
<Tab title="Heroku">
Coming Soon
</Tab>
</Tabs>

View File

@@ -1,40 +0,0 @@
---
title: "Create"
openapi: "POST /api/v1/integration"
---
## Integration Parameters
The integration creation endpoint is generic and can be used for all native integrations.
For specific integration parameters for a given service, please review the respective documentation below.
<Tabs>
<Tab title="AWS Secrets manager">
<ParamField body="integrationAuthId" type="string" required>
The ID of the integration auth object for authentication with AWS.
Refer [Create Integration Auth](./create-auth) for more info
</ParamField>
<ParamField body="isActive" type="boolean">
Whether the integration should be active or inactive
</ParamField>
<ParamField body="app" type="string" required>
The secret name used when saving secret in AWS SSM. Used for naming and can be arbitrary.
</ParamField>
<ParamField body="region" type="string" required>
The AWS region of the SSM. Example: `us-east-1`
</ParamField>
<ParamField body="sourceEnvironment" type="string" required>
The Infisical environment slug from where secrets will be synced from. Example: `dev`
</ParamField>
<ParamField body="secretPath" type="string" required>
The Infisical folder path from where secrets will be synced from. Example: `/some/path`. The root of the environment is `/`.
</ParamField>
</Tab>
<Tab title="GCP Secrets manager">
Coming Soon
</Tab>
<Tab title="Heroku">
Coming Soon
</Tab>
</Tabs>

View File

@@ -1,4 +0,0 @@
---
title: "Delete Auth By ID"
openapi: "DELETE /api/v1/integration-auth/{integrationAuthId}"
---

View File

@@ -1,4 +0,0 @@
---
title: "Delete Auth"
openapi: "DELETE /api/v1/integration-auth"
---

View File

@@ -1,4 +0,0 @@
---
title: "Delete"
openapi: "DELETE /api/v1/integration/{integrationId}"
---

View File

@@ -1,4 +0,0 @@
---
title: "Get Auth By ID"
openapi: "GET /api/v1/integration-auth/{integrationAuthId}"
---

View File

@@ -1,4 +0,0 @@
---
title: "List Auth"
openapi: "GET /api/v1/workspace/{workspaceId}/authorizations"
---

View File

@@ -1,4 +0,0 @@
---
title: "List Project Integrations"
openapi: "GET /api/v1/workspace/{workspaceId}/integrations"
---

View File

@@ -1,4 +0,0 @@
---
title: "Update"
openapi: "PATCH /api/v1/integration/{integrationId}"
---

View File

@@ -1,90 +0,0 @@
---
title: "Configure native integrations via API"
description: "How to use Infisical API to sync secrets to external secret managers"
---
The Infisical API allows you to create programmatic integrations that connect with third-party secret managers to synchronize secrets from Infisical.
This guide will primarily demonstrate the process using AWS Secret Store Manager (AWS SSM), but the steps are generally applicable to other secret management integrations.
<Info>
For details on setting up AWS SSM synchronization and understanding its prerequisites, refer to the [AWS SSM integration setup documentation](../../../integrations/cloud/aws-secret-manager).
</Info>
<Steps>
<Step title="Authenticate with AWS SSM">
Authentication is required for all integrations. Use the [Integration Auth API](../../endpoints/integrations/create-auth) with the following parameters to authenticate.
<ParamField body="integration" type="string" initialValue="aws-secret-manager" required>
Set this parameter to **aws-secret-manager**.
</ParamField>
<ParamField body="workspaceId" type="string" required>
The Infisical project ID for the integration.
</ParamField>
<ParamField body="accessId" type="string" required>
The AWS IAM User Access ID.
</ParamField>
<ParamField body="accessToken" type="string" required>
The AWS IAM User Access Secret Key.
</ParamField>
```bash Request
curl --request POST \
--url https://app.infisical.com/api/v1/integration-auth/access-token \
--header 'Authorization: <authorization>' \
--header 'Content-Type: application/json' \
--data '{
"workspaceId": "<workspaceid>",
"integration": "aws-secret-manager",
"accessId": "<aws iam user access id>",
"accessToken": "<aws iam user access secret key>"
}'
```
</Step>
<Step title="Configure the Synchronization Setup">
Once authentication between AWS SSM and Infisical is established, you can configure the synchronization behavior.
This involves specifying the source (environment and secret path in Infisical) and the destination in SSM to which the secrets will be synchronized.
Use the [integration API](../../endpoints/integrations/create) with the following parameters to configure the sync source and destination.
<ParamField body="integrationAuthId" type="string" required>
The ID of the integration authentication object used with AWS, obtained from the previous API response.
</ParamField>
<ParamField body="isActive" type="boolean">
Indicates whether the integration should be active or inactive.
</ParamField>
<ParamField body="app" type="string" required>
The secret name for saving in AWS SSM, which can be arbitrarily chosen.
</ParamField>
<ParamField body="region" type="string" required>
The AWS region where the SSM is located, e.g., `us-east-1`.
</ParamField>
<ParamField body="sourceEnvironment" type="string" required>
The Infisical environment slug from which secrets will be synchronized, e.g., `dev`.
</ParamField>
<ParamField body="secretPath" type="string" required>
The Infisical folder path from which secrets will be synchronized, e.g., `/some/path`. The root path is `/`.
</ParamField>
```bash Request
curl --request POST \
--url https://app.infisical.com/api/v1/integration \
--header 'Authorization: <authorization>' \
--header 'Content-Type: application/json' \
--data '{
"integrationAuthId": "<integrationauthid>",
"sourceEnvironment": "<sourceenvironment>",
"secretPath": "<secret-path, default is '/' >",
"app": "<app>",
"region": "<aws-ssm-region>"
}'
```
</Step>
</Steps>
<Check>
Congratulations! You have successfully set up an integration to synchronize secrets from Infisical with AWS SSM.
For more information, [view the integration API reference](../../endpoints/integrations).
</Check>

View File

@@ -10,6 +10,7 @@ infisical login
### Description ### Description
The CLI uses authentication to verify your identity. You can authenticate using: The CLI uses authentication to verify your identity. You can authenticate using:
- **Browser Login** (default): Opens a browser for authentication - **Browser Login** (default): Opens a browser for authentication
- **Direct Login**: Provide email and password via flags or environment variables for non-interactive workflows - **Direct Login**: Provide email and password via flags or environment variables for non-interactive workflows
- **Interactive CLI Login**: Use the `--interactive` flag to enter credentials via CLI prompts - **Interactive CLI Login**: Use the `--interactive` flag to enter credentials via CLI prompts
@@ -24,9 +25,9 @@ If you have added multiple users, you can switch between the users by using the
**JWT Token Output:** **JWT Token Output:**
- For **user authentication** with the `--plain --silent` flags: outputs only the JWT access token (useful for scripting) - For **user authentication** with the `--plain --silent` flags: outputs only the JWT access token (useful for scripting)
- For **machine identity authentication**: an access token is always printed to the console - For **machine identity authentication**: an access token is always printed to the console
Use the `--plain` flag to print only the token in plain text and the `--silent` flag to disable update alerts. Use the `--plain` flag to print only the token in plain text and the `--silent` flag to disable update alerts.
Both flags are ideal for capturing the token in environment variables or CI/CD pipelines. Both flags are ideal for capturing the token in environment variables or CI/CD pipelines.
</Info> </Info>
@@ -500,6 +501,30 @@ The login command supports a number of flags that you can use for different auth
The `jwt` flag can be substituted with the `INFISICAL_JWT` environment variable. The `jwt` flag can be substituted with the `INFISICAL_JWT` environment variable.
</Tip> </Tip>
</Accordion>
<Accordion title="--domain">
```bash
infisical login --domain=<domain-url>
```
#### Description
Specifies the Infisical API URL for non-US Cloud instances. This flag is required when connecting to any instance other than US Cloud (e.g. EU Cloud or self-hosted).
```bash
# Example for EU Cloud
infisical login --domain="https://eu.infisical.com"
# Example for localhost
infisical login --domain="http://localhost:8080"
# Example for self-hosted
infisical login --domain="https://your-self-hosted-infisical.com"
```
<Warning>
**Critical:** If you use `--domain` during login, you must also include it on **all subsequent CLI commands** (e.g., `infisical secrets`, `infisical export`, etc.). Alternatively, set the `INFISICAL_API_URL` environment variable to avoid having to use `--domain` on every command. Refer to the [Domain Configuration](/cli/usage#domain-configuration) section for more details.
</Warning>
</Accordion> </Accordion>
</AccordionGroup> </AccordionGroup>
@@ -529,8 +554,11 @@ The following examples demonstrate different ways to authenticate as a user with
# Basic direct login (defaults to US Cloud) # Basic direct login (defaults to US Cloud)
infisical login --email user@example.com --password "your-password" --organization-id "your-organization-id" infisical login --email user@example.com --password "your-password" --organization-id "your-organization-id"
# EU Cloud (Custom domain) # Basic direct login (EU Cloud)
infisical login --email user@example.com --password "your-password" --organization-id "your-organization-id" --domain https://eu.infisical.com infisical login --domain https://eu.infisical.com --email user@example.com --password "your-password" --organization-id "your-organization-id"
# Basic direct login (Self-hosted Instance)
infisical login --domain https://your-self-hosted-infisical.com --email user@example.com --password "your-password" --organization-id "your-organization-id"
# Output only JWT token for scripting # Output only JWT token for scripting
export INFISICAL_TOKEN=$(infisical login --email user@example.com --password "your-password" --organization-id "your-organization-id" --plain --silent) export INFISICAL_TOKEN=$(infisical login --email user@example.com --password "your-password" --organization-id "your-organization-id" --plain --silent)
@@ -550,6 +578,11 @@ The following examples demonstrate different ways to authenticate as a user with
# Or with plain output for token capture # Or with plain output for token capture
export INFISICAL_TOKEN=$(infisical login --plain --silent) export INFISICAL_TOKEN=$(infisical login --plain --silent)
``` ```
<Warning>
**For non-US Cloud instances:** If you're using EU Cloud or a self-hosted instance, you must set `INFISICAL_API_URL` before login or use `--domain` on all commands. Refer to the [Domain Configuration](/cli/usage#domain-configuration) section for more details.
</Warning>
</Accordion> </Accordion>
<Accordion title="Interactive CLI Login"> <Accordion title="Interactive CLI Login">
@@ -571,7 +604,7 @@ The following examples demonstrate different ways to authenticate as a user with
</AccordionGroup> </AccordionGroup>
<Tip> <Tip>
If you have SSO enabled, we recommend using the default browser login. If you have SSO enabled, we recommend using the default browser login.
</Tip> </Tip>
### Machine Identity Authentication Quick Start ### Machine Identity Authentication Quick Start
@@ -584,6 +617,10 @@ In this example we'll be using the `universal-auth` method to login to obtain an
export INFISICAL_TOKEN=$(infisical login --method=universal-auth --client-id=<client-id> --client-secret=<client-secret> --silent --plain) # silent and plain is important to ensure only the token itself is printed, so we can easily set it as an environment variable. export INFISICAL_TOKEN=$(infisical login --method=universal-auth --client-id=<client-id> --client-secret=<client-secret> --silent --plain) # silent and plain is important to ensure only the token itself is printed, so we can easily set it as an environment variable.
``` ```
<Warning>
**For non-US Cloud instances:** If you're using EU Cloud or a self-hosted instance, you must set `INFISICAL_API_URL` before login or use `--domain` on all commands. Refer to the [Domain Configuration](/cli/usage#domain-configuration) section for more details.
</Warning>
Now that we've set the `INFISICAL_TOKEN` environment variable, we can use the CLI to interact with Infisical. The CLI will automatically check for the presence of the `INFISICAL_TOKEN` environment variable and use it for authentication. Now that we've set the `INFISICAL_TOKEN` environment variable, we can use the CLI to interact with Infisical. The CLI will automatically check for the presence of the `INFISICAL_TOKEN` environment variable and use it for authentication.

View File

@@ -127,10 +127,66 @@ The CLI is designed for a variety of secret management applications ranging from
<Note> <Note>
Starting with CLI version v0.4.0, you can now choose to log in via Infisical Cloud (US/EU) or your own self-hosted instance by simply running `infisical login` and following the on-screen instructions — no need to manually set the `INFISICAL_API_URL` environment variable. Starting with CLI version v0.4.0, you can now choose to log in via Infisical Cloud (US/EU) or your own self-hosted instance by simply running `infisical login` and following the on-screen instructions — no need to manually set the `INFISICAL_API_URL` environment variable.
For versions prior to v0.4.0, the CLI defaults to the US Cloud. To connect to the EU Cloud or a self-hosted instance, set the `INFISICAL_API_URL` environment variable to `https://eu.infisical.com` or your custom URL. For versions prior to v0.4.0, the CLI defaults to US Cloud. To connect to EU Cloud or a self-hosted instance, set the `INFISICAL_API_URL` environment variable to `https://eu.infisical.com` or your custom URL.
</Note> </Note>
<Warning>
## Domain Configuration
**Important:** If you're not using interactive login, you must configure the domain for **all CLI commands**.
The CLI defaults to US Cloud (https://app.infisical.com). To connect to **EU Cloud (https://eu.infisical.com)** or a **self-hosted instance**, you must configure the domain in one of the following ways:
- Use the `INFISICAL_API_URL` environment variable
- Use the `--domain` flag on every command
<Tabs>
<Tab title='Use Environment Variable (Recommended)'>
The easiest way to ensure all CLI commands use the correct domain is to set
the `INFISICAL_API_URL` environment variable. This applies the domain
setting globally to all commands:
```bash
# Linux/MacOS
export INFISICAL_API_URL="https://your-domain.infisical.com"
# Windows PowerShell
setx INFISICAL_API_URL "https://your-domain.infisical.com"
```
Once set, all subsequent CLI commands will automatically use this domain:
```bash
# Login with the domain
infisical login --method=universal-auth --client-id=<client-id> --client-secret=<client-secret> --silent --plain
# All other commands will also use the same domain automatically
infisical secrets --projectId <id> --env dev
```
</Tab>
<Tab title='Use --domain Flag'>
The `--domain` flag can be used to set the domain for a single command. This
applies the domain setting to the command only:
```bash
# Login with domain
infisical login --domain="https://your-domain.infisical.com" --method=universal-auth --client-id=<client-id> --client-secret=<client-secret> --silent --plain
# All subsequent commands must also include --domain
infisical secrets --domain="https://your-domain.infisical.com" --projectId=<id> --env=dev
```
<Note>
If you use `--domain` during login but forget to include it on subsequent commands, you may encounter authentication errors.
</Note>
</Tab>
</Tabs>
</Warning>
<Tip> <Tip>
## Custom Request Headers ## Custom Request Headers
@@ -186,51 +242,65 @@ For security and privacy concerns, we recommend you to configure your terminal t
## FAQ ## FAQ
<AccordionGroup> <AccordionGroup>
<Accordion title="Can I connect the CLI to my self-hosted Infisical instance?"> <Accordion title="Can I connect the CLI to my self-hosted or non-US Cloud Infisical instance?">
Yes. The CLI is set to connect to Infisical Cloud by default, but if you're running your own instance of Infisical, you can direct the CLI to it using one of the methods provided below. Yes. The CLI is set to connect to Infisical US Cloud by default, but if you're using EU Cloud or a self-hosted instance you can configure the domain for **all CLI commands**.
#### Method 1: Use the updated CLI #### Method 1: Use the updated CLI (v0.4.0+)
Beginning with CLI version V0.4.0, it is now possible to choose between logging in through the Infisical cloud or your own self-hosted instance. Simply execute the `infisical login` command and follow the on-screen instructions. Beginning with CLI version V0.4.0, you can choose between logging in through Infisical US Cloud, EU Cloud, or your own self-hosted instance. Simply execute the `infisical login` command and follow the on-screen instructions.
#### Method 2: Export environment variable #### Method 2: Export environment variable
You can point the CLI to the self-hosted Infisical instance by exporting the environment variable `INFISICAL_API_URL` in your terminal. You can point the CLI to the self-hosted Infisical instance by exporting the environment variable `INFISICAL_API_URL` in your terminal.
<Tabs> <Tabs>
<Tab title="Linux/MacOs"> <Tab title="Linux/MacOs">
```bash ```bash
# set backend host # Set the API URL
export INFISICAL_API_URL="https://your-self-hosted-infisical.com/api" export INFISICAL_API_URL="https://your-self-hosted-infisical.com"
# remove backend host # For EU Cloud
export INFISICAL_API_URL="https://eu.infisical.com"
# Remove the setting
unset INFISICAL_API_URL unset INFISICAL_API_URL
``` ```
</Tab> </Tab>
<Tab title="Windows Powershell"> <Tab title="Windows Powershell">
```bash ```bash
# set backend host # Set the API URL
setx INFISICAL_API_URL "https://your-self-hosted-infisical.com/api" setx INFISICAL_API_URL "https://your-self-hosted-infisical.com"
# remove backend host # For EU Cloud
setx INFISICAL_API_URL "https://eu.infisical.com"
# Remove the setting
setx INFISICAL_API_URL "" setx INFISICAL_API_URL ""
# NOTE: Once set or removed, please restart powershell for the change to take effect # NOTE: Once set, please restart powershell for the change to take effect
``` ```
</Tab> </Tab>
</Tabs> </Tabs>
#### Method 3: Set manually on every command #### Method 3: Set manually on every command
Another option to point the CLI to your self-hosted Infisical instance is to set it via a flag on every command you run. If you prefer not to use an environment variable, you must include the `--domain` flag on **every CLI command** you run:
```bash ```bash
# Example # Login with domain
infisical <any-command> --domain="https://your-self-hosted-infisical.com/api" infisical login --domain="https://your-domain.infisical.com" --method=oidc-auth --jwt $JWT
```
# All subsequent commands must also include --domain
infisical secrets --domain="https://your-self-hosted-infisical.com" --projectId <id> --env dev
infisical export --domain="https://your-self-hosted-infisical.com" --format=dotenv-export
```
<Tip>
**Best Practice:** Use `INFISICAL_API_URL` environment variable (Method 2) to avoid having to remember the `--domain` flag on every command. This is especially important in CI/CD pipelines and automation scripts.
</Tip>
</Accordion> </Accordion>
<Accordion title="Can I use the CLI with service tokens?"> <Accordion title="Can I use the CLI with service tokens?">

View File

@@ -491,6 +491,10 @@
"pages": [ "pages": [
"integrations/platforms/ansible", "integrations/platforms/ansible",
"integrations/platforms/apache-airflow", "integrations/platforms/apache-airflow",
{
"group": "AWS",
"pages": ["integrations/platforms/aws/lambda"]
},
{ {
"group": "Kubernetes Operator", "group": "Kubernetes Operator",
"pages": [ "pages": [
@@ -569,71 +573,14 @@
} }
] ]
}, },
{
"group": "Native Integrations",
"pages": [
{
"group": "AWS",
"pages": [
"integrations/cloud/aws-parameter-store",
"integrations/cloud/aws-secret-manager",
"integrations/cloud/aws-amplify"
]
},
"integrations/cloud/vercel",
"integrations/cloud/azure-key-vault",
"integrations/cloud/azure-app-configuration",
"integrations/cloud/azure-devops",
"integrations/cloud/gcp-secret-manager",
{
"group": "Cloudflare",
"pages": [
"integrations/cloud/cloudflare-pages",
"integrations/cloud/cloudflare-workers"
]
},
"integrations/cloud/terraform-cloud",
"integrations/cloud/databricks",
{
"group": "View more",
"pages": [
"integrations/cloud/digital-ocean-app-platform",
"integrations/cloud/heroku",
"integrations/cloud/netlify",
"integrations/cloud/flyio",
"integrations/cloud/railway",
"integrations/cloud/render",
"integrations/cloud/laravel-forge",
"integrations/cloud/supabase",
"integrations/cloud/northflank",
"integrations/cloud/hasura-cloud",
"integrations/cloud/qovery",
"integrations/cloud/hashicorp-vault",
"integrations/cloud/cloud-66",
"integrations/cloud/windmill"
]
}
]
},
{ {
"group": "CI/CD Integrations", "group": "CI/CD Integrations",
"pages": [ "pages": [
"integrations/cicd/jenkins", "integrations/cicd/aws-amplify",
"integrations/cicd/bitbucket",
"integrations/cicd/githubactions", "integrations/cicd/githubactions",
"integrations/cicd/gitlab", "integrations/cicd/gitlab",
"integrations/cicd/bitbucket", "integrations/cicd/jenkins"
"integrations/cloud/teamcity",
{
"group": "View more",
"pages": [
"integrations/cicd/circleci",
"integrations/cicd/travisci",
"integrations/cicd/rundeck",
"integrations/cicd/codefresh",
"integrations/cloud/checkly",
"integrations/cicd/octopus-deploy"
]
}
] ]
}, },
{ {
@@ -885,11 +832,7 @@
"group": "Overview", "group": "Overview",
"pages": [ "pages": [
"api-reference/overview/introduction", "api-reference/overview/introduction",
"api-reference/overview/authentication", "api-reference/overview/authentication"
{
"group": "Examples",
"pages": ["api-reference/overview/examples/integration"]
}
] ]
}, },
{ {
@@ -2491,20 +2434,6 @@
] ]
} }
] ]
},
{
"group": "Integrations",
"pages": [
"api-reference/endpoints/integrations/create-auth",
"api-reference/endpoints/integrations/list-auth",
"api-reference/endpoints/integrations/find-auth",
"api-reference/endpoints/integrations/delete-auth",
"api-reference/endpoints/integrations/delete-auth-by-id",
"api-reference/endpoints/integrations/create",
"api-reference/endpoints/integrations/update",
"api-reference/endpoints/integrations/delete",
"api-reference/endpoints/integrations/list-project-integrations"
]
} }
] ]
}, },

View File

@@ -183,32 +183,7 @@ At this stage, you know how to use the Infisical CLI to inject secrets into your
## Infisical-Vercel integration for production environment variables ## Infisical-Vercel integration for production environment variables
We'll now use the Infisical-Vercel integration send secrets from Infisical to Vercel as production environment variables. Use our [Vercel Secret Syncs](../../integrations/secret-syncs/vercel) guide to sync secrets from Infisical to Vercel as production environment variables.
### Infisical-Vercel integration
To begin we have to import the Next.js app into Vercel as a project. [Follow these instructions](https://vercel.com/docs/frameworks/nextjs) to deploy the Next.js app to Vercel.
Next, navigate to your project's integrations tab in Infisical and press on the Vercel tile to grant Infisical access to your Vercel account.
![integrations](../../images/integrations.png)
![integrations vercel authorization](../../images/integrations/vercel/integrations-vercel-auth.png)
<Note>
Opting in for the Infisical-Vercel integration will break end-to-end encryption since Infisical will be able to read
your secrets. This is, however, necessary for Infisical to sync the secrets to Vercel.
Your secrets remain encrypted at rest following our [security guide mechanics](/internals/security).
</Note>
Now select **Production** for (the source) **Environment** and sync it to the **Production Environment** of the (target) application in Vercel.
Lastly, press create integration to start syncing secrets to Vercel.
![integrations vercel](../../images/integrations/vercel/integrations-vercel-create.png)
![integrations vercel](../../images/integrations/vercel/integrations-vercel.png)
You should now see your secret from Infisical appear as production environment variables in your Vercel project.
At this stage, you know how to use the Infisical-Vercel integration to sync production secrets from Infisical to Vercel. At this stage, you know how to use the Infisical-Vercel integration to sync production secrets from Infisical to Vercel.
@@ -245,4 +220,4 @@ At this stage, you know how to use the Infisical-Vercel integration to sync prod
See also: See also:
- [Documentation for the Infisical CLI](/cli/overview) - [Documentation for the Infisical CLI](/cli/overview)
- [Documentation for the Vercel integration](/integrations/cloud/vercel) - [Documentation for the Vercel Secret Sync](../../integrations/secret-syncs/vercel)

View File

@@ -1,12 +0,0 @@
---
title: "Integrations"
description: "How to sync your secrets among various 3rd-party services with Infisical."
---
Integrations allow environment variables to be synced across your entire infrastructure from local development to CI/CD and production.
<Card title="View integrations" icon="link" href="/integrations/overview">
View all available integrations and their guides
</Card>
![integrations](../../images/integrations.png)

View File

@@ -12,6 +12,8 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access
<Accordion title="Self-Hosted Instance Setup"> <Accordion title="Self-Hosted Instance Setup">
Using the GitLab Connection with OAuth on a self-hosted instance of Infisical requires configuring an OAuth application in GitLab and registering your instance with it. Using the GitLab Connection with OAuth on a self-hosted instance of Infisical requires configuring an OAuth application in GitLab and registering your instance with it.
<Tip>If you're self-hosting GitLab with custom certificates, you will have to configure your Infisical instance to trust these certificates. To learn how, please follow [this guide](../../self-hosting/guides/custom-certificates).</Tip>
**Prerequisites:** **Prerequisites:**
- A GitLab account with existing projects - A GitLab account with existing projects
- Self-hosted Infisical instance - Self-hosted Infisical instance

View File

@@ -75,10 +75,6 @@ to limit the access of this entity to the minimal permission set required to per
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 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. 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>
## Platform Managed Credentials ## Platform Managed Credentials

View File

@@ -19,7 +19,7 @@ This approach enables you to fetch secrets from Infisical during Amplify build t
<Tab title="Machine Identity (Recommended)"> <Tab title="Machine Identity (Recommended)">
<Steps> <Steps>
<Step title="Create a machine identity"> <Step title="Create a machine identity">
Create a machine identtiy and connect it to your Infisical project. You can read more about how to use machine identities [here](/documentation/platform/identities/machine-identities). The machine identity will allow you to authenticate and fetch secrets from Infisical. Create a machine identity and connect it to your Infisical project. You can read more about how to use machine identities [here](/documentation/platform/identities/machine-identities). The machine identity will allow you to authenticate and fetch secrets from Infisical.
</Step> </Step>
<Step title="Set the machine identity client ID and client secret as Amplify environment variables"> <Step title="Set the machine identity client ID and client secret as Amplify environment variables">
@@ -108,7 +108,7 @@ This approach enables you to fetch secrets from Infisical during Amplify build t
<Steps> <Steps>
<Step title="Follow the AWS SSM Parameter Store Integration guide"> <Step title="Follow the AWS SSM Parameter Store Integration guide">
Follow the [Infisical AWS SSM Parameter Store Integration Guide](./aws-parameter-store) to set up the integration. Pause once you reach the step where it asks you to select the path you would like to sync. Follow the [Infisical AWS SSM Parameter Store Secret Syncs Guide](../secret-syncs/aws-parameter-store) to set up the integration. Pause once you reach the step where it asks you to select the path you would like to sync.
</Step> </Step>
<Step title="Find your Amplify App ID"> <Step title="Find your Amplify App ID">
![amplify app id](../../images/integrations/aws/integrations-amplify-app-id.png) ![amplify app id](../../images/integrations/aws/integrations-amplify-app-id.png)

View File

@@ -12,29 +12,7 @@ Prerequisites:
<AccordionGroup> <AccordionGroup>
<Accordion title="Push secrets to Bitbucket from Infisical"> <Accordion title="Push secrets to Bitbucket from Infisical">
<Steps> Use our [Bitbucket Secret Syncs](../secret-syncs/bitbucket)
<Step title="Authorize Infisical for Bitbucket">
Navigate to your project's integrations tab in Infisical.
![integrations](/images/integrations.png)
Press on the Bitbucket tile and grant Infisical access to your Bitbucket account.
![integrations bitbucket authorization](/images/integrations/bitbucket/integrations-bitbucket.png)
</Step>
<Step title='Configure integration'>
Select which workspace, repository, and optionally, deployment environment, you'd like to sync your secrets
to.
![integrations configure
bitbucket](/images/integrations/bitbucket/integrations-bitbucket-configuration.png)
Once created, your integration will begin syncing secrets to the configured repository or deployment
environment.
![integrations bitbucket](/images/integrations/bitbucket/integrations-bitbucket.png)
</Step>
</Steps>
</Accordion> </Accordion>
<Accordion title="Pull secrets in Bitbucket pipelines from Infisical"> <Accordion title="Pull secrets in Bitbucket pipelines from Infisical">
<Steps> <Steps>

View File

@@ -1,40 +0,0 @@
---
title: "CircleCI"
description: "How to sync secrets from Infisical to CircleCI"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for CircleCI">
Obtain an API token in User Settings > Personal API Tokens
![integrations circleci token](/images/integrations/circleci/integrations-circleci-token.png)
Navigate to your project's integrations tab in Infisical.
![integrations](/images/integrations.png)
Press on the CircleCI tile and input your CircleCI API token to grant Infisical access to your CircleCI account.
![integrations circleci authorization](/images/integrations/circleci/integrations-circleci-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which CircleCI project or context.
<Tabs>
<Tab title="Project">
![integrations circle ci project](/images/integrations/circleci/integrations-circleci-create-project.png)
</Tab>
<Tab title="Context">
![integrations circle ci project](/images/integrations/circleci/integrations-circleci-create-context.png)
</Tab>
</Tabs>
Finally, press create integration to start syncing secrets to CircleCI.
![integrations circleci](/images/integrations/circleci/integrations-circleci.png)
</Step>
</Steps>

View File

@@ -1,32 +0,0 @@
---
title: "Codefresh"
description: "How to sync secrets from Infisical to Codefresh"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Codefresh">
Obtain an API key in User Settings > API Keys
![integrations codefresh dashboard](../../images/integrations/codefresh/integrations-codefresh-dashboard.png)
![integrations codefresh token](../../images/integrations/codefresh/integrations-codefresh-token.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Codefresh tile and input your Codefresh API key to grant Infisical access to your Codefresh account.
![integrations codefresh authorization](../../images/integrations/codefresh/integrations-codefresh-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Codefresh service and press create integration to start syncing secrets to Codefresh.
![create integration codefresh](../../images/integrations/codefresh/integrations-codefresh-create.png)
![integrations codefresh](../../images/integrations/codefresh/integrations-codefresh.png)
</Step>
</Steps>

View File

@@ -4,204 +4,6 @@ description: "How to sync secrets from Infisical to GitHub Actions"
--- ---
<Note> <Note>
Alternatively, you can use Infisical's official GitHub Action Use our [GitHub Secret Syncs](../secret-syncs/github) to sync secrets to GitHub at the organization-level, repository-level, and repository environment-level.
[here](https://github.com/Infisical/secrets-action). Alternatively, you can use Infisical's official GitHub Action [here](https://github.com/Infisical/secrets-action).
</Note> </Note>
Infisical lets you sync secrets to GitHub at the organization-level, repository-level, and repository environment-level.
## Connecting with GitHub App (Recommended)
<Tabs>
<Tab title="Usage">
<Steps>
<Step title="Authorize GitHub Infisical App">
Navigate to your project's integrations tab in Infisical and press on the GitHub tile.
![integrations](../../images/integrations/github/app/integration-overview.png)
Select GitHub App as the authentication method and click **Connect to GitHub**.
![integrations github app auth selection](../../images/integrations/github/app/github-app-method-selection.png)
You will then be redirected to the GitHub app installation page.
![integrations github app installation](../../images/integrations/github/app/github-app-installation.png)
Install and authorize the GitHub application. This will redirect you back to the Infisical integration page.
</Step>
<Step title="Configure Infisical GitHub integration">
Select which Infisical environment secrets you want to sync to which GitHub organization, repository, or repository environment.
<Tabs>
<Tab title="Repository">
![integrations github](../../images/integrations/github/integrations-github-scope-repo.png)
</Tab>
<Tab title="Organization">
![integrations github](../../images/integrations/github/integrations-github-scope-org.png)
When using the organization scope, your secrets will be saved in the top-level of your GitHub Organization.
You can choose the visibility, which defines which repositories can access the secrets. The options are:
- **All public repositories**: All public repositories in the organization can access the secrets.
- **All private repositories**: All private repositories in the organization can access the secrets.
- **Selected repositories**: Only the selected repositories can access the secrets. This gives a more fine-grained control over which repositories can access the secrets. You can select _both_ private and public repositories with this option.
</Tab>
<Tab title="Repository Environment">
![integrations github](../../images/integrations/github/integrations-github-scope-env.png)
</Tab>
</Tabs>
Finally, press create integration to start syncing secrets to GitHub.
![integrations github](../../images/integrations/github/integrations-github.png)
</Step>
</Steps>
</Tab>
<Tab title="Self-Hosted Setup">
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**.
![integrations github app create](../../images/integrations/github/app/self-hosted-github-app-create.png)
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/integrations/github/oauth2/callback`).
![integrations github app basic details](../../images/integrations/github/app/self-hosted-github-app-basic-details.png)
Enable request user authorization during app installation.
![integrations github app enable auth](../../images/integrations/github/app/self-hosted-github-app-enable-oauth.png)
Disable webhook by unchecking the Active checkbox.
![integrations github app webhook](../../images/integrations/github/app/self-hosted-github-app-webhook.png)
Set the repository permissions as follows: Metadata: Read-only, Secrets: Read and write, Environments: Read and write, Actions: Read.
![integrations github app repository](../../images/integrations/github/app/self-hosted-github-app-repository.png)
Similarly, set the organization permissions as follows: Secrets: Read and write.
![integrations github app organization](../../images/integrations/github/app/self-hosted-github-app-organization.png)
Create the Github application.
![integrations github app create confirm](../../images/integrations/github/app/self-hosted-github-app-create-confirm.png)
<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.
![integrations github app create secret](../../images/integrations/github/app/self-hosted-github-app-secret.png)
Generate a new **Private Key** for your Github application.
![integrations github app create private key](../../images/integrations/github/app/self-hosted-github-app-private-key.png)
Obtain the necessary Github application credentials. This would be the application slug, client ID, app ID, client secret, and private key.
![integrations github app credentials](../../images/integrations/github/app/self-hosted-github-app-credentials.png)
Back in your Infisical instance, add the five new environment variables for the credentials of your GitHub application:
- `CLIENT_ID_GITHUB_APP`: The **Client ID** of your GitHub application.
- `CLIENT_SECRET_GITHUB_APP`: The **Client Secret** of your GitHub application.
- `CLIENT_SLUG_GITHUB_APP`: The **Slug** of your GitHub application. This is the one found in the URL.
- `CLIENT_APP_ID_GITHUB_APP`: The **App ID** of your GitHub application.
- `CLIENT_PRIVATE_KEY_GITHUB_APP`: The **Private Key** of your GitHub application.
Once added, restart your Infisical instance and use the GitHub integration via app authentication.
</Step>
</Steps>
</Tab>
</Tabs>
## Connecting with GitHub OAuth
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- Ensure that you have admin privileges to the repository you want to sync secrets to.
<Tabs>
<Tab title="Usage">
<Steps>
<Step title="Authorize Infisical for GitHub">
Navigate to your project's integrations tab in Infisical and press on the GitHub tile.
![integrations](../../images/integrations/github/integration-overview.png)
Select OAuth as the authentication method and click **Connect to GitHub**.
![integrations github oauth auth selection](../../images/integrations/github/github-oauth-method-selection.png)
Grant Infisical access to your GitHub account (organization and repo privileges).
![integrations github authorization](../../images/integrations/github/integrations-github-auth.png)
</Step>
<Step title="Configure Infisical GitHub integration">
Select which Infisical environment secrets you want to sync to which GitHub organization, repository, or repository environment.
<Tabs>
<Tab title="Repository">
![integrations github](../../images/integrations/github/integrations-github-scope-repo.png)
</Tab>
<Tab title="Organization">
![integrations github](../../images/integrations/github/integrations-github-scope-org.png)
When using the organization scope, your secrets will be saved in the top-level of your GitHub Organization.
You can choose the visibility, which defines which repositories can access the secrets. The options are:
- **All public repositories**: All public repositories in the organization can access the secrets.
- **All private repositories**: All private repositories in the organization can access the secrets.
- **Selected repositories**: Only the selected repositories can access the secrets. This gives a more fine-grained control over which repositories can access the secrets. You can select _both_ private and public repositories with this option.
</Tab>
<Tab title="Repository Environment">
![integrations github](../../images/integrations/github/integrations-github-scope-env.png)
</Tab>
</Tabs>
Finally, press create integration to start syncing secrets to GitHub.
![integrations github](../../images/integrations/github/integrations-github.png)
</Step>
</Steps>
</Tab>
<Tab title="Self-Hosted Setup">
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.
![integrations github config](../../images/integrations/github/integrations-github-config-settings.png)
![integrations github config](../../images/integrations/github/integrations-github-config-dev-settings.png)
![integrations github config](../../images/integrations/github/integrations-github-config-new-app.png)
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/integrations/github/oauth2/callback`.
![integrations github config](../../images/integrations/github/integrations-github-config-new-app-form.png)
<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.
![integrations github config](../../images/integrations/github/integrations-github-config-credentials.png)
Back in your Infisical instance, add two new environment variables for the credentials of your GitHub OAuth application:
- `CLIENT_ID_GITHUB`: The **Client ID** of your GitHub OAuth application.
- `CLIENT_SECRET_GITHUB`: The **Client Secret** of your GitHub OAuth application.
Once added, restart your Infisical instance and use the GitHub integration.
</Step>
</Steps>
</Tab>
</Tabs>

View File

@@ -3,41 +3,13 @@ title: "GitLab"
description: "How to sync secrets from Infisical to GitLab" description: "How to sync secrets from Infisical to GitLab"
--- ---
<Tabs>
<Tab title="Usage">
Prerequisites: Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com) - Set up and add envars to [Infisical Cloud](https://app.infisical.com).
<AccordionGroup> <AccordionGroup>
<Accordion title="Standard"> <Accordion title="Standard">
<Steps> Use our [GitLab Secret Syncs](../secret-syncs/gitlab)
<Step title="Authorize Infisical for GitLab">
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the GitLab tile and grant Infisical access to your GitLab account.
![integrations gitlab authorization](../../images/integrations/gitlab/integrations-gitlab-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which GitLab repository and press create integration to start syncing secrets to GitLab.
![integrations gitlab](../../images/integrations/gitlab/integrations-gitlab-create.png)
Note that the GitLab integration supports a few options in the **Options** tab:
- Secret Prefix: If inputted, the prefix is appended to the front of every secret name prior to being synced.
- Secret Suffix: If inputted, the suffix to appended to the back of every name of every secret prior to being synced.
Setting a secret prefix or suffix ensures that existing secrets in GitLab are not overwritten during the sync. As part of this process, Infisical abstains from mutating any secrets in GitLab without the specified prefix or suffix.
![integrations gitlab options](../../images/integrations/gitlab/integrations-gitlab-create-options.png)
![integrations gitlab](../../images/integrations/gitlab/integrations-gitlab.png)
</Step>
</Steps>
</Accordion> </Accordion>
<Accordion title="Pipeline"> <Accordion title="Pipeline">
<Steps> <Steps>
@@ -70,42 +42,4 @@ description: "How to sync secrets from Infisical to GitLab"
</Step> </Step>
</Steps> </Steps>
</Accordion> </Accordion>
</AccordionGroup> </AccordionGroup>
</Tab>
<Tab title="Self-Hosted Setup">
Using the GitLab integration on a self-hosted instance of Infisical requires configuring an application in GitLab
and registering your instance with it.
<Tip>If you're self-hosting Gitlab with custom certificates, you will have to configure your Infisical instance to trust these certificates. To learn how, please follow [this guide](../../self-hosting/guides/custom-certificates).</Tip>
<Steps>
<Step title="Create an OAuth application in GitLab">
Navigate to your user Settings > Applications to create a new GitLab application.
![integrations gitlab config](../../images/integrations/gitlab/integrations-gitlab-config-edit-profile.png)
![integrations gitlab config](../../images/integrations/gitlab/integrations-gitlab-config-new-app.png)
Create the application. As part of the form, set the **Redirect URI** to `https://your-domain.com/integrations/gitlab/oauth2/callback`.
![integrations gitlab config](../../images/integrations/gitlab/integrations-gitlab-config-new-app-form.png)
<Note>
If you have a GitLab group, you can create an OAuth application under it
in your group Settings > Applications.
</Note>
</Step>
<Step title="Add your OAuth application credentials to Infisical">
Obtain the **Application ID** and **Secret** for your GitLab application.
![integrations gitlab config](../../images/integrations/gitlab/integrations-gitlab-config-credentials.png)
Back in your Infisical instance, add two new environment variables for the credentials of your GitLab application:
- `CLIENT_ID_GITLAB`: The **Client ID** of your GitLab application.
- `CLIENT_SECRET_GITLAB`: The **Secret** of your GitLab application.
Once added, restart your Infisical instance and use the GitLab integration.
</Step>
</Steps>
</Tab>
</Tabs>

View File

@@ -1,81 +0,0 @@
---
title: "Octopus Deploy"
description: "Learn how to sync secrets from Infisical to Octopus Deploy"
---
Prerequisites:
- Set up and add secrets to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Create a Service Account for Infisical in Octopus Deploy">
Navigate to **Configuration** > **Users** and click on the **Create Service Account** button.
![integrations octopus deploy
users](/images/integrations/octopus-deploy/integrations-octopus-deploy-user-settings.png)
Fill out the required fields and click on the **Save** button.
![integrations octopus deploy service
account](/images/integrations/octopus-deploy/integrations-octopus-deploy-create-service-account.png)
</Step>
<Step title="Generate an API Key for your Service Account">
On the **Service Account** user page, expand the **API Keys** section and click on the **New API Key** button.
![integrations octopus deploy
new api key](/images/integrations/octopus-deploy/integrations-octopus-deploy-create-api-key.png)
Fill out the required fields and click on the **Generate New** button.
![integrations octopus deploy
generate api key](/images/integrations/octopus-deploy/integrations-octopus-deploy-generate-api-key.png)
<Note>If you configure your access token to expire,
you will need to generate a new API key for Infisical prior to this date to keep your integration running.</Note>
Copy the generated **API Key** and click on the **Close** button.
![integrations octopus deploy
copy api key](/images/integrations/octopus-deploy/integrations-octopus-deploy-copy-api-key.png)
</Step>
<Step title="Create a Service Accounts Team and assign your Service Account">
<Note>You can skip creating a new team if you already have an Octopus Deploy team configured with
the **Project Contributor** role to assign your Service Account to.</Note>
Navigate to **Configuration** > **Teams** and click on the **Add Team** button.
![integrations octopus deploy
teams](/images/integrations/octopus-deploy/integrations-octopus-deploy-team-settings.png)
Create a new team for **Service Accounts** and click on the **Save** button.
![integrations octopus deploy add
team](/images/integrations/octopus-deploy/integrations-octopus-deploy-create-team.png)
<Info>
If you need to sync only one space, assign the service account to that space. Otherwise, allow access to all spaces for multi-space syncs.
</Info>
On the **Members** tab, click on the **Add Member** button, add your **Infisical Service Account** and click on the **Add** button.
![integrations octopus deploy add service account to team](/images/integrations/octopus-deploy/integrations-octopus-deploy-add-to-team.png)
On the **User Roles** tab, click on the **Include User Role** button, and add the **Project Contributor** role. Optionally,
click on the **Define Scope** button to further refine what projects your Service Account has access to. Click on the **Apply** button once complete.
![integrations octopus deploy add user roles to team](/images/integrations/octopus-deploy/integrations-octopus-deploy-add-role.png)
Save your team changes by clicking on the **Save** button.
![integrations octopus deploy save team changes](/images/integrations/octopus-deploy/integrations-octopus-deploy-save-team.png)
</Step>
<Step title="Setup Integration">
In Infisical, navigate to your **Project** > **Integrations** page and select the **Octopus Deploy** integration.
![integration octopus deploy](/images/integrations/octopus-deploy/integrations-octopus-deploy-integrations.png)
Enter your **Instance URL** and **API Key** from **Octopus Deploy** to authorize Infisical.
![integration octopus deploy](/images/integrations/octopus-deploy/integrations-octopus-deploy-authorize.png)
Select a **Space** and **Project** from **Octopus Deploy** to sync secrets to; configuring additional **Scope Values** as needed. Click on the **Create Integration** button once configured.
![integration octopus deploy](/images/integrations/octopus-deploy/integrations-octopus-deploy-create.png)
Your Infisical secrets will begin to sync to **Octopus Deploy**.
![integration octopus deploy](/images/integrations/octopus-deploy/integrations-octopus-deploy-sync.png)
</Step>
</Steps>

View File

@@ -1,32 +0,0 @@
---
title: "Rundeck"
description: "How to sync secrets from Infisical to Rundeck"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Rundeck">
Obtain a User API Token in the Profile settings of Rundeck
![integrations rundeck token](../../images/integrations/rundeck/integrations-rundeck-token.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Rundeck tile and input your Rundeck instance Base URL and User API token to grant Infisical access to manage Rundeck keys
![integrations rundeck authorization](../../images/integrations/rundeck/integrations-rundeck-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to a Rundeck Key Storage Path and press create integration to start syncing secrets to Rundeck.
![create integration rundeck](../../images/integrations/rundeck/integrations-rundeck-create.png)
![integrations rundeck](../../images/integrations/rundeck/integrations-rundeck.png)
</Step>
</Steps>

View File

@@ -1,31 +0,0 @@
---
title: "Travis CI"
description: "How to sync secrets from Infisical to Travis CI"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Travis CI">
Obtain your API token in User Settings > API authentication > Token
![integrations travis ci token](../../images/integrations/travis-ci/integrations-travisci-token.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Travis CI tile and input your Travis CI API token to grant Infisical access to your Travis CI account.
![integrations travis ci authorization](../../images/integrations/travis-ci/integrations-travisci-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Travis CI repository and press create integration to start syncing secrets to Travis CI.
![create integration travis ci](../../images/integrations/travis-ci/integrations-travisci-create.png)
![integrations travis ci](../../images/integrations/travis-ci/integrations-travisci.png)
</Step>
</Steps>

View File

@@ -1,8 +0,0 @@
---
title: "AWS Parameter Store"
description: "Learn how to sync secrets from Infisical to AWS Parameter Store."
---
<Note>
The AWS Parameter Store Native Integration will be deprecated in 2026. Please migrate to our new [AWS Parameter Store Sync](../secret-syncs/aws-parameter-store).
</Note>

View File

@@ -1,8 +0,0 @@
---
title: "AWS Secrets Manager"
description: "Learn how to sync secrets from Infisical to AWS Secrets Manager."
---
<Note>
The AWS Secrets Manager Native Integration will be deprecated in 2026. Please migrate to our new [AWS Secrets Manager Sync](../secret-syncs/aws-secrets-manager).
</Note>

View File

@@ -1,8 +0,0 @@
---
title: "Azure App Configuration"
description: "How to sync secrets from Infisical to Azure App Configuration"
---
<Note>
The Azure App Configuration Native Integration will be deprecated in 2026. Please migrate to our new [Azure App Configuration Sync](../secret-syncs/azure-app-configuration).
</Note>

View File

@@ -1,55 +0,0 @@
---
title: "Azure DevOps"
description: "How to sync secrets from Infisical to Azure DevOps"
---
### Usage
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
- Create a new [Azure DevOps](https://dev.azure.com) project if you don't have one already.
#### Create a new Azure DevOps personal access token (PAT)
You'll need to create a new personal access token (PAT) in order to authenticate Infisical with Azure DevOps.
<Steps>
<Step title="Navigate to Azure DevOps">
![integrations](../../images/integrations/azure-devops/overview-page.png)
</Step>
<Step title="Create a new token">
Make sure the newly created token has Read/Write access to the Release scope.
![integrations](../../images/integrations/azure-devops/create-new-token.png)
<Note>
Please make sure that the token has access to the following scopes: Variable Groups _(read, create, & manage)_, Release _(read/write)_, Project and Team _(read)_, Service Connections _(read & query)_
</Note>
</Step>
<Step title="Copy the new access token">
Copy the newly created token as this will be used to authenticate Infisical with Azure DevOps.
![integrations](../../images/integrations/azure-devops/new-token-created.png)
</Step>
</Steps>
#### Setup the Infisical Azure DevOps integration
Navigate to your project's integrations tab and select the 'Azure DevOps' integration.
![integrations](../../images/integrations.png)
<Steps>
<Step title="Authorize Infisical for Azure DevOps">
Enter your credentials that you obtained from the previous step.
1. Azure DevOps API token is the personal access token (PAT) you created in the previous step.
2. Azure DevOps organization name is the name of your Azure DevOps organization.
![integrations](../../images/integrations/azure-devops/new-infiscial-integration-step-1.png)
</Step>
<Step title="Configure the integration">
Select Infisical project and secret path you want to sync into Azure DevOps.
Finally, press create integration to start syncing secrets to Azure DevOps.
![integrations](../../images/integrations/azure-devops/new-infiscial-integration-step-2.png)
</Step>
</Steps>
Now you have successfully integrated Infisical with Azure DevOps. Your existing and future secret changes will automatically sync to Azure DevOps.
You can view your secrets by navigating to your Azure DevOps project and selecting the 'Library' tab under 'Pipelines' in the 'Library' section.

View File

@@ -1,8 +0,0 @@
---
title: "Azure Key Vault"
description: "How to sync secrets from Infisical to Azure Key Vault"
---
<Note>
The Azure Key Vault Native Integration will be deprecated in 2026. Please migrate to our new [Azure Key Vault Sync](../secret-syncs/azure-key-vault).
</Note>

View File

@@ -1,45 +0,0 @@
---
title: "Checkly"
description: "How to sync secrets from Infisical to Checkly"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Checkly">
Obtain a Checkly API Key in User Settings > API Keys.
![integrations checkly dashboard](../../images/integrations/checkly/integrations-checkly-dashboard.png)
![integrations checkly token](../../images/integrations/checkly/integrations-checkly-token.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Checkly tile and input your Checkly API Key to grant Infisical access to your Checkly account.
![integrations checkly authorization](../../images/integrations/checkly/integrations-checkly-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to Checkly and press create integration to start syncing secrets.
![integrations checkly](../../images/integrations/checkly/integrations-checkly-create.png)
<Note>
Infisical integrates with Checkly's environment variables at the **global** and **group** levels.
To sync secrets to a specific group, you can select a group from the Checkly Group dropdown; otherwise, leaving it empty will sync secrets globally.
</Note>
![integrations checkly](../../images/integrations/checkly/integrations-checkly.png)
<Info>
In the new version of the Checkly integration, you are able to specify suffixes that depend on the secrets' environment and path.
If you choose to do so, you should utilize such suffixes for ALL Checkly integrations – otherwise the integration system
might run into issues with deleting secrets from the wrong environments.
</Info>
</Step>
</Steps>

View File

@@ -1,48 +0,0 @@
---
title: "Cloud 66"
description: "How to sync secrets from Infisical to Cloud 66"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
## Navigate to your project's integrations tab
![integrations](../../images/integrations.png)
## Enter your Cloud 66 Access Token
In Cloud 66 Dashboard, click on the top right icon > Account Settings > Access Token
![integrations cloud 66 dashboard](../../images/integrations/cloud-66/integrations-cloud-66-dashboard.png)
![integrations cloud 66 access token](../../images/integrations/cloud-66/integrations-cloud-66-access-token.png)
Create new Personal Access Token.
![integrations cloud 66 personal access token](../../images/integrations/cloud-66/integrations-cloud-66-pat.png)
Name it **infisical** and check **Public** and **Admin**. Then click "Create Token"
![integrations cloud 66 personal access token setup](../../images/integrations/cloud-66/integrations-cloud-66-pat-setup.png)
Copy and save your token.
![integrations cloud 66 copy API token](../../images/integrations/cloud-66/integrations-cloud-66-copy-pat.png)
### Go to Infisical Integration Page
Click on the Cloud 66 tile and enter your API token to grant Infisical access to your Cloud 66 account.
![integrations cloud 66 tile in infisical dashboard](../../images/integrations/cloud-66/integrations-cloud-66-infisical-dashboard.png)
Enter your Cloud 66 Personal Access Token here. Then click "Connect to Cloud 66".
![integrations cloud 66 tile in infisical dashboard](../../images/integrations/cloud-66/integrations-cloud-66-paste-pat.png)
## Start integration
Select which Infisical environment secrets you want to sync to which Cloud 66 stacks and press create integration to start syncing secrets to Cloud 66.
![integrations laravel forge](../../images/integrations/cloud-66/integrations-cloud-66-create.png)
<Warning>
Any existing environment variables in Cloud 66 will be deleted when you start syncing. Make sure to add all the secrets into the Infisical dashboard first before doing any integrations.
</Warning>
Done!
![integrations laravel forge](../../images/integrations/cloud-66/integrations-cloud-66-done.png)

View File

@@ -1,39 +0,0 @@
---
title: "Cloudflare Pages"
description: "How to sync secrets from Infisical to Cloudflare Pages"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Cloudflare Pages">
Obtain a Cloudflare [API token](https://dash.cloudflare.com/profile/api-tokens) and [Account ID](https://developers.cloudflare.com/fundamentals/get-started/basic-tasks/find-account-and-zone-ids/):
Create a new [API token](https://dash.cloudflare.com/profile/api-tokens) in My Profile > API Tokens
![integrations cloudflare credentials 1](../../images/integrations/cloudflare/integrations-cloudflare-credentials-1.png)
![integrations cloudflare credentials 2](../../images/integrations/cloudflare/integrations-cloudflare-credentials-2.png)
![integrations cloudflare credentials 3](../../images/integrations/cloudflare/integrations-cloudflare-credentials-3.png)
Copy your [Account ID](https://developers.cloudflare.com/fundamentals/get-started/basic-tasks/find-account-and-zone-ids/) from Account > Workers & Pages > Overview
![integrations cloudflare credentials 4](../../images/integrations/cloudflare/integrations-cloudflare-credentials-4.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Cloudflare Pages tile and input your Cloudflare API token and account ID to grant Infisical access to your Cloudflare Pages.
![integrations cloudflare authorization](../../images/integrations/cloudflare/integrations-cloudflare-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to Cloudflare and press create integration to start syncing secrets.
![integrations cloudflare](../../images/integrations/cloudflare/integrations-cloudflare-create.png)
![integrations cloudflare](../../images/integrations/cloudflare/integrations-cloudflare.png)
</Step>
</Steps>

View File

@@ -1,39 +0,0 @@
---
title: "Cloudflare Workers"
description: "How to sync secrets from Infisical to Cloudflare Workers"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Cloudflare Workers">
Obtain a Cloudflare [API token](https://dash.cloudflare.com/profile/api-tokens) and [Account ID](https://developers.cloudflare.com/fundamentals/get-started/basic-tasks/find-account-and-zone-ids/):
Create a new [API token](https://dash.cloudflare.com/profile/api-tokens) in My Profile > API Tokens
![integrations cloudflare credentials 1](../../images/integrations/cloudflare/integrations-cloudflare-credentials-1.png)
![integrations cloudflare credentials 2](../../images/integrations/cloudflare/integrations-cloudflare-credentials-2.png)
![integrations cloudflare credentials 3](../../images/integrations/cloudflare/integrations-cloudflare-workers-permission.png)
Copy your [Account ID](https://developers.cloudflare.com/fundamentals/get-started/basic-tasks/find-account-and-zone-ids/) from Account > Workers & Pages > Overview
![integrations cloudflare credentials 4](../../images/integrations/cloudflare/integrations-cloudflare-credentials-4.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Cloudflare Workers tile and input your Cloudflare API token and account ID to grant Infisical access to your Cloudflare Workers.
![integrations cloudflare authorization](../../images/integrations/cloudflare/integration-cloudflare-workers-connect.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to Cloudflare Workers and press create integration to start syncing secrets.
![integrations cloudflare](../../images/integrations/cloudflare/integration-cloudflare-workers-create.png)
</Step>
</Steps>

View File

@@ -1,8 +0,0 @@
---
title: "Databricks"
description: "Learn how to sync secrets from Infisical to Databricks."
---
<Note>
The Databricks Native Integration will be deprecated in 2026. Please migrate to our new [Databricks Sync](../secret-syncs/databricks).
</Note>

View File

@@ -1,32 +0,0 @@
---
title: "Digital Ocean App Platform"
description: "How to sync secrets from Infisical to Digital Ocean App Platform"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
## Get your Digital Ocean Personal Access Tokens
On Digital Ocean dashboard, navigate to **API > Tokens** and click on "Generate New Token"
![integrations digital ocean dashboard](../../images/integrations/digital-ocean/integrations-do-dashboard.png)
Name it **infisical**, choose **No expiry**, and make sure to check **Write (optional)**. Then click on "Generate Token" and copy your API token.
![integrations digital ocean token modal](../../images/integrations/digital-ocean/integrations-do-token-modal.png)
## Navigate to your project's integrations tab
Click on the **Digital Ocean App Platform** tile and enter your API token to grant Infisical access to your Digital Ocean account.
![integrations](../../images/integrations.png)
Then enter your Digital Ocean Personal Access Token here. Then click "Connect to Digital Ocean App Platform".
![integrations infisical dashboard digital ocean integration](../../images/integrations/digital-ocean/integrations-do-enter-token.png)
## Start integration
Select which Infisical environment secrets you want to sync to which Digital Ocean App and click "Create Integration".
![integrations digital ocean select projects](../../images/integrations/digital-ocean/integrations-do-select-projects.png)
Done!
![integrations digital ocean integration success](../../images/integrations/digital-ocean/integrations-do-success.png)

View File

@@ -1,32 +0,0 @@
---
title: "Fly.io"
description: "How to sync secrets from Infisical to Fly.io"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Fly.io">
Obtain a Fly.io access token in Access Tokens
![integrations fly dashboard](../../images/integrations/flyio/integrations-flyio-dashboard.png)
![integrations fly token](../../images/integrations/flyio/integrations-flyio-token.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Fly.io tile and input your Fly.io access token to grant Infisical access to your Fly.io account.
![integrations fly authorization](../../images/integrations/flyio/integrations-flyio-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Fly.io app and press create integration to start syncing secrets to Fly.io.
![integrations fly](../../images/integrations/flyio/integrations-flyio-create.png)
![integrations fly](../../images/integrations/flyio/integrations-flyio.png)
</Step>
</Steps>

View File

@@ -1,8 +0,0 @@
---
title: "GCP Secret Manager"
description: "How to sync secrets from Infisical to GCP Secret Manager"
---
<Note>
The GCP Secret Manager Native Integration will be deprecated in 2026. Please migrate to our new [GCP Secret Manager Sync](../secret-syncs/gcp-secret-manager).
</Note>

View File

@@ -1,8 +0,0 @@
---
title: "HashiCorp Vault"
description: "How to sync secrets from Infisical to HashiCorp Vault"
---
<Note>
The Hashicorp Vault Native Integration will be deprecated in 2026. Please migrate to our new [Hashicorp Vault Sync](../secret-syncs/hashicorp-vault).
</Note>

View File

@@ -1,31 +0,0 @@
---
title: "Hasura Cloud"
description: "How to sync secrets from Infisical to Hasura Cloud"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Hasura Cloud">
Obtain a Hasura Cloud Access Token in My Account > Access Tokens
![integrations hasura cloud tokens](../../images/integrations/hasura-cloud/integrations-hasura-cloud-tokens.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Hasura Cloud tile and input your Hasura Cloud access token to grant Infisical access to your Hasura Cloud account.
![integrations hasura cloud authorization](../../images/integrations/hasura-cloud/integrations-hasura-cloud-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Hasura Cloud project and press create integration to start syncing secrets to Hasura Cloud.
![integrations hasura cloud](../../images/integrations/hasura-cloud/integrations-hasura-cloud-create.png)
![integrations hasura cloud](../../images/integrations/hasura-cloud/integrations-hasura-cloud.png)
</Step>
</Steps>

View File

@@ -1,71 +0,0 @@
---
title: "Heroku"
description: "How to sync secrets from Infisical to Heroku"
---
<Tabs>
<Tab title="Usage">
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Heroku">
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Heroku tile and grant Infisical access to your Heroku account.
![integrations heroku authorization](../../images/integrations/heroku/integrations-heroku-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Heroku app and press create integration to start syncing secrets to Heroku.
![integrations heroku](../../images/integrations/heroku/integrations-heroku-create.png)
Here's some guidance on each field:
- Project Environment: The environment in the current Infisical project from which you want to sync secrets from.
- Secrets Path: The path in the current Infisical project from which you want to sync secrets from such as `/` (for secrets that do not reside in a folder) or `/foo/bar` (for secrets nested in a folder, in this case a folder called `bar` in another folder called `foo`).
- Heroku App: The application in Heroku that you want to sync secrets to.
- Initial Sync Behavior (default is **Import - Prefer values from Infisical**): The behavior of the first sync operation triggered after creating the integration.
- **No Import - Overwrite all values in Heroku**: Sync secrets and overwrite any existing secrets in Heroku.
- **Import - Prefer values from Infisical**: Import secrets from Heroku to Infisical; if a secret with the same name already exists in Infisical, do nothing. Afterwards, sync secrets to Heroku.
- **Import - Prefer values from Heroku**: Import secrets from Heroku to Infisical; if a secret with the same name already exists in Infisical, replace its value with the one from Heroku. Afterwards, sync secrets to Heroku.
![integrations heroku](../../images/integrations/heroku/integrations-heroku.png)
</Step>
</Steps>
</Tab>
<Tab title="Self-Hosted Setup">
Using the Heroku integration on a self-hosted instance of Infisical requires configuring an API client in Heroku
and registering your instance with it.
<Steps>
<Step title="Create an API client in Heroku">
Navigate to your user Account settings > Applications to create a new API client.
![integrations Heroku config](../../images/integrations/heroku/integrations-heroku-config-settings.png)
![integrations Heroku config](../../images/integrations/heroku/integrations-heroku-config-applications.png)
![integrations Heroku config](../../images/integrations/heroku/integrations-heroku-config-new-app.png)
Create the API client. As part of the form, set the **OAuth callback URL** to `https://your-domain.com/integrations/heroku/oauth2/callback`.
![integrations Heroku config](../../images/integrations/heroku/integrations-heroku-config-new-app-form.png)
</Step>
<Step title="Add your Heroku API client credentials to Infisical">
Obtain the **Client ID** and **Client Secret** for your Heroku API client.
![integrations Heroku config](../../images/integrations/heroku/integrations-heroku-config-credentials.png)
Back in your Infisical instance, add two new environment variables for the credentials of your Heroku API client.
- `CLIENT_ID_HEROKU`: The **Client ID** of your Heroku API client.
- `CLIENT_SECRET_HEROKU`: The **Client Secret** of your Heroku API client.
Once added, restart your Infisical instance and use the Heroku integration.
</Step>
</Steps>
</Tab>
</Tabs>

View File

@@ -1,38 +0,0 @@
---
title: "Laravel Forge"
description: "How to sync secrets from Infisical to Laravel Forge"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Laravel Forge">
Obtain a Laravel Forge access token in API Tokens
![integrations laravel forge dashboard](../../images/integrations/laravel-forge/integrations-laravelforge-dashboard.png)
![integrations laravel forge api tokens](../../images/integrations/laravel-forge/integrations-laravelforge-api.png)
Obtain your Laravel Forge Server ID in Servers > Server ID
![integrations laravel forge server](../../images/integrations/laravel-forge/integrations-laravelforge-servers.png)
![integrations laravel forge server id](../../images/integrations/laravel-forge/integrations-laravelforge-serverid.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Laravel Forge tile and input your Laravel Forge access token and server ID to grant Infisical access to your Laravel Forge account.
![integrations laravel forge authorization](../../images/integrations/laravel-forge/integrations-laravelforge-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Laravel Forge site and press create integration to start syncing secrets to Laravel Forge.
![integrations laravel forge](../../images/integrations/laravel-forge/integrations-laravelforge-create.png)
![integrations laravel forge](../../images/integrations/laravel-forge/integrations-laravelforge.png)
</Step>
</Steps>

View File

@@ -1,66 +0,0 @@
---
title: "Netlify"
description: "How to sync secrets from Infisical to Netlify"
---
<Tabs>
<Tab title="Usage">
<Warning>
Infisical integrates with Netlify's new environment variable experience. If
your site uses Netlify's old environment variable experience, you'll have to
upgrade it to the new one to use this integration.
</Warning>
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Netlify">
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Netlify tile and grant Infisical access to your Netlify account.
![integrations netlify authorization](../../images/integrations/netlify/integrations-netlify-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Netlify app and context. Lastly, press create integration to start syncing secrets to Netlify.
![integrations netlify](../../images/integrations/netlify/integrations-netlify-create.png)
![integrations netlify](../../images/integrations/netlify/integrations-netlify.png)
</Step>
</Steps>
</Tab>
<Tab title="Self-Hosted Setup">
Using the Netlify integration on a self-hosted instance of Infisical requires configuring an OAuth application in Netlify
and registering your instance with it.
<Steps>
<Step title="Create an OAuth application in Netlify">
Navigate to your User settings > Applications > OAuth to create a new OAuth application.
![integrations Netlify config](../../images/integrations/netlify/integrations-netlify-config-user-settings.png)
![integrations Netlify config](../../images/integrations/netlify/integrations-netlify-config-new-app.png)
Create the OAuth application. As part of the form, set the **Redirect URI** to `https://your-domain.com/integrations/netlify/oauth2/callback`.
![integrations Netlify config](../../images/integrations/netlify/integrations-netlify-config-new-app-form.png)
</Step>
<Step title="Add your Netlify OAuth application credentials to Infisical">
Obtain the **Client ID** and **Secret** for your Netlify OAuth application.
![integrations Netlify config](../../images/integrations/netlify/integrations-netlify-config-credentials.png)
Back in your Infisical instance, add two new environment variables for the credentials of your Netlify OAuth application.
- `CLIENT_ID_NETLIFY`: The **Client ID** of your Netlify OAuth application.
- `CLIENT_SECRET_NETLIFY`: The **Secret** of your Netlify OAuth application.
Once added, restart your Infisical instance and use the Netlify integration.
</Step>
</Steps>
</Tab>
</Tabs>

View File

@@ -1,33 +0,0 @@
---
title: "Northflank"
description: "How to sync secrets from Infisical to Northflank"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- Have a [Northflank](https://northflank.com) project with a secret group ready
<Steps>
<Step title="Authorize Infisical for Northflank">
Obtain a Northflank API token in Account settings > API > Tokens
![integrations northflank dashboard](../../images/integrations/northflank/integrations-northflank-dashboard.png)
![integrations northflank token](../../images/integrations/northflank/integrations-northflank-token.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Northflank tile and input your Northflank API token to grant Infisical access to your Northflank account.
![integrations northflank authorization](../../images/integrations/northflank/integrations-northflank-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Northflank project and secret group. Finally, press create integration to start syncing secrets to Northflank.
![integrations northflank](../../images/integrations/northflank/integrations-northflank-create.png)
![integrations northflank](../../images/integrations/northflank/integrations-northflank.png)
</Step>
</Steps>

View File

@@ -1,38 +0,0 @@
---
title: "Qovery"
description: "How to sync secrets from Infisical to Qovery"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Qovery">
Obtain a Qovery API Token in Settings > API Token.
![integrations qovery api token](../../images/integrations/qovery/integrations-qovery-token.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Qovery tile and input your Qovery API Token to grant Infisical access to your Qovery account.
![integrations qovery authorization](../../images/integrations/qovery/integrations-qovery-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to Qovery and press create integration to start syncing secrets.
![integrations qovery create](../../images/integrations/qovery/integrations-qovery-create-1.png)
![integrations qovery create](../../images/integrations/qovery/integrations-qovery-create-2.png)
<Note>
Infisical supports syncing secrets to various Qovery scopes including applications, jobs, or containers.
</Note>
![integrations qovery settings](../../images/integrations/qovery/integrations-qovery.png)
</Step>
</Steps>

View File

@@ -1,47 +0,0 @@
---
title: "Railway"
description: "How to sync secrets from Infisical to Railway"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Railway">
Obtain a Railway API Token in your Railway [Account Settings > Tokens](https://railway.app/account/tokens).
![integrations railway dashboard](../../images/integrations/railway/integrations-railway-dashboard.png)
![integrations railway token](../../images/integrations/railway/integrations-railway-token.png)
<Note>
If this is your first time creating a Railway API token, then you'll be prompted to join
Railway's Private Boarding Beta program on the Railway Account Settings > Tokens page.
Note that Railway project tokens will not work for this integration since they don't work with
Railway's Public API.
</Note>
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Railway tile and input your Railway API Key to grant Infisical access to your Railway account.
![integrations railway authorization](../../images/integrations/railway/integrations-railway-authorization.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Railway project and environment (and optionally service). Lastly, press create integration to start syncing secrets to Railway.
![integrations create railway](../../images/integrations/railway/integrations-railway-create.png)
<Note>
Infisical integrates with both Railway's [shared variables](https://blog.railway.app/p/shared-variables-release) at the project environment level as well as service variables at the service level.
To sync secrets to a specific service in a project, you can select a service from the Railway Service dropdown; otherwise, leaving it empty will sync secrets to the shared variables of that project.
</Note>
![integrations railway](../../images/integrations/railway/integrations-railway.png)
</Step>
</Steps>

View File

@@ -1,9 +0,0 @@
---
title: "Render"
description: "How to sync secrets from Infisical to Render"
---
<Note>
The Render Native Integration will be deprecated in 2026. Please migrate to
our new [Render Sync](../secret-syncs/render).
</Note>

View File

@@ -1,39 +0,0 @@
---
title: "Supabase"
description: "How to sync secrets from Infisical to Supabase"
---
<Note>
The Supabase integration is useful if your Supabase project uses sensitive-information such as [environment variables in edge functions](https://supabase.com/docs/guides/functions/secrets).
Synced envars can be accessed in edge functions using Deno's built-in handler: `Deno.env.get(MY_SECRET_NAME)`.
</Note>
Prerequisites:
- Have an account and project set up at [Supabase](https://supabase.com/)
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Supabase">
Obtain a Supabase Access Token in your Supabase [Account > Access Tokens](https://app.supabase.com/account/tokens).
![integrations supabase dashboard](../../images/integrations/supabase/integrations-supabase-dashboard.png)
![integrations supabase token](../../images/integrations/supabase/integrations-supabase-token.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Supabase tile and input your Supabase Access Token to grant Infisical access to your Supabase account.
![integrations supabase authorization](../../images/integrations/supabase/integrations-supabase-authorization.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Supabase project. Lastly, press create integration to start syncing secrets to Supabase.
![integrations supabase create](../../images/integrations/supabase/integrations-supabase-create.png)
![integrations supabase](../../images/integrations/supabase/integrations-supabase.png)
</Step>
</Steps>

View File

@@ -1,8 +0,0 @@
---
title: "Terraform Cloud"
description: "How to sync secrets from Infisical to Terraform Cloud"
---
<Note>
The Terraform Cloud Native Integration will be deprecated in 2026. Please migrate to our new [Terraform Cloud Sync](../secret-syncs/terraform-cloud).
</Note>

View File

@@ -1,8 +0,0 @@
---
title: "Vercel"
description: "How to sync secrets from Infisical to Vercel"
---
<Note>
The Vercel Native Integration will be deprecated in 2026. Please migrate to our new [Vercel Sync](../secret-syncs/vercel).
</Note>

View File

@@ -1,8 +0,0 @@
---
title: "Windmill"
description: "How to sync secrets from Infisical to Windmill"
---
<Note>
The Windmill Native Integration will be deprecated in 2026. Please migrate to our new [Windmill Sync](../secret-syncs/windmill).
</Note>

View File

@@ -1,61 +0,0 @@
---
title: "Overview"
description: "How to use Infisical to inject secrets and configs into various 3-rd party services and frameworks."
---
Integrations allow environment variables to be synced from Infisical into your local development workflow, CI/CD pipelines, and production infrastructure.
Missing an integration? [Throw in a request](https://github.com/Infisical/infisical/issues).
| Integration | Type | Status |
| ------------------------------------------------------------------------------------- | ---------------------- | ---------------------------------- |
| [Docker](/integrations/platforms/docker) | Platform | Available |
| [Docker-Compose](/integrations/platforms/docker-compose) | Platform | Available |
| [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available |
| [Terraform](https://registry.terraform.io/providers/Infisical/infisical/latest/docs) | Infrastructure as code | Available |
| [PM2](/integrations/platforms/pm2) | Platform | Available |
| [Heroku](/integrations/cloud/heroku) | Cloud | Available |
| [Vercel](/integrations/cloud/vercel) | Cloud | Available |
| [Netlify](/integrations/cloud/netlify) | Cloud | Available |
| [Render](/integrations/cloud/render) | Cloud | Available |
| [Laravel Forge](/integrations/cloud/laravel-forge) | Cloud | Available |
| [Railway](/integrations/cloud/railway) | Cloud | Available |
| [Terraform Cloud](/integrations/cloud/terraform-cloud) | Cloud | Available |
| [TeamCity](/integrations/cloud/teamcity) | Cloud | Available |
| [Fly.io](/integrations/cloud/flyio) | Cloud | Available |
| [Supabase](/integrations/cloud/supabase) | Cloud | Available |
| [Northflank](/integrations/cloud/northflank) | Cloud | Available |
| [Cloudflare Pages](/integrations/cloud/cloudflare-pages) | Cloud | Available |
| [Cloudflare Workers](/integrations/cloud/cloudflare-workers) | Cloud | Available |
| [Checkly](/integrations/cloud/checkly) | Cloud | Available |
| [Qovery](/integrations/cloud/qovery) | Cloud | Available |
| [HashiCorp Vault](/integrations/cloud/hashicorp-vault) | Cloud | Available |
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
| [AWS Secrets Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
| [Azure Key Vault](/integrations/cloud/azure-key-vault) | Cloud | Available |
| [GCP Secret Manager](/integrations/cloud/gcp-secret-manager) | Cloud | Available |
| [Windmill](/integrations/cloud/windmill) | Cloud | Available |
| [Bitbucket](/integrations/cicd/bitbucket) | CI/CD | Available |
| [Codefresh](/integrations/cicd/codefresh) | CI/CD | Available |
| [GitHub Actions](/integrations/cicd/githubactions) | CI/CD | Available |
| [GitLab](/integrations/cicd/gitlab) | CI/CD | Available |
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Available |
| [Travis CI](/integrations/cicd/travisci) | CI/CD | Available |
| [Rundeck](/integrations/cicd/rundeck) | CI/CD | Available |
| [Octopus Deploy](/integrations/cicd/octopus-deploy) | CI/CD | Available |
| [React](/integrations/frameworks/react) | Framework | Available |
| [Vue](/integrations/frameworks/vue) | Framework | Available |
| [Express](/integrations/frameworks/express) | Framework | Available |
| [Next.js](/integrations/frameworks/nextjs) | Framework | Available |
| [NestJS](/integrations/frameworks/nestjs) | Framework | Available |
| [SvelteKit](/integrations/frameworks/sveltekit) | Framework | Available |
| [Nuxt](/integrations/frameworks/nuxt) | Framework | Available |
| [Gatsby](/integrations/frameworks/gatsby) | Framework | Available |
| [Remix](/integrations/frameworks/remix) | Framework | Available |
| [Vite](/integrations/frameworks/vite) | Framework | Available |
| [Fiber](/integrations/frameworks/fiber) | Framework | Available |
| [Django](/integrations/frameworks/django) | Framework | Available |
| [Flask](/integrations/frameworks/flask) | Framework | Available |
| [Laravel](/integrations/frameworks/laravel) | Framework | Available |
| [Ruby on Rails](/integrations/frameworks/rails) | Framework | Available |
| Jenkins | CI/CD | Available |

View File

@@ -0,0 +1,98 @@
---
title: "AWS Lambda"
sidebarTitle: "AWS Lambda"
description: "How to use Infisical secrets in AWS Lambda"
---
Learn how to sync Infisical secrets to AWS Lambda regardless of how you deploy your function. This guide covers the following strategies:
- Infisical SDKs
- AWS Secrets Manager integration
- AWS Systems Manager Parameter Store integration
- AWS CLI
## Choose your sync strategy
### 1. Fetch secrets at runtime with Infisical SDKs
If you control the Lambda code, the simplest method is to fetch secrets directly from Infisical using one of our SDKs.
You can read more about the Infisical SDKs [here](/sdks/overview).
### 2. Push via secret sync
Configure a secret sync from your Infisical project, and Infisical will keep your Secrets Manager or Parameter Store values up to date. Your Lambda function can then reference those secrets directly.
Learn more about the [AWS Secrets Manager integration](/integrations/secret-syncs/aws-secrets-manager) and the [AWS Parameter Store integration](/integrations/secret-syncs/aws-parameter-store).
### 3. Push environment variables directly using the AWS CLI
For straightforward workflows or quick rotations, you can push Infisical secrets directly into Lambda environment variables using the AWS CLI.
## Prerequisites
- AWS CLI v2 installed and authenticated
- `jq` installed locally
- An IAM principal with `lambda:UpdateFunctionConfiguration`
- Infisical CLI (`infisical`) configured
### IAM permissions
Attach a policy like the one below to the IAM user or role responsible for updating Lambda configuration:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LambdaConfig",
"Effect": "Allow",
"Action": ["lambda:UpdateFunctionConfiguration"],
"Resource": "*"
}
]
}
```
<Note>
{" "}
Replacing Lambda environment variables using the AWS CLI overwrites the entire
`Variables` object. Make sure to export your current values so you can import them
into Infisical.{" "}
</Note>
#### Push secrets to Lambda
Use the Infisical CLI to export secrets as JSON and pass them to the AWS CLI.
The example below targets a project by ID, but you can also use the `--project` and `--env` flags.
Learn more about `infisical export` [here](/cli/commands/export#infisical-export).
```bash
FUNCTION_NAME=infisical-env-test
REGION=us-east-1
PROJECT_ID=1234567890
aws lambda update-function-configuration \
--function-name "$FUNCTION_NAME" \
--region "$REGION" \
--environment "$(
infisical export \
--format=json \
--projectId="$PROJECT_ID" \
| jq 'map({(.key): .value}) | add | {Variables: .}'
)"
```
On success, the updated `Environment.Variables` block will be returned.
Verify the values in the Lambda console or by invoking the function.
<Tip>
Automate this step in CI/CD. Run `infisical export` using an Infisical Token
scoped to your project and environment, and trigger the sync as part of your
deployment workflow. Learn more about the [Infisical
Token](/cli/commands/export#infisical-export:infisical-token).
</Tip>
<Note>
We recommend using automatic secret syncs to AWS Secrets Manager or AWS
Parameter Store to keep your secrets continuously in sync and avoid manually
updating the Lambda configuration.
</Note>

View File

@@ -8,12 +8,12 @@ It eliminates the need to modify application logic by enabling clients to decide
![agent diagram](/images/agent/infisical-agent-diagram.png) ![agent diagram](/images/agent/infisical-agent-diagram.png)
### Key features: ## Key Features
- Token renewal: Automatically authenticates with Infisical and deposits renewed access tokens at specified path for applications to consume - **Token lifecycle management**: Automatically authenticates with Infisical and deposits renewed access tokens at specified path for applications to consume
- Templating: Renders secrets via user provided templates to desired formats for applications to consume - **Templating**: Renders secrets and dynamic secret leases via user provided templates to desired formats for applications to consume
### Token renewal ## Token Renewal
The Infisical agent can help manage the life cycle of access tokens. The token renewal process is split into two main components: a `Method`, which is the authentication process suitable for your current setup, and `Sinks`, which are the places where the agent deposits the new access token whenever it receives updates. The Infisical agent can help manage the life cycle of access tokens. The token renewal process is split into two main components: a `Method`, which is the authentication process suitable for your current setup, and `Sinks`, which are the places where the agent deposits the new access token whenever it receives updates.
@@ -28,7 +28,7 @@ Every time the agent successfully retrieves a new access token, it writes the ne
to retrieve secrets from Infisical to retrieve secrets from Infisical
</Info> </Info>
### Templating ## Templating
The Infisical agent can help deliver formatted secrets to your application in a variety of environments. To achieve this, the agent will retrieve secrets from Infisical, format them using a specified template, and then save these formatted secrets to a designated file path. The Infisical agent can help deliver formatted secrets to your application in a variety of environments. To achieve this, the agent will retrieve secrets from Infisical, format them using a specified template, and then save these formatted secrets to a designated file path.
@@ -40,31 +40,203 @@ If this initial attempt is unsuccessful, the agent will momentarily pauses befor
Once the agent successfully obtains a valid access token, the agent proceeds to fetch the secrets from Infisical using it. Once the agent successfully obtains a valid access token, the agent proceeds to fetch the secrets from Infisical using it.
It then formats these secrets using the user provided templates and writes the formatted data to configured file paths. It then formats these secrets using the user provided templates and writes the formatted data to configured file paths.
### Available secret template functions
The secret template functions is what you will use to fetch resources such as static secrets and dynamic secret leases from Infisical. Below is a list of the available secret template functions that you can use in your templates.
<AccordionGroup>
<Accordion title="secret">
```bash
secret "<project-id>" "environment-slug" "<secret-path>" "<optional-modifier>"
```
```bash example-template-usage-1
{{- with secret "6553ccb2b7da580d7f6e7260" "dev" "/" `{"recursive": false, "expandSecretReferences": true}` }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
```bash example-template-usage-2
{{- with secret "da8056c8-01e2-4d24-b39f-cb4e004b8d44" "staging" "/" `{"recursive": true, "expandSecretReferences": true}` }}
{{- range . }}
{{- if eq .SecretPath "/"}}
{{ .Key }}={{ .Value }}
{{- else}}
{{ .SecretPath }}/{{ .Key }}={{ .Value }}
{{- end}}
{{- end }}
{{- end }}
```
**Function name**: `secret`
**Description**: This function can be used to render the full list of secrets within a given project, environment and secret path.
An optional JSON argument is also available. It includes the properties `recursive`, which defaults to false, and `expandSecretReferences`, which defaults to true and expands the returned secrets.
**Returns**: A single secret object with the following keys `Key, WorkspaceId, Value, SecretPath, Type, ID, and Comment`
</Accordion>
<Accordion title="getSecretByName">
```bash
getSecretByName "<project-id>" "<environment-slug>" "<secret-path>" "<secret-name>"
```
```bash example-template-usage
{{ with getSecretByName "d821f21d-aa90-453b-8448-8c78c1160a0e" "dev" "/" "POSTHOG_HOST"}}
{{ if .Value }}
password = "{{ .Value }}"
{{ end }}
{{ end }}
```
**Function name**: `getSecretByName`
**Description**: This function can be used to render a single secret by it's name.
**Returns**: A list of secret objects with the following keys `Key, WorkspaceId, Value, Type, ID, and Comment`
</Accordion>
<Accordion title="dynamic_secret">
```bash
dynamic_secret "<project-slug>" "<environment-slug>" "<secret-path>" "<dynamic-secret-name>" "<lease-ttl>"
```
```bash example-redis-dynamic-secret
{{ with dynamic_secret "aaa-o7en-s5qm" "dev" "/" "redis" "1m" }}
{{ .DB_USERNAME }}={{ .DB_PASSWORD }}
{{- end }}
```
**Function Name**: `dynamic_secret`
**Description**: This function can be used to render a dynamic secret lease credentials. The credentials are automatically renewed before they expire, ensuring that the rendered credentials are always up-to-date.
**Returns**: An object with keys corresponding to the dynamic secret lease credentials.
<Tip>
Note that if you have multiple dynamic secret templates with identical configurations, only one lease will be created in Infisical for those templates, and the same lease will be written to your specified destination paths.
</Tip>
</Accordion>
</AccordionGroup>
## Caching
The Infisical Agent supports clientside caching of Dynamic Secret leases. If the cache is enabled, the agent will persist the dynamic secret leases to the cache across restarts of the agent.
### Persistent Caching
The Agent currently only supports persistent caching. To utilize persistent caching, you must be within a Kubernetes environment. We recommend using the [Infisical Agent Injector](/integrations/platforms/kubernetes-injector) to inject the agent into pods within your Kubernetes cluster on demand.
### Cache eviction
Cache eviction is the process of removing cached data from the cache. The Agent will automatically evict cached data when the cache is full during a garbage collection cycle which is triggered every 10 minutes.
The cache will also automatically evict cached data that has gone stale or is about to go stale. For dynamic resources (such as dynamic secret leases), there's a TTL (Time-to-Live) associated with each lease which is used to determine if the lease is stale or about to go stale.
If a stale dynamic secret lease is detected, it will be automatically evicted from the cache and replaced with a new up-to-date lease.
### Cache Configuration
Configuring the cache is done through the agent configuration file. The following fields are available to configure the cache:
<AccordionGroup>
<Accordion title="Persistent Caching">
<ParamField query="cache.persistent.type" type="string">
The type of persistent caching to use. Currently only `kubernetes` is available, and will only work within Kubernetes environments.
</ParamField>
<ParamField query="cache.persistent.path" type="string">
The path to where your persistent cache will be stored.
</ParamField>
<Note>
Persistent caching is only supported within kubernetes environments at the moment. Please refer to the [Infisical Agent Injector](/integrations/platforms/kubernetes-injector) documentation for more information on how to use persistent caching within Kubernetes environments.
</Note>
```yaml example-agent-config-file.yaml
cache:
persistent:
type: "kubernetes"
path: "/home/infisical/cache"
service-account-token-path: "/var/run/secrets/kubernetes.io/serviceaccount/token"
```
</Accordion>
</AccordionGroup>
## Retrying mechanism
The agent will automatically attempt to retry failed API requests such as authentication, secrets retrieval, dynamic secret lease provisioning, etc.
By default, the agent will retry up to 3 times with a base delay of 200ms and a maximum delay of 5s.
You can configure the retrying mechanism through the agent configuration file. The following fields are available to configure the retrying mechanism:
<ParamField query="infisical.retry-strategy.max-retries" type="number">
How many times to retry failed API requests such as authentication, secret retrieval, etc. Defaults to `3` retries.
</ParamField>
<ParamField query="infisical.retry-strategy.max-delay" type="duration">
The maximum delay between retries. Defaults to `5s` (5 seconds).
</ParamField>
<ParamField query="infisical.retry-strategy.base-delay" type="duration">
The base delay between retries. Defaults to `200ms` (200 milliseconds).
</ParamField>
```yaml example-agent-config-file.yaml
infisical:
address: "https://app.infisical.com"
retry-strategy:
max-retries: 3
max-delay: "5s"
base-delay: "200ms"
# ... rest of the agent configuration file
```
## Agent configuration file ## Agent configuration file
To set up the authentication method for token renewal and to define secret templates, the Infisical agent requires a YAML configuration file containing properties defined below. To set up the authentication method for token renewal and to define secret templates, the Infisical agent requires a YAML configuration file containing properties defined below.
While specifying an authentication method is mandatory to start the agent, configuring sinks and secret templates are optional. While specifying an authentication method is mandatory to start the agent, configuring sinks and secret templates are optional.
| Field | Description |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `infisical.address` | The URL of the Infisical service. Default: `"https://app.infisical.com"`. | | Field | Description |
| `infisical.exit-after-auth` | Whether to exit the agent after authentication and first secret render. Default: `"false"`. | | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `infisical.revoke-credentials-on-shutdown` | Whether to revoke all managed dynamic secret leases and identity access tokens on shutdown. Default: `"false"`. | | `infisical.address` | The URL of the Infisical service. Default: `"https://app.infisical.com"`. |
| `auth.type` | The type of authentication method used. Available options: `universal-auth`, `kubernetes`, `azure`, `gcp-id-token`, `gcp-iam`, `aws-iam` | | `infisical.exit-after-auth` | Whether to exit the agent after authentication and first secret render. Default: `"false"`. |
| `auth.config.identity-id` | The file path where the machine identity id is stored<br/><br/>This field is required when using any of the following auth types: `kubernetes`, `azure`, `gcp-id-token`, `gcp-iam`, or `aws-iam`. | | `infisical.revoke-credentials-on-shutdown` | Whether to revoke all managed dynamic secret leases and identity access tokens on shutdown. Default: `"false"`. |
| `auth.config.service-account-token` | Path to the Kubernetes service account token to use (optional)<br/><br/>Default: `/var/run/secrets/kubernetes.io/serviceaccount/token` | | `infisical.retry-strategy.max-retries` | How many times to retry failed API requests such as authentication, secret retrieval, etc. Defaults to `3` retries. |
| `auth.config.service-account-key` | Path to your GCP service account key file. This field is required when using `gcp-iam` auth type.<br/><br/>Please note that the file should be in JSON format. | | `infisical.retry-strategy.max-delay` | The maximum delay between retries. Defaults to `5s` (5 seconds). |
| `auth.config.client-id` | The file path where the universal-auth client id is stored. | | `infisical.retry-strategy.base-delay` | The base delay between retries. Defaults to `200ms` (200 milliseconds). |
| `auth.config.client-secret` | The file path where the universal-auth client secret is stored. | | `auth.type` | The type of authentication method used. Available options: `universal-auth`, `kubernetes`, `azure`, `gcp-id-token`, `gcp-iam`, `aws-iam` |
| `auth.config.remove_client_secret_on_read` | This will instruct the agent to remove the client secret from disk. | | `auth.config.identity-id` | The file path where the machine identity id is stored<br/><br/>This field is required when using any of the following auth types: `kubernetes`, `azure`, `gcp-id-token`, `gcp-iam`, or `aws-iam`. |
| `sinks[].type` | The type of sink in a list of sinks. Each item specifies a sink type. Currently, only `"file"` type is available. | | `auth.config.service-account-token` | Path to the Kubernetes service account token to use (optional)<br/><br/>Default: `/var/run/secrets/kubernetes.io/serviceaccount/token` |
| `sinks[].config.path` | The file path where the access token should be stored for each sink in the list. | | `auth.config.service-account-key` | Path to your GCP service account key file. This field is required when using `gcp-iam` auth type.<br/><br/>Please note that the file should be in JSON format. |
| `templates[].source-path` | The path to the template file that should be used to render secrets. | | `auth.config.client-id` | The file path where the universal-auth client id is stored. |
| `templates[].template-content` | The inline secret template to be used for rendering the secrets. | | `auth.config.client-secret` | The file path where the universal-auth client secret is stored. |
| `templates[].destination-path` | The path where the rendered secrets from the source template will be saved to. | | `auth.config.remove_client_secret_on_read` | This will instruct the agent to remove the client secret from disk. |
| `templates[].config.polling-interval` | How frequently to check for secret changes. Default: `5 minutes` (optional) | | `sinks[].type` | The type of sink in a list of sinks. Each item specifies a sink type. Currently, only `"file"` type is available. |
| `templates[].config.execute.command` | The command to execute when secret change is detected (optional) | | `sinks[].config.path` | The file path where the access token should be stored for each sink in the list. |
| `templates[].config.execute.timeout` | How long in seconds to wait for command to execute before timing out (optional) | | `cache.persistent.type` | The type of persistent caching to use. Currently only `kubernetes` is available, and will only work within Kubernetes environments. |
| `cache.persistent.path` | The path to where your persistent cache will be stored. |
| `cache.persistent.service-account-token-path` | The path to the Kubernetes service account token to use for encrypting the persistent cache. Required when using `kubernetes` cache type. Defaults to `/var/run/secrets/kubernetes.io/serviceaccount/token` |
| `templates[].source-path` | The path to the template file that should be used to render secrets. |
| `templates[].template-content` | The inline secret template to be used for rendering the secrets. |
| `templates[].destination-path` | The path where the rendered secrets from the source template will be saved to. |
| `templates[].config.polling-interval` | How frequently to check for secret changes. Default: `5m` (5 minutes) (optional) |
| `templates[].config.execute.command` | The command to execute when secret change is detected (optional) |
| `templates[].config.execute.timeout` | How long in seconds to wait for command to execute before timing out (optional) |
## Authentication ## Authentication
@@ -308,81 +480,4 @@ After defining the agent configuration file, run the command below pointing to t
```bash ```bash
infisical agent --config example-agent-config-file.yaml infisical agent --config example-agent-config-file.yaml
``` ```
### Available secret template functions
<Accordion title="listSecrets">
```bash
listSecrets "<project-id>" "environment-slug" "<secret-path>" "<optional-modifier>"
```
```bash example-template-usage-1
{{- with listSecrets "6553ccb2b7da580d7f6e7260" "dev" "/" `{"recursive": false, "expandSecretReferences": true}` }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
```bash example-template-usage-2
{{- with secret "da8056c8-01e2-4d24-b39f-cb4e004b8d44" "staging" "/" `{"recursive": true, "expandSecretReferences": true}` }}
{{- range . }}
{{- if eq .SecretPath "/"}}
{{ .Key }}={{ .Value }}
{{- else}}
{{ .SecretPath }}/{{ .Key }}={{ .Value }}
{{- end}}
{{- end }}
{{- end }}
```
**Function name**: listSecrets
**Description**: This function can be used to render the full list of secrets within a given project, environment and secret path.
An optional JSON argument is also available. It includes the properties `recursive`, which defaults to false, and `expandSecretReferences`, which defaults to true and expands the returned secrets.
**Returns**: A single secret object with the following keys `Key, WorkspaceId, Value, SecretPath, Type, ID, and Comment`
</Accordion>
<Accordion title="getSecretByName">
```bash
getSecretByName "<project-id>" "<environment-slug>" "<secret-path>" "<secret-name>"
```
```bash example-template-usage
{{ with getSecretByName "d821f21d-aa90-453b-8448-8c78c1160a0e" "dev" "/" "POSTHOG_HOST"}}
{{ if .Value }}
password = "{{ .Value }}"
{{ end }}
{{ end }}
```
**Function name**: getSecretByName
**Description**: This function can be used to render a single secret by it's name.
**Returns**: A list of secret objects with the following keys `Key, WorkspaceId, Value, Type, ID, and Comment`
</Accordion>
<Accordion title="dynamic_secret">
```bash
dynamic_secret "<project-slug>" "<environment-slug>" "<secret-path>" "<dynamic-secret-name>" "<lease-ttl>"
```
```bash example-redis-dynamic-secret
{{ with dynamic_secret "aaa-o7en-s5qm" "dev" "/" "redis" "1m" }}
{{ .DB_USERNAME }}={{ .DB_PASSWORD }}
{{- end }}
**Function Name**: dynamic_secret
**Description**: This function can be used to render a dynamic secret lease credentials. The credentials are automatically renewed before they expire, ensuring that the rendered credentials are always up-to-date.
**Returns**: An object with keys corresponding to the dynamic secret lease credentials.
```
</Accordion>

View File

@@ -120,19 +120,83 @@ You will need to set the `nodeSelector.kubernetes.io/os` label to `windows` and
The Infisical Agent Injector supports the following annotations: The Infisical Agent Injector supports the following annotations:
<Accordion title="org.infisical.com/inject"> <AccordionGroup>
The inject annotation is used to enable the injector on a pod. Set the value to `true` and the pod will be patched with an Infisical Agent container on update or create. <Accordion title="org.infisical.com/inject">
</Accordion> The inject annotation is used to enable the injector on a pod. Set the value to `true` and the pod will be patched with an Infisical Agent container on update or create.
<Accordion title="org.infisical.com/inject-mode"> </Accordion>
The inject mode annotation is used to specify the mode to use to inject the secrets into the pod. <Accordion title="org.infisical.com/inject-mode">
The inject mode annotation is used to specify the mode to use to inject the secrets into the pod.
- `init`: The init method will create an init container for the pod that will render the secrets into a shared volume mount within the pod. The agent init container will run before any other containers in the pod runs, including other init containers. - `init`: The init method will create an init container for the pod that will render the secrets into a shared volume mount within the pod. The agent init container will run before any other containers in the pod runs, including other init containers.
- `sidecar`: The sidecar method will create a sidecar container for the pod that will render the secrets into a shared volume mount within the pod. The agent sidecar container will run alongside the main container in the pod. This means that the secrets rendered will always be in sync with your Infisical secrets. - `sidecar`: The sidecar method will create a sidecar container for the pod that will render the secrets into a shared volume mount within the pod. The agent sidecar container will run alongside the main container in the pod. This means that the secrets rendered will always be in sync with your Infisical secrets.
- `sidecar-init`: The sidecar-init method will create the init container and the sidecar container from the other two methods. The init container will run before any other container and fetch the secrets from the start and the sidecar container will keep the secrets in sync throughout the lifecycle of the deployment. - `sidecar-init`: The sidecar-init method will create the init container and the sidecar container from the other two methods. The init container will run before any other container and fetch the secrets from the start and the sidecar container will keep the secrets in sync throughout the lifecycle of the deployment.
</Accordion> </Accordion>
<Accordion title="org.infisical.com/agent-config-map"> <Accordion title="org.infisical.com/agent-config-map">
The agent config map annotation is used to specify the name of the config map that contains the configuration for the injector. The config map must be in the same namespace as the pod. The agent config map annotation is used to specify the name of the config map that contains the configuration for the injector. The config map must be in the same namespace as the pod.
</Accordion> </Accordion>
<Accordion title="org.infisical.com/agent-cache-enabled">
Whether to enable client-side caching of dynamic secret leases. Defaults to `false`. If you set this to `true`, the agent will persist any dynamic secret leases across restarts of the agent. This is especially useful when using the `sidecar-init` inject mode, to pass the dynamic secret leases created in the init container to the sidecar container.
This will ensure that no new leases are created except those initially created in the init container. The sidecar container will register the leases created in the init container and start managing them from that point onwards.
</Accordion>
<Accordion title="org.infisical.com/agent-revoke-on-shutdown">
Whether to revoke all managed dynamic secret leases and machine identity access tokens on shutdown. Defaults to `false`.
If you set this to `true`, all managed dynamic secret leases and machine identity access tokens will be revoked when a `SIGTERM` signal is sent to the agents container _(such as when a pod is terminated or when the pod is restarted)_.
**Note:** In disaster events such as cluster power outages, a `SIGTERM` signal won't be sent to the agents container, and the credentials will not be revoked.
</Accordion>
<Accordion title="org.infisical.com/agent-client-max-retries">
How many times to retry failed API requests such as authentication, secret retrieval, etc. Defaults to `3` retries. Refer to the [Retrying mechanism](/integrations/platforms/infisical-agent#retrying-mechanism) documentation for more information on how to configure the retry strategy.
</Accordion>
<Accordion title="org.infisical.com/agent-client-max-delay">
The maximum delay between retries. Defaults to `5s` (5 seconds). Refer to the [Retrying mechanism](/integrations/platforms/infisical-agent#retrying-mechanism) documentation for more information on how to configure the retry strategy.
</Accordion>
<Accordion title="org.infisical.com/agent-client-base-delay">
The base delay between retries. Defaults to `200ms` (200 milliseconds). Refer to the [Retrying mechanism](/integrations/platforms/infisical-agent#retrying-mechanism) documentation for more information on how to configure the retry strategy.
</Accordion>
<Accordion title="org.infisical.com/agent-limits-cpu">
The maximum CPU limit for the agent containers.
Linux Pods: Defaults to `500m` (500 milliCPUs).
Windows Pods: Defaults to `500m` (500 milliCPUs).
</Accordion>
<Accordion title="org.infisical.com/agent-requests-cpu">
The minimum CPU request for the agent containers.
Linux Pods: Defaults to `100m` (100 milliCPUs).
Windows Pods: Defaults to `100m` (100 milliCPUs).
</Accordion>
<Accordion title="org.infisical.com/agent-limits-memory">
The maximum memory limit for the agent containers.
Linux Pods: Defaults to `128Mi` (128 megabytes).
Windows Pods: Defaults to `512Mi` (512 megabytes).
</Accordion>
<Accordion title="org.infisical.com/agent-requests-memory">
The minimum memory request for the agent containers.
Linux Pods: Defaults to `64Mi` (64 megabytes).
Windows Pods: Defaults to `256Mi` (256 megabytes).
</Accordion>
<Accordion title="org.infisical.com/agent-limits-ephemeral">
The maximum ephemeral storage limit for the agent containers. Doesn't have an explicit default value. The default value will conform to the default ephemeral storage limit for the pod.
</Accordion>
<Accordion title="org.infisical.com/agent-requests-ephemeral">
The minimum ephemeral storage request for the agent containers. Doesn't have an explicit default value. The default value will conform to the default ephemeral storage request for the pod.
</Accordion>
</AccordionGroup>
## ConfigMap Configuration ## ConfigMap Configuration
@@ -141,18 +205,22 @@ The Infisical Agent Injector supports the following annotations:
When you are configuring a pod to use the injector, you must create a config map in the same namespace as the pod you want to inject secrets into. When you are configuring a pod to use the injector, you must create a config map in the same namespace as the pod you want to inject secrets into.
The entire config needs to be of string format and needs to be assigned to the `config.yaml` key in the config map. You can find a full example of the config at the end of this section. The entire config needs to be of string format and needs to be assigned to the `config.yaml` key in the config map. You can find a full example of the config at the end of this section.
<AccordionGroup>
<Accordion title="infisical.address"> <Accordion title="infisical.address">
The address of your Infisical instance. This field is optional and will default to `https://app.infisical.com` if not provided. The address of your Infisical instance. This field is optional and will default to `https://app.infisical.com` if not provided.
</Accordion> </Accordion>
<Accordion title="infisical.revoke-credentials-on-shutdown"> <Accordion title="infisical.revoke-credentials-on-shutdown">
Whether to revoke all managed dynamic secret leases and identity access tokens on shutdown. Default: `"false"`. Whether to revoke all managed dynamic secret leases and machine identity access tokens on shutdown. Default: `"false"`.
If this is set to `true`, all managed dynamic secret leases and identity access tokens will be revoked when a `SIGTERM` signal is sent to the agents container _(such as when a pod is terminated or when the pod is restarted)_. If this is set to `true`, all managed dynamic secret leases and machine identity access tokens will be revoked when a `SIGTERM` signal is sent to the agents container _(such as when a pod is terminated or when the pod is restarted)_.
**Note:** In disaster events such as cluster power outages, a `SIGTERM` signal won't be sent to the agents container, and the credentials will not be revoked. **Note:** In disaster events such as cluster power outages, a `SIGTERM` signal won't be sent to the agents container, and the credentials will not be revoked.
<Note> <Note>
Note that this is currently unsupported on Windows-based pods, and will only work when injecting into Linux-based pods. This is currently unsupported on Windows-based pods, and will only work when injecting into Linux-based pods.
It's recommended to use the annotation `org.infisical.com/agent-revoke-on-shutdown: "true"` instead of configuring the revoke on shutdown on the config map. Refer to the [Supported annotations](/integrations/platforms/kubernetes-injector#supported-annotations) documentation for more information on how to configure the revoke on shutdown through annotations.
</Note> </Note>
</Accordion> </Accordion>
@@ -162,8 +230,59 @@ The entire config needs to be of string format and needs to be assigned to the `
Please note that the pod's default service account will be used to authenticate with Infisical. Please note that the pod's default service account will be used to authenticate with Infisical.
</Accordion> </Accordion>
<Accordion title="infisical.auth.config.identity-id"> <Accordion title="infisical.auth.config.identity-id">
The ID of the machine identity to use to connect to Infisical. This field is required if the `infisical.auth.type` is set to `kubernetes`. The ID of the machine identity to use for Kubernetes or LDAP authentication. This field is required if the `infisical.auth.type` is set to `kubernetes`.
</Accordion>
<Accordion title="infisical.auth.config.username">
The LDAP username to use for LDAP authentication.
This field is required if the `infisical.auth.type` is set to `ldap-auth`.
</Accordion>
<Accordion title="infisical.auth.config.password">
The LDAP password to use for LDAP authentication.
This field is required if the `infisical.auth.type` is set to `ldap-auth`.
</Accordion>
<Accordion title="infisical.retry-strategy.max-retries">
How many times to retry failed API requests such as authentication, secret retrieval, etc. Defaults to `3` retries. Refer to the [Retrying mechanism](/integrations/platforms/infisical-agent#retrying-mechanism) documentation for more information on how to configure the retry strategy.
<Note>
You can also configure the max retries through annotations. Refer to the [Supported annotations](/integrations/platforms/kubernetes-injector#supported-annotations) documentation for more information on how to configure the max retries through annotations.
</Note>
</Accordion>
<Accordion title="infisical.retry-strategy.max-delay">
The maximum delay between retries. Defaults to `5s` (5 seconds). Refer to the [Retrying mechanism](/integrations/platforms/infisical-agent#retrying-mechanism) documentation for more information on how to configure the retry strategy.
<Note>
You can also configure the max delay through annotations. Refer to the [Supported annotations](/integrations/platforms/kubernetes-injector#supported-annotations) documentation for more information on how to configure the max delay through annotations.
</Note>
</Accordion>
<Accordion title="infisical.retry-strategy.base-delay">
The base delay between retries. Defaults to `200ms` (200 milliseconds). Refer to the [Retrying mechanism](/integrations/platforms/infisical-agent#retrying-mechanism) documentation for more information on how to configure the retry strategy.
<Note>
You can also configure the base delay through annotations. Refer to the [Supported annotations](/integrations/platforms/kubernetes-injector#supported-annotations) documentation for more information on how to configure the base delay through annotations.
</Note>
</Accordion>
<Accordion title="cache.persistent.type">
The type of persistent caching to use. Currently only `kubernetes` is available, and will only work within Kubernetes environments.
<Note>
It is recommended to use the annotation `org.infisical.com/agent-cache-enabled: "true"` instead of configuring the cache on the config map. Refer to the [Supported annotations](/integrations/platforms/kubernetes-injector#supported-annotations) documentation for more information on how to configure the cache through annotations.
</Note>
</Accordion>
<Accordion title="cache.persistent.service-account-token-path">
The path to the Kubernetes service account token to use for encrypting the persistent cache. Required when using `kubernetes` cache type. Defaults to `/var/run/secrets/kubernetes.io/serviceaccount/token`.
<Note>
It is recommended to use the annotation `org.infisical.com/agent-cache-enabled: "true"` instead of configuring the cache on the config map. Refer to the [Supported annotations](/integrations/platforms/kubernetes-injector#supported-annotations) documentation for more information on how to configure the cache through annotations.
</Note>
</Accordion> </Accordion>
<Accordion title="templates[]"> <Accordion title="templates[]">
@@ -180,6 +299,7 @@ The templates hold an array of templates that will be rendered and injected into
This will be rendered as a [Go Template](https://pkg.go.dev/text/template) and will have access to the following variables. This will be rendered as a [Go Template](https://pkg.go.dev/text/template) and will have access to the following variables.
It follows the templating format and supports the same functions as the [Infisical Agent](/integrations/platforms/infisical-agent#quick-start-infisical-agent) It follows the templating format and supports the same functions as the [Infisical Agent](/integrations/platforms/infisical-agent#quick-start-infisical-agent)
</Accordion> </Accordion>
</AccordionGroup>
### Authentication ### Authentication
@@ -271,7 +391,7 @@ The Infisical Agent Injector supports Machine Identity [Kubernetes Auth](/docume
</Accordion> </Accordion>
</AccordionGroup> </AccordionGroup>
To use the config map in your pod, you will need to add the `org.infisical.com/agent-config-map` annotation to your pod's deployment. The value of the annotation is the name of the config map you created above. To use the config map in your pod, you will need to add the `org.infisical.com/agent-config-map` annotation to your pod's deployment. The value of the annotation is the name of the config map you created above. The config map must be in the same namespace as the pod you're injecting into.
```yaml ```yaml
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod

View File

@@ -5,10 +5,6 @@ description: "Learn how to sync secrets to third-party services with Infisical."
Secret Syncs enable you to sync secrets from Infisical to third-party services using [App Connections](/integrations/app-connections/overview). Secret Syncs enable you to sync secrets from Infisical to third-party services using [App Connections](/integrations/app-connections/overview).
<Note>
Secret Syncs will gradually replace Native Integrations as they become available. Native Integrations will be deprecated in the future, so opt for configuring a Secret Sync when available.
</Note>
## Concept ## Concept
Secret Syncs are a project-level resource used to sync secrets, via an [App Connection](/integrations/app-connections/overview), from a particular project environment and folder path (source) Secret Syncs are a project-level resource used to sync secrets, via an [App Connection](/integrations/app-connections/overview), from a particular project environment and folder path (source)
@@ -92,7 +88,7 @@ via the UI or API for the third-party service you intend to sync secrets to.
<Note> <Note>
Infisical is continuously expanding it's Secret Sync third-party service support. If the service you need isn't available, Infisical is continuously expanding it's Secret Sync third-party service support. If the service you need isn't available,
you can still use our Native Integrations in the interim, or contact us at team@infisical.com to make a request . you can contact us at team@infisical.com to make a request.
</Note> </Note>
## Key Schemas ## Key Schemas

View File

@@ -703,110 +703,6 @@ You can configure third-party app connections for re-use across Infisical Projec
</ParamField> </ParamField>
</Accordion> </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.
<Accordion title="Heroku">
<ParamField query="CLIENT_ID_HEROKU" type="string" default="none" optional>
OAuth2 client ID for Heroku integration
</ParamField>
<ParamField
query="CLIENT_SECRET_HEROKU"
type="string"
default="none"
optional
>
OAuth2 client secret for Heroku integration
</ParamField>
</Accordion>
<Accordion title="Vercel">
<ParamField query="CLIENT_ID_VERCEL" type="string" default="none" optional>
OAuth2 client ID for Vercel integration
</ParamField>
{" "}
<ParamField query="CLIENT_SECRET_VERCEL" type="string" default="none" optional>
OAuth2 client secret for Vercel integration
</ParamField>
<ParamField query="CLIENT_SLUG_VERCEL" type="string" default="none" optional>
OAuth2 slug for Vercel integration
</ParamField>
</Accordion>
<Accordion title="Netlify">
<ParamField query="CLIENT_ID_NETLIFY" type="string" default="none" optional>
OAuth2 client ID for Netlify integration
</ParamField>
<ParamField query="CLIENT_SECRET_NETLIFY" type="string" default="none" optional>
OAuth2 client secret for Netlify integration
</ParamField>
</Accordion>
<Accordion title="Github">
<ParamField query="CLIENT_ID_GITHUB" type="string" default="none" optional>
OAuth2 client ID for GitHub integration
</ParamField>
<ParamField query="CLIENT_SECRET_GITHUB" type="string" default="none" optional>
OAuth2 client secret for GitHub integration
</ParamField>
</Accordion>
<Accordion title="Bitbucket">
<ParamField query="CLIENT_ID_BITBUCKET" type="string" default="none" optional>
OAuth2 client ID for Bitbucket integration
</ParamField>
<ParamField query="CLIENT_SECRET_BITBUCKET" type="string" default="none" optional>
OAuth2 client secret for Bitbucket integration
</ParamField>
</Accordion>
<Accordion title="GCP Secrets Manager">
<ParamField query="CLIENT_ID_GCP_SECRET_MANAGER" type="string" default="none" optional>
OAuth2 client id for GCP secrets manager integration
</ParamField>
<ParamField query="CLIENT_SECRET_GCP_SECRET_MANAGER" type="string" default="none" optional>
OAuth2 client secret for GCP secrets manager integration
</ParamField>
</Accordion>
<Accordion title="AWS Integration">
<ParamField query="CLIENT_ID_AWS_INTEGRATION" type="string" default="none" optional>
The AWS IAM User access key for assuming roles.
</ParamField>
<ParamField query="CLIENT_SECRET_AWS_INTEGRATION" type="string" default="none" optional>
The AWS IAM User secret key for assuming roles.
</ParamField>
</Accordion>
<Accordion title="Azure">
<ParamField query="CLIENT_ID_AZURE" type="string" default="none" optional>
OAuth2 client id for Azure integration
</ParamField>
<ParamField query="CLIENT_SECRET_AZURE" type="string" default="none" optional>
OAuth2 client secret for Azure integration
</ParamField>
</Accordion>
<Accordion title="Gitlab">
<ParamField query="CLIENT_ID_GITLAB" type="string" default="none" optional>
OAuth2 client id for Gitlab integration
</ParamField>
<ParamField query="CLIENT_SECRET_GITLAB" type="string" default="none" optional>
OAuth2 client secret for Gitlab integration
</ParamField>
</Accordion>
## Secret Scanning ## Secret Scanning
<Accordion title="GitHub"> <Accordion title="GitHub">

View File

@@ -18,7 +18,8 @@ import {
Tooltip, Tooltip,
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSub, useProject } from "@app/context";
import { useGetWorkspaceIntegrations } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/projects/types"; import { ProjectType } from "@app/hooks/api/projects/types";
import { import {
@@ -46,6 +47,9 @@ type TForm = { permissions: Record<ProjectPermissionSub, boolean> };
const Content = ({ onClose, type: projectType }: ContentProps) => { const Content = ({ onClose, type: projectType }: ContentProps) => {
const rootForm = useFormContext<TFormSchema>(); const rootForm = useFormContext<TFormSchema>();
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const { currentProject } = useProject();
const { data: integrations = [] } = useGetWorkspaceIntegrations(currentProject?.id ?? "");
const { const {
control, control,
handleSubmit, handleSubmit,
@@ -60,6 +64,8 @@ const Content = ({ onClose, type: projectType }: ContentProps) => {
} }
}); });
const hasNativeIntegrations = integrations.length > 0;
const filteredPolicies = Object.entries(PROJECT_PERMISSION_OBJECT) const filteredPolicies = Object.entries(PROJECT_PERMISSION_OBJECT)
.filter( .filter(
([subject, { title }]) => ([subject, { title }]) =>
@@ -68,6 +74,11 @@ const Content = ({ onClose, type: projectType }: ContentProps) => {
] && (search ? title.toLowerCase().includes(search.toLowerCase()) : true) ] && (search ? title.toLowerCase().includes(search.toLowerCase()) : true)
) )
.filter(([subject]) => !EXCLUDED_PERMISSION_SUBS.includes(subject as ProjectPermissionSub)) .filter(([subject]) => !EXCLUDED_PERMISSION_SUBS.includes(subject as ProjectPermissionSub))
.filter(
([subject]) =>
// Hide Native Integrations policy if project has no integrations
subject !== ProjectPermissionSub.Integrations || hasNativeIntegrations
)
.sort((a, b) => a[1].title.localeCompare(b[1].title)) .sort((a, b) => a[1].title.localeCompare(b[1].title))
.map(([subject]) => subject); .map(([subject]) => subject);

View File

@@ -11,7 +11,11 @@ import { Button } from "@app/components/v2";
import { ProjectPermissionSub, useProject } from "@app/context"; import { ProjectPermissionSub, useProject } from "@app/context";
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext"; import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
import { evaluatePermissionsAbility } from "@app/helpers/permissions"; import { evaluatePermissionsAbility } from "@app/helpers/permissions";
import { useGetProjectRoleBySlug, useUpdateProjectRole } from "@app/hooks/api"; import {
useGetProjectRoleBySlug,
useGetWorkspaceIntegrations,
useUpdateProjectRole
} from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/projects/types"; import { ProjectType } from "@app/hooks/api/projects/types";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
@@ -105,6 +109,8 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
currentProject?.id ?? "", currentProject?.id ?? "",
roleSlug as string roleSlug as string
); );
const { data: integrations = [] } = useGetWorkspaceIntegrations(projectId);
const hasNativeIntegrations = integrations.length > 0;
const [showAccessTree, setShowAccessTree] = useState<ProjectPermissionSub | null>(null); const [showAccessTree, setShowAccessTree] = useState<ProjectPermissionSub | null>(null);
@@ -198,6 +204,11 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
{!isPending && <PermissionEmptyState />} {!isPending && <PermissionEmptyState />}
{(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]) {(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[])
.filter((subject) => !EXCLUDED_PERMISSION_SUBS.includes(subject)) .filter((subject) => !EXCLUDED_PERMISSION_SUBS.includes(subject))
.filter(
(subject) =>
// Hide Native Integrations policy if project has no integrations
subject !== ProjectPermissionSub.Integrations || hasNativeIntegrations
)
.map((subject) => ( .map((subject) => (
<GeneralPermissionPolicies <GeneralPermissionPolicies
subject={subject} subject={subject}

View File

@@ -1,5 +1,7 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useSearch } from "@tanstack/react-router"; import { useNavigate, useSearch } from "@tanstack/react-router";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
@@ -12,6 +14,7 @@ import {
useProject useProject
} from "@app/context"; } from "@app/context";
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types"; import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
import { useGetWorkspaceIntegrations } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/projects/types"; import { ProjectType } from "@app/hooks/api/projects/types";
import { IntegrationsListPageTabs } from "@app/types/integrations"; import { IntegrationsListPageTabs } from "@app/types/integrations";
@@ -32,6 +35,9 @@ export const IntegrationsListPage = () => {
from: ROUTE_PATHS.SecretManager.IntegrationsListPage.id from: ROUTE_PATHS.SecretManager.IntegrationsListPage.id
}); });
const { data: integrations } = useGetWorkspaceIntegrations(currentProject.id);
const hasNativeIntegrations = Boolean(integrations?.length);
const updateSelectedTab = (tab: string) => { const updateSelectedTab = (tab: string) => {
navigate({ navigate({
to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path, to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path,
@@ -65,15 +71,17 @@ export const IntegrationsListPage = () => {
<Tab variant="project" value={IntegrationsListPageTabs.SecretSyncs}> <Tab variant="project" value={IntegrationsListPageTabs.SecretSyncs}>
Secret Syncs Secret Syncs
</Tab> </Tab>
<Tab variant="project" value={IntegrationsListPageTabs.NativeIntegrations}>
Native Integrations
</Tab>
<Tab variant="project" value={IntegrationsListPageTabs.FrameworkIntegrations}> <Tab variant="project" value={IntegrationsListPageTabs.FrameworkIntegrations}>
Framework Integrations Framework Integrations
</Tab> </Tab>
<Tab variant="project" value={IntegrationsListPageTabs.InfrastructureIntegrations}> <Tab variant="project" value={IntegrationsListPageTabs.InfrastructureIntegrations}>
Infrastructure Integrations Infrastructure Integrations
</Tab> </Tab>
{hasNativeIntegrations && (
<Tab variant="project" value={IntegrationsListPageTabs.NativeIntegrations}>
Native Integrations
</Tab>
)}
</TabList> </TabList>
<TabPanel value={IntegrationsListPageTabs.SecretSyncs}> <TabPanel value={IntegrationsListPageTabs.SecretSyncs}>
<ProjectPermissionCan <ProjectPermissionCan
@@ -84,21 +92,53 @@ export const IntegrationsListPage = () => {
<SecretSyncsTab /> <SecretSyncsTab />
</ProjectPermissionCan> </ProjectPermissionCan>
</TabPanel> </TabPanel>
<TabPanel value={IntegrationsListPageTabs.NativeIntegrations}>
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.Integrations}
>
<NativeIntegrationsTab />
</ProjectPermissionCan>
</TabPanel>
<TabPanel value={IntegrationsListPageTabs.FrameworkIntegrations}> <TabPanel value={IntegrationsListPageTabs.FrameworkIntegrations}>
<FrameworkIntegrationTab /> <FrameworkIntegrationTab />
</TabPanel> </TabPanel>
<TabPanel value={IntegrationsListPageTabs.InfrastructureIntegrations}> <TabPanel value={IntegrationsListPageTabs.InfrastructureIntegrations}>
<InfrastructureIntegrationTab /> <InfrastructureIntegrationTab />
</TabPanel> </TabPanel>
{hasNativeIntegrations && (
<TabPanel value={IntegrationsListPageTabs.NativeIntegrations}>
<div className="mb-4 flex items-start rounded-md border border-yellow-600/75 bg-yellow-900/20 px-3 py-2">
<div className="flex text-sm text-yellow-100">
<FontAwesomeIcon icon={faWarning} className="mt-1 mr-2 text-yellow-600" />
<div>
<p className="font-medium">
We&apos;re moving Native Integrations to{" "}
<a
href="https://infisical.com/docs/integrations/secret-syncs/overview"
target="_blank"
rel="noopener noreferrer"
className="underline underline-offset-2 hover:text-mineshaft-100"
>
Secret Syncs
</a>
.
</p>
<p className="mt-0.5 text-yellow-100/80">
If the integration you need isn&apos;t available in the Secret Syncs menu,
please get in touch with us at{" "}
<a
href="mailto:team@infisical.com"
className="underline underline-offset-2 hover:text-mineshaft-100"
>
team@infisical.com
</a>
.
</p>
</div>
</div>
</div>
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.Integrations}
>
<NativeIntegrationsTab />
</ProjectPermissionCan>
</TabPanel>
)}
</Tabs> </Tabs>
</div> </div>
</div> </div>

View File

@@ -1,10 +1,4 @@
import crypto from "crypto";
import { NavigateFn } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { localStorageService } from "@app/helpers/localStorage";
import { TCloudIntegration } from "@app/hooks/api/types";
export const createIntegrationMissingEnvVarsNotification = ( export const createIntegrationMissingEnvVarsNotification = (
slug: string, slug: string,
@@ -27,349 +21,3 @@ export const createIntegrationMissingEnvVarsNotification = (
), ),
title: "Missing Environment Variables" title: "Missing Environment Variables"
}); });
export const redirectForProviderAuth = (
orgId: string,
projectId: string,
navigate: NavigateFn,
integrationOption: TCloudIntegration
) => {
try {
// generate CSRF token for OAuth2 code-token exchange integrations
const state = crypto.randomBytes(16).toString("hex");
localStorage.setItem("latestCSRFToken", state);
localStorageService.setIntegrationProjectId(projectId);
switch (integrationOption.slug) {
case "gcp-secret-manager":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/gcp-secret-manager/authorize",
params: {
orgId,
projectId
}
});
break;
case "azure-key-vault": {
if (!integrationOption.clientId) {
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
return;
}
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/azure-key-vault/authorize",
params: {
orgId,
projectId
},
search: {
clientId: integrationOption.clientId,
state
}
});
break;
}
case "azure-app-configuration": {
if (!integrationOption.clientId) {
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
return;
}
const link = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/azure-app-configuration/oauth2/callback&response_mode=query&scope=https://azconfig.io/.default openid offline_access&state=${state}`;
window.location.assign(link);
break;
}
case "aws-parameter-store":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/aws-parameter-store/authorize",
params: {
orgId,
projectId
}
});
break;
case "aws-secret-manager":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/aws-secret-manager/authorize",
params: {
orgId,
projectId
}
});
break;
case "heroku": {
if (!integrationOption.clientId) {
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
return;
}
const link = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`;
window.location.assign(link);
break;
}
case "vercel": {
if (!integrationOption.clientSlug) {
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
return;
}
const link = `https://vercel.com/integrations/${integrationOption.clientSlug}/new?state=${state}`;
window.location.assign(link);
break;
}
case "netlify": {
if (!integrationOption.clientId) {
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
return;
}
const link = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/integrations/netlify/oauth2/callback`;
window.location.assign(link);
break;
}
case "github":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/github/auth-mode-selection",
params: {
orgId,
projectId
}
});
break;
case "gitlab":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/gitlab/authorize",
params: {
orgId,
projectId
}
});
break;
case "render":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/render/authorize",
params: {
orgId,
projectId
}
});
break;
case "flyio":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/flyio/authorize",
params: {
orgId,
projectId
}
});
break;
case "circleci":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/circleci/authorize",
params: {
orgId,
projectId
}
});
break;
case "databricks":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/databricks/authorize",
params: {
orgId,
projectId
}
});
break;
case "laravel-forge":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/laravel-forge/authorize",
params: {
orgId,
projectId
}
});
break;
case "travisci":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/travisci/authorize",
params: {
orgId,
projectId
}
});
break;
case "supabase":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/supabase/authorize",
params: {
orgId,
projectId
}
});
break;
case "checkly":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/checkly/authorize",
params: {
orgId,
projectId
}
});
break;
case "qovery":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/qovery/authorize",
params: {
orgId,
projectId
}
});
break;
case "railway":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/railway/authorize",
params: {
orgId,
projectId
}
});
break;
case "terraform-cloud":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/terraform-cloud/authorize",
params: {
orgId,
projectId
}
});
break;
case "hashicorp-vault":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/hashicorp-vault/authorize",
params: {
orgId,
projectId
}
});
break;
case "cloudflare-pages":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/cloudflare-pages/authorize",
params: {
orgId,
projectId
}
});
break;
case "cloudflare-workers":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/cloudflare-workers/authorize",
params: {
orgId,
projectId
}
});
break;
case "bitbucket": {
if (!integrationOption.clientId) {
createIntegrationMissingEnvVarsNotification(integrationOption.slug, "cicd");
return;
}
const link = `https://bitbucket.org/site/oauth2/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/bitbucket/oauth2/callback&state=${state}`;
window.location.assign(link);
break;
}
case "codefresh":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/codefresh/authorize",
params: {
orgId,
projectId
}
});
break;
case "digital-ocean-app-platform":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/digital-ocean-app-platform/authorize",
params: {
orgId,
projectId
}
});
break;
case "cloud-66":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/cloud-66/authorize",
params: {
orgId,
projectId
}
});
break;
case "northflank":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/northflank/authorize",
params: {
orgId,
projectId
}
});
break;
case "windmill":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/windmill/authorize",
params: {
orgId,
projectId
}
});
break;
case "teamcity":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/teamcity/authorize",
params: {
orgId,
projectId
}
});
break;
case "hasura-cloud":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/hasura-cloud/authorize",
params: {
orgId,
projectId
}
});
break;
case "rundeck":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/rundeck/authorize",
params: {
orgId,
projectId
}
});
break;
case "azure-devops":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/azure-devops/authorize",
params: {
orgId,
projectId
}
});
break;
case "octopus-deploy":
navigate({
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/octopus-deploy/authorize",
params: {
orgId,
projectId
}
});
break;
default:
break;
}
} catch (err) {
console.error(err);
}
};

View File

@@ -1,258 +0,0 @@
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import {
faCheck,
faChevronLeft,
faMagnifyingGlass,
faSearch,
faXmark
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { NoEnvironmentsBanner } from "@app/components/integrations/NoEnvironmentsBanner";
import { createNotification } from "@app/components/notifications";
import {
Button,
DeleteActionModal,
EmptyState,
Input,
Skeleton,
Tooltip
} from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
import {
ProjectPermissionActions,
ProjectPermissionSub,
useOrganization,
useProject,
useProjectPermission
} from "@app/context";
import { usePopUp } from "@app/hooks";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { IntegrationAuth, TCloudIntegration } from "@app/hooks/api/types";
import { IntegrationsListPageTabs } from "@app/types/integrations";
type Props = {
isLoading?: boolean;
integrationAuths?: Record<string, IntegrationAuth>;
cloudIntegrations?: TCloudIntegration[];
onIntegrationStart: (slug: string) => void;
// cb: handle popUpClose child->parent communication pattern
onIntegrationRevoke: (slug: string, cb: () => void) => void;
onViewActiveIntegrations?: () => void;
};
type TRevokeIntegrationPopUp = { provider: string };
const SECRET_SYNCS = Object.values(SecretSync) as string[];
const isSecretSyncAvailable = (type: string) => SECRET_SYNCS.includes(type);
export const CloudIntegrationSection = ({
isLoading,
cloudIntegrations = [],
integrationAuths = {},
onIntegrationStart,
onIntegrationRevoke,
onViewActiveIntegrations
}: Props) => {
const { t } = useTranslation();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"deleteConfirmation"
] as const);
const { permission } = useProjectPermission();
const { currentOrg } = useOrganization();
const { currentProject } = useProject();
const navigate = useNavigate();
const isEmpty = !isLoading && !cloudIntegrations?.length;
const sortedCloudIntegrations = useMemo(() => {
const sortedIntegrations = cloudIntegrations.sort((a, b) => a.name.localeCompare(b.name));
if (currentProject?.environments.length === 0) {
return sortedIntegrations.map((integration) => ({ ...integration, isAvailable: false }));
}
return sortedIntegrations;
}, [cloudIntegrations, currentProject?.environments]);
const [search, setSearch] = useState("");
const filteredIntegrations = sortedCloudIntegrations?.filter((cloudIntegration) =>
cloudIntegration.name.toLowerCase().includes(search.toLowerCase().trim())
);
return (
<div>
{currentProject?.environments.length === 0 && (
<div className="px-5">
<NoEnvironmentsBanner projectId={currentProject.id} />
</div>
)}
<div className="m-4 mt-0 flex flex-col items-start justify-between px-2 text-xl">
{onViewActiveIntegrations && (
<Button
variant="link"
onClick={onViewActiveIntegrations}
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
>
Back to Integrations
</Button>
)}
<div className="flex w-full flex-col justify-between gap-4 whitespace-nowrap lg:flex-row lg:items-end lg:gap-8">
<div className="flex-1">
<h1 className="text-3xl font-medium">{t("integrations.cloud-integrations")}</h1>
<p className="text-base text-gray-400">{t("integrations.click-to-start")}</p>
</div>
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search cloud integrations..."
containerClassName="flex-1 h-min text-base"
/>
</div>
</div>
<div className="mx-2 grid grid-cols-3 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7">
{isLoading &&
Array.from({ length: 12 }).map((_, index) => (
<Skeleton className="h-32" key={`cloud-integration-skeleton-${index + 1}`} />
))}
{!isLoading && filteredIntegrations.length ? (
filteredIntegrations.map((cloudIntegration) => {
const syncSlug = cloudIntegration.syncSlug ?? cloudIntegration.slug;
const isSyncAvailable = isSecretSyncAvailable(syncSlug);
return (
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
className={`group relative ${
cloudIntegration.isAvailable
? "cursor-pointer duration-200 hover:bg-mineshaft-700"
: "opacity-50"
} flex h-36 flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-3`}
onClick={() => {
if (isSyncAvailable) {
navigate({
to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path,
params: {
orgId: currentOrg.id,
projectId: currentProject.id
},
search: {
selectedTab: IntegrationsListPageTabs.SecretSyncs,
addSync: syncSlug as SecretSync
}
});
return;
}
if (!cloudIntegration.isAvailable) return;
if (
permission.cannot(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
)
) {
createNotification({
type: "error",
text: "You do not have permission to create an integration"
});
return;
}
onIntegrationStart(cloudIntegration.slug);
}}
key={cloudIntegration.slug}
>
<div className="m-auto flex flex-col items-center">
<img
src={`/images/integrations/${cloudIntegration.image}`}
height={60}
width={60}
className="mt-auto"
alt="integration logo"
/>
<div
className={`mt-2 max-w-xs text-center text-sm font-medium text-gray-300 duration-200 group-hover:text-gray-200 ${isSyncAvailable ? "mb-4" : ""}`}
>
{cloudIntegration.name}
</div>
</div>
{cloudIntegration.isAvailable &&
Boolean(integrationAuths?.[cloudIntegration.slug]) && (
<div className="absolute top-0 right-0 z-30 h-full">
<div className="relative h-full">
<div className="absolute top-0 right-0 w-24 flex-row items-center overflow-hidden rounded-tr-md rounded-bl-md bg-primary px-2 py-0.5 text-xs whitespace-nowrap text-black opacity-80 transition-all duration-300 group-hover:w-0 group-hover:p-0">
<FontAwesomeIcon icon={faCheck} className="mr-2 text-xs" />
Authorized
</div>
<Tooltip content="Revoke Access">
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={async (event) => {
event.stopPropagation();
handlePopUpOpen("deleteConfirmation", {
provider: cloudIntegration.slug
});
}}
className="absolute top-0 right-0 flex h-0 w-12 cursor-pointer items-center justify-center overflow-hidden rounded-r-md bg-red text-xs opacity-50 transition-all duration-300 group-hover:h-full hover:opacity-100"
>
<FontAwesomeIcon icon={faXmark} size="xl" />
</div>
</Tooltip>
</div>
</div>
)}
{isSyncAvailable && (
<div className="absolute bottom-0 left-0 z-30 h-full w-full">
<div className="relative h-full">
<div className="absolute bottom-0 left-0 w-full flex-row overflow-hidden rounded-br-md rounded-bl-md bg-yellow/20 px-2 py-0.5 text-center text-xs whitespace-nowrap text-yellow">
Secret Sync Available
</div>
</div>
</div>
)}
</div>
);
})
) : (
<EmptyState
className="col-span-full h-32 w-full rounded-md bg-transparent pt-14"
title="No cloud integrations match search..."
icon={faSearch}
/>
)}
</div>
{isEmpty && (
<div className="mx-6 grid max-w-5xl grid-cols-4 grid-rows-2 gap-4">
{Array.from({ length: 16 }).map((_, index) => (
<div
key={`dummy-cloud-integration-${index + 1}`}
className="h-32 animate-pulse rounded-md border border-mineshaft-600 bg-mineshaft-800"
/>
))}
</div>
)}
<DeleteActionModal
isOpen={popUp.deleteConfirmation.isOpen}
title={`Are you sure you want to revoke access ${
(popUp?.deleteConfirmation.data as TRevokeIntegrationPopUp)?.provider || " "
}?`}
subTitle="This will remove all the secret integration of this provider!!!"
onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)}
deleteKey={(popUp?.deleteConfirmation?.data as TRevokeIntegrationPopUp)?.provider || ""}
onDeleteApproved={async () => {
onIntegrationRevoke(
(popUp.deleteConfirmation.data as TRevokeIntegrationPopUp)?.provider,
() => handlePopUpClose("deleteConfirmation")
);
}}
/>
</div>
);
};

View File

@@ -1 +0,0 @@
export { CloudIntegrationSection } from "./CloudIntegrationSection";

View File

@@ -1,11 +1,8 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect } from "react";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, Checkbox, DeleteActionModal, Spinner } from "@app/components/v2"; import { Checkbox, DeleteActionModal, Spinner } from "@app/components/v2";
import { useOrganization, useProject } from "@app/context"; import { useProject } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks"; import { usePopUp, useToggle } from "@app/hooks";
import { import {
useDeleteIntegration, useDeleteIntegration,
@@ -17,38 +14,26 @@ import {
import { IntegrationAuth } from "@app/hooks/api/integrationAuth/types"; import { IntegrationAuth } from "@app/hooks/api/integrationAuth/types";
import { TIntegration } from "@app/hooks/api/integrations/types"; import { TIntegration } from "@app/hooks/api/integrations/types";
import { redirectForProviderAuth } from "../../IntegrationsListPage.utils";
import { CloudIntegrationSection } from "../CloudIntegrationSection";
import { IntegrationsTable } from "./IntegrationsTable"; import { IntegrationsTable } from "./IntegrationsTable";
enum IntegrationView {
List = "list",
New = "new"
}
export const NativeIntegrationsTab = () => { export const NativeIntegrationsTab = () => {
const { currentOrg } = useOrganization();
const { currentProject } = useProject(); const { currentProject } = useProject();
const { environments, id: workspaceId } = currentProject; const { environments, id: workspaceId } = currentProject;
const navigate = useNavigate();
const { data: cloudIntegrations, isPending: isCloudIntegrationsLoading } = const { data: cloudIntegrations, isPending: isCloudIntegrationsLoading } =
useGetCloudIntegrations(); useGetCloudIntegrations();
const { const { data: integrationAuths, isFetching: isIntegrationAuthFetching } =
data: integrationAuths, useGetWorkspaceAuthorizations(
isPending: isIntegrationAuthLoading, workspaceId,
isFetching: isIntegrationAuthFetching useCallback((data: IntegrationAuth[]) => {
} = useGetWorkspaceAuthorizations( const groupBy: Record<string, IntegrationAuth> = {};
workspaceId, data.forEach((el) => {
useCallback((data: IntegrationAuth[]) => { groupBy[el.integration] = el;
const groupBy: Record<string, IntegrationAuth> = {}; });
data.forEach((el) => { return groupBy;
groupBy[el.integration] = el; }, [])
}); );
return groupBy;
}, [])
);
// mutation // mutation
const { const {
@@ -58,11 +43,8 @@ export const NativeIntegrationsTab = () => {
} = useGetWorkspaceIntegrations(workspaceId); } = useGetWorkspaceIntegrations(workspaceId);
const { mutateAsync: deleteIntegration } = useDeleteIntegration(); const { mutateAsync: deleteIntegration } = useDeleteIntegration();
const {
mutateAsync: deleteIntegrationAuths, const { reset: resetDeleteIntegrationAuths } = useDeleteIntegrationAuths();
isSuccess: isDeleteIntegrationAuthSuccess,
reset: resetDeleteIntegrationAuths
} = useDeleteIntegrationAuths();
const isIntegrationsAuthorizedEmpty = !Object.keys(integrationAuths || {}).length; const isIntegrationsAuthorizedEmpty = !Object.keys(integrationAuths || {}).length;
const isIntegrationsEmpty = !integrations?.length; const isIntegrationsEmpty = !integrations?.length;
@@ -71,7 +53,6 @@ export const NativeIntegrationsTab = () => {
// After the refetch is completed check if its empty. Then set bot active and reset the submit hook for isSuccess to go back to false // After the refetch is completed check if its empty. Then set bot active and reset the submit hook for isSuccess to go back to false
useEffect(() => { useEffect(() => {
if ( if (
isDeleteIntegrationAuthSuccess &&
!isIntegrationFetching && !isIntegrationFetching &&
!isIntegrationAuthFetching && !isIntegrationAuthFetching &&
isIntegrationsAuthorizedEmpty && isIntegrationsAuthorizedEmpty &&
@@ -81,29 +62,11 @@ export const NativeIntegrationsTab = () => {
} }
}, [ }, [
isIntegrationFetching, isIntegrationFetching,
isDeleteIntegrationAuthSuccess,
isIntegrationAuthFetching, isIntegrationAuthFetching,
isIntegrationsAuthorizedEmpty, isIntegrationsAuthorizedEmpty,
isIntegrationsEmpty isIntegrationsEmpty
]); ]);
const handleProviderIntegration = async (provider: string) => {
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);
if (!selectedCloudIntegration) return;
try {
redirectForProviderAuth(currentOrg.id, currentProject.id, navigate, selectedCloudIntegration);
} catch (error) {
console.error(error);
}
};
// function to strat integration for a provider
// confirmation to user passing the bot key for provider to get secret access
const handleProviderIntegrationStart = (provider: string) => {
handleProviderIntegration(provider);
};
const handleIntegrationDelete = async ( const handleIntegrationDelete = async (
integrationId: string, integrationId: string,
shouldDeleteIntegrationSecrets: boolean, shouldDeleteIntegrationSecrets: boolean,
@@ -117,28 +80,11 @@ export const NativeIntegrationsTab = () => {
}); });
}; };
const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => {
const integrationAuthForProvider = integrationAuths?.[provider];
if (!integrationAuthForProvider) return;
await deleteIntegrationAuths({
integration: provider,
workspaceId
});
if (cb) cb();
createNotification({
type: "success",
text: "Revoked provider authentication"
});
};
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"deleteConfirmation", "deleteConfirmation",
"deleteSecretsConfirmation" "deleteSecretsConfirmation"
] as const); ] as const);
const [view, setView] = useState<IntegrationView>(IntegrationView.List);
const [shouldDeleteSecrets, setShouldDeleteSecrets] = useToggle(false); const [shouldDeleteSecrets, setShouldDeleteSecrets] = useToggle(false);
if (isIntegrationLoading || isCloudIntegrationsLoading) if (isIntegrationLoading || isCloudIntegrationsLoading)
@@ -150,18 +96,10 @@ export const NativeIntegrationsTab = () => {
return ( return (
<> <>
{view === IntegrationView.List ? ( {integrations?.length && (
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<p className="text-xl font-medium text-mineshaft-100">Native Integrations</p> <p className="text-xl font-medium text-mineshaft-100">Native Integrations</p>
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => setView(IntegrationView.New)}
>
Add Integration
</Button>
</div> </div>
<IntegrationsTable <IntegrationsTable
cloudIntegrations={cloudIntegrations} cloudIntegrations={cloudIntegrations}
@@ -175,15 +113,6 @@ export const NativeIntegrationsTab = () => {
}} }}
/> />
</div> </div>
) : (
<CloudIntegrationSection
onIntegrationStart={handleProviderIntegrationStart}
onIntegrationRevoke={handleIntegrationAuthRevoke}
integrationAuths={integrationAuths}
cloudIntegrations={cloudIntegrations}
isLoading={isIntegrationAuthLoading || isCloudIntegrationsLoading}
onViewActiveIntegrations={() => setView(IntegrationView.List)}
/>
)} )}
<DeleteActionModal <DeleteActionModal
isOpen={popUp.deleteConfirmation.isOpen} isOpen={popUp.deleteConfirmation.isOpen}

View File

@@ -121,6 +121,9 @@ const Page = () => {
const tableRef = useRef<HTMLTableElement>(null); const tableRef = useRef<HTMLTableElement>(null);
const [isVisible, setIsVisible] = useState(false); const [isVisible, setIsVisible] = useState(false);
const [selectedDynamicSecretId, setSelectedDynamicSecretId] = useState<string | null>(
routerQueryParams.dynamicSecretId || ""
);
const { isBatchMode, pendingChanges } = useBatchMode(); const { isBatchMode, pendingChanges } = useBatchMode();
const { loadPendingChanges, setExistingKeys } = useBatchModeActions(); const { loadPendingChanges, setExistingKeys } = useBatchModeActions();
@@ -165,6 +168,28 @@ const Page = () => {
if (isVisible) setIsVisible(false); if (isVisible) setIsVisible(false);
}, [environment]); }, [environment]);
useEffect(() => {
if (routerQueryParams.dynamicSecretId !== null) {
setSelectedDynamicSecretId(routerQueryParams.dynamicSecretId);
navigate({
search: (prev) => ({
...prev,
dynamicSecretId: undefined
})
});
// if any of the router query params are changed, we have to clear the selected dynamic secret id to avoid re-rendering the lease modal when it suddendly becomes available
} else {
setSelectedDynamicSecretId(null);
}
}, [
routerQueryParams.filterBy,
routerQueryParams.search,
routerQueryParams.secretPath,
routerQueryParams.tags
]);
const canReadSecret = hasSecretReadValueOrDescribePermission( const canReadSecret = hasSecretReadValueOrDescribePermission(
permission, permission,
ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSecretActions.DescribeSecret,
@@ -1039,6 +1064,7 @@ const Page = () => {
)} )}
{canReadDynamicSecret && Boolean(dynamicSecrets?.length) && ( {canReadDynamicSecret && Boolean(dynamicSecrets?.length) && (
<DynamicSecretListView <DynamicSecretListView
selectedDynamicSecretId={selectedDynamicSecretId}
environment={environment} environment={environment}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}

View File

@@ -1,3 +1,4 @@
import { useEffect } from "react";
import { subject } from "@casl/ability"; import { subject } from "@casl/ability";
import { faEdit, faFingerprint, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; import { faEdit, faFingerprint, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -13,6 +14,7 @@ import {
Tag, Tag,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteDynamicSecret } from "@app/hooks/api"; import { useDeleteDynamicSecret } from "@app/hooks/api";
@@ -36,9 +38,11 @@ type Props = {
environment: string; environment: string;
projectSlug: string; projectSlug: string;
secretPath?: string; secretPath?: string;
selectedDynamicSecretId: string | null;
}; };
export const DynamicSecretListView = ({ export const DynamicSecretListView = ({
selectedDynamicSecretId,
dynamicSecrets = [], dynamicSecrets = [],
environment, environment,
projectSlug, projectSlug,
@@ -71,6 +75,15 @@ export const DynamicSecretListView = ({
}); });
}; };
useEffect(() => {
if (
selectedDynamicSecretId &&
dynamicSecrets.find((secret) => secret.id === selectedDynamicSecretId)
) {
handlePopUpOpen("dynamicSecretLeases", selectedDynamicSecretId);
}
}, [selectedDynamicSecretId]);
return ( return (
<> <>
{dynamicSecrets.map((secret) => { {dynamicSecrets.map((secret) => {
@@ -231,7 +244,12 @@ export const DynamicSecretListView = ({
</div> </div>
</div> </div>
<ModalContent <ModalContent
title="Dynamic secret leases" title={
<div className="flex items-center space-x-2">
<p>Dynamic secret leases</p>
<Badge variant="neutral">{secret.name}</Badge>
</div>
}
subTitle="Revoke or renew your secret leases" subTitle="Revoke or renew your secret leases"
className="max-w-3xl" className="max-w-3xl"
> >

View File

@@ -12,6 +12,7 @@ const SecretDashboardPageQueryParamsSchema = z.object({
search: z.string().catch(""), search: z.string().catch(""),
tags: z.string().catch(""), tags: z.string().catch(""),
filterBy: z.string().catch(""), filterBy: z.string().catch(""),
dynamicSecretId: z.string().catch(""),
connectionId: z.string().optional(), connectionId: z.string().optional(),
connectionName: z.string().optional() connectionName: z.string().optional()
}); });
@@ -26,7 +27,8 @@ export const Route = createFileRoute(
secretPath: "/", secretPath: "/",
search: "", search: "",
tags: "", tags: "",
filterBy: "" filterBy: "",
dynamicSecretId: ""
}) })
] ]
}, },