Merge branch 'main' into feat/secretsBatchUI

This commit is contained in:
Carlos Monastyrski
2025-07-16 08:41:51 -03:00
378 changed files with 6428 additions and 2385 deletions
@@ -1,9 +1,6 @@
import crypto from "node:crypto";
import bcrypt from "bcrypt";
import { TApiKeys } from "@app/db/schemas/api-keys";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { TUserDALFactory } from "../user/user-dal";
@@ -27,7 +24,7 @@ export const apiKeyServiceFactory = ({ apiKeyDAL, userDAL }: TApiKeyServiceFacto
const createApiKey = async (userId: string, name: string, expiresIn: number) => {
const appCfg = getConfig();
const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, appCfg.SALT_ROUNDS);
const secretHash = await crypto.hashing().createHash(secret, appCfg.SALT_ROUNDS);
const expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
@@ -59,7 +56,7 @@ export const apiKeyServiceFactory = ({ apiKeyDAL, userDAL }: TApiKeyServiceFacto
throw new UnauthorizedError();
}
const isMatch = await bcrypt.compare(TOKEN_SECRET, apiKey.secretHash);
const isMatch = await crypto.hashing().compareHash(TOKEN_SECRET, apiKey.secretHash);
if (!isMatch) throw new UnauthorizedError();
await apiKeyDAL.updateById(apiKey.id, { lastUsed: new Date() });
const user = await userDAL.findById(apiKey.userId);
@@ -30,7 +30,8 @@ export enum AppConnection {
Cloudflare = "cloudflare",
Zabbix = "zabbix",
Railway = "railway",
Bitbucket = "bitbucket"
Bitbucket = "bitbucket",
Checkly = "checkly"
}
export enum AWSRegion {
@@ -6,7 +6,7 @@ import {
} from "@app/ee/services/app-connections/oci";
import { getOracleDBConnectionListItem, OracleDBConnectionMethod } from "@app/ee/services/app-connections/oracledb";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { generateHash } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError } from "@app/lib/errors";
import { APP_CONNECTION_NAME_MAP, APP_CONNECTION_PLAN_MAP } from "@app/services/app-connection/app-connection-maps";
import {
@@ -56,6 +56,7 @@ import {
validateBitbucketConnectionCredentials
} from "./bitbucket";
import { CamundaConnectionMethod, getCamundaConnectionListItem, validateCamundaConnectionCredentials } from "./camunda";
import { ChecklyConnectionMethod, getChecklyConnectionListItem, validateChecklyConnectionCredentials } from "./checkly";
import { CloudflareConnectionMethod } from "./cloudflare/cloudflare-connection-enum";
import {
getCloudflareConnectionListItem,
@@ -146,7 +147,8 @@ export const listAppConnectionOptions = () => {
getCloudflareConnectionListItem(),
getZabbixConnectionListItem(),
getRailwayConnectionListItem(),
getBitbucketConnectionListItem()
getBitbucketConnectionListItem(),
getChecklyConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name));
};
@@ -229,7 +231,8 @@ export const validateAppConnectionCredentials = async (
[AppConnection.Cloudflare]: validateCloudflareConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Zabbix]: validateZabbixConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Railway]: validateRailwayConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Checkly]: validateChecklyConnectionCredentials as TAppConnectionCredentialsValidator
};
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
@@ -287,6 +290,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case LdapConnectionMethod.SimpleBind:
return "Simple Bind";
case RenderConnectionMethod.ApiKey:
case ChecklyConnectionMethod.ApiKey:
return "API Key";
default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
@@ -305,7 +309,7 @@ export const decryptAppConnection = async (
orgId: appConnection.orgId,
kmsService
}),
credentialsHash: generateHash(appConnection.encryptedCredentials)
credentialsHash: crypto.nativeCrypto.createHash("sha256").update(appConnection.encryptedCredentials).digest("hex")
} as TAppConnection;
};
@@ -350,7 +354,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.Cloudflare]: platformManagedCredentialsNotSupported,
[AppConnection.Zabbix]: platformManagedCredentialsNotSupported,
[AppConnection.Railway]: platformManagedCredentialsNotSupported,
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported,
[AppConnection.Checkly]: platformManagedCredentialsNotSupported
};
export const enterpriseAppCheck = async (
@@ -32,7 +32,8 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.Cloudflare]: "Cloudflare",
[AppConnection.Zabbix]: "Zabbix",
[AppConnection.Railway]: "Railway",
[AppConnection.Bitbucket]: "Bitbucket"
[AppConnection.Bitbucket]: "Bitbucket",
[AppConnection.Checkly]: "Checkly"
};
export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
@@ -67,5 +68,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
[AppConnection.Cloudflare]: AppConnectionPlanType.Regular,
[AppConnection.Zabbix]: AppConnectionPlanType.Regular,
[AppConnection.Railway]: AppConnectionPlanType.Regular,
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular,
[AppConnection.Checkly]: AppConnectionPlanType.Regular
};
@@ -6,7 +6,7 @@ import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/ap
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { generateHash } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { DatabaseErrorCode } from "@app/lib/error-codes";
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
import { DiscriminativePick, OrgServiceActor } from "@app/lib/types";
@@ -49,6 +49,8 @@ import { ValidateBitbucketConnectionCredentialsSchema } from "./bitbucket";
import { bitbucketConnectionService } from "./bitbucket/bitbucket-connection-service";
import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
import { camundaConnectionService } from "./camunda/camunda-connection-service";
import { ValidateChecklyConnectionCredentialsSchema } from "./checkly";
import { checklyConnectionService } from "./checkly/checkly-connection-service";
import { ValidateCloudflareConnectionCredentialsSchema } from "./cloudflare/cloudflare-connection-schema";
import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service";
import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
@@ -128,7 +130,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.Cloudflare]: ValidateCloudflareConnectionCredentialsSchema,
[AppConnection.Zabbix]: ValidateZabbixConnectionCredentialsSchema,
[AppConnection.Railway]: ValidateRailwayConnectionCredentialsSchema,
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema,
[AppConnection.Checkly]: ValidateChecklyConnectionCredentialsSchema
};
export const appConnectionServiceFactory = ({
@@ -281,7 +284,7 @@ export const appConnectionServiceFactory = ({
return {
...connection,
credentialsHash: generateHash(connection.encryptedCredentials),
credentialsHash: crypto.nativeCrypto.createHash("sha256").update(connection.encryptedCredentials).digest("hex"),
credentials: validatedCredentials
} as TAppConnection;
} catch (err) {
@@ -541,6 +544,7 @@ export const appConnectionServiceFactory = ({
cloudflare: cloudflareConnectionService(connectAppConnectionById),
zabbix: zabbixConnectionService(connectAppConnectionById),
railway: railwayConnectionService(connectAppConnectionById),
bitbucket: bitbucketConnectionService(connectAppConnectionById)
bitbucket: bitbucketConnectionService(connectAppConnectionById),
checkly: checklyConnectionService(connectAppConnectionById)
};
};
@@ -68,6 +68,12 @@ import {
TCamundaConnectionInput,
TValidateCamundaConnectionCredentialsSchema
} from "./camunda";
import {
TChecklyConnection,
TChecklyConnectionConfig,
TChecklyConnectionInput,
TValidateChecklyConnectionCredentialsSchema
} from "./checkly";
import {
TCloudflareConnection,
TCloudflareConnectionConfig,
@@ -217,6 +223,7 @@ export type TAppConnection = { id: string } & (
| TBitbucketConnection
| TZabbixConnection
| TRailwayConnection
| TChecklyConnection
);
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
@@ -256,6 +263,7 @@ export type TAppConnectionInput = { id: string } & (
| TBitbucketConnectionInput
| TZabbixConnectionInput
| TRailwayConnectionInput
| TChecklyConnectionInput
);
export type TSqlConnectionInput =
@@ -302,7 +310,8 @@ export type TAppConnectionConfig =
| TCloudflareConnectionConfig
| TBitbucketConnectionConfig
| TZabbixConnectionConfig
| TRailwayConnectionConfig;
| TRailwayConnectionConfig
| TChecklyConnectionConfig;
export type TValidateAppConnectionCredentialsSchema =
| TValidateAwsConnectionCredentialsSchema
@@ -336,7 +345,8 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateCloudflareConnectionCredentialsSchema
| TValidateBitbucketConnectionCredentialsSchema
| TValidateZabbixConnectionCredentialsSchema
| TValidateRailwayConnectionCredentialsSchema;
| TValidateRailwayConnectionCredentialsSchema
| TValidateChecklyConnectionCredentialsSchema;
export type TListAwsConnectionKmsKeys = {
connectionId: string;
@@ -1,9 +1,10 @@
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
import AWS from "aws-sdk";
import { AxiosError } from "axios";
import { randomUUID } from "crypto";
import { CustomAWSHasher } from "@app/lib/aws/hashing";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums";
@@ -35,6 +36,8 @@ export const getAwsConnectionConfig = async (appConnection: TAwsConnectionConfig
case AwsConnectionMethod.AssumeRole: {
const client = new STSClient({
region,
useFipsEndpoint: crypto.isFipsModeEnabled(),
sha256: CustomAWSHasher,
credentials:
appCfg.INF_APP_CONNECTION_AWS_ACCESS_KEY_ID && appCfg.INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY
? {
@@ -46,7 +49,7 @@ export const getAwsConnectionConfig = async (appConnection: TAwsConnectionConfig
const command = new AssumeRoleCommand({
RoleArn: credentials.roleArn,
RoleSessionName: `infisical-app-connection-${randomUUID()}`,
RoleSessionName: `infisical-app-connection-${crypto.nativeCrypto.randomUUID()}`,
DurationSeconds: 900, // 15 mins
ExternalId: orgId
});
@@ -0,0 +1,3 @@
export enum ChecklyConnectionMethod {
ApiKey = "api-key"
}
@@ -0,0 +1,35 @@
/* eslint-disable no-await-in-loop */
import { AxiosError } from "axios";
import { BadRequestError } from "@app/lib/errors";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { ChecklyConnectionMethod } from "./checkly-connection-constants";
import { ChecklyPublicAPI } from "./checkly-connection-public-client";
import { TChecklyConnectionConfig } from "./checkly-connection-types";
export const getChecklyConnectionListItem = () => {
return {
name: "Checkly" as const,
app: AppConnection.Checkly as const,
methods: Object.values(ChecklyConnectionMethod)
};
};
export const validateChecklyConnectionCredentials = async (config: TChecklyConnectionConfig) => {
try {
await ChecklyPublicAPI.healthcheck(config);
} catch (error: unknown) {
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: "Unable to validate connection - verify credentials"
});
}
return config.credentials;
};
@@ -0,0 +1,186 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable class-methods-use-this */
import { AxiosInstance, AxiosRequestConfig, AxiosResponse, HttpStatusCode, isAxiosError } from "axios";
import { createRequestClient } from "@app/lib/config/request";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { ChecklyConnectionMethod } from "./checkly-connection-constants";
import { TChecklyAccount, TChecklyConnectionConfig, TChecklyVariable } from "./checkly-connection-types";
export function getChecklyAuthHeaders(
connection: TChecklyConnectionConfig,
accountId?: string
): Record<string, string> {
switch (connection.method) {
case ChecklyConnectionMethod.ApiKey:
return {
Authorization: `Bearer ${connection.credentials.apiKey}`,
...(accountId && { "X-Checkly-Account": accountId })
};
default:
throw new Error(`Unsupported Checkly connection method`);
}
}
export function getChecklyRatelimiter(response: AxiosResponse): {
maxAttempts: number;
isRatelimited: boolean;
wait: () => Promise<void>;
} {
const wait = () => {
return new Promise<void>((res) => {
setTimeout(res, 60 * 1000); // Wait for 60 seconds
});
};
return {
isRatelimited: response.status === HttpStatusCode.TooManyRequests,
wait,
maxAttempts: 3
};
}
class ChecklyPublicClient {
private client: AxiosInstance;
constructor() {
this.client = createRequestClient({
baseURL: IntegrationUrls.CHECKLY_API_URL,
headers: {
"Content-Type": "application/json"
}
});
}
async send<T>(
connection: TChecklyConnectionConfig,
config: AxiosRequestConfig & { accountId?: string },
retryAttempt = 0
): Promise<T | undefined> {
const response = await this.client.request<T>({
...config,
timeout: 1000 * 60, // 60 seconds timeout
validateStatus: (status) => (status >= 200 && status < 300) || status === HttpStatusCode.TooManyRequests,
headers: getChecklyAuthHeaders(connection, config.accountId)
});
const limiter = getChecklyRatelimiter(response);
if (limiter.isRatelimited && retryAttempt <= limiter.maxAttempts) {
await limiter.wait();
return this.send(connection, config, retryAttempt + 1);
}
return response.data;
}
healthcheck(connection: TChecklyConnectionConfig) {
switch (connection.method) {
case ChecklyConnectionMethod.ApiKey:
return this.getChecklyAccounts(connection);
default:
throw new Error(`Unsupported Checkly connection method`);
}
}
async getVariables(connection: TChecklyConnectionConfig, accountId: string, limit: number = 50, page: number = 1) {
const res = await this.send<TChecklyVariable[]>(connection, {
accountId,
method: "GET",
url: `/v1/variables`,
params: {
limit,
page
}
});
return res;
}
async createVariable(connection: TChecklyConnectionConfig, accountId: string, variable: TChecklyVariable) {
const res = await this.send<TChecklyVariable>(connection, {
accountId,
method: "POST",
url: `/v1/variables`,
data: variable
});
return res;
}
async updateVariable(connection: TChecklyConnectionConfig, accountId: string, variable: TChecklyVariable) {
const res = await this.send<TChecklyVariable>(connection, {
accountId,
method: "PUT",
url: `/v1/variables/${variable.key}`,
data: variable
});
return res;
}
async getVariable(connection: TChecklyConnectionConfig, accountId: string, variable: Pick<TChecklyVariable, "key">) {
try {
const res = await this.send<TChecklyVariable>(connection, {
accountId,
method: "GET",
url: `/v1/variables/${variable.key}`
});
return res;
} catch (error) {
if (isAxiosError(error) && error.response?.status === HttpStatusCode.NotFound) {
return null;
}
throw error;
}
}
async upsertVariable(connection: TChecklyConnectionConfig, accountId: string, variable: TChecklyVariable) {
const res = await this.getVariable(connection, accountId, variable);
if (!res) {
return this.createVariable(connection, accountId, variable);
}
await this.updateVariable(connection, accountId, variable);
return res;
}
async deleteVariable(
connection: TChecklyConnectionConfig,
accountId: string,
variable: Pick<TChecklyVariable, "key">
) {
try {
const res = await this.send<TChecklyVariable>(connection, {
accountId,
method: "DELETE",
url: `/v1/variables/${variable.key}`
});
return res;
} catch (error) {
if (isAxiosError(error) && error.response?.status === HttpStatusCode.NotFound) {
return null;
}
throw error;
}
}
async getChecklyAccounts(connection: TChecklyConnectionConfig) {
// This endpoint is in beta and might be subject to changes
// Refer: https://developers.checklyhq.com/reference/getv1accounts
const res = await this.send<TChecklyAccount[]>(connection, {
method: "GET",
url: `/v1/accounts`
});
return res;
}
}
export const ChecklyPublicAPI = new ChecklyPublicClient();
@@ -0,0 +1,62 @@
import z from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { ChecklyConnectionMethod } from "./checkly-connection-constants";
export const ChecklyConnectionMethodSchema = z
.nativeEnum(ChecklyConnectionMethod)
.describe(AppConnections.CREATE(AppConnection.Checkly).method);
export const ChecklyConnectionAccessTokenCredentialsSchema = z.object({
apiKey: z.string().trim().min(1, "API Key required").max(255).describe(AppConnections.CREDENTIALS.CHECKLY.apiKey)
});
const BaseChecklyConnectionSchema = BaseAppConnectionSchema.extend({
app: z.literal(AppConnection.Checkly)
});
export const ChecklyConnectionSchema = BaseChecklyConnectionSchema.extend({
method: ChecklyConnectionMethodSchema,
credentials: ChecklyConnectionAccessTokenCredentialsSchema
});
export const SanitizedChecklyConnectionSchema = z.discriminatedUnion("method", [
BaseChecklyConnectionSchema.extend({
method: ChecklyConnectionMethodSchema,
credentials: ChecklyConnectionAccessTokenCredentialsSchema.pick({})
})
]);
export const ValidateChecklyConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: ChecklyConnectionMethodSchema,
credentials: ChecklyConnectionAccessTokenCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.Checkly).credentials
)
})
]);
export const CreateChecklyConnectionSchema = ValidateChecklyConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.Checkly)
);
export const UpdateChecklyConnectionSchema = z
.object({
credentials: ChecklyConnectionAccessTokenCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.Checkly).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Checkly));
export const ChecklyConnectionListItemSchema = z.object({
name: z.literal("Checkly"),
app: z.literal(AppConnection.Checkly),
methods: z.nativeEnum(ChecklyConnectionMethod).array()
});
@@ -0,0 +1,30 @@
import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { ChecklyPublicAPI } from "./checkly-connection-public-client";
import { TChecklyConnection } from "./checkly-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TChecklyConnection>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const checklyConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listAccounts = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.Checkly, connectionId, actor);
try {
const accounts = await ChecklyPublicAPI.getChecklyAccounts(appConnection);
return accounts!;
} catch (error) {
logger.error(error, "Failed to list accounts on Checkly");
return [];
}
};
return {
listAccounts
};
};
@@ -0,0 +1,35 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
ChecklyConnectionSchema,
CreateChecklyConnectionSchema,
ValidateChecklyConnectionCredentialsSchema
} from "./checkly-connection-schemas";
export type TChecklyConnection = z.infer<typeof ChecklyConnectionSchema>;
export type TChecklyConnectionInput = z.infer<typeof CreateChecklyConnectionSchema> & {
app: AppConnection.Checkly;
};
export type TValidateChecklyConnectionCredentialsSchema = typeof ValidateChecklyConnectionCredentialsSchema;
export type TChecklyConnectionConfig = DiscriminativePick<TChecklyConnection, "method" | "app" | "credentials"> & {
orgId: string;
};
export type TChecklyVariable = {
key: string;
value: string;
locked: boolean;
secret: boolean;
};
export type TChecklyAccount = {
id: string;
name: string;
runtimeId: string;
};
@@ -0,0 +1,4 @@
export * from "./checkly-connection-constants";
export * from "./checkly-connection-fns";
export * from "./checkly-connection-schemas";
export * from "./checkly-connection-types";
@@ -9,7 +9,8 @@ import { CloudflareConnectionMethod } from "./cloudflare-connection-enum";
import {
TCloudflareConnection,
TCloudflareConnectionConfig,
TCloudflarePagesProject
TCloudflarePagesProject,
TCloudflareWorkersScript
} from "./cloudflare-connection-types";
export const getCloudflareConnectionListItem = () => {
@@ -43,6 +44,28 @@ export const listCloudflarePagesProjects = async (
}));
};
export const listCloudflareWorkersScripts = async (
appConnection: TCloudflareConnection
): Promise<TCloudflareWorkersScript[]> => {
const {
credentials: { apiToken, accountId }
} = appConnection;
const { data } = await request.get<{ result: { id: string }[] }>(
`${IntegrationUrls.CLOUDFLARE_API_URL}/client/v4/accounts/${accountId}/workers/scripts`,
{
headers: {
Authorization: `Bearer ${apiToken}`,
Accept: "application/json"
}
}
);
return data.result.map((a) => ({
id: a.id
}));
};
export const validateCloudflareConnectionCredentials = async (config: TCloudflareConnectionConfig) => {
const { apiToken, accountId } = config.credentials;
@@ -2,7 +2,7 @@ import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { listCloudflarePagesProjects } from "./cloudflare-connection-fns";
import { listCloudflarePagesProjects, listCloudflareWorkersScripts } from "./cloudflare-connection-fns";
import { TCloudflareConnection } from "./cloudflare-connection-types";
type TGetAppConnectionFunc = (
@@ -19,12 +19,31 @@ export const cloudflareConnectionService = (getAppConnection: TGetAppConnectionF
return projects;
} catch (error) {
logger.error(error, "Failed to list Cloudflare Pages projects for Cloudflare connection");
logger.error(
error,
`Failed to list Cloudflare Pages projects for Cloudflare connection [connectionId=${connectionId}]`
);
return [];
}
};
const listWorkersScripts = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.Cloudflare, connectionId, actor);
try {
const projects = await listCloudflareWorkersScripts(appConnection);
return projects;
} catch (error) {
logger.error(
error,
`Failed to list Cloudflare Workers scripts for Cloudflare connection [connectionId=${connectionId}]`
);
return [];
}
};
return {
listPagesProjects
listPagesProjects,
listWorkersScripts
};
};
@@ -28,3 +28,7 @@ export type TCloudflarePagesProject = {
id: string;
name: string;
};
export type TCloudflareWorkersScript = {
id: string;
};
@@ -7,7 +7,6 @@ import { request } from "@app/lib/config/request";
import { BadRequestError, ForbiddenRequestError, InternalServerError } from "@app/lib/errors";
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { getInstanceIntegrationsConfig } from "@app/services/super-admin/super-admin-service";
import { AppConnection } from "../app-connection-enums";
import { GitHubConnectionMethod } from "./github-connection-enums";
@@ -15,14 +14,13 @@ import { TGitHubConnection, TGitHubConnectionConfig } from "./github-connection-
export const getGitHubConnectionListItem = () => {
const { INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID, INF_APP_CONNECTION_GITHUB_APP_SLUG } = getConfig();
const { gitHubAppConnection } = getInstanceIntegrationsConfig();
return {
name: "GitHub" as const,
app: AppConnection.GitHub as const,
methods: Object.values(GitHubConnectionMethod) as [GitHubConnectionMethod.App, GitHubConnectionMethod.OAuth],
oauthClientId: INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID,
appClientSlug: gitHubAppConnection.appSlug || INF_APP_CONNECTION_GITHUB_APP_SLUG
appClientSlug: INF_APP_CONNECTION_GITHUB_APP_SLUG
};
};
@@ -32,10 +30,9 @@ export const getGitHubClient = (appConnection: TGitHubConnection) => {
const { method, credentials } = appConnection;
let client: Octokit;
const { gitHubAppConnection } = getInstanceIntegrationsConfig();
const appId = gitHubAppConnection.appId || appCfg.INF_APP_CONNECTION_GITHUB_APP_ID;
const appPrivateKey = gitHubAppConnection.privateKey || appCfg.INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY;
const appId = appCfg.INF_APP_CONNECTION_GITHUB_APP_ID;
const appPrivateKey = appCfg.INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY;
switch (method) {
case GitHubConnectionMethod.App:
@@ -157,8 +154,6 @@ type TokenRespData = {
export const validateGitHubConnectionCredentials = async (config: TGitHubConnectionConfig) => {
const { credentials, method } = config;
const { gitHubAppConnection } = getInstanceIntegrationsConfig();
const {
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID,
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET,
@@ -170,8 +165,8 @@ export const validateGitHubConnectionCredentials = async (config: TGitHubConnect
const { clientId, clientSecret } =
method === GitHubConnectionMethod.App
? {
clientId: gitHubAppConnection.clientId || INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID,
clientSecret: gitHubAppConnection.clientSecret || INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET
clientId: INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID,
clientSecret: INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET
}
: // oauth
{
@@ -1,11 +1,8 @@
import crypto from "node:crypto";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { Knex } from "knex";
import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
@@ -81,7 +78,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu
const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => {
const { token, ...tkCfg } = getTokenConfig(type);
const appCfg = getConfig();
const tokenHash = await bcrypt.hash(token, appCfg.SALT_ROUNDS);
const tokenHash = await crypto.hashing().createHash(token, appCfg.SALT_ROUNDS);
await tokenDAL.transaction(async (tx) => {
await tokenDAL.delete({ userId, type, orgId: orgId || null }, tx);
const newToken = await tokenDAL.create(
@@ -115,7 +112,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu
throw new Error("Token expired. Please try again");
}
const isValidToken = await bcrypt.compare(code, token.tokenHash);
const isValidToken = await crypto.hashing().compareHash(code, token.tokenHash);
if (!isValidToken) {
if (token?.triesLeft) {
if (token.triesLeft === 1) {
@@ -162,7 +159,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu
message: "Failed to find refresh token"
});
const decodedToken = jwt.verify(refreshToken, appCfg.AUTH_SECRET) as AuthModeRefreshJwtTokenPayload;
const decodedToken = crypto.jwt().verify(refreshToken, appCfg.AUTH_SECRET) as AuthModeRefreshJwtTokenPayload;
if (decodedToken.authTokenType !== AuthTokenType.REFRESH_TOKEN)
throw new UnauthorizedError({
+4 -5
View File
@@ -1,6 +1,5 @@
import jwt from "jsonwebtoken";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { AuthModeProviderJwtTokenPayload, AuthModeProviderSignUpTokenPayload, AuthTokenType } from "./auth-type";
@@ -8,7 +7,7 @@ import { AuthModeProviderJwtTokenPayload, AuthModeProviderSignUpTokenPayload, Au
export const validateProviderAuthToken = (providerToken: string, username?: string) => {
if (!providerToken) throw new UnauthorizedError();
const appCfg = getConfig();
const decodedToken = jwt.verify(providerToken, appCfg.AUTH_SECRET) as AuthModeProviderJwtTokenPayload;
const decodedToken = crypto.jwt().verify(providerToken, appCfg.AUTH_SECRET) as AuthModeProviderJwtTokenPayload;
if (decodedToken.authTokenType !== AuthTokenType.PROVIDER_TOKEN) throw new UnauthorizedError();
@@ -38,7 +37,7 @@ export const validateSignUpAuthorization = (token: string, userId: string, valid
});
}
const decodedToken = jwt.verify(AUTH_TOKEN_VALUE, appCfg.AUTH_SECRET) as AuthModeProviderSignUpTokenPayload;
const decodedToken = crypto.jwt().verify(AUTH_TOKEN_VALUE, appCfg.AUTH_SECRET) as AuthModeProviderSignUpTokenPayload;
if (!validate) return decodedToken;
if (decodedToken.authTokenType !== AuthTokenType.SIGNUP_TOKEN) throw new UnauthorizedError();
@@ -64,7 +63,7 @@ export const validatePasswordResetAuthorization = (token?: string) => {
});
}
const decodedToken = jwt.verify(AUTH_TOKEN_VALUE, appCfg.AUTH_SECRET) as AuthModeProviderSignUpTokenPayload;
const decodedToken = crypto.jwt().verify(AUTH_TOKEN_VALUE, appCfg.AUTH_SECRET) as AuthModeProviderSignUpTokenPayload;
if (decodedToken.authTokenType !== AuthTokenType.SIGNUP_TOKEN) {
throw new UnauthorizedError({
+15 -12
View File
@@ -1,5 +1,3 @@
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { Knex } from "knex";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TUsers, UserDeviceSchema } from "@app/db/schemas";
@@ -7,8 +5,7 @@ import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/a
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { crypto, generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
import { getUserPrivateKey } from "@app/lib/crypto/srp";
import { BadRequestError, DatabaseError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { getMinExpiresIn, removeTrailingSlash } from "@app/lib/fn";
@@ -157,7 +154,7 @@ export const authLoginServiceFactory = ({
}
}
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
authMethod,
authTokenType: AuthTokenType.ACCESS_TOKEN,
@@ -172,7 +169,7 @@ export const authLoginServiceFactory = ({
{ expiresIn: tokenSessionExpiresIn }
);
const refreshToken = jwt.sign(
const refreshToken = crypto.jwt().sign(
{
authMethod,
authTokenType: AuthTokenType.REFRESH_TOKEN,
@@ -336,8 +333,14 @@ export const authLoginServiceFactory = ({
);
return "";
});
const hashedPassword = await bcrypt.hash(password, cfg.BCRYPT_SALT_ROUND);
const { iv, tag, ciphertext, encoding } = infisicalSymmetricEncypt(privateKey);
const hashedPassword = await crypto.hashing().createHash(password, cfg.SALT_ROUNDS);
const { iv, tag, ciphertext, encoding } = crypto
.encryption()
.symmetric()
.encryptWithRootEncryptionKey(privateKey);
await userDAL.updateUserEncryptionByUserId(userEnc.userId, {
serverPrivateKey: null,
clientPublicKey: null,
@@ -388,7 +391,7 @@ export const authLoginServiceFactory = ({
authJwtToken = authJwtToken.replace("Bearer ", ""); // remove bearer from token
// The decoded JWT token, which contains the auth method.
const decodedToken = jwt.verify(authJwtToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload;
const decodedToken = crypto.jwt().verify(authJwtToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload;
if (!decodedToken.authMethod) throw new UnauthorizedError({ name: "Auth method not found on existing token" });
const user = await userDAL.findUserEncKeyByUserId(decodedToken.userId);
@@ -413,7 +416,7 @@ export const authLoginServiceFactory = ({
if (shouldCheckMfa && (!decodedToken.isMfaVerified || decodedToken.mfaMethod !== mfaMethod)) {
enforceUserLockStatus(Boolean(user.isLocked), user.temporaryLockDateEnd);
const mfaToken = jwt.sign(
const mfaToken = crypto.jwt().sign(
{
authMethod: decodedToken.authMethod,
authTokenType: AuthTokenType.MFA_TOKEN,
@@ -624,7 +627,7 @@ export const authLoginServiceFactory = ({
throw err;
}
const decodedToken = jwt.verify(mfaJwtToken, getConfig().AUTH_SECRET) as AuthModeMfaJwtTokenPayload;
const decodedToken = crypto.jwt().verify(mfaJwtToken, getConfig().AUTH_SECRET) as AuthModeMfaJwtTokenPayload;
const userEnc = await userDAL.findUserEncKeyByUserId(userId);
if (!userEnc) throw new Error("Failed to authenticate user");
@@ -774,7 +777,7 @@ export const authLoginServiceFactory = ({
const userEnc = await userDAL.findUserEncKeyByUserId(user.id);
const isUserCompleted = user.isAccepted;
const providerAuthToken = jwt.sign(
const providerAuthToken = crypto.jwt().sign(
{
authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user.id,
@@ -1,10 +1,7 @@
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { generateUserSrpKeys } from "@app/lib/crypto/srp";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
@@ -95,7 +92,7 @@ export const authPaswordServiceFactory = ({
if (!isValidClientProof) throw new Error("Failed to authenticate. Try again?");
const appCfg = getConfig();
const hashedPassword = await bcrypt.hash(password, appCfg.BCRYPT_SALT_ROUND);
const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS);
await userDAL.updateUserEncryptionByUserId(userId, {
encryptionVersion: 2,
protectedKey,
@@ -175,7 +172,7 @@ export const authPaswordServiceFactory = ({
code
});
const token = jwt.sign(
const token = crypto.jwt().sign(
{
authTokenType: AuthTokenType.SIGNUP_TOKEN,
userId: user.id
@@ -208,13 +205,13 @@ export const authPaswordServiceFactory = ({
throw new BadRequestError({ message: "Current password is required." });
}
const isValid = await bcrypt.compare(oldPassword, user.hashedPassword);
const isValid = await crypto.hashing().compareHash(oldPassword, user.hashedPassword);
if (!isValid) {
throw new BadRequestError({ message: "Incorrect current password." });
}
}
const newHashedPassword = await bcrypt.hash(newPassword, cfg.BCRYPT_SALT_ROUND);
const newHashedPassword = await crypto.hashing().createHash(newPassword, cfg.SALT_ROUNDS);
// we need to get the original private key first for v2
let privateKey: string;
@@ -225,12 +222,15 @@ export const authPaswordServiceFactory = ({
user.serverEncryptedPrivateKeyEncoding &&
user.encryptionVersion === UserEncryption.V2
) {
privateKey = infisicalSymmetricDecrypt({
iv: user.serverEncryptedPrivateKeyIV,
tag: user.serverEncryptedPrivateKeyTag,
ciphertext: user.serverEncryptedPrivateKey,
keyEncoding: user.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
});
privateKey = crypto
.encryption()
.symmetric()
.decryptWithRootEncryptionKey({
iv: user.serverEncryptedPrivateKeyIV,
tag: user.serverEncryptedPrivateKeyTag,
ciphertext: user.serverEncryptedPrivateKey,
keyEncoding: user.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
});
} else {
throw new BadRequestError({
message: "Cannot reset password without current credentials or recovery method",
@@ -243,7 +243,7 @@ export const authPaswordServiceFactory = ({
privateKey
});
const { tag, iv, ciphertext, encoding } = infisicalSymmetricEncypt(privateKey);
const { tag, iv, ciphertext, encoding } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(privateKey);
await userDAL.updateUserEncryptionByUserId(userId, {
hashedPassword: newHashedPassword,
@@ -285,7 +285,7 @@ export const authPaswordServiceFactory = ({
}: TResetPasswordViaBackupKeyDTO) => {
const cfg = getConfig();
const hashedPassword = await bcrypt.hash(password, cfg.BCRYPT_SALT_ROUND);
const hashedPassword = await crypto.hashing().createHash(password, cfg.SALT_ROUNDS);
await userDAL.updateUserEncryptionByUserId(userId, {
encryptionVersion: 2,
@@ -461,7 +461,7 @@ export const authPaswordServiceFactory = ({
const cfg = getConfig();
const hashedPassword = await bcrypt.hash(password, cfg.BCRYPT_SALT_ROUND);
const hashedPassword = await crypto.hashing().createHash(password, cfg.SALT_ROUNDS);
await userDAL.updateUserEncryptionByUserId(
actor.id,
@@ -1,13 +1,10 @@
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { OrgMembershipStatus, SecretKeyEncoding, TableName } from "@app/db/schemas";
import { convertPendingGroupAdditionsToGroupMemberships } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
import { getConfig } from "@app/lib/config/env";
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { getMinExpiresIn } from "@app/lib/fn";
@@ -132,7 +129,7 @@ export const authSignupServiceFactory = ({
await userDAL.updateById(user.id, { isEmailVerified: true });
// generate jwt token this is a temporary token
const jwtToken = jwt.sign(
const jwtToken = crypto.jwt().sign(
{
authTokenType: AuthTokenType.SIGNUP_TOKEN,
userId: user.id.toString()
@@ -193,7 +190,7 @@ export const authSignupServiceFactory = ({
validateSignUpAuthorization(authorization, user.id);
}
const hashedPassword = await bcrypt.hash(password, appCfg.BCRYPT_SALT_ROUND);
const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS);
const privateKey = await getUserPrivateKey(password, {
salt,
protectedKey,
@@ -204,7 +201,7 @@ export const authSignupServiceFactory = ({
tag: encryptedPrivateKeyTag,
encryptionVersion: UserEncryption.V2
});
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey);
const { tag, encoding, ciphertext, iv } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(privateKey);
const updateduser = await authDAL.transaction(async (tx) => {
const us = await userDAL.updateById(user.id, { firstName, lastName, isAccepted: true }, tx);
if (!us) throw new Error("User not found");
@@ -225,12 +222,15 @@ export const authSignupServiceFactory = ({
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding
) {
// get server generated password
const serverGeneratedPassword = infisicalSymmetricDecrypt({
iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV,
tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag,
ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey,
keyEncoding: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
});
const serverGeneratedPassword = crypto
.encryption()
.symmetric()
.decryptWithRootEncryptionKey({
iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV,
tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag,
ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey,
keyEncoding: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
});
const serverGeneratedPrivateKey = await getUserPrivateKey(serverGeneratedPassword, {
...systemGeneratedUserEncryptionKey
});
@@ -365,7 +365,7 @@ export const authSignupServiceFactory = ({
});
if (!tokenSession) throw new Error("Failed to create token");
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
authMethod: authMethod || AuthMethod.EMAIL,
authTokenType: AuthTokenType.ACCESS_TOKEN,
@@ -378,7 +378,7 @@ export const authSignupServiceFactory = ({
{ expiresIn: tokenSessionExpiresIn }
);
const refreshToken = jwt.sign(
const refreshToken = crypto.jwt().sign(
{
authMethod: authMethod || AuthMethod.EMAIL,
authTokenType: AuthTokenType.REFRESH_TOKEN,
@@ -436,7 +436,7 @@ export const authSignupServiceFactory = ({
});
const appCfg = getConfig();
const hashedPassword = await bcrypt.hash(password, appCfg.BCRYPT_SALT_ROUND);
const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS);
const privateKey = await getUserPrivateKey(password, {
salt,
protectedKey,
@@ -447,7 +447,7 @@ export const authSignupServiceFactory = ({
tag: encryptedPrivateKeyTag,
encryptionVersion: 2
});
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey);
const { tag, encoding, ciphertext, iv } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(privateKey);
const updateduser = await authDAL.transaction(async (tx) => {
const us = await userDAL.updateById(user.id, { firstName, lastName, isAccepted: true }, tx);
if (!us) throw new Error("User not found");
@@ -464,12 +464,15 @@ export const authSignupServiceFactory = ({
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding
) {
// get server generated password
const serverGeneratedPassword = infisicalSymmetricDecrypt({
iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV,
tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag,
ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey,
keyEncoding: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
});
const serverGeneratedPassword = crypto
.encryption()
.symmetric()
.decryptWithRootEncryptionKey({
iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV,
tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag,
ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey,
keyEncoding: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
});
const serverGeneratedPrivateKey = await getUserPrivateKey(serverGeneratedPassword, {
...systemGeneratedUserEncryptionKey
});
@@ -552,7 +555,7 @@ export const authSignupServiceFactory = ({
});
if (!tokenSession) throw new Error("Failed to create token");
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
authMethod: AuthMethod.EMAIL,
authTokenType: AuthTokenType.ACCESS_TOKEN,
@@ -564,7 +567,7 @@ export const authSignupServiceFactory = ({
{ expiresIn: appCfg.JWT_SIGNUP_LIFETIME }
);
const refreshToken = jwt.sign(
const refreshToken = crypto.jwt().sign(
{
authMethod: AuthMethod.EMAIL,
authTokenType: AuthTokenType.REFRESH_TOKEN,
@@ -1,10 +1,11 @@
import { ChangeResourceRecordSetsCommand, Route53Client } from "@aws-sdk/client-route-53";
import * as x509 from "@peculiar/x509";
import acme from "acme-client";
import { KeyObject } from "crypto";
import { TableName } from "@app/db/schemas";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { CustomAWSHasher } from "@app/lib/aws/hashing";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, CryptographyError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
@@ -102,6 +103,8 @@ export const route53InsertTxtRecord = async (
) => {
const config = await getAwsConnectionConfig(connection, AWSRegion.US_WEST_1); // REGION is irrelevant because Route53 is global
const route53Client = new Route53Client({
sha256: CustomAWSHasher,
useFipsEndpoint: crypto.isFipsModeEnabled(),
credentials: config.credentials!,
region: config.region
});
@@ -187,6 +190,12 @@ export const AcmeCertificateAuthorityFns = ({
enableDirectIssuance: boolean;
actor: OrgServiceActor;
}) => {
if (crypto.isFipsModeEnabled()) {
throw new CryptographyError({
message: "ACME is currently not supported in FIPS mode of operation."
});
}
const { dnsAppConnectionId, directoryUrl, accountEmail, dnsProviderConfig } = configuration;
const appConnection = await appConnectionDAL.findById(dnsAppConnectionId);
@@ -404,8 +413,9 @@ export const AcmeCertificateAuthorityFns = ({
});
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const skLeafObj = KeyObject.from(leafKeys.privateKey);
const leafKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const skLeafObj = crypto.nativeCrypto.KeyObject.from(leafKeys.privateKey);
const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string;
const [, certificateCsr] = await acme.crypto.createCsr(
@@ -1,6 +1,6 @@
import * as x509 from "@peculiar/x509";
import crypto from "crypto";
import { crypto } from "@app/lib/crypto/cryptography";
import { NotFoundError } from "@app/lib/errors";
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
@@ -133,8 +133,8 @@ export const getCaCredentials = async ({
});
const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm);
const skObj = crypto.createPrivateKey({ key: decryptedPrivateKey, format: "der", type: "pkcs8" });
const caPrivateKey = await crypto.subtle.importKey(
const skObj = crypto.nativeCrypto.createPrivateKey({ key: decryptedPrivateKey, format: "der", type: "pkcs8" });
const caPrivateKey = await crypto.nativeCrypto.subtle.importKey(
"pkcs8",
skObj.export({ format: "der", type: "pkcs8" }),
alg,
@@ -142,10 +142,14 @@ export const getCaCredentials = async ({
["sign"]
);
const pkObj = crypto.createPublicKey(skObj);
const caPublicKey = await crypto.subtle.importKey("spki", pkObj.export({ format: "der", type: "spki" }), alg, true, [
"verify"
]);
const pkObj = crypto.nativeCrypto.createPublicKey(skObj);
const caPublicKey = await crypto.nativeCrypto.subtle.importKey(
"spki",
pkObj.export({ format: "der", type: "spki" }),
alg,
true,
["verify"]
);
return {
caSecret,
@@ -277,10 +281,14 @@ export const rebuildCaCrl = async ({
cipherTextBlob: caSecret.encryptedPrivateKey
});
const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [
"sign"
]);
const skObj = crypto.nativeCrypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
const sk = await crypto.nativeCrypto.subtle.importKey(
"pkcs8",
skObj.export({ format: "der", type: "pkcs8" }),
alg,
true,
["sign"]
);
const revokedCerts = await certificateDAL.find({
caId: ca.id,
@@ -1,8 +1,8 @@
import * as x509 from "@peculiar/x509";
import crypto from "crypto";
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
@@ -198,10 +198,14 @@ export const certificateAuthorityQueueFactory = ({
cipherTextBlob: caSecret.encryptedPrivateKey
});
const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [
"sign"
]);
const skObj = crypto.nativeCrypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
const sk = await crypto.nativeCrypto.subtle.importKey(
"pkcs8",
skObj.export({ format: "der", type: "pkcs8" }),
alg,
true,
["sign"]
);
const revokedCerts = await certificateDAL.find({
caId: ca.id,
@@ -1,12 +1,12 @@
/* eslint-disable no-bitwise */
import * as x509 from "@peculiar/x509";
import { KeyObject } from "crypto";
import RE2 from "re2";
import { z } from "zod";
import { TCertificateTemplates, TPkiSubscribers } from "@app/db/schemas";
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { isFQDN } from "@app/lib/validator/validate-url";
@@ -99,7 +99,7 @@ export const InternalCertificateAuthorityFns = ({
}
const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm);
const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const leafKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const csrObj = await x509.Pkcs10CertificateRequestGenerator.create({
name: `CN=${subscriber.commonName}`,
@@ -184,7 +184,7 @@ export const InternalCertificateAuthorityFns = ({
extensions
});
const skLeafObj = KeyObject.from(leafKeys.privateKey);
const skLeafObj = crypto.nativeCrypto.KeyObject.from(leafKeys.privateKey);
const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string;
const kmsEncryptor = await kmsService.encryptWithKmsKey({
@@ -331,7 +331,7 @@ export const InternalCertificateAuthorityFns = ({
});
const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm);
const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const leafKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const csrObj = await x509.Pkcs10CertificateRequestGenerator.create({
name: `CN=${commonName}`,
@@ -450,7 +450,7 @@ export const InternalCertificateAuthorityFns = ({
extensions
});
const skLeafObj = KeyObject.from(leafKeys.privateKey);
const skLeafObj = crypto.nativeCrypto.KeyObject.from(leafKeys.privateKey);
const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string;
const kmsEncryptor = await kmsService.encryptWithKmsKey({
@@ -2,7 +2,6 @@
import { ForbiddenError, subject } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import slugify from "@sindresorhus/slugify";
import crypto, { KeyObject } from "crypto";
import { z } from "zod";
import { TableName, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
@@ -15,6 +14,7 @@ import {
} from "@app/ee/services/permission/project-permission";
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid";
@@ -171,7 +171,7 @@ export const internalCertificateAuthorityServiceFactory = ({
});
const alg = keyAlgorithmToAlgCfg(keyAlgorithm);
const keys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const keys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const newCa = await certificateAuthorityDAL.transaction(async (tx) => {
const notBeforeDate = notBefore ? new Date(notBefore) : new Date();
@@ -225,8 +225,8 @@ export const internalCertificateAuthorityServiceFactory = ({
kmsId: certificateManagerKmsId
});
// // https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey
const skObj = KeyObject.from(keys.privateKey);
// https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey
const skObj = crypto.nativeCrypto.KeyObject.from(keys.privateKey);
const { cipherTextBlob: encryptedPrivateKey } = await kmsEncryptor({
plainText: skObj.export({
@@ -1102,9 +1102,9 @@ export const internalCertificateAuthorityServiceFactory = ({
kmsService
});
const isCaAndCertPublicKeySame = Buffer.from(await crypto.subtle.exportKey("spki", caPublicKey)).equals(
Buffer.from(certObj.publicKey.rawData)
);
const isCaAndCertPublicKeySame = Buffer.from(
await crypto.nativeCrypto.subtle.exportKey("spki", caPublicKey)
).equals(Buffer.from(certObj.publicKey.rawData));
if (!isCaAndCertPublicKeySame) {
throw new BadRequestError({ message: "CA and certificate public key do not match" });
@@ -1265,7 +1265,7 @@ export const internalCertificateAuthorityServiceFactory = ({
}
const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm);
const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const leafKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const csrObj = await x509.Pkcs10CertificateRequestGenerator.create({
name: `CN=${commonName}`,
@@ -1412,7 +1412,7 @@ export const internalCertificateAuthorityServiceFactory = ({
extensions
});
const skLeafObj = KeyObject.from(leafKeys.privateKey);
const skLeafObj = crypto.nativeCrypto.KeyObject.from(leafKeys.privateKey);
const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string;
const kmsEncryptor = await kmsService.encryptWithKmsKey({
@@ -1,6 +1,5 @@
import { ForbiddenError, subject } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import bcrypt from "bcrypt";
import { TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -11,6 +10,7 @@ import {
} from "@app/ee/services/permission/project-permission";
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { isCertChainValid } from "../certificate/certificate-fns";
@@ -308,7 +308,7 @@ export const certificateTemplateServiceFactory = ({
encryptedCaChain = cipherTextBlob;
}
const hashedPassphrase = await bcrypt.hash(passphrase, appCfg.SALT_ROUNDS);
const hashedPassphrase = await crypto.hashing().createHash(passphrase, appCfg.SALT_ROUNDS);
const estConfig = await certificateTemplateEstConfigDAL.create({
certificateTemplateId,
hashedPassphrase,
@@ -404,7 +404,7 @@ export const certificateTemplateServiceFactory = ({
}
if (passphrase) {
const hashedPassphrase = await bcrypt.hash(passphrase, appCfg.SALT_ROUNDS);
const hashedPassphrase = await crypto.hashing().createHash(passphrase, appCfg.SALT_ROUNDS);
updatedData.hashedPassphrase = hashedPassphrase;
}
@@ -1,8 +1,7 @@
import crypto from "node:crypto";
import * as x509 from "@peculiar/x509";
import RE2 from "re2";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { getProjectKmsCertificateKeyId } from "../project/project-fns";
@@ -87,10 +86,10 @@ export const getCertificateCredentials = async ({
});
try {
const skObj = crypto.createPrivateKey({ key: decryptedPrivateKey, format: "pem", type: "pkcs8" });
const skObj = crypto.nativeCrypto.createPrivateKey({ key: decryptedPrivateKey, format: "pem", type: "pkcs8" });
const certPrivateKey = skObj.export({ format: "pem", type: "pkcs8" }).toString();
const pkObj = crypto.createPublicKey(skObj);
const pkObj = crypto.nativeCrypto.createPublicKey(skObj);
const certPublicKey = pkObj.export({ format: "pem", type: "spki" }).toString();
return {
@@ -1,6 +1,5 @@
import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import { createPrivateKey, createPublicKey, sign, verify } from "crypto";
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
@@ -8,6 +7,7 @@ import {
ProjectPermissionCertificateActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
@@ -373,16 +373,16 @@ export const certificateServiceFactory = ({
// Verify private key matches the certificate
let privateKey;
try {
privateKey = createPrivateKey(privateKeyPem);
privateKey = crypto.nativeCrypto.createPrivateKey(privateKeyPem);
} catch (err) {
throw new BadRequestError({ message: "Invalid private key format" });
}
try {
const message = Buffer.from(Buffer.alloc(32));
const publicKey = createPublicKey(certificatePem);
const signature = sign(null, message, privateKey);
const isValid = verify(null, message, publicKey, signature);
const publicKey = crypto.nativeCrypto.createPublicKey(certificatePem);
const signature = crypto.nativeCrypto.sign(null, message, privateKey);
const isValid = crypto.nativeCrypto.verify(null, message, publicKey, signature);
if (!isValid) {
throw new BadRequestError({ message: "Private key does not match certificate" });
@@ -1,10 +1,10 @@
import slugify from "@sindresorhus/slugify";
import { randomUUID } from "crypto";
import sjcl from "sjcl";
import tweetnacl from "tweetnacl";
import tweetnaclUtil from "tweetnacl-util";
import { SecretType, TSecretFolders } from "@app/db/schemas";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { chunkArray } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
@@ -228,7 +228,7 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
}
infisicalImportData.secrets.push({
id: randomUUID(),
id: crypto.nativeCrypto.randomUUID(),
name: secretName,
environmentId: matchingAppEnv.id,
value: resolvedSecret.val || "",
@@ -261,7 +261,7 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
}
infisicalImportData.secrets.push({
id: randomUUID(),
id: crypto.nativeCrypto.randomUUID(),
name: secretName,
environmentId: matchingAppEnv.id,
value: secretData.val || "",
@@ -348,7 +348,7 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
}
infisicalImportData.secrets.push({
id: randomUUID(),
id: crypto.nativeCrypto.randomUUID(),
name: secret,
environmentId: matchingEnv.id,
value: resolvedSecret.val || "",
@@ -380,7 +380,7 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
}
infisicalImportData.secrets.push({
id: randomUUID(),
id: crypto.nativeCrypto.randomUUID(),
name: secret,
environmentId: matchingEnv.id,
value: selectedSecret.val || "",
@@ -461,7 +461,7 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
}
infisicalImportData.secrets.push({
id: randomUUID(),
id: crypto.nativeCrypto.randomUUID(),
name: secretName,
environmentId: subEnv ? subEnv.parentEnvironmentId : env,
value: resolvedSecret.val || "",
@@ -487,7 +487,7 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
const folderId = targetIdToFolderIdsMap.get(subEnv?.id || "") || subEnv?.id;
infisicalImportData.secrets.push({
id: randomUUID(),
id: crypto.nativeCrypto.randomUUID(),
name: secretName,
environmentId: subEnv ? subEnv.parentEnvironmentId : env,
value: secretData.val || "",
@@ -1,5 +1,5 @@
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
@@ -98,7 +98,7 @@ export const externalMigrationQueueFactory = ({
template: SmtpTemplates.ExternalImportStarted
});
const decrypted = infisicalSymmetricDecrypt({
const decrypted = crypto.encryption().symmetric().decryptWithRootEncryptionKey({
ciphertext: data.ciphertext,
iv: data.iv,
keyEncoding: data.encoding,
@@ -1,7 +1,7 @@
import { OrgMembershipRole } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { ForbiddenRequestError } from "@app/lib/errors";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
import { TUserDALFactory } from "../user/user-dal";
import { decryptEnvKeyDataFn, parseEnvKeyDataFn } from "./external-migration-fns";
@@ -29,6 +29,10 @@ export const externalMigrationServiceFactory = ({
actorOrgId,
actorAuthMethod
}: TImportEnvKeyDataCreate) => {
if (crypto.isFipsModeEnabled()) {
throw new BadRequestError({ message: "EnvKey migration is not supported when running in FIPS mode." });
}
const { membership } = await permissionService.getOrgPermission(
actor,
actorId,
@@ -52,7 +56,7 @@ export const externalMigrationServiceFactory = ({
actorAuthMethod
});
const encrypted = infisicalSymmetricEncypt(stringifiedJson);
const encrypted = crypto.encryption().symmetric().encryptWithRootEncryptionKey(stringifiedJson);
await externalMigrationQueue.startImport({
actorEmail: user.email!,
@@ -8,8 +8,7 @@ import {
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionGroupActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
@@ -213,14 +212,17 @@ export const groupProjectServiceFactory = ({
});
}
const botPrivateKey = infisicalSymmetricDecrypt({
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
iv: bot.iv,
tag: bot.tag,
ciphertext: bot.encryptedPrivateKey
});
const botPrivateKey = crypto
.encryption()
.symmetric()
.decryptWithRootEncryptionKey({
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
iv: bot.iv,
tag: bot.tag,
ciphertext: bot.encryptedPrivateKey
});
const plaintextProjectKey = decryptAsymmetric({
const plaintextProjectKey = crypto.encryption().asymmetric().decrypt({
ciphertext: ghostUserLatestKey.encryptedKey,
nonce: ghostUserLatestKey.nonce,
publicKey: ghostUserLatestKey.sender.publicKey,
@@ -228,7 +230,10 @@ export const groupProjectServiceFactory = ({
});
const projectKeyData = groupMembers.map(({ user: { publicKey, id } }) => {
const { ciphertext: encryptedKey, nonce } = encryptAsymmetric(plaintextProjectKey, publicKey, botPrivateKey);
const { ciphertext: encryptedKey, nonce } = crypto
.encryption()
.asymmetric()
.encrypt(plaintextProjectKey, publicKey, botPrivateKey);
return {
encryptedKey,
@@ -30,10 +30,17 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
const removeExpiredTokens = async (tx?: Knex) => {
logger.info(`${QueueName.DailyResourceCleanUp}: remove expired access token started`);
const BATCH_SIZE = 10000;
const MAX_RETRY_ON_FAILURE = 3;
const QUERY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
const MAX_TTL = 315_360_000; // Maximum TTL value in seconds (10 years)
try {
const docs = (tx || db)(TableName.IdentityAccessToken)
let deletedTokenIds: { id: string }[] = [];
let numberOfRetryOnFailure = 0;
let isRetrying = false;
const getExpiredTokensQuery = (dbClient: Knex | Knex.Transaction) =>
dbClient(TableName.IdentityAccessToken)
.where({
isAccessTokenRevoked: true
})
@@ -47,34 +54,64 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
);
})
.orWhere((qb) => {
void qb.where("accessTokenTTL", ">", 0).andWhere((qb2) => {
void qb2
.where((qb3) => {
void qb3
.whereNotNull("accessTokenLastRenewedAt")
// accessTokenLastRenewedAt + convert_integer_to_seconds(accessTokenTTL) < present_date
.andWhereRaw(
`"${TableName.IdentityAccessToken}"."accessTokenLastRenewedAt" + make_interval(secs => LEAST("${TableName.IdentityAccessToken}"."accessTokenTTL", ?)) < NOW()`,
[MAX_TTL]
);
})
.orWhere((qb3) => {
void qb3
.whereNull("accessTokenLastRenewedAt")
// created + convert_integer_to_seconds(accessTokenTTL) < present_date
.andWhereRaw(
`"${TableName.IdentityAccessToken}"."createdAt" + make_interval(secs => LEAST("${TableName.IdentityAccessToken}"."accessTokenTTL", ?)) < NOW()`,
[MAX_TTL]
);
});
void qb.where("accessTokenTTL", ">", 0).andWhereRaw(
`
-- Check if the token's effective expiration time has passed.
-- The expiration time is calculated by adding its TTL to its last renewal/creation time.
COALESCE(
"${TableName.IdentityAccessToken}"."accessTokenLastRenewedAt", -- Use last renewal time if available
"${TableName.IdentityAccessToken}"."createdAt" -- Otherwise, use creation time
)
+ make_interval(
secs => LEAST(
"${TableName.IdentityAccessToken}"."accessTokenTTL", -- Token's specified TTL
? -- Capped by MAX_TTL (parameterized value)
)
)
< NOW() -- Check if the calculated time is before now
`,
[MAX_TTL]
);
});
do {
try {
const deleteBatch = async (dbClient: Knex | Knex.Transaction) => {
const idsToDeleteQuery = getExpiredTokensQuery(dbClient).select("id").limit(BATCH_SIZE);
return dbClient(TableName.IdentityAccessToken).whereIn("id", idsToDeleteQuery).del().returning("id");
};
if (tx) {
// eslint-disable-next-line no-await-in-loop
deletedTokenIds = await deleteBatch(tx);
} else {
// eslint-disable-next-line no-await-in-loop
deletedTokenIds = await db.transaction(async (trx) => {
await trx.raw(`SET statement_timeout = ${QUERY_TIMEOUT_MS}`);
return deleteBatch(trx);
});
})
.delete();
await docs;
logger.info(`${QueueName.DailyResourceCleanUp}: remove expired access token completed`);
} catch (error) {
throw new DatabaseError({ error, name: "IdentityAccessTokenPrune" });
}
numberOfRetryOnFailure = 0; // reset
} catch (error) {
numberOfRetryOnFailure += 1;
logger.error(error, "Failed to delete a batch of expired identity access tokens on pruning");
} finally {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 10); // time to breathe for db
});
}
isRetrying = numberOfRetryOnFailure > 0;
} while (deletedTokenIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
if (numberOfRetryOnFailure >= MAX_RETRY_ON_FAILURE) {
logger.error(
`IdentityAccessTokenPrune: Pruning failed and stopped after ${MAX_RETRY_ON_FAILURE} consecutive retries.`
);
}
logger.info(`${QueueName.DailyResourceCleanUp}: remove expired access token completed`);
};
return { ...identityAccessTokenOrm, findOne, removeExpiredTokens };
@@ -1,7 +1,6 @@
import jwt, { JwtPayload } from "jsonwebtoken";
import { IdentityAuthMethod, TableName, TIdentityAccessTokens } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { checkIPAgainstBlocklist, TIp } from "@app/lib/ip";
@@ -81,7 +80,7 @@ export const identityAccessTokenServiceFactory = ({
const renewAccessToken = async ({ accessToken }: TRenewAccessTokenDTO) => {
const appCfg = getConfig();
const decodedToken = jwt.verify(accessToken, appCfg.AUTH_SECRET) as TIdentityAccessTokenJwtPayload;
const decodedToken = crypto.jwt().verify(accessToken, appCfg.AUTH_SECRET) as TIdentityAccessTokenJwtPayload;
if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) {
throw new BadRequestError({ message: "Only identity access tokens can be renewed" });
}
@@ -145,7 +144,7 @@ export const identityAccessTokenServiceFactory = ({
expiresIn = undefined;
}
const renewedToken = jwt.sign(
const renewedToken = crypto.jwt().sign(
{
identityId: decodedToken.identityId,
clientSecretId: decodedToken.clientSecretId,
@@ -162,7 +161,7 @@ export const identityAccessTokenServiceFactory = ({
const revokeAccessToken = async (accessToken: string) => {
const appCfg = getConfig();
const decodedToken = jwt.verify(accessToken, appCfg.AUTH_SECRET) as JwtPayload & {
const decodedToken = crypto.jwt().verify(accessToken, appCfg.AUTH_SECRET) as TIdentityAccessTokenJwtPayload & {
identityAccessTokenId: string;
};
if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) {
@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ForbiddenError } from "@casl/ability";
import { AxiosError } from "axios";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -13,6 +12,7 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { logger } from "@app/lib/logger";
@@ -103,7 +103,7 @@ export const identityAliCloudAuthServiceFactory = ({
});
const appCfg = getConfig();
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
identityId: identityAliCloudAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ForbiddenError } from "@casl/ability";
import axios from "axios";
import jwt from "jsonwebtoken";
import RE2 from "re2";
import { IdentityAuthMethod } from "@app/db/schemas";
@@ -13,6 +12,7 @@ import {
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
@@ -168,7 +168,7 @@ export const identityAwsAuthServiceFactory = ({
});
const appCfg = getConfig();
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
identityId: identityAwsAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
@@ -1,6 +1,6 @@
import axios from "axios";
import jwt from "jsonwebtoken";
import { crypto } from "@app/lib/crypto";
import { UnauthorizedError } from "@app/lib/errors";
import { TAzureAuthJwtPayload, TAzureJwksUriResponse, TDecodedAzureAuthJwt } from "./identity-azure-auth-types";
@@ -16,7 +16,7 @@ export const validateAzureIdentity = async ({
}) => {
const jwksUri = `https://login.microsoftonline.com/${tenantId}/discovery/keys`;
const decodedJwt = jwt.decode(azureJwt, { complete: true }) as TDecodedAzureAuthJwt;
const decodedJwt = crypto.jwt().decode(azureJwt, { complete: true }) as TDecodedAzureAuthJwt;
const { kid } = decodedJwt.header;
@@ -35,7 +35,7 @@ export const validateAzureIdentity = async ({
resource = resource.slice(0, -1);
}
return jwt.verify(azureJwt, publicKey, {
return crypto.jwt().verify(azureJwt, publicKey, {
audience: resource,
issuer: `https://sts.windows.net/${tenantId}/`
}) as TAzureAuthJwtPayload;
@@ -1,5 +1,4 @@
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -10,6 +9,7 @@ import {
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
@@ -96,7 +96,7 @@ export const identityAzureAuthServiceFactory = ({
});
const appCfg = getConfig();
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
identityId: identityAzureAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
@@ -1,7 +1,7 @@
import axios from "axios";
import { OAuth2Client } from "google-auth-library";
import jwt from "jsonwebtoken";
import { crypto } from "@app/lib/crypto";
import { UnauthorizedError } from "@app/lib/errors";
import { TDecodedGcpIamAuthJwt, TGcpIdTokenPayload } from "./identity-gcp-auth-types";
@@ -48,7 +48,7 @@ export const validateIamIdentity = async ({
identityId: string;
jwt: string;
}) => {
const decodedJwt = jwt.decode(serviceAccountJwt, { complete: true }) as TDecodedGcpIamAuthJwt;
const decodedJwt = crypto.jwt().decode(serviceAccountJwt, { complete: true }) as TDecodedGcpIamAuthJwt;
const { sub, aud } = decodedJwt.payload;
const {
@@ -61,7 +61,7 @@ export const validateIamIdentity = async ({
const publicKey = data[decodedJwt.header.kid];
jwt.verify(serviceAccountJwt, publicKey, {
crypto.jwt().verify(serviceAccountJwt, publicKey, {
algorithms: ["RS256"]
});
@@ -1,5 +1,4 @@
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -10,6 +9,7 @@ import {
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
@@ -135,7 +135,7 @@ export const identityGcpAuthServiceFactory = ({
});
const appCfg = getConfig();
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
identityId: identityGcpAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
@@ -12,6 +12,7 @@ import {
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import {
BadRequestError,
ForbiddenRequestError,
@@ -79,7 +80,7 @@ export const identityJwtAuthServiceFactory = ({
orgId: identityMembershipOrg.orgId
});
const decodedToken = jwt.decode(jwtValue, { complete: true });
const decodedToken = crypto.jwt().decode(jwtValue, { complete: true });
if (!decodedToken) {
throw new UnauthorizedError({
message: "Invalid JWT"
@@ -106,11 +107,11 @@ export const identityJwtAuthServiceFactory = ({
});
}
const { kid } = decodedToken.header;
const { kid } = decodedToken.header as { kid: string };
const jwtSigningKey = await client.getSigningKey(kid);
try {
tokenData = jwt.verify(jwtValue, jwtSigningKey.getPublicKey()) as Record<string, string>;
tokenData = crypto.jwt().verify(jwtValue, jwtSigningKey.getPublicKey()) as Record<string, string>;
} catch (error) {
if (error instanceof jwt.JsonWebTokenError) {
throw new UnauthorizedError({
@@ -129,7 +130,7 @@ export const identityJwtAuthServiceFactory = ({
let isMatchAnyKey = false;
for (const publicKey of decryptedPublicKeys) {
try {
tokenData = jwt.verify(jwtValue, publicKey) as Record<string, string>;
tokenData = crypto.jwt().verify(jwtValue, publicKey) as Record<string, string>;
isMatchAnyKey = true;
} catch (error) {
if (error instanceof jwt.JsonWebTokenError) {
@@ -225,7 +226,7 @@ export const identityJwtAuthServiceFactory = ({
});
const appCfg = getConfig();
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
identityId: identityJwtAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
@@ -1,7 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import axios, { AxiosError } from "axios";
import https from "https";
import jwt from "jsonwebtoken";
import RE2 from "re2";
import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas";
@@ -19,6 +18,7 @@ import {
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { GatewayHttpProxyActions, GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
@@ -396,7 +396,7 @@ export const identityKubernetesAuthServiceFactory = ({
});
const appCfg = getConfig();
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
identityId: identityKubernetesAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { testLDAPConfig } from "@app/ee/services/ldap-config/ldap-fns";
@@ -12,6 +11,7 @@ import {
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
@@ -153,7 +153,7 @@ export const identityLdapAuthServiceFactory = ({
});
const appCfg = getConfig();
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
identityId: identityLdapAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ForbiddenError } from "@casl/ability";
import { AxiosError } from "axios";
import jwt from "jsonwebtoken";
import RE2 from "re2";
import { IdentityAuthMethod } from "@app/db/schemas";
@@ -14,6 +13,7 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { logger } from "@app/lib/logger";
@@ -107,7 +107,7 @@ export const identityOciAuthServiceFactory = ({
});
const appCfg = getConfig();
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
identityId: identityOciAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
@@ -13,6 +13,7 @@ import {
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import {
BadRequestError,
ForbiddenRequestError,
@@ -93,7 +94,7 @@ export const identityOidcAuthServiceFactory = ({
);
const jwksUri = discoveryDoc.jwks_uri;
const decodedToken = jwt.decode(oidcJwt, { complete: true });
const decodedToken = crypto.jwt().decode(oidcJwt, { complete: true });
if (!decodedToken) {
throw new UnauthorizedError({
message: "Invalid JWT"
@@ -105,12 +106,12 @@ export const identityOidcAuthServiceFactory = ({
requestAgent: identityOidcAuth.oidcDiscoveryUrl.includes("https") ? requestAgent : undefined
});
const { kid } = decodedToken.header;
const { kid } = decodedToken.header as { kid: string };
const oidcSigningKey = await client.getSigningKey(kid);
let tokenData: Record<string, string>;
try {
tokenData = jwt.verify(oidcJwt, oidcSigningKey.getPublicKey(), {
tokenData = crypto.jwt().verify(oidcJwt, oidcSigningKey.getPublicKey(), {
issuer: identityOidcAuth.boundIssuer
}) as Record<string, string>;
} catch (error) {
@@ -193,7 +194,7 @@ export const identityOidcAuthServiceFactory = ({
});
const appCfg = getConfig();
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
identityId: identityOidcAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
@@ -1,7 +1,4 @@
import crypto from "node:crypto";
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -13,6 +10,7 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
@@ -87,8 +85,8 @@ export const identityTlsCertAuthServiceFactory = ({
throw new BadRequestError({ message: "Missing client certificate" });
}
const clientCertificateX509 = new crypto.X509Certificate(leafCertificate);
const caCertificateX509 = new crypto.X509Certificate(caCertificate);
const clientCertificateX509 = new crypto.nativeCrypto.X509Certificate(leafCertificate);
const caCertificateX509 = new crypto.nativeCrypto.X509Certificate(caCertificate);
const isValidCertificate = clientCertificateX509.verify(caCertificateX509.publicKey);
if (!isValidCertificate)
@@ -136,7 +134,7 @@ export const identityTlsCertAuthServiceFactory = ({
});
const appCfg = getConfig();
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
identityId: identityTlsCertAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
@@ -1,5 +1,4 @@
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -10,6 +9,7 @@ import {
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
@@ -362,7 +362,7 @@ export const identityTokenAuthServiceFactory = ({
});
const appCfg = getConfig();
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
identityId: identityTokenAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
@@ -1,8 +1,4 @@
import crypto from "node:crypto";
import { ForbiddenError } from "@casl/ability";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -13,6 +9,7 @@ import {
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { checkIPAgainstBlocklist, extractIPDetails, isValidIpOrCidr, TIp } from "@app/lib/ip";
@@ -76,7 +73,8 @@ export const identityUaServiceFactory = ({
let validClientSecretInfo: (typeof clientSecrtInfo)[0] | null = null;
for await (const info of clientSecrtInfo) {
const isMatch = await bcrypt.compare(clientSecret, info.clientSecretHash);
const isMatch = await crypto.hashing().compareHash(clientSecret, info.clientSecretHash);
if (isMatch) {
validClientSecretInfo = info;
break;
@@ -148,7 +146,7 @@ export const identityUaServiceFactory = ({
});
const appCfg = getConfig();
const accessToken = jwt.sign(
const accessToken = crypto.jwt().sign(
{
identityId: identityUa.identityId,
clientSecretId: validClientSecretInfo.id,
@@ -250,7 +248,7 @@ export const identityUaServiceFactory = ({
const doc = await identityUaDAL.create(
{
identityId: identityMembershipOrg.identityId,
clientId: crypto.randomUUID(),
clientId: crypto.nativeCrypto.randomUUID(),
clientSecretTrustedIps: JSON.stringify(reformattedClientSecretTrustedIps),
accessTokenMaxTTL,
accessTokenTTL,
@@ -494,7 +492,7 @@ export const identityUaServiceFactory = ({
const appCfg = getConfig();
const clientSecret = crypto.randomBytes(32).toString("hex");
const clientSecretHash = await bcrypt.hash(clientSecret, appCfg.SALT_ROUNDS);
const clientSecretHash = await crypto.hashing().createHash(clientSecret, appCfg.SALT_ROUNDS);
const identityUaAuth = await identityUaDAL.findOne({ identityId: identityMembershipOrg.identityId });
if (!identityUaAuth) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
@@ -9,7 +9,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { decryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
@@ -218,13 +218,22 @@ export const integrationAuthServiceFactory = ({
} else {
if (!botKey) throw new NotFoundError({ message: `Project bot key for project with ID '${projectId}' not found` });
if (tokenExchange.refreshToken) {
const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenExchange.refreshToken, botKey);
const refreshEncToken = crypto.encryption().symmetric().encrypt({
plaintext: tokenExchange.refreshToken,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
updateDoc.refreshIV = refreshEncToken.iv;
updateDoc.refreshTag = refreshEncToken.tag;
updateDoc.refreshCiphertext = refreshEncToken.ciphertext;
}
if (tokenExchange.accessToken) {
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenExchange.accessToken, botKey);
const accessEncToken = crypto.encryption().symmetric().encrypt({
plaintext: tokenExchange.accessToken,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
updateDoc.accessIV = accessEncToken.iv;
updateDoc.accessTag = accessEncToken.tag;
updateDoc.accessCiphertext = accessEncToken.ciphertext;
@@ -346,11 +355,19 @@ export const integrationAuthServiceFactory = ({
url,
updateDoc.metadata as Record<string, string>
);
const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.refreshToken, botKey);
const refreshEncToken = crypto.encryption().symmetric().encrypt({
plaintext: tokenDetails.refreshToken,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
updateDoc.refreshIV = refreshEncToken.iv;
updateDoc.refreshTag = refreshEncToken.tag;
updateDoc.refreshCiphertext = refreshEncToken.ciphertext;
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.accessToken, botKey);
const accessEncToken = crypto.encryption().symmetric().encrypt({
plaintext: tokenDetails.accessToken,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
updateDoc.accessIV = accessEncToken.iv;
updateDoc.accessTag = accessEncToken.tag;
updateDoc.accessCiphertext = accessEncToken.ciphertext;
@@ -360,19 +377,31 @@ export const integrationAuthServiceFactory = ({
if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) {
if (accessToken) {
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessToken, botKey);
const accessEncToken = crypto.encryption().symmetric().encrypt({
plaintext: accessToken,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
updateDoc.accessIV = accessEncToken.iv;
updateDoc.accessTag = accessEncToken.tag;
updateDoc.accessCiphertext = accessEncToken.ciphertext;
}
if (accessId) {
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessId, botKey);
const accessEncToken = crypto.encryption().symmetric().encrypt({
plaintext: accessId,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
updateDoc.accessIdIV = accessEncToken.iv;
updateDoc.accessIdTag = accessEncToken.tag;
updateDoc.accessIdCiphertext = accessEncToken.ciphertext;
}
if (awsAssumeIamRoleArn) {
const awsAssumeIamRoleArnEnc = encryptSymmetric128BitHexKeyUTF8(awsAssumeIamRoleArn, botKey);
const awsAssumeIamRoleArnEnc = crypto.encryption().symmetric().encrypt({
plaintext: awsAssumeIamRoleArn,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
updateDoc.awsAssumeIamRoleArnCipherText = awsAssumeIamRoleArnEnc.ciphertext;
updateDoc.awsAssumeIamRoleArnIV = awsAssumeIamRoleArnEnc.iv;
updateDoc.awsAssumeIamRoleArnTag = awsAssumeIamRoleArnEnc.tag;
@@ -487,11 +516,21 @@ export const integrationAuthServiceFactory = ({
url,
updateDoc.metadata as Record<string, string>
);
const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.refreshToken, botKey);
const refreshEncToken = crypto.encryption().symmetric().encrypt({
plaintext: tokenDetails.refreshToken,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
updateDoc.refreshIV = refreshEncToken.iv;
updateDoc.refreshTag = refreshEncToken.tag;
updateDoc.refreshCiphertext = refreshEncToken.ciphertext;
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.accessToken, botKey);
const accessEncToken = crypto.encryption().symmetric().encrypt({
plaintext: tokenDetails.accessToken,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
updateDoc.accessIV = accessEncToken.iv;
updateDoc.accessTag = accessEncToken.tag;
updateDoc.accessCiphertext = accessEncToken.ciphertext;
@@ -501,19 +540,32 @@ export const integrationAuthServiceFactory = ({
if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) {
if (accessToken) {
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessToken, botKey);
const accessEncToken = crypto.encryption().symmetric().encrypt({
plaintext: accessToken,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
updateDoc.accessIV = accessEncToken.iv;
updateDoc.accessTag = accessEncToken.tag;
updateDoc.accessCiphertext = accessEncToken.ciphertext;
}
if (accessId) {
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessId, botKey);
const accessEncToken = crypto.encryption().symmetric().encrypt({
plaintext: accessId,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
updateDoc.accessIdIV = accessEncToken.iv;
updateDoc.accessIdTag = accessEncToken.tag;
updateDoc.accessIdCiphertext = accessEncToken.ciphertext;
}
if (awsAssumeIamRoleArn) {
const awsAssumeIamRoleArnEnc = encryptSymmetric128BitHexKeyUTF8(awsAssumeIamRoleArn, botKey);
const awsAssumeIamRoleArnEnc = crypto.encryption().symmetric().encrypt({
plaintext: awsAssumeIamRoleArn,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
updateDoc.awsAssumeIamRoleArnCipherText = awsAssumeIamRoleArnEnc.ciphertext;
updateDoc.awsAssumeIamRoleArnIV = awsAssumeIamRoleArnEnc.iv;
updateDoc.awsAssumeIamRoleArnTag = awsAssumeIamRoleArnEnc.tag;
@@ -596,20 +648,22 @@ export const integrationAuthServiceFactory = ({
} else {
if (!botKey) throw new NotFoundError({ message: "Project bot key not found" });
if (integrationAuth.accessTag && integrationAuth.accessIV && integrationAuth.accessCiphertext) {
accessToken = decryptSymmetric128BitHexKeyUTF8({
accessToken = crypto.encryption().symmetric().decrypt({
ciphertext: integrationAuth.accessCiphertext,
iv: integrationAuth.accessIV,
tag: integrationAuth.accessTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
});
}
if (integrationAuth.refreshCiphertext && integrationAuth.refreshIV && integrationAuth.refreshTag) {
const refreshToken = decryptSymmetric128BitHexKeyUTF8({
const refreshToken = crypto.encryption().symmetric().decrypt({
key: botKey,
ciphertext: integrationAuth.refreshCiphertext,
iv: integrationAuth.refreshIV,
tag: integrationAuth.refreshTag
tag: integrationAuth.refreshTag,
keySize: SymmetricKeySize.Bits128
});
if (integrationAuth.accessExpiresAt && integrationAuth.accessExpiresAt < new Date()) {
@@ -620,8 +674,18 @@ export const integrationAuthServiceFactory = ({
integrationAuth?.url,
integrationAuth.metadata as Record<string, string>
);
const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.refreshToken, botKey);
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.accessToken, botKey);
const refreshEncToken = crypto.encryption().symmetric().encrypt({
plaintext: tokenDetails.refreshToken,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const accessEncToken = crypto.encryption().symmetric().encrypt({
plaintext: tokenDetails.accessToken,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
accessToken = tokenDetails.accessToken;
await integrationAuthDAL.updateById(integrationAuth.id, {
refreshIV: refreshEncToken.iv,
@@ -637,11 +701,12 @@ export const integrationAuthServiceFactory = ({
if (!accessToken) throw new BadRequestError({ message: "Missing access token" });
if (integrationAuth.accessIdTag && integrationAuth.accessIdIV && integrationAuth.accessIdCiphertext) {
accessId = decryptSymmetric128BitHexKeyUTF8({
accessId = crypto.encryption().symmetric().decrypt({
key: botKey,
ciphertext: integrationAuth.accessIdCiphertext,
iv: integrationAuth.accessIdIV,
tag: integrationAuth.accessIdTag
tag: integrationAuth.accessIdTag,
keySize: SymmetricKeySize.Bits128
});
}
}
@@ -5,7 +5,7 @@ import { Octokit } from "@octokit/rest";
import { TIntegrationAuths, TIntegrations } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
@@ -109,12 +109,14 @@ const getIntegrationSecretsV1 = async (
// process secrets in current folder
const secrets = await secretDAL.findByFolderId(dto.folderId);
secrets.forEach((secret) => {
const secretKey = decryptSymmetric128BitHexKeyUTF8({
const secretKey = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key: dto.key
key: dto.key,
keySize: SymmetricKeySize.Bits128
});
content[secretKey] = true;
@@ -23,7 +23,6 @@ import { createAppAuth } from "@octokit/auth-app";
import { Octokit } from "@octokit/rest";
import AWS, { AWSError } from "aws-sdk";
import { AxiosError } from "axios";
import { randomUUID } from "crypto";
import https from "https";
import sodium from "libsodium-wrappers";
import isEqual from "lodash.isequal";
@@ -31,8 +30,10 @@ import RE2 from "re2";
import { z } from "zod";
import { SecretType, TIntegrationAuths, TIntegrations } from "@app/db/schemas";
import { CustomAWSHasher } from "@app/lib/aws/hashing";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/secret/secret-types";
@@ -796,6 +797,8 @@ const syncSecretsAWSParameterStore = async ({
if (awsAssumeRoleArn) {
const client = new STSClient({
region: integration.region as string,
useFipsEndpoint: crypto.isFipsModeEnabled(),
sha256: CustomAWSHasher,
credentials:
appCfg.CLIENT_ID_AWS_INTEGRATION && appCfg.CLIENT_SECRET_AWS_INTEGRATION
? {
@@ -806,7 +809,7 @@ const syncSecretsAWSParameterStore = async ({
});
const command = new AssumeRoleCommand({
RoleArn: awsAssumeRoleArn,
RoleSessionName: `infisical-parameter-store-${randomUUID()}`,
RoleSessionName: `infisical-parameter-store-${crypto.nativeCrypto.randomUUID()}`,
DurationSeconds: 900, // 15mins
ExternalId: projectId
});
@@ -1126,7 +1129,7 @@ const syncSecretsAWSSecretManager = async ({
});
const command = new AssumeRoleCommand({
RoleArn: awsAssumeRoleArn,
RoleSessionName: `infisical-sm-${randomUUID()}`,
RoleSessionName: `infisical-sm-${crypto.nativeCrypto.randomUUID()}`,
DurationSeconds: 900, // 15mins
ExternalId: projectId
});
@@ -1,7 +1,6 @@
import jwt from "jsonwebtoken";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, ForbiddenRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { Integrations, IntegrationUrls } from "./integration-list";
@@ -718,7 +717,7 @@ const exchangeRefreshGCPSecretManager = async ({
exp: Math.floor(Date.now() / 1000) + 3600
};
const token = jwt.sign(payload, serviceAccount.private_key, { algorithm: "RS256" });
const token = crypto.jwt().sign(payload, serviceAccount.private_key, { algorithm: "RS256" });
const { data }: { data: ServiceAccountAccessTokenGCPSecretManagerResponse } = await request.post(
IntegrationUrls.GCP_TOKEN_URL,
+13 -8
View File
@@ -14,9 +14,8 @@ import {
import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
import { TEnvConfig } from "@app/lib/config/env";
import { randomSecureBytes } from "@app/lib/crypto";
import { symmetricCipherService, SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
import { generateHash } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { AsymmetricKeyAlgorithm, signingService } from "@app/lib/crypto/sign";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
@@ -101,7 +100,7 @@ export const kmsServiceFactory = ({
let kmsKeyMaterial: Buffer | null = null;
if (keyUsage === KmsKeyUsage.ENCRYPT_DECRYPT) {
kmsKeyMaterial = randomSecureBytes(
kmsKeyMaterial = crypto.randomBytes(
getByteLengthForSymmetricEncryptionAlgorithm(encryptionAlgorithm as SymmetricKeyAlgorithm)
);
} else if (keyUsage === KmsKeyUsage.SIGN_VERIFY) {
@@ -618,7 +617,7 @@ export const kmsServiceFactory = ({
return;
}
const dataKey = randomSecureBytes();
const dataKey = crypto.randomBytes(32);
const kmsEncryptor = await encryptWithKmsKey(
{
kmsId: kmsKeyId
@@ -761,7 +760,7 @@ export const kmsServiceFactory = ({
return;
}
const dataKey = randomSecureBytes();
const dataKey = crypto.randomBytes(32);
const kmsEncryptor = await encryptWithKmsKey(
{
kmsId: kmsKeyId
@@ -831,6 +830,7 @@ export const kmsServiceFactory = ({
const $getBasicEncryptionKey = () => {
const encryptionKey = envConfig.ENCRYPTION_KEY || envConfig.ROOT_ENCRYPTION_KEY;
const isBase64 = !envConfig.ENCRYPTION_KEY;
if (!encryptionKey)
throw new Error(
@@ -994,7 +994,7 @@ export const kmsServiceFactory = ({
"base64"
)}`;
const verificationHash = generateHash(secretManagerBackup);
const verificationHash = crypto.nativeCrypto.createHash("sha256").update(secretManagerBackup).digest("hex");
secretManagerBackup = `${secretManagerBackup}.${verificationHash}`;
return {
@@ -1011,7 +1011,12 @@ export const kmsServiceFactory = ({
}
const [, backupProjectId, , backupKmsKeyId, backupBase64EncryptedDataKey, backupHash] = backup.split(".");
const computedHash = generateHash(backup.substring(0, backup.lastIndexOf(".")));
const computedHash = crypto.nativeCrypto
.createHash("sha256")
.update(backup.substring(0, backup.lastIndexOf(".")))
.digest("hex");
if (computedHash !== backupHash) {
throw new BadRequestError({
message: "Invalid backup"
@@ -1075,7 +1080,7 @@ export const kmsServiceFactory = ({
if (existingRootConfig) return existingRootConfig;
logger.info("KMS: Generating new ROOT Key");
const newRootKey = randomSecureBytes(32);
const newRootKey = crypto.randomBytes(32);
const encryptedRootKey = await $encryptRootKey(newRootKey, RootKeyEncryptionStrategy.Software).catch((err) => {
logger.error({ hsmEnabled: hsmService.isActive() }, "KMS: Failed to encrypt ROOT Key");
throw err;
@@ -1,11 +1,11 @@
/* eslint-disable class-methods-use-this */
import axios from "axios";
import { TeamsActivityHandler, TurnContext } from "botbuilder";
import jwt from "jsonwebtoken";
import { Knex } from "knex";
import { z } from "zod";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TNotification, TriggerFeature } from "@app/lib/workflow-integrations/types";
@@ -71,7 +71,7 @@ export const verifyTenantFromCode = async (
);
// Verify application token
const { tid: tenantIdFromApplicationAccessToken } = jwt.decode(applicationAccessToken) as { tid: string };
const { tid: tenantIdFromApplicationAccessToken } = crypto.jwt().decode(applicationAccessToken) as { tid: string };
if (tenantIdFromApplicationAccessToken !== tenantId) {
throw new BadRequestError({
@@ -80,7 +80,9 @@ export const verifyTenantFromCode = async (
}
// Verify user authorization token
const { tid: tenantIdFromAuthorizationAccessToken } = jwt.decode(authorizationAccessToken) as { tid: string };
const { tid: tenantIdFromAuthorizationAccessToken } = crypto.jwt().decode(authorizationAccessToken) as {
tid: string;
};
if (tenantIdFromAuthorizationAccessToken !== tenantId) {
throw new BadRequestError({
@@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability";
import { ProjectMembershipRole, ProjectVersion, SecretKeyEncoding } from "@app/db/schemas";
import { OrgPermissionAdminConsoleAction, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TProjectDALFactory } from "../project/project-dal";
@@ -144,12 +144,15 @@ export const orgAdminServiceFactory = ({
});
}
const botPrivateKey = infisicalSymmetricDecrypt({
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
iv: bot.iv,
tag: bot.tag,
ciphertext: bot.encryptedPrivateKey
});
const botPrivateKey = crypto
.encryption()
.symmetric()
.decryptWithRootEncryptionKey({
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
iv: bot.iv,
tag: bot.tag,
ciphertext: bot.encryptedPrivateKey
});
const userEncryptionKey = await userDAL.findUserEncKeyByUserId(actorId);
if (!userEncryptionKey)
+35 -20
View File
@@ -1,7 +1,5 @@
import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import crypto from "crypto";
import jwt from "jsonwebtoken";
import { Knex } from "knex";
import {
@@ -33,8 +31,7 @@ import { ProjectPermissionMemberActions, ProjectPermissionSub } from "@app/ee/se
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
import { TSamlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
import { getConfig } from "@app/lib/config/env";
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
import { generateSymmetricKey, infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { generateUserSrpKeys } from "@app/lib/crypto/srp";
import { applyJitter } from "@app/lib/dates";
import { delay as delayMs } from "@app/lib/delay";
@@ -261,6 +258,7 @@ export const orgServiceFactory = ({
const addGhostUser = async (orgId: string, tx?: Knex) => {
const email = `sudo-${alphaNumericNanoId(16)}-${orgId}@infisical.com`; // We add a nanoid because the email is unique. And we have to create a new ghost user each time, so we can have access to the private key.
const password = crypto.randomBytes(128).toString("hex");
const user = await userDAL.create(
@@ -502,22 +500,22 @@ export const orgServiceFactory = ({
orgName: string;
userEmail?: string | null;
}) => {
const { privateKey, publicKey } = generateAsymmetricKeyPair();
const key = generateSymmetricKey();
const { privateKey, publicKey } = await crypto.encryption().asymmetric().generateKeyPair();
const key = crypto.randomBytes(32).toString("base64");
const {
ciphertext: encryptedPrivateKey,
iv: privateKeyIV,
tag: privateKeyTag,
encoding: privateKeyKeyEncoding,
algorithm: privateKeyAlgorithm
} = infisicalSymmetricEncypt(privateKey);
} = crypto.encryption().symmetric().encryptWithRootEncryptionKey(privateKey);
const {
ciphertext: encryptedSymmetricKey,
iv: symmetricKeyIV,
tag: symmetricKeyTag,
encoding: symmetricKeyKeyEncoding,
algorithm: symmetricKeyAlgorithm
} = infisicalSymmetricEncypt(key);
} = crypto.encryption().symmetric().encryptWithRootEncryptionKey(key);
const customerId = await licenseService.generateOrgCustomerId(orgName, userEmail);
const organization = await orgDAL.transaction(async (tx) => {
@@ -600,7 +598,7 @@ export const orgServiceFactory = ({
const cfg = getConfig();
const authToken = authorizationHeader.replace("Bearer ", "");
const decodedToken = jwt.verify(authToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload;
const decodedToken = crypto.jwt().verify(authToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload;
if (!decodedToken.authMethod) throw new UnauthorizedError({ name: "Auth method not found on existing token" });
const response = await orgDAL.transaction(async (tx) => {
@@ -839,6 +837,7 @@ export const orgServiceFactory = ({
const mailsForOrgInvitation: { email: string; userId: string; firstName: string; lastName: string }[] = [];
const mailsForProjectInvitation: { email: string[]; projectName: string }[] = [];
const newProjectMemberships: TProjectMemberships[] = [];
await orgDAL.transaction(async (tx) => {
const users: Pick<TUsers, "id" | "firstName" | "lastName" | "email" | "username">[] = [];
@@ -878,7 +877,10 @@ export const orgServiceFactory = ({
// Then when user sign in (as login is not possible as isAccepted is false) we rencrypt the private key with the user password
if (!inviteeUser || (inviteeUser && !inviteeUser?.isAccepted && !existingEncrytionKey)) {
const serverGeneratedPassword = crypto.randomBytes(32).toString("hex");
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(serverGeneratedPassword);
const { tag, encoding, ciphertext, iv } = crypto
.encryption()
.symmetric()
.encryptWithRootEncryptionKey(serverGeneratedPassword);
const encKeys = await generateUserSrpKeys(inviteeEmail, serverGeneratedPassword);
await userDAL.createUserEncryption(
{
@@ -1096,9 +1098,10 @@ export const orgServiceFactory = ({
tx
);
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(
newGhostUser.keys.plainPrivateKey
);
const { iv, tag, ciphertext, encoding, algorithm } = crypto
.encryption()
.symmetric()
.encryptWithRootEncryptionKey(newGhostUser.keys.plainPrivateKey);
if (autoGeneratedBot) {
await projectBotDAL.updateById(
autoGeneratedBot.id,
@@ -1136,12 +1139,15 @@ export const orgServiceFactory = ({
});
}
const botPrivateKey = infisicalSymmetricDecrypt({
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
iv: bot.iv,
tag: bot.tag,
ciphertext: bot.encryptedPrivateKey
});
const botPrivateKey = crypto
.encryption()
.symmetric()
.decryptWithRootEncryptionKey({
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
iv: bot.iv,
tag: bot.tag,
ciphertext: bot.encryptedPrivateKey
});
const newWsMembers = assignWorkspaceKeysToMembers({
decryptKey: ghostUserLatestKey,
@@ -1274,6 +1280,8 @@ export const orgServiceFactory = ({
message: "No pending invitation found"
});
const organization = await orgDAL.findById(orgId);
await tokenService.validateTokenForUser({
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
userId: user.id,
@@ -1296,8 +1304,15 @@ export const orgServiceFactory = ({
return { user };
}
if (
organization.authEnforced &&
!(organization.bypassOrgAuthEnabled && orgMembership.role === OrgMembershipRole.Admin)
) {
return { user };
}
const appCfg = getConfig();
const token = jwt.sign(
const token = crypto.jwt().sign(
{
authTokenType: AuthTokenType.SIGNUP_TOKEN,
userId: user.id
@@ -1,24 +1,22 @@
import { SecretKeyEncoding } from "@app/db/schemas";
import {
decryptAsymmetric,
encryptAsymmetric,
generateAsymmetricKeyPair,
infisicalSymmetricDecrypt,
infisicalSymmetricEncypt
} from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { NotFoundError } from "@app/lib/errors";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { TGetPrivateKeyDTO } from "./project-bot-types";
export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) =>
infisicalSymmetricDecrypt({
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
iv: bot.iv,
tag: bot.tag,
ciphertext: bot.encryptedPrivateKey
});
export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) => {
return crypto
.encryption()
.symmetric()
.decryptWithRootEncryptionKey({
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
iv: bot.iv,
tag: bot.tag,
ciphertext: bot.encryptedPrivateKey
});
};
export const getBotKeyFnFactory = (
projectBotDAL: TProjectBotDALFactory,
@@ -51,22 +49,31 @@ export const getBotKeyFnFactory = (
projectV1Keys.serverEncryptedPrivateKeyTag &&
projectV1Keys.serverEncryptedPrivateKeyEncoding
) {
userPrivateKey = infisicalSymmetricDecrypt({
iv: projectV1Keys.serverEncryptedPrivateKeyIV,
tag: projectV1Keys.serverEncryptedPrivateKeyTag,
ciphertext: projectV1Keys.serverEncryptedPrivateKey,
keyEncoding: projectV1Keys.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
});
userPrivateKey = crypto
.encryption()
.symmetric()
.decryptWithRootEncryptionKey({
iv: projectV1Keys.serverEncryptedPrivateKeyIV,
tag: projectV1Keys.serverEncryptedPrivateKeyTag,
ciphertext: projectV1Keys.serverEncryptedPrivateKey,
keyEncoding: projectV1Keys.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
});
}
const workspaceKey = decryptAsymmetric({
const workspaceKey = crypto.encryption().asymmetric().decrypt({
ciphertext: projectV1Keys.projectEncryptedKey,
nonce: projectV1Keys.projectKeyNonce,
publicKey: projectV1Keys.senderPublicKey,
privateKey: userPrivateKey
});
const botKey = generateAsymmetricKeyPair();
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(botKey.privateKey);
const encryptedWorkspaceKey = encryptAsymmetric(workspaceKey, botKey.publicKey, userPrivateKey);
const botKey = await crypto.encryption().asymmetric().generateKeyPair();
const { iv, tag, ciphertext, encoding, algorithm } = crypto
.encryption()
.symmetric()
.encryptWithRootEncryptionKey(botKey.privateKey);
const encryptedWorkspaceKey = crypto
.encryption()
.asymmetric()
.encrypt(workspaceKey, botKey.publicKey, userPrivateKey);
let botId;
if (!bot) {
@@ -105,7 +112,7 @@ export const getBotKeyFnFactory = (
}
const botPrivateKey = getBotPrivateKey({ bot });
const botKey = decryptAsymmetric({
const botKey = crypto.encryption().asymmetric().decrypt({
ciphertext: bot.encryptedProjectKey,
privateKey: botPrivateKey,
nonce: bot.encryptedProjectKeyNonce,
@@ -3,8 +3,7 @@ import { ForbiddenError } from "@casl/ability";
import { ProjectVersion } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TProjectDALFactory } from "../project/project-dal";
@@ -54,9 +53,13 @@ export const projectBotServiceFactory = ({
const doc = await projectBotDAL.findOne({ projectId }, tx);
if (doc) return doc;
const keys = privateKey && publicKey ? { privateKey, publicKey } : generateAsymmetricKeyPair();
const keys =
privateKey && publicKey ? { privateKey, publicKey } : await crypto.encryption().asymmetric().generateKeyPair();
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(keys.privateKey);
const { iv, tag, ciphertext, encoding, algorithm } = crypto
.encryption()
.symmetric()
.encryptWithRootEncryptionKey(keys.privateKey);
const project = await projectDAL.findById(projectId, tx);
+10 -14
View File
@@ -1,10 +1,8 @@
import crypto from "crypto";
import { ProjectVersion, TProjects } from "@app/db/schemas";
import { createSshCaHelper } from "@app/ee/services/ssh/ssh-certificate-authority-fns";
import { SshCaKeySource } from "@app/ee/services/ssh/ssh-certificate-authority-types";
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto";
import { crypto } from "@app/lib/crypto/cryptography";
import { NotFoundError } from "@app/lib/errors";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -12,7 +10,7 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
import { AddUserToWsDTO, TBootstrapSshProjectDTO } from "./project-types";
export const assignWorkspaceKeysToMembers = ({ members, decryptKey, userPrivateKey }: AddUserToWsDTO) => {
const plaintextProjectKey = decryptAsymmetric({
const plaintextProjectKey = crypto.encryption().asymmetric().decrypt({
ciphertext: decryptKey.encryptedKey,
nonce: decryptKey.nonce,
publicKey: decryptKey.sender.publicKey,
@@ -20,11 +18,10 @@ export const assignWorkspaceKeysToMembers = ({ members, decryptKey, userPrivateK
});
const newWsMembers = members.map(({ orgMembershipId, userPublicKey }) => {
const { ciphertext: inviteeCipherText, nonce: inviteeNonce } = encryptAsymmetric(
plaintextProjectKey,
userPublicKey,
userPrivateKey
);
const { ciphertext: inviteeCipherText, nonce: inviteeNonce } = crypto
.encryption()
.asymmetric()
.encrypt(plaintextProjectKey, userPublicKey, userPrivateKey);
return {
orgMembershipId,
@@ -47,11 +44,10 @@ export const createProjectKey = ({ publicKey, privateKey, plainProjectKey }: TCr
const randomBytes = plainProjectKey || crypto.randomBytes(16).toString("hex");
// 4. Encrypt the project key with the users key pair.
const { ciphertext: encryptedProjectKey, nonce: encryptedProjectKeyIv } = encryptAsymmetric(
randomBytes,
publicKey,
privateKey
);
const { ciphertext: encryptedProjectKey, nonce: encryptedProjectKeyIv } = crypto
.encryption()
.asymmetric()
.encrypt(randomBytes, publicKey, privateKey);
return { key: encryptedProjectKey, iv: encryptedProjectKeyIv };
};
+101 -47
View File
@@ -21,14 +21,10 @@ import {
decryptIntegrationAuths,
decryptSecretApprovals,
decryptSecrets,
decryptSecretVersions
decryptSecretVersions,
SymmetricKeySize
} from "@app/lib/crypto";
import {
decryptAsymmetric,
encryptSymmetric128BitHexKeyUTF8,
infisicalSymmetricDecrypt,
infisicalSymmetricEncypt
} from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueJobTypes, TQueueServiceFactory } from "@app/queue";
@@ -118,17 +114,14 @@ export const projectQueueFactory = ({
await projectDAL.setProjectUpgradeStatus(data.projectId, ProjectUpgradeStatus.InProgress); // Set the status to in progress. This is important to prevent multiple upgrades at the same time.
// eslint-disable-next-line no-promise-executor-return
// await new Promise((resolve) => setTimeout(resolve, 50_000));
const userPrivateKey = infisicalSymmetricDecrypt({
const userPrivateKey = crypto.encryption().symmetric().decryptWithRootEncryptionKey({
keyEncoding: data.encryptedPrivateKey.keyEncoding,
ciphertext: data.encryptedPrivateKey.encryptedKey,
iv: data.encryptedPrivateKey.encryptedKeyIv,
tag: data.encryptedPrivateKey.encryptedKeyTag
});
const decryptedPlainProjectKey = decryptAsymmetric({
const decryptedPlainProjectKey = crypto.encryption().asymmetric().decrypt({
ciphertext: oldProjectKey.encryptedKey,
nonce: oldProjectKey.nonce,
publicKey: oldProjectKey.sender.publicKey,
@@ -321,7 +314,10 @@ export const projectQueueFactory = ({
await projectKeyDAL.insertMany(newProjectMembers, tx);
// Encrypt the bot private key (which is the same as the ghost user)
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(ghostUser.keys.plainPrivateKey);
const { iv, tag, ciphertext, encoding, algorithm } = crypto
.encryption()
.symmetric()
.encryptWithRootEncryptionKey(ghostUser.keys.plainPrivateKey);
// 5. Create a bot for the project
const newBot = await projectBotDAL.create(
@@ -342,14 +338,17 @@ export const projectQueueFactory = ({
tx
);
const botPrivateKey = infisicalSymmetricDecrypt({
keyEncoding: newBot.keyEncoding as SecretKeyEncoding,
iv: newBot.iv,
tag: newBot.tag,
ciphertext: newBot.encryptedPrivateKey
});
const botPrivateKey = crypto
.encryption()
.symmetric()
.decryptWithRootEncryptionKey({
keyEncoding: newBot.keyEncoding as SecretKeyEncoding,
iv: newBot.iv,
tag: newBot.tag,
ciphertext: newBot.encryptedPrivateKey
});
const botKey = decryptAsymmetric({
const botKey = crypto.encryption().asymmetric().decrypt({
ciphertext: newBot.encryptedProjectKey!,
privateKey: botPrivateKey,
nonce: newBot.encryptedProjectKeyNonce!,
@@ -361,12 +360,29 @@ export const projectQueueFactory = ({
const updatedSecretApprovals: TSecretApprovalRequestsSecrets[] = [];
const updatedIntegrationAuths: TIntegrationAuths[] = [];
for (const rawSecret of decryptedSecrets) {
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecret.decrypted.secretKey, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecret.decrypted.secretValue || "", botKey);
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(
rawSecret.decrypted.secretComment || "",
botKey
);
const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({
plaintext: rawSecret.decrypted.secretKey,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretValueEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: rawSecret.decrypted.secretValue || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretCommentEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: rawSecret.decrypted.secretComment || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const payload: TSecrets = {
...rawSecret.original,
@@ -393,15 +409,29 @@ export const projectQueueFactory = ({
}
for (const rawSecretVersion of decryptedSecretVersions) {
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecretVersion.decrypted.secretKey, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(
rawSecretVersion.decrypted.secretValue || "",
botKey
);
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(
rawSecretVersion.decrypted.secretComment || "",
botKey
);
const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({
plaintext: rawSecretVersion.decrypted.secretKey,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretValueEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: rawSecretVersion.decrypted.secretValue || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretCommentEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: rawSecretVersion.decrypted.secretComment || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const payload: TSecretVersions = {
...rawSecretVersion.original,
@@ -428,15 +458,27 @@ export const projectQueueFactory = ({
}
for (const rawSecretApproval of decryptedApprovalSecrets) {
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(rawSecretApproval.decrypted.secretKey, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(
rawSecretApproval.decrypted.secretValue || "",
botKey
);
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(
rawSecretApproval.decrypted.secretComment || "",
botKey
);
const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({
plaintext: rawSecretApproval.decrypted.secretKey,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretValueEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: rawSecretApproval.decrypted.secretValue || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretCommentEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: rawSecretApproval.decrypted.secretComment || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const payload: TSecretApprovalRequestsSecrets = {
...rawSecretApproval.original,
@@ -463,9 +505,21 @@ export const projectQueueFactory = ({
}
for (const integrationAuth of decryptedIntegrationAuths) {
const access = encryptSymmetric128BitHexKeyUTF8(integrationAuth.decrypted.access, botKey);
const accessId = encryptSymmetric128BitHexKeyUTF8(integrationAuth.decrypted.accessId, botKey);
const refresh = encryptSymmetric128BitHexKeyUTF8(integrationAuth.decrypted.refresh, botKey);
const access = crypto.encryption().symmetric().encrypt({
plaintext: integrationAuth.decrypted.access,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const accessId = crypto.encryption().symmetric().encrypt({
plaintext: integrationAuth.decrypted.accessId,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const refresh = crypto.encryption().symmetric().encrypt({
plaintext: integrationAuth.decrypted.refresh,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const payload: TIntegrationAuths = {
...integrationAuth.original,
@@ -27,7 +27,7 @@ import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
import { TSshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal";
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid";
@@ -377,7 +377,10 @@ export const projectServiceFactory = ({
tx
);
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(ghostUser.keys.plainPrivateKey);
const { iv, tag, ciphertext, encoding, algorithm } = crypto
.encryption()
.symmetric()
.encryptWithRootEncryptionKey(ghostUser.keys.plainPrivateKey);
// 5. Create & a bot for the project
await projectBotDAL.create(
@@ -820,7 +823,7 @@ export const projectServiceFactory = ({
});
}
const encryptedPrivateKey = infisicalSymmetricEncypt(userPrivateKey);
const encryptedPrivateKey = crypto.encryption().symmetric().encryptWithRootEncryptionKey(userPrivateKey);
await projectQueue.upgradeProject({
projectId,
@@ -1,10 +1,7 @@
import crypto from "node:crypto";
import bcrypt from "bcrypt";
import { TSecretSharing } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { SecretSharingAccessType } from "@app/lib/types";
@@ -136,7 +133,7 @@ export const secretSharingServiceFactory = ({
const encryptedSecret = encryptWithRoot(Buffer.from(secretValue));
const id = crypto.randomBytes(32).toString("hex");
const hashedPassword = password ? await bcrypt.hash(password, 10) : null;
const hashedPassword = password ? await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS) : null;
const newSharedSecret = await secretSharingDAL.create({
identifier: id,
@@ -386,8 +383,10 @@ export const secretSharingServiceFactory = ({
const encryptWithRoot = kmsService.encryptWithRootKey();
const encryptedSecret = encryptWithRoot(Buffer.from(secretValue));
const appCfg = getConfig();
const id = crypto.randomBytes(32).toString("hex");
const hashedPassword = password ? await bcrypt.hash(password, 10) : null;
const hashedPassword = password ? await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS) : null;
const newSharedSecret = await secretSharingDAL.create({
identifier: id,
@@ -529,7 +528,7 @@ export const secretSharingServiceFactory = ({
const hasProvidedPassword = Boolean(password);
if (isPasswordProtected) {
if (hasProvidedPassword) {
const isMatch = await bcrypt.compare(password as string, sharedSecret.password as string);
const isMatch = await crypto.hashing().compareHash(password as string, sharedSecret.password as string);
if (!isMatch) throw new UnauthorizedError({ message: "Invalid credentials" });
} else {
return { isPasswordProtected };
@@ -24,6 +24,8 @@ import {
Tag
} from "aws-sdk/clients/secretsmanager";
import { CustomAWSHasher } from "@app/lib/aws/hashing";
import { crypto } from "@app/lib/crypto";
import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns";
import { AwsSecretsManagerSyncMappingBehavior } from "@app/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-enums";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
@@ -46,6 +48,8 @@ const getSecretsManagerClient = async (secretSync: TAwsSecretsManagerSyncWithCre
const secretsManagerClient = new SecretsManagerClient({
region: config.region,
useFipsEndpoint: crypto.isFipsModeEnabled(),
sha256: CustomAWSHasher,
credentials: config.credentials!
});
@@ -17,7 +17,7 @@ export const AzureDevOpsSyncDestinationConfigSchema = z.object({
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_DEVOPS?.devopsProjectId || "Azure DevOps Project ID"),
devopsProjectName: z
.string()
.min(1, "Project name required")
.optional()
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_DEVOPS?.devopsProjectName || "Azure DevOps Project Name")
});
@@ -0,0 +1,10 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
export const CHECKLY_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "Checkly",
destination: SecretSync.Checkly,
connection: AppConnection.Checkly,
canImportSecrets: false
};
@@ -0,0 +1,102 @@
/* eslint-disable no-continue */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ChecklyPublicAPI } from "@app/services/app-connection/checkly/checkly-connection-public-client";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { SecretSyncError } from "../secret-sync-errors";
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
import { TSecretMap } from "../secret-sync-types";
import { TChecklySyncWithCredentials } from "./checkly-sync-types";
export const ChecklySyncFns = {
async getSecrets(secretSync: TChecklySyncWithCredentials) {
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
},
async syncSecrets(secretSync: TChecklySyncWithCredentials, secretMap: TSecretMap) {
const {
environment,
syncOptions: { disableSecretDeletion, keySchema }
} = secretSync;
const config = secretSync.destinationConfig;
const variables = await ChecklyPublicAPI.getVariables(secretSync.connection, config.accountId);
const checklySecrets = Object.fromEntries(variables!.map((variable) => [variable.key, variable]));
for await (const key of Object.keys(secretMap)) {
try {
const entry = secretMap[key];
// If value is empty, we skip the upsert - checkly does not allow empty values
if (entry.value.trim() === "") {
// Delete the secret from Checkly if its empty
if (!disableSecretDeletion) {
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
key
});
}
continue; // Skip empty values
}
await ChecklyPublicAPI.upsertVariable(secretSync.connection, config.accountId, {
key,
value: entry.value,
secret: true,
locked: true
});
} catch (error) {
throw new SecretSyncError({
error,
secretKey: key
});
}
}
if (disableSecretDeletion) return;
for await (const key of Object.keys(checklySecrets)) {
try {
// eslint-disable-next-line no-continue
if (!matchesSchema(key, environment?.slug || "", keySchema)) continue;
if (!secretMap[key]) {
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
key
});
}
} catch (error) {
throw new SecretSyncError({
error,
secretKey: key
});
}
}
},
async removeSecrets(secretSync: TChecklySyncWithCredentials, secretMap: TSecretMap) {
const config = secretSync.destinationConfig;
const variables = await ChecklyPublicAPI.getVariables(secretSync.connection, config.accountId);
const checklySecrets = Object.fromEntries(variables!.map((variable) => [variable.key, variable]));
for await (const secret of Object.keys(checklySecrets)) {
try {
if (secret in secretMap) {
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
key: secret
});
}
} catch (error) {
throw new SecretSyncError({
error,
secretKey: secret
});
}
}
}
};
@@ -0,0 +1,43 @@
import { z } from "zod";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import {
BaseSecretSyncSchema,
GenericCreateSecretSyncFieldsSchema,
GenericUpdateSecretSyncFieldsSchema
} from "@app/services/secret-sync/secret-sync-schemas";
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
const ChecklySyncDestinationConfigSchema = z.object({
accountId: z.string().min(1, "Account ID is required").max(255, "Account ID must be less than 255 characters"),
accountName: z.string().min(1, "Account Name is required").max(255, "Account ID must be less than 255 characters")
});
const ChecklySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
export const ChecklySyncSchema = BaseSecretSyncSchema(SecretSync.Checkly, ChecklySyncOptionsConfig).extend({
destination: z.literal(SecretSync.Checkly),
destinationConfig: ChecklySyncDestinationConfigSchema
});
export const CreateChecklySyncSchema = GenericCreateSecretSyncFieldsSchema(
SecretSync.Checkly,
ChecklySyncOptionsConfig
).extend({
destinationConfig: ChecklySyncDestinationConfigSchema
});
export const UpdateChecklySyncSchema = GenericUpdateSecretSyncFieldsSchema(
SecretSync.Checkly,
ChecklySyncOptionsConfig
).extend({
destinationConfig: ChecklySyncDestinationConfigSchema.optional()
});
export const ChecklySyncListItemSchema = z.object({
name: z.literal("Checkly"),
connection: z.literal(AppConnection.Checkly),
destination: z.literal(SecretSync.Checkly),
canImportSecrets: z.literal(false)
});
@@ -0,0 +1,23 @@
import z from "zod";
import { TChecklyConnection, TChecklyVariable } from "@app/services/app-connection/checkly";
import { ChecklySyncListItemSchema, ChecklySyncSchema, CreateChecklySyncSchema } from "./checkly-sync-schemas";
export type TChecklySyncListItem = z.infer<typeof ChecklySyncListItemSchema>;
export type TChecklySync = z.infer<typeof ChecklySyncSchema>;
export type TChecklySyncInput = z.infer<typeof CreateChecklySyncSchema>;
export type TChecklySyncWithCredentials = TChecklySync & {
connection: TChecklyConnection;
};
export type TChecklySecret = TChecklyVariable;
export type TChecklyVariablesGraphResponse = {
data: {
variables: Record<string, string>;
};
};
@@ -0,0 +1,10 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
export const CLOUDFLARE_WORKERS_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "Cloudflare Workers",
destination: SecretSync.CloudflareWorkers,
connection: AppConnection.Cloudflare,
canImportSecrets: false
};
@@ -0,0 +1,121 @@
import { request } from "@app/lib/config/request";
import { applyJitter } from "@app/lib/dates";
import { delay as delayMs } from "@app/lib/delay";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
import { TCloudflareWorkersSyncWithCredentials } from "./cloudflare-workers-types";
const getSecretKeys = async (secretSync: TCloudflareWorkersSyncWithCredentials): Promise<string[]> => {
const {
destinationConfig,
connection: {
credentials: { apiToken, accountId }
}
} = secretSync;
const { data } = await request.get<{
result: Array<{ name: string }>;
}>(
`${IntegrationUrls.CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accountId}/workers/scripts/${destinationConfig.scriptId}/secrets`,
{
headers: {
Authorization: `Bearer ${apiToken}`,
Accept: "application/json"
}
}
);
return data.result.map((s) => s.name);
};
export const CloudflareWorkersSyncFns = {
syncSecrets: async (secretSync: TCloudflareWorkersSyncWithCredentials, secretMap: TSecretMap) => {
const {
connection: {
credentials: { apiToken, accountId }
},
destinationConfig: { scriptId }
} = secretSync;
const existingSecretNames = await getSecretKeys(secretSync);
const secretMapKeys = new Set(Object.keys(secretMap));
for await (const [key, val] of Object.entries(secretMap)) {
await delayMs(Math.max(0, applyJitter(100, 200)));
await request.put(
`${IntegrationUrls.CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accountId}/workers/scripts/${scriptId}/secrets`,
{ name: key, text: val.value, type: "secret_text" },
{
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json"
}
}
);
}
if (!secretSync.syncOptions.disableSecretDeletion) {
const secretsToDelete = existingSecretNames.filter((existingKey) => {
const isManagedBySchema = matchesSchema(
existingKey,
secretSync.environment?.slug || "",
secretSync.syncOptions.keySchema
);
const isInNewSecretMap = secretMapKeys.has(existingKey);
return !isInNewSecretMap && isManagedBySchema;
});
for await (const key of secretsToDelete) {
await delayMs(Math.max(0, applyJitter(100, 200)));
await request.delete(
`${IntegrationUrls.CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accountId}/workers/scripts/${scriptId}/secrets/${key}`,
{
headers: {
Authorization: `Bearer ${apiToken}`
}
}
);
}
}
},
getSecrets: async (secretSync: TCloudflareWorkersSyncWithCredentials): Promise<TSecretMap> => {
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
},
removeSecrets: async (secretSync: TCloudflareWorkersSyncWithCredentials, secretMap: TSecretMap) => {
const {
connection: {
credentials: { apiToken, accountId }
},
destinationConfig: { scriptId }
} = secretSync;
const existingSecretNames = await getSecretKeys(secretSync);
const secretMapToRemoveKeys = new Set(Object.keys(secretMap));
for await (const existingKey of existingSecretNames) {
const isManagedBySchema = matchesSchema(
existingKey,
secretSync.environment?.slug || "",
secretSync.syncOptions.keySchema
);
const isInSecretMapToRemove = secretMapToRemoveKeys.has(existingKey);
if (isInSecretMapToRemove && isManagedBySchema) {
await delayMs(Math.max(0, applyJitter(100, 200)));
await request.delete(
`${IntegrationUrls.CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accountId}/workers/scripts/${scriptId}/secrets/${existingKey}`,
{
headers: {
Authorization: `Bearer ${apiToken}`
}
}
);
}
}
}
};
@@ -0,0 +1,55 @@
import RE2 from "re2";
import { z } from "zod";
import { SecretSyncs } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import {
BaseSecretSyncSchema,
GenericCreateSecretSyncFieldsSchema,
GenericUpdateSecretSyncFieldsSchema
} from "@app/services/secret-sync/secret-sync-schemas";
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
const CloudflareWorkersSyncDestinationConfigSchema = z.object({
scriptId: z
.string()
.min(1, "Script ID is required")
.max(64)
.refine((val) => {
const re2 = new RE2(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/);
return re2.test(val);
}, "Invalid script ID format")
.describe(SecretSyncs.DESTINATION_CONFIG.CLOUDFLARE_WORKERS.scriptId)
});
const CloudflareWorkersSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
export const CloudflareWorkersSyncSchema = BaseSecretSyncSchema(
SecretSync.CloudflareWorkers,
CloudflareWorkersSyncOptionsConfig
).extend({
destination: z.literal(SecretSync.CloudflareWorkers),
destinationConfig: CloudflareWorkersSyncDestinationConfigSchema
});
export const CreateCloudflareWorkersSyncSchema = GenericCreateSecretSyncFieldsSchema(
SecretSync.CloudflareWorkers,
CloudflareWorkersSyncOptionsConfig
).extend({
destinationConfig: CloudflareWorkersSyncDestinationConfigSchema
});
export const UpdateCloudflareWorkersSyncSchema = GenericUpdateSecretSyncFieldsSchema(
SecretSync.CloudflareWorkers,
CloudflareWorkersSyncOptionsConfig
).extend({
destinationConfig: CloudflareWorkersSyncDestinationConfigSchema.optional()
});
export const CloudflareWorkersSyncListItemSchema = z.object({
name: z.literal("Cloudflare Workers"),
connection: z.literal(AppConnection.Cloudflare),
destination: z.literal(SecretSync.CloudflareWorkers),
canImportSecrets: z.literal(false)
});
@@ -0,0 +1,19 @@
import z from "zod";
import { TCloudflareConnection } from "@app/services/app-connection/cloudflare/cloudflare-connection-types";
import {
CloudflareWorkersSyncListItemSchema,
CloudflareWorkersSyncSchema,
CreateCloudflareWorkersSyncSchema
} from "./cloudflare-workers-schemas";
export type TCloudflareWorkersSyncListItem = z.infer<typeof CloudflareWorkersSyncListItemSchema>;
export type TCloudflareWorkersSync = z.infer<typeof CloudflareWorkersSyncSchema>;
export type TCloudflareWorkersSyncInput = z.infer<typeof CreateCloudflareWorkersSyncSchema>;
export type TCloudflareWorkersSyncWithCredentials = TCloudflareWorkersSync & {
connection: TCloudflareConnection;
};
@@ -0,0 +1,4 @@
export * from "./cloudflare-workers-constants";
export * from "./cloudflare-workers-fns";
export * from "./cloudflare-workers-schemas";
export * from "./cloudflare-workers-types";
@@ -21,8 +21,11 @@ export enum SecretSync {
Flyio = "flyio",
GitLab = "gitlab",
CloudflarePages = "cloudflare-pages",
CloudflareWorkers = "cloudflare-workers",
Zabbix = "zabbix",
Railway = "railway"
Railway = "railway",
Checkly = "checkly"
}
export enum SecretSyncInitialSyncBehavior {
@@ -29,8 +29,11 @@ import { AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION, azureAppConfigurationSyncFact
import { AZURE_DEVOPS_SYNC_LIST_OPTION, azureDevOpsSyncFactory } from "./azure-devops";
import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault";
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
import { CHECKLY_SYNC_LIST_OPTION } from "./checkly/checkly-sync-constants";
import { ChecklySyncFns } from "./checkly/checkly-sync-fns";
import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants";
import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns";
import { CLOUDFLARE_WORKERS_SYNC_LIST_OPTION, CloudflareWorkersSyncFns } from "./cloudflare-workers";
import { FLYIO_SYNC_LIST_OPTION, FlyioSyncFns } from "./flyio";
import { GCP_SYNC_LIST_OPTION } from "./gcp";
import { GcpSyncFns } from "./gcp/gcp-sync-fns";
@@ -72,8 +75,11 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
[SecretSync.Flyio]: FLYIO_SYNC_LIST_OPTION,
[SecretSync.GitLab]: GITLAB_SYNC_LIST_OPTION,
[SecretSync.CloudflarePages]: CLOUDFLARE_PAGES_SYNC_LIST_OPTION,
[SecretSync.CloudflareWorkers]: CLOUDFLARE_WORKERS_SYNC_LIST_OPTION,
[SecretSync.Zabbix]: ZABBIX_SYNC_LIST_OPTION,
[SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION
[SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION,
[SecretSync.Checkly]: CHECKLY_SYNC_LIST_OPTION
};
export const listSecretSyncOptions = () => {
@@ -241,10 +247,14 @@ export const SecretSyncFns = {
return GitLabSyncFns.syncSecrets(secretSync, schemaSecretMap, { appConnectionDAL, kmsService });
case SecretSync.CloudflarePages:
return CloudflarePagesSyncFns.syncSecrets(secretSync, schemaSecretMap);
case SecretSync.CloudflareWorkers:
return CloudflareWorkersSyncFns.syncSecrets(secretSync, schemaSecretMap);
case SecretSync.Zabbix:
return ZabbixSyncFns.syncSecrets(secretSync, schemaSecretMap);
case SecretSync.Railway:
return RailwaySyncFns.syncSecrets(secretSync, schemaSecretMap);
case SecretSync.Checkly:
return ChecklySyncFns.syncSecrets(secretSync, schemaSecretMap);
default:
throw new Error(
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -337,12 +347,18 @@ export const SecretSyncFns = {
case SecretSync.CloudflarePages:
secretMap = await CloudflarePagesSyncFns.getSecrets(secretSync);
break;
case SecretSync.CloudflareWorkers:
secretMap = await CloudflareWorkersSyncFns.getSecrets(secretSync);
break;
case SecretSync.Zabbix:
secretMap = await ZabbixSyncFns.getSecrets(secretSync);
break;
case SecretSync.Railway:
secretMap = await RailwaySyncFns.getSecrets(secretSync);
break;
case SecretSync.Checkly:
secretMap = await ChecklySyncFns.getSecrets(secretSync);
break;
default:
throw new Error(
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -420,10 +436,14 @@ export const SecretSyncFns = {
return GitLabSyncFns.removeSecrets(secretSync, schemaSecretMap, { appConnectionDAL, kmsService });
case SecretSync.CloudflarePages:
return CloudflarePagesSyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.CloudflareWorkers:
return CloudflareWorkersSyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.Zabbix:
return ZabbixSyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.Railway:
return RailwaySyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.Checkly:
return ChecklySyncFns.removeSecrets(secretSync, schemaSecretMap);
default:
throw new Error(
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -24,8 +24,11 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.Flyio]: "Fly.io",
[SecretSync.GitLab]: "GitLab",
[SecretSync.CloudflarePages]: "Cloudflare Pages",
[SecretSync.CloudflareWorkers]: "Cloudflare Workers",
[SecretSync.Zabbix]: "Zabbix",
[SecretSync.Railway]: "Railway"
[SecretSync.Railway]: "Railway",
[SecretSync.Checkly]: "Checkly"
};
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
@@ -51,8 +54,11 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.Flyio]: AppConnection.Flyio,
[SecretSync.GitLab]: AppConnection.GitLab,
[SecretSync.CloudflarePages]: AppConnection.Cloudflare,
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
[SecretSync.Zabbix]: AppConnection.Zabbix,
[SecretSync.Railway]: AppConnection.Railway
[SecretSync.Railway]: AppConnection.Railway,
[SecretSync.Checkly]: AppConnection.Checkly
};
export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
@@ -78,6 +84,9 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
[SecretSync.Flyio]: SecretSyncPlanType.Regular,
[SecretSync.GitLab]: SecretSyncPlanType.Regular,
[SecretSync.CloudflarePages]: SecretSyncPlanType.Regular,
[SecretSync.CloudflareWorkers]: SecretSyncPlanType.Regular,
[SecretSync.Zabbix]: SecretSyncPlanType.Regular,
[SecretSync.Railway]: SecretSyncPlanType.Regular
[SecretSync.Railway]: SecretSyncPlanType.Regular,
[SecretSync.Checkly]: SecretSyncPlanType.Regular
};
@@ -72,12 +72,24 @@ import {
TAzureKeyVaultSyncListItem,
TAzureKeyVaultSyncWithCredentials
} from "./azure-key-vault";
import {
TChecklySync,
TChecklySyncInput,
TChecklySyncListItem,
TChecklySyncWithCredentials
} from "./checkly/checkly-sync-types";
import {
TCloudflarePagesSync,
TCloudflarePagesSyncInput,
TCloudflarePagesSyncListItem,
TCloudflarePagesSyncWithCredentials
} from "./cloudflare-pages/cloudflare-pages-types";
import {
TCloudflareWorkersSync,
TCloudflareWorkersSyncInput,
TCloudflareWorkersSyncListItem,
TCloudflareWorkersSyncWithCredentials
} from "./cloudflare-workers";
import { TFlyioSync, TFlyioSyncInput, TFlyioSyncListItem, TFlyioSyncWithCredentials } from "./flyio/flyio-sync-types";
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
import { TGitLabSync, TGitLabSyncInput, TGitLabSyncListItem, TGitLabSyncWithCredentials } from "./gitlab";
@@ -144,8 +156,10 @@ export type TSecretSync =
| TFlyioSync
| TGitLabSync
| TCloudflarePagesSync
| TCloudflareWorkersSync
| TZabbixSync
| TRailwaySync;
| TRailwaySync
| TChecklySync;
export type TSecretSyncWithCredentials =
| TAwsParameterStoreSyncWithCredentials
@@ -170,8 +184,10 @@ export type TSecretSyncWithCredentials =
| TFlyioSyncWithCredentials
| TGitLabSyncWithCredentials
| TCloudflarePagesSyncWithCredentials
| TCloudflareWorkersSyncWithCredentials
| TZabbixSyncWithCredentials
| TRailwaySyncWithCredentials;
| TRailwaySyncWithCredentials
| TChecklySyncWithCredentials;
export type TSecretSyncInput =
| TAwsParameterStoreSyncInput
@@ -196,8 +212,10 @@ export type TSecretSyncInput =
| TFlyioSyncInput
| TGitLabSyncInput
| TCloudflarePagesSyncInput
| TCloudflareWorkersSyncInput
| TZabbixSyncInput
| TRailwaySyncInput;
| TRailwaySyncInput
| TChecklySyncInput;
export type TSecretSyncListItem =
| TAwsParameterStoreSyncListItem
@@ -222,8 +240,10 @@ export type TSecretSyncListItem =
| TFlyioSyncListItem
| TGitLabSyncListItem
| TCloudflarePagesSyncListItem
| TCloudflareWorkersSyncListItem
| TZabbixSyncListItem
| TRailwaySyncListItem;
| TRailwaySyncListItem
| TChecklySyncListItem;
export type TSyncOptionsConfig = {
canImportSecrets: boolean;
+69 -27
View File
@@ -15,11 +15,8 @@ import { hasSecretReadValueOrDescribePermission } from "@app/ee/services/permiss
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
import {
buildSecretBlindIndexFromName,
decryptSymmetric128BitHexKeyUTF8,
encryptSymmetric128BitHexKeyUTF8
} from "@app/lib/crypto";
import { buildSecretBlindIndexFromName } from "@app/lib/crypto";
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy, unique } from "@app/lib/fn";
@@ -237,17 +234,19 @@ export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderD
const secrets = await secretDAL.findByFolderId(folder.id);
const decryptedSec = secrets.reduce<Record<string, string>>((prev, secret) => {
const decryptedSecretKey = decryptSymmetric128BitHexKeyUTF8({
const decryptedSecretKey = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key: secretEncKey
key: secretEncKey,
keySize: SymmetricKeySize.Bits128
});
const decryptedSecretValue = decryptSymmetric128BitHexKeyUTF8({
const decryptedSecretValue = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key: secretEncKey
key: secretEncKey,
keySize: SymmetricKeySize.Bits128
});
// eslint-disable-next-line
@@ -364,30 +363,33 @@ export const decryptSecretRaw = (
},
key: string
) => {
const secretKey = decryptSymmetric128BitHexKeyUTF8({
const secretKey = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key
key,
keySize: SymmetricKeySize.Bits128
});
const secretValue = !secret.secretValueHidden
? decryptSymmetric128BitHexKeyUTF8({
? crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key
key,
keySize: SymmetricKeySize.Bits128
})
: INFISICAL_SECRET_VALUE_HIDDEN_MASK;
let secretComment = "";
if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) {
secretComment = decryptSymmetric128BitHexKeyUTF8({
secretComment = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretCommentCiphertext,
iv: secret.secretCommentIV,
tag: secret.secretCommentTag,
key
key,
keySize: SymmetricKeySize.Bits128
});
}
@@ -875,11 +877,30 @@ export const createManySecretsRawFnFactory = ({
message: `Project bot not found for project with ID '${projectId}'. Please upgrade your project.`,
name: "bot_not_found_error"
});
const inputSecrets = secrets.map((secret) => {
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretName, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretValue || "", botKey);
const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({
plaintext: secret.secretName,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretValueEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: secret.secretValue || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretReferences = getAllNestedSecretReferences(secret.secretValue || "");
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretComment || "", botKey);
const secretCommentEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: secret.secretComment || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
return {
type: secret.type,
@@ -1063,10 +1084,28 @@ export const updateManySecretsRawFnFactory = ({
throw new BadRequestError({ message: "New secret name cannot be empty" });
}
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretName, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretValue || "", botKey);
const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({
plaintext: secret.secretName,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretValueEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: secret.secretValue || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretReferences = getAllNestedSecretReferences(secret.secretValue || "");
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretComment || "", botKey);
const secretCommentEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: secret.secretComment || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
return {
type: secret.type,
@@ -1149,28 +1188,31 @@ export const decryptSecretWithBot = (
>,
key: string
) => {
const secretKey = decryptSymmetric128BitHexKeyUTF8({
const secretKey = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key
key,
keySize: SymmetricKeySize.Bits128
});
const secretValue = decryptSymmetric128BitHexKeyUTF8({
const secretValue = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key
key,
keySize: SymmetricKeySize.Bits128
});
let secretComment = "";
if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) {
secretComment = decryptSymmetric128BitHexKeyUTF8({
secretComment = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretCommentCiphertext,
iv: secret.secretCommentIV,
tag: secret.secretCommentTag,
key
key,
keySize: SymmetricKeySize.Bits128
});
}
+66 -41
View File
@@ -18,8 +18,7 @@ import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-d
import { TSnapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal";
import { KeyStorePrefixes, KeyStoreTtls, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { getTimeDifferenceInSeconds, groupBy, isSamePath, unique } from "@app/lib/fn";
@@ -504,18 +503,20 @@ export const secretQueueFactory = ({
const secrets = await secretDAL.findByFolderId(dto.folderId);
await Promise.allSettled(
secrets.map(async (secret) => {
const secretKey = decryptSymmetric128BitHexKeyUTF8({
const secretKey = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key: dto.key
key: dto.key,
keySize: SymmetricKeySize.Bits128
});
const secretValue = decryptSymmetric128BitHexKeyUTF8({
const secretValue = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key: dto.key
key: dto.key,
keySize: SymmetricKeySize.Bits128
});
const expandedSecretValue = await expandSecretReferences({
environment: dto.environment,
@@ -527,11 +528,12 @@ export const secretQueueFactory = ({
content[secretKey] = { value: expandedSecretValue || "" };
if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) {
const commentValue = decryptSymmetric128BitHexKeyUTF8({
const commentValue = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretCommentCiphertext,
iv: secret.secretCommentIV,
tag: secret.secretCommentTag,
key: dto.key
key: dto.key,
keySize: SymmetricKeySize.Bits128
});
content[secretKey].comment = commentValue;
}
@@ -952,12 +954,16 @@ export const secretQueueFactory = ({
integrationAuth.awsAssumeIamRoleArnIV &&
integrationAuth.awsAssumeIamRoleArnCipherText
) {
awsAssumeRoleArn = decryptSymmetric128BitHexKeyUTF8({
ciphertext: integrationAuth.awsAssumeIamRoleArnCipherText,
iv: integrationAuth.awsAssumeIamRoleArnIV,
tag: integrationAuth.awsAssumeIamRoleArnTag,
key: botKey as string
});
awsAssumeRoleArn = crypto
.encryption()
.symmetric()
.decrypt({
ciphertext: integrationAuth.awsAssumeIamRoleArnCipherText,
iv: integrationAuth.awsAssumeIamRoleArnIV,
tag: integrationAuth.awsAssumeIamRoleArnTag,
key: botKey as string,
keySize: SymmetricKeySize.Bits128
});
}
const suffixedSecrets: typeof secrets = {};
@@ -1236,7 +1242,10 @@ export const secretQueueFactory = ({
},
tx
);
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(ghostUser.keys.plainPrivateKey);
const { iv, tag, ciphertext, encoding, algorithm } = crypto
.encryption()
.symmetric()
.encryptWithRootEncryptionKey(ghostUser.keys.plainPrivateKey);
await projectBotDAL.updateById(
bot.id,
{
@@ -1267,27 +1276,31 @@ export const secretQueueFactory = ({
secretId: string;
references: { environment: string; secretPath: string; secretKey: string }[];
}[] = [];
await secretV2BridgeDAL.batchInsert(
projectV1Secrets.map((el) => {
const key = decryptSymmetric128BitHexKeyUTF8({
const key = crypto.encryption().symmetric().decrypt({
ciphertext: el.secretKeyCiphertext,
iv: el.secretKeyIV,
tag: el.secretKeyTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const value = decryptSymmetric128BitHexKeyUTF8({
const value = crypto.encryption().symmetric().decrypt({
ciphertext: el.secretValueCiphertext,
iv: el.secretValueIV,
tag: el.secretValueTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const comment =
el.secretCommentCiphertext && el.secretCommentTag && el.secretCommentIV
? decryptSymmetric128BitHexKeyUTF8({
? crypto.encryption().symmetric().decrypt({
ciphertext: el.secretCommentCiphertext,
iv: el.secretCommentIV,
tag: el.secretCommentTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
})
: "";
const encryptedValue = secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob;
@@ -1325,6 +1338,7 @@ export const secretQueueFactory = ({
const projectV3SecretVersionsGroupById: Record<string, TSecretVersionsV2> = {};
const projectV3SecretVersionTags: { secret_versions_v2Id: string; secret_tagsId: string }[] = [];
const projectV3SnapshotSecrets: Omit<TSecretSnapshotSecretsV2, "id">[] = [];
snapshots.forEach(({ secretVersions = [], ...snapshot }) => {
secretVersions.forEach((el) => {
projectV3SnapshotSecrets.push({
@@ -1336,25 +1350,28 @@ export const secretQueueFactory = ({
});
if (projectV3SecretVersionsGroupById[el.id]) return;
const key = decryptSymmetric128BitHexKeyUTF8({
const key = crypto.encryption().symmetric().decrypt({
ciphertext: el.secretKeyCiphertext,
iv: el.secretKeyIV,
tag: el.secretKeyTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const value = decryptSymmetric128BitHexKeyUTF8({
const value = crypto.encryption().symmetric().decrypt({
ciphertext: el.secretValueCiphertext,
iv: el.secretValueIV,
tag: el.secretValueTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const comment =
el.secretCommentCiphertext && el.secretCommentTag && el.secretCommentIV
? decryptSymmetric128BitHexKeyUTF8({
? crypto.encryption().symmetric().decrypt({
ciphertext: el.secretCommentCiphertext,
iv: el.secretCommentIV,
tag: el.secretCommentTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
})
: "";
const encryptedValue = secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob;
@@ -1395,25 +1412,28 @@ export const secretQueueFactory = ({
);
Object.values(latestSecretVersionByFolder).forEach((el) => {
if (projectV3SecretVersionsGroupById[el.id]) return;
const key = decryptSymmetric128BitHexKeyUTF8({
const key = crypto.encryption().symmetric().decrypt({
ciphertext: el.secretKeyCiphertext,
iv: el.secretKeyIV,
tag: el.secretKeyTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const value = decryptSymmetric128BitHexKeyUTF8({
const value = crypto.encryption().symmetric().decrypt({
ciphertext: el.secretValueCiphertext,
iv: el.secretValueIV,
tag: el.secretValueTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const comment =
el.secretCommentCiphertext && el.secretCommentTag && el.secretCommentIV
? decryptSymmetric128BitHexKeyUTF8({
? crypto.encryption().symmetric().decrypt({
ciphertext: el.secretCommentCiphertext,
iv: el.secretCommentIV,
tag: el.secretCommentTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
})
: "";
const encryptedValue = secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob;
@@ -1475,42 +1495,47 @@ export const secretQueueFactory = ({
* */
// eslint-disable-next-line no-await-in-loop
const projectV1IntegrationAuths = await integrationAuthDAL.find({ projectId }, { tx });
await integrationAuthDAL.upsert(
projectV1IntegrationAuths.map((el) => {
const accessToken =
el.accessIV && el.accessTag && el.accessCiphertext
? decryptSymmetric128BitHexKeyUTF8({
? crypto.encryption().symmetric().decrypt({
ciphertext: el.accessCiphertext,
iv: el.accessIV,
tag: el.accessTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
})
: undefined;
const accessId =
el.accessIdIV && el.accessIdTag && el.accessIdCiphertext
? decryptSymmetric128BitHexKeyUTF8({
? crypto.encryption().symmetric().decrypt({
ciphertext: el.accessIdCiphertext,
iv: el.accessIdIV,
tag: el.accessIdTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
})
: undefined;
const refreshToken =
el.refreshIV && el.refreshTag && el.refreshCiphertext
? decryptSymmetric128BitHexKeyUTF8({
? crypto.encryption().symmetric().decrypt({
ciphertext: el.refreshCiphertext,
iv: el.refreshIV,
tag: el.refreshTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
})
: undefined;
const awsAssumeRoleArn =
el.awsAssumeIamRoleArnCipherText && el.awsAssumeIamRoleArnIV && el.awsAssumeIamRoleArnTag
? decryptSymmetric128BitHexKeyUTF8({
? crypto.encryption().symmetric().decrypt({
ciphertext: el.awsAssumeIamRoleArnCipherText,
iv: el.awsAssumeIamRoleArnIV,
tag: el.awsAssumeIamRoleArnTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
})
: undefined;
+125 -35
View File
@@ -28,11 +28,8 @@ import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-
import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service";
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
import { getConfig } from "@app/lib/config/env";
import {
buildSecretBlindIndexFromName,
decryptSymmetric128BitHexKeyUTF8,
encryptSymmetric128BitHexKeyUTF8
} from "@app/lib/crypto";
import { buildSecretBlindIndexFromName, SymmetricKeySize } from "@app/lib/crypto";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy, pick } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
@@ -161,12 +158,16 @@ export const secretServiceFactory = ({
return (el: { ciphertext?: string; iv: string; tag: string }) =>
projectBot?.botKey
? getAllNestedSecretReferences(
decryptSymmetric128BitHexKeyUTF8({
ciphertext: el.ciphertext || "",
iv: el.iv,
tag: el.tag,
key: projectBot.botKey
})
crypto
.encryption()
.symmetric()
.decrypt({
ciphertext: el.ciphertext || "",
iv: el.iv,
tag: el.tag,
key: projectBot.botKey,
keySize: SymmetricKeySize.Bits128
})
)
: undefined;
};
@@ -1696,9 +1697,28 @@ export const secretServiceFactory = ({
message: `Project bot for project with ID '${projectId}' not found. Please upgrade your project.`,
name: "bot_not_found_error"
});
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretName, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey);
const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({
plaintext: secretName,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretValueEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: secretValue || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretCommentEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: secretComment || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
if (policy) {
const approval = await secretApprovalRequestService.generateSecretApprovalRequest({
policy,
@@ -1864,9 +1884,31 @@ export const secretServiceFactory = ({
name: "bot_not_found_error"
});
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey);
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(newSecretName || secretName, botKey);
const secretValueEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: secretValue || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretCommentEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: secretComment || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretKeyEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: newSecretName || secretName,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
if (policy) {
const approval = await secretApprovalRequestService.generateSecretApprovalRequest({
@@ -2110,11 +2152,30 @@ export const secretServiceFactory = ({
message: `Project bot for project with ID '${projectId}' not found. Please upgrade your project.`,
name: "bot_not_found_error"
});
const sanitizedSecrets = inputSecrets.map(
({ secretComment, secretKey, metadata, tagIds, secretValue, skipMultilineEncoding }) => {
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretKey, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey);
const secretKeyEncrypted = crypto.encryption().symmetric().encrypt({
plaintext: secretKey,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretValueEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: secretValue || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretCommentEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: secretComment || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
return {
secretName: secretKey,
skipMultilineEncoding,
@@ -2254,6 +2315,7 @@ export const secretServiceFactory = ({
message: `Project bot for project with ID '${projectId}' not found. Please upgrade your project.`,
name: "bot_not_found_error"
});
const sanitizedSecrets = inputSecrets.map(
({
secretComment,
@@ -2265,9 +2327,30 @@ export const secretServiceFactory = ({
secretReminderNote,
secretReminderRepeatDays
}) => {
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(newSecretName || secretKey, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey);
const secretKeyEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: newSecretName || secretKey,
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretValueEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: secretValue || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretCommentEncrypted = crypto
.encryption()
.symmetric()
.encrypt({
plaintext: secretComment || "",
key: botKey,
keySize: SymmetricKeySize.Bits128
});
return {
secretName: secretKey,
newSecretName,
@@ -2476,12 +2559,14 @@ export const secretServiceFactory = ({
limit,
sort: [["createdAt", "desc"]]
});
return secretVersions.map((el) => {
const secretKey = decryptSymmetric128BitHexKeyUTF8({
const secretKey = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
});
const secretValueHidden = !hasSecretReadValueOrDescribePermission(
@@ -2798,11 +2883,12 @@ export const secretServiceFactory = ({
secrets.map(({ id, secretValueCiphertext, secretValueIV, secretValueTag }) => ({
secretId: id,
references: getAllNestedSecretReferences(
decryptSymmetric128BitHexKeyUTF8({
crypto.encryption().symmetric().decrypt({
ciphertext: secretValueCiphertext,
iv: secretValueIV,
tag: secretValueTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
})
)
})),
@@ -2902,11 +2988,12 @@ export const secretServiceFactory = ({
const destinationActions = [ProjectPermissionSecretActions.Create, ProjectPermissionSecretActions.Edit] as const;
const decryptedSourceSecrets = sourceSecrets.map((secret) => {
const secretKey = decryptSymmetric128BitHexKeyUTF8({
const secretKey = crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
});
for (const destinationAction of destinationActions) {
@@ -2942,11 +3029,12 @@ export const secretServiceFactory = ({
return {
...secret,
secretKey,
secretValue: decryptSymmetric128BitHexKeyUTF8({
secretValue: crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
})
};
});
@@ -2967,17 +3055,19 @@ export const secretServiceFactory = ({
const decryptedDestinationSecrets = destinationSecretsFromDB.map((secret) => {
return {
...secret,
secretKey: decryptSymmetric128BitHexKeyUTF8({
secretKey: crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
}),
secretValue: decryptSymmetric128BitHexKeyUTF8({
secretValue: crypto.encryption().symmetric().decrypt({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key: botKey
key: botKey,
keySize: SymmetricKeySize.Bits128
})
};
});
@@ -1,7 +1,4 @@
import crypto from "node:crypto";
import { ForbiddenError, subject } from "@casl/ability";
import bcrypt from "bcrypt";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
@@ -10,6 +7,7 @@ import {
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
@@ -87,7 +85,7 @@ export const serviceTokenServiceFactory = ({
throw new NotFoundError({ message: `One or more selected environments not found` });
const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, appCfg.SALT_ROUNDS);
const secretHash = await crypto.hashing().createHash(secret, appCfg.SALT_ROUNDS);
let expiresAt: Date | null = null;
if (expiresIn) {
expiresAt = new Date();
@@ -178,7 +176,7 @@ export const serviceTokenServiceFactory = ({
throw new ForbiddenRequestError({ message: "Service token has expired" });
}
const isMatch = await bcrypt.compare(tokenSecret, serviceToken.secretHash);
const isMatch = await crypto.hashing().compareHash(tokenSecret, serviceToken.secretHash);
if (!isMatch) throw new UnauthorizedError({ message: "Invalid service token" });
await accessTokenQueue.updateServiceTokenStatus(serviceToken.id);
@@ -1,6 +1,4 @@
import bcrypt from "bcrypt";
import { CronJob } from "cron";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod, OrgMembershipRole, TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -12,7 +10,7 @@ import {
overwriteSchema,
validateOverrides
} from "@app/lib/config/env";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
@@ -164,6 +162,7 @@ export const superAdminServiceFactory = ({
const newCfg = await serverCfgDAL.create({
// @ts-expect-error id is kept as fixed for idempotence and to avoid race condition
id: ADMIN_CONFIG_DB_UUID,
fipsEnabled: crypto.isFipsModeEnabled(),
initialized: false,
allowSignUp: true,
defaultAuthOrgId: null
@@ -277,6 +276,7 @@ export const superAdminServiceFactory = ({
const $syncEnvConfig = async () => {
const config = await getEnvOverrides();
overrideEnvConfig(config);
};
@@ -483,6 +483,7 @@ export const superAdminServiceFactory = ({
userAgent
}: TAdminSignUpDTO) => {
const appCfg = getConfig();
const sanitizedEmail = email.trim().toLowerCase();
const existingUser = await userDAL.findOne({ username: sanitizedEmail });
if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exists" });
@@ -497,8 +498,10 @@ export const superAdminServiceFactory = ({
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
});
const hashedPassword = await bcrypt.hash(password, appCfg.BCRYPT_SALT_ROUND);
const { iv, tag, ciphertext, encoding } = infisicalSymmetricEncypt(privateKey);
const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS);
const { iv, tag, ciphertext, encoding } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(privateKey);
const userInfo = await userDAL.transaction(async (tx) => {
const newUser = await userDAL.create(
{
@@ -584,7 +587,7 @@ export const superAdminServiceFactory = ({
},
tx
);
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(password);
const { tag, encoding, ciphertext, iv } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(password);
const encKeys = await generateUserSrpKeys(sanitizedEmail, password);
const userEnc = await userDAL.createUserEncryption(
@@ -666,7 +669,7 @@ export const superAdminServiceFactory = ({
tx
);
const generatedAccessToken = jwt.sign(
const generatedAccessToken = crypto.jwt().sign(
{
identityId: newIdentity.id,
identityAccessTokenId: newToken.id,
@@ -1,4 +1,3 @@
import { createHash, randomUUID } from "crypto";
import { PostHog } from "posthog-node";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -6,6 +5,7 @@ import { InstanceType } from "@app/ee/services/license/license-types";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { crypto } from "@app/lib/crypto/cryptography";
import { logger } from "@app/lib/logger";
import { PostHogEventTypes, TPostHogEvent, TSecretModifiedEvent } from "./telemetry-types";
@@ -42,7 +42,7 @@ export type TTelemetryServiceFactoryDep = {
const getBucketForDistinctId = (distinctId: string): string => {
// Use SHA-256 hash for consistent distribution
const hash = createHash("sha256").update(distinctId).digest("hex");
const hash = crypto.nativeCrypto.createHash("sha256").update(distinctId).digest("hex");
// Take first 8 characters and convert to number for better distribution
const hashNumber = parseInt(hash.substring(0, 8), 16);
@@ -53,7 +53,7 @@ const getBucketForDistinctId = (distinctId: string): string => {
export const createTelemetryEventKey = (event: string, distinctId: string): string => {
const bucketId = getBucketForDistinctId(distinctId);
return `telemetry-event-${event}-${bucketId}-${distinctId}-${randomUUID()}`;
return `telemetry-event-${event}-${bucketId}-${distinctId}-${crypto.nativeCrypto.randomUUID()}`;
};
export const telemetryServiceFactory = ({ keyStore, licenseService }: TTelemetryServiceFactoryDep) => {
+1 -1
View File
@@ -1,3 +1,3 @@
import crypto from "node:crypto";
import { crypto } from "@app/lib/crypto/cryptography";
export const generateRecoveryCode = () => String(crypto.randomInt(10 ** 7, 10 ** 8 - 1));
+11 -7
View File
@@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability";
import { SecretKeyEncoding } from "@app/db/schemas";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
@@ -217,12 +217,16 @@ export const userServiceFactory = ({
if (!user?.serverEncryptedPrivateKey || !user.serverEncryptedPrivateKeyIV || !user.serverEncryptedPrivateKeyTag) {
throw new NotFoundError({ message: `Private key for user with ID '${userId}' not found` });
}
const privateKey = infisicalSymmetricDecrypt({
ciphertext: user.serverEncryptedPrivateKey,
tag: user.serverEncryptedPrivateKeyTag,
iv: user.serverEncryptedPrivateKeyIV,
keyEncoding: user.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
});
const privateKey = crypto
.encryption()
.symmetric()
.decryptWithRootEncryptionKey({
ciphertext: user.serverEncryptedPrivateKey,
tag: user.serverEncryptedPrivateKeyTag,
iv: user.serverEncryptedPrivateKeyIV,
keyEncoding: user.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
});
return privateKey;
};
+5 -4
View File
@@ -1,11 +1,10 @@
import crypto from "node:crypto";
import { AxiosError } from "axios";
import picomatch from "picomatch";
import { TWebhooks } from "@app/db/schemas";
import { EventType, TAuditLogServiceFactory, WebhookTriggeredEvent } from "@app/ee/services/audit-log/audit-log-types";
import { request } from "@app/lib/config/request";
import { crypto } from "@app/lib/crypto/cryptography";
import { NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { ActorType } from "@app/services/auth/auth-type";
@@ -41,9 +40,11 @@ export const triggerWebhookRequest = async (
const headers: Record<string, string> = {};
const payload = { ...data, timestamp: Date.now() };
const { secretKey, url } = decryptWebhookDetails(webhook, decryptor);
if (secretKey) {
const webhookSign = crypto.createHmac("sha256", secretKey).update(JSON.stringify(payload)).digest("hex");
const webhookSign = crypto.nativeCrypto
.createHmac("sha256", secretKey)
.update(JSON.stringify(payload))
.digest("hex");
headers["x-infisical-signature"] = `t=${payload.timestamp};${webhookSign}`;
}