mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 16:52:38 +00:00
Merge branch 'heads/main' into daniel/ms-teams-integration
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
|
||||
name: "AWS IAM User Secret",
|
||||
type: SecretRotation.AwsIamUserSecret,
|
||||
connection: AppConnection.AWS,
|
||||
template: {
|
||||
secretsMapping: {
|
||||
accessKeyId: "AWS_ACCESS_KEY_ID",
|
||||
secretAccessKey: "AWS_SECRET_ACCESS_KEY"
|
||||
}
|
||||
}
|
||||
};
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
import AWS from "aws-sdk";
|
||||
|
||||
import {
|
||||
TAwsIamUserSecretRotationGeneratedCredentials,
|
||||
TAwsIamUserSecretRotationWithConnection
|
||||
} from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret/aws-iam-user-secret-rotation-types";
|
||||
import {
|
||||
TRotationFactory,
|
||||
TRotationFactoryGetSecretsPayload,
|
||||
TRotationFactoryIssueCredentials,
|
||||
TRotationFactoryRevokeCredentials,
|
||||
TRotationFactoryRotateCredentials
|
||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||
import { getAwsConnectionConfig } from "@app/services/app-connection/aws";
|
||||
|
||||
const getCreateDate = (key: AWS.IAM.AccessKeyMetadata): number => {
|
||||
return key.CreateDate ? new Date(key.CreateDate).getTime() : 0;
|
||||
};
|
||||
|
||||
export const awsIamUserSecretRotationFactory: TRotationFactory<
|
||||
TAwsIamUserSecretRotationWithConnection,
|
||||
TAwsIamUserSecretRotationGeneratedCredentials
|
||||
> = (secretRotation) => {
|
||||
const {
|
||||
parameters: { region, userName },
|
||||
connection,
|
||||
secretsMapping
|
||||
} = secretRotation;
|
||||
|
||||
const $rotateClientSecret = async () => {
|
||||
const { credentials } = await getAwsConnectionConfig(connection, region);
|
||||
const iam = new AWS.IAM({ credentials });
|
||||
|
||||
const { AccessKeyMetadata } = await iam.listAccessKeys({ UserName: userName }).promise();
|
||||
|
||||
if (AccessKeyMetadata && AccessKeyMetadata.length > 0) {
|
||||
// Sort keys by creation date (oldest first)
|
||||
const sortedKeys = [...AccessKeyMetadata].sort((a, b) => getCreateDate(a) - getCreateDate(b));
|
||||
|
||||
// If we already have 2 keys, delete the oldest one
|
||||
if (sortedKeys.length >= 2) {
|
||||
const accessId = sortedKeys[0].AccessKeyId || sortedKeys[1].AccessKeyId;
|
||||
if (accessId) {
|
||||
await iam
|
||||
.deleteAccessKey({
|
||||
UserName: userName,
|
||||
AccessKeyId: accessId
|
||||
})
|
||||
.promise();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { AccessKey } = await iam.createAccessKey({ UserName: userName }).promise();
|
||||
|
||||
return {
|
||||
accessKeyId: AccessKey.AccessKeyId,
|
||||
secretAccessKey: AccessKey.SecretAccessKey
|
||||
};
|
||||
};
|
||||
|
||||
const issueCredentials: TRotationFactoryIssueCredentials<TAwsIamUserSecretRotationGeneratedCredentials> = async (
|
||||
callback
|
||||
) => {
|
||||
const credentials = await $rotateClientSecret();
|
||||
|
||||
return callback(credentials);
|
||||
};
|
||||
|
||||
const revokeCredentials: TRotationFactoryRevokeCredentials<TAwsIamUserSecretRotationGeneratedCredentials> = async (
|
||||
generatedCredentials,
|
||||
callback
|
||||
) => {
|
||||
const { credentials } = await getAwsConnectionConfig(connection, region);
|
||||
const iam = new AWS.IAM({ credentials });
|
||||
|
||||
await Promise.all(
|
||||
generatedCredentials.map((generatedCredential) =>
|
||||
iam
|
||||
.deleteAccessKey({
|
||||
UserName: userName,
|
||||
AccessKeyId: generatedCredential.accessKeyId
|
||||
})
|
||||
.promise()
|
||||
)
|
||||
);
|
||||
|
||||
return callback();
|
||||
};
|
||||
|
||||
const rotateCredentials: TRotationFactoryRotateCredentials<TAwsIamUserSecretRotationGeneratedCredentials> = async (
|
||||
_,
|
||||
callback
|
||||
) => {
|
||||
const credentials = await $rotateClientSecret();
|
||||
|
||||
return callback(credentials);
|
||||
};
|
||||
|
||||
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TAwsIamUserSecretRotationGeneratedCredentials> = (
|
||||
generatedCredentials
|
||||
) => {
|
||||
const secrets = [
|
||||
{
|
||||
key: secretsMapping.accessKeyId,
|
||||
value: generatedCredentials.accessKeyId
|
||||
},
|
||||
{
|
||||
key: secretsMapping.secretAccessKey,
|
||||
value: generatedCredentials.secretAccessKey
|
||||
}
|
||||
];
|
||||
|
||||
return secrets;
|
||||
};
|
||||
|
||||
return {
|
||||
issueCredentials,
|
||||
revokeCredentials,
|
||||
rotateCredentials,
|
||||
getSecretsPayload
|
||||
};
|
||||
};
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
import {
|
||||
BaseCreateSecretRotationSchema,
|
||||
BaseSecretRotationSchema,
|
||||
BaseUpdateSecretRotationSchema
|
||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
|
||||
import { SecretRotations } from "@app/lib/api-docs";
|
||||
import { SecretNameSchema } from "@app/server/lib/schemas";
|
||||
import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const AwsIamUserSecretRotationGeneratedCredentialsSchema = z
|
||||
.object({
|
||||
accessKeyId: z.string(),
|
||||
secretAccessKey: z.string()
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.max(2);
|
||||
|
||||
const AwsIamUserSecretRotationParametersSchema = z.object({
|
||||
userName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Client Name Required")
|
||||
.describe(SecretRotations.PARAMETERS.AWS_IAM_USER_SECRET.userName),
|
||||
region: z.nativeEnum(AWSRegion).describe(SecretRotations.PARAMETERS.AWS_IAM_USER_SECRET.region).optional()
|
||||
});
|
||||
|
||||
const AwsIamUserSecretRotationSecretsMappingSchema = z.object({
|
||||
accessKeyId: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AWS_IAM_USER_SECRET.accessKeyId),
|
||||
secretAccessKey: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AWS_IAM_USER_SECRET.secretAccessKey)
|
||||
});
|
||||
|
||||
export const AwsIamUserSecretRotationTemplateSchema = z.object({
|
||||
secretsMapping: z.object({
|
||||
accessKeyId: z.string(),
|
||||
secretAccessKey: z.string()
|
||||
})
|
||||
});
|
||||
|
||||
export const AwsIamUserSecretRotationSchema = BaseSecretRotationSchema(SecretRotation.AwsIamUserSecret).extend({
|
||||
type: z.literal(SecretRotation.AwsIamUserSecret),
|
||||
parameters: AwsIamUserSecretRotationParametersSchema,
|
||||
secretsMapping: AwsIamUserSecretRotationSecretsMappingSchema
|
||||
});
|
||||
|
||||
export const CreateAwsIamUserSecretRotationSchema = BaseCreateSecretRotationSchema(
|
||||
SecretRotation.AwsIamUserSecret
|
||||
).extend({
|
||||
parameters: AwsIamUserSecretRotationParametersSchema,
|
||||
secretsMapping: AwsIamUserSecretRotationSecretsMappingSchema
|
||||
});
|
||||
|
||||
export const UpdateAwsIamUserSecretRotationSchema = BaseUpdateSecretRotationSchema(
|
||||
SecretRotation.AwsIamUserSecret
|
||||
).extend({
|
||||
parameters: AwsIamUserSecretRotationParametersSchema.optional(),
|
||||
secretsMapping: AwsIamUserSecretRotationSecretsMappingSchema.optional()
|
||||
});
|
||||
|
||||
export const AwsIamUserSecretRotationListItemSchema = z.object({
|
||||
name: z.literal("AWS IAM User Secret"),
|
||||
connection: z.literal(AppConnection.AWS),
|
||||
type: z.literal(SecretRotation.AwsIamUserSecret),
|
||||
template: AwsIamUserSecretRotationTemplateSchema
|
||||
});
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { TAwsConnection } from "@app/services/app-connection/aws";
|
||||
|
||||
import {
|
||||
AwsIamUserSecretRotationGeneratedCredentialsSchema,
|
||||
AwsIamUserSecretRotationListItemSchema,
|
||||
AwsIamUserSecretRotationSchema,
|
||||
CreateAwsIamUserSecretRotationSchema
|
||||
} from "./aws-iam-user-secret-rotation-schemas";
|
||||
|
||||
export type TAwsIamUserSecretRotation = z.infer<typeof AwsIamUserSecretRotationSchema>;
|
||||
|
||||
export type TAwsIamUserSecretRotationInput = z.infer<typeof CreateAwsIamUserSecretRotationSchema>;
|
||||
|
||||
export type TAwsIamUserSecretRotationListItem = z.infer<typeof AwsIamUserSecretRotationListItemSchema>;
|
||||
|
||||
export type TAwsIamUserSecretRotationWithConnection = TAwsIamUserSecretRotation & {
|
||||
connection: TAwsConnection;
|
||||
};
|
||||
|
||||
export type TAwsIamUserSecretRotationGeneratedCredentials = z.infer<
|
||||
typeof AwsIamUserSecretRotationGeneratedCredentialsSchema
|
||||
>;
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./aws-iam-user-secret-rotation-constants";
|
||||
export * from "./aws-iam-user-secret-rotation-schemas";
|
||||
export * from "./aws-iam-user-secret-rotation-types";
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./ldap-password-rotation-constants";
|
||||
export * from "./ldap-password-rotation-schemas";
|
||||
export * from "./ldap-password-rotation-types";
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const LDAP_PASSWORD_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
|
||||
name: "LDAP Password",
|
||||
type: SecretRotation.LdapPassword,
|
||||
connection: AppConnection.LDAP,
|
||||
template: {
|
||||
secretsMapping: {
|
||||
dn: "LDAP_DN",
|
||||
password: "LDAP_PASSWORD"
|
||||
}
|
||||
}
|
||||
};
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
import ldap from "ldapjs";
|
||||
|
||||
import {
|
||||
TRotationFactory,
|
||||
TRotationFactoryGetSecretsPayload,
|
||||
TRotationFactoryIssueCredentials,
|
||||
TRotationFactoryRevokeCredentials,
|
||||
TRotationFactoryRotateCredentials
|
||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns";
|
||||
import { getLdapConnectionClient, LdapProvider, TLdapConnection } from "@app/services/app-connection/ldap";
|
||||
|
||||
import { generatePassword } from "../shared/utils";
|
||||
import {
|
||||
TLdapPasswordRotationGeneratedCredentials,
|
||||
TLdapPasswordRotationWithConnection
|
||||
} from "./ldap-password-rotation-types";
|
||||
|
||||
const getEncodedPassword = (password: string) => Buffer.from(`"${password}"`, "utf16le");
|
||||
|
||||
export const ldapPasswordRotationFactory: TRotationFactory<
|
||||
TLdapPasswordRotationWithConnection,
|
||||
TLdapPasswordRotationGeneratedCredentials
|
||||
> = (secretRotation, appConnectionDAL, kmsService) => {
|
||||
const {
|
||||
connection,
|
||||
parameters: { dn, passwordRequirements },
|
||||
secretsMapping
|
||||
} = secretRotation;
|
||||
|
||||
const $verifyCredentials = async (credentials: Pick<TLdapConnection["credentials"], "dn" | "password">) => {
|
||||
try {
|
||||
const client = await getLdapConnectionClient({ ...connection.credentials, ...credentials });
|
||||
|
||||
client.unbind();
|
||||
client.destroy();
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to verify credentials - ${(error as Error).message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const $rotatePassword = async () => {
|
||||
const { credentials, orgId } = connection;
|
||||
|
||||
if (!credentials.url.startsWith("ldaps")) throw new Error("Password Rotation requires an LDAPS connection");
|
||||
|
||||
const client = await getLdapConnectionClient(credentials);
|
||||
const isPersonalRotation = credentials.dn === dn;
|
||||
|
||||
const password = generatePassword(passwordRequirements);
|
||||
|
||||
let changes: ldap.Change[] | ldap.Change;
|
||||
|
||||
switch (credentials.provider) {
|
||||
case LdapProvider.ActiveDirectory:
|
||||
{
|
||||
const encodedPassword = getEncodedPassword(password);
|
||||
|
||||
// service account vs personal password rotation require different changes
|
||||
if (isPersonalRotation) {
|
||||
const currentEncodedPassword = getEncodedPassword(credentials.password);
|
||||
|
||||
changes = [
|
||||
new ldap.Change({
|
||||
operation: "delete",
|
||||
modification: {
|
||||
type: "unicodePwd",
|
||||
values: [currentEncodedPassword]
|
||||
}
|
||||
}),
|
||||
new ldap.Change({
|
||||
operation: "add",
|
||||
modification: {
|
||||
type: "unicodePwd",
|
||||
values: [encodedPassword]
|
||||
}
|
||||
})
|
||||
];
|
||||
} else {
|
||||
changes = new ldap.Change({
|
||||
operation: "replace",
|
||||
modification: {
|
||||
type: "unicodePwd",
|
||||
values: [encodedPassword]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled provider: ${credentials.provider as LdapProvider}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
client.modify(dn, changes, (err) => {
|
||||
if (err) {
|
||||
logger.error(err, "LDAP Password Rotation Failed");
|
||||
reject(new Error(`Provider Modify Error: ${err.message}`));
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
client.unbind();
|
||||
client.destroy();
|
||||
}
|
||||
|
||||
await $verifyCredentials({ dn, password });
|
||||
|
||||
if (isPersonalRotation) {
|
||||
const updatedCredentials: TLdapConnection["credentials"] = {
|
||||
...credentials,
|
||||
password
|
||||
};
|
||||
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedCredentials,
|
||||
orgId,
|
||||
kmsService
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(connection.id, { encryptedCredentials });
|
||||
}
|
||||
|
||||
return { dn, password };
|
||||
};
|
||||
|
||||
const issueCredentials: TRotationFactoryIssueCredentials<TLdapPasswordRotationGeneratedCredentials> = async (
|
||||
callback
|
||||
) => {
|
||||
const credentials = await $rotatePassword();
|
||||
|
||||
return callback(credentials);
|
||||
};
|
||||
|
||||
const revokeCredentials: TRotationFactoryRevokeCredentials<TLdapPasswordRotationGeneratedCredentials> = async (
|
||||
_,
|
||||
callback
|
||||
) => {
|
||||
// we just rotate to a new password, essentially revoking old credentials
|
||||
await $rotatePassword();
|
||||
|
||||
return callback();
|
||||
};
|
||||
|
||||
const rotateCredentials: TRotationFactoryRotateCredentials<TLdapPasswordRotationGeneratedCredentials> = async (
|
||||
_,
|
||||
callback
|
||||
) => {
|
||||
const credentials = await $rotatePassword();
|
||||
|
||||
return callback(credentials);
|
||||
};
|
||||
|
||||
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TLdapPasswordRotationGeneratedCredentials> = (
|
||||
generatedCredentials
|
||||
) => {
|
||||
const secrets = [
|
||||
{
|
||||
key: secretsMapping.dn,
|
||||
value: generatedCredentials.dn
|
||||
},
|
||||
{
|
||||
key: secretsMapping.password,
|
||||
value: generatedCredentials.password
|
||||
}
|
||||
];
|
||||
|
||||
return secrets;
|
||||
};
|
||||
|
||||
return {
|
||||
issueCredentials,
|
||||
revokeCredentials,
|
||||
rotateCredentials,
|
||||
getSecretsPayload
|
||||
};
|
||||
};
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import RE2 from "re2";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
import {
|
||||
BaseCreateSecretRotationSchema,
|
||||
BaseSecretRotationSchema,
|
||||
BaseUpdateSecretRotationSchema
|
||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
|
||||
import { PasswordRequirementsSchema } from "@app/ee/services/secret-rotation-v2/shared/general";
|
||||
import { SecretRotations } from "@app/lib/api-docs";
|
||||
import { DistinguishedNameRegex } from "@app/lib/regex";
|
||||
import { SecretNameSchema } from "@app/server/lib/schemas";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const LdapPasswordRotationGeneratedCredentialsSchema = z
|
||||
.object({
|
||||
dn: z.string(),
|
||||
password: z.string()
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.max(2);
|
||||
|
||||
const LdapPasswordRotationParametersSchema = z.object({
|
||||
dn: z
|
||||
.string()
|
||||
.trim()
|
||||
.regex(new RE2(DistinguishedNameRegex), "Invalid DN format, ie; CN=user,OU=users,DC=example,DC=com")
|
||||
.min(1, "Distinguished Name (DN) Required")
|
||||
.describe(SecretRotations.PARAMETERS.LDAP_PASSWORD.dn),
|
||||
passwordRequirements: PasswordRequirementsSchema.optional()
|
||||
});
|
||||
|
||||
const LdapPasswordRotationSecretsMappingSchema = z.object({
|
||||
dn: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.LDAP_PASSWORD.dn),
|
||||
password: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.LDAP_PASSWORD.password)
|
||||
});
|
||||
|
||||
export const LdapPasswordRotationTemplateSchema = z.object({
|
||||
secretsMapping: z.object({
|
||||
dn: z.string(),
|
||||
password: z.string()
|
||||
})
|
||||
});
|
||||
|
||||
export const LdapPasswordRotationSchema = BaseSecretRotationSchema(SecretRotation.LdapPassword).extend({
|
||||
type: z.literal(SecretRotation.LdapPassword),
|
||||
parameters: LdapPasswordRotationParametersSchema,
|
||||
secretsMapping: LdapPasswordRotationSecretsMappingSchema
|
||||
});
|
||||
|
||||
export const CreateLdapPasswordRotationSchema = BaseCreateSecretRotationSchema(SecretRotation.LdapPassword).extend({
|
||||
parameters: LdapPasswordRotationParametersSchema,
|
||||
secretsMapping: LdapPasswordRotationSecretsMappingSchema
|
||||
});
|
||||
|
||||
export const UpdateLdapPasswordRotationSchema = BaseUpdateSecretRotationSchema(SecretRotation.LdapPassword).extend({
|
||||
parameters: LdapPasswordRotationParametersSchema.optional(),
|
||||
secretsMapping: LdapPasswordRotationSecretsMappingSchema.optional()
|
||||
});
|
||||
|
||||
export const LdapPasswordRotationListItemSchema = z.object({
|
||||
name: z.literal("LDAP Password"),
|
||||
connection: z.literal(AppConnection.LDAP),
|
||||
type: z.literal(SecretRotation.LdapPassword),
|
||||
template: LdapPasswordRotationTemplateSchema
|
||||
});
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { TLdapConnection } from "@app/services/app-connection/ldap";
|
||||
|
||||
import {
|
||||
CreateLdapPasswordRotationSchema,
|
||||
LdapPasswordRotationGeneratedCredentialsSchema,
|
||||
LdapPasswordRotationListItemSchema,
|
||||
LdapPasswordRotationSchema
|
||||
} from "./ldap-password-rotation-schemas";
|
||||
|
||||
export type TLdapPasswordRotation = z.infer<typeof LdapPasswordRotationSchema>;
|
||||
|
||||
export type TLdapPasswordRotationInput = z.infer<typeof CreateLdapPasswordRotationSchema>;
|
||||
|
||||
export type TLdapPasswordRotationListItem = z.infer<typeof LdapPasswordRotationListItemSchema>;
|
||||
|
||||
export type TLdapPasswordRotationWithConnection = TLdapPasswordRotation & {
|
||||
connection: TLdapConnection;
|
||||
};
|
||||
|
||||
export type TLdapPasswordRotationGeneratedCredentials = z.infer<typeof LdapPasswordRotationGeneratedCredentialsSchema>;
|
||||
@@ -1,7 +1,9 @@
|
||||
export enum SecretRotation {
|
||||
PostgresCredentials = "postgres-credentials",
|
||||
MsSqlCredentials = "mssql-credentials",
|
||||
Auth0ClientSecret = "auth0-client-secret"
|
||||
Auth0ClientSecret = "auth0-client-secret",
|
||||
LdapPassword = "ldap-password",
|
||||
AwsIamUserSecret = "aws-iam-user-secret"
|
||||
}
|
||||
|
||||
export enum SecretRotationStatus {
|
||||
|
||||
@@ -4,6 +4,8 @@ import { getConfig } from "@app/lib/config/env";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
|
||||
import { AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./auth0-client-secret";
|
||||
import { AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION } from "./aws-iam-user-secret";
|
||||
import { LDAP_PASSWORD_ROTATION_LIST_OPTION } from "./ldap-password";
|
||||
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
||||
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
||||
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
||||
@@ -18,7 +20,9 @@ import {
|
||||
const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2ListItem> = {
|
||||
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION
|
||||
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.LdapPassword]: LDAP_PASSWORD_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION
|
||||
};
|
||||
|
||||
export const listSecretRotationOptions = () => {
|
||||
|
||||
@@ -3,12 +3,16 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
|
||||
|
||||
export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
||||
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
||||
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Sever Credentials",
|
||||
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret"
|
||||
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
||||
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
||||
[SecretRotation.LdapPassword]: "LDAP Password",
|
||||
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret"
|
||||
};
|
||||
|
||||
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
||||
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
||||
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
||||
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0
|
||||
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
||||
[SecretRotation.LdapPassword]: AppConnection.LDAP,
|
||||
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { auth0ClientSecretRotationFactory } from "@app/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-fns";
|
||||
import { ldapPasswordRotationFactory } from "@app/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns";
|
||||
import { SecretRotation, SecretRotationStatus } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
import {
|
||||
calculateNextRotationAt,
|
||||
@@ -77,6 +78,7 @@ import {
|
||||
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||
|
||||
import { awsIamUserSecretRotationFactory } from "./aws-iam-user-secret/aws-iam-user-secret-rotation-fns";
|
||||
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
||||
|
||||
export type TSecretRotationV2ServiceFactoryDep = {
|
||||
@@ -114,7 +116,9 @@ type TRotationFactoryImplementation = TRotationFactory<
|
||||
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
|
||||
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation
|
||||
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.LdapPassword]: ldapPasswordRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation
|
||||
};
|
||||
|
||||
export const secretRotationV2ServiceFactory = ({
|
||||
@@ -449,6 +453,18 @@ export const secretRotationV2ServiceFactory = ({
|
||||
kmsService
|
||||
);
|
||||
|
||||
// even though we have a db constraint we want to check before any rotation of credentials is attempted
|
||||
// to prevent creation failure after external credentials have been modified
|
||||
const conflictingRotation = await secretRotationV2DAL.findOne({
|
||||
name: payload.name,
|
||||
folderId: folder.id
|
||||
});
|
||||
|
||||
if (conflictingRotation)
|
||||
throw new BadRequestError({
|
||||
message: `A Secret Rotation with the name "${payload.name}" already exists at the secret path "${secretPath}"`
|
||||
});
|
||||
|
||||
try {
|
||||
const currentTime = new Date();
|
||||
|
||||
|
||||
@@ -12,6 +12,20 @@ import {
|
||||
TAuth0ClientSecretRotationListItem,
|
||||
TAuth0ClientSecretRotationWithConnection
|
||||
} from "./auth0-client-secret";
|
||||
import {
|
||||
TAwsIamUserSecretRotation,
|
||||
TAwsIamUserSecretRotationGeneratedCredentials,
|
||||
TAwsIamUserSecretRotationInput,
|
||||
TAwsIamUserSecretRotationListItem,
|
||||
TAwsIamUserSecretRotationWithConnection
|
||||
} from "./aws-iam-user-secret";
|
||||
import {
|
||||
TLdapPasswordRotation,
|
||||
TLdapPasswordRotationGeneratedCredentials,
|
||||
TLdapPasswordRotationInput,
|
||||
TLdapPasswordRotationListItem,
|
||||
TLdapPasswordRotationWithConnection
|
||||
} from "./ldap-password";
|
||||
import {
|
||||
TMsSqlCredentialsRotation,
|
||||
TMsSqlCredentialsRotationInput,
|
||||
@@ -27,26 +41,39 @@ import {
|
||||
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
||||
import { SecretRotation } from "./secret-rotation-v2-enums";
|
||||
|
||||
export type TSecretRotationV2 = TPostgresCredentialsRotation | TMsSqlCredentialsRotation | TAuth0ClientSecretRotation;
|
||||
export type TSecretRotationV2 =
|
||||
| TPostgresCredentialsRotation
|
||||
| TMsSqlCredentialsRotation
|
||||
| TAuth0ClientSecretRotation
|
||||
| TLdapPasswordRotation
|
||||
| TAwsIamUserSecretRotation;
|
||||
|
||||
export type TSecretRotationV2WithConnection =
|
||||
| TPostgresCredentialsRotationWithConnection
|
||||
| TMsSqlCredentialsRotationWithConnection
|
||||
| TAuth0ClientSecretRotationWithConnection;
|
||||
| TAuth0ClientSecretRotationWithConnection
|
||||
| TLdapPasswordRotationWithConnection
|
||||
| TAwsIamUserSecretRotationWithConnection;
|
||||
|
||||
export type TSecretRotationV2GeneratedCredentials =
|
||||
| TSqlCredentialsRotationGeneratedCredentials
|
||||
| TAuth0ClientSecretRotationGeneratedCredentials;
|
||||
| TAuth0ClientSecretRotationGeneratedCredentials
|
||||
| TLdapPasswordRotationGeneratedCredentials
|
||||
| TAwsIamUserSecretRotationGeneratedCredentials;
|
||||
|
||||
export type TSecretRotationV2Input =
|
||||
| TPostgresCredentialsRotationInput
|
||||
| TMsSqlCredentialsRotationInput
|
||||
| TAuth0ClientSecretRotationInput;
|
||||
| TAuth0ClientSecretRotationInput
|
||||
| TLdapPasswordRotationInput
|
||||
| TAwsIamUserSecretRotationInput;
|
||||
|
||||
export type TSecretRotationV2ListItem =
|
||||
| TPostgresCredentialsRotationListItem
|
||||
| TMsSqlCredentialsRotationListItem
|
||||
| TAuth0ClientSecretRotationListItem;
|
||||
| TAuth0ClientSecretRotationListItem
|
||||
| TLdapPasswordRotationListItem
|
||||
| TAwsIamUserSecretRotationListItem;
|
||||
|
||||
export type TSecretRotationV2Raw = NonNullable<Awaited<ReturnType<TSecretRotationV2DALFactory["findById"]>>>;
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
||||
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||
|
||||
import { AwsIamUserSecretRotationSchema } from "./aws-iam-user-secret";
|
||||
|
||||
export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
||||
PostgresCredentialsRotationSchema,
|
||||
MsSqlCredentialsRotationSchema,
|
||||
Auth0ClientSecretRotationSchema
|
||||
Auth0ClientSecretRotationSchema,
|
||||
LdapPasswordRotationSchema,
|
||||
AwsIamUserSecretRotationSchema
|
||||
]);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./password-requirements-schema";
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import RE2 from "re2";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretRotations } from "@app/lib/api-docs";
|
||||
|
||||
export const PasswordRequirementsSchema = z
|
||||
.object({
|
||||
length: z
|
||||
.number()
|
||||
.min(1, "Password length must be a positive number")
|
||||
.max(250, "Password length must be less than 250")
|
||||
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.length),
|
||||
required: z.object({
|
||||
digits: z
|
||||
.number()
|
||||
.min(0, "Digit count must be non-negative")
|
||||
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.required.digits),
|
||||
lowercase: z
|
||||
.number()
|
||||
.min(0, "Lowercase count must be non-negative")
|
||||
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.required.lowercase),
|
||||
uppercase: z
|
||||
.number()
|
||||
.min(0, "Uppercase count must be non-negative")
|
||||
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.required.uppercase),
|
||||
symbols: z
|
||||
.number()
|
||||
.min(0, "Symbol count must be non-negative")
|
||||
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.required.symbols)
|
||||
}),
|
||||
allowedSymbols: z
|
||||
.string()
|
||||
.regex(new RE2("[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?~]"), "Invalid symbols")
|
||||
.optional()
|
||||
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.allowedSymbols)
|
||||
})
|
||||
.refine((data) => {
|
||||
return Object.values(data.required).some((count) => count > 0);
|
||||
}, "At least one character type must be required")
|
||||
.refine((data) => {
|
||||
const total = Object.values(data.required).reduce((sum, count) => sum + count, 0);
|
||||
return total <= data.length;
|
||||
}, "Sum of required characters cannot exceed the total length")
|
||||
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.base);
|
||||
@@ -1,6 +1,17 @@
|
||||
import { randomInt } from "crypto";
|
||||
|
||||
const DEFAULT_PASSWORD_REQUIREMENTS = {
|
||||
type TPasswordRequirements = {
|
||||
length: number;
|
||||
required: {
|
||||
lowercase: number;
|
||||
uppercase: number;
|
||||
digits: number;
|
||||
symbols: number;
|
||||
};
|
||||
allowedSymbols?: string;
|
||||
};
|
||||
|
||||
const DEFAULT_PASSWORD_REQUIREMENTS: TPasswordRequirements = {
|
||||
length: 48,
|
||||
required: {
|
||||
lowercase: 1,
|
||||
@@ -11,9 +22,9 @@ const DEFAULT_PASSWORD_REQUIREMENTS = {
|
||||
allowedSymbols: "-_.~!*"
|
||||
};
|
||||
|
||||
export const generatePassword = () => {
|
||||
export const generatePassword = (passwordRequirements?: TPasswordRequirements) => {
|
||||
try {
|
||||
const { length, required, allowedSymbols } = DEFAULT_PASSWORD_REQUIREMENTS;
|
||||
const { length, required, allowedSymbols } = passwordRequirements ?? DEFAULT_PASSWORD_REQUIREMENTS;
|
||||
|
||||
const chars = {
|
||||
lowercase: "abcdefghijklmnopqrstuvwxyz",
|
||||
|
||||
Reference in New Issue
Block a user