Resolve merge conflicts

This commit is contained in:
Tuan Dang
2024-05-09 12:51:24 -07:00
214 changed files with 404 additions and 13253 deletions
@@ -74,21 +74,21 @@ jobs:
uses: pr-mpt/actions-commit-hash@v2 uses: pr-mpt/actions-commit-hash@v2
- name: Download task definition - name: Download task definition
run: | run: |
aws ecs describe-task-definition --task-definition infisical-prod-platform --query taskDefinition > task-definition.json aws ecs describe-task-definition --task-definition infisical-core-platform --query taskDefinition > task-definition.json
- name: Render Amazon ECS task definition - name: Render Amazon ECS task definition
id: render-web-container id: render-web-container
uses: aws-actions/amazon-ecs-render-task-definition@v1 uses: aws-actions/amazon-ecs-render-task-definition@v1
with: with:
task-definition: task-definition.json task-definition: task-definition.json
container-name: infisical-prod-platform container-name: infisical-core-platform
image: infisical/staging_infisical:${{ steps.commit.outputs.short }} image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info" environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service - name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1 uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with: with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }} task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-prod-platform service: infisical-core-platform
cluster: infisical-prod-platform cluster: infisical-core-platform
wait-for-service-stability: true wait-for-service-stability: true
production-postgres-deployment: production-postgres-deployment:
@@ -135,6 +135,6 @@ jobs:
uses: aws-actions/amazon-ecs-deploy-task-definition@v1 uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with: with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }} task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-prod-platform service: infisical-core-platform
cluster: infisical-prod-platform cluster: infisical-core-platform
wait-for-service-stability: true wait-for-service-stability: true
+2 -2
View File
@@ -32,7 +32,7 @@ import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-se
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service"; import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
import { TIdentityServiceFactory } from "@app/services/identity/identity-service"; import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service"; import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { TIdentityAwsIamAuthServiceFactory } from "@app/services/identity-aws-iam-auth/identity-aws-iam-auth-service"; import { TIdentityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service"; import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service"; import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service"; import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service";
@@ -118,7 +118,7 @@ declare module "fastify" {
identityProject: TIdentityProjectServiceFactory; identityProject: TIdentityProjectServiceFactory;
identityUa: TIdentityUaServiceFactory; identityUa: TIdentityUaServiceFactory;
identityGcpAuth: TIdentityGcpAuthServiceFactory; identityGcpAuth: TIdentityGcpAuthServiceFactory;
identityAwsIamAuth: TIdentityAwsIamAuthServiceFactory; identityAwsAuth: TIdentityAwsAuthServiceFactory;
accessApprovalPolicy: TAccessApprovalPolicyServiceFactory; accessApprovalPolicy: TAccessApprovalPolicyServiceFactory;
accessApprovalRequest: TAccessApprovalRequestServiceFactory; accessApprovalRequest: TAccessApprovalRequestServiceFactory;
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory; secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;
+7 -7
View File
@@ -59,9 +59,9 @@ import {
TIdentityAccessTokens, TIdentityAccessTokens,
TIdentityAccessTokensInsert, TIdentityAccessTokensInsert,
TIdentityAccessTokensUpdate, TIdentityAccessTokensUpdate,
TIdentityAwsIamAuths, TIdentityAwsAuths,
TIdentityAwsIamAuthsInsert, TIdentityAwsAuthsInsert,
TIdentityAwsIamAuthsUpdate, TIdentityAwsAuthsUpdate,
TIdentityGcpAuths, TIdentityGcpAuths,
TIdentityGcpAuthsInsert, TIdentityGcpAuthsInsert,
TIdentityGcpAuthsUpdate, TIdentityGcpAuthsUpdate,
@@ -337,10 +337,10 @@ declare module "knex/types/tables" {
TIdentityGcpAuthsInsert, TIdentityGcpAuthsInsert,
TIdentityGcpAuthsUpdate TIdentityGcpAuthsUpdate
>; >;
[TableName.IdentityAwsIamAuth]: Knex.CompositeTableType< [TableName.IdentityAwsAuth]: Knex.CompositeTableType<
TIdentityAwsIamAuths, TIdentityAwsAuths,
TIdentityAwsIamAuthsInsert, TIdentityAwsAuthsInsert,
TIdentityAwsIamAuthsUpdate TIdentityAwsAuthsUpdate
>; >;
[TableName.IdentityUaClientSecret]: Knex.CompositeTableType< [TableName.IdentityUaClientSecret]: Knex.CompositeTableType<
TIdentityUaClientSecrets, TIdentityUaClientSecrets,
@@ -4,8 +4,8 @@ import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils"; import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityAwsIamAuth))) { if (!(await knex.schema.hasTable(TableName.IdentityAwsAuth))) {
await knex.schema.createTable(TableName.IdentityAwsIamAuth, (t) => { await knex.schema.createTable(TableName.IdentityAwsAuth, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable(); t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable(); t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
@@ -14,16 +14,17 @@ export async function up(knex: Knex): Promise<void> {
t.timestamps(true, true, true); t.timestamps(true, true, true);
t.uuid("identityId").notNullable().unique(); t.uuid("identityId").notNullable().unique();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE"); t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.string("type").notNullable();
t.string("stsEndpoint").notNullable(); t.string("stsEndpoint").notNullable();
t.string("allowedPrincipalArns").notNullable(); t.string("allowedPrincipalArns").notNullable();
t.string("allowedAccountIds").notNullable(); t.string("allowedAccountIds").notNullable();
}); });
} }
await createOnUpdateTrigger(knex, TableName.IdentityAwsIamAuth); await createOnUpdateTrigger(knex, TableName.IdentityAwsAuth);
} }
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityAwsIamAuth); await knex.schema.dropTableIfExists(TableName.IdentityAwsAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityAwsIamAuth); await dropOnUpdateTrigger(knex, TableName.IdentityAwsAuth);
} }
@@ -7,7 +7,7 @@ import { z } from "zod";
import { TImmutableDBKeys } from "./models"; import { TImmutableDBKeys } from "./models";
export const IdentityAwsIamAuthsSchema = z.object({ export const IdentityAwsAuthsSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
accessTokenTTL: z.coerce.number().default(7200), accessTokenTTL: z.coerce.number().default(7200),
accessTokenMaxTTL: z.coerce.number().default(7200), accessTokenMaxTTL: z.coerce.number().default(7200),
@@ -16,11 +16,12 @@ export const IdentityAwsIamAuthsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
identityId: z.string().uuid(), identityId: z.string().uuid(),
type: z.string(),
stsEndpoint: z.string(), stsEndpoint: z.string(),
allowedPrincipalArns: z.string(), allowedPrincipalArns: z.string(),
allowedAccountIds: z.string() allowedAccountIds: z.string()
}); });
export type TIdentityAwsIamAuths = z.infer<typeof IdentityAwsIamAuthsSchema>; export type TIdentityAwsAuths = z.infer<typeof IdentityAwsAuthsSchema>;
export type TIdentityAwsIamAuthsInsert = Omit<z.input<typeof IdentityAwsIamAuthsSchema>, TImmutableDBKeys>; export type TIdentityAwsAuthsInsert = Omit<z.input<typeof IdentityAwsAuthsSchema>, TImmutableDBKeys>;
export type TIdentityAwsIamAuthsUpdate = Partial<Omit<z.input<typeof IdentityAwsIamAuthsSchema>, TImmutableDBKeys>>; export type TIdentityAwsAuthsUpdate = Partial<Omit<z.input<typeof IdentityAwsAuthsSchema>, TImmutableDBKeys>>;
+1 -1
View File
@@ -17,7 +17,7 @@ export * from "./group-project-memberships";
export * from "./groups"; export * from "./groups";
export * from "./identities"; export * from "./identities";
export * from "./identity-access-tokens"; export * from "./identity-access-tokens";
export * from "./identity-aws-iam-auths"; export * from "./identity-aws-auths";
export * from "./identity-gcp-auths"; export * from "./identity-gcp-auths";
export * from "./identity-org-memberships"; export * from "./identity-org-memberships";
export * from "./identity-project-additional-privilege"; export * from "./identity-project-additional-privilege";
+2 -2
View File
@@ -46,7 +46,7 @@ export enum TableName {
IdentityUniversalAuth = "identity_universal_auths", IdentityUniversalAuth = "identity_universal_auths",
IdentityGcpAuth = "identity_gcp_auths", IdentityGcpAuth = "identity_gcp_auths",
IdentityUaClientSecret = "identity_ua_client_secrets", IdentityUaClientSecret = "identity_ua_client_secrets",
IdentityAwsIamAuth = "identity_aws_iam_auths", IdentityAwsAuth = "identity_aws_auths",
IdentityOrgMembership = "identity_org_memberships", IdentityOrgMembership = "identity_org_memberships",
IdentityProjectMembership = "identity_project_memberships", IdentityProjectMembership = "identity_project_memberships",
IdentityProjectMembershipRole = "identity_project_membership_role", IdentityProjectMembershipRole = "identity_project_membership_role",
@@ -146,5 +146,5 @@ export enum ProjectUpgradeStatus {
export enum IdentityAuthMethod { export enum IdentityAuthMethod {
Univeral = "universal-auth", Univeral = "universal-auth",
GCP_AUTH = "gcp-auth", GCP_AUTH = "gcp-auth",
AWS_IAM_AUTH = "aws-iam-auth" AWS_AUTH = "aws-auth"
} }
@@ -70,10 +70,10 @@ export enum EventType {
ADD_IDENTITY_GCP_AUTH = "add-identity-gcp -auth", ADD_IDENTITY_GCP_AUTH = "add-identity-gcp -auth",
UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth", UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth",
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth", GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
LOGIN_IDENTITY_AWS_IAM_AUTH = "login-identity-aws-iam-auth", LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
ADD_IDENTITY_AWS_IAM_AUTH = "add-identity-aws-iam-auth", ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
UPDATE_IDENTITY_AWS_IAM_AUTH = "update-identity-aws-iam-auth", UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
GET_IDENTITY_AWS_IAM_AUTH = "get-identity-aws-iam-auth", GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
CREATE_ENVIRONMENT = "create-environment", CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment", UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment", DELETE_ENVIRONMENT = "delete-environment",
@@ -460,17 +460,17 @@ interface GetIdentityGcpAuthEvent {
}; };
} }
interface LoginIdentityAwsIamAuthEvent { interface LoginIdentityAwsAuthEvent {
type: EventType.LOGIN_IDENTITY_AWS_IAM_AUTH; type: EventType.LOGIN_IDENTITY_AWS_AUTH;
metadata: { metadata: {
identityId: string; identityId: string;
identityAwsIamAuthId: string; identityAwsAuthId: string;
identityAccessTokenId: string; identityAccessTokenId: string;
}; };
} }
interface AddIdentityAwsIamAuthEvent { interface AddIdentityAwsAuthEvent {
type: EventType.ADD_IDENTITY_AWS_IAM_AUTH; type: EventType.ADD_IDENTITY_AWS_AUTH;
metadata: { metadata: {
identityId: string; identityId: string;
stsEndpoint: string; stsEndpoint: string;
@@ -483,8 +483,8 @@ interface AddIdentityAwsIamAuthEvent {
}; };
} }
interface UpdateIdentityAwsIamAuthEvent { interface UpdateIdentityAwsAuthEvent {
type: EventType.UPDATE_IDENTITY_AWS_IAM_AUTH; type: EventType.UPDATE_IDENTITY_AWS_AUTH;
metadata: { metadata: {
identityId: string; identityId: string;
stsEndpoint?: string; stsEndpoint?: string;
@@ -497,8 +497,8 @@ interface UpdateIdentityAwsIamAuthEvent {
}; };
} }
interface GetIdentityAwsIamAuthEvent { interface GetIdentityAwsAuthEvent {
type: EventType.GET_IDENTITY_AWS_IAM_AUTH; type: EventType.GET_IDENTITY_AWS_AUTH;
metadata: { metadata: {
identityId: string; identityId: string;
}; };
@@ -762,10 +762,10 @@ export type Event =
| AddIdentityGcpAuthEvent | AddIdentityGcpAuthEvent
| UpdateIdentityGcpAuthEvent | UpdateIdentityGcpAuthEvent
| GetIdentityGcpAuthEvent | GetIdentityGcpAuthEvent
| LoginIdentityAwsIamAuthEvent | LoginIdentityAwsAuthEvent
| AddIdentityAwsIamAuthEvent | AddIdentityAwsAuthEvent
| UpdateIdentityAwsIamAuthEvent | UpdateIdentityAwsAuthEvent
| GetIdentityAwsIamAuthEvent | GetIdentityAwsAuthEvent
| CreateEnvironmentEvent | CreateEnvironmentEvent
| UpdateEnvironmentEvent | UpdateEnvironmentEvent
| DeleteEnvironmentEvent | DeleteEnvironmentEvent
+6 -3
View File
@@ -92,7 +92,7 @@ export const UNIVERSAL_AUTH = {
} }
} as const; } as const;
export const AWS_IAM_AUTH = { export const AWS_AUTH = {
LOGIN: { LOGIN: {
identityId: "The ID of the identity to login.", identityId: "The ID of the identity to login.",
iamHttpRequestMethod: "The HTTP request method used in the signed request.", iamHttpRequestMethod: "The HTTP request method used in the signed request.",
@@ -288,7 +288,8 @@ export const RAW_SECRETS = {
recursive: recursive:
"Whether or not to fetch all secrets from the specified base path, and all of its subdirectories. Note, the max depth is 20 deep.", "Whether or not to fetch all secrets from the specified base path, and all of its subdirectories. Note, the max depth is 20 deep.",
workspaceId: "The ID of the project to list secrets from.", workspaceId: "The ID of the project to list secrets from.",
workspaceSlug: "The slug of the project to list secrets from. This parameter is only usable by machine identities.", workspaceSlug:
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
environment: "The slug of the environment to list secrets from.", environment: "The slug of the environment to list secrets from.",
secretPath: "The secret path to list secrets from.", secretPath: "The secret path to list secrets from.",
includeImports: "Weather to include imported secrets or not." includeImports: "Weather to include imported secrets or not."
@@ -307,6 +308,7 @@ export const RAW_SECRETS = {
GET: { GET: {
secretName: "The name of the secret to get.", secretName: "The name of the secret to get.",
workspaceId: "The ID of the project to get the secret from.", workspaceId: "The ID of the project to get the secret from.",
workspaceSlug: "The slug of the project to get the secret from.",
environment: "The slug of the environment to get the secret from.", environment: "The slug of the environment to get the secret from.",
secretPath: "The path of the secret to get.", secretPath: "The path of the secret to get.",
version: "The version of the secret to get.", version: "The version of the secret to get.",
@@ -625,7 +627,8 @@ export const INTEGRATION = {
shouldAutoRedeploy: "Used by Render to trigger auto deploy.", shouldAutoRedeploy: "Used by Render to trigger auto deploy.",
secretGCPLabel: "The label for GCP secrets.", secretGCPLabel: "The label for GCP secrets.",
secretAWSTag: "The tags for AWS secrets.", secretAWSTag: "The tags for AWS secrets.",
kmsKeyId: "The ID of the encryption key from AWS KMS." kmsKeyId: "The ID of the encryption key from AWS KMS.",
shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store."
} }
}, },
UPDATE: { UPDATE: {
+6 -6
View File
@@ -78,8 +78,8 @@ import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
import { identityServiceFactory } from "@app/services/identity/identity-service"; import { identityServiceFactory } from "@app/services/identity/identity-service";
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal"; import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service"; import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { identityAwsIamAuthDALFactory } from "@app/services/identity-aws-iam-auth/identity-aws-iam-auth-dal"; import { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal";
import { identityAwsIamAuthServiceFactory } from "@app/services/identity-aws-iam-auth/identity-aws-iam-auth-service"; import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
import { identityGcpAuthDALFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-dal"; import { identityGcpAuthDALFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-dal";
import { identityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service"; import { identityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
@@ -205,7 +205,7 @@ export const registerRoutes = async (
const identityUaDAL = identityUaDALFactory(db); const identityUaDAL = identityUaDALFactory(db);
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db); const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
const identityAwsIamAuthDAL = identityAwsIamAuthDALFactory(db); const identityAwsAuthDAL = identityAwsAuthDALFactory(db);
const identityGcpAuthDAL = identityGcpAuthDALFactory(db); const identityGcpAuthDAL = identityGcpAuthDALFactory(db);
@@ -716,9 +716,9 @@ export const registerRoutes = async (
licenseService licenseService
}); });
const identityAWSIAMAuthService = identityAwsIamAuthServiceFactory({ const identityAwsAuthService = identityAwsAuthServiceFactory({
identityAccessTokenDAL, identityAccessTokenDAL,
identityAwsIamAuthDAL, identityAwsAuthDAL,
identityOrgMembershipDAL, identityOrgMembershipDAL,
identityDAL, identityDAL,
licenseService, licenseService,
@@ -794,7 +794,7 @@ export const registerRoutes = async (
identityProject: identityProjectService, identityProject: identityProjectService,
identityUa: identityUaService, identityUa: identityUaService,
identityGcpAuth: identityGcpAuthService, identityGcpAuth: identityGcpAuthService,
identityAwsIamAuth: identityAWSIAMAuthService, identityAwsAuth: identityAwsAuthService,
secretApprovalPolicy: sapService, secretApprovalPolicy: sapService,
accessApprovalPolicy: accessApprovalPolicyService, accessApprovalPolicy: accessApprovalPolicyService,
accessApprovalRequest: accessApprovalRequestService, accessApprovalRequest: accessApprovalRequestService,
@@ -1,8 +1,8 @@
import { z } from "zod"; import { z } from "zod";
import { IdentityAwsIamAuthsSchema } from "@app/db/schemas"; import { IdentityAwsAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { AWS_IAM_AUTH } from "@app/lib/api-docs"; import { AWS_AUTH } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -10,22 +10,22 @@ import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
import { import {
validateAccountIds, validateAccountIds,
validatePrincipalArns validatePrincipalArns
} from "@app/services/identity-aws-iam-auth/identity-aws-iam-auth-validators"; } from "@app/services/identity-aws-auth/identity-aws-auth-validators";
export const registerIdentityAwsIamAuthRouter = async (server: FastifyZodProvider) => { export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
method: "POST", method: "POST",
url: "/aws-iam-auth/login", url: "/aws-auth/login",
config: { config: {
rateLimit: writeLimit rateLimit: writeLimit
}, },
schema: { schema: {
description: "Login with AWS IAM Auth", description: "Login with AWS Auth",
body: z.object({ body: z.object({
identityId: z.string().describe(AWS_IAM_AUTH.LOGIN.identityId), identityId: z.string().describe(AWS_AUTH.LOGIN.identityId),
iamHttpRequestMethod: z.string().default("POST").describe(AWS_IAM_AUTH.LOGIN.iamHttpRequestMethod), iamHttpRequestMethod: z.string().default("POST").describe(AWS_AUTH.LOGIN.iamHttpRequestMethod),
iamRequestBody: z.string().describe(AWS_IAM_AUTH.LOGIN.iamRequestBody), iamRequestBody: z.string().describe(AWS_AUTH.LOGIN.iamRequestBody),
iamRequestHeaders: z.string().describe(AWS_IAM_AUTH.LOGIN.iamRequestHeaders) iamRequestHeaders: z.string().describe(AWS_AUTH.LOGIN.iamRequestHeaders)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -37,18 +37,18 @@ export const registerIdentityAwsIamAuthRouter = async (server: FastifyZodProvide
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityAwsIamAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityAwsAuth, accessToken, identityAccessToken, identityMembershipOrg } =
await server.services.identityAwsIamAuth.login(req.body); await server.services.identityAwsAuth.login(req.body);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId, orgId: identityMembershipOrg?.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_AWS_IAM_AUTH, type: EventType.LOGIN_IDENTITY_AWS_AUTH,
metadata: { metadata: {
identityId: identityAwsIamAuth.identityId, identityId: identityAwsAuth.identityId,
identityAccessTokenId: identityAccessToken.id, identityAccessTokenId: identityAccessToken.id,
identityAwsIamAuthId: identityAwsIamAuth.id identityAwsAuthId: identityAwsAuth.id
} }
} }
}); });
@@ -56,21 +56,21 @@ export const registerIdentityAwsIamAuthRouter = async (server: FastifyZodProvide
return { return {
accessToken, accessToken,
tokenType: "Bearer" as const, tokenType: "Bearer" as const,
expiresIn: identityAwsIamAuth.accessTokenTTL, expiresIn: identityAwsAuth.accessTokenTTL,
accessTokenMaxTTL: identityAwsIamAuth.accessTokenMaxTTL accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL
}; };
} }
}); });
server.route({ server.route({
method: "POST", method: "POST",
url: "/aws-iam-auth/identities/:identityId", url: "/aws-auth/identities/:identityId",
config: { config: {
rateLimit: writeLimit rateLimit: writeLimit
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: { schema: {
description: "Attach AWS IAM Auth configuration onto identity", description: "Attach AWS Auth configuration onto identity",
security: [ security: [
{ {
bearerAuth: [] bearerAuth: []
@@ -109,12 +109,12 @@ export const registerIdentityAwsIamAuthRouter = async (server: FastifyZodProvide
}), }),
response: { response: {
200: z.object({ 200: z.object({
identityAwsIamAuth: IdentityAwsIamAuthsSchema identityAwsAuth: IdentityAwsAuthsSchema
}) })
} }
}, },
handler: async (req) => { handler: async (req) => {
const identityAwsIamAuth = await server.services.identityAwsIamAuth.attachAwsIamAuth({ const identityAwsAuth = await server.services.identityAwsAuth.attachAwsAuth({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
@@ -125,35 +125,35 @@ export const registerIdentityAwsIamAuthRouter = async (server: FastifyZodProvide
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityAwsIamAuth.orgId, orgId: identityAwsAuth.orgId,
event: { event: {
type: EventType.ADD_IDENTITY_AWS_IAM_AUTH, type: EventType.ADD_IDENTITY_AWS_AUTH,
metadata: { metadata: {
identityId: identityAwsIamAuth.identityId, identityId: identityAwsAuth.identityId,
stsEndpoint: identityAwsIamAuth.stsEndpoint, stsEndpoint: identityAwsAuth.stsEndpoint,
allowedPrincipalArns: identityAwsIamAuth.allowedPrincipalArns, allowedPrincipalArns: identityAwsAuth.allowedPrincipalArns,
allowedAccountIds: identityAwsIamAuth.allowedAccountIds, allowedAccountIds: identityAwsAuth.allowedAccountIds,
accessTokenTTL: identityAwsIamAuth.accessTokenTTL, accessTokenTTL: identityAwsAuth.accessTokenTTL,
accessTokenMaxTTL: identityAwsIamAuth.accessTokenMaxTTL, accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityAwsIamAuth.accessTokenTrustedIps as TIdentityTrustedIp[], accessTokenTrustedIps: identityAwsAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityAwsIamAuth.accessTokenNumUsesLimit accessTokenNumUsesLimit: identityAwsAuth.accessTokenNumUsesLimit
} }
} }
}); });
return { identityAwsIamAuth }; return { identityAwsAuth };
} }
}); });
server.route({ server.route({
method: "PATCH", method: "PATCH",
url: "/aws-iam-auth/identities/:identityId", url: "/aws-auth/identities/:identityId",
config: { config: {
rateLimit: writeLimit rateLimit: writeLimit
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: { schema: {
description: "Update AWS IAM Auth configuration on identity", description: "Update AWS Auth configuration on identity",
security: [ security: [
{ {
bearerAuth: [] bearerAuth: []
@@ -185,12 +185,12 @@ export const registerIdentityAwsIamAuthRouter = async (server: FastifyZodProvide
}), }),
response: { response: {
200: z.object({ 200: z.object({
identityAwsIamAuth: IdentityAwsIamAuthsSchema identityAwsAuth: IdentityAwsAuthsSchema
}) })
} }
}, },
handler: async (req) => { handler: async (req) => {
const identityAwsIamAuth = await server.services.identityAwsIamAuth.updateAwsIamAuth({ const identityAwsAuth = await server.services.identityAwsAuth.updateAwsAuth({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
@@ -201,35 +201,35 @@ export const registerIdentityAwsIamAuthRouter = async (server: FastifyZodProvide
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityAwsIamAuth.orgId, orgId: identityAwsAuth.orgId,
event: { event: {
type: EventType.UPDATE_IDENTITY_AWS_IAM_AUTH, type: EventType.UPDATE_IDENTITY_AWS_AUTH,
metadata: { metadata: {
identityId: identityAwsIamAuth.identityId, identityId: identityAwsAuth.identityId,
stsEndpoint: identityAwsIamAuth.stsEndpoint, stsEndpoint: identityAwsAuth.stsEndpoint,
allowedPrincipalArns: identityAwsIamAuth.allowedPrincipalArns, allowedPrincipalArns: identityAwsAuth.allowedPrincipalArns,
allowedAccountIds: identityAwsIamAuth.allowedAccountIds, allowedAccountIds: identityAwsAuth.allowedAccountIds,
accessTokenTTL: identityAwsIamAuth.accessTokenTTL, accessTokenTTL: identityAwsAuth.accessTokenTTL,
accessTokenMaxTTL: identityAwsIamAuth.accessTokenMaxTTL, accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityAwsIamAuth.accessTokenTrustedIps as TIdentityTrustedIp[], accessTokenTrustedIps: identityAwsAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityAwsIamAuth.accessTokenNumUsesLimit accessTokenNumUsesLimit: identityAwsAuth.accessTokenNumUsesLimit
} }
} }
}); });
return { identityAwsIamAuth }; return { identityAwsAuth };
} }
}); });
server.route({ server.route({
method: "GET", method: "GET",
url: "/aws-iam-auth/identities/:identityId", url: "/aws-auth/identities/:identityId",
config: { config: {
rateLimit: readLimit rateLimit: readLimit
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: { schema: {
description: "Retrieve AWS IAM Auth configuration on identity", description: "Retrieve AWS Auth configuration on identity",
security: [ security: [
{ {
bearerAuth: [] bearerAuth: []
@@ -240,12 +240,12 @@ export const registerIdentityAwsIamAuthRouter = async (server: FastifyZodProvide
}), }),
response: { response: {
200: z.object({ 200: z.object({
identityAwsIamAuth: IdentityAwsIamAuthsSchema identityAwsAuth: IdentityAwsAuthsSchema
}) })
} }
}, },
handler: async (req) => { handler: async (req) => {
const identityAwsIamAuth = await server.services.identityAwsIamAuth.getAwsIamAuth({ const identityAwsAuth = await server.services.identityAwsAuth.getAwsAuth({
identityId: req.params.identityId, identityId: req.params.identityId,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
@@ -255,15 +255,15 @@ export const registerIdentityAwsIamAuthRouter = async (server: FastifyZodProvide
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityAwsIamAuth.orgId, orgId: identityAwsAuth.orgId,
event: { event: {
type: EventType.GET_IDENTITY_AWS_IAM_AUTH, type: EventType.GET_IDENTITY_AWS_AUTH,
metadata: { metadata: {
identityId: identityAwsIamAuth.identityId identityId: identityAwsAuth.identityId
} }
} }
}); });
return { identityAwsIamAuth }; return { identityAwsAuth };
} }
}); });
}; };
+3 -2
View File
@@ -2,7 +2,7 @@ import { registerAdminRouter } from "./admin-router";
import { registerAuthRoutes } from "./auth-router"; import { registerAuthRoutes } from "./auth-router";
import { registerProjectBotRouter } from "./bot-router"; import { registerProjectBotRouter } from "./bot-router";
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router"; import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
import { registerIdentityAwsIamAuthRouter } from "./identity-aws-iam-auth-router"; import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router"; import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
import { registerIdentityRouter } from "./identity-router"; import { registerIdentityRouter } from "./identity-router";
import { registerIdentityUaRouter } from "./identity-ua"; import { registerIdentityUaRouter } from "./identity-ua";
@@ -30,8 +30,9 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
await authRouter.register(registerAuthRoutes); await authRouter.register(registerAuthRoutes);
await authRouter.register(registerIdentityUaRouter); await authRouter.register(registerIdentityUaRouter);
await authRouter.register(registerIdentityGcpAuthRouter); await authRouter.register(registerIdentityGcpAuthRouter);
await authRouter.register(registerIdentityAwsIamAuthRouter);
await authRouter.register(registerIdentityAccessTokenRouter); await authRouter.register(registerIdentityAccessTokenRouter);
await authRouter.register(registerIdentityAccessTokenRouter);
await authRouter.register(registerIdentityAwsAuthRouter);
}, },
{ prefix: "/auth" } { prefix: "/auth" }
); );
@@ -66,7 +66,8 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
) )
.optional() .optional()
.describe(INTEGRATION.CREATE.metadata.secretAWSTag), .describe(INTEGRATION.CREATE.metadata.secretAWSTag),
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId) kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete)
}) })
.default({}) .default({})
}), }),
@@ -293,6 +293,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}), }),
querystring: z.object({ querystring: z.object({
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.GET.workspaceId), workspaceId: z.string().trim().optional().describe(RAW_SECRETS.GET.workspaceId),
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.GET.workspaceSlug),
environment: z.string().trim().optional().describe(RAW_SECRETS.GET.environment), environment: z.string().trim().optional().describe(RAW_SECRETS.GET.environment),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath), secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath),
version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version), version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version),
@@ -311,6 +312,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const { workspaceSlug } = req.query;
let { secretPath, environment, workspaceId } = req.query; let { secretPath, environment, workspaceId } = req.query;
if (req.auth.actor === ActorType.SERVICE) { if (req.auth.actor === ActorType.SERVICE) {
const scope = ServiceTokenScopes.parse(req.auth.serviceToken.scopes); const scope = ServiceTokenScopes.parse(req.auth.serviceToken.scopes);
@@ -322,7 +324,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
} }
} }
if (!workspaceId || !environment) throw new BadRequestError({ message: "Missing workspace id or environment" }); if (!environment) throw new BadRequestError({ message: "Missing environment" });
if (!workspaceId && !workspaceSlug)
throw new BadRequestError({ message: "You must provide workspaceSlug or workspaceId" });
const secret = await server.services.secret.getSecretByNameRaw({ const secret = await server.services.secret.getSecretByNameRaw({
actorId: req.permission.id, actorId: req.permission.id,
@@ -331,6 +335,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
environment, environment,
projectId: workspaceId, projectId: workspaceId,
projectSlug: workspaceSlug,
path: secretPath, path: secretPath,
secretName: req.params.secretName, secretName: req.params.secretName,
type: req.query.type, type: req.query.type,
@@ -339,7 +344,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
projectId: req.query.workspaceId, projectId: secret.workspace,
...req.auditLogInfo, ...req.auditLogInfo,
event: { event: {
type: EventType.GET_SECRET, type: EventType.GET_SECRET,
@@ -358,7 +363,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
distinctId: getTelemetryDistinctId(req), distinctId: getTelemetryDistinctId(req),
properties: { properties: {
numberOfSecrets: 1, numberOfSecrets: 1,
workspaceId, workspaceId: secret.workspace,
environment, environment,
secretPath: req.query.secretPath, secretPath: req.query.secretPath,
channel: getUserAgentType(req.headers["user-agent"]), channel: getUserAgentType(req.headers["user-agent"]),
@@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TIdentityAwsAuthDALFactory = ReturnType<typeof identityAwsAuthDALFactory>;
export const identityAwsAuthDALFactory = (db: TDbClient) => {
const awsAuthOrm = ormify(db, TableName.IdentityAwsAuth);
return awsAuthOrm;
};
@@ -16,48 +16,43 @@ import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TIdentityAwsIamAuthDALFactory } from "./identity-aws-iam-auth-dal"; import { TIdentityAwsAuthDALFactory } from "./identity-aws-auth-dal";
import { extractPrincipalArn } from "./identity-aws-iam-auth-fns"; import { extractPrincipalArn } from "./identity-aws-auth-fns";
import { import {
TAttachAWSIAMAuthDTO, TAttachAwsAuthDTO,
TAWSGetCallerIdentityHeaders, TAwsGetCallerIdentityHeaders,
TGetAWSIAMAuthDTO, TGetAwsAuthDTO,
TGetCallerIdentityResponse, TGetCallerIdentityResponse,
TLoginAWSIAMAuthDTO, TLoginAwsAuthDTO,
TUpdateAWSIAMAuthDTO TUpdateAwsAuthDTO
} from "./identity-aws-iam-auth-types"; } from "./identity-aws-auth-types";
type TIdentityAwsIamAuthServiceFactoryDep = { type TIdentityAwsAuthServiceFactoryDep = {
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
identityAwsIamAuthDAL: Pick<TIdentityAwsIamAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">; identityAwsAuthDAL: Pick<TIdentityAwsAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">; identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
identityDAL: Pick<TIdentityDALFactory, "updateById">; identityDAL: Pick<TIdentityDALFactory, "updateById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
}; };
export type TIdentityAwsIamAuthServiceFactory = ReturnType<typeof identityAwsIamAuthServiceFactory>; export type TIdentityAwsAuthServiceFactory = ReturnType<typeof identityAwsAuthServiceFactory>;
export const identityAwsIamAuthServiceFactory = ({ export const identityAwsAuthServiceFactory = ({
identityAccessTokenDAL, identityAccessTokenDAL,
identityAwsIamAuthDAL, identityAwsAuthDAL,
identityOrgMembershipDAL, identityOrgMembershipDAL,
identityDAL, identityDAL,
licenseService, licenseService,
permissionService permissionService
}: TIdentityAwsIamAuthServiceFactoryDep) => { }: TIdentityAwsAuthServiceFactoryDep) => {
const login = async ({ const login = async ({ identityId, iamHttpRequestMethod, iamRequestBody, iamRequestHeaders }: TLoginAwsAuthDTO) => {
identityId, const identityAwsAuth = await identityAwsAuthDAL.findOne({ identityId });
iamHttpRequestMethod, if (!identityAwsAuth) throw new UnauthorizedError();
iamRequestBody,
iamRequestHeaders
}: TLoginAWSIAMAuthDTO) => {
const identityAwsIamAuth = await identityAwsIamAuthDAL.findOne({ identityId });
if (!identityAwsIamAuth) throw new UnauthorizedError();
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityAwsIamAuth.identityId }); const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityAwsAuth.identityId });
const headers: TAWSGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString()); const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString());
const body: string = Buffer.from(iamRequestBody, "base64").toString(); const body: string = Buffer.from(iamRequestBody, "base64").toString();
const { const {
@@ -68,15 +63,15 @@ export const identityAwsIamAuthServiceFactory = ({
} }
}: { data: TGetCallerIdentityResponse } = await axios({ }: { data: TGetCallerIdentityResponse } = await axios({
method: iamHttpRequestMethod, method: iamHttpRequestMethod,
url: identityAwsIamAuth.stsEndpoint, url: identityAwsAuth.stsEndpoint,
headers, headers,
data: body data: body
}); });
if (identityAwsIamAuth.allowedAccountIds) { if (identityAwsAuth.allowedAccountIds) {
// validate if Account is in the list of allowed Account IDs // validate if Account is in the list of allowed Account IDs
const isAccountAllowed = identityAwsIamAuth.allowedAccountIds const isAccountAllowed = identityAwsAuth.allowedAccountIds
.split(",") .split(",")
.map((accountId) => accountId.trim()) .map((accountId) => accountId.trim())
.some((accountId) => accountId === Account); .some((accountId) => accountId === Account);
@@ -84,10 +79,10 @@ export const identityAwsIamAuthServiceFactory = ({
if (!isAccountAllowed) throw new UnauthorizedError(); if (!isAccountAllowed) throw new UnauthorizedError();
} }
if (identityAwsIamAuth.allowedPrincipalArns) { if (identityAwsAuth.allowedPrincipalArns) {
// validate if Arn is in the list of allowed Principal ARNs // validate if Arn is in the list of allowed Principal ARNs
const isArnAllowed = identityAwsIamAuth.allowedPrincipalArns const isArnAllowed = identityAwsAuth.allowedPrincipalArns
.split(",") .split(",")
.map((principalArn) => principalArn.trim()) .map((principalArn) => principalArn.trim())
.some((principalArn) => { .some((principalArn) => {
@@ -100,15 +95,15 @@ export const identityAwsIamAuthServiceFactory = ({
if (!isArnAllowed) throw new UnauthorizedError(); if (!isArnAllowed) throw new UnauthorizedError();
} }
const identityAccessToken = await identityAwsIamAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityAwsAuthDAL.transaction(async (tx) => {
const newToken = await identityAccessTokenDAL.create( const newToken = await identityAccessTokenDAL.create(
{ {
identityId: identityAwsIamAuth.identityId, identityId: identityAwsAuth.identityId,
isAccessTokenRevoked: false, isAccessTokenRevoked: false,
accessTokenTTL: identityAwsIamAuth.accessTokenTTL, accessTokenTTL: identityAwsAuth.accessTokenTTL,
accessTokenMaxTTL: identityAwsIamAuth.accessTokenMaxTTL, accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL,
accessTokenNumUses: 0, accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityAwsIamAuth.accessTokenNumUsesLimit accessTokenNumUsesLimit: identityAwsAuth.accessTokenNumUsesLimit
}, },
tx tx
); );
@@ -118,7 +113,7 @@ export const identityAwsIamAuthServiceFactory = ({
const appCfg = getConfig(); const appCfg = getConfig();
const accessToken = jwt.sign( const accessToken = jwt.sign(
{ {
identityId: identityAwsIamAuth.identityId, identityId: identityAwsAuth.identityId,
identityAccessTokenId: identityAccessToken.id, identityAccessTokenId: identityAccessToken.id,
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
} as TIdentityAccessTokenJwtPayload, } as TIdentityAccessTokenJwtPayload,
@@ -131,10 +126,10 @@ export const identityAwsIamAuthServiceFactory = ({
} }
); );
return { accessToken, identityAwsIamAuth, identityAccessToken, identityMembershipOrg }; return { accessToken, identityAwsAuth, identityAccessToken, identityMembershipOrg };
}; };
const attachAwsIamAuth = async ({ const attachAwsAuth = async ({
identityId, identityId,
stsEndpoint, stsEndpoint,
allowedPrincipalArns, allowedPrincipalArns,
@@ -147,12 +142,12 @@ export const identityAwsIamAuthServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actor, actor,
actorOrgId actorOrgId
}: TAttachAWSIAMAuthDTO) => { }: TAttachAwsAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity.authMethod) if (identityMembershipOrg.identity.authMethod)
throw new BadRequestError({ throw new BadRequestError({
message: "Failed to add AWS IAM Auth to already configured identity" message: "Failed to add AWS Auth to already configured identity"
}); });
if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) { if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) {
@@ -186,10 +181,11 @@ export const identityAwsIamAuthServiceFactory = ({
return extractIPDetails(accessTokenTrustedIp.ipAddress); return extractIPDetails(accessTokenTrustedIp.ipAddress);
}); });
const identityAwsIamAuth = await identityAwsIamAuthDAL.transaction(async (tx) => { const identityAwsAuth = await identityAwsAuthDAL.transaction(async (tx) => {
const doc = await identityAwsIamAuthDAL.create( const doc = await identityAwsAuthDAL.create(
{ {
identityId: identityMembershipOrg.identityId, identityId: identityMembershipOrg.identityId,
type: "iam",
stsEndpoint, stsEndpoint,
allowedPrincipalArns, allowedPrincipalArns,
allowedAccountIds, allowedAccountIds,
@@ -203,16 +199,16 @@ export const identityAwsIamAuthServiceFactory = ({
await identityDAL.updateById( await identityDAL.updateById(
identityMembershipOrg.identityId, identityMembershipOrg.identityId,
{ {
authMethod: IdentityAuthMethod.AWS_IAM_AUTH authMethod: IdentityAuthMethod.AWS_AUTH
}, },
tx tx
); );
return doc; return doc;
}); });
return { ...identityAwsIamAuth, orgId: identityMembershipOrg.orgId }; return { ...identityAwsAuth, orgId: identityMembershipOrg.orgId };
}; };
const updateAwsIamAuth = async ({ const updateAwsAuth = async ({
identityId, identityId,
stsEndpoint, stsEndpoint,
allowedPrincipalArns, allowedPrincipalArns,
@@ -225,20 +221,19 @@ export const identityAwsIamAuthServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actor, actor,
actorOrgId actorOrgId
}: TUpdateAWSIAMAuthDTO) => { }: TUpdateAwsAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_IAM_AUTH) if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH)
throw new BadRequestError({ throw new BadRequestError({
message: "Failed to update AWS IAM Auth" message: "Failed to update AWS Auth"
}); });
const identityAwsIamAuth = await identityAwsIamAuthDAL.findOne({ identityId }); const identityAwsAuth = await identityAwsAuthDAL.findOne({ identityId });
if ( if (
(accessTokenMaxTTL || identityAwsIamAuth.accessTokenMaxTTL) > 0 && (accessTokenMaxTTL || identityAwsAuth.accessTokenMaxTTL) > 0 &&
(accessTokenTTL || identityAwsIamAuth.accessTokenMaxTTL) > (accessTokenTTL || identityAwsAuth.accessTokenMaxTTL) > (accessTokenMaxTTL || identityAwsAuth.accessTokenMaxTTL)
(accessTokenMaxTTL || identityAwsIamAuth.accessTokenMaxTTL)
) { ) {
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
@@ -270,7 +265,7 @@ export const identityAwsIamAuthServiceFactory = ({
return extractIPDetails(accessTokenTrustedIp.ipAddress); return extractIPDetails(accessTokenTrustedIp.ipAddress);
}); });
const updatedAwsIamAuth = await identityAwsIamAuthDAL.updateById(identityAwsIamAuth.id, { const updatedAwsAuth = await identityAwsAuthDAL.updateById(identityAwsAuth.id, {
stsEndpoint, stsEndpoint,
allowedPrincipalArns, allowedPrincipalArns,
allowedAccountIds, allowedAccountIds,
@@ -282,18 +277,18 @@ export const identityAwsIamAuthServiceFactory = ({
: undefined : undefined
}); });
return { ...updatedAwsIamAuth, orgId: identityMembershipOrg.orgId }; return { ...updatedAwsAuth, orgId: identityMembershipOrg.orgId };
}; };
const getAwsIamAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetAWSIAMAuthDTO) => { const getAwsAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetAwsAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_IAM_AUTH) if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH)
throw new BadRequestError({ throw new BadRequestError({
message: "The identity does not have AWS IAM Auth attached" message: "The identity does not have AWS Auth attached"
}); });
const awsIamIdentityAuth = await identityAwsIamAuthDAL.findOne({ identityId }); const awsIdentityAuth = await identityAwsAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission(
actor, actor,
@@ -303,13 +298,13 @@ export const identityAwsIamAuthServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
return { ...awsIamIdentityAuth, orgId: identityMembershipOrg.orgId }; return { ...awsIdentityAuth, orgId: identityMembershipOrg.orgId };
}; };
return { return {
login, login,
attachAwsIamAuth, attachAwsAuth,
updateAwsIamAuth, updateAwsAuth,
getAwsIamAuth getAwsAuth
}; };
}; };
@@ -1,13 +1,13 @@
import { TProjectPermission } from "@app/lib/types"; import { TProjectPermission } from "@app/lib/types";
export type TLoginAWSIAMAuthDTO = { export type TLoginAwsAuthDTO = {
identityId: string; identityId: string;
iamHttpRequestMethod: string; iamHttpRequestMethod: string;
iamRequestBody: string; iamRequestBody: string;
iamRequestHeaders: string; iamRequestHeaders: string;
}; };
export type TAttachAWSIAMAuthDTO = { export type TAttachAwsAuthDTO = {
identityId: string; identityId: string;
stsEndpoint: string; stsEndpoint: string;
allowedPrincipalArns: string; allowedPrincipalArns: string;
@@ -18,7 +18,7 @@ export type TAttachAWSIAMAuthDTO = {
accessTokenTrustedIps: { ipAddress: string }[]; accessTokenTrustedIps: { ipAddress: string }[];
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TUpdateAWSIAMAuthDTO = { export type TUpdateAwsAuthDTO = {
identityId: string; identityId: string;
stsEndpoint?: string; stsEndpoint?: string;
allowedPrincipalArns?: string; allowedPrincipalArns?: string;
@@ -29,11 +29,11 @@ export type TUpdateAWSIAMAuthDTO = {
accessTokenTrustedIps?: { ipAddress: string }[]; accessTokenTrustedIps?: { ipAddress: string }[];
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TGetAWSIAMAuthDTO = { export type TGetAwsAuthDTO = {
identityId: string; identityId: string;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TAWSGetCallerIdentityHeaders = { export type TAwsGetCallerIdentityHeaders = {
"Content-Type": string; "Content-Type": string;
Host: string; Host: string;
"X-Amz-Date": string; "X-Amz-Date": string;
@@ -1,11 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TIdentityAwsIamAuthDALFactory = ReturnType<typeof identityAwsIamAuthDALFactory>;
export const identityAwsIamAuthDALFactory = (db: TDbClient) => {
const awsIamAuthOrm = ormify(db, TableName.IdentityAwsIamAuth);
return awsIamAuthOrm;
};
@@ -52,7 +52,7 @@ export const identityUaServiceFactory = ({
}: TIdentityUaServiceFactoryDep) => { }: TIdentityUaServiceFactoryDep) => {
const login = async (clientId: string, clientSecret: string, ip: string) => { const login = async (clientId: string, clientSecret: string, ip: string) => {
const identityUa = await identityUaDAL.findOne({ clientId }); const identityUa = await identityUaDAL.findOne({ clientId });
if (!identityUa) throw new UnauthorizedError(); if (!identityUa) throw new UnauthorizedError({ message: "Invalid credentials" });
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityUa.identityId }); const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityUa.identityId });
@@ -68,7 +68,7 @@ export const identityUaServiceFactory = ({
const validClientSecretInfo = clientSecrtInfo.find(({ clientSecretHash }) => const validClientSecretInfo = clientSecrtInfo.find(({ clientSecretHash }) =>
bcrypt.compareSync(clientSecret, clientSecretHash) bcrypt.compareSync(clientSecret, clientSecretHash)
); );
if (!validClientSecretInfo) throw new UnauthorizedError(); if (!validClientSecretInfo) throw new UnauthorizedError({ message: "Invalid credentials" });
const { clientSecretTTL, clientSecretNumUses, clientSecretNumUsesLimit } = validClientSecretInfo; const { clientSecretTTL, clientSecretNumUses, clientSecretNumUsesLimit } = validClientSecretInfo;
if (Number(clientSecretTTL) > 0) { if (Number(clientSecretTTL) > 0) {
@@ -517,20 +517,22 @@ const syncSecretsAWSParameterStore = async ({
}) })
); );
// Identify secrets to delete if (!metadata.shouldDisableDelete) {
await Promise.all( // Identify secrets to delete
Object.keys(awsParameterStoreSecretsObj).map(async (key) => { await Promise.all(
if (!(key in secrets)) { Object.keys(awsParameterStoreSecretsObj).map(async (key) => {
// case: if (!(key in secrets)) {
// -> delete secret // case:
await ssm // -> delete secret
.deleteParameter({ await ssm
Name: awsParameterStoreSecretsObj[key].Name as string .deleteParameter({
}) Name: awsParameterStoreSecretsObj[key].Name as string
.promise(); })
} .promise();
}) }
); })
);
}
}; };
/** /**
@@ -27,6 +27,7 @@ export type TCreateIntegrationDTO = {
value: string; value: string;
}[]; }[];
kmsKeyId?: string; kmsKeyId?: string;
shouldDisableDelete?: boolean;
}; };
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
+4
View File
@@ -546,6 +546,10 @@ export const orgServiceFactory = ({
code code
}); });
await userDAL.updateById(user.id, {
isEmailVerified: true
});
if (user.isAccepted) { if (user.isAccepted) {
// this means user has already completed signup process // this means user has already completed signup process
// isAccepted is set true when keys are exchanged // isAccepted is set true when keys are exchanged
@@ -972,7 +972,8 @@ export const secretServiceFactory = ({
path, path,
actor, actor,
environment, environment,
projectId, projectId: workspaceId,
projectSlug,
actorId, actorId,
actorOrgId, actorOrgId,
actorAuthMethod, actorAuthMethod,
@@ -980,6 +981,8 @@ export const secretServiceFactory = ({
includeImports, includeImports,
version version
}: TGetASecretRawDTO) => { }: TGetASecretRawDTO) => {
const projectId = workspaceId || (await projectDAL.findProjectBySlug(projectSlug as string, actorOrgId)).id;
const botKey = await projectBotService.getBotKey(projectId); const botKey = await projectBotService.getBotKey(projectId);
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
+3 -1
View File
@@ -152,7 +152,9 @@ export type TGetASecretRawDTO = {
type: "shared" | "personal"; type: "shared" | "personal";
includeImports?: boolean; includeImports?: boolean;
version?: number; version?: number;
} & TProjectPermission; projectSlug?: string;
projectId?: string;
} & Omit<TProjectPermission, "projectId">;
export type TCreateSecretRawDTO = TProjectPermission & { export type TCreateSecretRawDTO = TProjectPermission & {
secretPath: string; secretPath: string;
@@ -51,7 +51,7 @@ As a result, the 3 main concepts that are important to understand are:
- **[Identities](/documentation/platform/identities/overview)**: users or machines with a set permissions assigned to them. - **[Identities](/documentation/platform/identities/overview)**: users or machines with a set permissions assigned to them.
- **[Clients](/integrations/platforms/kubernetes)**: Infisical-developed tools for managing secrets in various infrastructure components (e.g., [Kubernetes Operator](/integrations/platforms/kubernetes), [Infisical Agent](/integrations/platforms/infisical-agent), [CLI](/cli/usage), [SDKs](/sdks/overview), [API](/api-reference/overview/introduction), [Web Dashboard](/documentation/platform/organization)). - **[Clients](/integrations/platforms/kubernetes)**: Infisical-developed tools for managing secrets in various infrastructure components (e.g., [Kubernetes Operator](/integrations/platforms/kubernetes), [Infisical Agent](/integrations/platforms/infisical-agent), [CLI](/cli/usage), [SDKs](/sdks/overview), [API](/api-reference/overview/introduction), [Web Dashboard](/documentation/platform/organization)).
- **[Authentication Methods](/documentation/platform/identities/universal-auth)**: ways for Identities to authenticate inside different clients (e.g., SAML SSO for Web Dashboard, Universal Auth for Infisical Agent, AWS IAM Auth etc.). - **[Authentication Methods](/documentation/platform/identities/universal-auth)**: ways for Identities to authenticate inside different clients (e.g., SAML SSO for Web Dashboard, Universal Auth for Infisical Agent, AWS Auth etc.).
## How to get started with Infisical? ## How to get started with Infisical?
@@ -1,34 +1,60 @@
--- ---
title: AWS IAM Auth title: AWS Auth
description: "Learn how to authenticate with Infisical for EC2 instances, Lambda functions, and other IAM principals." description: "Learn how to authenticate with Infisical for EC2 instances, Lambda functions, and other IAM principals."
--- ---
**AWS IAM Auth** is an AWS-native authentication method for IAM principals like EC2 instances or Lambda functions to access Infisical. **AWS Auth** is an AWS-native authentication method for IAM principals like EC2 instances or Lambda functions to access Infisical.
## Diagram
The following sequence digram illustrates the AWS Auth workflow for authenticating AWS IAM principals with Infisical.
```mermaid
sequenceDiagram
participant Client as Client
participant Infis as Infisical
participant AWS as AWS STS
Note over Client,Client: Step 1: Sign GetCallerIdentityQuery
Note over Client,Infis: Step 2: Login Operation
Client->>Infis: Send signed query details /api/v1/auth/aws-auth/login
Note over Infis,AWS: Step 3: Query verification
Infis->>AWS: Forward signed GetCallerIdentity query
AWS-->>Infis: Return IAM user/role details
Note over Infis: Step 4: Identity Property Validation
Infis->>Client: Return short-lived access token
Note over Client,Infis: Step 4: Access Infisical API with Token
Client->>Infis: Make authenticated requests using the short-lived access token
```
## Concept ## Concept
At a high-level, Infisical authenticates an IAM principal by verifying its identity and checking that it meets specific requirements (e.g. it is an allowed IAM principal ARN) at the `/api/v1/auth/aws-iam-auth/login` endpoint. If successful, At a high-level, Infisical authenticates an IAM principal by verifying its identity and checking that it meets specific requirements (e.g. it is an allowed IAM principal ARN) at the `/api/v1/auth/aws-auth/login` endpoint. If successful,
then Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API. then Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API.
In AWS IAM Auth, an IAM principal signs a `GetCallerIdentity` query using the [AWS Signature v4 algorithm](https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html); this is done using the credentials from the AWS environment where the IAM principal is running. In AWS Auth, an IAM principal signs a `GetCallerIdentity` query using the [AWS Signature v4 algorithm](https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html); this is done using the credentials from the AWS environment where the IAM principal is running.
The query data including the request method, request body, and request headers are sent to Infisical afterwhich Infisical forwards the signed query to AWS STS API via the [sts:GetCallerIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html) method to verify and obtain the identity of the IAM principal. The query data including the request method, request body, and request headers are sent to Infisical afterwhich Infisical forwards the signed query to AWS STS API via the [sts:GetCallerIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html) method to verify and obtain the identity of the IAM principal.
Once obtained, the identity information is verified against specified requirements such as if the associated IAM principal ARN is allowed to authenticate with Infisical. If all is well, Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API. Once obtained, the identity information is verified against specified requirements such as if the associated IAM principal ARN is allowed to authenticate with Infisical. If all is well, Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API.
<Note> <Note>
We recommend using one of Infisical's clients like SDKs or the Infisical Agent We recommend using one of Infisical's clients like SDKs or the Infisical Agent
to authenticate with Infisical using AWS IAM Auth as they handle the to authenticate with Infisical using AWS Auth as they handle the
authentication process including the signed `GetCallerIdentity` query authentication process including the signed `GetCallerIdentity` query
construction for you. construction for you.
Also, note that Infisical needs network-level access to send requests to the AWS STS API Also, note that Infisical needs network-level access to send requests to the AWS STS API
as part of the AWS IAM Auth workflow. as part of the AWS Auth workflow.
</Note> </Note>
## Workflow ## Workflow
In the following steps, we explore how to create and use identities for your workloads and applications on AWS to In the following steps, we explore how to create and use identities for your workloads and applications on AWS to
access the Infisical API using the AWS IAM authentication method. access the Infisical API using the AWS Auth authentication method.
<Steps> <Steps>
<Step title="Creating an identity"> <Step title="Creating an identity">
@@ -45,9 +71,9 @@ access the Infisical API using the AWS IAM authentication method.
- Name (required): A friendly name for the identity. - Name (required): A friendly name for the identity.
- Role (required): A role from the **Organization Roles** tab for the identity to assume. The organization role assigned will determine what organization level resources this identity can have access to. - Role (required): A role from the **Organization Roles** tab for the identity to assume. The organization role assigned will determine what organization level resources this identity can have access to.
Once you've created an identity, you'll be prompted to configure the authentication method for it. Here, select **AWS IAM Auth**. Once you've created an identity, you'll be prompted to configure the authentication method for it. Here, select **AWS Auth**.
![identities create iam auth method](/images/platform/identities/identities-org-create-aws-iam-auth-method.png) ![identities create aws auth method](/images/platform/identities/identities-org-create-aws-auth-method.png)
Here's some more guidance on each field: Here's some more guidance on each field:
@@ -71,7 +97,7 @@ access the Infisical API using the AWS IAM authentication method.
![identities project create](/images/platform/identities/identities-project-create.png) ![identities project create](/images/platform/identities/identities-project-create.png)
</Step> </Step>
<Step title="Accessing the Infisical API with the identity"> <Step title="Accessing the Infisical API with the identity">
To access the Infisical API as the identity, you need to construct a signed `GetCallerIdentity` query using the [AWS Signature v4 algorithm](https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html) and make a request to the `/api/v1/auth/aws-iam-auth/login` endpoint containing the query data To access the Infisical API as the identity, you need to construct a signed `GetCallerIdentity` query using the [AWS Signature v4 algorithm](https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html) and make a request to the `/api/v1/auth/aws-auth/login` endpoint containing the query data
in exchange for an access token. in exchange for an access token.
We provide a few code examples below of how you can authenticate with Infisical from inside a Lambda function, EC2 instance, etc. and obtain an access token to access the [Infisical API](/api-reference/overview/introduction). We provide a few code examples below of how you can authenticate with Infisical from inside a Lambda function, EC2 instance, etc. and obtain an access token to access the [Infisical API](/api-reference/overview/introduction).
@@ -119,7 +145,7 @@ access the Infisical API using the AWS IAM authentication method.
const identityId = "<your-identity-id>"; const identityId = "<your-identity-id>";
const { data } = await axios.post( const { data } = await axios.post(
`${infisicalUrl}/api/v1/auth/aws-iam-auth/login`, `${infisicalUrl}/api/v1/auth/aws-auth/login`,
{ {
identityId, identityId,
iamHttpRequestMethod: "POST", iamHttpRequestMethod: "POST",
@@ -191,7 +217,7 @@ access the Infisical API using the AWS IAM authentication method.
const infisicalUrl = "https://app.infisical.com"; // or your self-hosted Infisical URL const infisicalUrl = "https://app.infisical.com"; // or your self-hosted Infisical URL
const identityId = "<your-identity-id>"; const identityId = "<your-identity-id>";
const { data } = await axios.post(`${infisicalUrl}/api/v1/auth/aws-iam-auth/login`, { const { data } = await axios.post(`${infisicalUrl}/api/v1/auth/aws-auth/login`, {
identityId, identityId,
iamHttpRequestMethod: "POST", iamHttpRequestMethod: "POST",
iamRequestUrl: Buffer.from(iamRequestURL).toString("base64"), iamRequestUrl: Buffer.from(iamRequestURL).toString("base64"),
@@ -239,7 +265,7 @@ access the Infisical API using the AWS IAM authentication method.
#### Sample request #### Sample request
```bash Request ```bash Request
curl --location --request POST 'https://app.infisical.com/api/v1/auth/aws-iam-auth/login' \ curl --location --request POST 'https://app.infisical.com/api/v1/auth/aws-auth/login' \
--header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'identityId=...' \ --data-urlencode 'identityId=...' \
--data-urlencode 'iamHttpRequestMethod=...' \ --data-urlencode 'iamHttpRequestMethod=...' \
@@ -263,7 +289,7 @@ access the Infisical API using the AWS IAM authentication method.
</AccordionGroup> </AccordionGroup>
<Tip> <Tip>
We recommend using one of Infisical's clients like SDKs or the Infisical Agent to authenticate with Infisical using AWS IAM Auth as they handle the authentication process including the signed `GetCallerIdentity` query construction for you. We recommend using one of Infisical's clients like SDKs or the Infisical Agent to authenticate with Infisical using AWS Auth as they handle the authentication process including the signed `GetCallerIdentity` query construction for you.
</Tip> </Tip>
<Note> <Note>
@@ -7,7 +7,7 @@ description: "Learn how to use Machine Identities to programmatically interact w
An Infisical machine identity is an entity that represents a workload or application that require access to various resources in Infisical. This is conceptually similar to an IAM user in AWS or service account in Google Cloud Platform (GCP). An Infisical machine identity is an entity that represents a workload or application that require access to various resources in Infisical. This is conceptually similar to an IAM user in AWS or service account in Google Cloud Platform (GCP).
Each identity must authenticate with the Infisical API using a supported authentication method like [Universal Auth](/documentation/platform/identities/universal-auth), [AWS IAM Auth](/documentation/platform/identities/aws-iam-auth), or [GCP IAM Auth](/documentation/platform/identities/gcp-auth) to get back a short-lived access token to be used in subsequent requests. Each identity must authenticate with the Infisical API using a supported authentication method like [Universal Auth](/documentation/platform/identities/universal-auth), [AWS Auth](/documentation/platform/identities/aws-auth), or [GCP Auth](/documentation/platform/identities/gcp-auth) to get back a short-lived access token to be used in subsequent requests.
![organization identities](/images/platform/organization/organization-machine-identities.png) ![organization identities](/images/platform/organization/organization-machine-identities.png)
@@ -38,8 +38,8 @@ Machine Identity support for the rest of the clients is planned to be released i
To interact with various resources in Infisical, Machine Identities are able to authenticate using: To interact with various resources in Infisical, Machine Identities are able to authenticate using:
- [Universal Auth](/documentation/platform/identities/universal-auth): A platform-agnostic authentication method that can be configured on an identity suitable to authenticate from any platform/environment. - [Universal Auth](/documentation/platform/identities/universal-auth): A platform-agnostic authentication method that can be configured on an identity suitable to authenticate from any platform/environment.
- [AWS IAM Auth](/documentation/platform/identities/aws-iam-auth): An AWS-native authentication method for IAM principals like EC2 instances or Lambda functions to authenticate with Infisical. - [AWS Auth](/documentation/platform/identities/aws-auth): An AWS-native authentication method for IAM principals like EC2 instances or Lambda functions to authenticate with Infisical.
- [GCP IAM Auth](/documentation/platform/identities/gcp-auth): A GCP-native authentication method for IAM service accounts to authenticate with Infisical. - [GCP Auth](/documentation/platform/identities/gcp-auth): A GCP-native authentication method for IAM service accounts and GCE instances to authenticate with Infisical.
## FAQ ## FAQ
@@ -5,6 +5,25 @@ description: "Learn how to authenticate to Infisical from any platform or enviro
**Universal Auth** is a platform-agnostic authentication method that can be configured for a [machine identity](/documentation/platform/identities/machine-identities) suitable to authenticate from any platform/environment. **Universal Auth** is a platform-agnostic authentication method that can be configured for a [machine identity](/documentation/platform/identities/machine-identities) suitable to authenticate from any platform/environment.
## Diagram
The following sequence digram illustrates the Universal Auth workflow for authenticating clients with Infisical.
```mermaid
sequenceDiagram
participant Client as Client
participant Infis as Infisical
Note over Client,Infis: Step 1: Login Operation
Client->>Infis: Send Client ID and Client Secret
Note over Infis: Step 2: Client ID and Client Secret validation
Infis->>Client: Return short-lived access token
Note over Client,Infis: Step 3: Access Infisical API with Token
Client->>Infis: Make authenticated requests using the short-lived access token
```
## Concept ## Concept
In this method, Infisical authenticates an identity by verifying the credentials issued for it at the `/api/v1/auth/universal-auth/login` endpoint. If successful, In this method, Infisical authenticates an identity by verifying the credentials issued for it at the `/api/v1/auth/universal-auth/login` endpoint. If successful,
@@ -12,7 +31,7 @@ then Infisical returns a short-lived access token that can be used to make authe
In Universal Auth, an identity is given a **Client ID** and one or more **Client Secret(s)**. Together, a **Client ID** and **Client Secret** can be exchanged for a short-lived access token to authenticate with the Infisical API. In Universal Auth, an identity is given a **Client ID** and one or more **Client Secret(s)**. Together, a **Client ID** and **Client Secret** can be exchanged for a short-lived access token to authenticate with the Infisical API.
## Workflow ## Guide
In the following steps, we explore how to create and use identities for your workloads and applications to access the Infisical API In the following steps, we explore how to create and use identities for your workloads and applications to access the Infisical API
using the Universal Auth authentication method. using the Universal Auth authentication method.

Before

Width:  |  Height:  |  Size: 538 KiB

After

Width:  |  Height:  |  Size: 538 KiB

@@ -77,6 +77,8 @@ spec:
projectSlug: <project-slug> projectSlug: <project-slug>
envSlug: <env-slug> # "dev", "staging", "prod", etc.. envSlug: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: "<secrets-path>" # Root is "/" secretsPath: "<secrets-path>" # Root is "/"
recursive: true # Fetch all secrets from the specified path and all sub-directories. Default is false.
credentialsRef: credentialsRef:
secretName: universal-auth-credentials secretName: universal-auth-credentials
secretNamespace: default secretNamespace: default
@@ -89,6 +91,7 @@ spec:
secretsScope: secretsScope:
envSlug: <env-slug> envSlug: <env-slug>
secretsPath: <secrets-path> # Root is "/" secretsPath: <secrets-path> # Root is "/"
recursive: true # Fetch all secrets from the specified path and all sub-directories. Default is false.
managedSecretReference: managedSecretReference:
secretName: managed-secret secretName: managed-secret
+1 -1
View File
@@ -153,8 +153,8 @@
"documentation/platform/auth-methods/email-password", "documentation/platform/auth-methods/email-password",
"documentation/platform/token", "documentation/platform/token",
"documentation/platform/identities/universal-auth", "documentation/platform/identities/universal-auth",
"documentation/platform/identities/aws-iam-auth",
"documentation/platform/identities/gcp-auth", "documentation/platform/identities/gcp-auth",
"documentation/platform/identities/aws-auth",
"documentation/platform/mfa", "documentation/platform/mfa",
{ {
"group": "SSO", "group": "SSO",
@@ -31,6 +31,7 @@ export const SecretPathInput = ({
const [inputValue, setInputValue] = useState(propValue ?? ""); const [inputValue, setInputValue] = useState(propValue ?? "");
const [secretPath, setSecretPath] = useState("/"); const [secretPath, setSecretPath] = useState("/");
const [suggestions, setSuggestions] = useState<string[]>([]); const [suggestions, setSuggestions] = useState<string[]>([]);
const [isInputFocused, setIsInputFocus] = useState(false);
const [highlightedIndex, setHighlightedIndex] = useState(-1); const [highlightedIndex, setHighlightedIndex] = useState(-1);
const debouncedInputValue = useDebounce(inputValue, 200); const debouncedInputValue = useDebounce(inputValue, 200);
@@ -55,7 +56,9 @@ export const SecretPathInput = ({
) { ) {
setSecretPath(debouncedInputValue); setSecretPath(debouncedInputValue);
} }
}, [debouncedInputValue]);
useEffect(() => {
// filter suggestions based on matching // filter suggestions based on matching
const searchFragment = debouncedInputValue.split("/").pop() || ""; const searchFragment = debouncedInputValue.split("/").pop() || "";
const filteredSuggestions = folders const filteredSuggestions = folders
@@ -65,7 +68,7 @@ export const SecretPathInput = ({
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
setSuggestions(filteredSuggestions); setSuggestions(filteredSuggestions);
}, [debouncedInputValue]); }, [debouncedInputValue, folders]);
const handleSuggestionSelect = (selectedIndex: number) => { const handleSuggestionSelect = (selectedIndex: number) => {
if (!suggestions[selectedIndex]) { if (!suggestions[selectedIndex]) {
@@ -75,7 +78,7 @@ export const SecretPathInput = ({
const validPaths = inputValue.split("/"); const validPaths = inputValue.split("/");
validPaths.pop(); validPaths.pop();
const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}`; const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}/`;
onChange?.(newValue); onChange?.(newValue);
setInputValue(newValue); setInputValue(newValue);
setSecretPath(newValue); setSecretPath(newValue);
@@ -108,7 +111,7 @@ export const SecretPathInput = ({
return ( return (
<Popover.Root <Popover.Root
open={suggestions.length > 0 && inputValue.length > 1} open={suggestions.length > 0 && isInputFocused}
onOpenChange={() => { onOpenChange={() => {
setHighlightedIndex(-1); setHighlightedIndex(-1);
}} }}
@@ -119,6 +122,8 @@ export const SecretPathInput = ({
type="text" type="text"
autoComplete="off" autoComplete="off"
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onFocus={() => setIsInputFocus(true)}
onBlur={() => setIsInputFocus(false)}
value={inputValue} value={inputValue}
onChange={handleInputChange} onChange={handleInputChange}
className={containerClassName} className={containerClassName}
@@ -150,8 +155,9 @@ export const SecretPathInput = ({
key={`secret-reference-secret-${i + 1}`} key={`secret-reference-secret-${i + 1}`}
> >
<div <div
className={`${highlightedIndex === i ? "bg-gray-600" : "" className={`${
} text-md relative mb-0.5 flex w-full cursor-pointer select-none items-center justify-between rounded-md px-2 py-1 outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-500`} highlightedIndex === i ? "bg-gray-600" : ""
} text-md relative mb-0.5 flex w-full cursor-pointer select-none items-center justify-between rounded-md px-2 py-1 outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-500`}
> >
<div className="flex gap-2"> <div className="flex gap-2">
<div className="flex items-center text-yellow-700"> <div className="flex items-center text-yellow-700">
@@ -3,5 +3,5 @@ import { IdentityAuthMethod } from "./enums";
export const identityAuthToNameMap: { [I in IdentityAuthMethod]: string } = { export const identityAuthToNameMap: { [I in IdentityAuthMethod]: string } = {
[IdentityAuthMethod.UNIVERSAL_AUTH]: "Universal Auth", [IdentityAuthMethod.UNIVERSAL_AUTH]: "Universal Auth",
[IdentityAuthMethod.GCP_AUTH]: "GCP Auth", [IdentityAuthMethod.GCP_AUTH]: "GCP Auth",
[IdentityAuthMethod.AWS_IAM_AUTH]: "AWS IAM Auth" [IdentityAuthMethod.AWS_AUTH]: "AWS Auth"
}; };
+1 -1
View File
@@ -1,5 +1,5 @@
export enum IdentityAuthMethod { export enum IdentityAuthMethod {
UNIVERSAL_AUTH = "universal-auth", UNIVERSAL_AUTH = "universal-auth",
GCP_AUTH = "gcp-auth", GCP_AUTH = "gcp-auth",
AWS_IAM_AUTH = "aws-iam-auth" AWS_AUTH = "aws-auth"
} }
+4 -5
View File
@@ -1,7 +1,7 @@
export { identityAuthToNameMap } from "./constants"; export { identityAuthToNameMap } from "./constants";
export { IdentityAuthMethod } from "./enums"; export { IdentityAuthMethod } from "./enums";
export { export {
useAddIdentityAwsIamAuth, useAddIdentityAwsAuth,
useAddIdentityGcpAuth, useAddIdentityGcpAuth,
useAddIdentityUniversalAuth, useAddIdentityUniversalAuth,
useCreateIdentity, useCreateIdentity,
@@ -9,12 +9,11 @@ export {
useDeleteIdentity, useDeleteIdentity,
useRevokeIdentityUniversalAuthClientSecret, useRevokeIdentityUniversalAuthClientSecret,
useUpdateIdentity, useUpdateIdentity,
useUpdateIdentityAwsIamAuth, useUpdateIdentityAwsAuth,
useUpdateIdentityGcpAuth, useUpdateIdentityGcpAuth,
useUpdateIdentityUniversalAuth useUpdateIdentityUniversalAuth} from "./mutations";
} from "./mutations";
export { export {
useGetIdentityAwsIamAuth, useGetIdentityAwsAuth,
useGetIdentityGcpAuth, useGetIdentityGcpAuth,
useGetIdentityUniversalAuth, useGetIdentityUniversalAuth,
useGetIdentityUniversalAuthClientSecrets useGetIdentityUniversalAuthClientSecrets
+15 -15
View File
@@ -5,7 +5,7 @@ import { apiRequest } from "@app/config/request";
import { organizationKeys } from "../organization/queries"; import { organizationKeys } from "../organization/queries";
import { identitiesKeys } from "./queries"; import { identitiesKeys } from "./queries";
import { import {
AddIdentityAwsIamAuthDTO, AddIdentityAwsAuthDTO,
AddIdentityGcpAuthDTO, AddIdentityGcpAuthDTO,
AddIdentityUniversalAuthDTO, AddIdentityUniversalAuthDTO,
ClientSecretData, ClientSecretData,
@@ -15,10 +15,10 @@ import {
DeleteIdentityDTO, DeleteIdentityDTO,
DeleteIdentityUniversalAuthClientSecretDTO, DeleteIdentityUniversalAuthClientSecretDTO,
Identity, Identity,
IdentityAwsIamAuth, IdentityAwsAuth,
IdentityGcpAuth, IdentityGcpAuth,
IdentityUniversalAuth, IdentityUniversalAuth,
UpdateIdentityAwsIamAuthDTO, UpdateIdentityAwsAuthDTO,
UpdateIdentityDTO, UpdateIdentityDTO,
UpdateIdentityGcpAuthDTO, UpdateIdentityGcpAuthDTO,
UpdateIdentityUniversalAuthDTO UpdateIdentityUniversalAuthDTO
@@ -256,9 +256,9 @@ export const useUpdateIdentityGcpAuth = () => {
}); });
}; };
export const useAddIdentityAwsIamAuth = () => { export const useAddIdentityAwsAuth = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<IdentityAwsIamAuth, {}, AddIdentityAwsIamAuthDTO>({ return useMutation<IdentityAwsAuth, {}, AddIdentityAwsAuthDTO>({
mutationFn: async ({ mutationFn: async ({
identityId, identityId,
stsEndpoint, stsEndpoint,
@@ -270,9 +270,9 @@ export const useAddIdentityAwsIamAuth = () => {
accessTokenTrustedIps accessTokenTrustedIps
}) => { }) => {
const { const {
data: { identityAwsIamAuth } data: { identityAwsAuth }
} = await apiRequest.post<{ identityAwsIamAuth: IdentityAwsIamAuth }>( } = await apiRequest.post<{ identityAwsAuth: IdentityAwsAuth }>(
`/api/v1/auth/aws-iam-auth/identities/${identityId}`, `/api/v1/auth/aws-auth/identities/${identityId}`,
{ {
stsEndpoint, stsEndpoint,
allowedPrincipalArns, allowedPrincipalArns,
@@ -284,7 +284,7 @@ export const useAddIdentityAwsIamAuth = () => {
} }
); );
return identityAwsIamAuth; return identityAwsAuth;
}, },
onSuccess: (_, { organizationId }) => { onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
@@ -292,9 +292,9 @@ export const useAddIdentityAwsIamAuth = () => {
}); });
}; };
export const useUpdateIdentityAwsIamAuth = () => { export const useUpdateIdentityAwsAuth = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<IdentityAwsIamAuth, {}, UpdateIdentityAwsIamAuthDTO>({ return useMutation<IdentityAwsAuth, {}, UpdateIdentityAwsAuthDTO>({
mutationFn: async ({ mutationFn: async ({
identityId, identityId,
stsEndpoint, stsEndpoint,
@@ -306,9 +306,9 @@ export const useUpdateIdentityAwsIamAuth = () => {
accessTokenTrustedIps accessTokenTrustedIps
}) => { }) => {
const { const {
data: { identityAwsIamAuth } data: { identityAwsAuth }
} = await apiRequest.patch<{ identityAwsIamAuth: IdentityAwsIamAuth }>( } = await apiRequest.patch<{ identityAwsAuth: IdentityAwsAuth }>(
`/api/v1/auth/aws-iam-auth/identities/${identityId}`, `/api/v1/auth/aws-auth/identities/${identityId}`,
{ {
stsEndpoint, stsEndpoint,
allowedPrincipalArns, allowedPrincipalArns,
@@ -320,7 +320,7 @@ export const useUpdateIdentityAwsIamAuth = () => {
} }
); );
return identityAwsIamAuth; return identityAwsAuth;
}, },
onSuccess: (_, { organizationId }) => { onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
+8 -13
View File
@@ -2,12 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { import { ClientSecretData, IdentityAwsAuth, IdentityGcpAuth, IdentityUniversalAuth } from "./types";
ClientSecretData,
IdentityAwsIamAuth,
IdentityGcpAuth,
IdentityUniversalAuth
} from "./types";
export const identitiesKeys = { export const identitiesKeys = {
getIdentityUniversalAuth: (identityId: string) => getIdentityUniversalAuth: (identityId: string) =>
@@ -15,7 +10,7 @@ export const identitiesKeys = {
getIdentityUniversalAuthClientSecrets: (identityId: string) => getIdentityUniversalAuthClientSecrets: (identityId: string) =>
[{ identityId }, "identity-universal-auth-client-secrets"] as const, [{ identityId }, "identity-universal-auth-client-secrets"] as const,
getIdentityGcpAuth: (identityId: string) => [{ identityId }, "identity-gcp-auth"] as const, getIdentityGcpAuth: (identityId: string) => [{ identityId }, "identity-gcp-auth"] as const,
getIdentityAwsIamAuth: (identityId: string) => [{ identityId }, "identity-aws-iam-auth"] as const getIdentityAwsAuth: (identityId: string) => [{ identityId }, "identity-aws-auth"] as const
}; };
export const useGetIdentityUniversalAuth = (identityId: string) => { export const useGetIdentityUniversalAuth = (identityId: string) => {
@@ -63,17 +58,17 @@ export const useGetIdentityGcpAuth = (identityId: string) => {
}); });
}; };
export const useGetIdentityAwsIamAuth = (identityId: string) => { export const useGetIdentityAwsAuth = (identityId: string) => {
return useQuery({ return useQuery({
enabled: Boolean(identityId), enabled: Boolean(identityId),
queryKey: identitiesKeys.getIdentityAwsIamAuth(identityId), queryKey: identitiesKeys.getIdentityAwsAuth(identityId),
queryFn: async () => { queryFn: async () => {
const { const {
data: { identityAwsIamAuth } data: { identityAwsAuth }
} = await apiRequest.get<{ identityAwsIamAuth: IdentityAwsIamAuth }>( } = await apiRequest.get<{ identityAwsAuth: IdentityAwsAuth }>(
`/api/v1/auth/aws-iam-auth/identities/${identityId}` `/api/v1/auth/aws-auth/identities/${identityId}`
); );
return identityAwsIamAuth; return identityAwsAuth;
} }
}); });
}; };
+4 -3
View File
@@ -158,8 +158,9 @@ export type UpdateIdentityGcpAuthDTO = {
}[]; }[];
}; };
export type IdentityAwsIamAuth = { export type IdentityAwsAuth = {
identityId: string; identityId: string;
type: "iam";
stsEndpoint: string; stsEndpoint: string;
allowedPrincipalArns: string; allowedPrincipalArns: string;
allowedAccountIds: string; allowedAccountIds: string;
@@ -169,7 +170,7 @@ export type IdentityAwsIamAuth = {
accessTokenTrustedIps: IdentityTrustedIp[]; accessTokenTrustedIps: IdentityTrustedIp[];
}; };
export type AddIdentityAwsIamAuthDTO = { export type AddIdentityAwsAuthDTO = {
organizationId: string; organizationId: string;
identityId: string; identityId: string;
stsEndpoint: string; stsEndpoint: string;
@@ -183,7 +184,7 @@ export type AddIdentityAwsIamAuthDTO = {
}[]; }[];
}; };
export type UpdateIdentityAwsIamAuthDTO = { export type UpdateIdentityAwsAuthDTO = {
organizationId: string; organizationId: string;
identityId: string; identityId: string;
stsEndpoint?: string; stsEndpoint?: string;
@@ -68,6 +68,7 @@ export const useCreateIntegration = () => {
value: string; value: string;
}[]; }[];
kmsKeyId?: string; kmsKeyId?: string;
shouldDisableDelete?: boolean;
}; };
}) => { }) => {
const { const {
@@ -79,7 +79,7 @@ export const useGetFoldersByEnv = ({
}); });
}); });
return [...names]; return [...names];
}, [(folders || []).map((folder) => folder.data)]); }, [...(folders || []).map((folder) => folder.data)]);
const isFolderPresentInEnv = useCallback( const isFolderPresentInEnv = useCallback(
(name: string, env: string) => { (name: string, env: string) => {
@@ -91,7 +91,7 @@ export const useGetFoldersByEnv = ({
} }
return false; return false;
}, },
[(folders || []).map((folder) => folder.data)] [...(folders || []).map((folder) => folder.data)]
); );
const getFolderByNameAndEnv = useCallback( const getFolderByNameAndEnv = useCallback(
@@ -89,6 +89,7 @@ export default function AWSParameterStoreCreateIntegrationPage() {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [shouldTag, setShouldTag] = useState(false); const [shouldTag, setShouldTag] = useState(false);
const [shouldDisableDelete, setShouldDisableDelete] = useState(false);
const [tagKey, setTagKey] = useState(""); const [tagKey, setTagKey] = useState("");
const [tagValue, setTagValue] = useState(""); const [tagValue, setTagValue] = useState("");
const [kmsKeyId, setKmsKeyId] = useState(""); const [kmsKeyId, setKmsKeyId] = useState("");
@@ -144,7 +145,8 @@ export default function AWSParameterStoreCreateIntegrationPage() {
] ]
} }
: {}), : {}),
...(kmsKeyId && { kmsKeyId }) ...(kmsKeyId && { kmsKeyId }),
...(shouldDisableDelete && { shouldDisableDelete })
} }
}); });
@@ -273,6 +275,15 @@ export default function AWSParameterStoreCreateIntegrationPage() {
exit={{ opacity: 0, translateX: 30 }} exit={{ opacity: 0, translateX: 30 }}
> >
<div className="mt-2 ml-1"> <div className="mt-2 ml-1">
<Switch
id="delete-aws"
onCheckedChange={() => setShouldDisableDelete(!shouldDisableDelete)}
isChecked={shouldDisableDelete}
>
Disable deleting secrets in AWS Parameter Store
</Switch>
</div>
<div className="mt-4 ml-1">
<Switch <Switch
id="tag-aws" id="tag-aws"
onCheckedChange={() => setShouldTag(!shouldTag)} onCheckedChange={() => setShouldTag(!shouldTag)}
@@ -14,7 +14,7 @@ import {
import { IdentityAuthMethod } from "@app/hooks/api/identities"; import { IdentityAuthMethod } from "@app/hooks/api/identities";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityAwsIamAuthForm } from "./IdentityAwsIamAuthForm"; import { IdentityAwsAuthForm } from "./IdentityAwsAuthForm";
import { IdentityGcpAuthForm } from "./IdentityGcpAuthForm"; import { IdentityGcpAuthForm } from "./IdentityGcpAuthForm";
import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm"; import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm";
@@ -29,8 +29,8 @@ type Props = {
const identityAuthMethods = [ const identityAuthMethods = [
{ label: "Universal Auth", value: IdentityAuthMethod.UNIVERSAL_AUTH }, { label: "Universal Auth", value: IdentityAuthMethod.UNIVERSAL_AUTH },
{ label: "AWS IAM Auth", value: IdentityAuthMethod.AWS_IAM_AUTH }, { label: "GCP Auth", value: IdentityAuthMethod.GCP_AUTH },
{ label: "GCP Auth", value: IdentityAuthMethod.GCP_AUTH } { label: "AWS Auth", value: IdentityAuthMethod.AWS_AUTH }
]; ];
const schema = yup const schema = yup
@@ -68,9 +68,9 @@ export const IdentityAuthMethodModal = ({ popUp, handlePopUpOpen, handlePopUpTog
const renderIdentityAuthForm = () => { const renderIdentityAuthForm = () => {
switch (identityAuthMethodData?.authMethod ?? authMethod) { switch (identityAuthMethodData?.authMethod ?? authMethod) {
case IdentityAuthMethod.AWS_IAM_AUTH: { case IdentityAuthMethod.AWS_AUTH: {
return ( return (
<IdentityAwsIamAuthForm <IdentityAwsAuthForm
handlePopUpOpen={handlePopUpOpen} handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
identityAuthMethodData={identityAuthMethodData} identityAuthMethodData={identityAuthMethodData}
@@ -9,9 +9,9 @@ import { createNotification } from "@app/components/notifications";
import { Button, FormControl, IconButton, Input } from "@app/components/v2"; import { Button, FormControl, IconButton, Input } from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context"; import { useOrganization, useSubscription } from "@app/context";
import { import {
useAddIdentityAwsIamAuth, useAddIdentityAwsAuth,
useGetIdentityAwsIamAuth, useGetIdentityAwsAuth,
useUpdateIdentityAwsIamAuth useUpdateIdentityAwsAuth
} from "@app/hooks/api"; } from "@app/hooks/api";
import { IdentityAuthMethod } from "@app/hooks/api/identities"; import { IdentityAuthMethod } from "@app/hooks/api/identities";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
@@ -52,7 +52,7 @@ type Props = {
}; };
}; };
export const IdentityAwsIamAuthForm = ({ export const IdentityAwsAuthForm = ({
handlePopUpOpen, handlePopUpOpen,
handlePopUpToggle, handlePopUpToggle,
identityAuthMethodData identityAuthMethodData
@@ -61,10 +61,10 @@ export const IdentityAwsIamAuthForm = ({
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const { mutateAsync: addMutateAsync } = useAddIdentityAwsIamAuth(); const { mutateAsync: addMutateAsync } = useAddIdentityAwsAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityAwsIamAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityAwsAuth();
const { data } = useGetIdentityAwsIamAuth(identityAuthMethodData?.identityId ?? ""); const { data } = useGetIdentityAwsAuth(identityAuthMethodData?.identityId ?? "");
const { const {
control, control,
+1 -1
View File
@@ -13,7 +13,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # 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. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: v0.5.0 version: v0.5.1
# This is the version number of the application being deployed. This version number should be # 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 # 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. # follow Semantic Versioning. They should reflect the version the application is using.
@@ -67,6 +67,8 @@ spec:
properties: properties:
envSlug: envSlug:
type: string type: string
recursive:
type: boolean
secretsPath: secretsPath:
type: string type: string
required: required:
@@ -111,6 +113,8 @@ spec:
type: string type: string
projectSlug: projectSlug:
type: string type: string
recursive:
type: boolean
secretsPath: secretsPath:
type: string type: string
required: required:
+1 -1
View File
@@ -32,7 +32,7 @@ controllerManager:
- ALL - ALL
image: image:
repository: infisical/kubernetes-operator repository: infisical/kubernetes-operator
tag: v0.5.0 # fixed to prevent accidental upgrade tag: v0.5.1 # fixed to prevent accidental upgrade
resources: resources:
limits: limits:
cpu: 500m cpu: 500m
@@ -38,6 +38,8 @@ type SecretScopeInWorkspace struct {
SecretsPath string `json:"secretsPath"` SecretsPath string `json:"secretsPath"`
// +kubebuilder:validation:Required // +kubebuilder:validation:Required
EnvSlug string `json:"envSlug"` EnvSlug string `json:"envSlug"`
// +kubebuilder:validation:Optional
Recursive bool `json:"recursive"`
} }
type MachineIdentityScopeInWorkspace struct { type MachineIdentityScopeInWorkspace struct {
@@ -47,6 +49,8 @@ type MachineIdentityScopeInWorkspace struct {
EnvSlug string `json:"envSlug"` EnvSlug string `json:"envSlug"`
// +kubebuilder:validation:Required // +kubebuilder:validation:Required
ProjectSlug string `json:"projectSlug"` ProjectSlug string `json:"projectSlug"`
// +kubebuilder:validation:Optional
Recursive bool `json:"recursive"`
} }
type KubeSecretReference struct { type KubeSecretReference struct {
@@ -67,6 +67,8 @@ spec:
properties: properties:
envSlug: envSlug:
type: string type: string
recursive:
type: boolean
secretsPath: secretsPath:
type: string type: string
required: required:
@@ -111,6 +113,8 @@ spec:
type: string type: string
projectSlug: projectSlug:
type: string type: string
recursive:
type: boolean
secretsPath: secretsPath:
type: string type: string
required: required:
+2
View File
@@ -19,12 +19,14 @@ spec:
secretsScope: secretsScope:
envSlug: <env-slug> envSlug: <env-slug>
secretsPath: <secrets-path> # Root is "/" secretsPath: <secrets-path> # Root is "/"
recursive: true # Wether or not to use recursive mode (Fetches all secrets in an environment from a given secret path, and all folders inside the path) / defaults to false
universalAuth: universalAuth:
secretsScope: secretsScope:
projectSlug: <project-slug> projectSlug: <project-slug>
envSlug: <env-slug> # "dev", "staging", "prod", etc.. envSlug: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: "<secrets-path>" # Root is "/" secretsPath: "<secrets-path>" # Root is "/"
recursive: true # Wether or not to use recursive mode (Fetches all secrets in an environment from a given secret path, and all folders inside the path) / defaults to false
credentialsRef: credentialsRef:
secretName: universal-auth-credentials secretName: universal-auth-credentials
@@ -269,7 +269,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
} else if infisicalMachineIdentityCreds.ClientId != "" && infisicalMachineIdentityCreds.ClientSecret != "" { } else if infisicalMachineIdentityCreds.ClientId != "" && infisicalMachineIdentityCreds.ClientSecret != "" {
authStrategy = AuthStrategy.UNIVERSAL_MACHINE_IDENTITY authStrategy = AuthStrategy.UNIVERSAL_MACHINE_IDENTITY
} else { } else {
return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets") return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets\n")
} }
r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, err) r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, err)
@@ -312,8 +312,9 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
} else if authStrategy == AuthStrategy.SERVICE_TOKEN { // Service Tokens (deprecated) } else if authStrategy == AuthStrategy.SERVICE_TOKEN { // Service Tokens (deprecated)
envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug
secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath
recursive := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.Recursive
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath) plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath, recursive)
if err != nil { if err != nil {
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err) return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
} }
+12 -4
View File
@@ -66,6 +66,10 @@ func CallGetSecretsV3(httpClient *resty.Client, request GetEncryptedSecretsV3Req
httpRequest.SetQueryParam("secretPath", request.SecretPath) httpRequest.SetQueryParam("secretPath", request.SecretPath)
} }
if request.Recursive {
httpRequest.SetQueryParam("recursive", "true")
}
response, err := httpRequest.Get(fmt.Sprintf("%v/v3/secrets", API_HOST_URL)) response, err := httpRequest.Get(fmt.Sprintf("%v/v3/secrets", API_HOST_URL))
if err != nil { if err != nil {
@@ -148,19 +152,23 @@ func CallUniversalMachineIdentityRefreshAccessToken(request MachineIdentityUnive
func CallGetDecryptedSecretsV3(httpClient *resty.Client, request GetDecryptedSecretsV3Request) (GetDecryptedSecretsV3Response, error) { func CallGetDecryptedSecretsV3(httpClient *resty.Client, request GetDecryptedSecretsV3Request) (GetDecryptedSecretsV3Response, error) {
var decryptedSecretsResponse GetDecryptedSecretsV3Response var decryptedSecretsResponse GetDecryptedSecretsV3Response
response, err := httpClient. req := httpClient.
R(). R().
SetResult(&decryptedSecretsResponse). SetResult(&decryptedSecretsResponse).
SetHeader("User-Agent", USER_AGENT_NAME). SetHeader("User-Agent", USER_AGENT_NAME).
SetQueryParam("secretPath", request.SecretPath). SetQueryParam("secretPath", request.SecretPath).
SetQueryParam("workspaceSlug", request.ProjectSlug). SetQueryParam("workspaceSlug", request.ProjectSlug).
SetQueryParam("environment", request.Environment). SetQueryParam("environment", request.Environment)
Get(fmt.Sprintf("%v/v3/secrets/raw", API_HOST_URL))
if request.Recursive {
req.SetQueryParam("recursive", "true")
}
response, err := req.Get(fmt.Sprintf("%v/v3/secrets/raw", API_HOST_URL))
if err != nil { if err != nil {
return GetDecryptedSecretsV3Response{}, fmt.Errorf("CallGetDecryptedSecretsV3: Unable to complete api request [err=%s]", err) return GetDecryptedSecretsV3Response{}, fmt.Errorf("CallGetDecryptedSecretsV3: Unable to complete api request [err=%s]", err)
} }
if response.IsError() { if response.IsError() {
return GetDecryptedSecretsV3Response{}, fmt.Errorf("CallGetDecryptedSecretsV3: Unsuccessful response: [response=%s]", response) return GetDecryptedSecretsV3Response{}, fmt.Errorf("CallGetDecryptedSecretsV3: Unsuccessful response: [response=%s]", response)
} }
+2
View File
@@ -31,6 +31,7 @@ type GetEncryptedWorkspaceKeyResponse struct {
type GetEncryptedSecretsV3Request struct { type GetEncryptedSecretsV3Request struct {
Environment string `json:"environment"` Environment string `json:"environment"`
WorkspaceId string `json:"workspaceId"` WorkspaceId string `json:"workspaceId"`
Recursive bool `json:"recursive"`
SecretPath string `json:"secretPath"` SecretPath string `json:"secretPath"`
IncludeImport bool `json:"include_imports"` IncludeImport bool `json:"include_imports"`
ETag string `json:"etag,omitempty"` ETag string `json:"etag,omitempty"`
@@ -100,6 +101,7 @@ type GetDecryptedSecretsV3Request struct {
ProjectSlug string `json:"workspaceSlug"` ProjectSlug string `json:"workspaceSlug"`
Environment string `json:"environment"` Environment string `json:"environment"`
SecretPath string `json:"secretPath"` SecretPath string `json:"secretPath"`
Recursive bool `json:"recursive"`
ETag string `json:"etag,omitempty"` ETag string `json:"etag,omitempty"`
} }
+4 -2
View File
@@ -60,6 +60,7 @@ func GetPlainTextSecretsViaUniversalAuth(accessToken string, etag string, secret
secretsResponse, err := api.CallGetDecryptedSecretsV3(httpClient, api.GetDecryptedSecretsV3Request{ secretsResponse, err := api.CallGetDecryptedSecretsV3(httpClient, api.GetDecryptedSecretsV3Request{
ProjectSlug: secretScope.ProjectSlug, ProjectSlug: secretScope.ProjectSlug,
Environment: secretScope.EnvSlug, Environment: secretScope.EnvSlug,
Recursive: secretScope.Recursive,
SecretPath: secretScope.SecretsPath, SecretPath: secretScope.SecretsPath,
ETag: etag, ETag: etag,
}) })
@@ -85,7 +86,7 @@ func GetPlainTextSecretsViaUniversalAuth(accessToken string, etag string, secret
}, nil }, nil
} }
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, envSlug string, secretPath string) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) { func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, envSlug string, secretPath string, recursive bool) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4) serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
if len(serviceTokenParts) < 4 { if len(serviceTokenParts) < 4 {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again") return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
@@ -106,6 +107,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, en
encryptedSecretsResponse, err := api.CallGetSecretsV3(httpClient, api.GetEncryptedSecretsV3Request{ encryptedSecretsResponse, err := api.CallGetSecretsV3(httpClient, api.GetEncryptedSecretsV3Request{
WorkspaceId: serviceTokenDetails.Workspace, WorkspaceId: serviceTokenDetails.Workspace,
Environment: envSlug, Environment: envSlug,
Recursive: recursive,
ETag: etag, ETag: etag,
SecretPath: secretPath, SecretPath: secretPath,
}) })
@@ -376,7 +378,7 @@ func ExpandSecrets(secrets []model.SingleEnvironmentVariable, infisicalToken str
if crossRefSec, ok := crossEnvRefSecs[uniqKey]; !ok { if crossRefSec, ok := crossEnvRefSecs[uniqKey]; !ok {
// if not in cross reference cache, fetch it from server // if not in cross reference cache, fetch it from server
refSecs, _, err := GetPlainTextSecretsViaServiceToken(infisicalToken, "", env, secPath) refSecs, _, err := GetPlainTextSecretsViaServiceToken(infisicalToken, "", env, secPath, false)
if err != nil { if err != nil {
fmt.Printf("Could not fetch secrets in environment: %s secret-path: %s", env, secPath) fmt.Printf("Could not fetch secrets in environment: %s secret-path: %s", env, secPath)
// HandleError(err, fmt.Sprintf("Could not fetch secrets in environment: %s secret-path: %s", env, secPath), "If you are using a service token to fetch secrets, please ensure it is valid") // HandleError(err, fmt.Sprintf("Could not fetch secrets in environment: %s secret-path: %s", env, secPath), "If you are using a service token to fetch secrets, please ensure it is valid")
-1
View File
@@ -1 +0,0 @@
db
-1487
View File
File diff suppressed because it is too large Load Diff
-33
View File
@@ -1,33 +0,0 @@
{
"name": "pg-migrator",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"migration": "tsx src/index.ts",
"rollback": "tsx src/rollback.ts",
"migrate:audit-log": "tsx src/audit-log-migrator.ts"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^20.11.0",
"@types/prompt-sync": "^4.2.3",
"@types/uuid": "^9.0.7",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@casl/ability": "^6.5.0",
"@sindresorhus/slugify": "^2.2.1",
"dotenv": "^16.3.1",
"knex": "^3.1.0",
"level": "^8.0.0",
"mongoose": "^8.0.4",
"nanoid": "^5.0.4",
"pg": "^8.11.3",
"prompt-sync": "^4.2.0",
"uuid": "^9.0.1",
"zod": "^3.22.4"
}
}
-451
View File
@@ -1,451 +0,0 @@
import { Knex } from "knex";
import {
TableName,
TApiKeys,
TApiKeysInsert,
TApiKeysUpdate,
TAuditLogs,
TAuditLogsInsert,
TAuditLogsUpdate,
TAuthTokens,
TAuthTokenSessions,
TAuthTokenSessionsInsert,
TAuthTokenSessionsUpdate,
TAuthTokensInsert,
TAuthTokensUpdate,
TBackupPrivateKey,
TBackupPrivateKeyInsert,
TBackupPrivateKeyUpdate,
TGitAppInstallSessions,
TGitAppInstallSessionsInsert,
TGitAppInstallSessionsUpdate,
TGitAppOrg,
TGitAppOrgInsert,
TGitAppOrgUpdate,
TIdentities,
TIdentitiesInsert,
TIdentitiesUpdate,
TIdentityAccessTokens,
TIdentityAccessTokensInsert,
TIdentityAccessTokensUpdate,
TIdentityOrgMemberships,
TIdentityOrgMembershipsInsert,
TIdentityOrgMembershipsUpdate,
TIdentityProjectMemberships,
TIdentityProjectMembershipsInsert,
TIdentityProjectMembershipsUpdate,
TIdentityUaClientSecrets,
TIdentityUaClientSecretsInsert,
TIdentityUaClientSecretsUpdate,
TIdentityUniversalAuths,
TIdentityUniversalAuthsInsert,
TIdentityUniversalAuthsUpdate,
TIncidentContacts,
TIncidentContactsInsert,
TIncidentContactsUpdate,
TIntegrationAuths,
TIntegrationAuthsInsert,
TIntegrationAuthsUpdate,
TIntegrations,
TIntegrationsInsert,
TIntegrationsUpdate,
TOrganizations,
TOrganizationsInsert,
TOrganizationsUpdate,
TOrgBots,
TOrgBotsInsert,
TOrgBotsUpdate,
TOrgMemberships,
TOrgMembershipsInsert,
TOrgMembershipsUpdate,
TOrgRoles,
TOrgRolesInsert,
TOrgRolesUpdate,
TProjectBots,
TProjectBotsInsert,
TProjectBotsUpdate,
TProjectEnvironments,
TProjectEnvironmentsInsert,
TProjectEnvironmentsUpdate,
TProjectKeys,
TProjectKeysInsert,
TProjectKeysUpdate,
TProjectMemberships,
TProjectMembershipsInsert,
TProjectMembershipsUpdate,
TProjectRoles,
TProjectRolesInsert,
TProjectRolesUpdate,
TProjects,
TProjectsInsert,
TProjectsUpdate,
TSamlConfigs,
TSamlConfigsInsert,
TSamlConfigsUpdate,
TSecretApprovalPolicies,
TSecretApprovalPoliciesApprovers,
TSecretApprovalPoliciesApproversInsert,
TSecretApprovalPoliciesApproversUpdate,
TSecretApprovalPoliciesInsert,
TSecretApprovalPoliciesUpdate,
TSecretApprovalRequests,
TSecretApprovalRequestSecretTags,
TSecretApprovalRequestSecretTagsInsert,
TSecretApprovalRequestSecretTagsUpdate,
TSecretApprovalRequestsInsert,
TSecretApprovalRequestsReviewers,
TSecretApprovalRequestsReviewersInsert,
TSecretApprovalRequestsReviewersUpdate,
TSecretApprovalRequestsSecrets,
TSecretApprovalRequestsSecretsInsert,
TSecretApprovalRequestsSecretsUpdate,
TSecretApprovalRequestsUpdate,
TSecretBlindIndexes,
TSecretBlindIndexesInsert,
TSecretBlindIndexesUpdate,
TSecretFolders,
TSecretFoldersInsert,
TSecretFoldersUpdate,
TSecretFolderVersions,
TSecretFolderVersionsInsert,
TSecretFolderVersionsUpdate,
TSecretImports,
TSecretImportsInsert,
TSecretImportsUpdate,
TSecretRotationOutputs,
TSecretRotationOutputsInsert,
TSecretRotationOutputsUpdate,
TSecretRotations,
TSecretRotationsInsert,
TSecretRotationsUpdate,
TSecrets,
TSecretScanningGitRisks,
TSecretScanningGitRisksInsert,
TSecretScanningGitRisksUpdate,
TSecretsInsert,
TSecretSnapshotFolders,
TSecretSnapshotFoldersInsert,
TSecretSnapshotFoldersUpdate,
TSecretSnapshots,
TSecretSnapshotSecrets,
TSecretSnapshotSecretsInsert,
TSecretSnapshotSecretsUpdate,
TSecretSnapshotsInsert,
TSecretSnapshotsUpdate,
TSecretsUpdate,
TSecretTagJunction,
TSecretTagJunctionInsert,
TSecretTagJunctionUpdate,
TSecretTags,
TSecretTagsInsert,
TSecretTagsUpdate,
TSecretVersions,
TSecretVersionsInsert,
TSecretVersionsUpdate,
TSecretVersionTagJunction,
TSecretVersionTagJunctionInsert,
TSecretVersionTagJunctionUpdate,
TServiceTokens,
TServiceTokensInsert,
TServiceTokensUpdate,
TSuperAdmin,
TSuperAdminInsert,
TSuperAdminUpdate,
TTrustedIps,
TTrustedIpsInsert,
TTrustedIpsUpdate,
TUserActions,
TUserActionsInsert,
TUserActionsUpdate,
TUserEncryptionKeys,
TUserEncryptionKeysInsert,
TUserEncryptionKeysUpdate,
TUsers,
TUsersInsert,
TUsersUpdate,
TWebhooks,
TWebhooksInsert,
TWebhooksUpdate,
} from "../schemas";
declare module "knex/types/tables" {
interface Tables {
[TableName.Users]: Knex.CompositeTableType<
TUsers,
TUsersInsert,
TUsersUpdate
>;
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
TUserEncryptionKeys,
TUserEncryptionKeysInsert,
TUserEncryptionKeysUpdate
>;
[TableName.AuthTokens]: Knex.CompositeTableType<
TAuthTokens,
TAuthTokensInsert,
TAuthTokensUpdate
>;
[TableName.AuthTokenSession]: Knex.CompositeTableType<
TAuthTokenSessions,
TAuthTokenSessionsInsert,
TAuthTokenSessionsUpdate
>;
[TableName.BackupPrivateKey]: Knex.CompositeTableType<
TBackupPrivateKey,
TBackupPrivateKeyInsert,
TBackupPrivateKeyUpdate
>;
[TableName.Organization]: Knex.CompositeTableType<
TOrganizations,
TOrganizationsInsert,
TOrganizationsUpdate
>;
[TableName.OrgMembership]: Knex.CompositeTableType<
TOrgMemberships,
TOrgMembershipsInsert,
TOrgMembershipsUpdate
>;
[TableName.OrgRoles]: Knex.CompositeTableType<
TOrgRoles,
TOrgRolesInsert,
TOrgRolesUpdate
>;
[TableName.IncidentContact]: Knex.CompositeTableType<
TIncidentContacts,
TIncidentContactsInsert,
TIncidentContactsUpdate
>;
[TableName.UserAction]: Knex.CompositeTableType<
TUserActions,
TUserActionsInsert,
TUserActionsUpdate
>;
[TableName.SuperAdmin]: Knex.CompositeTableType<
TSuperAdmin,
TSuperAdminInsert,
TSuperAdminUpdate
>;
[TableName.ApiKey]: Knex.CompositeTableType<
TApiKeys,
TApiKeysInsert,
TApiKeysUpdate
>;
[TableName.Project]: Knex.CompositeTableType<
TProjects,
TProjectsInsert,
TProjectsUpdate
>;
[TableName.ProjectMembership]: Knex.CompositeTableType<
TProjectMemberships,
TProjectMembershipsInsert,
TProjectMembershipsUpdate
>;
[TableName.Environment]: Knex.CompositeTableType<
TProjectEnvironments,
TProjectEnvironmentsInsert,
TProjectEnvironmentsUpdate
>;
[TableName.ProjectBot]: Knex.CompositeTableType<
TProjectBots,
TProjectBotsInsert,
TProjectBotsUpdate
>;
[TableName.ProjectRoles]: Knex.CompositeTableType<
TProjectRoles,
TProjectRolesInsert,
TProjectRolesUpdate
>;
[TableName.ProjectKeys]: Knex.CompositeTableType<
TProjectKeys,
TProjectKeysInsert,
TProjectKeysUpdate
>;
[TableName.Secret]: Knex.CompositeTableType<
TSecrets,
TSecretsInsert,
TSecretsUpdate
>;
[TableName.SecretBlindIndex]: Knex.CompositeTableType<
TSecretBlindIndexes,
TSecretBlindIndexesInsert,
TSecretBlindIndexesUpdate
>;
[TableName.SecretVersion]: Knex.CompositeTableType<
TSecretVersions,
TSecretVersionsInsert,
TSecretVersionsUpdate
>;
[TableName.SecretFolder]: Knex.CompositeTableType<
TSecretFolders,
TSecretFoldersInsert,
TSecretFoldersUpdate
>;
[TableName.SecretFolderVersion]: Knex.CompositeTableType<
TSecretFolderVersions,
TSecretFolderVersionsInsert,
TSecretFolderVersionsUpdate
>;
[TableName.SecretTag]: Knex.CompositeTableType<
TSecretTags,
TSecretTagsInsert,
TSecretTagsUpdate
>;
[TableName.SecretImport]: Knex.CompositeTableType<
TSecretImports,
TSecretImportsInsert,
TSecretImportsUpdate
>;
[TableName.Integration]: Knex.CompositeTableType<
TIntegrations,
TIntegrationsInsert,
TIntegrationsUpdate
>;
[TableName.Webhook]: Knex.CompositeTableType<
TWebhooks,
TWebhooksInsert,
TWebhooksUpdate
>;
[TableName.ServiceToken]: Knex.CompositeTableType<
TServiceTokens,
TServiceTokensInsert,
TServiceTokensUpdate
>;
[TableName.IntegrationAuth]: Knex.CompositeTableType<
TIntegrationAuths,
TIntegrationAuthsInsert,
TIntegrationAuthsUpdate
>;
[TableName.Identity]: Knex.CompositeTableType<
TIdentities,
TIdentitiesInsert,
TIdentitiesUpdate
>;
[TableName.IdentityUniversalAuth]: Knex.CompositeTableType<
TIdentityUniversalAuths,
TIdentityUniversalAuthsInsert,
TIdentityUniversalAuthsUpdate
>;
[TableName.IdentityUaClientSecret]: Knex.CompositeTableType<
TIdentityUaClientSecrets,
TIdentityUaClientSecretsInsert,
TIdentityUaClientSecretsUpdate
>;
[TableName.IdentityAccessToken]: Knex.CompositeTableType<
TIdentityAccessTokens,
TIdentityAccessTokensInsert,
TIdentityAccessTokensUpdate
>;
[TableName.IdentityOrgMembership]: Knex.CompositeTableType<
TIdentityOrgMemberships,
TIdentityOrgMembershipsInsert,
TIdentityOrgMembershipsUpdate
>;
[TableName.IdentityProjectMembership]: Knex.CompositeTableType<
TIdentityProjectMemberships,
TIdentityProjectMembershipsInsert,
TIdentityProjectMembershipsUpdate
>;
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
TSecretApprovalPolicies,
TSecretApprovalPoliciesInsert,
TSecretApprovalPoliciesUpdate
>;
[TableName.SecretApprovalPolicyApprover]: Knex.CompositeTableType<
TSecretApprovalPoliciesApprovers,
TSecretApprovalPoliciesApproversInsert,
TSecretApprovalPoliciesApproversUpdate
>;
[TableName.SecretApprovalRequest]: Knex.CompositeTableType<
TSecretApprovalRequests,
TSecretApprovalRequestsInsert,
TSecretApprovalRequestsUpdate
>;
[TableName.SecretApprovalRequestReviewer]: Knex.CompositeTableType<
TSecretApprovalRequestsReviewers,
TSecretApprovalRequestsReviewersInsert,
TSecretApprovalRequestsReviewersUpdate
>;
[TableName.SecretApprovalRequestSecret]: Knex.CompositeTableType<
TSecretApprovalRequestsSecrets,
TSecretApprovalRequestsSecretsInsert,
TSecretApprovalRequestsSecretsUpdate
>;
[TableName.SecretApprovalRequestSecretTag]: Knex.CompositeTableType<
TSecretApprovalRequestSecretTags,
TSecretApprovalRequestSecretTagsInsert,
TSecretApprovalRequestSecretTagsUpdate
>;
[TableName.SecretRotation]: Knex.CompositeTableType<
TSecretRotations,
TSecretRotationsInsert,
TSecretRotationsUpdate
>;
[TableName.SecretRotationOutput]: Knex.CompositeTableType<
TSecretRotationOutputs,
TSecretRotationOutputsInsert,
TSecretRotationOutputsUpdate
>;
[TableName.Snapshot]: Knex.CompositeTableType<
TSecretSnapshots,
TSecretSnapshotsInsert,
TSecretSnapshotsUpdate
>;
[TableName.SnapshotSecret]: Knex.CompositeTableType<
TSecretSnapshotSecrets,
TSecretSnapshotSecretsInsert,
TSecretSnapshotSecretsUpdate
>;
[TableName.SnapshotFolder]: Knex.CompositeTableType<
TSecretSnapshotFolders,
TSecretSnapshotFoldersInsert,
TSecretSnapshotFoldersUpdate
>;
[TableName.SamlConfig]: Knex.CompositeTableType<
TSamlConfigs,
TSamlConfigsInsert,
TSamlConfigsUpdate
>;
[TableName.OrgBot]: Knex.CompositeTableType<
TOrgBots,
TOrgBotsInsert,
TOrgBotsUpdate
>;
[TableName.AuditLog]: Knex.CompositeTableType<
TAuditLogs,
TAuditLogsInsert,
TAuditLogsUpdate
>;
[TableName.GitAppInstallSession]: Knex.CompositeTableType<
TGitAppInstallSessions,
TGitAppInstallSessionsInsert,
TGitAppInstallSessionsUpdate
>;
[TableName.GitAppOrg]: Knex.CompositeTableType<
TGitAppOrg,
TGitAppOrgInsert,
TGitAppOrgUpdate
>;
[TableName.SecretScanningGitRisk]: Knex.CompositeTableType<
TSecretScanningGitRisks,
TSecretScanningGitRisksInsert,
TSecretScanningGitRisksUpdate
>;
[TableName.TrustedIps]: Knex.CompositeTableType<
TTrustedIps,
TTrustedIpsInsert,
TTrustedIpsUpdate
>;
// Junction tables
[TableName.JnSecretTag]: Knex.CompositeTableType<
TSecretTagJunction,
TSecretTagJunctionInsert,
TSecretTagJunctionUpdate
>;
[TableName.SecretVersionTag]: Knex.CompositeTableType<
TSecretVersionTagJunction,
TSecretVersionTagJunctionInsert,
TSecretVersionTagJunctionUpdate
>;
}
}
File diff suppressed because it is too large Load Diff
-36
View File
@@ -1,36 +0,0 @@
import { TFolderSchema } from "./models";
export const folderBfsTraversal = async (
root: TFolderSchema,
callback: (
data: TFolderSchema & { parentId: string | null },
) => void | Promise<void>,
) => {
const queue = [root];
while (queue.length) {
const folder = queue.pop() as TFolderSchema & { parentId: null };
callback(folder);
queue.push(
...folder.children.map((el) => ({
...el,
parentId: folder.id,
})),
);
}
};
export const flattenFolders = (folders: TFolderSchema) => {
const flattened: {
id: string;
parentId: string | null;
name: string;
version: number;
}[] = [];
if(!folders) return []
folderBfsTraversal(folders, ({ name, version, parentId, id }) => {
flattened.push({ name, version, parentId, id });
});
return flattened;
};
File diff suppressed because it is too large Load Diff
@@ -1,37 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import {
createOnUpdateTrigger,
createUpdateAtTriggerFunction,
dropOnUpdateTrigger,
dropUpdatedAtTriggerFunction
} from "../utils";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.Users);
if (!isTablePresent) {
await knex.schema.createTable(TableName.Users, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("email").notNullable();
t.specificType("authMethods", "text[]");
t.boolean("superAdmin").defaultTo(false);
t.string("firstName");
t.string("lastName");
t.boolean("isAccepted").defaultTo(false);
t.boolean("isMfaEnabled").defaultTo(false);
t.specificType("mfaMethods", "text[]");
t.jsonb("devices");
t.timestamps(true, true, true);
});
}
// this is a one time function
await createUpdateAtTriggerFunction(knex);
await createOnUpdateTrigger(knex, TableName.Users);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.Users);
await dropOnUpdateTrigger(knex, TableName.Users);
await dropUpdatedAtTriggerFunction(knex);
}
@@ -1,31 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.UserEncryptionKey);
if (!isTablePresent) {
await knex.schema.createTable(TableName.UserEncryptionKey, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.text("clientPublicKey");
t.text("serverPrivateKey");
t.integer("encryptionVersion").defaultTo(2);
t.text("protectedKey");
t.text("protectedKeyIV");
t.text("protectedKeyTag");
t.text("publicKey").notNullable();
t.text("encryptedPrivateKey").notNullable();
t.text("iv").notNullable();
t.text("tag").notNullable();
t.text("salt").notNullable();
t.text("verifier").notNullable();
// one to one relationship
t.uuid("userId").notNullable().unique();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.UserEncryptionKey);
}
@@ -1,25 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.AuthTokens);
if (!isTablePresent) {
await knex.schema.createTable(TableName.AuthTokens, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("type").notNullable();
t.string("phoneNumber");
t.string("tokenHash").notNullable();
t.integer("triesLeft");
t.datetime("expiresAt").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.uuid("userId");
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.AuthTokens);
}
@@ -1,29 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.AuthTokenSession);
if (!isTablePresent) {
await knex.schema.createTable(TableName.AuthTokenSession, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("ip").notNullable();
t.string("userAgent");
t.integer("refreshVersion").notNullable().defaultTo(1);
t.integer("accessVersion").notNullable().defaultTo(1);
t.datetime("lastUsed").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
// this is a one time function
await createOnUpdateTrigger(knex, TableName.AuthTokenSession);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.AuthTokenSession);
await dropOnUpdateTrigger(knex, TableName.AuthTokenSession);
}
@@ -1,26 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesTableExist = await knex.schema.hasTable(TableName.BackupPrivateKey);
if (!doesTableExist) {
await knex.schema.createTable(TableName.BackupPrivateKey, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.text("encryptedPrivateKey").notNullable();
t.text("iv").notNullable();
t.text("tag").notNullable();
t.string("algorithm").notNullable();
t.string("keyEncoding").notNullable();
t.text("salt").notNullable();
t.text("verifier").notNullable();
t.timestamps(true, true, true);
t.uuid("userId").notNullable().unique();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.BackupPrivateKey);
}
@@ -1,35 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.Organization);
if (!isTablePresent) {
await knex.schema.createTable(TableName.Organization, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("customerId");
t.string("slug").notNullable();
// does not need update trigger we will do it manually
t.unique("slug");
t.timestamps(true, true, true);
});
await knex.schema.alterTable(TableName.AuthTokens, (t) => {
t.uuid("orgId");
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
});
}
// this is a one time function
await createOnUpdateTrigger(knex, TableName.Organization);
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.AuthTokens, "orgId")) {
await knex.schema.alterTable(TableName.AuthTokens, (t) => {
t.dropColumn("orgId");
});
}
await knex.schema.dropTableIfExists(TableName.Organization);
await dropOnUpdateTrigger(knex, TableName.Organization);
}
@@ -1,48 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { OrgMembershipStatus } from "../schemas/models";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const isOrgRolePresent = await knex.schema.hasTable(TableName.OrgRoles);
if (!isOrgRolePresent) {
await knex.schema.createTable(TableName.OrgRoles, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("description");
t.string("slug").notNullable();
t.jsonb("permissions").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
});
}
const isOrgTablePresent = await knex.schema.hasTable(TableName.OrgMembership);
if (!isOrgTablePresent) {
await knex.schema.createTable(TableName.OrgMembership, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("role").notNullable();
t.string("status").notNullable().defaultTo(OrgMembershipStatus.Invited);
t.string("inviteEmail");
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.uuid("userId");
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.uuid("roleId");
t.foreign("roleId").references("id").inTable(TableName.OrgRoles);
});
}
// this is a one time function
await createOnUpdateTrigger(knex, TableName.OrgMembership);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.OrgMembership);
await knex.schema.dropTableIfExists(TableName.OrgRoles);
await dropOnUpdateTrigger(knex, TableName.OrgMembership);
}
@@ -1,25 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.IncidentContact);
if (!isTablePresent) {
await knex.schema.createTable(TableName.IncidentContact, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("email").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
});
}
// this is a one time function
await createOnUpdateTrigger(knex, TableName.IncidentContact);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IncidentContact);
await dropOnUpdateTrigger(knex, TableName.IncidentContact);
}
@@ -1,20 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.UserAction);
if (!isTablePresent) {
await knex.schema.createTable(TableName.UserAction, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("action").notNullable();
t.timestamps(true, true, true);
t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.UserAction);
}
@@ -1,23 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.SuperAdmin);
if (!isTablePresent) {
await knex.schema.createTable(TableName.SuperAdmin, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.boolean("initialized").defaultTo(false);
t.boolean("allowSignUp").defaultTo(true);
t.timestamps(true, true, true);
});
}
// this is a one time function
await createOnUpdateTrigger(knex, TableName.SuperAdmin);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SuperAdmin);
await dropOnUpdateTrigger(knex, TableName.SuperAdmin);
}
@@ -1,26 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.ApiKey);
if (!isTablePresent) {
await knex.schema.createTable(TableName.ApiKey, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.datetime("lastUsed");
t.datetime("expiresAt");
t.string("secretHash").notNullable();
t.timestamps(true, true, true);
t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.ApiKey);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ApiKey);
await dropOnUpdateTrigger(knex, TableName.ApiKey);
}
@@ -1,62 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.Project))) {
await knex.schema.createTable(TableName.Project, (t) => {
t.string("id", 36).primary().defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("slug").notNullable();
t.boolean("autoCapitalization").defaultTo(true);
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
t.unique(["orgId", "slug"]);
});
}
await createOnUpdateTrigger(knex, TableName.Project);
// environments
if (!(await knex.schema.hasTable(TableName.Environment))) {
await knex.schema.createTable(TableName.Environment, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("slug").notNullable();
t.integer("position").notNullable();
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
// this will ensure ever env has its position
t.unique(["projectId", "position"], {
indexName: "env_pos_composite_uniqe",
deferrable: "deferred"
});
t.timestamps(true, true, true);
});
}
// project key
if (!(await knex.schema.hasTable(TableName.ProjectKeys))) {
await knex.schema.createTable(TableName.ProjectKeys, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.text("encryptedKey").notNullable();
t.text("nonce").notNullable();
t.uuid("receiverId").notNullable();
t.foreign("receiverId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.uuid("senderId");
// if sender is deleted just don't do anything to this record
t.foreign("senderId").references("id").inTable(TableName.Users).onDelete("SET NULL");
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.ProjectKeys);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.Environment);
await knex.schema.dropTableIfExists(TableName.ProjectKeys);
await knex.schema.dropTableIfExists(TableName.Project);
await dropOnUpdateTrigger(knex, TableName.ProjectKeys);
await dropOnUpdateTrigger(knex, TableName.Project);
}
@@ -1,43 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.ProjectRoles))) {
await knex.schema.createTable(TableName.ProjectRoles, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("description");
t.string("slug").notNullable();
t.jsonb("permissions").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
});
}
if (!(await knex.schema.hasTable(TableName.ProjectMembership))) {
await knex.schema.createTable(TableName.ProjectMembership, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("role").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
// until role is changed/removed the role should not deleted
t.uuid("roleId");
t.foreign("roleId").references("id").inTable(TableName.ProjectRoles);
});
}
await createOnUpdateTrigger(knex, TableName.ProjectMembership);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ProjectMembership);
await knex.schema.dropTableIfExists(TableName.ProjectRoles);
await dropOnUpdateTrigger(knex, TableName.ProjectMembership);
}
@@ -1,42 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretFolder))) {
await knex.schema.createTable(TableName.SecretFolder, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.integer("version").defaultTo(1);
t.timestamps(true, true, true);
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.uuid("parentId");
t.foreign("parentId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.SecretFolder);
if (!(await knex.schema.hasTable(TableName.SecretFolderVersion))) {
await knex.schema.createTable(TableName.SecretFolderVersion, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.integer("version").defaultTo(1);
t.timestamps(true, true, true);
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.uuid("folderId").notNullable();
// t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("SET NULL");
});
}
await createOnUpdateTrigger(knex, TableName.SecretFolderVersion);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretFolderVersion);
await knex.schema.dropTableIfExists(TableName.SecretFolder);
await dropOnUpdateTrigger(knex, TableName.SecretFolder);
await dropOnUpdateTrigger(knex, TableName.SecretFolderVersion);
}
@@ -1,30 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretImport))) {
await knex.schema.createTable(TableName.SecretImport, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.integer("version").defaultTo(1);
t.string("importPath").notNullable();
t.uuid("importEnv").notNullable();
t.foreign("importEnv").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.integer("position").notNullable();
t.timestamps(true, true, true);
t.uuid("folderId").notNullable();
t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE");
t.unique(["folderId", "position"], {
indexName: "import_pos_composite_uniqe",
deferrable: "deferred"
});
});
}
await createOnUpdateTrigger(knex, TableName.SecretImport);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretImport);
await dropOnUpdateTrigger(knex, TableName.SecretImport);
}
@@ -1,26 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretTag))) {
await knex.schema.createTable(TableName.SecretTag, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("slug").notNullable();
t.string("color");
t.timestamps(true, true, true);
t.uuid("createdBy");
t.foreign("createdBy").references("id").inTable(TableName.Users).onDelete("SET NULL");
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.SecretTag);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretTag);
await dropOnUpdateTrigger(knex, TableName.SecretTag);
}
@@ -1,93 +0,0 @@
import { Knex } from "knex";
import {
SecretEncryptionAlgo,
SecretKeyEncoding,
SecretType,
TableName,
} from "../schemas";
import {
createJunctionTable,
createOnUpdateTrigger,
dropOnUpdateTrigger,
} from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretBlindIndex))) {
await knex.schema.createTable(TableName.SecretBlindIndex, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.text("encryptedSaltCipherText").notNullable();
t.text("saltIV").notNullable();
t.text("saltTag").notNullable();
t.string("algorithm")
.notNullable()
.defaultTo(SecretEncryptionAlgo.AES_256_GCM);
t.string("keyEncoding").notNullable().defaultTo(SecretKeyEncoding.UTF8);
t.string("projectId").notNullable().unique();
t.foreign("projectId")
.references("id")
.inTable(TableName.Project)
.onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretBlindIndex);
if (!(await knex.schema.hasTable(TableName.Secret))) {
await knex.schema.createTable(TableName.Secret, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.integer("version").defaultTo(1).notNullable();
t.string("type").notNullable().defaultTo(SecretType.Shared);
// t.text("secretKeyHash").notNullable();
// t.text("secretValueHash");
// t.text("secretCommentHash");
// this is required but for backward compatiability we are making it nullable
t.text("secretBlindIndex");
t.text("secretKeyCiphertext").notNullable();
t.text("secretKeyIV").notNullable();
t.text("secretKeyTag").notNullable();
t.text("secretValueCiphertext").notNullable();
t.text("secretValueIV").notNullable(); // symmetric encryption
t.text("secretValueTag").notNullable();
t.text("secretCommentCiphertext");
t.text("secretCommentIV");
t.text("secretCommentTag");
t.string("secretReminderNote");
t.integer("secretReminderRepeatDays");
t.boolean("skipMultilineEncoding").defaultTo(false);
t.string("algorithm")
.notNullable()
.defaultTo(SecretEncryptionAlgo.AES_256_GCM);
t.string("keyEncoding").notNullable().defaultTo(SecretKeyEncoding.UTF8);
t.jsonb("metadata");
t.uuid("userId");
t.foreign("userId")
.references("id")
.inTable(TableName.Users)
.onDelete("CASCADE");
t.uuid("folderId").notNullable();
t.foreign("folderId")
.references("id")
.inTable(TableName.SecretFolder)
.onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.Secret);
// many to many relation between tags
await createJunctionTable(
knex,
TableName.JnSecretTag,
TableName.Secret,
TableName.SecretTag,
);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretBlindIndex);
await dropOnUpdateTrigger(knex, TableName.SecretBlindIndex);
await knex.schema.dropTableIfExists(TableName.JnSecretTag);
await knex.schema.dropTableIfExists(TableName.Secret);
await dropOnUpdateTrigger(knex, TableName.Secret);
}
@@ -1,53 +0,0 @@
import { Knex } from "knex";
import { SecretEncryptionAlgo, SecretKeyEncoding, SecretType, TableName } from "../schemas";
import { createJunctionTable, createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretVersion))) {
await knex.schema.createTable(TableName.SecretVersion, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.integer("version").defaultTo(1).notNullable();
t.string("type").notNullable().defaultTo(SecretType.Shared);
t.text("secretBlindIndex");
t.text("secretKeyCiphertext").notNullable();
t.text("secretKeyIV").notNullable();
t.text("secretKeyTag").notNullable();
t.text("secretValueCiphertext").notNullable();
t.text("secretValueIV").notNullable(); // symmetric encryption
t.text("secretValueTag").notNullable();
t.text("secretCommentCiphertext");
t.text("secretCommentIV");
t.text("secretCommentTag");
t.string("secretReminderNote");
t.integer("secretReminderRepeatDays");
t.boolean("skipMultilineEncoding").defaultTo(false);
t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM);
t.string("keyEncoding").notNullable().defaultTo(SecretKeyEncoding.UTF8);
t.jsonb("metadata");
// to avoid orphan rows
t.uuid("envId");
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.uuid("secretId").notNullable();
t.uuid("folderId").notNullable();
// t.foreign("secretId").references("id").inTable(TableName.Secret).onDelete("SET NULL");
t.uuid("userId");
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretVersion);
// many to many relation between tags
await createJunctionTable(
knex,
TableName.SecretVersionTag,
TableName.SecretVersion,
TableName.SecretTag
);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretVersionTag);
await knex.schema.dropTableIfExists(TableName.SecretVersion);
await dropOnUpdateTrigger(knex, TableName.SecretVersion);
}
@@ -1,35 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.ProjectBot))) {
await knex.schema.createTable(TableName.ProjectBot, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.boolean("isActive").defaultTo(false).notNullable();
t.text("encryptedPrivateKey").notNullable();
t.text("publicKey").notNullable();
t.text("iv").notNullable();
t.text("tag").notNullable();
t.string("algorithm").notNullable();
t.string("keyEncoding").notNullable();
t.text("encryptedProjectKey");
t.text("encryptedProjectKeyNonce");
// one to one relationship
t.string("projectId").notNullable().unique();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.uuid("senderId");
t.foreign("senderId").references("id").inTable(TableName.Users).onDelete("SET NULL");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.ProjectBot);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ProjectBot);
await dropOnUpdateTrigger(knex, TableName.ProjectBot);
}
@@ -1,71 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IntegrationAuth))) {
await knex.schema.createTable(TableName.IntegrationAuth, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("integration").notNullable();
t.string("teamId"); // vercel-specific
t.string("url"); // for self hosted
t.string("namespace"); // hashicorp specific
t.string("accountId"); // netlify
t.text("refreshCiphertext");
t.string("refreshIV");
t.string("refreshTag");
t.string("accessIdCiphertext");
t.string("accessIdIV");
t.string("accessIdTag");
t.text("accessCiphertext");
t.string("accessIV");
t.string("accessTag");
t.datetime("accessExpiresAt");
t.jsonb("metadata");
t.string("algorithm").notNullable();
t.string("keyEncoding").notNullable();
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.IntegrationAuth);
if (!(await knex.schema.hasTable(TableName.Integration))) {
await knex.schema.createTable(TableName.Integration, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.boolean("isActive").notNullable();
t.string("url"); // self hosted
t.string("app"); // name of app in provider
t.string("appId");
t.string("targetEnvironment");
t.string("targetEnvironmentId");
t.string("targetService"); // railway - qovery specific
t.string("targetServiceId");
t.string("owner"); // github specific
t.string("path"); // aws parameter store / vercel preview branch
t.string("region"); // aws
t.string("scope"); // qovery specific scope
t.string("integration").notNullable();
t.jsonb("metadata");
t.uuid("integrationAuthId").notNullable();
t.foreign("integrationAuthId")
.references("id")
.inTable(TableName.IntegrationAuth)
.onDelete("CASCADE");
t.uuid("envId").notNullable();
t.string("secretPath").defaultTo("/").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.Integration);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.Integration);
await knex.schema.dropTableIfExists(TableName.IntegrationAuth);
await dropOnUpdateTrigger(knex, TableName.IntegrationAuth);
await dropOnUpdateTrigger(knex, TableName.Integration);
}
@@ -1,32 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.ServiceToken))) {
await knex.schema.createTable(TableName.ServiceToken, (t) => {
t.string("id", 36).primary().defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.jsonb("scopes").notNullable();
t.specificType("permissions", "text[]").notNullable();
t.datetime("lastUsed");
t.datetime("expiresAt");
t.text("secretHash").notNullable();
t.text("encryptedKey");
t.text("iv");
t.text("tag");
t.timestamps(true, true, true);
// user is old one
t.string("createdBy").notNullable();
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.ServiceToken);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ServiceToken);
await dropOnUpdateTrigger(knex, TableName.ServiceToken);
}
@@ -1,32 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.Webhook))) {
await knex.schema.createTable(TableName.Webhook, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("secretPath").notNullable().defaultTo("/");
t.string("url").notNullable();
t.string("lastStatus");
t.text("lastRunErrorMessage");
t.boolean("isDisabled").defaultTo(false).notNullable();
// webhook signature
t.text("encryptedSecretKey");
t.text("iv");
t.text("tag");
t.string("algorithm");
t.string("keyEncoding");
t.timestamps(true, true, true);
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.Webhook);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.Webhook);
await dropOnUpdateTrigger(knex, TableName.Webhook);
}
@@ -1,21 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.Identity))) {
await knex.schema.createTable(TableName.Identity, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("authMethod");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.Identity);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.Identity);
await dropOnUpdateTrigger(knex, TableName.Identity);
}
@@ -1,49 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityUniversalAuth))) {
await knex.schema.createTable(TableName.IdentityUniversalAuth, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("clientId").notNullable();
t.bigint("accessTokenTTL").defaultTo(7200).notNullable();
t.bigint("accessTokenMaxTTL").defaultTo(7200).notNullable();
t.bigint("accessTokenNumUsesLimit").defaultTo(0).notNullable();
t.jsonb("clientSecretTrustedIps").notNullable();
t.jsonb("accessTokenTrustedIps").notNullable();
t.timestamps(true, true, true);
t.uuid("identityId").notNullable().unique();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
});
}
if (!(await knex.schema.hasTable(TableName.IdentityUaClientSecret))) {
await knex.schema.createTable(TableName.IdentityUaClientSecret, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("description").notNullable();
t.string("clientSecretPrefix").notNullable();
t.string("clientSecretHash").notNullable();
t.datetime("clientSecretLastUsedAt");
t.bigint("clientSecretNumUses").defaultTo(0).notNullable();
t.bigint("clientSecretNumUsesLimit").defaultTo(0).notNullable();
t.bigint("clientSecretTTL").defaultTo(0).notNullable();
t.boolean("isClientSecretRevoked").defaultTo(false).notNullable();
t.timestamps(true, true, true);
t.uuid("identityUAId").notNullable();
t.foreign("identityUAId")
.references("id")
.inTable(TableName.IdentityUniversalAuth)
.onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.IdentityUniversalAuth);
await createOnUpdateTrigger(knex, TableName.IdentityUaClientSecret);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityUaClientSecret);
await knex.schema.dropTableIfExists(TableName.IdentityUniversalAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityUaClientSecret);
await dropOnUpdateTrigger(knex, TableName.IdentityUniversalAuth);
}
@@ -1,37 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityAccessToken))) {
await knex.schema.createTable(TableName.IdentityAccessToken, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.integer("accessTokenTTL").defaultTo(2592000).notNullable(); // 30 days second
t.integer("accessTokenMaxTTL").defaultTo(2592000).notNullable();
t.integer("accessTokenNumUses").defaultTo(0).notNullable();
t.integer("accessTokenNumUsesLimit").defaultTo(0).notNullable();
t.datetime("accessTokenLastUsedAt");
t.datetime("accessTokenLastRenewedAt");
t.boolean("isAccessTokenRevoked").defaultTo(false).notNullable();
t.uuid("identityUAClientSecretId");
t.foreign("identityUAClientSecretId")
.references("id")
.inTable(TableName.IdentityUaClientSecret)
.onDelete("CASCADE");
t.uuid("identityId").notNullable();
t.foreign("identityId")
.references("id")
.inTable(TableName.Identity)
.onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.IdentityAccessToken);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityAccessToken);
await dropOnUpdateTrigger(knex, TableName.IdentityAccessToken);
}
@@ -1,44 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityOrgMembership))) {
await knex.schema.createTable(TableName.IdentityOrgMembership, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("role").notNullable();
t.uuid("roleId");
t.foreign("roleId").references("id").inTable(TableName.OrgRoles);
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
t.uuid("identityId").notNullable();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.IdentityOrgMembership);
if (!(await knex.schema.hasTable(TableName.IdentityProjectMembership))) {
await knex.schema.createTable(TableName.IdentityProjectMembership, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("role").notNullable();
t.uuid("roleId");
t.foreign("roleId").references("id").inTable(TableName.ProjectRoles);
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.uuid("identityId").notNullable();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.IdentityProjectMembership);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityOrgMembership);
await knex.schema.dropTableIfExists(TableName.IdentityProjectMembership);
await dropOnUpdateTrigger(knex, TableName.IdentityProjectMembership);
await dropOnUpdateTrigger(knex, TableName.IdentityOrgMembership);
}
@@ -1,45 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretApprovalPolicy))) {
await knex.schema.createTable(TableName.SecretApprovalPolicy, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("secretPath");
t.integer("approvals").defaultTo(1).notNullable();
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretApprovalPolicy);
if (!(await knex.schema.hasTable(TableName.SecretApprovalPolicyApprover))) {
await knex.schema.createTable(TableName.SecretApprovalPolicyApprover, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("approverId").notNullable();
t.foreign("approverId")
.references("id")
.inTable(TableName.ProjectMembership)
.onDelete("CASCADE");
t.uuid("policyId").notNullable();
t.foreign("policyId")
.references("id")
.inTable(TableName.SecretApprovalPolicy)
.onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretApprovalPolicyApprover);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretApprovalPolicyApprover);
await knex.schema.dropTableIfExists(TableName.SecretApprovalPolicy);
await dropOnUpdateTrigger(knex, TableName.SecretApprovalPolicyApprover);
await dropOnUpdateTrigger(knex, TableName.SecretApprovalPolicy);
}
@@ -1,118 +0,0 @@
import { Knex } from "knex";
import { SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretApprovalRequest))) {
await knex.schema.createTable(TableName.SecretApprovalRequest, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("policyId").notNullable();
t.boolean("hasMerged").defaultTo(false).notNullable();
t.string("status").defaultTo("open").notNullable();
t.jsonb("conflicts");
t.foreign("policyId")
.references("id")
.inTable(TableName.SecretApprovalPolicy)
.onDelete("CASCADE");
t.string("slug").notNullable();
t.uuid("folderId").notNullable();
t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE");
t.uuid("statusChangeBy");
t.foreign("statusChangeBy")
.references("id")
.inTable(TableName.ProjectMembership)
.onDelete("SET NULL");
t.uuid("committerId").notNullable();
t.foreign("committerId")
.references("id")
.inTable(TableName.ProjectMembership)
.onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretApprovalRequest);
if (!(await knex.schema.hasTable(TableName.SecretApprovalRequestReviewer))) {
await knex.schema.createTable(TableName.SecretApprovalRequestReviewer, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("member").notNullable();
t.foreign("member").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
t.string("status").notNullable();
t.uuid("requestId").notNullable();
t.foreign("requestId")
.references("id")
.inTable(TableName.SecretApprovalRequest)
.onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretApprovalRequestReviewer);
if (!(await knex.schema.hasTable(TableName.SecretApprovalRequestSecret))) {
await knex.schema.createTable(TableName.SecretApprovalRequestSecret, (t) => {
// everything related to secret
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.integer("version").defaultTo(1);
t.text("secretBlindIndex");
t.text("secretKeyCiphertext").notNullable();
t.text("secretKeyIV").notNullable();
t.text("secretKeyTag").notNullable();
t.text("secretValueCiphertext").notNullable();
t.text("secretValueIV").notNullable(); // symmetric encryption
t.text("secretValueTag").notNullable();
t.text("secretCommentCiphertext");
t.text("secretCommentIV");
t.text("secretCommentTag");
t.string("secretReminderNote");
t.integer("secretReminderRepeatDays");
t.boolean("skipMultilineEncoding").defaultTo(false);
t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM);
t.string("keyEncoding").notNullable().defaultTo(SecretKeyEncoding.UTF8);
t.jsonb("metadata");
t.timestamps(true, true, true);
// commit details
t.uuid("requestId").notNullable();
t.foreign("requestId")
.references("id")
.inTable(TableName.SecretApprovalRequest)
.onDelete("CASCADE");
t.string("op").notNullable();
t.uuid("secretId");
t.foreign("secretId").references("id").inTable(TableName.Secret).onDelete("SET NULL");
t.uuid("secretVersion");
t.foreign("secretVersion")
.references("id")
.inTable(TableName.SecretVersion)
.onDelete("SET NULL");
});
}
await createOnUpdateTrigger(knex, TableName.SecretApprovalRequestSecret);
if (!(await knex.schema.hasTable(TableName.SecretApprovalRequestSecretTag))) {
await knex.schema.createTable(TableName.SecretApprovalRequestSecretTag, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("secretId").notNullable();
t.foreign("secretId")
.references("id")
.inTable(TableName.SecretApprovalRequestSecret)
.onDelete("CASCADE");
t.uuid("tagId").notNullable();
t.foreign("tagId").references("id").inTable(TableName.SecretTag).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretApprovalRequestSecretTag);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretApprovalRequestSecretTag);
await knex.schema.dropTableIfExists(TableName.SecretApprovalRequestSecret);
await knex.schema.dropTableIfExists(TableName.SecretApprovalRequestReviewer);
await knex.schema.dropTableIfExists(TableName.SecretApprovalRequest);
await dropOnUpdateTrigger(knex, TableName.SecretApprovalRequestSecretTag);
await dropOnUpdateTrigger(knex, TableName.SecretApprovalRequestSecret);
await dropOnUpdateTrigger(knex, TableName.SecretApprovalRequestReviewer);
await dropOnUpdateTrigger(knex, TableName.SecretApprovalRequest);
}
@@ -1,47 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretRotation))) {
await knex.schema.createTable(TableName.SecretRotation, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("provider").notNullable();
t.string("secretPath").notNullable();
t.integer("interval").notNullable();
t.datetime("lastRotatedAt");
t.string("status");
t.text("statusMessage");
t.text("encryptedData");
t.text("encryptedDataIV");
t.text("encryptedDataTag");
t.string("algorithm");
t.string("keyEncoding");
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretRotation);
if (!(await knex.schema.hasTable(TableName.SecretRotationOutput))) {
await knex.schema.createTable(TableName.SecretRotationOutput, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("key").notNullable();
t.uuid("secretId").notNullable();
t.foreign("secretId").references("id").inTable(TableName.Secret).onDelete("CASCADE");
t.uuid("rotationId").notNullable();
t.foreign("rotationId")
.references("id")
.inTable(TableName.SecretRotation)
.onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretRotationOutput);
await knex.schema.dropTableIfExists(TableName.SecretRotation);
await dropOnUpdateTrigger(knex, TableName.SecretRotation);
}
@@ -1,61 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.Snapshot))) {
await knex.schema.createTable(TableName.Snapshot, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
// this is not a relation kept like that
// this ensure snapshot are not lost when folder gets deleted and rolled back
t.uuid("folderId").notNullable();
t.uuid("parentFolderId");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.Snapshot);
if (!(await knex.schema.hasTable(TableName.SnapshotSecret))) {
await knex.schema.createTable(TableName.SnapshotSecret, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
// not a relation kept like that to keep it when rolled back
t.uuid("secretVersionId").notNullable();
t.foreign("secretVersionId")
.references("id")
.inTable(TableName.SecretVersion)
.onDelete("CASCADE");
t.uuid("snapshotId").notNullable();
t.foreign("snapshotId").references("id").inTable(TableName.Snapshot).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
if (!(await knex.schema.hasTable(TableName.SnapshotFolder))) {
await knex.schema.createTable(TableName.SnapshotFolder, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
// not a relation kept like that to keep it when rolled back
t.uuid("folderVersionId").notNullable();
t.foreign("folderVersionId")
.references("id")
.inTable(TableName.SecretFolderVersion)
.onDelete("CASCADE");
t.uuid("snapshotId").notNullable();
t.foreign("snapshotId").references("id").inTable(TableName.Snapshot).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SnapshotSecret);
await knex.schema.dropTableIfExists(TableName.SnapshotFolder);
await knex.schema.dropTableIfExists(TableName.Snapshot);
await dropOnUpdateTrigger(knex, TableName.Snapshot);
}
@@ -1,33 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SamlConfig))) {
await knex.schema.createTable(TableName.SamlConfig, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("authProvider").notNullable();
t.boolean("isActive").notNullable();
t.string("encryptedEntryPoint");
t.string("entryPointIV");
t.string("entryPointTag");
t.string("encryptedIssuer");
t.string("issuerTag");
t.string("issuerIV");
t.text("encryptedCert");
t.string("certIV");
t.string("certTag");
t.timestamps(true, true, true);
t.uuid("orgId").notNullable().unique();
t.foreign("orgId").references("id").inTable(TableName.Organization);
});
}
await createOnUpdateTrigger(knex, TableName.SamlConfig);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SamlConfig);
await dropOnUpdateTrigger(knex, TableName.SamlConfig);
}
@@ -1,35 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.OrgBot))) {
await knex.schema.createTable(TableName.OrgBot, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.text("publicKey").notNullable();
t.text("encryptedSymmetricKey").notNullable();
t.text("symmetricKeyIV").notNullable();
t.text("symmetricKeyTag").notNullable();
t.string("symmetricKeyAlgorithm").notNullable();
t.string("symmetricKeyKeyEncoding").notNullable();
t.text("encryptedPrivateKey").notNullable();
t.text("privateKeyIV").notNullable();
t.text("privateKeyTag").notNullable();
t.string("privateKeyAlgorithm").notNullable();
t.string("privateKeyKeyEncoding").notNullable();
// one to one relationship
t.uuid("orgId").notNullable().unique();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.OrgBot);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.OrgBot);
await dropOnUpdateTrigger(knex, TableName.OrgBot);
}
@@ -1,29 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.AuditLog))) {
await knex.schema.createTable(TableName.AuditLog, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("actor").notNullable();
t.jsonb("actorMetadata").notNullable();
t.string("ipAddress");
t.string("eventType").notNullable();
t.jsonb("eventMetadata");
t.string("userAgent");
t.string("userAgentType");
t.datetime("expiresAt");
t.timestamps(true, true, true);
// no trigger needed as this collection is append only
t.uuid("orgId");
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.string("projectId");
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.AuditLog);
}
@@ -1,79 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.GitAppInstallSession))) {
await knex.schema.createTable(TableName.GitAppInstallSession, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("sessionId").notNullable().unique();
t.uuid("userId");
// one to one relationship
t.uuid("orgId").notNullable().unique();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
createOnUpdateTrigger(knex, TableName.GitAppInstallSession);
if (!(await knex.schema.hasTable(TableName.GitAppOrg))) {
await knex.schema.createTable(TableName.GitAppOrg, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("installationId").notNullable().unique();
t.uuid("userId").notNullable();
// one to one relationship
t.uuid("orgId").notNullable().unique();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
createOnUpdateTrigger(knex, TableName.GitAppOrg);
if (!(await knex.schema.hasTable(TableName.SecretScanningGitRisk))) {
await knex.schema.createTable(TableName.SecretScanningGitRisk, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("description");
t.string("startLine");
t.string("endLine");
t.string("startColumn");
t.string("endColumn");
t.string("file");
t.string("symlinkFile");
t.string("commit");
t.string("entropy");
t.string("author");
t.string("email");
t.string("date");
t.text("message");
t.specificType("tags", "text[]");
t.string("ruleID");
t.string("fingerprint").unique();
t.string("fingerPrintWithoutCommitId");
t.boolean("isFalsePositive").defaultTo(false);
t.boolean("isResolved").defaultTo(false);
t.string("riskOwner");
t.string("installationId").notNullable();
t.string("repositoryId");
t.string("repositoryLink");
t.string("repositoryFullName");
t.string("pusherName");
t.string("pusherEmail");
t.string("status");
// one to one relationship
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
createOnUpdateTrigger(knex, TableName.SecretScanningGitRisk);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretScanningGitRisk);
await knex.schema.dropTableIfExists(TableName.GitAppOrg);
await knex.schema.dropTableIfExists(TableName.GitAppInstallSession);
await dropOnUpdateTrigger(knex, TableName.SecretScanningGitRisk);
await dropOnUpdateTrigger(knex, TableName.GitAppOrg);
await dropOnUpdateTrigger(knex, TableName.GitAppInstallSession);
}
@@ -1,26 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.TrustedIps))) {
await knex.schema.createTable(TableName.TrustedIps, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("ipAddress").notNullable();
t.string("type").notNullable();
t.integer("prefix");
t.boolean("isActive").defaultTo(true);
t.string("comment");
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project);
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.TrustedIps);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.TrustedIps);
await dropOnUpdateTrigger(knex, TableName.TrustedIps);
}
-39
View File
@@ -1,39 +0,0 @@
import { Schema, Types, model } from "mongoose";
export interface IAPIKeyData {
_id: Types.ObjectId;
name: string;
user: Types.ObjectId;
lastUsed: Date;
expiresAt: Date;
secretHash: string;
}
const apiKeyDataSchema = new Schema<IAPIKeyData>(
{
name: {
type: String,
required: true,
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
lastUsed: {
type: Date,
},
expiresAt: {
type: Date,
},
secretHash: {
type: String,
required: true,
},
},
{
timestamps: true,
},
);
export const APIKeyData = model<IAPIKeyData>("APIKeyData", apiKeyDataSchema);
-38
View File
@@ -1,38 +0,0 @@
import { Document, Schema, Types, model } from "mongoose";
export interface IAPIKeyDataV2 extends Document {
_id: Types.ObjectId;
name: string;
user: Types.ObjectId;
lastUsed?: Date
usageCount: number;
expiresAt?: Date;
}
const apiKeyDataV2Schema = new Schema(
{
name: {
type: String,
required: true
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true
},
lastUsed: {
type: Date,
required: false
},
usageCount: {
type: Number,
default: 0,
required: true
}
},
{
timestamps: true
}
);
export const APIKeyDataV2 = model<IAPIKeyDataV2>("APIKeyDataV2", apiKeyDataV2Schema);

Some files were not shown because too many files have changed in this diff Show More