mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 11:29:42 +00:00
Merge branch 'main' into feat/secretsBatchUI
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
+5
-5
@@ -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({
|
||||
|
||||
+9
-9
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,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));
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user