mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge branch 'main' of https://github.com/Infisical/infisical into feat/adds-expiring-scim-token-notification
This commit is contained in:
15
.env.example
15
.env.example
@@ -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=
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Delete Auth By ID"
|
|
||||||
openapi: "DELETE /api/v1/integration-auth/{integrationAuthId}"
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Delete Auth"
|
|
||||||
openapi: "DELETE /api/v1/integration-auth"
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Delete"
|
|
||||||
openapi: "DELETE /api/v1/integration/{integrationId}"
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Get Auth By ID"
|
|
||||||
openapi: "GET /api/v1/integration-auth/{integrationAuthId}"
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "List Auth"
|
|
||||||
openapi: "GET /api/v1/workspace/{workspaceId}/authorizations"
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "List Project Integrations"
|
|
||||||
openapi: "GET /api/v1/workspace/{workspaceId}/integrations"
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Update"
|
|
||||||
openapi: "PATCH /api/v1/integration/{integrationId}"
|
|
||||||
---
|
|
||||||
@@ -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>
|
|
||||||
@@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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?">
|
||||||
|
|||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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.
|
|
||||||
|
|
||||||

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

|
|
||||||
|
|
||||||
<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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -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>
|
|
||||||
|
|
||||||

|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||

|

|
||||||
@@ -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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Bitbucket tile and grant Infisical access to your Bitbucket account.
|
|
||||||
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title='Configure integration'>
|
|
||||||
Select which workspace, repository, and optionally, deployment environment, you'd like to sync your secrets
|
|
||||||
to.
|
|
||||||

|
|
||||||
|
|
||||||
Once created, your integration will begin syncing secrets to the configured repository or deployment
|
|
||||||
environment.
|
|
||||||
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="Pull secrets in Bitbucket pipelines from Infisical">
|
<Accordion title="Pull secrets in Bitbucket pipelines from Infisical">
|
||||||
<Steps>
|
<Steps>
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the CircleCI tile and input your CircleCI API token to grant Infisical access to your CircleCI account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</Step>
|
|
||||||
<Step title="Start integration">
|
|
||||||
Select which Infisical environment secrets you want to sync to which CircleCI project or context.
|
|
||||||
<Tabs>
|
|
||||||
<Tab title="Project">
|
|
||||||

|
|
||||||
</Tab>
|
|
||||||
<Tab title="Context">
|
|
||||||

|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
Finally, press create integration to start syncing secrets to CircleCI.
|
|
||||||

|
|
||||||
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Codefresh tile and input your Codefresh API key to grant Infisical access to your Codefresh account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Select GitHub App as the authentication method and click **Connect to GitHub**.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
You will then be redirected to the GitHub app installation page.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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">
|
|
||||||

|
|
||||||
</Tab>
|
|
||||||
<Tab title="Organization">
|
|
||||||

|
|
||||||
|
|
||||||
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">
|
|
||||||

|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
Finally, press create integration to start syncing secrets to GitHub.
|
|
||||||
|
|
||||||

|
|
||||||
</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**.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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`).
|
|
||||||
|
|
||||||

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

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

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

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

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

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

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

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

|
|
||||||
|
|
||||||
Back in your Infisical instance, add the five new environment variables for the credentials of your GitHub application:
|
|
||||||
|
|
||||||
- `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.
|
|
||||||

|
|
||||||
|
|
||||||
Select OAuth as the authentication method and click **Connect to GitHub**.
|
|
||||||

|
|
||||||
|
|
||||||
Grant Infisical access to your GitHub account (organization and repo privileges).
|
|
||||||

|
|
||||||
|
|
||||||
</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">
|
|
||||||

|
|
||||||
</Tab>
|
|
||||||
<Tab title="Organization">
|
|
||||||

|
|
||||||
|
|
||||||
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">
|
|
||||||

|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
Finally, press create integration to start syncing secrets to GitHub.
|
|
||||||
|
|
||||||

|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||

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

|
|
||||||
|
|
||||||
Back in your Infisical instance, add two new environment variables for the credentials of your GitHub OAuth application:
|
|
||||||
|
|
||||||
- `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>
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the GitLab tile and grant Infisical access to your GitLab account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||

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

|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Create the application. As part of the form, set the **Redirect URI** to `https://your-domain.com/integrations/gitlab/oauth2/callback`.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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>
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Fill out the required fields and click on the **Save** button.
|
|
||||||

|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Fill out the required fields and click on the **Generate New** button.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<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.
|
|
||||||
|
|
||||||

|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Create a new team for **Service Accounts** and click on the **Save** button.
|
|
||||||

|
|
||||||
|
|
||||||
<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.
|
|
||||||

|
|
||||||
|
|
||||||
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.
|
|
||||||

|
|
||||||
|
|
||||||
Save your team changes by clicking on the **Save** button.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Setup Integration">
|
|
||||||
In Infisical, navigate to your **Project** > **Integrations** page and select the **Octopus Deploy** integration.
|
|
||||||

|
|
||||||
|
|
||||||
Enter your **Instance URL** and **API Key** from **Octopus Deploy** to authorize Infisical.
|
|
||||||

|
|
||||||
|
|
||||||
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.
|
|
||||||

|
|
||||||
|
|
||||||
Your Infisical secrets will begin to sync to **Octopus Deploy**.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Rundeck tile and input your Rundeck instance Base URL and User API token to grant Infisical access to manage Rundeck keys
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Travis CI tile and input your Travis CI API token to grant Infisical access to your Travis CI account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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">
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Create a new token">
|
|
||||||
Make sure the newly created token has Read/Write access to the Release scope.
|
|
||||||

|
|
||||||
|
|
||||||
<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.
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
#### Setup the Infisical Azure DevOps integration
|
|
||||||
Navigate to your project's integrations tab and select the 'Azure DevOps' integration.
|
|
||||||

|
|
||||||
|
|
||||||
<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.
|
|
||||||
|
|
||||||

|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||
</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.
|
|
||||||
@@ -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>
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Checkly tile and input your Checkly API Key to grant Infisical access to your Checkly account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</Step>
|
|
||||||
<Step title="Start integration">
|
|
||||||
Select which Infisical environment secrets you want to sync to Checkly and press create integration to start syncing secrets.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<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>
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Enter your Cloud 66 Access Token
|
|
||||||
|
|
||||||
In Cloud 66 Dashboard, click on the top right icon > Account Settings > Access Token
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Create new Personal Access Token.
|
|
||||||

|
|
||||||
|
|
||||||
Name it **infisical** and check **Public** and **Admin**. Then click "Create Token"
|
|
||||||

|
|
||||||
|
|
||||||
Copy and save your token.
|
|
||||||

|
|
||||||
|
|
||||||
### 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.
|
|
||||||

|
|
||||||
|
|
||||||
Enter your Cloud 66 Personal Access Token here. Then click "Connect to Cloud 66".
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
## 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.
|
|
||||||

|
|
||||||
|
|
||||||
<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!
|
|
||||||

|
|
||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Copy your [Account ID](https://developers.cloudflare.com/fundamentals/get-started/basic-tasks/find-account-and-zone-ids/) from Account > Workers & Pages > Overview
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Cloudflare Pages tile and input your Cloudflare API token and account ID to grant Infisical access to your Cloudflare Pages.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</Step>
|
|
||||||
<Step title="Start integration">
|
|
||||||
Select which Infisical environment secrets you want to sync to Cloudflare and press create integration to start syncing secrets.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Copy your [Account ID](https://developers.cloudflare.com/fundamentals/get-started/basic-tasks/find-account-and-zone-ids/) from Account > Workers & Pages > Overview
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Cloudflare Workers tile and input your Cloudflare API token and account ID to grant Infisical access to your Cloudflare Workers.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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"
|
|
||||||

|
|
||||||
|
|
||||||
Name it **infisical**, choose **No expiry**, and make sure to check **Write (optional)**. Then click on "Generate Token" and copy your API token.
|
|
||||||

|
|
||||||
|
|
||||||
## 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.
|
|
||||||

|
|
||||||
|
|
||||||
Then enter your Digital Ocean Personal Access Token here. Then click "Connect to Digital Ocean App Platform".
|
|
||||||

|
|
||||||
|
|
||||||
## Start integration
|
|
||||||
|
|
||||||
Select which Infisical environment secrets you want to sync to which Digital Ocean App and click "Create Integration".
|
|
||||||

|
|
||||||
|
|
||||||
Done!
|
|
||||||

|
|
||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Fly.io tile and input your Fly.io access token to grant Infisical access to your Fly.io account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Hasura Cloud tile and input your Hasura Cloud access token to grant Infisical access to your Hasura Cloud account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Heroku tile and grant Infisical access to your Heroku account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||

|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Create the API client. As part of the form, set the **OAuth callback URL** to `https://your-domain.com/integrations/heroku/oauth2/callback`.
|
|
||||||
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Add your Heroku API client credentials to Infisical">
|
|
||||||
Obtain the **Client ID** and **Client Secret** for your Heroku API client.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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>
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Obtain your Laravel Forge Server ID in Servers > Server ID
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Netlify tile and grant Infisical access to your Netlify account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Create the OAuth application. As part of the form, set the **Redirect URI** to `https://your-domain.com/integrations/netlify/oauth2/callback`.
|
|
||||||
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
<Step title="Add your Netlify OAuth application credentials to Infisical">
|
|
||||||
Obtain the **Client ID** and **Secret** for your Netlify OAuth application.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Northflank tile and input your Northflank API token to grant Infisical access to your Northflank account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Qovery tile and input your Qovery API Token to grant Infisical access to your Qovery account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</Step>
|
|
||||||
<Step title="Start integration">
|
|
||||||
Select which Infisical environment secrets you want to sync to Qovery and press create integration to start syncing secrets.
|
|
||||||
|
|
||||||

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

|
|
||||||
|
|
||||||
<Note>
|
|
||||||
Infisical supports syncing secrets to various Qovery scopes including applications, jobs, or containers.
|
|
||||||
</Note>
|
|
||||||
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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).
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
<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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Railway tile and input your Railway API Key to grant Infisical access to your Railway account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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).
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Supabase tile and input your Supabase Access Token to grant Infisical access to your Supabase account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</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.
|
|
||||||
|
|
||||||

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

|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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 |
|
|
||||||
98
docs/integrations/platforms/aws/lambda.mdx
Normal file
98
docs/integrations/platforms/aws/lambda.mdx
Normal 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>
|
||||||
@@ -8,12 +8,12 @@ It eliminates the need to modify application logic by enabling clients to decide
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### 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>
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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'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'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>
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { CloudIntegrationSection } from "./CloudIntegrationSection";
|
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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: ""
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user