Compare commits

...

33 Commits

Author SHA1 Message Date
Sheen Capadngan
089a7e880b misc: added message for bypass 2024-11-18 17:29:01 +08:00
Sheen Capadngan
3f6a0c77f1 misc: finalized user messages 2024-11-09 01:51:11 +08:00
Sheen Capadngan
9e4b66e215 misc: made users automatically verified 2024-11-09 00:38:45 +08:00
Sheen Capadngan
8a14914bc3 misc: added more error handling 2024-11-08 21:43:25 +08:00
Vlad Matsiiako
bf09fa33fa Merge pull request #2705 from Infisical/vmatsiiako-changelog-patch-1-2
Update changelog
2024-11-07 18:27:49 -08:00
Vlad Matsiiako
a87e7b792c fix typo 2024-11-07 18:17:43 -08:00
Vlad Matsiiako
e8ca020903 Update changelog 2024-11-07 18:10:59 -08:00
Vlad Matsiiako
a603938488 Fix typo 2024-11-07 18:08:29 -08:00
Vlad Matsiiako
cff7981fe0 Added Update component to changelog 2024-11-07 18:07:25 -08:00
Vlad Matsiiako
b39d5c6682 Update changelog 2024-11-07 17:54:28 -08:00
Maidul Islam
dd1f1d07cc Merge pull request #2689 from Infisical/doc/updated-internal-permission
doc: updated internal permission docs for v2
2024-11-07 13:42:01 -05:00
BlackMagiq
c3f8c55672 Merge pull request #2703 from Infisical/remove-ip
Remove unused ip package from frontend
2024-11-07 09:54:10 -08:00
Tuan Dang
75aeef3897 Remove ip package from frontend 2024-11-07 09:48:14 -08:00
Maidul Islam
c97fe77aec Merge pull request #2696 from akhilmhdh/fix/debounce-secret-sync
feat: added queue level debounce for secret sync and removed stale check
2024-11-07 12:37:36 -05:00
Sheen Capadngan
3e16d7e160 doc: added migration tips 2024-11-07 18:51:26 +08:00
Maidul Islam
6bf4b4a380 Merge pull request #2692 from Infisical/daniel/more-envkey-fixes
fix(external-migrations): env-key edge cases
2024-11-07 02:27:46 -05:00
Maidul Islam
9dedaa6779 update infisical helm docs 2024-11-06 16:57:02 -05:00
Maidul Islam
8eab7d2f01 Merge pull request #2700 from Infisical/infisical-helm-auto-create-sa
Add support for auto creating SA for job and deployment
2024-11-06 16:41:57 -05:00
Maidul Islam
4e796e7e41 Add support for auto creating SA for job and deployment 2024-11-06 16:37:34 -05:00
Maidul Islam
c6fa647825 Merge pull request #2699 from Infisical/misc/address-remaining-ui-ux-issues-audit
misc: address other ui/ux issues with audit logs
2024-11-06 14:24:42 -05:00
Sheen Capadngan
496cebb08f misc: address other ui/ux issues with audit 2024-11-07 03:07:39 +08:00
Maidul Islam
33db6df7f2 Merge pull request #2698 from Infisical/misc/made-audit-logs-metadata-json
misc: made audit logs metadata into json
2024-11-06 12:36:28 -05:00
Sheen Capadngan
88d25e97e9 misc: added undefined handling for actor 2024-11-07 01:33:38 +08:00
Sheen Capadngan
4ad9fa1ad1 misc: made audit logs metadata into json 2024-11-07 01:26:26 +08:00
=
1642fb42d8 feat: resolved test failing due to timeout 2024-11-06 16:54:54 +05:30
=
3983c2bc4a feat: added queue level debounce for secret sync and removed stale check in sync 2024-11-06 16:29:03 +05:30
Daniel Hougaard
34d87ca30f Update external-migration-fns.ts 2024-11-06 10:49:45 +04:00
Daniel Hougaard
12b6f27151 fix envkey 2024-11-06 10:35:27 +04:00
Maidul Islam
ea426e8b2d Merge pull request #2685 from akhilmhdh/fix/tag-no-update-in-approval
fix: resolved tag update not happening via approval
2024-11-05 09:54:13 -05:00
=
4d567f0b08 fix: resolved tag update not happening via approval 2024-11-05 20:18:16 +05:30
Sheen
6548372e3b Merge pull request #2690 from Infisical/feat/add-mssql-secret-rotation-support
feat: add mssql secret rotation template
2024-11-05 22:33:56 +08:00
Sheen Capadngan
4f1fe8a9fa doc: updated overview 2024-11-05 01:37:26 +08:00
Sheen Capadngan
b0031b71e0 doc: updated internal permission docs 2024-11-05 01:21:35 +08:00
29 changed files with 701 additions and 728 deletions

View File

@@ -5,6 +5,9 @@ export const mockSmtpServer = (): TSmtpService => {
return {
sendMail: async (data) => {
storage.push(data);
},
verify: async () => {
return true;
}
};
};

View File

@@ -118,9 +118,9 @@ describe.each([{ secretPath: "/" }, { secretPath: "/deep" }])(
value: "stage-value"
});
// wait for 5 second for replication to finish
// wait for 10 second for replication to finish
await new Promise((resolve) => {
setTimeout(resolve, 5000); // time to breathe for db
setTimeout(resolve, 10000); // time to breathe for db
});
const secret = await getSecretByNameV2({
@@ -173,9 +173,9 @@ describe.each([{ secretPath: "/" }, { secretPath: "/deep" }])(
value: "prod-value"
});
// wait for 5 second for replication to finish
// wait for 10 second for replication to finish
await new Promise((resolve) => {
setTimeout(resolve, 5000); // time to breathe for db
setTimeout(resolve, 10000); // time to breathe for db
});
const secret = await getSecretByNameV2({
@@ -343,9 +343,9 @@ describe.each([{ path: "/" }, { path: "/deep" }])(
value: "prod-value"
});
// wait for 5 second for replication to finish
// wait for 10 second for replication to finish
await new Promise((resolve) => {
setTimeout(resolve, 5000); // time to breathe for db
setTimeout(resolve, 10000); // time to breathe for db
});
const secret = await getSecretByNameV2({

View File

@@ -17,7 +17,7 @@ import {
infisicalSymmetricDecrypt,
infisicalSymmetricEncypt
} from "@app/lib/crypto/encryption";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TokenType } from "@app/services/auth-token/auth-token-types";
@@ -56,7 +56,7 @@ type TOidcConfigServiceFactoryDep = {
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
smtpService: Pick<TSmtpService, "sendMail">;
smtpService: Pick<TSmtpService, "sendMail" | "verify">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne" | "update" | "create">;
};
@@ -223,6 +223,7 @@ export const oidcConfigServiceFactory = ({
let newUser: TUsers | undefined;
if (serverCfg.trustOidcEmails) {
// we prioritize getting the most complete user to create the new alias under
newUser = await userDAL.findOne(
{
email,
@@ -230,6 +231,23 @@ export const oidcConfigServiceFactory = ({
},
tx
);
if (!newUser) {
// this fetches user entries created via invites
newUser = await userDAL.findOne(
{
username: email
},
tx
);
if (newUser && !newUser.isEmailVerified) {
// we automatically mark it as email-verified because we've configured trust for OIDC emails
newUser = await userDAL.updateById(newUser.id, {
isEmailVerified: true
});
}
}
}
if (!newUser) {
@@ -332,14 +350,20 @@ export const oidcConfigServiceFactory = ({
userId: user.id
});
await smtpService.sendMail({
template: SmtpTemplates.EmailVerification,
subjectLine: "Infisical confirmation code",
recipients: [user.email],
substitutions: {
code: token
}
});
await smtpService
.sendMail({
template: SmtpTemplates.EmailVerification,
subjectLine: "Infisical confirmation code",
recipients: [user.email],
substitutions: {
code: token
}
})
.catch((err: Error) => {
throw new OidcAuthError({
message: `Error sending email confirmation code for user registration - contact the Infisical instance admin. ${err.message}`
});
});
}
return { isUserCompleted, providerAuthToken };
@@ -395,6 +419,18 @@ export const oidcConfigServiceFactory = ({
message: `Organization bot for organization with ID '${org.id}' not found`,
name: "OrgBotNotFound"
});
const serverCfg = await getServerCfg();
if (isActive && !serverCfg.trustOidcEmails) {
const isSmtpConnected = await smtpService.verify();
if (!isSmtpConnected) {
throw new BadRequestError({
message:
"Cannot enable OIDC when there are issues with the instance's SMTP configuration. Bypass this by turning on trust for OIDC emails in the server admin console."
});
}
}
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,

View File

@@ -267,7 +267,8 @@ export const secretApprovalRequestServiceFactory = ({
: "",
secretComment: el.secretVersion.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
: ""
: "",
tags: el.secretVersion.tags
}
: undefined
}));
@@ -571,7 +572,7 @@ export const secretApprovalRequestServiceFactory = ({
reminderNote: el.reminderNote,
skipMultilineEncoding: el.skipMultilineEncoding,
key: el.key,
tagIds: el?.tags.map(({ id }) => id),
tags: el?.tags.map(({ id }) => id),
...encryptedValue
}
};

View File

@@ -29,7 +29,7 @@ export const KeyStorePrefixes = {
};
export const KeyStoreTtls = {
SetSyncSecretIntegrationLastRunTimestampInSeconds: 10,
SetSyncSecretIntegrationLastRunTimestampInSeconds: 60,
AccessTokenStatusUpdateInSeconds: 120
};

View File

@@ -133,3 +133,15 @@ export class ScimRequestError extends Error {
this.status = status;
}
}
export class OidcAuthError extends Error {
name: string;
error: unknown;
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown }) {
super(message || "Something went wrong");
this.name = name || "OidcAuthError";
this.error = error;
}
}

View File

@@ -46,10 +46,10 @@ export const bootstrapCheck = async ({ db }: BootstrapOpt) => {
await createTransport(smtpCfg)
.verify()
.then(async () => {
console.info("SMTP successfully connected");
console.info(`SMTP - Verified connection to ${appCfg.SMTP_HOST}:${appCfg.SMTP_PORT}`);
})
.catch((err) => {
console.error(`SMTP - Failed to connect to ${appCfg.SMTP_HOST}:${appCfg.SMTP_PORT}`);
.catch((err: Error) => {
console.error(`SMTP - Failed to connect to ${appCfg.SMTP_HOST}:${appCfg.SMTP_PORT} - ${err.message}`);
logger.error(err);
});

View File

@@ -10,6 +10,7 @@ import {
GatewayTimeoutError,
InternalServerError,
NotFoundError,
OidcAuthError,
RateLimitError,
ScimRequestError,
UnauthorizedError
@@ -83,7 +84,10 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
status: error.status,
detail: error.detail
});
// Handle JWT errors and make them more human-readable for the end-user.
} else if (error instanceof OidcAuthError) {
void res
.status(HttpStatusCodes.InternalServerError)
.send({ statusCode: HttpStatusCodes.InternalServerError, message: error.message, error: error.name });
} else if (error instanceof jwt.JsonWebTokenError) {
const message = (() => {
if (error.message === JWTErrors.JwtExpired) {

View File

@@ -126,6 +126,8 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
return findRootInheritedSecret(inheritedEnv.variables[secretName], secretName, envs);
};
const targetIdToFolderIdsMap = new Map<string, string>();
const processBranches = () => {
for (const subEnv of parsedJson.subEnvironments) {
const app = parsedJson.apps.find((a) => a.id === subEnv.envParentId);
@@ -135,12 +137,21 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
// Handle regular app branches
const branchEnvironment = infisicalImportData.environments.find((e) => e.id === subEnv.parentEnvironmentId);
infisicalImportData.folders.push({
name: subEnv.subName,
parentFolderId: subEnv.parentEnvironmentId,
environmentId: branchEnvironment!.id,
id: subEnv.id
});
// check if the folder already exists in the same parent environment with the same name
const folderExists = infisicalImportData.folders.some(
(f) => f.name === subEnv.subName && f.parentFolderId === subEnv.parentEnvironmentId
);
// No need to map to target ID's here, because we are not dealing with blocks
if (!folderExists) {
infisicalImportData.folders.push({
name: subEnv.subName,
parentFolderId: subEnv.parentEnvironmentId,
environmentId: branchEnvironment!.id,
id: subEnv.id
});
}
}
if (block) {
@@ -162,13 +173,22 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
// eslint-disable-next-line no-continue
if (!matchingAppEnv) continue;
// 3. Create a folder in the matching app environment
infisicalImportData.folders.push({
name: subEnv.subName,
parentFolderId: matchingAppEnv.id,
environmentId: matchingAppEnv.id,
id: `${subEnv.id}-${appId}` // Create unique ID for each app's copy of the branch
});
const folderExists = infisicalImportData.folders.some(
(f) => f.name === subEnv.subName && f.parentFolderId === matchingAppEnv.id
);
if (!folderExists) {
// 3. Create a folder in the matching app environment
infisicalImportData.folders.push({
name: subEnv.subName,
parentFolderId: matchingAppEnv.id,
environmentId: matchingAppEnv.id,
id: `${subEnv.id}-${appId}` // Create unique ID for each app's copy of the branch
});
} else {
// folder already exists, so lets map the old folder id to the new folder id
targetIdToFolderIdsMap.set(subEnv.id, `${subEnv.id}-${appId}`);
}
// 4. Process secrets in the block branch for this app
const branchSecrets = parsedJson.envs[subEnv.id]?.variables || {};
@@ -408,17 +428,18 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
// Process each secret in this environment or branch
for (const [secretName, secretData] of Object.entries(envData.variables)) {
const environmentId = subEnv ? subEnv.parentEnvironmentId : env;
const indexOfExistingSecret = infisicalImportData.secrets.findIndex(
(s) => s.name === secretName && s.environmentId === environmentId
(s) =>
s.name === secretName &&
(s.environmentId === subEnv?.parentEnvironmentId || s.environmentId === env) &&
(s.folderId ? s.folderId === subEnv?.id : true) &&
(secretData.val ? s.value === secretData.val : true)
);
if (secretData.inheritsEnvironmentId) {
const resolvedSecret = findRootInheritedSecret(secretData, secretName, parsedJson.envs);
// Check if there's already a secret with this name in the environment, if there is, we should override it. Because if there's already one, we know its coming from a block.
// Variables from the normal environment should take precedence over variables from the block.
if (indexOfExistingSecret !== -1) {
// if a existing secret is found, we should replace it directly
const newSecret: (typeof infisicalImportData.secrets)[number] = {
@@ -456,12 +477,14 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
continue;
}
const folderId = targetIdToFolderIdsMap.get(subEnv?.id || "") || subEnv?.id;
infisicalImportData.secrets.push({
id: randomUUID(),
name: secretName,
environmentId: subEnv ? subEnv.parentEnvironmentId : env,
value: secretData.val || "",
...(subEnv && { folderId: subEnv.id }) // Add folderId if this is a branch secret
...(folderId && { folderId })
});
}
}
@@ -591,6 +614,7 @@ export const importDataIntoInfisicalFn = async ({
secretKey: string;
secretValue: string;
folderId?: string;
isFromBlock?: boolean;
}[]
>();
@@ -599,6 +623,8 @@ export const importDataIntoInfisicalFn = async ({
// Skip if we can't find either an environment or folder mapping for this secret
if (!originalToNewEnvironmentId.get(secret.environmentId) && !originalToNewFolderId.get(targetId)) {
logger.info({ secret }, "[importDataIntoInfisicalFn]: Could not find environment or folder for secret");
// eslint-disable-next-line no-continue
continue;
}
@@ -606,10 +632,22 @@ export const importDataIntoInfisicalFn = async ({
if (!mappedToEnvironmentId.has(targetId)) {
mappedToEnvironmentId.set(targetId, []);
}
const alreadyHasSecret = mappedToEnvironmentId
.get(targetId)!
.find((el) => el.secretKey === secret.name && el.folderId === secret.folderId);
if (alreadyHasSecret && alreadyHasSecret.isFromBlock) {
// remove the existing secret if any
mappedToEnvironmentId
.get(targetId)!
.splice(mappedToEnvironmentId.get(targetId)!.indexOf(alreadyHasSecret), 1);
}
mappedToEnvironmentId.get(targetId)!.push({
secretKey: secret.name,
secretValue: secret.value || "",
folderId: secret.folderId
folderId: secret.folderId,
isFromBlock: secret.appBlockOrderIndex !== undefined
});
}

View File

@@ -112,6 +112,8 @@ export type TGetSecrets = {
};
const MAX_SYNC_SECRET_DEPTH = 5;
const SYNC_SECRET_DEBOUNCE_INTERVAL_MS = 3000;
export const uniqueSecretQueueKey = (environment: string, secretPath: string) =>
`secret-queue-dedupe-${environment}-${secretPath}`;
@@ -169,6 +171,39 @@ export const secretQueueFactory = ({
);
};
const $generateActor = async (actorId?: string, isManual?: boolean): Promise<Actor> => {
if (isManual && actorId) {
const user = await userDAL.findById(actorId);
if (!user) {
throw new Error("User not found");
}
return {
type: ActorType.USER,
metadata: {
email: user.email,
username: user.username,
userId: user.id
}
};
}
return {
type: ActorType.PLATFORM,
metadata: {}
};
};
const $getJobKey = (projectId: string, environmentSlug: string, secretPath: string) => {
// the idea here is a timestamp based id which will be constant in a 3s interval
const timestampId = Math.floor(Date.now() / SYNC_SECRET_DEBOUNCE_INTERVAL_MS);
return `secret-queue-sync_${projectId}_${environmentSlug}_${secretPath}_${timestampId}`
.replace("/", "-")
.replace(":", "-");
};
const addSecretReminder = async ({ oldSecret, newSecret, projectId }: TCreateSecretReminderDTO) => {
try {
const appCfg = getConfig();
@@ -466,7 +501,7 @@ export const secretQueueFactory = ({
dto: TGetSecrets & { isManual?: boolean; actorId?: string; deDupeQueue?: Record<string, boolean> }
) => {
await queueService.queue(QueueName.IntegrationSync, QueueJobs.IntegrationSync, dto, {
attempts: 3,
attempts: 5,
delay: 1000,
backoff: {
type: "exponential",
@@ -479,10 +514,10 @@ export const secretQueueFactory = ({
const replicateSecrets = async (dto: Omit<TSyncSecretsDTO, "deDupeQueue">) => {
await queueService.queue(QueueName.SecretReplication, QueueJobs.SecretReplication, dto, {
attempts: 3,
attempts: 5,
backoff: {
type: "exponential",
delay: 2000
delay: 3000
},
removeOnComplete: true,
removeOnFail: true
@@ -499,6 +534,7 @@ export const secretQueueFactory = ({
logger.info(
`syncSecrets: syncing project secrets where [projectId=${dto.projectId}] [environment=${dto.environmentSlug}] [path=${dto.secretPath}]`
);
const deDuplicationKey = uniqueSecretQueueKey(dto.environmentSlug, dto.secretPath);
if (
!dto.excludeReplication
@@ -523,7 +559,8 @@ export const secretQueueFactory = ({
{
removeOnFail: true,
removeOnComplete: true,
delay: 1000,
jobId: $getJobKey(dto.projectId, dto.environmentSlug, dto.secretPath),
delay: SYNC_SECRET_DEBOUNCE_INTERVAL_MS,
attempts: 5,
backoff: {
type: "exponential",
@@ -532,7 +569,6 @@ export const secretQueueFactory = ({
}
);
};
const sendFailedIntegrationSyncEmails = async (payload: TFailedIntegrationSyncEmailsPayload) => {
const appCfg = getConfig();
if (!appCfg.isSmtpConfigured) return;
@@ -540,7 +576,6 @@ export const secretQueueFactory = ({
await queueService.queue(QueueName.IntegrationSync, QueueJobs.SendFailedIntegrationSyncEmails, payload, {
jobId: `send-failed-integration-sync-emails-${payload.projectId}-${payload.secretPath}-${payload.environmentSlug}`,
delay: 1_000 * 60, // 1 minute
removeOnFail: true,
removeOnComplete: true
});
@@ -733,80 +768,51 @@ export const secretQueueFactory = ({
);
}
const integrations = await integrationDAL.findByProjectIdV2(projectId, environment); // note: returns array of integrations + integration auths in this environment
const toBeSyncedIntegrations = integrations.filter(
// note: sync only the integrations sourced from secretPath
({ secretPath: integrationSecPath, isActive }) => isActive && isSamePath(secretPath, integrationSecPath)
const lock = await keyStore.acquireLock(
[KeyStorePrefixes.SyncSecretIntegrationLock(projectId, environment, secretPath)],
60000,
{
retryCount: 10,
retryDelay: 3000,
retryJitter: 500
}
);
const integrationsFailedToSync: { integrationId: string; syncMessage?: string }[] = [];
if (!integrations.length) return;
logger.info(
`getIntegrationSecrets: secret integration sync started [jobId=${job.id}] [jobId=${job.id}] [projectId=${job.data.projectId}] [environment=${environment}] [secretPath=${job.data.secretPath}] [depth=${depth}]`
);
const lock = await keyStore.acquireLock(
[KeyStorePrefixes.SyncSecretIntegrationLock(projectId, environment, secretPath)],
10000,
{
retryCount: 3,
retryDelay: 2000
}
);
const lockAcquiredTime = new Date();
const lastRunSyncIntegrationTimestamp = await keyStore.getItem(
KeyStorePrefixes.SyncSecretIntegrationLastRunTimestamp(projectId, environment, secretPath)
);
// check whether the integration should wait or not
if (lastRunSyncIntegrationTimestamp) {
const INTEGRATION_INTERVAL = 2000;
const isStaleSyncIntegration = new Date(job.timestamp) < new Date(lastRunSyncIntegrationTimestamp);
if (isStaleSyncIntegration) {
logger.info(
`getIntegrationSecrets: secret integration sync stale [jobId=${job.id}] [jobId=${job.id}] [projectId=${job.data.projectId}] [environment=${environment}] [secretPath=${job.data.secretPath}] [depth=${depth}]`
);
return;
}
const timeDifferenceWithLastIntegration = getTimeDifferenceInSeconds(
lockAcquiredTime.toISOString(),
lastRunSyncIntegrationTimestamp
);
if (timeDifferenceWithLastIntegration < INTEGRATION_INTERVAL && timeDifferenceWithLastIntegration > 0)
await new Promise((resolve) => {
setTimeout(resolve, 2000 - timeDifferenceWithLastIntegration * 1000);
});
}
const generateActor = async (): Promise<Actor> => {
if (isManual && actorId) {
const user = await userDAL.findById(actorId);
if (!user) {
throw new Error("User not found");
}
return {
type: ActorType.USER,
metadata: {
email: user.email,
username: user.username,
userId: user.id
}
};
}
return {
type: ActorType.PLATFORM,
metadata: {}
};
};
// akhilmhdh: this try catch is for lock release
try {
const lastRunSyncIntegrationTimestamp = await keyStore.getItem(
KeyStorePrefixes.SyncSecretIntegrationLastRunTimestamp(projectId, environment, secretPath)
);
// check whether the integration should wait or not
if (lastRunSyncIntegrationTimestamp) {
const INTEGRATION_INTERVAL = 2000;
const timeDifferenceWithLastIntegration = getTimeDifferenceInSeconds(
lockAcquiredTime.toISOString(),
lastRunSyncIntegrationTimestamp
);
// give some time for integration to breath
if (timeDifferenceWithLastIntegration < INTEGRATION_INTERVAL)
await new Promise((resolve) => {
setTimeout(resolve, INTEGRATION_INTERVAL);
});
}
const integrations = await integrationDAL.findByProjectIdV2(projectId, environment); // note: returns array of integrations + integration auths in this environment
const toBeSyncedIntegrations = integrations.filter(
// note: sync only the integrations sourced from secretPath
({ secretPath: integrationSecPath, isActive }) => isActive && isSamePath(secretPath, integrationSecPath)
);
if (!integrations.length) return;
logger.info(
`getIntegrationSecrets: secret integration sync started [jobId=${job.id}] [jobId=${job.id}] [projectId=${job.data.projectId}] [environment=${environment}] [secretPath=${job.data.secretPath}] [depth=${depth}]`
);
const secrets = shouldUseSecretV2Bridge
? await getIntegrationSecretsV2({
environment,
@@ -892,7 +898,7 @@ export const secretQueueFactory = ({
await auditLogService.createAuditLog({
projectId,
actor: await generateActor(),
actor: await $generateActor(actorId, isManual),
event: {
type: EventType.INTEGRATION_SYNCED,
metadata: {
@@ -931,7 +937,7 @@ export const secretQueueFactory = ({
await auditLogService.createAuditLog({
projectId,
actor: await generateActor(),
actor: await $generateActor(actorId, isManual),
event: {
type: EventType.INTEGRATION_SYNCED,
metadata: {

View File

@@ -77,5 +77,21 @@ export const smtpServiceFactory = (cfg: TSmtpConfig) => {
}
};
return { sendMail };
const verify = async () => {
const isConnected = smtp
.verify()
.then(async () => {
logger.info("SMTP connected");
return true;
})
.catch((err: Error) => {
logger.error("SMTP error");
logger.error(err);
return false;
});
return isConnected;
};
return { sendMail, verify };
};

View File

@@ -4,6 +4,17 @@ title: "Changelog"
The changelog below reflects new product developments and updates on a monthly basis.
## October 2024
- Significantly improved performance of audit log operations in UI.
- Released [Databricks integration](https://infisical.com/docs/integrations/cloud/databricks).
- Added ability to enforce 2FA organization-wide.
- Added multiple resource to the [Infisical Terraform Provider](https://registry.terraform.io/providers/Infisical/infisical/latest/docs), including AWS and GCP integrations.
- Released [Infisical KMS](https://infisical.com/docs/documentation/platform/kms/overview).
- Added support for [LDAP dynamic secrets](https://infisical.com/docs/documentation/platform/ldap/overview).
- Enabled changing auth methods for machine identities in the UI.
- Launched [Infisical EU Cloud](https://eu.infisical.com).
## September 2024
- Improved paginations for identities and secrets.
- Significant improvements to the [Infisical Terraform Provider](https://registry.terraform.io/providers/Infisical/infisical/latest/docs).

View File

@@ -3,81 +3,209 @@ title: "Permissions"
description: "Infisical's permissions system provides granular access control."
---
## Summary
## Overview
The Infisical permissions system is based on a role-based access control (RBAC) model. The system allows you to define roles and assign them to users and machines. Each role has a set of permissions that define what actions a user can perform.
Permissions are built on a subject-action-object model. The subject is the resource permission is being applied to, the action is what the permission allows.
Permissions are built on a subject-action-object model. The subject is the resource the permission is being applied to, the action is what the permission allows.
An example of a subject/action combination would be `secrets/read`. This permission allows the subject to read secrets.
Currently Infisical supports 4 actions:
1. `read`, allows the subject to read the object.
2. `create`, allows the subject to create the object.
3. `edit`, allows the subject to edit the object.
4. `delete`, allows the subject to delete the object.
Most subjects support all 4 actions, but some subjects only support a subset of actions. Please view the table below for a list of subjects and the actions they support.
Refer to the table below for a list of subjects and the actions they support.
## Subjects and Actions
<Tabs>
<Tab title="Project Permissions">
<Note>
Not all actions are applicable to all subjects. As an example, the `secrets-rollback` subject only supports `read`, and `create` as actions. While `secrets` support `read`, `create`, `edit`, `delete`.
Not all actions are applicable to all subjects. As an example, the
`secrets-rollback` subject only supports `read`, and `create` as actions.
While `secrets` support `read`, `create`, `edit`, `delete`.
</Note>
| Subject | Actions |
|-----------------------------|---------|
| `secrets` | `read`, `create`, `edit`, `delete` |
| `secret-approval` | `read`, `create`, `edit`, `delete` |
| `secret-rotation` | `read`, `create`, `edit`, `delete` |
| `secret-rollback` | `read`, `create` |
| `member` | `read`, `create`, `edit`, `delete` |
| `groups` | `read`, `create`, `edit`, `delete` |
| `role` | `read`, `create`, `edit`, `delete` |
| `integrations` | `read`, `create`, `edit`, `delete` |
| `webhooks` | `read`, `create`, `edit`, `delete` |
| `identity` | `read`, `create`, `edit`, `delete` |
| `service-tokens` | `read`, `create`, `edit`, `delete` |
| `settings` | `read`, `create`, `edit`, `delete` |
| `environments` | `read`, `create`, `edit`, `delete` |
| `tags` | `read`, `create`, `edit`, `delete` |
| `audit-logs` | `read`, `create`, `edit`, `delete` |
| `ip-allowlist` | `read`, `create`, `edit`, `delete` |
| `certificate-authorities` | `read`, `create`, `edit`, `delete` |
| `certificates` | `read`, `create`, `edit`, `delete` |
| `certificate-templates` | `read`, `create`, `edit`, `delete` |
| `pki-alerts` | `read`, `create`, `edit`, `delete` |
| `pki-collections` | `read`, `create`, `edit`, `delete` |
| `workspace` | `edit`, `delete` |
| `kms` | `edit` |
| Subject | Actions |
| ------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `role` | `read`, `create`, `edit`, `delete` |
| `member` | `read`, `create`, `edit`, `delete` |
| `groups` | `read`, `create`, `edit`, `delete` |
| `settings` | `read`, `create`, `edit`, `delete` |
| `integrations` | `read`, `create`, `edit`, `delete` |
| `webhooks` | `read`, `create`, `edit`, `delete` |
| `service-tokens` | `read`, `create`, `edit`, `delete` |
| `environments` | `read`, `create`, `edit`, `delete` |
| `tags` | `read`, `create`, `edit`, `delete` |
| `audit-logs` | `read`, `create`, `edit`, `delete` |
| `ip-allowlist` | `read`, `create`, `edit`, `delete` |
| `workspace` | `edit`, `delete` |
| `secrets` | `read`, `create`, `edit`, `delete` |
| `secret-folders` | `read`, `create`, `edit`, `delete` |
| `secret-imports` | `read`, `create`, `edit`, `delete` |
| `dynamic-secrets` | `read-root-credential`, `create-root-credential`, `edit-root-credential`, `delete-root-credential`, `lease` |
| `secret-rollback` | `read`, `create` |
| `secret-approval` | `read`, `create`, `edit`, `delete` |
| `secret-rotation` | `read`, `create`, `edit`, `delete` |
| `identity` | `read`, `create`, `edit`, `delete` |
| `certificate-authorities` | `read`, `create`, `edit`, `delete` |
| `certificates` | `read`, `create`, `edit`, `delete` |
| `certificate-templates` | `read`, `create`, `edit`, `delete` |
| `pki-alerts` | `read`, `create`, `edit`, `delete` |
| `pki-collections` | `read`, `create`, `edit`, `delete` |
| `kms` | `edit` |
| `cmek` | `read`, `create`, `edit`, `delete`, `encrypt`, `decrypt` |
These details are especially useful if you're using the API to [create new project roles](../api-reference/endpoints/project-roles/create).
The rules outlined on this page, also apply when using our Terraform Provider to manage your Infisical project roles, or any other of our clients that manage project roles.
</Tab>
<Tab title="Organization Permissions">
<Note>
Not all actions are applicable to all subjects. As an example, the `workspace` subject only supports `read`, and `create` as actions. While `member` support `read`, `create`, `edit`, `delete`.
</Note>
<Note>
Not all actions are applicable to all subjects. As an example, the `workspace`
subject only supports `read`, and `create` as actions. While `member` support
`read`, `create`, `edit`, `delete`.
</Note>
| Subject | Actions |
| ------------------ | ---------------------------------- |
| `workspace` | `read`, `create` |
| `role` | `read`, `create`, `edit`, `delete` |
| `member` | `read`, `create`, `edit`, `delete` |
| `secret-scanning` | `read`, `create`, `edit`, `delete` |
| `settings` | `read`, `create`, `edit`, `delete` |
| `incident-account` | `read`, `create`, `edit`, `delete` |
| `sso` | `read`, `create`, `edit`, `delete` |
| `scim` | `read`, `create`, `edit`, `delete` |
| `ldap` | `read`, `create`, `edit`, `delete` |
| `groups` | `read`, `create`, `edit`, `delete` |
| `billing` | `read`, `create`, `edit`, `delete` |
| `identity` | `read`, `create`, `edit`, `delete` |
| `kms` | `read` |
| Subject | Actions |
|-----------------------------|------------------------------------|
| `workspace` | `read`, `create` |
| `role` | `read`, `create`, `edit`, `delete` |
| `member` | `read`, `create`, `edit`, `delete` |
| `secret-scanning` | `read`, `create`, `edit`, `delete` |
| `settings` | `read`, `create`, `edit`, `delete` |
| `incident-account` | `read`, `create`, `edit`, `delete` |
| `sso` | `read`, `create`, `edit`, `delete` |
| `scim` | `read`, `create`, `edit`, `delete` |
| `ldap` | `read`, `create`, `edit`, `delete` |
| `groups` | `read`, `create`, `edit`, `delete` |
| `billing` | `read`, `create`, `edit`, `delete` |
| `identity` | `read`, `create`, `edit`, `delete` |
| `kms` | `read` |
</Tab>
</Tabs>
</Tabs>
## Inversion
Permission inversion allows you to explicitly deny actions instead of allowing them. This is supported for the following subjects:
- secrets
- secret-folders
- secret-imports
- dynamic-secrets
- cmek
When a permission is inverted, it changes from an "allow" rule to a "deny" rule. For example:
```typescript
// Regular permission - allows reading secrets
{
subject: "secrets",
action: ["read"]
}
// Inverted permission - denies reading secrets
{
subject: "secrets",
action: ["read"],
inverted: true
}
```
## Conditions
Conditions allow you to create more granular permissions by specifying criteria that must be met for the permission to apply. This is supported for the following subjects:
- secrets
- secret-folders
- secret-imports
- dynamic-secrets
### Properties
Conditions can be applied to the following properties:
- `environment`: Control access based on environment slugs
- `secretPath`: Control access based on secret paths
- `secretName`: Control access based on secret names
- `secretTags`: Control access based on tags (only supports $in operator)
### Operators
The following operators are available for conditions:
| Operator | Description | Example |
| -------- | ---------------------------------- | ----------------------------------------------------- |
| `$eq` | Equal | `{ environment: { $eq: "production" } }` |
| `$ne` | Not equal | `{ environment: { $ne: "development" } }` |
| `$in` | Matches any value in array | `{ environment: { $in: ["staging", "production"] } }` |
| `$glob` | Pattern matching using glob syntax | `{ secretPath: { $glob: "/app/\*" } }` |
These details are especially useful if you're using the API to [create new project roles](../api-reference/endpoints/project-roles/create).
The rules outlined on this page, also apply when using our Terraform Provider to manage your Infisical project roles, or any other of our clients that manage project roles.
## Migrating from permission V1 to permission V2
When upgrading to V2 permissions (i.e. when moving from using the `permissions` to `permissions_v2` field in your Terraform configurations, or upgrading to the V2 permission API), you'll need to update your permission structure as follows:
Any permissions for `secrets` should be expanded to include equivalent permissions for:
- `secret-imports`
- `secret-folders` (except for read permissions)
- `dynamic-secrets`
For dynamic secrets, the actions need to be mapped differently:
- `read` → `read-root-credential`
- `create` → `create-root-credential`
- `edit` → `edit-root-credential` (also adds `lease` permission)
- `delete` → `delete-root-credential`
Example:
```hcl
# Old V1 configuration
resource "infisical_project_role" "example" {
name = "example"
permissions = [
{
subject = "secrets"
action = "read"
},
{
subject = "secrets"
action = "edit"
}
]
}
# New V2 configuration
resource "infisical_project_role" "example" {
name = "example"
permissions_v2 = [
# Original secrets permission
{
subject = "secrets"
action = ["read", "edit"]
inverted = false
},
# Add equivalent secret-imports permission
{
subject = "secret-imports"
action = ["read", "edit"]
inverted = false
},
# Add secret-folders permission (without read)
{
subject = "secret-folders"
action = ["edit"]
inverted = false
},
# Add dynamic-secrets permission with mapped actions
{
subject = "dynamic-secrets"
action = ["read-root-credential", "edit-root-credential", "lease"]
inverted = false
}
]
}
```
Note: When moving to V2 permissions, make sure to include all the necessary expanded permissions based on your original `secrets` permissions.

View File

@@ -65,7 +65,6 @@
"i18next-browser-languagedetector": "^7.0.1",
"i18next-http-backend": "^2.2.0",
"infisical-node": "^1.0.37",
"ip": "^2.0.1",
"jspdf": "^2.5.2",
"jsrp": "^0.2.4",
"jwt-decode": "^3.1.2",
@@ -15976,11 +15975,6 @@
"loose-envify": "^1.0.0"
}
},
"node_modules/ip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ=="
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",

View File

@@ -78,7 +78,6 @@
"i18next-browser-languagedetector": "^7.0.1",
"i18next-http-backend": "^2.2.0",
"infisical-node": "^1.0.37",
"ip": "^2.0.1",
"jspdf": "^2.5.2",
"jsrp": "^0.2.4",
"jwt-decode": "^3.1.2",

View File

@@ -1,10 +1,12 @@
import { ChangeEventHandler, useState } from "react";
import { DayPicker, DayPickerProps } from "react-day-picker";
import { faCalendar } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { PopoverContentProps, PopoverProps } from "@radix-ui/react-popover";
import { format } from "date-fns";
import { format, setHours, setMinutes } from "date-fns";
import { Button } from "../Button";
import { Input } from "../Input";
import { Popover, PopoverContent, PopoverTrigger } from "../Popoverv2";
export type DatePickerProps = Omit<DayPickerProps, "selected"> & {
@@ -22,15 +24,58 @@ export const DatePicker = ({
popUpContentProps,
...props
}: DatePickerProps) => {
const [timeValue, setTimeValue] = useState<string>(value ? format(value, "HH:mm") : "00:00");
const handleTimeChange: ChangeEventHandler<HTMLInputElement> = (e) => {
const time = e.target.value;
if (time) {
setTimeValue(time);
if (value) {
const [hours, minutes] = time.split(":").map((str) => parseInt(str, 10));
const newSelectedDate = setHours(setMinutes(value, minutes), hours);
onChange(newSelectedDate);
}
}
};
const handleDaySelect = (date: Date | undefined) => {
if (!timeValue || !date) {
onChange(date);
return;
}
const [hours, minutes] = timeValue.split(":").map((str) => parseInt(str, 10));
const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), hours, minutes);
onChange(newDate);
};
return (
<Popover {...popUpProps}>
<PopoverTrigger asChild>
<Button variant="outline_bg" leftIcon={<FontAwesomeIcon icon={faCalendar} />}>
{value ? format(value, "PPP") : "Pick a date"}
{value ? format(value, "PPP") : "Pick a date and time"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-fit p-2" {...popUpContentProps}>
<DayPicker {...props} mode="single" selected={value} onSelect={onChange} />
<div className="mx-4 my-4">
<Input
type="time"
value={timeValue}
onChange={handleTimeChange}
className="bg-mineshaft-700 text-white [color-scheme:dark]"
/>
</div>
<DayPicker
{...props}
mode="single"
selected={value}
onSelect={handleDaySelect}
modifiersStyles={{
selected: {
background: "#cad62d"
}
}}
/>
</PopoverContent>
</Popover>
);

View File

@@ -12,7 +12,7 @@ export const AuditLogsPage = withPermission(
<p className="text-3xl font-semibold text-gray-200">Audit Logs</p>
<div />
</div>
<LogsSection filterClassName="static p-2" showFilters isOrgAuditLogs />
<LogsSection filterClassName="static py-2" showFilters isOrgAuditLogs showActorColumn />
</div>
</div>
);

View File

@@ -1,11 +1,7 @@
/* eslint-disable no-nested-ternary */
import { useEffect, useState } from "react";
import { Control, Controller, UseFormReset, UseFormSetValue, UseFormWatch } from "react-hook-form";
import {
faCheckCircle,
faChevronDown,
faFilterCircleXmark
} from "@fortawesome/free-solid-svg-icons";
import { faCaretDown, faCheckCircle, faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
@@ -20,7 +16,7 @@ import {
Select,
SelectItem
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { useOrganization, useWorkspace } from "@app/context";
import { useGetAuditLogActorFilterOpts } from "@app/hooks/api";
import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants";
import { ActorType, EventType } from "@app/hooks/api/auditLogs/enums";
@@ -60,11 +56,15 @@ export const LogsFilter = ({
const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
const { currentWorkspace, workspaces } = useWorkspace();
const { currentOrg } = useOrganization();
const workspacesInOrg = workspaces.filter((ws) => ws.orgId === currentOrg?.id);
const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?.id ?? "");
useEffect(() => {
if (workspaces.length) {
setValue("projectId", workspaces[0].id);
if (workspacesInOrg.length) {
setValue("projectId", workspacesInOrg[0].id);
}
}, [workspaces]);
@@ -130,7 +130,7 @@ export const LogsFilter = ({
: selectedEventTypes?.length === 0
? "All events"
: `${selectedEventTypes?.length} events selected`}
<FontAwesomeIcon icon={faChevronDown} className="ml-2 text-xs" />
<FontAwesomeIcon icon={faCaretDown} className="ml-2 text-xs" />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="z-[100] max-h-80 overflow-hidden">
@@ -221,10 +221,7 @@ export const LogsFilter = ({
if (e === "all") onChange(undefined);
else onChange(e);
}}
className={twMerge(
"w-full border border-mineshaft-500 bg-mineshaft-700 text-mineshaft-100",
value === undefined && "text-mineshaft-400"
)}
className={twMerge("w-full border border-mineshaft-500 bg-mineshaft-700")}
>
<SelectItem value="all" key="all">
All sources
@@ -239,7 +236,7 @@ export const LogsFilter = ({
)}
/>
{isOrgAuditLogs && workspaces.length > 0 && (
{isOrgAuditLogs && workspacesInOrg.length > 0 && (
<Controller
control={control}
name="projectId"
@@ -255,11 +252,11 @@ export const LogsFilter = ({
{...field}
onValueChange={(e) => onChange(e)}
className={twMerge(
"w-full border border-mineshaft-500 bg-mineshaft-700 text-mineshaft-100",
"w-full border border-mineshaft-500 bg-mineshaft-700 ",
value === undefined && "text-mineshaft-400"
)}
>
{workspaces.map((project) => (
{workspacesInOrg.map((project) => (
<SelectItem value={String(project.id || "")} key={project.id}>
{project.name}
</SelectItem>
@@ -277,10 +274,7 @@ export const LogsFilter = ({
<FormControl label="Start date" errorText={error?.message} isError={Boolean(error)}>
<DatePicker
value={field.value || undefined}
onChange={(date) => {
onChange(date);
setIsStartDatePickerOpen(false);
}}
onChange={onChange}
popUpProps={{
open: isStartDatePickerOpen,
onOpenChange: setIsStartDatePickerOpen
@@ -299,11 +293,7 @@ export const LogsFilter = ({
<FormControl label="End date" errorText={error?.message} isError={Boolean(error)}>
<DatePicker
value={field.value || undefined}
onChange={(pickedDate) => {
pickedDate?.setHours(23, 59, 59, 999); // we choose the end of today not the start of it (going off of aws cloud watch)
onChange(pickedDate);
setIsEndDatePickerOpen(false);
}}
onChange={onChange}
popUpProps={{
open: isEndDatePickerOpen,
onOpenChange: setIsEndDatePickerOpen

View File

@@ -88,7 +88,7 @@ export const LogsSection = ({
refetchInterval={refetchInterval}
remappedHeaders={remappedHeaders}
isOrgAuditLogs={isOrgAuditLogs}
showActorColumn={!!showActorColumn && !isOrgAuditLogs}
showActorColumn={!!showActorColumn}
filter={{
eventMetadata: presets?.eventMetadata,
projectId,

View File

@@ -1,7 +1,7 @@
import { Badge, Td, Tooltip, Tr } from "@app/components/v2";
import { Td, Tr } from "@app/components/v2";
import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants";
import { ActorType, EventType } from "@app/hooks/api/auditLogs/enums";
import { Actor, AuditLog, Event } from "@app/hooks/api/auditLogs/types";
import { Actor, AuditLog } from "@app/hooks/api/auditLogs/types";
type Props = {
auditLog: AuditLog;
@@ -11,6 +11,10 @@ type Props = {
export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Props) => {
const renderActor = (actor: Actor) => {
if (!actor) {
return <Td />;
}
switch (actor.type) {
case ActorType.USER:
return (
@@ -38,491 +42,6 @@ export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Prop
}
};
const renderMetadata = (event: Event) => {
const metadataKeys = Object.keys(event.metadata);
switch (event.type) {
case EventType.GET_SECRETS:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Path: ${event.metadata.secretPath}`}</p>
<p>{`# Secrets: ${event.metadata.numberOfSecrets}`}</p>
</Td>
);
case EventType.GET_SECRET:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Path: ${event.metadata.secretPath}`}</p>
<p>{`Secret: ${event.metadata.secretKey}`}</p>
</Td>
);
case EventType.CREATE_SECRET:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Path: ${event.metadata.secretPath}`}</p>
<p>{`Secret: ${event.metadata.secretKey}`}</p>
</Td>
);
case EventType.UPDATE_SECRET:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Path: ${event.metadata.secretPath}`}</p>
<p>{`Secret: ${event.metadata.secretKey}`}</p>
</Td>
);
case EventType.DELETE_SECRET:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Path: ${event.metadata.secretPath}`}</p>
<p>{`Secret: ${event.metadata.secretKey}`}</p>
</Td>
);
case EventType.AUTHORIZE_INTEGRATION:
return (
<Td>
<p>{`Integration: ${event.metadata.integration}`}</p>
</Td>
);
case EventType.UNAUTHORIZE_INTEGRATION:
return (
<Td>
<p>{`Integration: ${event.metadata.integration}`}</p>
</Td>
);
case EventType.CREATE_INTEGRATION:
return (
<Td>
<p>{`Integration: ${event.metadata.integration}`}</p>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Path: ${event.metadata.secretPath}`}</p>
{event.metadata.app && <p>{`Target app: ${event.metadata.app}`}</p>}
{event.metadata.appId && <p>{`Target app: ${event.metadata.appId}`}</p>}
{event.metadata.targetEnvironment && (
<p>{`Target environment: ${event.metadata.targetEnvironment}`}</p>
)}
{event.metadata.targetEnvironmentId && (
<p>{`Target environment ID: ${event.metadata.targetEnvironmentId}`}</p>
)}
</Td>
);
case EventType.DELETE_INTEGRATION:
return (
<Td>
<p>{`Integration: ${event.metadata.integration}`}</p>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Path: ${event.metadata.secretPath}`}</p>
{event.metadata.app && <p>{`Target App: ${event.metadata.app}`}</p>}
{event.metadata.appId && <p>{`Target app: ${event.metadata.appId}`}</p>}
{event.metadata.targetEnvironment && (
<p>{`Target environment: ${event.metadata.targetEnvironment}`}</p>
)}
{event.metadata.targetEnvironmentId && (
<p>{`Target environment ID: ${event.metadata.targetEnvironmentId}`}</p>
)}
</Td>
);
case EventType.ADD_TRUSTED_IP:
return (
<Td>
<p>{`IP: ${event.metadata.ipAddress}${
event.metadata.prefix !== undefined ? `/${event.metadata.prefix}` : ""
}`}</p>
</Td>
);
case EventType.UPDATE_TRUSTED_IP:
return (
<Td>
<p>{`IP: ${event.metadata.ipAddress}${
event.metadata.prefix !== undefined ? `/${event.metadata.prefix}` : ""
}`}</p>
</Td>
);
case EventType.DELETE_TRUSTED_IP:
return (
<Td>
<p>{`IP: ${event.metadata.ipAddress}${
event.metadata.prefix !== undefined ? `/${event.metadata.prefix}` : ""
}`}</p>
</Td>
);
case EventType.CREATE_SERVICE_TOKEN:
return (
<Td>
<p>{`Name: ${event.metadata.name}`}</p>
</Td>
);
case EventType.DELETE_SERVICE_TOKEN:
return (
<Td>
<p>{`Name: ${event.metadata.name}`}</p>
</Td>
);
case EventType.CREATE_IDENTITY:
return (
<Td>
<p>{`ID: ${event.metadata.identityId}`}</p>
<p>{`Name: ${event.metadata.name}`}</p>
</Td>
);
case EventType.UPDATE_IDENTITY:
return (
<Td>
<p>{`ID: ${event.metadata.identityId}`}</p>
<p>{`Name: ${event.metadata.name}`}</p>
</Td>
);
case EventType.DELETE_IDENTITY:
return (
<Td>
<p>{`ID: ${event.metadata.identityId}`}</p>
</Td>
);
case EventType.CREATE_ENVIRONMENT:
return (
<Td>
<p>{`Name: ${event.metadata.name}`}</p>
<p>{`Slug: ${event.metadata.slug}`}</p>
</Td>
);
case EventType.UPDATE_ENVIRONMENT:
return (
<Td>
<p>{`Old name: ${event.metadata.oldName}`}</p>
<p>{`New name: ${event.metadata.newName}`}</p>
<p>{`Old slug: ${event.metadata.oldSlug}`}</p>
<p>{`New slug: ${event.metadata.newSlug}`}</p>
</Td>
);
case EventType.DELETE_ENVIRONMENT:
return (
<Td>
<p>{`Name: ${event.metadata.name}`}</p>
<p>{`Slug: ${event.metadata.slug}`}</p>
</Td>
);
case EventType.ADD_WORKSPACE_MEMBER:
return (
<Td>
<p>{`Email: ${event.metadata.email}`}</p>
</Td>
);
case EventType.REMOVE_WORKSPACE_MEMBER:
return (
<Td>
<p>{`Email: ${event.metadata.email}`}</p>
</Td>
);
case EventType.CREATE_FOLDER:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Path: ${event.metadata.folderPath}`}</p>
<p>{`Folder: ${event.metadata.folderName}`}</p>
</Td>
);
case EventType.UPDATE_FOLDER:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Path: ${event.metadata.folderPath}`}</p>
<p>{`Old folder: ${event.metadata.oldFolderName}`}</p>
<p>{`New folder: ${event.metadata.newFolderName}`}</p>
</Td>
);
case EventType.DELETE_FOLDER:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Path: ${event.metadata.folderPath}`}</p>
<p>{`Folder: ${event.metadata.folderName}`}</p>
</Td>
);
case EventType.CREATE_WEBHOOK:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Secret path: ${event.metadata.secretPath}`}</p>
<p>{`Disabled: ${event.metadata.isDisabled}`}</p>
</Td>
);
case EventType.UPDATE_WEBHOOK_STATUS:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Secret path: ${event.metadata.secretPath}`}</p>
<p>{`Disabled: ${event.metadata.isDisabled}`}</p>
</Td>
);
case EventType.DELETE_WEBHOOK:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Secret path: ${event.metadata.secretPath}`}</p>
<p>{`Disabled: ${event.metadata.isDisabled}`}</p>
</Td>
);
case EventType.GET_SECRET_IMPORTS:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`# Imported paths: ${event.metadata.numberOfImports}`}</p>
</Td>
);
case EventType.CREATE_SECRET_IMPORT:
return (
<Td>
<p>{`Import from env: ${event.metadata.importFromEnvironment}`}</p>
<p>{`Import from path: ${event.metadata.importFromSecretPath}`}</p>
<p>{`Import to env: ${event.metadata.importToEnvironment}`}</p>
<p>{`Import to path: ${event.metadata.importToSecretPath}`}</p>
</Td>
);
case EventType.UPDATE_SECRET_IMPORT:
return (
<Td>
<p>{`Import to env: ${event.metadata.importToEnvironment}`}</p>
<p>{`Import to path: ${event.metadata.importToSecretPath}`}</p>
</Td>
);
case EventType.DELETE_SECRET_IMPORT:
return (
<Td>
<p>{`Import from env: ${event.metadata.importFromEnvironment}`}</p>
<p>{`Import from path: ${event.metadata.importFromSecretPath}`}</p>
<p>{`Import to env: ${event.metadata.importToEnvironment}`}</p>
<p>{`Import to path: ${event.metadata.importToSecretPath}`}</p>
</Td>
);
case EventType.UPDATE_USER_WORKSPACE_ROLE:
return (
<Td>
<p>{`Email: ${event.metadata.email}`}</p>
<p>{`Old role: ${event.metadata.oldRole}`}</p>
<p>{`New role: ${event.metadata.newRole}`}</p>
</Td>
);
case EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS:
return (
<Td>
<p>{`Email: ${event.metadata.email}`}</p>
{event.metadata.deniedPermissions.map((permission) => {
return (
<p
key={`audit-log-denied-permission-${event.metadata.userId}-${permission.environmentSlug}-${permission.ability}`}
>
{`Denied env-ability: ${permission.environmentSlug}-${permission.ability}`}
</p>
);
})}
</Td>
);
case EventType.ORG_ADMIN_ACCESS_PROJECT:
return (
<Td>
<p>{`Email: ${event.metadata.email}`}</p>
</Td>
);
case EventType.CREATE_PKI_ALERT:
case EventType.UPDATE_PKI_ALERT:
return (
<Td>
<p>{`Alert ID: ${event.metadata.pkiAlertId}`}</p>
<p>{`Name: ${event.metadata.name}`}</p>
<p>{`Alert Before Days: ${event.metadata.alertBeforeDays}`}</p>
</Td>
);
case EventType.GET_PKI_ALERT:
case EventType.DELETE_PKI_ALERT:
return (
<Td>
<p>{`Alert ID: ${event.metadata.pkiAlertId}`}</p>
</Td>
);
case EventType.CREATE_PKI_COLLECTION:
return (
<Td>
<p>{`Collection ID: ${event.metadata.pkiCollectionId}`}</p>
<p>{`Name: ${event.metadata.name}`}</p>
</Td>
);
case EventType.UPDATE_PKI_COLLECTION:
return (
<Td>
<p>{`Collection ID: ${event.metadata.pkiCollectionId}`}</p>
<p>{`Name: ${event.metadata.name}`}</p>
</Td>
);
case EventType.GET_PKI_COLLECTION:
case EventType.DELETE_PKI_COLLECTION:
return (
<Td>
<p>{`Collection ID: ${event.metadata.pkiCollectionId}`}</p>
</Td>
);
case EventType.GET_PKI_COLLECTION_ITEMS:
return (
<Td>
<p>{`Collection ID: ${event.metadata.pkiCollectionId}`}</p>
</Td>
);
case EventType.ADD_PKI_COLLECTION_ITEM:
return (
<Td>
<p>{`Collection ID: ${event.metadata.pkiCollectionId}`}</p>
<p>{`Collection Item ID: ${event.metadata.pkiCollectionItemId}`}</p>
<p>{`Type: ${event.metadata.type}`}</p>
<p>{`Item ID: ${event.metadata.itemId}`}</p>
</Td>
);
case EventType.DELETE_PKI_COLLECTION_ITEM:
return (
<Td>
<p>{`Collection ID: ${event.metadata.pkiCollectionId}`}</p>
<p>{`Collection Item ID: ${event.metadata.pkiCollectionItemId}`}</p>
</Td>
);
case EventType.CREATE_CA:
case EventType.GET_CA:
case EventType.UPDATE_CA:
case EventType.DELETE_CA:
case EventType.GET_CA_CSR:
case EventType.GET_CA_CERT:
case EventType.IMPORT_CA_CERT:
case EventType.GET_CA_CRL:
case EventType.SIGN_INTERMEDIATE:
case EventType.ISSUE_CERT:
return (
<Td>
<p>{`CA DN: ${event.metadata.dn}`}</p>
</Td>
);
case EventType.GET_CERT:
case EventType.DELETE_CERT:
case EventType.REVOKE_CERT:
case EventType.GET_CERT_BODY:
return (
<Td>
<p>{`Cert CN: ${event.metadata.cn}`}</p>
</Td>
);
case EventType.CREATE_CERTIFICATE_TEMPLATE:
case EventType.UPDATE_CERTIFICATE_TEMPLATE:
return (
<Td>
<p>{`Certificate Template ID: ${event.metadata.certificateTemplateId}`}</p>
<p>{`Certificate Authority ID: ${event.metadata.caId}`}</p>
<p>{`Name: ${event.metadata.name}`}</p>
<p>{`Common Name: ${event.metadata.commonName}`}</p>
<p>{`Subject Alternative Name: ${event.metadata.subjectAlternativeName}`}</p>
<p>{`TTL: ${event.metadata.ttl}`}</p>
{event.metadata.pkiCollectionId && (
<p>{`Collection ID: ${event.metadata.pkiCollectionId}`}</p>
)}
</Td>
);
case EventType.GET_CERTIFICATE_TEMPLATE:
case EventType.DELETE_CERTIFICATE_TEMPLATE:
return (
<Td>
<p>{`Certificate Template ID: ${event.metadata.certificateTemplateId}`}</p>
</Td>
);
case EventType.CREATE_CERTIFICATE_TEMPLATE_EST_CONFIG:
case EventType.UPDATE_CERTIFICATE_TEMPLATE_EST_CONFIG:
return (
<Td>
<p>{`Certificate Template ID: ${event.metadata.certificateTemplateId}`}</p>
<p>{`Enabled: ${event.metadata.isEnabled}`}</p>
</Td>
);
case EventType.GET_CERTIFICATE_TEMPLATE_EST_CONFIG:
return (
<Td>
<p>{`Certificate Template ID: ${event.metadata.certificateTemplateId}`}</p>
</Td>
);
case EventType.GET_PROJECT_SLACK_CONFIG:
return (
<Td>
<p>{`Project Slack Config ID: ${event.metadata.id}`}</p>
</Td>
);
case EventType.UPDATE_PROJECT_SLACK_CONFIG:
return (
<Td>
<p>{`Project Slack Config ID: ${event.metadata.id}`}</p>
<p>{`Slack integration ID: ${event.metadata.slackIntegrationId}`}</p>
<p>{`Access Request Notification Status: ${event.metadata.isAccessRequestNotificationEnabled}`}</p>
<p>{`Access Request Channels: ${event.metadata.accessRequestChannels}`}</p>
<p>{`Secret Approval Request Notification Status: ${event.metadata.isSecretRequestNotificationEnabled}`}</p>
<p>{`Secret Request Channels: ${event.metadata.secretRequestChannels}`}</p>
</Td>
);
case EventType.INTEGRATION_SYNCED:
return (
<Td>
<Tooltip
className="max-w-xs whitespace-normal break-words"
content={event.metadata.syncMessage!}
isDisabled={!event.metadata.syncMessage}
>
<Badge variant={event.metadata.isSynced ? "success" : "danger"}>
<p className="text-center">{event.metadata.isSynced ? "Successful" : "Failed"}</p>
</Badge>
</Tooltip>
</Td>
);
case EventType.GET_WORKSPACE_KEY:
return (
<Td>
<p>{`Key ID: ${event.metadata.keyId}`}</p>
</Td>
);
case EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH:
case EventType.ADD_IDENTITY_UNIVERSAL_AUTH:
case EventType.UPDATE_IDENTITY_UNIVERSAL_AUTH:
case EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS:
return (
<Td>
<p>{`Identity ID: ${event.metadata.identityId}`}</p>
</Td>
);
case EventType.CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET:
case EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET:
return (
<Td>
<p>{`Identity ID: ${event.metadata.identityId}`}</p>
<p>{`Client Secret ID: ${event.metadata.clientSecretId}`}</p>
</Td>
);
// ? If for some reason non the above events are matched, we will display the first 3 metadata items in the metadata object.
default:
if (metadataKeys.length) {
const maxMetadataLength = metadataKeys.length > 3 ? 3 : metadataKeys.length;
return (
<Td>
{Object.entries(event.metadata)
.slice(0, maxMetadataLength)
.map(([key, value]) => {
return <p key={`audit-log-metadata-${key}`}>{`${key}: ${value}`}</p>;
})}
</Td>
);
}
return <Td />;
}
};
const formatDate = (dateToFormat: string) => {
const date = new Date(dateToFormat);
const year = date.getFullYear();
@@ -576,7 +95,7 @@ export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Prop
{isOrgAuditLogs && <Td>{auditLog?.projectName ?? auditLog?.projectId ?? "N/A"}</Td>}
{showActorColumn && renderActor(auditLog.actor)}
{renderSource()}
{renderMetadata(auditLog.event)}
<Td className="max-w-xs break-all">{JSON.stringify(auditLog.event.metadata || {})}</Td>
</Tr>
);
};

View File

@@ -96,7 +96,7 @@ export const SecretApprovalRequestChangeItem = ({
<SecretInput isReadOnly value={secretVersion?.secretValue} />
</Td>
<Td>{secretVersion?.secretComment}</Td>
<Td>
<Td className="flex flex-wrap gap-2">
{secretVersion?.tags?.map(({ slug, id: tagId, color }) => (
<Tag
className="flex w-min items-center space-x-2"
@@ -118,7 +118,7 @@ export const SecretApprovalRequestChangeItem = ({
<SecretInput isReadOnly value={newVersion?.secretValue} />
</Td>
<Td>{newVersion?.secretComment}</Td>
<Td>
<Td className="flex flex-wrap gap-2">
{newVersion?.tags?.map(({ slug, id: tagId, color }) => (
<Tag
className="flex w-min items-center space-x-2"

View File

@@ -1,3 +1,13 @@
## 1.4.0 (November 06, 2024)
Changes:
* Chart is now fully documented
* New fields introduced: `infisical.databaseSchemaMigrationJob.image` and `infisical.serviceAccount`
Features:
* Added support for auto creating service account with required permissions via `infisical.serviceAccount.create`
## 1.3.0 (October 28, 2024)
Changes:

View File

@@ -1,13 +1,13 @@
apiVersion: v2
name: infisical-standalone
description: A helm chart for a full Infisical application
description: A helm chart to deploy Infisical
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 1.3.0
version: 1.4.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to

View File

@@ -0,0 +1,66 @@
# infisical-standalone
![Version: 1.4.0](https://img.shields.io/badge/Version-1.4.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.1](https://img.shields.io/badge/AppVersion-1.0.1-informational?style=flat-square)
A helm chart to deploy Infisical
## Requirements
| Repository | Name | Version |
|------------|------|---------|
| https://charts.bitnami.com/bitnami | postgresql | 14.1.3 |
| https://charts.bitnami.com/bitnami | redis | 18.14.0 |
| https://kubernetes.github.io/ingress-nginx | ingress-nginx | 4.0.13 |
## Values
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| fullnameOverride | string | `""` | Overrides the full name of the release, affecting resource names |
| infisical.affinity | object | `{}` | Node affinity settings for pod placement |
| infisical.autoDatabaseSchemaMigration | bool | `true` | Automatically migrates new database schema when deploying |
| infisical.databaseSchemaMigrationJob.image.pullPolicy | string | `"IfNotPresent"` | Pulls image only if not present on the node |
| infisical.databaseSchemaMigrationJob.image.repository | string | `"ghcr.io/groundnuty/k8s-wait-for"` | Image repository for migration wait job |
| infisical.databaseSchemaMigrationJob.image.tag | string | `"no-root-v2.0"` | Image tag version |
| infisical.deploymentAnnotations | object | `{}` | Custom annotations for Infisical deployment |
| infisical.enabled | bool | `true` | |
| infisical.fullnameOverride | string | `""` | Override for the full name of Infisical resources in this deployment |
| infisical.image.imagePullSecrets | list | `[]` | Secret references for pulling the image, if needed |
| infisical.image.pullPolicy | string | `"IfNotPresent"` | Pulls image only if not already present on the node |
| infisical.image.repository | string | `"infisical/infisical"` | Image repository for the Infisical service |
| infisical.image.tag | string | `"v0.93.1-postgres"` | Specific version tag of the Infisical image. View the latest version here https://hub.docker.com/r/infisical/infisical |
| infisical.kubeSecretRef | string | `"infisical-secrets"` | Kubernetes Secret reference containing Infisical root credentials |
| infisical.name | string | `"infisical"` | |
| infisical.podAnnotations | object | `{}` | Custom annotations for Infisical pods |
| infisical.replicaCount | int | `2` | Number of pod replicas for high availability |
| infisical.resources.limits.memory | string | `"600Mi"` | Memory limit for Infisical container |
| infisical.resources.requests.cpu | string | `"350m"` | CPU request for Infisical container |
| infisical.service.annotations | object | `{}` | Custom annotations for Infisical service |
| infisical.service.nodePort | string | `""` | Optional node port for service when using NodePort type |
| infisical.service.type | string | `"ClusterIP"` | Service type, can be changed based on exposure needs (e.g., LoadBalancer) |
| infisical.serviceAccount.annotations | object | `{}` | Custom annotations for the auto-created service account |
| infisical.serviceAccount.create | bool | `true` | Creates a new service account if true, with necessary permissions for this chart. If false and `serviceAccount.name` is not defined, the chart will attempt to use the Default service account |
| infisical.serviceAccount.name | string | `nil` | Optional custom service account name, if existing service account is used |
| ingress.annotations | object | `{}` | Custom annotations for ingress resource |
| ingress.enabled | bool | `true` | Enable or disable ingress configuration |
| ingress.hostName | string | `""` | Hostname for ingress access, e.g., app.example.com |
| ingress.ingressClassName | string | `"nginx"` | Specifies the ingress class, useful for multi-ingress setups |
| ingress.nginx.enabled | bool | `true` | Enable NGINX-specific settings, if using NGINX ingress controller |
| ingress.tls | list | `[]` | TLS settings for HTTPS access |
| nameOverride | string | `""` | Overrides the default release name |
| postgresql.auth.database | string | `"infisicalDB"` | Database name for Infisical |
| postgresql.auth.password | string | `"root"` | Password for PostgreSQL database access |
| postgresql.auth.username | string | `"infisical"` | Database username for PostgreSQL |
| postgresql.enabled | bool | `true` | Enables an in-cluster PostgreSQL deployment. To achieve HA for Postgres, we recommend deploying https://github.com/zalando/postgres-operator instead. |
| postgresql.fullnameOverride | string | `"postgresql"` | Full name override for PostgreSQL resources |
| postgresql.name | string | `"postgresql"` | PostgreSQL resource name |
| postgresql.useExistingPostgresSecret.enabled | bool | `false` | Set to true if using an existing Kubernetes secret that contains PostgreSQL connection string |
| postgresql.useExistingPostgresSecret.existingConnectionStringSecret.key | string | `""` | Key name in the Kubernetes secret that holds the connection string |
| postgresql.useExistingPostgresSecret.existingConnectionStringSecret.name | string | `""` | Kubernetes secret name containing the PostgreSQL connection string |
| redis.architecture | string | `"standalone"` | Redis deployment type (e.g., standalone or cluster) |
| redis.auth.password | string | `"mysecretpassword"` | Redis password |
| redis.cluster.enabled | bool | `false` | Clustered Redis deployment |
| redis.enabled | bool | `true` | Enables an in-cluster Redis deployment |
| redis.fullnameOverride | string | `"redis"` | Full name override for Redis resources |
| redis.name | string | `"redis"` | Redis resource name |
| redis.usePassword | bool | `true` | Requires a password for Redis authentication |

View File

@@ -40,6 +40,23 @@ component: {{ .Values.infisical.name | quote }}
{{ include "infisical.common.matchLabels" . }}
{{- end -}}
{{- define "infisical.roleName" -}}
{{- printf "%s-infisical" .Release.Name -}}
{{- end -}}
{{- define "infisical.roleBindingName" -}}
{{- printf "%s-infisical" .Release.Name -}}
{{- end -}}
{{- define "infisical.serviceAccountName" -}}
{{- if .Values.infisical.serviceAccount.create -}}
{{- printf "%s-infisical" .Release.Name -}}
{{- else -}}
{{- .Values.infisical.serviceAccount.name | default "default" -}}
{{- end -}}
{{- end -}}
{{/*
Create a fully qualified backend name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).

View File

@@ -34,10 +34,11 @@ spec:
{{- toYaml $infisicalValues.image.imagePullSecrets | nindent 6 }}
{{- end }}
{{- if $infisicalValues.autoDatabaseSchemaMigration }}
serviceAccountName: {{ include "infisical.serviceAccountName" . }}
initContainers:
- name: "migration-init"
image: {{ $infisicalValues.databaseSchemaMigrationInitContainer.image }}
imagePullPolicy: {{ $infisicalValues.databaseSchemaMigrationInitContainer.imagePullPolicy }}
image: "{{ $infisicalValues.databaseSchemaMigrationJob.image.repository }}:{{ $infisicalValues.databaseSchemaMigrationJob.image.tag }}"
imagePullPolicy: {{ $infisicalValues.databaseSchemaMigrationJob.image.pullPolicy }}
args:
- "job"
- "{{ .Release.Name }}-schema-migration-{{ .Release.Revision }}"

View File

@@ -1,8 +1,25 @@
---
{{- if .Values.infisical.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ printf "%s-infisical" .Release.Name }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "infisical.labels" . | nindent 4 }}
{{- with .Values.infisical.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: k8s-wait-for-infisical-schema-migration
name: {{ include "infisical.roleName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "infisical.labels" . | nindent 4 }}
rules:
- apiGroups: ["batch"]
resources: ["jobs"]
@@ -11,13 +28,15 @@ rules:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: infisical-database-schema-migration
name: {{ include "infisical.roleBindingName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "infisical.labels" . | nindent 4 }}
subjects:
- kind: ServiceAccount
name: {{ .Values.infisical.databaseSchemaMigrationJob.serviceAccountName | default "default" }}
namespace: {{ .Release.Namespace }}
- kind: ServiceAccount
name: {{ include "infisical.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
roleRef:
kind: Role
name: k8s-wait-for-infisical-schema-migration
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "infisical.roleName" . }}

View File

@@ -16,7 +16,7 @@ spec:
app.kubernetes.io/instance: {{ .Release.Name | quote }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
serviceAccountName: {{ .Values.infisical.databaseSchemaMigrationJob.serviceAccountName | default "default" }}
serviceAccountName: {{ include "infisical.serviceAccountName" . }}
{{- if $infisicalValues.image.imagePullSecrets }}
imagePullSecrets:
{{- toYaml $infisicalValues.image.imagePullSecrets | nindent 6 }}

View File

@@ -1,81 +1,139 @@
# -- Overrides the default release name
nameOverride: ""
# -- Overrides the full name of the release, affecting resource names
fullnameOverride: ""
infisical:
enabled: true
name: infisical
enabled: true # -- Enable Infisical chart deployment
name: infisical # -- Sets the name of the deployment within this chart
# -- Automatically migrates new database schema when deploying
autoDatabaseSchemaMigration: true
databaseSchemaMigrationInitContainer:
image: "ghcr.io/groundnuty/k8s-wait-for:no-root-v2.0"
imagePullPolicy: IfNotPresent
databaseSchemaMigrationJob:
serviceAccountName: default
image:
# -- Image repository for migration wait job
repository: ghcr.io/groundnuty/k8s-wait-for
# -- Image tag version
tag: no-root-v2.0
# -- Pulls image only if not present on the node
pullPolicy: IfNotPresent
serviceAccount:
# -- Creates a new service account if true, with necessary permissions for this chart. If false and `serviceAccount.name` is not defined, the chart will attempt to use the Default service account
create: true
# -- Custom annotations for the auto-created service account
annotations: {}
# -- Optional custom service account name, if existing service account is used
name: null
# -- Override for the full name of Infisical resources in this deployment
fullnameOverride: ""
# -- Custom annotations for Infisical pods
podAnnotations: {}
# -- Custom annotations for Infisical deployment
deploymentAnnotations: {}
# -- Number of pod replicas for high availability
replicaCount: 2
image:
# -- Image repository for the Infisical service
repository: infisical/infisical
tag: "v0.46.3-postgres"
# -- Specific version tag of the Infisical image. View the latest version here https://hub.docker.com/r/infisical/infisical
tag: "v0.93.1-postgres"
# -- Pulls image only if not already present on the node
pullPolicy: IfNotPresent
# -- Secret references for pulling the image, if needed
imagePullSecrets: []
# -- Node affinity settings for pod placement
affinity: {}
# -- Kubernetes Secret reference containing Infisical root credentials
kubeSecretRef: "infisical-secrets"
service:
# -- Custom annotations for Infisical service
annotations: {}
# -- Service type, can be changed based on exposure needs (e.g., LoadBalancer)
type: ClusterIP
# -- Optional node port for service when using NodePort type
nodePort: ""
resources:
limits:
# -- Memory limit for Infisical container
memory: 600Mi
requests:
# -- CPU request for Infisical container
cpu: 350m
ingress:
# -- Enable or disable ingress configuration
enabled: true
# -- Hostname for ingress access, e.g., app.example.com
hostName: ""
# -- Specifies the ingress class, useful for multi-ingress setups
ingressClassName: nginx
nginx:
# -- Enable NGINX-specific settings, if using NGINX ingress controller
enabled: true
# -- Custom annotations for ingress resource
annotations: {}
# -- TLS settings for HTTPS access
tls:
[]
# -- TLS secret name for HTTPS
# - secretName: letsencrypt-prod
# -- Domain name to associate with the TLS certificate
# hosts:
# - some.domain.com
postgresql:
# -- When enabled, this will start up a in cluster Postgres
# -- Enables an in-cluster PostgreSQL deployment. To achieve HA for Postgres, we recommend deploying https://github.com/zalando/postgres-operator instead.
enabled: true
# -- PostgreSQL resource name
name: "postgresql"
# -- Full name override for PostgreSQL resources
fullnameOverride: "postgresql"
auth:
# -- Database username for PostgreSQL
username: infisical
# -- Password for PostgreSQL database access
password: root
# -- Database name for Infisical
database: infisicalDB
useExistingPostgresSecret:
# -- When this is enabled, postgresql.enabled needs to be false
# -- Set to true if using an existing Kubernetes secret that contains PostgreSQL connection string
enabled: false
# -- The name from where to get the existing postgresql connection string
existingConnectionStringSecret:
# -- The name of the secret that contains the postgres connection string
# -- Kubernetes secret name containing the PostgreSQL connection string
name: ""
# -- Secret key name that contains the postgres connection string
# -- Key name in the Kubernetes secret that holds the connection string
key: ""
redis:
# -- Enables an in-cluster Redis deployment
enabled: true
# -- Redis resource name
name: "redis"
# -- Full name override for Redis resources
fullnameOverride: "redis"
cluster:
# -- Clustered Redis deployment
enabled: false
# -- Requires a password for Redis authentication
usePassword: true
auth:
# -- Redis password
password: "mysecretpassword"
# -- Redis deployment type (e.g., standalone or cluster)
architecture: standalone