Compare commits
54 Commits
k8s-auth
...
fix/addres
Author | SHA1 | Date | |
---|---|---|---|
c9b234dbea | |||
0bc778b9bf | |||
b0bc41da14 | |||
a234b686c2 | |||
6230167794 | |||
68d1849ba0 | |||
5c10427eaf | |||
290d99e02c | |||
b75d601754 | |||
de2a5b4255 | |||
663f8abc51 | |||
941a71efaf | |||
19bbc2ab26 | |||
f4de52e714 | |||
0b87121b67 | |||
e649667da8 | |||
6af4b3f64c | |||
efcc248486 | |||
82eeae6030 | |||
440c77965c | |||
880289217e | |||
d0947f1040 | |||
303edadb1e | |||
50155a610d | |||
c2830a56b6 | |||
b9a9b6b4d9 | |||
e7f7f271c8 | |||
b26e96c5a2 | |||
9b404c215b | |||
d6dae04959 | |||
629bd9b7c6 | |||
3d4aa0fdc9 | |||
711e30a6be | |||
7b1462fdee | |||
50915833ff | |||
44e37fd531 | |||
fa3f957738 | |||
224b26ced6 | |||
e833d9e67c | |||
dc08edb7d2 | |||
0b78e30848 | |||
9253c69325 | |||
7d3a62cc4c | |||
7e2147f14e | |||
32f39c98a7 | |||
ddf6db5a7e | |||
554dbf6c23 | |||
d1997f04c0 | |||
deefaa0961 | |||
ce4cb39a2d | |||
84724e5f65 | |||
56c2e12760 | |||
21656a7ab6 | |||
2ccc77ef40 |
@ -0,0 +1,43 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasIsSyncedColumn = await knex.schema.hasColumn(TableName.Integration, "isSynced");
|
||||
const hasSyncMessageColumn = await knex.schema.hasColumn(TableName.Integration, "syncMessage");
|
||||
const hasLastSyncJobId = await knex.schema.hasColumn(TableName.Integration, "lastSyncJobId");
|
||||
|
||||
await knex.schema.alterTable(TableName.Integration, (t) => {
|
||||
if (!hasIsSyncedColumn) {
|
||||
t.boolean("isSynced").nullable();
|
||||
}
|
||||
|
||||
if (!hasSyncMessageColumn) {
|
||||
t.text("syncMessage").nullable();
|
||||
}
|
||||
|
||||
if (!hasLastSyncJobId) {
|
||||
t.string("lastSyncJobId").nullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasIsSyncedColumn = await knex.schema.hasColumn(TableName.Integration, "isSynced");
|
||||
const hasSyncMessageColumn = await knex.schema.hasColumn(TableName.Integration, "syncMessage");
|
||||
const hasLastSyncJobId = await knex.schema.hasColumn(TableName.Integration, "lastSyncJobId");
|
||||
|
||||
await knex.schema.alterTable(TableName.Integration, (t) => {
|
||||
if (hasIsSyncedColumn) {
|
||||
t.dropColumn("isSynced");
|
||||
}
|
||||
|
||||
if (hasSyncMessageColumn) {
|
||||
t.dropColumn("syncMessage");
|
||||
}
|
||||
|
||||
if (hasLastSyncJobId) {
|
||||
t.dropColumn("lastSyncJobId");
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
|
||||
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
|
||||
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||
if (doesProjectIdExist) t.index("projectId");
|
||||
if (doesOrgIdExist) t.index("orgId");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
|
||||
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
|
||||
|
||||
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||
if (doesProjectIdExist) t.dropIndex("projectId");
|
||||
if (doesOrgIdExist) t.dropIndex("orgId");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "envId");
|
||||
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
|
||||
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
|
||||
if (doesEnvIdExist) t.index("envId");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "envId");
|
||||
|
||||
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
|
||||
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
|
||||
if (doesEnvIdExist) t.dropIndex("envId");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SecretVersion, "envId");
|
||||
if (await knex.schema.hasTable(TableName.SecretVersion)) {
|
||||
await knex.schema.alterTable(TableName.SecretVersion, (t) => {
|
||||
if (doesEnvIdExist) t.index("envId");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SecretVersion, "envId");
|
||||
|
||||
if (await knex.schema.hasTable(TableName.SecretVersion)) {
|
||||
await knex.schema.alterTable(TableName.SecretVersion, (t) => {
|
||||
if (doesEnvIdExist) t.dropIndex("envId");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "snapshotId");
|
||||
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
|
||||
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
|
||||
if (doesSnapshotIdExist) t.index("snapshotId");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "snapshotId");
|
||||
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
|
||||
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
|
||||
if (doesSnapshotIdExist) t.dropIndex("snapshotId");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotFolder, "snapshotId");
|
||||
if (await knex.schema.hasTable(TableName.SnapshotFolder)) {
|
||||
await knex.schema.alterTable(TableName.SnapshotFolder, (t) => {
|
||||
if (doesSnapshotIdExist) t.index("snapshotId");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotFolder, "snapshotId");
|
||||
if (await knex.schema.hasTable(TableName.SnapshotFolder)) {
|
||||
await knex.schema.alterTable(TableName.SnapshotFolder, (t) => {
|
||||
if (doesSnapshotIdExist) t.dropIndex("snapshotId");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const doesFolderIdExist = await knex.schema.hasColumn(TableName.Secret, "folderId");
|
||||
const doesUserIdExist = await knex.schema.hasColumn(TableName.Secret, "userId");
|
||||
if (await knex.schema.hasTable(TableName.Secret)) {
|
||||
await knex.schema.alterTable(TableName.Secret, (t) => {
|
||||
if (doesFolderIdExist && doesUserIdExist) t.index(["folderId", "userId"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const doesFolderIdExist = await knex.schema.hasColumn(TableName.Secret, "folderId");
|
||||
const doesUserIdExist = await knex.schema.hasColumn(TableName.Secret, "userId");
|
||||
|
||||
if (await knex.schema.hasTable(TableName.Secret)) {
|
||||
await knex.schema.alterTable(TableName.Secret, (t) => {
|
||||
if (doesUserIdExist && doesFolderIdExist) t.dropIndex(["folderId", "userId"]);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const doesExpireAtExist = await knex.schema.hasColumn(TableName.AuditLog, "expiresAt");
|
||||
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||
if (doesExpireAtExist) t.index("expiresAt");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const doesExpireAtExist = await knex.schema.hasColumn(TableName.AuditLog, "expiresAt");
|
||||
|
||||
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||
if (doesExpireAtExist) t.dropIndex("expiresAt");
|
||||
});
|
||||
}
|
||||
}
|
@ -28,7 +28,10 @@ export const IntegrationsSchema = z.object({
|
||||
secretPath: z.string().default("/"),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
lastUsed: z.date().nullable().optional()
|
||||
lastUsed: z.date().nullable().optional(),
|
||||
isSynced: z.boolean().nullable().optional(),
|
||||
syncMessage: z.string().nullable().optional(),
|
||||
lastSyncJobId: z.string().nullable().optional()
|
||||
});
|
||||
|
||||
export type TIntegrations = z.infer<typeof IntegrationsSchema>;
|
||||
|
@ -51,6 +51,7 @@ export enum EventType {
|
||||
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
|
||||
CREATE_INTEGRATION = "create-integration",
|
||||
DELETE_INTEGRATION = "delete-integration",
|
||||
MANUAL_SYNC_INTEGRATION = "manual-sync-integration",
|
||||
ADD_TRUSTED_IP = "add-trusted-ip",
|
||||
UPDATE_TRUSTED_IP = "update-trusted-ip",
|
||||
DELETE_TRUSTED_IP = "delete-trusted-ip",
|
||||
@ -281,6 +282,25 @@ interface DeleteIntegrationEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface ManualSyncIntegrationEvent {
|
||||
type: EventType.MANUAL_SYNC_INTEGRATION;
|
||||
metadata: {
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
interface AddTrustedIPEvent {
|
||||
type: EventType.ADD_TRUSTED_IP;
|
||||
metadata: {
|
||||
@ -791,6 +811,7 @@ export type Event =
|
||||
| UnauthorizeIntegrationEvent
|
||||
| CreateIntegrationEvent
|
||||
| DeleteIntegrationEvent
|
||||
| ManualSyncIntegrationEvent
|
||||
| AddTrustedIPEvent
|
||||
| UpdateTrustedIPEvent
|
||||
| DeleteTrustedIPEvent
|
||||
|
@ -148,36 +148,6 @@ export const PROJECTS = {
|
||||
name: "The new name of the project.",
|
||||
autoCapitalization: "Disable or enable auto-capitalization for the project."
|
||||
},
|
||||
INVITE_MEMBER: {
|
||||
projectId: "The ID of the project to invite the member to.",
|
||||
emails: "A list of organization member emails to invite to the project.",
|
||||
usernames: "A list of usernames to invite to the project."
|
||||
},
|
||||
REMOVE_MEMBER: {
|
||||
projectId: "The ID of the project to remove the member from.",
|
||||
emails: "A list of organization member emails to remove from the project.",
|
||||
usernames: "A list of usernames to remove from the project."
|
||||
},
|
||||
GET_USER_MEMBERSHIPS: {
|
||||
workspaceId: "The ID of the project to get memberships from."
|
||||
},
|
||||
UPDATE_USER_MEMBERSHIP: {
|
||||
workspaceId: "The ID of the project to update the membership for.",
|
||||
membershipId: "The ID of the membership to update.",
|
||||
roles: "A list of roles to update the membership to."
|
||||
},
|
||||
LIST_IDENTITY_MEMBERSHIPS: {
|
||||
projectId: "The ID of the project to get identity memberships from."
|
||||
},
|
||||
UPDATE_IDENTITY_MEMBERSHIP: {
|
||||
projectId: "The ID of the project to update the identity membership for.",
|
||||
identityId: "The ID of the identity to update the membership for.",
|
||||
roles: "A list of roles to update the membership to."
|
||||
},
|
||||
DELETE_IDENTITY_MEMBERSHIP: {
|
||||
projectId: "The ID of the project to delete the identity membership from.",
|
||||
identityId: "The ID of the identity to delete the membership from."
|
||||
},
|
||||
GET_KEY: {
|
||||
workspaceId: "The ID of the project to get the key from."
|
||||
},
|
||||
@ -216,6 +186,70 @@ export const PROJECTS = {
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const PROJECT_USERS = {
|
||||
INVITE_MEMBER: {
|
||||
projectId: "The ID of the project to invite the member to.",
|
||||
emails: "A list of organization member emails to invite to the project.",
|
||||
usernames: "A list of usernames to invite to the project."
|
||||
},
|
||||
REMOVE_MEMBER: {
|
||||
projectId: "The ID of the project to remove the member from.",
|
||||
emails: "A list of organization member emails to remove from the project.",
|
||||
usernames: "A list of usernames to remove from the project."
|
||||
},
|
||||
GET_USER_MEMBERSHIPS: {
|
||||
workspaceId: "The ID of the project to get memberships from."
|
||||
},
|
||||
GET_USER_MEMBERSHIP: {
|
||||
workspaceId: "The ID of the project to get memberships from.",
|
||||
username: "The username to get project membership of. Email is the default username."
|
||||
},
|
||||
UPDATE_USER_MEMBERSHIP: {
|
||||
workspaceId: "The ID of the project to update the membership for.",
|
||||
membershipId: "The ID of the membership to update.",
|
||||
roles: "A list of roles to update the membership to."
|
||||
}
|
||||
};
|
||||
|
||||
export const PROJECT_IDENTITIES = {
|
||||
LIST_IDENTITY_MEMBERSHIPS: {
|
||||
projectId: "The ID of the project to get identity memberships from."
|
||||
},
|
||||
GET_IDENTITY_MEMBERSHIP_BY_ID: {
|
||||
identityId: "The ID of the identity to get the membership for.",
|
||||
projectId: "The ID of the project to get the identity membership for."
|
||||
},
|
||||
UPDATE_IDENTITY_MEMBERSHIP: {
|
||||
projectId: "The ID of the project to update the identity membership for.",
|
||||
identityId: "The ID of the identity to update the membership for.",
|
||||
roles: {
|
||||
description: "A list of role slugs to assign to the identity project membership.",
|
||||
role: "The role slug to assign to the newly created identity project membership.",
|
||||
isTemporary: "Whether the assigned role is temporary.",
|
||||
temporaryMode: "Type of temporary expiry.",
|
||||
temporaryRange: "Expiry time for temporary access. In relative mode it could be 1s,2m,3h",
|
||||
temporaryAccessStartTime: "Time to which the temporary access starts"
|
||||
}
|
||||
},
|
||||
DELETE_IDENTITY_MEMBERSHIP: {
|
||||
projectId: "The ID of the project to delete the identity membership from.",
|
||||
identityId: "The ID of the identity to delete the membership from."
|
||||
},
|
||||
CREATE_IDENTITY_MEMBERSHIP: {
|
||||
projectId: "The ID of the project to create the identity membership from.",
|
||||
identityId: "The ID of the identity to create the membership from.",
|
||||
role: "The role slug to assign to the newly created identity project membership.",
|
||||
roles: {
|
||||
description: "A list of role slugs to assign to the newly created identity project membership.",
|
||||
role: "The role slug to assign to the newly created identity project membership.",
|
||||
isTemporary: "Whether the assigned role is temporary.",
|
||||
temporaryMode: "Type of temporary expiry.",
|
||||
temporaryRange: "Expiry time for temporary access. In relative mode it could be 1s,2m,3h",
|
||||
temporaryAccessStartTime: "Time to which the temporary access starts"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const ENVIRONMENTS = {
|
||||
CREATE: {
|
||||
workspaceId: "The ID of the project to create the environment in.",
|
||||
@ -628,6 +662,7 @@ export const INTEGRATION = {
|
||||
secretPrefix: "The prefix for the saved secret. Used by GCP.",
|
||||
secretSuffix: "The suffix for the saved secret. Used by GCP.",
|
||||
initialSyncBehavoir: "Type of syncing behavoir with the integration.",
|
||||
mappingBehavior: "The mapping behavior of the integration.",
|
||||
shouldAutoRedeploy: "Used by Render to trigger auto deploy.",
|
||||
secretGCPLabel: "The label for GCP secrets.",
|
||||
secretAWSTag: "The tags for AWS secrets.",
|
||||
@ -649,6 +684,9 @@ export const INTEGRATION = {
|
||||
},
|
||||
DELETE: {
|
||||
integrationId: "The ID of the integration object."
|
||||
},
|
||||
SYNC: {
|
||||
integrationId: "The ID of the integration object to manually sync"
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -30,6 +30,37 @@ const loggerConfig = z.object({
|
||||
NODE_ENV: z.enum(["development", "test", "production"]).default("production")
|
||||
});
|
||||
|
||||
const redactedKeys = [
|
||||
"accessToken",
|
||||
"authToken",
|
||||
"serviceToken",
|
||||
"identityAccessToken",
|
||||
"token",
|
||||
"privateKey",
|
||||
"serverPrivateKey",
|
||||
"plainPrivateKey",
|
||||
"plainProjectKey",
|
||||
"encryptedPrivateKey",
|
||||
"userPrivateKey",
|
||||
"protectedKey",
|
||||
"decryptKey",
|
||||
"encryptedProjectKey",
|
||||
"encryptedSymmetricKey",
|
||||
"encryptedPrivateKey",
|
||||
"backupPrivateKey",
|
||||
"secretKey",
|
||||
"SecretKey",
|
||||
"botPrivateKey",
|
||||
"encryptedKey",
|
||||
"plaintextProjectKey",
|
||||
"accessKey",
|
||||
"botKey",
|
||||
"decryptedSecret",
|
||||
"secrets",
|
||||
"key",
|
||||
"password"
|
||||
];
|
||||
|
||||
export const initLogger = async () => {
|
||||
const cfg = loggerConfig.parse(process.env);
|
||||
const targets: pino.TransportMultiOptions["targets"][number][] = [
|
||||
@ -74,7 +105,9 @@ export const initLogger = async () => {
|
||||
hostname: bindings.hostname
|
||||
// node_version: process.version
|
||||
})
|
||||
}
|
||||
},
|
||||
// redact until depth of three
|
||||
redact: [...redactedKeys, ...redactedKeys.map((key) => `*.${key}`), ...redactedKeys.map((key) => `*.*.${key}`)]
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
transport
|
||||
|
@ -36,7 +36,7 @@ export const writeLimit: RateLimitOptions = {
|
||||
export const secretsLimit: RateLimitOptions = {
|
||||
// secrets, folders, secret imports
|
||||
timeWindow: 60 * 1000,
|
||||
max: 1000,
|
||||
max: 60,
|
||||
keyGenerator: (req) => req.realIp
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import fp from "fastify-plugin";
|
||||
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
|
||||
// inject permission type needed based on auth extracted
|
||||
@ -15,6 +16,10 @@ export const injectPermission = fp(async (server) => {
|
||||
orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY"
|
||||
authMethod: req.auth.authMethod // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null
|
||||
};
|
||||
|
||||
logger.info(
|
||||
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.userId}] [type=${ActorType.USER}]`
|
||||
);
|
||||
} else if (req.auth.actor === ActorType.IDENTITY) {
|
||||
req.permission = {
|
||||
type: ActorType.IDENTITY,
|
||||
@ -22,6 +27,10 @@ export const injectPermission = fp(async (server) => {
|
||||
orgId: req.auth.orgId,
|
||||
authMethod: null
|
||||
};
|
||||
|
||||
logger.info(
|
||||
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.identityId}] [type=${ActorType.IDENTITY}]`
|
||||
);
|
||||
} else if (req.auth.actor === ActorType.SERVICE) {
|
||||
req.permission = {
|
||||
type: ActorType.SERVICE,
|
||||
@ -29,6 +38,10 @@ export const injectPermission = fp(async (server) => {
|
||||
orgId: req.auth.orgId,
|
||||
authMethod: null
|
||||
};
|
||||
|
||||
logger.info(
|
||||
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.serviceTokenId}] [type=${ActorType.SERVICE}]`
|
||||
);
|
||||
} else if (req.auth.actor === ActorType.SCIM_CLIENT) {
|
||||
req.permission = {
|
||||
type: ActorType.SCIM_CLIENT,
|
||||
@ -36,6 +49,10 @@ export const injectPermission = fp(async (server) => {
|
||||
orgId: req.auth.orgId,
|
||||
authMethod: null
|
||||
};
|
||||
|
||||
logger.info(
|
||||
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.scimTokenId}] [type=${ActorType.SCIM_CLIENT}]`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -8,6 +8,7 @@ import { writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { IntegrationMappingBehavior } from "@app/services/integration-auth/integration-list";
|
||||
import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telemetry/telemetry-types";
|
||||
|
||||
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||
@ -49,6 +50,10 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
|
||||
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
|
||||
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
|
||||
mappingBehavior: z
|
||||
.nativeEnum(IntegrationMappingBehavior)
|
||||
.optional()
|
||||
.describe(INTEGRATION.CREATE.metadata.mappingBehavior),
|
||||
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
|
||||
secretGCPLabel: z
|
||||
.object({
|
||||
@ -160,6 +165,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
|
||||
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
|
||||
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
|
||||
mappingBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.mappingBehavior),
|
||||
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
|
||||
secretGCPLabel: z
|
||||
.object({
|
||||
@ -262,5 +268,64 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(akhilmhdh-pg): manual sync
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:integrationId/sync",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Manually trigger sync of an integration by integration id",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
integrationId: z.string().trim().describe(INTEGRATION.SYNC.integrationId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
integration: IntegrationsSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const integration = await server.services.integration.syncIntegration({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
id: req.params.integrationId
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: integration.projectId,
|
||||
event: {
|
||||
type: EventType.MANUAL_SYNC_INTEGRATION,
|
||||
// eslint-disable-next-line
|
||||
metadata: 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
|
||||
// eslint-disable-next-line
|
||||
}) as any
|
||||
}
|
||||
});
|
||||
|
||||
return { integration };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
UsersSchema
|
||||
} from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { PROJECTS } from "@app/lib/api-docs";
|
||||
import { PROJECT_USERS } from "@app/lib/api-docs";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@ -30,7 +30,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(PROJECTS.GET_USER_MEMBERSHIPS.workspaceId)
|
||||
workspaceId: z.string().trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIPS.workspaceId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -74,6 +74,66 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:workspaceId/memberships/details",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Return project user memberships",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.workspaceId)
|
||||
}),
|
||||
body: z.object({
|
||||
username: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.username)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
membership: ProjectMembershipsSchema.extend({
|
||||
user: UsersSchema.pick({
|
||||
email: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
id: true
|
||||
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
|
||||
roles: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
role: z.string(),
|
||||
customRoleId: z.string().optional().nullable(),
|
||||
customRoleName: z.string().optional().nullable(),
|
||||
customRoleSlug: z.string().optional().nullable(),
|
||||
isTemporary: z.boolean(),
|
||||
temporaryMode: z.string().optional().nullable(),
|
||||
temporaryRange: z.string().nullable().optional(),
|
||||
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||
temporaryAccessEndTime: z.date().nullable().optional()
|
||||
})
|
||||
)
|
||||
}).omit({ createdAt: true, updatedAt: true })
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const membership = await server.services.projectMembership.getProjectMembershipByUsername({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.params.workspaceId,
|
||||
username: req.body.username
|
||||
});
|
||||
return { membership };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:workspaceId/memberships",
|
||||
@ -142,8 +202,8 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.workspaceId),
|
||||
membershipId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.membershipId)
|
||||
workspaceId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.workspaceId),
|
||||
membershipId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.membershipId)
|
||||
}),
|
||||
body: z.object({
|
||||
roles: z
|
||||
@ -164,7 +224,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
)
|
||||
.min(1)
|
||||
.refine((data) => data.some(({ isTemporary }) => !isTemporary), "At least one long lived role is required")
|
||||
.describe(PROJECTS.UPDATE_USER_MEMBERSHIP.roles)
|
||||
.describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.roles)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -7,7 +7,8 @@ import {
|
||||
ProjectMembershipRole,
|
||||
ProjectUserMembershipRolesSchema
|
||||
} from "@app/db/schemas";
|
||||
import { PROJECTS } from "@app/lib/api-docs";
|
||||
import { PROJECT_IDENTITIES } from "@app/lib/api-docs";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@ -22,12 +23,48 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Create project identity membership",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim(),
|
||||
identityId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
role: z.string().trim().min(1).default(ProjectMembershipRole.NoAccess)
|
||||
// @depreciated
|
||||
role: z.string().trim().optional().default(ProjectMembershipRole.NoAccess),
|
||||
roles: z
|
||||
.array(
|
||||
z.union([
|
||||
z.object({
|
||||
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
|
||||
isTemporary: z
|
||||
.literal(false)
|
||||
.default(false)
|
||||
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role)
|
||||
}),
|
||||
z.object({
|
||||
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
|
||||
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
|
||||
temporaryMode: z
|
||||
.nativeEnum(ProjectUserMembershipTemporaryMode)
|
||||
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
|
||||
temporaryRange: z
|
||||
.string()
|
||||
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
|
||||
temporaryAccessStartTime: z
|
||||
.string()
|
||||
.datetime()
|
||||
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role)
|
||||
})
|
||||
])
|
||||
)
|
||||
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.description)
|
||||
.optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -36,6 +73,9 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { role, roles } = req.body;
|
||||
if (!role && !roles) throw new BadRequestError({ message: "You must provide either role or roles field" });
|
||||
|
||||
const identityMembership = await server.services.identityProject.createProjectIdentity({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
@ -43,7 +83,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
actorOrgId: req.permission.orgId,
|
||||
identityId: req.params.identityId,
|
||||
projectId: req.params.projectId,
|
||||
role: req.body.role
|
||||
roles: roles || [{ role }]
|
||||
});
|
||||
return { identityMembership };
|
||||
}
|
||||
@ -64,28 +104,39 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.projectId),
|
||||
identityId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.identityId)
|
||||
projectId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.projectId),
|
||||
identityId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.identityId)
|
||||
}),
|
||||
body: z.object({
|
||||
roles: z
|
||||
.array(
|
||||
z.union([
|
||||
z.object({
|
||||
role: z.string(),
|
||||
isTemporary: z.literal(false).default(false)
|
||||
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
|
||||
isTemporary: z
|
||||
.literal(false)
|
||||
.default(false)
|
||||
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary)
|
||||
}),
|
||||
z.object({
|
||||
role: z.string(),
|
||||
isTemporary: z.literal(true),
|
||||
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
|
||||
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
||||
temporaryAccessStartTime: z.string().datetime()
|
||||
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
|
||||
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary),
|
||||
temporaryMode: z
|
||||
.nativeEnum(ProjectUserMembershipTemporaryMode)
|
||||
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode),
|
||||
temporaryRange: z
|
||||
.string()
|
||||
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryRange),
|
||||
temporaryAccessStartTime: z
|
||||
.string()
|
||||
.datetime()
|
||||
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryAccessStartTime)
|
||||
})
|
||||
])
|
||||
)
|
||||
.min(1)
|
||||
.describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.roles)
|
||||
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.description)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -122,8 +173,8 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.projectId),
|
||||
identityId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.identityId)
|
||||
projectId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.projectId),
|
||||
identityId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.identityId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -159,7 +210,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim().describe(PROJECTS.LIST_IDENTITY_MEMBERSHIPS.projectId)
|
||||
projectId: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.projectId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -200,4 +251,61 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
return { identityMemberships };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:projectId/identity-memberships/:identityId",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Return project identity membership",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.projectId),
|
||||
identityId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.identityId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
identityMembership: z.object({
|
||||
id: z.string(),
|
||||
identityId: z.string(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
roles: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
role: z.string(),
|
||||
customRoleId: z.string().optional().nullable(),
|
||||
customRoleName: z.string().optional().nullable(),
|
||||
customRoleSlug: z.string().optional().nullable(),
|
||||
isTemporary: z.boolean(),
|
||||
temporaryMode: z.string().optional().nullable(),
|
||||
temporaryRange: z.string().nullable().optional(),
|
||||
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||
temporaryAccessEndTime: z.date().nullable().optional()
|
||||
})
|
||||
),
|
||||
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const identityMembership = await server.services.identityProject.getProjectIdentityByIdentityId({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.params.projectId,
|
||||
identityId: req.params.identityId
|
||||
});
|
||||
return { identityMembership };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { ProjectMembershipsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { PROJECTS } from "@app/lib/api-docs";
|
||||
import { PROJECT_USERS } from "@app/lib/api-docs";
|
||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@ -22,11 +22,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().describe(PROJECTS.INVITE_MEMBER.projectId)
|
||||
projectId: z.string().describe(PROJECT_USERS.INVITE_MEMBER.projectId)
|
||||
}),
|
||||
body: z.object({
|
||||
emails: z.string().email().array().default([]).describe(PROJECTS.INVITE_MEMBER.emails),
|
||||
usernames: z.string().array().default([]).describe(PROJECTS.INVITE_MEMBER.usernames)
|
||||
emails: z.string().email().array().default([]).describe(PROJECT_USERS.INVITE_MEMBER.emails),
|
||||
usernames: z.string().array().default([]).describe(PROJECT_USERS.INVITE_MEMBER.usernames)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -77,11 +77,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().describe(PROJECTS.REMOVE_MEMBER.projectId)
|
||||
projectId: z.string().describe(PROJECT_USERS.REMOVE_MEMBER.projectId)
|
||||
}),
|
||||
body: z.object({
|
||||
emails: z.string().email().array().default([]).describe(PROJECTS.REMOVE_MEMBER.emails),
|
||||
usernames: z.string().array().default([]).describe(PROJECTS.REMOVE_MEMBER.usernames)
|
||||
emails: z.string().email().array().default([]).describe(PROJECT_USERS.REMOVE_MEMBER.emails),
|
||||
usernames: z.string().array().default([]).describe(PROJECT_USERS.REMOVE_MEMBER.usernames)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -10,11 +10,16 @@ export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFac
|
||||
export const identityProjectDALFactory = (db: TDbClient) => {
|
||||
const identityProjectOrm = ormify(db, TableName.IdentityProjectMembership);
|
||||
|
||||
const findByProjectId = async (projectId: string, tx?: Knex) => {
|
||||
const findByProjectId = async (projectId: string, filter: { identityId?: string } = {}, tx?: Knex) => {
|
||||
try {
|
||||
const docs = await (tx || db)(TableName.IdentityProjectMembership)
|
||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||
.join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`)
|
||||
.where((qb) => {
|
||||
if (filter.identityId) {
|
||||
void qb.where("identityId", filter.identityId);
|
||||
}
|
||||
})
|
||||
.join(
|
||||
TableName.IdentityProjectMembershipRole,
|
||||
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
|
||||
|
@ -18,6 +18,7 @@ import { TIdentityProjectMembershipRoleDALFactory } from "./identity-project-mem
|
||||
import {
|
||||
TCreateProjectIdentityDTO,
|
||||
TDeleteProjectIdentityDTO,
|
||||
TGetProjectIdentityByIdentityIdDTO,
|
||||
TListProjectIdentityDTO,
|
||||
TUpdateProjectIdentityDTO
|
||||
} from "./identity-project-types";
|
||||
@ -51,7 +52,7 @@ export const identityProjectServiceFactory = ({
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectId,
|
||||
role
|
||||
roles
|
||||
}: TCreateProjectIdentityDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
@ -78,18 +79,33 @@ export const identityProjectServiceFactory = ({
|
||||
message: `Failed to find identity with id ${identityId}`
|
||||
});
|
||||
|
||||
const { permission: rolePermission, role: customRole } = await permissionService.getProjectPermissionByRole(
|
||||
role,
|
||||
project.id
|
||||
for await (const { role: requestedRoleChange } of roles) {
|
||||
const { permission: rolePermission } = await permissionService.getProjectPermissionByRole(
|
||||
requestedRoleChange,
|
||||
projectId
|
||||
);
|
||||
|
||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
|
||||
|
||||
if (!hasRequiredPriviledges) {
|
||||
throw new ForbiddenRequestError({ message: "Failed to change to a more privileged role" });
|
||||
}
|
||||
}
|
||||
|
||||
// validate custom roles input
|
||||
const customInputRoles = roles.filter(
|
||||
({ role }) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
|
||||
);
|
||||
const hasCustomRole = Boolean(customInputRoles.length);
|
||||
const customRoles = hasCustomRole
|
||||
? await projectRoleDAL.find({
|
||||
projectId,
|
||||
$in: { slug: customInputRoles.map(({ role }) => role) }
|
||||
})
|
||||
: [];
|
||||
if (customRoles.length !== customInputRoles.length) throw new BadRequestError({ message: "Custom role not found" });
|
||||
|
||||
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
|
||||
if (!hasPriviledge)
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Failed to add identity to project with more privileged role"
|
||||
});
|
||||
const isCustomRole = Boolean(customRole);
|
||||
|
||||
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
||||
const projectIdentity = await identityProjectDAL.transaction(async (tx) => {
|
||||
const identityProjectMembership = await identityProjectDAL.create(
|
||||
{
|
||||
@ -98,16 +114,32 @@ export const identityProjectServiceFactory = ({
|
||||
},
|
||||
tx
|
||||
);
|
||||
const sanitizedProjectMembershipRoles = roles.map((inputRole) => {
|
||||
const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]);
|
||||
if (!inputRole.isTemporary) {
|
||||
return {
|
||||
projectMembershipId: identityProjectMembership.id,
|
||||
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
|
||||
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null
|
||||
};
|
||||
}
|
||||
|
||||
await identityProjectMembershipRoleDAL.create(
|
||||
{
|
||||
// check cron or relative here later for now its just relative
|
||||
const relativeTimeInMs = ms(inputRole.temporaryRange);
|
||||
return {
|
||||
projectMembershipId: identityProjectMembership.id,
|
||||
role: isCustomRole ? ProjectMembershipRole.Custom : role,
|
||||
customRoleId: customRole?.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
return identityProjectMembership;
|
||||
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
|
||||
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null,
|
||||
isTemporary: true,
|
||||
temporaryMode: ProjectUserMembershipTemporaryMode.Relative,
|
||||
temporaryRange: inputRole.temporaryRange,
|
||||
temporaryAccessStartTime: new Date(inputRole.temporaryAccessStartTime),
|
||||
temporaryAccessEndTime: new Date(new Date(inputRole.temporaryAccessStartTime).getTime() + relativeTimeInMs)
|
||||
};
|
||||
});
|
||||
|
||||
const identityRoles = await identityProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
|
||||
return { ...identityProjectMembership, roles: identityRoles };
|
||||
});
|
||||
return projectIdentity;
|
||||
};
|
||||
@ -251,10 +283,33 @@ export const identityProjectServiceFactory = ({
|
||||
return identityMemberships;
|
||||
};
|
||||
|
||||
const getProjectIdentityByIdentityId = async ({
|
||||
projectId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
identityId
|
||||
}: TGetProjectIdentityByIdentityIdDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
||||
|
||||
const [identityMembership] = await identityProjectDAL.findByProjectId(projectId, { identityId });
|
||||
if (!identityMembership) throw new BadRequestError({ message: `Membership not found for identity ${identityId}` });
|
||||
return identityMembership;
|
||||
};
|
||||
|
||||
return {
|
||||
createProjectIdentity,
|
||||
updateProjectIdentity,
|
||||
deleteProjectIdentity,
|
||||
listProjectIdentities
|
||||
listProjectIdentities,
|
||||
getProjectIdentityByIdentityId
|
||||
};
|
||||
};
|
||||
|
@ -4,7 +4,19 @@ import { ProjectUserMembershipTemporaryMode } from "../project-membership/projec
|
||||
|
||||
export type TCreateProjectIdentityDTO = {
|
||||
identityId: string;
|
||||
role: string;
|
||||
roles: (
|
||||
| {
|
||||
role: string;
|
||||
isTemporary?: false;
|
||||
}
|
||||
| {
|
||||
role: string;
|
||||
isTemporary: true;
|
||||
temporaryMode: ProjectUserMembershipTemporaryMode.Relative;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}
|
||||
)[];
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TUpdateProjectIdentityDTO = {
|
||||
@ -29,3 +41,7 @@ export type TDeleteProjectIdentityDTO = {
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TListProjectIdentityDTO = TProjectPermission;
|
||||
|
||||
export type TGetProjectIdentityByIdentityIdDTO = {
|
||||
identityId: string;
|
||||
} & TProjectPermission;
|
||||
|
@ -43,6 +43,11 @@ export enum IntegrationInitialSyncBehavior {
|
||||
PREFER_SOURCE = "prefer-source"
|
||||
}
|
||||
|
||||
export enum IntegrationMappingBehavior {
|
||||
ONE_TO_ONE = "one-to-one",
|
||||
MANY_TO_ONE = "many-to-one"
|
||||
}
|
||||
|
||||
export enum IntegrationUrls {
|
||||
// integration oauth endpoints
|
||||
GCP_TOKEN_URL = "https://oauth2.googleapis.com/token",
|
||||
|
@ -30,7 +30,12 @@ import { BadRequestError } from "@app/lib/errors";
|
||||
import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/secret/secret-types";
|
||||
|
||||
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
||||
import { IntegrationInitialSyncBehavior, Integrations, IntegrationUrls } from "./integration-list";
|
||||
import {
|
||||
IntegrationInitialSyncBehavior,
|
||||
IntegrationMappingBehavior,
|
||||
Integrations,
|
||||
IntegrationUrls
|
||||
} from "./integration-list";
|
||||
|
||||
const getSecretKeyValuePair = (secrets: Record<string, { value: string | null; comment?: string } | null>) =>
|
||||
Object.keys(secrets).reduce<Record<string, string | null | undefined>>((prev, key) => {
|
||||
@ -570,134 +575,145 @@ const syncSecretsAWSSecretManager = async ({
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
let secretsManager;
|
||||
const secKeyVal = getSecretKeyValuePair(secrets);
|
||||
const metadata = z.record(z.any()).parse(integration.metadata || {});
|
||||
try {
|
||||
if (!accessId) return;
|
||||
|
||||
secretsManager = new SecretsManagerClient({
|
||||
region: integration.region as string,
|
||||
credentials: {
|
||||
accessKeyId: accessId,
|
||||
secretAccessKey: accessToken
|
||||
if (!accessId) return;
|
||||
|
||||
const secretsManager = new SecretsManagerClient({
|
||||
region: integration.region as string,
|
||||
credentials: {
|
||||
accessKeyId: accessId,
|
||||
secretAccessKey: accessToken
|
||||
}
|
||||
});
|
||||
|
||||
const processAwsSecret = async (secretId: string, keyValuePairs: Record<string, string | null | undefined>) => {
|
||||
try {
|
||||
const awsSecretManagerSecret = await secretsManager.send(
|
||||
new GetSecretValueCommand({
|
||||
SecretId: secretId
|
||||
})
|
||||
);
|
||||
|
||||
let awsSecretManagerSecretObj: { [key: string]: AWS.SecretsManager } = {};
|
||||
|
||||
if (awsSecretManagerSecret?.SecretString) {
|
||||
awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString);
|
||||
}
|
||||
});
|
||||
|
||||
const awsSecretManagerSecret = await secretsManager.send(
|
||||
new GetSecretValueCommand({
|
||||
SecretId: integration.app as string
|
||||
})
|
||||
);
|
||||
if (!isEqual(awsSecretManagerSecretObj, keyValuePairs)) {
|
||||
await secretsManager.send(
|
||||
new UpdateSecretCommand({
|
||||
SecretId: secretId,
|
||||
SecretString: JSON.stringify(keyValuePairs)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
let awsSecretManagerSecretObj: { [key: string]: AWS.SecretsManager } = {};
|
||||
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
|
||||
|
||||
if (awsSecretManagerSecret?.SecretString) {
|
||||
awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString);
|
||||
}
|
||||
if (secretAWSTag && secretAWSTag.length) {
|
||||
const describedSecret = await secretsManager.send(
|
||||
// requires secretsmanager:DescribeSecret policy
|
||||
new DescribeSecretCommand({
|
||||
SecretId: secretId
|
||||
})
|
||||
);
|
||||
|
||||
if (!isEqual(awsSecretManagerSecretObj, secKeyVal)) {
|
||||
await secretsManager.send(
|
||||
new UpdateSecretCommand({
|
||||
SecretId: integration.app as string,
|
||||
SecretString: JSON.stringify(secKeyVal)
|
||||
})
|
||||
);
|
||||
}
|
||||
if (!describedSecret.Tags) return;
|
||||
|
||||
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
|
||||
const integrationTagObj = secretAWSTag.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.key] = item.value;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
);
|
||||
|
||||
if (secretAWSTag && secretAWSTag.length) {
|
||||
const describedSecret = await secretsManager.send(
|
||||
// requires secretsmanager:DescribeSecret policy
|
||||
new DescribeSecretCommand({
|
||||
SecretId: integration.app as string
|
||||
})
|
||||
);
|
||||
const awsTagObj = (describedSecret.Tags || []).reduce(
|
||||
(acc, item) => {
|
||||
if (item.Key && item.Value) {
|
||||
acc[item.Key] = item.Value;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
);
|
||||
|
||||
if (!describedSecret.Tags) return;
|
||||
const tagsToUpdate: { Key: string; Value: string }[] = [];
|
||||
const tagsToDelete: { Key: string; Value: string }[] = [];
|
||||
|
||||
const integrationTagObj = secretAWSTag.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.key] = item.value;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
);
|
||||
|
||||
const awsTagObj = (describedSecret.Tags || []).reduce(
|
||||
(acc, item) => {
|
||||
if (item.Key && item.Value) {
|
||||
acc[item.Key] = item.Value;
|
||||
describedSecret.Tags?.forEach((tag) => {
|
||||
if (tag.Key && tag.Value) {
|
||||
if (!(tag.Key in integrationTagObj)) {
|
||||
// delete tag from AWS secret manager
|
||||
tagsToDelete.push({
|
||||
Key: tag.Key,
|
||||
Value: tag.Value
|
||||
});
|
||||
} else if (tag.Value !== integrationTagObj[tag.Key]) {
|
||||
// update tag in AWS secret manager
|
||||
tagsToUpdate.push({
|
||||
Key: tag.Key,
|
||||
Value: integrationTagObj[tag.Key]
|
||||
});
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
);
|
||||
});
|
||||
|
||||
const tagsToUpdate: { Key: string; Value: string }[] = [];
|
||||
const tagsToDelete: { Key: string; Value: string }[] = [];
|
||||
|
||||
describedSecret.Tags?.forEach((tag) => {
|
||||
if (tag.Key && tag.Value) {
|
||||
if (!(tag.Key in integrationTagObj)) {
|
||||
// delete tag from AWS secret manager
|
||||
tagsToDelete.push({
|
||||
Key: tag.Key,
|
||||
Value: tag.Value
|
||||
});
|
||||
} else if (tag.Value !== integrationTagObj[tag.Key]) {
|
||||
// update tag in AWS secret manager
|
||||
secretAWSTag?.forEach((tag) => {
|
||||
if (!(tag.key in awsTagObj)) {
|
||||
// create tag in AWS secret manager
|
||||
tagsToUpdate.push({
|
||||
Key: tag.Key,
|
||||
Value: integrationTagObj[tag.Key]
|
||||
Key: tag.key,
|
||||
Value: tag.value
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
secretAWSTag?.forEach((tag) => {
|
||||
if (!(tag.key in awsTagObj)) {
|
||||
// create tag in AWS secret manager
|
||||
tagsToUpdate.push({
|
||||
Key: tag.key,
|
||||
Value: tag.value
|
||||
});
|
||||
if (tagsToUpdate.length) {
|
||||
await secretsManager.send(
|
||||
new TagResourceCommand({
|
||||
SecretId: secretId,
|
||||
Tags: tagsToUpdate
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (tagsToUpdate.length) {
|
||||
await secretsManager.send(
|
||||
new TagResourceCommand({
|
||||
SecretId: integration.app as string,
|
||||
Tags: tagsToUpdate
|
||||
})
|
||||
);
|
||||
if (tagsToDelete.length) {
|
||||
await secretsManager.send(
|
||||
new UntagResourceCommand({
|
||||
SecretId: secretId,
|
||||
TagKeys: tagsToDelete.map((tag) => tag.Key)
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (tagsToDelete.length) {
|
||||
} catch (err) {
|
||||
// case when AWS manager can't find the specified secret
|
||||
if (err instanceof ResourceNotFoundException && secretsManager) {
|
||||
await secretsManager.send(
|
||||
new UntagResourceCommand({
|
||||
SecretId: integration.app as string,
|
||||
TagKeys: tagsToDelete.map((tag) => tag.Key)
|
||||
new CreateSecretCommand({
|
||||
Name: secretId,
|
||||
SecretString: JSON.stringify(keyValuePairs),
|
||||
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
|
||||
Tags: metadata.secretAWSTag
|
||||
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
|
||||
: []
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// case when AWS manager can't find the specified secret
|
||||
if (err instanceof ResourceNotFoundException && secretsManager) {
|
||||
await secretsManager.send(
|
||||
new CreateSecretCommand({
|
||||
Name: integration.app as string,
|
||||
SecretString: JSON.stringify(secKeyVal),
|
||||
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
|
||||
Tags: metadata.secretAWSTag
|
||||
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
|
||||
: []
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
if (metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE) {
|
||||
for await (const [key, value] of Object.entries(secrets)) {
|
||||
await processAwsSecret(key, {
|
||||
[key]: value.value
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await processAwsSecret(integration.app as string, getSecretKeyValuePair(secrets));
|
||||
}
|
||||
};
|
||||
|
||||
@ -2965,7 +2981,7 @@ const syncSecretsDigitalOceanAppPlatform = async ({
|
||||
spec: {
|
||||
name: integration.app,
|
||||
...appSettings,
|
||||
envs: Object.entries(secrets).map(([key, data]) => ({ key, value: data.value }))
|
||||
envs: Object.entries(secrets).map(([key, data]) => ({ key, value: data.value, type: "SECRET" }))
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -9,7 +9,12 @@ import { TIntegrationAuthDALFactory } from "../integration-auth/integration-auth
|
||||
import { TSecretQueueFactory } from "../secret/secret-queue";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TIntegrationDALFactory } from "./integration-dal";
|
||||
import { TCreateIntegrationDTO, TDeleteIntegrationDTO, TUpdateIntegrationDTO } from "./integration-types";
|
||||
import {
|
||||
TCreateIntegrationDTO,
|
||||
TDeleteIntegrationDTO,
|
||||
TSyncIntegrationDTO,
|
||||
TUpdateIntegrationDTO
|
||||
} from "./integration-types";
|
||||
|
||||
type TIntegrationServiceFactoryDep = {
|
||||
integrationDAL: TIntegrationDALFactory;
|
||||
@ -201,10 +206,35 @@ export const integrationServiceFactory = ({
|
||||
return integrations;
|
||||
};
|
||||
|
||||
const syncIntegration = async ({ id, actorId, actor, actorOrgId, actorAuthMethod }: TSyncIntegrationDTO) => {
|
||||
const integration = await integrationDAL.findById(id);
|
||||
if (!integration) {
|
||||
throw new BadRequestError({ message: "Integration not found" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
integration.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||
|
||||
await secretQueueService.syncIntegrations({
|
||||
environment: integration.environment.slug,
|
||||
secretPath: integration.secretPath,
|
||||
projectId: integration.projectId
|
||||
});
|
||||
|
||||
return { ...integration, envId: integration.environment.id };
|
||||
};
|
||||
|
||||
return {
|
||||
createIntegration,
|
||||
updateIntegration,
|
||||
deleteIntegration,
|
||||
listIntegrationByProject
|
||||
listIntegrationByProject,
|
||||
syncIntegration
|
||||
};
|
||||
};
|
||||
|
@ -59,3 +59,7 @@ export type TUpdateIntegrationDTO = {
|
||||
export type TDeleteIntegrationDTO = {
|
||||
id: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TSyncIntegrationDTO = {
|
||||
id: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
@ -3,6 +3,7 @@ import { decryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/en
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TGetPrivateKeyDTO } from "./project-bot-types";
|
||||
|
||||
export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) =>
|
||||
@ -13,11 +14,17 @@ export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) =>
|
||||
ciphertext: bot.encryptedPrivateKey
|
||||
});
|
||||
|
||||
export const getBotKeyFnFactory = (projectBotDAL: TProjectBotDALFactory) => {
|
||||
export const getBotKeyFnFactory = (
|
||||
projectBotDAL: TProjectBotDALFactory,
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">
|
||||
) => {
|
||||
const getBotKeyFn = async (projectId: string) => {
|
||||
const bot = await projectBotDAL.findOne({ projectId });
|
||||
const project = await projectDAL.findById(projectId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found during bot lookup." });
|
||||
|
||||
if (!bot) throw new BadRequestError({ message: "failed to find bot key" });
|
||||
const bot = await projectBotDAL.findOne({ projectId: project.id });
|
||||
|
||||
if (!bot) throw new BadRequestError({ message: "Failed to find bot key" });
|
||||
if (!bot.isActive) throw new BadRequestError({ message: "Bot is not active" });
|
||||
if (!bot.encryptedProjectKeyNonce || !bot.encryptedProjectKey)
|
||||
throw new BadRequestError({ message: "Encryption key missing" });
|
||||
|
@ -25,7 +25,7 @@ export const projectBotServiceFactory = ({
|
||||
projectDAL,
|
||||
permissionService
|
||||
}: TProjectBotServiceFactoryDep) => {
|
||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
|
||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
|
||||
|
||||
const getBotKey = async (projectId: string) => {
|
||||
return getBotKeyFn(projectId);
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName, TUserEncryptionKeys } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
@ -9,11 +11,19 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
||||
const projectMemberOrm = ormify(db, TableName.ProjectMembership);
|
||||
|
||||
// special query
|
||||
const findAllProjectMembers = async (projectId: string) => {
|
||||
const findAllProjectMembers = async (projectId: string, filter: { usernames?: string[]; username?: string } = {}) => {
|
||||
try {
|
||||
const docs = await db(TableName.ProjectMembership)
|
||||
.where({ [`${TableName.ProjectMembership}.projectId` as "projectId"]: projectId })
|
||||
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
||||
.where((qb) => {
|
||||
if (filter.usernames) {
|
||||
void qb.whereIn("username", filter.usernames);
|
||||
}
|
||||
if (filter.username) {
|
||||
void qb.where("username", filter.username);
|
||||
}
|
||||
})
|
||||
.join<TUserEncryptionKeys>(
|
||||
TableName.UserEncryptionKey,
|
||||
`${TableName.UserEncryptionKey}.userId`,
|
||||
@ -96,9 +106,9 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findProjectGhostUser = async (projectId: string) => {
|
||||
const findProjectGhostUser = async (projectId: string, tx?: Knex) => {
|
||||
try {
|
||||
const ghostUser = await db(TableName.ProjectMembership)
|
||||
const ghostUser = await (tx || db)(TableName.ProjectMembership)
|
||||
.where({ projectId })
|
||||
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
||||
.select(selectAllTableCols(TableName.Users))
|
||||
|
@ -34,6 +34,7 @@ import {
|
||||
TAddUsersToWorkspaceNonE2EEDTO,
|
||||
TDeleteProjectMembershipOldDTO,
|
||||
TDeleteProjectMembershipsDTO,
|
||||
TGetProjectMembershipByUsernameDTO,
|
||||
TGetProjectMembershipDTO,
|
||||
TUpdateProjectMembershipDTO
|
||||
} from "./project-membership-types";
|
||||
@ -89,6 +90,28 @@ export const projectMembershipServiceFactory = ({
|
||||
return projectMembershipDAL.findAllProjectMembers(projectId);
|
||||
};
|
||||
|
||||
const getProjectMembershipByUsername = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectId,
|
||||
username
|
||||
}: TGetProjectMembershipByUsernameDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||
|
||||
const [membership] = await projectMembershipDAL.findAllProjectMembers(projectId, { username });
|
||||
if (!membership) throw new BadRequestError({ message: `Project membership not found for user ${username}` });
|
||||
return membership;
|
||||
};
|
||||
|
||||
const addUsersToProject = async ({
|
||||
projectId,
|
||||
actorId,
|
||||
@ -510,6 +533,7 @@ export const projectMembershipServiceFactory = ({
|
||||
|
||||
return {
|
||||
getProjectMemberships,
|
||||
getProjectMembershipByUsername,
|
||||
updateProjectMembership,
|
||||
addUsersToProjectNonE2EE,
|
||||
deleteProjectMemberships,
|
||||
|
@ -9,6 +9,10 @@ export type TInviteUserToProjectDTO = {
|
||||
emails: string[];
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TGetProjectMembershipByUsernameDTO = {
|
||||
username: string;
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TUpdateProjectMembershipDTO = {
|
||||
membershipId: string;
|
||||
roles: (
|
||||
|
@ -340,7 +340,7 @@ export const projectServiceFactory = ({
|
||||
|
||||
const deletedProject = await projectDAL.transaction(async (tx) => {
|
||||
const delProject = await projectDAL.deleteById(project.id, tx);
|
||||
const projectGhostUser = await projectMembershipDAL.findProjectGhostUser(project.id).catch(() => null);
|
||||
const projectGhostUser = await projectMembershipDAL.findProjectGhostUser(project.id, tx).catch(() => null);
|
||||
|
||||
// Delete the org membership for the ghost user if it's found.
|
||||
if (projectGhostUser) {
|
||||
|
@ -608,7 +608,7 @@ export const createManySecretsRawFnFactory = ({
|
||||
secretVersionTagDAL,
|
||||
folderDAL
|
||||
}: TCreateManySecretsRawFnFactory) => {
|
||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
|
||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
|
||||
const createManySecretsRawFn = async ({
|
||||
projectId,
|
||||
environment,
|
||||
@ -706,7 +706,7 @@ export const updateManySecretsRawFnFactory = ({
|
||||
secretVersionTagDAL,
|
||||
folderDAL
|
||||
}: TUpdateManySecretsRawFnFactory) => {
|
||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
|
||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
|
||||
const updateManySecretsRawFn = async ({
|
||||
projectId,
|
||||
environment,
|
||||
|
@ -463,20 +463,37 @@ export const secretQueueFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
await syncIntegrationSecrets({
|
||||
createManySecretsRawFn,
|
||||
updateManySecretsRawFn,
|
||||
integrationDAL,
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets,
|
||||
accessId: accessId as string,
|
||||
accessToken,
|
||||
appendices: {
|
||||
prefix: metadata?.secretPrefix || "",
|
||||
suffix: metadata?.secretSuffix || ""
|
||||
}
|
||||
});
|
||||
try {
|
||||
await syncIntegrationSecrets({
|
||||
createManySecretsRawFn,
|
||||
updateManySecretsRawFn,
|
||||
integrationDAL,
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets,
|
||||
accessId: accessId as string,
|
||||
accessToken,
|
||||
appendices: {
|
||||
prefix: metadata?.secretPrefix || "",
|
||||
suffix: metadata?.secretSuffix || ""
|
||||
}
|
||||
});
|
||||
|
||||
await integrationDAL.updateById(integration.id, {
|
||||
lastSyncJobId: job.id,
|
||||
lastUsed: new Date(),
|
||||
syncMessage: "",
|
||||
isSynced: true
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
logger.info("Secret integration sync error:", err);
|
||||
await integrationDAL.updateById(integration.id, {
|
||||
lastSyncJobId: job.id,
|
||||
lastUsed: new Date(),
|
||||
syncMessage: (err as Error)?.message,
|
||||
isSynced: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Secret integration sync ended: %s", job.id);
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Create Identity Membership"
|
||||
openapi: "POST /api/v2/workspace/{projectId}/identity-memberships/{identityId}"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get Identity by ID"
|
||||
openapi: "GET /api/v2/workspace/{projectId}/identity-memberships/{identityId}"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get By Username"
|
||||
openapi: "POST /api/v1/workspace/{workspaceId}/memberships/details"
|
||||
---
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Invite Member"
|
||||
openapi: "POST /api/v2/workspace/{projectId}/memberships"
|
||||
---
|
||||
---
|
@ -83,7 +83,7 @@ access the Infisical API using the AWS Auth authentication method.
|
||||
|
||||
- Allowed Principal ARNs: A comma-separated list of trusted IAM principal ARNs that are allowed to authenticate with Infisical. The values should take one of three forms: `arn:aws:iam::123456789012:user/MyUserName`, `arn:aws:iam::123456789012:role/MyRoleName`, or `arn:aws:iam::123456789012:*`. Using a wildcard in this case allows any IAM principal in the account `123456789012` to authenticate with Infisical under the identity.
|
||||
- Allowed Account IDs: A comma-separated list of trusted AWS account IDs that are allowed to authenticate with Infisical.
|
||||
- STS Endpoint (default is `https://sts.amazonaws.com/`): The endpoint URL for the AWS STS API. This is useful for AWS GovCloud or other AWS regions that have different STS endpoints.
|
||||
- STS Endpoint (default is `https://sts.amazonaws.com/`): The endpoint URL for the AWS STS API. This value should be adjusted based on the AWS region you are operating in (e.g. `https://sts.us-east-1.amazonaws.com/`); refer to the list of regional STS endpoints [here](https://docs.aws.amazon.com/general/latest/gr/sts.html).
|
||||
- Access Token TTL (default is `2592000` equivalent to 30 days): The lifetime for an acccess token in seconds. This value will be referenced at renewal time.
|
||||
- Access Token Max TTL (default is `2592000` equivalent to 30 days): The maximum lifetime for an acccess token in seconds. This value will be referenced at renewal time.
|
||||
- Access Token Max Number of Uses (default is `0`): The maximum number of times that an access token can be used; a value of `0` implies infinite number of uses.
|
||||
@ -264,6 +264,9 @@ access the Infisical API using the AWS Auth authentication method.
|
||||
request.headers["X-Amz-Date"] = AWS.util.date.iso8601(new Date()).replace(/[:-]|\.\d{3}/g, "");
|
||||
request.body = iamRequestBody;
|
||||
request.headers["Content-Length"] = Buffer.byteLength(iamRequestBody);
|
||||
|
||||
const signer = new AWS.Signers.V4(request, "sts");
|
||||
signer.addAuthorization(AWS.config.credentials, new Date());
|
||||
````
|
||||
|
||||
#### Sample request
|
||||
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 142 KiB |
BIN
docs/images/integrations/jenkins/plugin/add-infisical-secret.png
Normal file
After Width: | Height: | Size: 98 KiB |
BIN
docs/images/integrations/jenkins/plugin/install-plugin.png
Normal file
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 154 KiB |
After Width: | Height: | Size: 96 KiB |
BIN
docs/images/integrations/jenkins/plugin/plugin-checked.png
Normal file
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 194 KiB |
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "Jenkins"
|
||||
title: "Jenkins Plugin"
|
||||
description: "How to effectively and securely manage secrets in Jenkins using Infisical"
|
||||
---
|
||||
|
||||
@ -18,144 +18,135 @@ Prerequisites:
|
||||
|
||||
<Tabs>
|
||||
|
||||
<Tab title="Machine Identity (Recommended)">
|
||||
## Add Infisical Machine Identity to Jenkins
|
||||
<Tab title="Using Plugin with Machine Identities (Recommended)">
|
||||
|
||||
## Jenkins Infisical Plugin
|
||||
|
||||
After setting up your project in Infisical and installing the Infisical CLI to the environment where your Jenkins builds will run, you will need to add the Infisical Machine Identity to Jenkins.
|
||||
This plugin adds a build wrapper to set environment variables from [Infisical](https://infisical.com). Secrets are generally masked in the build log, so you can't accidentally print them.
|
||||
|
||||
To generate a Infisical machine identity, follow the guide [here](/documentation/platform/identities/machine-identities).
|
||||
Once you have generated the token, navigate to **Manage Jenkins > Manage Credentials** in your Jenkins instance.
|
||||
## Installation
|
||||
|
||||

|
||||
To install the plugin, navigate to `Manage Jenkins -> Plugins -> Available plugins` and search for `Infisical`. Install the plugin and restart Jenkins.
|
||||
|
||||
Click on the credential store you want to store the Infisical Machine Identity in. In this case, we're using the default Jenkins global store.
|
||||

|
||||
|
||||
<Info>
|
||||
Each of your projects will have a different `INFISICAL_TOKEN`.
|
||||
As a result, it may make sense to spread these out into separate credential domains depending on your use case.
|
||||
</Info>
|
||||
## Infisical Authentication
|
||||
|
||||

|
||||
Authenticating with Infisical is done through the use of [Machine Identities](https://infisical.com/docs/documentation/platform/identities/machine-identities).
|
||||
Currently the Jenkins plugin only supports [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth) for authentication. More methods will be added soon.
|
||||
|
||||
Now, click Add Credentials.
|
||||
|
||||

|
||||
|
||||
Choose **Secret text** for the **Kind** option from the dropdown list and enter the Infisical Service Token in the **Secret** field.
|
||||
Although the **ID** can be any value, we'll set it to `infisical-machine-identity-client-id` and `infisical-machine-identity-client-secret` for the sake of this guide.
|
||||
The description is optional and can be any text you prefer.
|
||||
### How does Universal Auth work?
|
||||
To use Universal Auth, you'll need to create a new Credential _(Infisical Universal Auth Credential)_. The credential should contain your Universal Auth client ID, and your Universal Auth client secret.
|
||||
Please [read more here](https://infisical.com/docs/documentation/platform/identities/universal-auth) on how to setup a Machine Identity to use universal auth.
|
||||
|
||||
|
||||

|
||||

|
||||
### Creating a Universal Auth credential
|
||||
|
||||
When you're done, you should see two credentials similar to the one below:
|
||||
Creating a universal auth credential inside Jenkins is very straight forward.
|
||||
|
||||

|
||||
Simply navigate to<br/>
|
||||
`Dashboard -> Manage Jenkins -> Credentials -> System -> Global credentials (unrestricted)`.
|
||||
|
||||
Press the `Add Credentials` button and select `Infisical Universal Auth Credential` in the `Kind` field.
|
||||
|
||||
## Use Infisical in a Freestyle Project
|
||||
The `ID` and `Description` field doesn't matter much in this case, as they won't be read anywhere. The description field will be displayed as the credential name during the plugin configuration.
|
||||
|
||||
To fetch secrets with Infisical in a Freestyle Project job, you'll need to expose the credential you created above as an environment variable to the Infisical CLI.
|
||||
To do so, first click **New Item** from the dashboard navigation sidebar:
|
||||
|
||||

|
||||
|
||||
Enter the name of the job, choose the **Freestyle Project** option, and click **OK**.
|
||||
|
||||

|
||||
|
||||
Scroll down to the **Build Environment** section and enable the **Use secret text(s) or file(s)** option. Then click **Add** under the **Bindings** section and choose **Secret text** from the dropdown menu.
|
||||
|
||||

|
||||
|
||||
Enter `INFISICAL_MACHINE_IDENTITY_CLIENT_ID` in the **Variable** field for the client ID, and `INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET` for the client secret. Then click the **Specific credentials** option from the Credentials section and select the credentials you created earlier.
|
||||
In this case, we saved it as `Infisical Machine Identity Client ID` and `Infisical Machine Identity Client Secret` so we'll choose those from the dropdown menu.
|
||||
|
||||
Make sure to add bindings for both the client ID and the client secret.
|
||||
|
||||

|
||||
|
||||
Scroll down to the **Build** section and choose **Execute shell** from the **Add build step** menu.
|
||||
|
||||

|
||||
|
||||
In the command field, you can now use the Infisical CLI to fetch secrets.
|
||||
The example command below will print the secrets using the service token passed as a credential. When done, click **Save**.
|
||||
|
||||
```bash
|
||||
export INFISICAL_TOKEN=$(infisical login --method=universal-auth --client-id=$INFISICAL_MACHINE_IDENTITY_CLIENT_ID --client-secret=$INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET --silent --plain)
|
||||
infisical secrets --env dev --projectId=<your-project-id>
|
||||
```
|
||||
|
||||

|
||||
|
||||
Finally, click **Build Now** from the navigation sidebar to run your new job.
|
||||
|
||||
<Info>
|
||||
Running into issues? Join Infisical's [community Slack](https://infisical.com/slack) for quick support.
|
||||
</Info>
|
||||

|
||||
|
||||
|
||||
|
||||
## Use Infisical in a Jenkins Pipeline
|
||||
## Plugin Usage
|
||||
### Configuration
|
||||
|
||||
To fetch secrets using Infisical in a Pipeline job, you'll need to expose the Jenkins credential you created above as an environment variable.
|
||||
To do so, click **New Item** from the dashboard navigation sidebar:
|
||||
Configuration takes place on a job-level basis.
|
||||
|
||||

|
||||
Inside your job, you simply tick the `Infisical Plugin` checkbox under "Build Environment". After enabling the plugin, you'll see a new section appear where you'll have to configure the plugin.
|
||||
|
||||
Enter the name of the job, choose the **Pipeline** option, and click OK.
|
||||

|
||||
|
||||

|
||||
You'll be prompted with 4 options to fill:
|
||||
* Infisical URL
|
||||
* This defaults to https://app.infisical.com. This field is only relevant if you're running a managed or self-hosted instance. If you are using Infisical Cloud, leave this as-is, otherwise enter the URL of your Infisical instance.
|
||||
* Infisical Credential
|
||||
* This is where you select your Infisical credential to use for authentication. In the step above [Creating a Universal Auth credential](#creating-a-universal-auth-credential), you can read on how to configure the credential. Simply select the credential you have created for this field.
|
||||
* Infisical Project Slug
|
||||
* This is the slug of the project you wish to fetch secrets from. You can find this in your project settings on Infisical by clicking "Copy project slug".
|
||||
* Environment Slug
|
||||
* This is the slug of the environment to fetch secrets from. In most cases it's either `dev`, `staging`, or `prod`. You can however create custom environments in Infisical. If you are using custom environments, you need to enter the slug of the custom environment you wish to fetch secrets from.
|
||||
|
||||
That's it! Now you're ready to select which secrets you want to fetch into Jenkins.
|
||||
By clicking the `Add an Infisical secret` in the Jenkins UI like seen in the screenshot below.
|
||||
|
||||
Scroll down to the **Pipeline** section, paste the following into the **Script** field, and click **Save**.
|
||||

|
||||
|
||||
```
|
||||
pipeline {
|
||||
agent any
|
||||
You need to select which secrets that should be pulled into Jenkins.
|
||||
You start by specifying a [folder path from Infisical](https://infisical.com/docs/documentation/platform/folder#comparing-folders). The root path is simply `/`. You also need to select wether or not you want to [include imports](https://infisical.com/docs/documentation/platform/secret-reference#secret-imports). Now you can add secrets the secret keys that you want to pull from Infisical into Jenkins. If you want to add multiple secrets, press the "Add key/value pair".
|
||||
|
||||
environment {
|
||||
MACHINE_IDENTITY_CLIENT_ID = credentials('infisical-machine-identity-client-id')
|
||||
MACHINE_IDENTITY_CLIENT_SECRET = credentials('infisical-machine-identity-client-secret')
|
||||
If you wish to pull secrets from multiple paths, you can press the "Add an Infisical secret" button at the bottom, and configure a new set of secrets to pull.
|
||||
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Run Infisical') {
|
||||
steps {
|
||||
sh("export INFISICAL_TOKEN=$(infisical login --method=universal-auth --client-id=${MACHINE_IDENTITY_CLIENT_ID} --client-secret=${MACHINE_IDENTITY_CLIENT_SECRET} --silent --plain)")
|
||||
sh("infisical secrets --env=dev --path=/ --projectId=<your-project-id>")
|
||||
## Pipeline usage
|
||||
|
||||
// doesn't work
|
||||
// sh("docker run --rm test-container infisical secrets --projectId=<your-project-id>")
|
||||
|
||||
// works
|
||||
// sh("docker run -e INFISICAL_TOKEN=${INFISICAL_TOKEN} --rm test-container infisical secrets --env=dev --path=/ --projectId=<your-project-id>")
|
||||
### Generating pipeline block
|
||||
|
||||
// doesn't work
|
||||
// sh("docker-compose up -d")
|
||||
Using the Infisical Plugin in a Jenkins pipeline is very straight forward. To generate a block to use the Infisical Plugin in a Pipeline, simply to go `{JENKINS_URL}/jenkins/job/{JOB_ID}/pipeline-syntax/`.
|
||||
|
||||
You can find a direct link on the Pipeline configuration page in the very bottom of the page, see image below.
|
||||
|
||||

|
||||
|
||||
On the Snippet Generator page, simply configure the Infisical Plugin like it's documented in the [Configuration documentation](#configuration) step.
|
||||
|
||||
Once you have filled out the configuration, press `Generate Pipeline Script`, and it will generate a block you can use in your pipeline.
|
||||
|
||||

|
||||
|
||||
### Using Infisical in a Pipeline
|
||||
|
||||
Using the generated block in a pipeline is very straight forward. There's a few approaches on how to implement the block in a Pipeline script.
|
||||
Here's an example of using the generated block in a pipeline script. Make sure to replace the placeholder values with your own values.
|
||||
|
||||
The script is formatted for clarity. All these fields will be pre-filled for you if you use the `Snippet Generator` like described in the [step above](#generating-pipeline-block).
|
||||
```groovy
|
||||
node {
|
||||
withInfisical(
|
||||
configuration: [
|
||||
infisicalCredentialId: 'YOUR_CREDENTIAL_ID',
|
||||
infisicalEnvironmentSlug: 'PROJECT_ENV_SLUG',
|
||||
infisicalProjectSlug: 'PROJECT_SLUG',
|
||||
infisicalUrl: 'https://app.infisical.com' // Change this to your Infisical instance URL if you aren't using Infisical Cloud.
|
||||
],
|
||||
infisicalSecrets: [
|
||||
infisicalSecret(
|
||||
includeImports: true,
|
||||
path: '/',
|
||||
secretValues: [
|
||||
[infisicalKey: 'DATABASE_URL'],
|
||||
[infisicalKey: "API_URL"],
|
||||
[infisicalKey: 'THIS_KEY_MIGHT_NOT_EXIST', isRequired: false],
|
||||
]
|
||||
)
|
||||
]
|
||||
) {
|
||||
// Code runs here
|
||||
sh "printenv"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
// works
|
||||
// sh("INFISICAL_TOKEN=${INFISICAL_TOKEN} docker-compose up -d")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Service Token (Deprecated)">
|
||||
<Tab title="Using CLI with Service Tokens (Deprecated)">
|
||||
## Add Infisical Service Token to Jenkins
|
||||
|
||||
<Warning>
|
||||
<Warning>
|
||||
Service tokens are being deprecated in favor of [machine identities](/documentation/platform/identities/machine-identities).
|
||||
They will be removed in the future in accordance with the deprecation notice and timeline stated [here](https://infisical.com/blog/deprecating-api-keys).
|
||||
|
||||
Service tokens are being deprecated in favor of [machine identities](/documentation/platform/identities/machine-identities).
|
||||
|
||||
They will be removed in the future in accordance with the deprecation notice and timeline stated [here](https://infisical.com/blog/deprecating-api-keys).
|
||||
|
||||
</Warning>
|
||||
**Please use our Jenkins Plugin instead!**
|
||||
</Warning>
|
||||
|
||||
After setting up your project in Infisical and installing the Infisical CLI to the environment where your Jenkins builds will run, you will need to add the Infisical Service Token to Jenkins.
|
||||
|
||||
|
@ -72,6 +72,9 @@ Prerequisites:
|
||||
<ParamField path="AWS Region" type="string" required>
|
||||
The region that you want to integrate with in AWS Secrets Manager.
|
||||
</ParamField>
|
||||
<ParamField path="Mapping Behavior" type="string" required>
|
||||
How you want the integration to map the secrets. The selected value could be either one to one or one to many.
|
||||
</ParamField>
|
||||
<ParamField path="AWS SM Secret Name" type="string" required>
|
||||
The secret name/path in AWS into which you want to sync the secrets from Infisical.
|
||||
</ParamField>
|
||||
|
@ -32,7 +32,10 @@
|
||||
"thumbsRating": true
|
||||
},
|
||||
"api": {
|
||||
"baseUrl": ["https://app.infisical.com", "http://localhost:8080"]
|
||||
"baseUrl": [
|
||||
"https://app.infisical.com",
|
||||
"http://localhost:8080"
|
||||
]
|
||||
},
|
||||
"topbarLinks": [
|
||||
{
|
||||
@ -73,7 +76,9 @@
|
||||
"documentation/getting-started/introduction",
|
||||
{
|
||||
"group": "Quickstart",
|
||||
"pages": ["documentation/guides/local-development"]
|
||||
"pages": [
|
||||
"documentation/guides/local-development"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Guides",
|
||||
@ -214,7 +219,9 @@
|
||||
},
|
||||
{
|
||||
"group": "Reference architectures",
|
||||
"pages": ["self-hosting/reference-architectures/aws-ecs"]
|
||||
"pages": [
|
||||
"self-hosting/reference-architectures/aws-ecs"
|
||||
]
|
||||
},
|
||||
"self-hosting/ee",
|
||||
"self-hosting/faq"
|
||||
@ -370,11 +377,15 @@
|
||||
},
|
||||
{
|
||||
"group": "Build Tool Integrations",
|
||||
"pages": ["integrations/build-tools/gradle"]
|
||||
"pages": [
|
||||
"integrations/build-tools/gradle"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "",
|
||||
"pages": ["sdks/overview"]
|
||||
"pages": [
|
||||
"sdks/overview"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "SDK's",
|
||||
@ -392,7 +403,9 @@
|
||||
"api-reference/overview/authentication",
|
||||
{
|
||||
"group": "Examples",
|
||||
"pages": ["api-reference/overview/examples/integration"]
|
||||
"pages": [
|
||||
"api-reference/overview/examples/integration"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -438,17 +451,30 @@
|
||||
"api-reference/endpoints/workspaces/delete-workspace",
|
||||
"api-reference/endpoints/workspaces/get-workspace",
|
||||
"api-reference/endpoints/workspaces/update-workspace",
|
||||
"api-reference/endpoints/workspaces/invite-member-to-workspace",
|
||||
"api-reference/endpoints/workspaces/remove-member-from-workspace",
|
||||
"api-reference/endpoints/workspaces/memberships",
|
||||
"api-reference/endpoints/workspaces/update-membership",
|
||||
"api-reference/endpoints/workspaces/list-identity-memberships",
|
||||
"api-reference/endpoints/workspaces/update-identity-membership",
|
||||
"api-reference/endpoints/workspaces/delete-identity-membership",
|
||||
"api-reference/endpoints/workspaces/secret-snapshots",
|
||||
"api-reference/endpoints/workspaces/rollback-snapshot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Project Users",
|
||||
"pages": [
|
||||
"api-reference/endpoints/project-users/invite-member-to-workspace",
|
||||
"api-reference/endpoints/project-users/remove-member-from-workspace",
|
||||
"api-reference/endpoints/project-users/memberships",
|
||||
"api-reference/endpoints/project-users/get-by-username",
|
||||
"api-reference/endpoints/project-users/update-membership"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Project Identities",
|
||||
"pages": [
|
||||
"api-reference/endpoints/project-identities/add-identity-membership",
|
||||
"api-reference/endpoints/project-identities/list-identity-memberships",
|
||||
"api-reference/endpoints/project-identities/get-by-id",
|
||||
"api-reference/endpoints/project-identities/update-identity-membership",
|
||||
"api-reference/endpoints/project-identities/delete-identity-membership"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Environments",
|
||||
"pages": [
|
||||
@ -525,11 +551,15 @@
|
||||
},
|
||||
{
|
||||
"group": "Service Tokens",
|
||||
"pages": ["api-reference/endpoints/service-tokens/get"]
|
||||
"pages": [
|
||||
"api-reference/endpoints/service-tokens/get"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Audit Logs",
|
||||
"pages": ["api-reference/endpoints/audit-logs/export-audit-log"]
|
||||
"pages": [
|
||||
"api-reference/endpoints/audit-logs/export-audit-log"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -545,7 +575,9 @@
|
||||
},
|
||||
{
|
||||
"group": "",
|
||||
"pages": ["changelog/overview"]
|
||||
"pages": [
|
||||
"changelog/overview"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Contributing",
|
||||
@ -569,7 +601,9 @@
|
||||
},
|
||||
{
|
||||
"group": "Contributing to SDK",
|
||||
"pages": ["contributing/sdk/developing"]
|
||||
"pages": [
|
||||
"contributing/sdk/developing"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { workspaceKeys } from "../workspace/queries";
|
||||
@ -63,6 +64,7 @@ export const useCreateIntegration = () => {
|
||||
secretSuffix?: string;
|
||||
initialSyncBehavior?: string;
|
||||
shouldAutoRedeploy?: boolean;
|
||||
mappingBehavior?: string;
|
||||
secretAWSTag?: {
|
||||
key: string;
|
||||
value: string;
|
||||
@ -110,3 +112,15 @@ export const useDeleteIntegration = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useSyncIntegration = () => {
|
||||
return useMutation<{}, {}, { id: string; workspaceId: string; lastUsed: string }>({
|
||||
mutationFn: ({ id }) => apiRequest.post(`/api/v1/integration/${id}/sync`),
|
||||
onSuccess: () => {
|
||||
createNotification({
|
||||
text: "Successfully triggered manual sync",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -29,10 +29,14 @@ export type TIntegration = {
|
||||
secretPath: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastUsed?: string;
|
||||
isSynced?: boolean;
|
||||
syncMessage?: string;
|
||||
__v: number;
|
||||
metadata?: {
|
||||
secretSuffix?: string;
|
||||
syncBehavior?: IntegrationSyncBehavior;
|
||||
mappingBehavior?: IntegrationMappingBehavior;
|
||||
scope: string;
|
||||
org: string;
|
||||
project: string;
|
||||
@ -45,3 +49,8 @@ export enum IntegrationSyncBehavior {
|
||||
PREFER_TARGET = "prefer-target",
|
||||
PREFER_SOURCE = "prefer-source"
|
||||
}
|
||||
|
||||
export enum IntegrationMappingBehavior {
|
||||
ONE_TO_ONE = "one-to-one",
|
||||
MANY_TO_ONE = "many-to-one"
|
||||
}
|
||||
|
@ -198,7 +198,8 @@ export const useGetWorkspaceIntegrations = (workspaceId: string) =>
|
||||
useQuery({
|
||||
queryKey: workspaceKeys.getWorkspaceIntegrations(workspaceId),
|
||||
queryFn: () => fetchWorkspaceIntegrations(workspaceId),
|
||||
enabled: Boolean(workspaceId)
|
||||
enabled: Boolean(workspaceId),
|
||||
refetchInterval: 4000
|
||||
});
|
||||
|
||||
export const createWorkspace = ({
|
||||
|
@ -15,6 +15,7 @@ import queryString from "query-string";
|
||||
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries";
|
||||
import { IntegrationMappingBehavior } from "@app/hooks/api/integrations/types";
|
||||
|
||||
import {
|
||||
Button,
|
||||
@ -70,6 +71,17 @@ const awsRegions = [
|
||||
{ name: "AWS GovCloud (US-West)", slug: "us-gov-west-1" }
|
||||
];
|
||||
|
||||
const mappingBehaviors = [
|
||||
{
|
||||
label: "Many to One (All Infisical secrets will be mapped to a single AWS secret)",
|
||||
value: IntegrationMappingBehavior.MANY_TO_ONE
|
||||
},
|
||||
{
|
||||
label: "One to One - (Each Infisical secret will be mapped to its own AWS secret)",
|
||||
value: IntegrationMappingBehavior.ONE_TO_ONE
|
||||
}
|
||||
];
|
||||
|
||||
export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
@ -84,6 +96,9 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
const [selectedAWSRegion, setSelectedAWSRegion] = useState("");
|
||||
const [selectedMappingBehavior, setSelectedMappingBehavior] = useState(
|
||||
IntegrationMappingBehavior.MANY_TO_ONE
|
||||
);
|
||||
const [targetSecretName, setTargetSecretName] = useState("");
|
||||
const [targetSecretNameErrorText, setTargetSecretNameErrorText] = useState("");
|
||||
const [tagKey, setTagKey] = useState("");
|
||||
@ -116,7 +131,14 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
if (targetSecretName.trim() === "") {
|
||||
if (!selectedMappingBehavior) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
selectedMappingBehavior === IntegrationMappingBehavior.MANY_TO_ONE &&
|
||||
targetSecretName.trim() === ""
|
||||
) {
|
||||
setTargetSecretName("Secret name cannot be blank");
|
||||
return;
|
||||
}
|
||||
@ -143,7 +165,8 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
]
|
||||
}
|
||||
: {}),
|
||||
...(kmsKeyId && { kmsKeyId })
|
||||
...(kmsKeyId && { kmsKeyId }),
|
||||
mappingBehavior: selectedMappingBehavior
|
||||
}
|
||||
});
|
||||
|
||||
@ -248,19 +271,40 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="AWS SM Secret Name"
|
||||
errorText={targetSecretNameErrorText}
|
||||
isError={targetSecretNameErrorText !== "" ?? false}
|
||||
>
|
||||
<Input
|
||||
placeholder={`${workspace.name
|
||||
.toLowerCase()
|
||||
.replace(/ /g, "-")}/${selectedSourceEnvironment}`}
|
||||
value={targetSecretName}
|
||||
onChange={(e) => setTargetSecretName(e.target.value)}
|
||||
/>
|
||||
<FormControl label="Mapping Behavior">
|
||||
<Select
|
||||
value={selectedMappingBehavior}
|
||||
onValueChange={(val) => {
|
||||
setSelectedMappingBehavior(val as IntegrationMappingBehavior);
|
||||
}}
|
||||
className="w-full border border-mineshaft-500 text-left"
|
||||
>
|
||||
{mappingBehaviors.map((option) => (
|
||||
<SelectItem
|
||||
value={option.value}
|
||||
className="text-left"
|
||||
key={`aws-environment-${option.value}`}
|
||||
>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{selectedMappingBehavior === IntegrationMappingBehavior.MANY_TO_ONE && (
|
||||
<FormControl
|
||||
label="AWS SM Secret Name"
|
||||
errorText={targetSecretNameErrorText}
|
||||
isError={targetSecretNameErrorText !== "" ?? false}
|
||||
>
|
||||
<Input
|
||||
placeholder={`${workspace.name
|
||||
.toLowerCase()
|
||||
.replace(/ /g, "-")}/${selectedSourceEnvironment}`}
|
||||
value={targetSecretName}
|
||||
onChange={(e) => setTargetSecretName(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.Options}>
|
||||
|
@ -121,6 +121,14 @@ export default function LoginPage() {
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
// Case: User has no organizations.
|
||||
// This can happen if the user was previously a member, but the organization was deleted or the user was removed.
|
||||
useEffect(() => {
|
||||
if (!organizations.isLoading && organizations.data?.length === 0) {
|
||||
router.push("/org/none");
|
||||
}
|
||||
}, [organizations.isLoading, organizations.data]);
|
||||
|
||||
if (userLoading || !user) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
@ -1,21 +1,27 @@
|
||||
import Link from "next/link";
|
||||
import { faArrowRight, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCalendarCheck } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faArrowRight, faRefresh, faWarning, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
EmptyState,
|
||||
FormLabel,
|
||||
IconButton,
|
||||
Skeleton,
|
||||
Tag,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useSyncIntegration } from "@app/hooks/api/integrations/queries";
|
||||
import { IntegrationMappingBehavior } from "@app/hooks/api/integrations/types";
|
||||
import { TIntegration } from "@app/hooks/api/types";
|
||||
|
||||
type Props = {
|
||||
@ -39,6 +45,8 @@ export const IntegrationsSection = ({
|
||||
"deleteConfirmation"
|
||||
] as const);
|
||||
|
||||
const { mutate: syncIntegration } = useSyncIntegration();
|
||||
|
||||
return (
|
||||
<div className="mb-8">
|
||||
<div className="mx-4 mb-4 mt-6 flex flex-col items-start justify-between px-2 text-xl">
|
||||
@ -74,7 +82,7 @@ export const IntegrationsSection = ({
|
||||
</div>
|
||||
)}
|
||||
{!isLoading && isBotActive && (
|
||||
<div className="flex flex-col min-w-max space-y-4 p-6 pt-0">
|
||||
<div className="flex min-w-max flex-col space-y-4 p-6 pt-0">
|
||||
{integrations?.map((integration) => (
|
||||
<div
|
||||
className="max-w-8xl flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-3"
|
||||
@ -124,29 +132,35 @@ export const IntegrationsSection = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel
|
||||
label={
|
||||
(integration.integration === "qovery" && integration?.scope) ||
|
||||
(integration.integration === "aws-secret-manager" && "Secret") ||
|
||||
(integration.integration === "aws-parameter-store" && "Path") ||
|
||||
(integration?.integration === "terraform-cloud" && "Project") ||
|
||||
(integration?.scope === "github-org" && "Organization") ||
|
||||
(["github-repo", "github-env"].includes(integration?.scope as string) &&
|
||||
"Repository") ||
|
||||
"App"
|
||||
}
|
||||
/>
|
||||
<div className="min-w-[8rem] max-w-[12rem] overflow-scroll no-scrollbar no-scrollbar::-webkit-scrollbar whitespace-nowrap rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{(integration.integration === "hashicorp-vault" &&
|
||||
`${integration.app} - path: ${integration.path}`) ||
|
||||
(integration.scope === "github-org" && `${integration.owner}`) ||
|
||||
(integration.integration === "aws-parameter-store" && `${integration.path}`) ||
|
||||
(integration.scope?.startsWith("github-") &&
|
||||
`${integration.owner}/${integration.app}`) ||
|
||||
integration.app}
|
||||
{!(
|
||||
integration.integration === "aws-secret-manager" &&
|
||||
integration.metadata?.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE
|
||||
) && (
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel
|
||||
label={
|
||||
(integration.integration === "qovery" && integration?.scope) ||
|
||||
(integration.integration === "aws-secret-manager" && "Secret") ||
|
||||
(integration.integration === "aws-parameter-store" && "Path") ||
|
||||
(integration?.integration === "terraform-cloud" && "Project") ||
|
||||
(integration?.scope === "github-org" && "Organization") ||
|
||||
(["github-repo", "github-env"].includes(integration?.scope as string) &&
|
||||
"Repository") ||
|
||||
"App"
|
||||
}
|
||||
/>
|
||||
<div className="no-scrollbar::-webkit-scrollbar min-w-[8rem] max-w-[12rem] overflow-scroll whitespace-nowrap rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200 no-scrollbar">
|
||||
{(integration.integration === "hashicorp-vault" &&
|
||||
`${integration.app} - path: ${integration.path}`) ||
|
||||
(integration.scope === "github-org" && `${integration.owner}`) ||
|
||||
(integration.integration === "aws-parameter-store" &&
|
||||
`${integration.path}`) ||
|
||||
(integration.scope?.startsWith("github-") &&
|
||||
`${integration.owner}/${integration.app}`) ||
|
||||
integration.app}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(integration.integration === "vercel" ||
|
||||
integration.integration === "netlify" ||
|
||||
integration.integration === "railway" ||
|
||||
@ -187,13 +201,70 @@ export const IntegrationsSection = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex cursor-default items-center">
|
||||
<div className="mt-[1.5rem] flex cursor-default">
|
||||
{integration.isSynced != null && integration.lastUsed != null && (
|
||||
<Tag
|
||||
key={integration.id}
|
||||
className={integration.isSynced ? "bg-green-800" : "bg-red/80"}
|
||||
>
|
||||
<Tooltip
|
||||
center
|
||||
className="max-w-xs whitespace-normal break-words"
|
||||
content={
|
||||
<div className="flex max-h-[10rem] flex-col overflow-auto ">
|
||||
<div className="flex self-start">
|
||||
<FontAwesomeIcon
|
||||
icon={faCalendarCheck}
|
||||
className="pt-0.5 pr-2 text-sm"
|
||||
/>
|
||||
<div className="text-sm">Last sync</div>
|
||||
</div>
|
||||
<div className="pl-5 text-left text-xs">
|
||||
{format(new Date(integration.lastUsed), "yyyy-MM-dd, hh:mm aaa")}
|
||||
</div>
|
||||
{!integration.isSynced && (
|
||||
<>
|
||||
<div className="mt-2 flex self-start">
|
||||
<FontAwesomeIcon icon={faXmark} className="pt-1 pr-2 text-sm" />
|
||||
<div className="text-sm">Fail reason</div>
|
||||
</div>
|
||||
<div className="pl-5 text-left text-xs">
|
||||
{integration.syncMessage}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="flex items-center space-x-2 text-white">
|
||||
<div>Sync Status</div>
|
||||
{!integration.isSynced && <FontAwesomeIcon icon={faWarning} />}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Tag>
|
||||
)}
|
||||
<div className="mr-1 flex items-end opacity-80 duration-200 hover:opacity-100">
|
||||
<Tooltip className="text-center" content="Manually sync integration secrets">
|
||||
<Button
|
||||
onClick={() =>
|
||||
syncIntegration({
|
||||
workspaceId,
|
||||
id: integration.id,
|
||||
lastUsed: integration.lastUsed as string
|
||||
})
|
||||
}
|
||||
className="max-w-[2.5rem] border-none bg-mineshaft-500"
|
||||
>
|
||||
<FontAwesomeIcon icon={faRefresh} className="px-1 text-bunker-200" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={ProjectPermissionSub.Integrations}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<div className="ml-2 opacity-80 duration-200 hover:opacity-100">
|
||||
<div className="flex items-end opacity-80 duration-200 hover:opacity-100">
|
||||
<Tooltip content="Remove Integration">
|
||||
<IconButton
|
||||
onClick={() => handlePopUpOpen("deleteConfirmation", integration)}
|
||||
@ -217,7 +288,9 @@ export const IntegrationsSection = ({
|
||||
isOpen={popUp.deleteConfirmation.isOpen}
|
||||
title={`Are you sure want to remove ${
|
||||
(popUp?.deleteConfirmation.data as TIntegration)?.integration || " "
|
||||
} integration for ${(popUp?.deleteConfirmation.data as TIntegration)?.app || "this project"}?`}
|
||||
} integration for ${
|
||||
(popUp?.deleteConfirmation.data as TIntegration)?.app || "this project"
|
||||
}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)}
|
||||
deleteKey={
|
||||
(popUp?.deleteConfirmation?.data as TIntegration)?.app ||
|
||||
|
@ -152,7 +152,7 @@ export const SecretDropzone = ({
|
||||
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
setDragActive.off();
|
||||
parseFile(e.dataTransfer.files[0]);
|
||||
parseFile(e.dataTransfer.files[0], e.dataTransfer.files[0].type === "application/json");
|
||||
};
|
||||
|
||||
const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
|
4
helm-charts/infisical/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
charts/
|
||||
node_modules/
|
||||
package*.json
|
||||
*.bak
|
@ -1,23 +0,0 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
@ -1,15 +0,0 @@
|
||||
dependencies:
|
||||
- name: mongodb
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
version: 13.9.4
|
||||
- name: mailhog
|
||||
repository: https://codecentric.github.io/helm-charts
|
||||
version: 5.2.3
|
||||
- name: redis
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
version: 17.15.0
|
||||
- name: ingress-nginx
|
||||
repository: https://kubernetes.github.io/ingress-nginx
|
||||
version: 4.0.13
|
||||
digest: sha256:1762132c45000bb6d410c6da2291ac5c65f91331550a473b370374ba042d0744
|
||||
generated: "2023-08-10T15:03:12.219788-04:00"
|
@ -1,34 +0,0 @@
|
||||
apiVersion: v2
|
||||
name: infisical
|
||||
description: A helm chart for a full Infisical application
|
||||
|
||||
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: 0.4.2
|
||||
|
||||
# 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
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "1.17.0"
|
||||
|
||||
dependencies:
|
||||
- name: mongodb
|
||||
version: "~13.9.1"
|
||||
repository: "https://charts.bitnami.com/bitnami"
|
||||
condition: mongodb.enabled
|
||||
- name: mailhog
|
||||
version: "~5.2.3"
|
||||
repository: "https://codecentric.github.io/helm-charts"
|
||||
condition: mailhog.enabled
|
||||
- name: redis
|
||||
version: 17.15.0
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
condition: redis.enabled
|
||||
- name: ingress-nginx
|
||||
version: 4.0.13
|
||||
repository: https://kubernetes.github.io/ingress-nginx
|
||||
condition: ingress.nginx.enabled
|
@ -1,328 +0,0 @@
|
||||
# Infisical Helm Chart
|
||||
|
||||
This is the Infisical application Helm chart. This chart includes the following :
|
||||
|
||||
| Service | Description |
|
||||
| ---------- | ----------------------------------- |
|
||||
| `backend` | Infisical's API |
|
||||
| `mongodb` | Infisical's database |
|
||||
| `redis` | Infisical's cache service |
|
||||
| `mailhog` | Infisical's development SMTP server |
|
||||
|
||||
## Installation
|
||||
|
||||
To install the chart, run the following :
|
||||
|
||||
```sh
|
||||
# Add the Infisical repository
|
||||
helm repo add infisical 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/' && helm repo update
|
||||
|
||||
# Install Infisical (with default values)
|
||||
helm upgrade --install --atomic \
|
||||
-n infisical-dev --create-namespace \
|
||||
infisical infisical/infisical
|
||||
|
||||
# Install Infisical (with custom inline values, replace with your own values)
|
||||
helm upgrade --install --atomic \
|
||||
-n infisical-dev --create-namespace \
|
||||
--set mongodb.enabled=false \
|
||||
--set mongodbConnection.externalMongoDBConnectionString="mongodb://<user>:<pass>@<host>:<port>/<database-name>" \
|
||||
infisical infisical/infisical
|
||||
|
||||
# Install Infisical (with custom values file, replace with your own values file)
|
||||
helm upgrade --install --atomic \
|
||||
-n infisical-dev --create-namespace \
|
||||
-f custom-values.yaml \
|
||||
infisical infisical/infisical
|
||||
```
|
||||
|
||||
### Backup up encryption keys
|
||||
|
||||
If you did not explicitly set required environment variables, this helm chart will auto-generated them by default. It's recommended to save these credentials somewhere safe. Run the following command in your cluster where Infisical chart is installed.
|
||||
|
||||
This command requires [`jq`](https://stedolan.github.io/jq/download/)
|
||||
|
||||
```sh
|
||||
# export secrets to a given file (requires jq)
|
||||
kubectl get secrets -n <namespace> <secret-name> \
|
||||
-o json | jq '.data | map_values(@base64d)' > \
|
||||
<dest-filename>.bak
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
### Common parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ------------------ | ------------------------- | ----- |
|
||||
| `nameOverride` | Override release name | `""` |
|
||||
| `fullnameOverride` | Override release fullname | `""` |
|
||||
|
||||
### Infisical backend parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
|
||||
| `backend.enabled` | Enable backend | `true` |
|
||||
| `backend.name` | Backend name | `backend` |
|
||||
| `backend.fullnameOverride` | Backend fullnameOverride | `""` |
|
||||
| `backend.podAnnotations` | Backend pod annotations | `{}` |
|
||||
| `backend.deploymentAnnotations` | Backend deployment annotations | `{}` |
|
||||
| `backend.replicaCount` | Backend replica count | `2` |
|
||||
| `backend.image.repository` | Backend image repository | `infisical/infisical` |
|
||||
| `backend.image.tag` | Backend image tag | `latest` |
|
||||
| `backend.image.pullPolicy` | Backend image pullPolicy | `IfNotPresent` |
|
||||
| `backend.affinity` | Backend pod affinity | `{}` |
|
||||
| `backend.kubeSecretRef` | Backend secret resource reference name (containing required [backend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars)) | `""` |
|
||||
| `backend.service.annotations` | Backend service annotations | `{}` |
|
||||
| `backend.service.type` | Backend service type | `ClusterIP` |
|
||||
| `backend.service.nodePort` | Backend service nodePort (used if above type is `NodePort`) | `""` |
|
||||
| `backendEnvironmentVariables.ENCRYPTION_KEY` | **Required** Backend encryption key (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
|
||||
| `backendEnvironmentVariables.JWT_SIGNUP_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
|
||||
| `backendEnvironmentVariables.JWT_REFRESH_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
|
||||
| `backendEnvironmentVariables.JWT_AUTH_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
|
||||
| `backendEnvironmentVariables.JWT_SERVICE_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
|
||||
| `backendEnvironmentVariables.JWT_MFA_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
|
||||
| `backendEnvironmentVariables.JWT_PROVIDER_AUTH_SECRET` | **Required** Secrets to sign JWT OAuth tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
|
||||
| `backendEnvironmentVariables.SMTP_HOST` | **Required** Hostname to connect to for establishing SMTP connections | `""` |
|
||||
| `backendEnvironmentVariables.SMTP_PORT` | Port to connect to for establishing SMTP connections | `587` |
|
||||
| `backendEnvironmentVariables.SMTP_SECURE` | If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported | `false` |
|
||||
| `backendEnvironmentVariables.SMTP_FROM_NAME` | Name label to be used in From field (e.g. Infisical) | `Infisical` |
|
||||
| `backendEnvironmentVariables.SMTP_FROM_ADDRESS` | **Required** Email address to be used for sending emails (e.g. dev@infisical.com) | `""` |
|
||||
| `backendEnvironmentVariables.SMTP_USERNAME` | **Required** Credential to connect to host (e.g. team@infisical.com) | `""` |
|
||||
| `backendEnvironmentVariables.SMTP_PASSWORD` | **Required** Credential to connect to host | `""` |
|
||||
| `backendEnvironmentVariables.SITE_URL` | Absolute URL including the protocol (e.g. https://app.infisical.com) | `infisical.local` |
|
||||
| `backendEnvironmentVariables.INVITE_ONLY_SIGNUP` | To disable account creation from the login page (invites only) | `false` |
|
||||
| `backendEnvironmentVariables.MONGO_URL` | MongoDB connection string (external or internal)</br>Leave it empty for auto-generated connection string | `""` |
|
||||
| `backendEnvironmentVariables.REDIS_URL` | | `redis://redis-master:6379` |
|
||||
|
||||
### MongoDB(®) parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
|
||||
| `mongodb.enabled` | Enable MongoDB(®) | `true` |
|
||||
| `mongodb.name` | Name used to build variables (deprecated) | `mongodb` |
|
||||
| `mongodb.fullnameOverride` | Fullname override | `mongodb` |
|
||||
| `mongodb.nameOverride` | Name override | `mongodb` |
|
||||
| `mongodb.podAnnotations` | Pod annotations | `{}` |
|
||||
| `mongodb.useStatefulSet` | Set to true to use a StatefulSet instead of a Deployment (only when `architecture: standalone`) | `true` |
|
||||
| `mongodb.architecture` | MongoDB(®) architecture (`standalone` or `replicaset`) | `standalone` |
|
||||
| `mongodb.image.repository` | MongoDB(®) image registry | `bitnami/mongodb` |
|
||||
| `mongodb.image.tag` | MongoDB(®) image tag (immutable tags are recommended) | `6.0.4-debian-11-r0` |
|
||||
| `mongodb.image.pullPolicy` | MongoDB(®) image pull policy | `IfNotPresent` |
|
||||
| `mongodb.livenessProbe.enabled` | Enable livenessProbe | `true` |
|
||||
| `mongodb.livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `30` |
|
||||
| `mongodb.livenessProbe.periodSeconds` | Period seconds for livenessProbe | `20` |
|
||||
| `mongodb.livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `10` |
|
||||
| `mongodb.livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `6` |
|
||||
| `mongodb.livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` |
|
||||
| `mongodb.readinessProbe.enabled` | Enable readinessProbe | `true` |
|
||||
| `mongodb.readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `5` |
|
||||
| `mongodb.readinessProbe.periodSeconds` | Period seconds for readinessProbe | `10` |
|
||||
| `mongodb.readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `10` |
|
||||
| `mongodb.readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `6` |
|
||||
| `mongodb.readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` |
|
||||
| `mongodb.service.annotations` | Service annotations | `{}` |
|
||||
| `mongodb.auth.enabled` | Enable custom authentication | `true` |
|
||||
| `mongodb.auth.usernames` | Custom usernames list ([special characters warning](https://www.mongodb.com/docs/manual/reference/connection-string/#standard-connection-string-format)) | `["infisical"]` |
|
||||
| `mongodb.auth.passwords` | Custom passwords list, match the above usernames order ([special characters warning](https://www.mongodb.com/docs/manual/reference/connection-string/#standard-connection-string-format)) | `["infisical"]` |
|
||||
| `mongodb.auth.databases` | Custom databases list ([special characters warning](https://www.mongodb.com/docs/manual/reference/connection-string/#standard-connection-string-format)) | `["infisical"]` |
|
||||
| `mongodb.auth.rootUser` | Database root user name | `root` |
|
||||
| `mongodb.auth.rootPassword` | Database root user password | `root` |
|
||||
| `mongodb.auth.existingSecret` | Existing secret with MongoDB(®) credentials (keys: `mongodb-passwords`, `mongodb-root-password`, `mongodb-metrics-password`, `mongodb-replica-set-key`) | `""` |
|
||||
| `mongodb.persistence.enabled` | Enable database persistence | `true` |
|
||||
| `mongodb.persistence.existingClaim` | Existing persistent volume claim name | `""` |
|
||||
| `mongodb.persistence.resourcePolicy` | Keep the persistent volume even on deletion (`keep` or `""`) | `keep` |
|
||||
| `mongodb.persistence.accessModes` | Persistent volume access modes | `["ReadWriteOnce"]` |
|
||||
| `mongodb.persistence.size` | Persistent storage request size | `8Gi` |
|
||||
| `mongodbConnection.externalMongoDBConnectionString` | Deprecated :warning: External MongoDB connection string</br>Use backendEnvironmentVariables.MONGO_URL instead | `""` |
|
||||
|
||||
### Ingress parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| -------------------------- | ------------------------------------------------------------------------ | ------- |
|
||||
| `ingress.enabled` | Enable ingress | `true` |
|
||||
| `ingress.ingressClassName` | Ingress class name | `nginx` |
|
||||
| `ingress.nginx.enabled` | Ingress controller | `false` |
|
||||
| `ingress.annotations` | Ingress annotations | `{}` |
|
||||
| `ingress.hostName` | Ingress hostname (your custom domain name, e.g. `infisical.example.org`) | `""` |
|
||||
| `ingress.tls` | Ingress TLS hosts (matching above hostName) | `[]` |
|
||||
|
||||
### Mailhog parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ---------------------------------- | -------------------------- | ------------------------- |
|
||||
| `mailhog.enabled` | Enable Mailhog | `false` |
|
||||
| `mailhog.fullnameOverride` | Fullname override | `mailhog` |
|
||||
| `mailhog.nameOverride` | Name override | `""` |
|
||||
| `mailhog.image.repository` | Image repository | `lytrax/mailhog` |
|
||||
| `mailhog.image.tag` | Image tag | `latest` |
|
||||
| `mailhog.image.pullPolicy` | Image pull policy | `IfNotPresent` |
|
||||
| `mailhog.containerPort.http.port` | Mailhog HTTP port (Web UI) | `8025` |
|
||||
| `mailhog.containerPort.smtp.port` | Mailhog SMTP port (Mail) | `1025` |
|
||||
| `mailhog.ingress.enabled` | Enable ingress | `true` |
|
||||
| `mailhog.ingress.ingressClassName` | Ingress class name | `nginx` |
|
||||
| `mailhog.ingress.annotations` | Ingress annotations | `{}` |
|
||||
| `mailhog.ingress.labels` | Ingress labels | `{}` |
|
||||
| `mailhog.ingress.hosts[0].host` | Mailhog host | `mailhog.infisical.local` |
|
||||
|
||||
### Redis parameters
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Persistence
|
||||
|
||||
The database persistence is enabled by default, your volumes will remain on your cluster even after uninstalling the chart. To disable persistence, set this value `mongodb.persistence.enabled: false`
|
||||
|
||||
## Local development
|
||||
|
||||
Find the resources and configuration about how to setup your local develoment environment on a k8s environment.
|
||||
|
||||
### Requirements
|
||||
|
||||
To create a local k8s environment, you'll need :
|
||||
|
||||
- [`helm`](https://helm.sh/docs/intro/install/) <kbd>required</kbd>
|
||||
- to generate the manifests and deploy the chart
|
||||
- local/remote k8s cluster <kbd>required</kbd>
|
||||
- e.g. [`kind`](https://kubernetes.io/docs/tasks/tools/), [`minikube`](https://kubernetes.io/docs/tasks/tools/) or an online provider
|
||||
- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) <kbd>optional</kbd>
|
||||
- to interact with the cluster
|
||||
|
||||
### Examples
|
||||
|
||||
ℹ️ Find complete setup scripts in [**./examples**](./examples)
|
||||
|
||||
Below example will deploy the following :
|
||||
|
||||
- [**infisical.local**](https://infisical.local)
|
||||
- Your local Infisical instance
|
||||
- You may have to add `infisical.local` to your `/etc/hosts` or similar depending your OS
|
||||
- The corresponding IP will depend on the tool or the way you're exposing the services ([learn more](https://minikube.sigs.k8s.io/docs/handbook/host-access/))
|
||||
|
||||
- [**mailhog.infisical.local**](https://mailhog.infisical.local)
|
||||
- Local SMTP server used to receive the emails (e.g. signup verification code)
|
||||
- You may have to add `mailhog.infisical.local` to your `/etc/hosts` or similar depending your OS
|
||||
- The corresponding IP will depend on the tool or the way you're exposing the services ([learn more](https://minikube.sigs.k8s.io/docs/handbook/host-access/))
|
||||
|
||||
Use below values to setup a local development environment, adapt those variables as you need
|
||||
|
||||
#### TL;DR
|
||||
|
||||
If you're running a k8s cluster with `ingress-nginx`, you can run one of the below scripts :
|
||||
|
||||
```sh
|
||||
# With 'kind' + 'helm', to create a local cluster and deploy the chart
|
||||
./examples.local-kind.sh
|
||||
|
||||
# With 'helm' only, if you already have a cluster to deploy the chart
|
||||
./examples.local-helm.sh
|
||||
```
|
||||
|
||||
#### Instructions
|
||||
|
||||
Here's the step-by-step instructions to setup your local development environment. First create the below file :
|
||||
|
||||
```yaml
|
||||
# values.dev.yaml
|
||||
|
||||
# Enable mailhog for local development
|
||||
mailhog:
|
||||
enabled: true
|
||||
|
||||
# Configure backend development variables (required)
|
||||
backendEnvironmentVariables:
|
||||
SITE_URL: https://infisical.local
|
||||
SMTP_FROM_ADDRESS: dev@infisical.local
|
||||
SMTP_FROM_NAME: Local Infisical
|
||||
SMTP_HOST: mailhog
|
||||
SMTP_PASSWORD: ""
|
||||
SMTP_PORT: 1025
|
||||
SMTP_SECURE: false
|
||||
SMTP_USERNAME: dev@infisical.local
|
||||
|
||||
# Configure frontend development variables (required)
|
||||
frontendEnvironmentVariables:
|
||||
SITE_URL: https://infisical.local
|
||||
```
|
||||
|
||||
After creating the above file, run :
|
||||
|
||||
```sh
|
||||
# Fetch the required charts
|
||||
helm dep update
|
||||
|
||||
# Install/upgrade Infisical
|
||||
helm upgrade --install --atomic \
|
||||
-n infisical-dev --create-namespace \
|
||||
-f ./values.dev.yaml \
|
||||
infisical-dev .
|
||||
```
|
||||
|
||||
## Upgrading
|
||||
|
||||
Find the chart upgrade instructions below. When upgrading from your version to one of the listed below, please follow every instructions in between.
|
||||
|
||||
Here's a snippet to upgrade your installation manually :
|
||||
|
||||
```sh
|
||||
# replace below '<placeholders>' with your own values
|
||||
helm upgrade --install --atomic \
|
||||
-n "<your-namesapce>" --create-namespace \
|
||||
-f "<your-values.yaml>" \
|
||||
<your-release-name> .
|
||||
```
|
||||
|
||||
ℹ️ Since we provide references to the k8s secret resources within the pods, their manifest file doesnt change and though doesnt reload (no changes detected). When upgrading your secrets, you'll have to do it through Helm (a timestamp field will be updated and your pods restarted)
|
||||
|
||||
### 0.1.16
|
||||
|
||||
- Auto-generation for the following variables, to ease your future upgrades or setups :
|
||||
- `ENCRYPTION_KEY`
|
||||
- `JWT_SIGNUP_SECRET`
|
||||
- `JWT_REFRESH_SECRET`
|
||||
- `JWT_AUTH_SECRET`
|
||||
- `JWT_SERVICE_SECRET`
|
||||
- `JWT_MFA_SECRET`
|
||||
|
||||
We've migrated the applications' environment variables into `secrets` resources, shared within the deployments through `envFrom`. If you upgrade your installation make sure to backup your deployments' environment variables (e.g. encryption key and jwt secrets).
|
||||
|
||||
The preference order is :
|
||||
- **user-defined** (values file or inline)
|
||||
- **existing-secret** (for existing installations, you don't have to specify the secrets when upgrading if they already exist)
|
||||
- **auto-generated** (if none of the values above have been found, we'll auto-generate a value for the user, only for the above mentioned variables)
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. Make sure **you have all the required environment variables** defined in the value file (or inline `--set`) you'll provide to `helm`
|
||||
1. e.g. All the above mentioned variables
|
||||
1. **Backup your existing secrets** (safety precaution)
|
||||
1. with below [snippets](#snippets)
|
||||
1. **Upgrade the chart**, with the [instructions](#upgrading)
|
||||
1. It'll create a secret per service, and store the secrets/conf within (auto-generate if you don't provide the required ones)
|
||||
1. It'll link the secret to the deployment through `envFrom`
|
||||
1. It'll automatically remove the hard-coded `env.*` variables from your infisical deployments
|
||||
1. Make sure that the **created secrets match the ones in your backups**
|
||||
1. e.g. `kubectl get secret -n <namespace> <release-name>-backend --template={{.data.ENCRYPTION_KEY}} | base64 -d`
|
||||
1. You're all set!
|
||||
|
||||
#### Snippets
|
||||
|
||||
Here's some snippets to backup your current secrets **before the upgrade** (:warning: it requires [`jq`](https://stedolan.github.io/jq/download/)) :
|
||||
|
||||
```sh
|
||||
# replace the below variables with yours (namespace + app)
|
||||
namespace=infisical; app=infisical; components="frontend backend"
|
||||
|
||||
for component in $components; do
|
||||
dpl=$(kubectl get deployment -n $namespace -l app=$app -l component=$component \
|
||||
-o jsonpath="{.items[0].metadata.name}")
|
||||
|
||||
kubectl get deployments -n $namespace $dpl \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].env[*]}' | \
|
||||
jq -r '.name + ":" + .value' > infisical-$component-conf.bak
|
||||
done
|
||||
```
|
@ -1,37 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
## Infisical local k8s development environment setup script
|
||||
## using 'helm' and assume you already have a cluster and an ingress (nginx)
|
||||
##
|
||||
|
||||
##
|
||||
## DEVELOPMENT USE ONLY
|
||||
## DO NOT USE IN PRODUCTION
|
||||
##
|
||||
|
||||
# define variables
|
||||
cluster_name=infisical
|
||||
host=infisical.local
|
||||
|
||||
# install infisical (local development)
|
||||
helm dep update
|
||||
cat <<EOF | helm upgrade --install --atomic \
|
||||
-n infisical-dev --create-namespace \
|
||||
-f - \
|
||||
infisical-dev .
|
||||
mailhog:
|
||||
enabled: true
|
||||
backendEnvironmentVariables:
|
||||
SITE_URL: https://$host
|
||||
SMTP_FROM_ADDRESS: dev@$host
|
||||
SMTP_FROM_NAME: Local Infisical
|
||||
SMTP_HOST: mailhog
|
||||
SMTP_PASSWORD: ""
|
||||
SMTP_PORT: 1025
|
||||
SMTP_SECURE: false
|
||||
SMTP_USERNAME: dev@$host
|
||||
frontendEnvironmentVariables:
|
||||
SITE_URL: https://$host
|
||||
ingress:
|
||||
hostName: $host
|
||||
EOF
|
@ -1,74 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
## Infisical local k8s development environment setup script
|
||||
## using 'kind' and 'ingress-nginx'
|
||||
## https://kind.sigs.k8s.io/docs/user/ingress/
|
||||
##
|
||||
|
||||
##
|
||||
## DEVELOPMENT USE ONLY
|
||||
## DO NOT USE IN PRODUCTION
|
||||
##
|
||||
|
||||
# define variables
|
||||
cluster_name=infisical
|
||||
host=infisical.local
|
||||
|
||||
# create the local cluster (expose 80/443 on localhost)
|
||||
cat <<EOF | kind create cluster -n $cluster_name --wait --config=-
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
- role: control-plane
|
||||
kubeadmConfigPatches:
|
||||
- |
|
||||
kind: InitConfiguration
|
||||
nodeRegistration:
|
||||
kubeletExtraArgs:
|
||||
node-labels: "ingress-ready=true"
|
||||
extraPortMappings:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
protocol: TCP
|
||||
- containerPort: 443
|
||||
hostPort: 443
|
||||
protocol: TCP
|
||||
EOF
|
||||
|
||||
# install ingress-nginx
|
||||
# kind version : https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
|
||||
helm upgrade -i --atomic \
|
||||
--repo https://kubernetes.github.io/ingress-nginx \
|
||||
ingress-nginx ingress-nginx \
|
||||
-n ingress-nginx --create-namespace \
|
||||
--set controller.service.type="NodePort" \
|
||||
--set controller.hostPort.enabled=true \
|
||||
--set controller.service.externalTrafficPolicy=Local
|
||||
|
||||
kubectl wait -n ingress-nginx \
|
||||
--for=condition=ready pod \
|
||||
--selector=app.kubernetes.io/component=controller \
|
||||
--timeout=120s
|
||||
|
||||
# install infisical (local development)
|
||||
helm dep update
|
||||
cat <<EOF | helm upgrade --install --atomic \
|
||||
-n infisical-dev --create-namespace \
|
||||
-f - \
|
||||
infisical-dev .
|
||||
mailhog:
|
||||
enabled: true
|
||||
backendEnvironmentVariables:
|
||||
SITE_URL: https://$host
|
||||
SMTP_FROM_ADDRESS: dev@$host
|
||||
SMTP_FROM_NAME: Local Infisical
|
||||
SMTP_HOST: mailhog
|
||||
SMTP_PASSWORD: ""
|
||||
SMTP_PORT: 1025
|
||||
SMTP_SECURE: false
|
||||
SMTP_USERNAME: dev@$host
|
||||
frontendEnvironmentVariables:
|
||||
SITE_URL: https://$host
|
||||
ingress:
|
||||
hostName: $host
|
||||
EOF
|
@ -1,56 +0,0 @@
|
||||
##
|
||||
|
||||
-- Infisical Helm Chart --
|
||||
|
||||
██╗███╗ ██╗███████╗██╗███████╗██╗ ██████╗ █████╗ ██╗
|
||||
██║████╗ ██║██╔════╝██║██╔════╝██║██╔════╝██╔══██╗██║
|
||||
██║██╔██╗ ██║█████╗ ██║███████╗██║██║ ███████║██║
|
||||
██║██║╚██╗██║██╔══╝ ██║╚════██║██║██║ ██╔══██║██║
|
||||
██║██║ ╚████║██║ ██║███████║██║╚██████╗██║ ██║███████╗
|
||||
╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝
|
||||
{{ .Chart.Name }} ({{ .Chart.Version }})
|
||||
|
||||
|
||||
╭―― Thank you for installing Infisical! 👋 ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――┤
|
||||
│
|
||||
│ Infisical / All-in-one open-source SecretOps solution to manage your secrets across your infra! 🔒🔑
|
||||
│
|
||||
│ Visit < https://infisical.com/docs/self-hosting/overview > for further documentation about self-hosting!
|
||||
│
|
||||
│ Current installation (infisical) :
|
||||
│ • infisical-backend : {{ .Values.backend.enabled }}
|
||||
│ • mongodb : {{ .Values.mongodb.enabled }}
|
||||
│ • mailhog : {{ .Values.mailhog.enabled }}
|
||||
| • nginx : {{ .Values.ingress.nginx.enabled }}
|
||||
│
|
||||
╰―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――┤
|
||||
|
||||
――― Here's a list of helpful commands to get you started 📝 ―――――――――――――――――――――――――――――――――――――――――┤
|
||||
|
||||
→ Get all the Infisical resources (excluding secrets/pvcs)
|
||||
$ kubectl get all -n {{ .Release.Namespace }}
|
||||
|
||||
→ Get your release status
|
||||
$ helm status -n {{ .Release.Namespace }} {{ .Release.Name }}
|
||||
|
||||
→ Get your release resources
|
||||
$ helm get all -n {{ .Release.Namespace }} {{ .Release.Name }}
|
||||
|
||||
→ Uninstall your release
|
||||
$ helm uninstall -n {{ .Release.Namespace }} {{ .Release.Name }}
|
||||
|
||||
→ Get MongoDB root password
|
||||
$ kubectl get secret -n {{ .Release.Namespace }} mongodb
|
||||
-o jsonpath="{.data['mongodb-root-password']}" | base64 -d
|
||||
|
||||
→ Get MongoDB users passwords
|
||||
$ kubectl get secret -n {{ .Release.Namespace }} mongodb
|
||||
-o jsonpath="{.data['mongodb-passwords']}" | base64 -d
|
||||
|
||||
→ Export your backend secrets (requires jq)
|
||||
$ kubectl get secrets/<your-secret-name> -n {{ .Release.Namespace }} \
|
||||
-o json | jq '.data | map_values(@base64d)' > <dest-filename>.bak
|
||||
|
||||
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――┤
|
||||
|
||||
##
|
@ -1,104 +0,0 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "infisical.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "infisical.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create unified labels for infisical components
|
||||
*/}}
|
||||
{{- define "infisical.common.matchLabels" -}}
|
||||
app: {{ template "infisical.name" . }}
|
||||
release: {{ .Release.Name }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "infisical.common.metaLabels" -}}
|
||||
chart: {{ template "infisical.chart" . }}
|
||||
heritage: {{ .Release.Service }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "infisical.common.labels" -}}
|
||||
{{ include "infisical.common.matchLabels" . }}
|
||||
{{ include "infisical.common.metaLabels" . }}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
{{- define "infisical.backend.labels" -}}
|
||||
{{ include "infisical.backend.matchLabels" . }}
|
||||
{{ include "infisical.common.metaLabels" . }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "infisical.backend.matchLabels" -}}
|
||||
component: {{ .Values.backend.name | quote }}
|
||||
{{ include "infisical.common.matchLabels" . }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "infisical.mongodb.labels" -}}
|
||||
{{ include "infisical.mongodb.matchLabels" . }}
|
||||
{{ include "infisical.common.metaLabels" . }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "infisical.mongodb.matchLabels" -}}
|
||||
component: {{ .Values.mongodb.name | quote }}
|
||||
{{ include "infisical.common.matchLabels" . }}
|
||||
{{- 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).
|
||||
*/}}
|
||||
{{- define "infisical.backend.fullname" -}}
|
||||
{{- if .Values.backend.fullnameOverride -}}
|
||||
{{- .Values.backend.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- printf "%s-%s" .Release.Name .Values.backend.name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s-%s" .Release.Name $name .Values.backend.name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
{{/*
|
||||
Create a fully qualified mongodb name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
*/}}
|
||||
{{- define "infisical.mongodb.fullname" -}}
|
||||
{{- if .Values.mongodb.fullnameOverride -}}
|
||||
{{- .Values.mongodb.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- printf "%s-%s" .Release.Name .Values.mongodb.name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s-%s" .Release.Name $name .Values.mongodb.name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the mongodb connection string.
|
||||
*/}}
|
||||
{{- define "infisical.mongodb.connectionString" -}}
|
||||
{{- $host := include "infisical.mongodb.fullname" . -}}
|
||||
{{- $port := 27017 -}}
|
||||
{{- $user := first .Values.mongodb.auth.usernames | default "root" -}}
|
||||
{{- $pass := first .Values.mongodb.auth.passwords | default "root" -}}
|
||||
{{- $database := first .Values.mongodb.auth.databases | default "test" -}}
|
||||
{{- $connectionString := printf "mongodb://%s:%s@%s:%d/%s" $user $pass $host $port $database -}}
|
||||
{{/* Backward compatibility (< 0.1.16, deprecated) */}}
|
||||
{{- if .Values.mongodbConnection.externalMongoDBConnectionString -}}
|
||||
{{- $connectionString = .Values.mongodbConnection.externalMongoDBConnectionString -}}
|
||||
{{- end -}}
|
||||
{{- printf "%s" $connectionString -}}
|
||||
{{- end -}}
|
@ -1,101 +0,0 @@
|
||||
{{- $backend := .Values.backend }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "infisical.backend.fullname" . }}
|
||||
annotations:
|
||||
updatedAt: {{ now | date "2006-01-01 MST 15:04:05" | quote }}
|
||||
{{- with $backend.deploymentAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "infisical.backend.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ $backend.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "infisical.backend.matchLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "infisical.backend.matchLabels" . | nindent 8 }}
|
||||
annotations:
|
||||
updatedAt: {{ now | date "2006-01-01 MST 15:04:05" | quote }}
|
||||
{{- with $backend.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with $backend.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ template "infisical.name" . }}-{{ $backend.name }}
|
||||
image: "{{ $backend.image.repository }}:{{ $backend.image.tag | default "latest" }}"
|
||||
imagePullPolicy: {{ $backend.image.pullPolicy }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/status
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ $backend.kubeSecretRef | default (include "infisical.backend.fullname" .) }}
|
||||
{{- if $backend.resources }}
|
||||
resources: {{- toYaml $backend.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "infisical.backend.fullname" . }}
|
||||
labels:
|
||||
{{- include "infisical.backend.labels" . | nindent 4 }}
|
||||
{{- with $backend.service.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ $backend.service.type }}
|
||||
selector:
|
||||
{{- include "infisical.backend.matchLabels" . | nindent 8 }}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 8080 # container port
|
||||
{{- if eq $backend.service.type "NodePort" }}
|
||||
nodePort: {{ $backend.service.nodePort }}
|
||||
{{- end }}
|
||||
|
||||
---
|
||||
|
||||
{{ if not $backend.kubeSecretRef }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "infisical.backend.fullname" . }}
|
||||
annotations:
|
||||
"helm.sh/resource-policy": "keep"
|
||||
type: Opaque
|
||||
stringData:
|
||||
{{- $requiredVars := dict "ENCRYPTION_KEY" (randAlphaNum 32 | lower)
|
||||
"JWT_SIGNUP_SECRET" (randAlphaNum 32 | lower)
|
||||
"JWT_REFRESH_SECRET" (randAlphaNum 32 | lower)
|
||||
"JWT_AUTH_SECRET" (randAlphaNum 32 | lower)
|
||||
"JWT_SERVICE_SECRET" (randAlphaNum 32 | lower)
|
||||
"JWT_MFA_SECRET" (randAlphaNum 32 | lower)
|
||||
"JWT_PROVIDER_AUTH_SECRET" (randAlphaNum 32 | lower)
|
||||
"MONGO_URL" (include "infisical.mongodb.connectionString" .) }}
|
||||
{{- $secretObj := (lookup "v1" "Secret" .Release.Namespace (include "infisical.backend.fullname" .)) | default dict }}
|
||||
{{- $secretData := (get $secretObj "data") | default dict }}
|
||||
{{ range $key, $value := .Values.backendEnvironmentVariables }}
|
||||
{{- $default := get $requiredVars $key -}}
|
||||
{{- $current := get $secretData $key | b64dec -}}
|
||||
{{- $v := $value | default ($current | default $default) -}}
|
||||
{{ $key }}: {{ $v | quote }}
|
||||
{{ end -}}
|
||||
{{- end }}
|
@ -1,50 +0,0 @@
|
||||
{{ if .Values.ingress.enabled }}
|
||||
{{- $ingress := .Values.ingress }}
|
||||
{{- if and $ingress.ingressClassName (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey $ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set $ingress.annotations "kubernetes.io/ingress.class" $ingress.ingressClassName}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: infisical-ingress
|
||||
{{- with $ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and $ingress.ingressClassName (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ $ingress.ingressClassName | default "nginx" }}
|
||||
{{- end }}
|
||||
{{- if $ingress.tls }}
|
||||
tls:
|
||||
{{- range $ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "infisical.backend.fullname" . }}
|
||||
port:
|
||||
number: 8080
|
||||
- path: /ss-webhook
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "infisical.backend.fullname" . }}
|
||||
port:
|
||||
number: 8080
|
||||
{{- if $ingress.hostName }}
|
||||
host: {{ $ingress.hostName }}
|
||||
{{- end }}
|
||||
{{ end }}
|
@ -1,378 +0,0 @@
|
||||
## @section Common parameters
|
||||
##
|
||||
|
||||
## @param nameOverride Override release name
|
||||
##
|
||||
nameOverride: ""
|
||||
## @param fullnameOverride Override release fullname
|
||||
##
|
||||
fullnameOverride: ""
|
||||
|
||||
## @section Infisical backend parameters
|
||||
## Documentation : https://infisical.com/docs/self-hosting/deployments/kubernetes
|
||||
##
|
||||
|
||||
backend:
|
||||
## @param backend.enabled Enable backend
|
||||
##
|
||||
enabled: true
|
||||
## @param backend.name Backend name
|
||||
##
|
||||
name: backend
|
||||
## @param backend.fullnameOverride Backend fullnameOverride
|
||||
##
|
||||
fullnameOverride: ""
|
||||
## @param backend.podAnnotations Backend pod annotations
|
||||
##
|
||||
podAnnotations: {}
|
||||
## @param backend.deploymentAnnotations Backend deployment annotations
|
||||
##
|
||||
deploymentAnnotations: {}
|
||||
## @param backend.replicaCount Backend replica count
|
||||
##
|
||||
replicaCount: 2
|
||||
## Backend image parameters
|
||||
##
|
||||
image:
|
||||
## @param backend.image.repository Backend image repository
|
||||
##
|
||||
repository: infisical/infisical
|
||||
## @param backend.image.tag Backend image tag
|
||||
##
|
||||
tag: "latest"
|
||||
## @param backend.image.pullPolicy Backend image pullPolicy
|
||||
##
|
||||
pullPolicy: IfNotPresent
|
||||
## @param backend.affinity Backend pod affinity
|
||||
##
|
||||
affinity: {}
|
||||
## @param backend.kubeSecretRef Backend secret resource reference name (containing required [backend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars))
|
||||
##
|
||||
kubeSecretRef: ""
|
||||
## Backend service
|
||||
##
|
||||
service:
|
||||
## @param backend.service.annotations Backend service annotations
|
||||
##
|
||||
annotations: {}
|
||||
## @param backend.service.type Backend service type
|
||||
##
|
||||
type: ClusterIP
|
||||
## @param backend.service.nodePort Backend service nodePort (used if above type is `NodePort`)
|
||||
##
|
||||
nodePort: ""
|
||||
|
||||
## Backend variables configuration
|
||||
## Documentation : https://infisical.com/docs/self-hosting/configuration/envars
|
||||
##
|
||||
backendEnvironmentVariables:
|
||||
## @param backendEnvironmentVariables.ENCRYPTION_KEY **Required** Backend encryption key (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret)
|
||||
## Command to generate the required value (linux) : 'hexdump -vn16 -e'4/4 "%08X" 1 "\n"' /dev/urandom', 'openssl rand -hex 16'
|
||||
##
|
||||
ENCRYPTION_KEY: ""
|
||||
## @param backendEnvironmentVariables.JWT_SIGNUP_SECRET **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret)
|
||||
## @param backendEnvironmentVariables.JWT_REFRESH_SECRET **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret)
|
||||
## @param backendEnvironmentVariables.JWT_AUTH_SECRET **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret)
|
||||
## @param backendEnvironmentVariables.JWT_SERVICE_SECRET **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret)
|
||||
## @param backendEnvironmentVariables.JWT_MFA_SECRET **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret)
|
||||
## @param backendEnvironmentVariables.JWT_PROVIDER_AUTH_SECRET **Required** Secrets to sign JWT OAuth tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret)
|
||||
## Command to generate the required value (linux) : 'hexdump -vn16 -e'4/4 "%08X" 1 "\n"' /dev/urandom', 'openssl rand -hex 16'
|
||||
##
|
||||
JWT_SIGNUP_SECRET: ""
|
||||
JWT_REFRESH_SECRET: ""
|
||||
JWT_AUTH_SECRET: ""
|
||||
JWT_SERVICE_SECRET: ""
|
||||
JWT_MFA_SECRET: ""
|
||||
JWT_PROVIDER_AUTH_SECRET: ""
|
||||
## @param backendEnvironmentVariables.SMTP_HOST **Required** Hostname to connect to for establishing SMTP connections
|
||||
## @param backendEnvironmentVariables.SMTP_PORT Port to connect to for establishing SMTP connections
|
||||
## @param backendEnvironmentVariables.SMTP_SECURE If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported
|
||||
## @param backendEnvironmentVariables.SMTP_FROM_NAME Name label to be used in From field (e.g. Infisical)
|
||||
## @param backendEnvironmentVariables.SMTP_FROM_ADDRESS **Required** Email address to be used for sending emails (e.g. dev@infisical.com)
|
||||
## @param backendEnvironmentVariables.SMTP_USERNAME **Required** Credential to connect to host (e.g. team@infisical.com)
|
||||
## @param backendEnvironmentVariables.SMTP_PASSWORD **Required** Credential to connect to host
|
||||
##
|
||||
SMTP_HOST: ""
|
||||
SMTP_PORT: 587
|
||||
SMTP_SECURE: false
|
||||
SMTP_FROM_NAME: Infisical
|
||||
SMTP_FROM_ADDRESS: ""
|
||||
SMTP_USERNAME: ""
|
||||
SMTP_PASSWORD: ""
|
||||
## @param backendEnvironmentVariables.SITE_URL Absolute URL including the protocol (e.g. https://app.infisical.com)
|
||||
##
|
||||
SITE_URL: infisical.local
|
||||
## @param backendEnvironmentVariables.INVITE_ONLY_SIGNUP To disable account creation from the login page (invites only)
|
||||
##
|
||||
INVITE_ONLY_SIGNUP: false
|
||||
## @param backendEnvironmentVariables.MONGO_URL MongoDB connection string (external or internal)</br>Leave it empty for auto-generated connection string
|
||||
## By default the backend will automatically be connected to a Mongo instance within the cluster
|
||||
## However, it is recommended to add a managed document DB connection string for production-use (DBaaS)
|
||||
## Learn about connection string type here https://www.mongodb.com/docs/manual/reference/connection-string/
|
||||
## e.g. "mongodb://<user>:<pass>@<host>:<port>/<database-name>"
|
||||
##
|
||||
MONGO_URL: ""
|
||||
|
||||
## @param backendEnvironmentVariables.REDIS_URL
|
||||
## By default, the backend will use the Redis that is auto deployed along with Infisical
|
||||
REDIS_URL: "redis://redis-master:6379"
|
||||
|
||||
## @section MongoDB(®) parameters
|
||||
## Documentation : https://github.com/bitnami/charts/blob/main/bitnami/mongodb/values.yaml
|
||||
##
|
||||
|
||||
mongodb:
|
||||
## @param mongodb.enabled Enable MongoDB(®)
|
||||
##
|
||||
enabled: true
|
||||
## @param mongodb.name Name used to build variables (deprecated)
|
||||
##
|
||||
name: "mongodb"
|
||||
## @param mongodb.fullnameOverride Fullname override
|
||||
##
|
||||
fullnameOverride: "mongodb"
|
||||
## @param mongodb.nameOverride Name override
|
||||
##
|
||||
nameOverride: "mongodb"
|
||||
## @param mongodb.podAnnotations Pod annotations
|
||||
##
|
||||
podAnnotations: {}
|
||||
## @param mongodb.useStatefulSet Set to true to use a StatefulSet instead of a Deployment (only when `architecture: standalone`)
|
||||
##
|
||||
useStatefulSet: true
|
||||
## @param mongodb.architecture MongoDB(®) architecture (`standalone` or `replicaset`)
|
||||
##
|
||||
architecture: "standalone"
|
||||
## Bitnami MongoDB(®) image
|
||||
## ref: https://hub.docker.com/r/bitnami/mongodb/tags/
|
||||
## @param mongodb.image.repository MongoDB(®) image registry
|
||||
## @param mongodb.image.tag MongoDB(®) image tag (immutable tags are recommended)
|
||||
## @param mongodb.image.pullPolicy MongoDB(®) image pull policy
|
||||
##
|
||||
image:
|
||||
repository: bitnami/mongodb
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "6.0.4-debian-11-r0"
|
||||
## Bitnami MongoDB(®) pods' liveness probe
|
||||
## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes
|
||||
## @param mongodb.livenessProbe.enabled Enable livenessProbe
|
||||
## @param mongodb.livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe
|
||||
## @param mongodb.livenessProbe.periodSeconds Period seconds for livenessProbe
|
||||
## @param mongodb.livenessProbe.timeoutSeconds Timeout seconds for livenessProbe
|
||||
## @param mongodb.livenessProbe.failureThreshold Failure threshold for livenessProbe
|
||||
## @param mongodb.livenessProbe.successThreshold Success threshold for livenessProbe
|
||||
##
|
||||
livenessProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 20
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 6
|
||||
successThreshold: 1
|
||||
## Bitnami MongoDB(®) pods' readiness probe. Evaluated as a template.
|
||||
## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes
|
||||
## @param mongodb.readinessProbe.enabled Enable readinessProbe
|
||||
## @param mongodb.readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe
|
||||
## @param mongodb.readinessProbe.periodSeconds Period seconds for readinessProbe
|
||||
## @param mongodb.readinessProbe.timeoutSeconds Timeout seconds for readinessProbe
|
||||
## @param mongodb.readinessProbe.failureThreshold Failure threshold for readinessProbe
|
||||
## @param mongodb.readinessProbe.successThreshold Success threshold for readinessProbe
|
||||
##
|
||||
readinessProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 6
|
||||
successThreshold: 1
|
||||
## @param mongodb.service.annotations Service annotations
|
||||
##
|
||||
service:
|
||||
annotations: {}
|
||||
## Infisical MongoDB custom authentication
|
||||
##
|
||||
auth:
|
||||
## @param mongodb.auth.enabled Enable custom authentication
|
||||
##
|
||||
enabled: true
|
||||
## @param mongodb.auth.usernames Custom usernames list ([special characters warning](https://www.mongodb.com/docs/manual/reference/connection-string/#standard-connection-string-format))
|
||||
##
|
||||
usernames:
|
||||
- "infisical"
|
||||
## @param mongodb.auth.passwords Custom passwords list, match the above usernames order ([special characters warning](https://www.mongodb.com/docs/manual/reference/connection-string/#standard-connection-string-format))
|
||||
##
|
||||
passwords:
|
||||
- "infisical"
|
||||
## @param mongodb.auth.databases Custom databases list ([special characters warning](https://www.mongodb.com/docs/manual/reference/connection-string/#standard-connection-string-format))
|
||||
##
|
||||
databases:
|
||||
- "infisical"
|
||||
## @param mongodb.auth.rootUser Database root user name
|
||||
##
|
||||
rootUser: root
|
||||
## @param mongodb.auth.rootPassword Database root user password
|
||||
##
|
||||
rootPassword: root
|
||||
|
||||
## @param mongodb.auth.existingSecret Existing secret with MongoDB(®) credentials (keys: `mongodb-passwords`, `mongodb-root-password`, `mongodb-metrics-password`, `mongodb-replica-set-key`)
|
||||
## NOTE: When it's set the previous parameters are ignored.
|
||||
##
|
||||
existingSecret: ""
|
||||
|
||||
## MongoDB persistence configuration
|
||||
##
|
||||
persistence:
|
||||
## @param mongodb.persistence.enabled Enable database persistence
|
||||
##
|
||||
enabled: true
|
||||
## @param mongodb.persistence.existingClaim Existing persistent volume claim name
|
||||
##
|
||||
existingClaim: ""
|
||||
## @param mongodb.persistence.resourcePolicy Keep the persistent volume even on deletion (`keep` or `""`)
|
||||
##
|
||||
resourcePolicy: "keep"
|
||||
## @param mongodb.persistence.accessModes Persistent volume access modes
|
||||
##
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
## @param mongodb.persistence.size Persistent storage request size
|
||||
##
|
||||
size: 8Gi
|
||||
|
||||
## @param mongodbConnection.externalMongoDBConnectionString Deprecated :warning: External MongoDB connection string</br>Use backendEnvironmentVariables.MONGO_URL instead
|
||||
## By default the backend will be connected to a Mongo instance within the cluster
|
||||
## However, it is recommended to add a managed document DB connection string for production-use (DBaaS)
|
||||
## Learn about connection string type here https://www.mongodb.com/docs/manual/reference/connection-string/
|
||||
## e.g. "mongodb://<user>:<pass>@<host>:<port>/<database-name>"
|
||||
##
|
||||
mongodbConnection:
|
||||
externalMongoDBConnectionString: ""
|
||||
|
||||
## @section Ingress parameters
|
||||
##
|
||||
|
||||
ingress:
|
||||
## @param ingress.enabled Enable ingress
|
||||
##
|
||||
enabled: true
|
||||
## @param ingress.ingressClassName Ingress class name
|
||||
##
|
||||
ingressClassName: nginx
|
||||
## @param ingress.nginx.enabled Ingress controller
|
||||
##
|
||||
nginx:
|
||||
enabled: false
|
||||
## @param ingress.annotations Ingress annotations
|
||||
##
|
||||
annotations:
|
||||
{}
|
||||
# kubernetes.io/ingress.class: "nginx"
|
||||
# cert-manager.io/issuer: letsencrypt-nginx
|
||||
## @param ingress.hostName Ingress hostname (your custom domain name, e.g. `infisical.example.org`)
|
||||
## Replace with your own domain
|
||||
##
|
||||
hostName: ""
|
||||
## @param ingress.tls Ingress TLS hosts (matching above hostName)
|
||||
## Replace with your own domain
|
||||
##
|
||||
tls:
|
||||
[]
|
||||
# - secretName: letsencrypt-nginx
|
||||
# hosts:
|
||||
# - infisical.local
|
||||
|
||||
## @section Mailhog parameters
|
||||
## Documentation : https://github.com/codecentric/helm-charts/blob/master/charts/mailhog/values.yaml
|
||||
##
|
||||
|
||||
mailhog:
|
||||
## @param mailhog.enabled Enable Mailhog
|
||||
##
|
||||
enabled: false
|
||||
## @param mailhog.fullnameOverride Fullname override
|
||||
##
|
||||
fullnameOverride: "mailhog"
|
||||
## @param mailhog.nameOverride Name override
|
||||
##
|
||||
nameOverride: ""
|
||||
## @param mailhog.image.repository Image repository
|
||||
## Why we use this version : https://github.com/mailhog/MailHog/issues/353#issuecomment-821137362
|
||||
## @param mailhog.image.tag Image tag
|
||||
## @param mailhog.image.pullPolicy Image pull policy
|
||||
##
|
||||
image:
|
||||
repository: lytrax/mailhog
|
||||
tag: "latest"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
containerPort:
|
||||
## @param mailhog.containerPort.http.port Mailhog HTTP port (Web UI)
|
||||
## @skip mailhog.containerPort.http.name
|
||||
##
|
||||
http:
|
||||
name: http
|
||||
port: 8025
|
||||
## @param mailhog.containerPort.smtp.port Mailhog SMTP port (Mail)
|
||||
## @skip mailhog.containerPort.smtp.name
|
||||
##
|
||||
smtp:
|
||||
name: tcp-smtp
|
||||
port: 1025
|
||||
## @skip mailhog.service
|
||||
##
|
||||
service:
|
||||
annotations: {}
|
||||
extraPorts: []
|
||||
clusterIP: ""
|
||||
externalIPs: []
|
||||
loadBalancerIP: ""
|
||||
loadBalancerSourceRanges: []
|
||||
type: ClusterIP
|
||||
# Named target ports are not supported by GCE health checks, so when deploying on GKE
|
||||
# and exposing it via GCE ingress, the health checks fail and the load balancer returns a 502.
|
||||
namedTargetPort: true
|
||||
port:
|
||||
http: 8025
|
||||
smtp: 1025
|
||||
nodePort:
|
||||
http: ""
|
||||
smtp: ""
|
||||
## Mailhog ingress
|
||||
##
|
||||
ingress:
|
||||
## @param mailhog.ingress.enabled Enable ingress
|
||||
##
|
||||
enabled: true
|
||||
## @param mailhog.ingress.ingressClassName Ingress class name
|
||||
##
|
||||
ingressClassName: nginx
|
||||
## @param mailhog.ingress.annotations Ingress annotations
|
||||
##
|
||||
annotations:
|
||||
{}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
## @param mailhog.ingress.labels Ingress labels
|
||||
##
|
||||
labels: {}
|
||||
hosts:
|
||||
## @param mailhog.ingress.hosts[0].host Mailhog host
|
||||
##
|
||||
- host: mailhog.infisical.local
|
||||
## @skip mailhog.ingress.hosts[0].paths
|
||||
##
|
||||
paths:
|
||||
- path: "/"
|
||||
pathType: Prefix
|
||||
|
||||
## @section Redis parameters
|
||||
## Documentation : https://github.com/bitnami/charts/tree/main/bitnami/redis#parameters
|
||||
##
|
||||
## @skip redis
|
||||
##
|
||||
redis:
|
||||
name: "redis"
|
||||
fullnameOverride: "redis"
|
||||
enabled: true
|
||||
architecture: standalone
|
||||
auth:
|
||||
enabled: false
|