mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-20 01:48:03 +00:00
Compare commits
39 Commits
ssh-non-in
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
632572f7c3 | |||
a524690d01 | |||
f93edbb37f | |||
fa8154ecdd | |||
d977092502 | |||
cceb29b93a | |||
02b44365f1 | |||
b506393765 | |||
204269a10d | |||
cf1f83aaa3 | |||
7894181234 | |||
04b20ed11d | |||
7a4a877e39 | |||
8f670bde88 | |||
ff9011c899 | |||
57c96abe03 | |||
7699705334 | |||
7c49f6e302 | |||
b329b5aa4b | |||
0882c181d0 | |||
8672dd641a | |||
e0dc2dd6d8 | |||
4973447676 | |||
bd2e2b7931 | |||
aa893a40a9 | |||
23df78eff8 | |||
84255d1b26 | |||
3a6b2a593b | |||
d3ee30f5e6 | |||
33dea34061 | |||
da68073e86 | |||
7bd312a287 | |||
d61e6752d6 | |||
636aee2ea9 | |||
5819b8c576 | |||
b85809293c | |||
f143d8c358 | |||
2e3330bf69 | |||
9032bbe514 |
@ -0,0 +1,19 @@
|
|||||||
|
import {
|
||||||
|
AwsIamUserSecretRotationGeneratedCredentialsSchema,
|
||||||
|
AwsIamUserSecretRotationSchema,
|
||||||
|
CreateAwsIamUserSecretRotationSchema,
|
||||||
|
UpdateAwsIamUserSecretRotationSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret";
|
||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
|
||||||
|
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
|
||||||
|
|
||||||
|
export const registerAwsIamUserSecretRotationRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSecretRotationEndpoints({
|
||||||
|
type: SecretRotation.AwsIamUserSecret,
|
||||||
|
server,
|
||||||
|
responseSchema: AwsIamUserSecretRotationSchema,
|
||||||
|
createSchema: CreateAwsIamUserSecretRotationSchema,
|
||||||
|
updateSchema: UpdateAwsIamUserSecretRotationSchema,
|
||||||
|
generatedCredentialsSchema: AwsIamUserSecretRotationGeneratedCredentialsSchema
|
||||||
|
});
|
@ -1,6 +1,8 @@
|
|||||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
|
||||||
import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router";
|
import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router";
|
||||||
|
import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-rotation-router";
|
||||||
|
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
|
||||||
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
||||||
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
||||||
|
|
||||||
@ -12,5 +14,7 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
|
|||||||
> = {
|
> = {
|
||||||
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
||||||
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
||||||
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter
|
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
||||||
|
[SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter,
|
||||||
|
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
import {
|
||||||
|
CreateLdapPasswordRotationSchema,
|
||||||
|
LdapPasswordRotationGeneratedCredentialsSchema,
|
||||||
|
LdapPasswordRotationSchema,
|
||||||
|
UpdateLdapPasswordRotationSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
|
||||||
|
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
|
||||||
|
|
||||||
|
export const registerLdapPasswordRotationRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSecretRotationEndpoints({
|
||||||
|
type: SecretRotation.LdapPassword,
|
||||||
|
server,
|
||||||
|
responseSchema: LdapPasswordRotationSchema,
|
||||||
|
createSchema: CreateLdapPasswordRotationSchema,
|
||||||
|
updateSchema: UpdateLdapPasswordRotationSchema,
|
||||||
|
generatedCredentialsSchema: LdapPasswordRotationGeneratedCredentialsSchema
|
||||||
|
});
|
@ -2,6 +2,8 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { Auth0ClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
import { Auth0ClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
||||||
|
import { AwsIamUserSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret";
|
||||||
|
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||||
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
||||||
@ -13,7 +15,9 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
|||||||
const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
||||||
PostgresCredentialsRotationListItemSchema,
|
PostgresCredentialsRotationListItemSchema,
|
||||||
MsSqlCredentialsRotationListItemSchema,
|
MsSqlCredentialsRotationListItemSchema,
|
||||||
Auth0ClientSecretRotationListItemSchema
|
Auth0ClientSecretRotationListItemSchema,
|
||||||
|
LdapPasswordRotationListItemSchema,
|
||||||
|
AwsIamUserSecretRotationListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -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
|
||||||
|
};
|
||||||
|
};
|
@ -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
|
||||||
|
});
|
@ -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";
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -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
|
||||||
|
};
|
||||||
|
};
|
@ -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
|
||||||
|
});
|
@ -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 {
|
export enum SecretRotation {
|
||||||
PostgresCredentials = "postgres-credentials",
|
PostgresCredentials = "postgres-credentials",
|
||||||
MsSqlCredentials = "mssql-credentials",
|
MsSqlCredentials = "mssql-credentials",
|
||||||
Auth0ClientSecret = "auth0-client-secret"
|
Auth0ClientSecret = "auth0-client-secret",
|
||||||
|
LdapPassword = "ldap-password",
|
||||||
|
AwsIamUserSecret = "aws-iam-user-secret"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretRotationStatus {
|
export enum SecretRotationStatus {
|
||||||
|
@ -4,6 +4,8 @@ import { getConfig } from "@app/lib/config/env";
|
|||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
|
||||||
import { AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./auth0-client-secret";
|
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 { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
||||||
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
||||||
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
||||||
@ -18,7 +20,9 @@ import {
|
|||||||
const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2ListItem> = {
|
const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2ListItem> = {
|
||||||
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.MsSqlCredentials]: MSSQL_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 = () => {
|
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> = {
|
export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
||||||
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
||||||
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Sever Credentials",
|
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
||||||
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret"
|
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
||||||
|
[SecretRotation.LdapPassword]: "LDAP Password",
|
||||||
|
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
||||||
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
||||||
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
[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
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} 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 { 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 { SecretRotation, SecretRotationStatus } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
import {
|
import {
|
||||||
calculateNextRotationAt,
|
calculateNextRotationAt,
|
||||||
@ -77,6 +78,7 @@ import {
|
|||||||
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-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";
|
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
||||||
|
|
||||||
export type TSecretRotationV2ServiceFactoryDep = {
|
export type TSecretRotationV2ServiceFactoryDep = {
|
||||||
@ -114,7 +116,9 @@ type TRotationFactoryImplementation = TRotationFactory<
|
|||||||
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
|
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
|
||||||
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.MsSqlCredentials]: 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 = ({
|
export const secretRotationV2ServiceFactory = ({
|
||||||
@ -449,6 +453,18 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
kmsService
|
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 {
|
try {
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
|
|
||||||
|
@ -12,6 +12,20 @@ import {
|
|||||||
TAuth0ClientSecretRotationListItem,
|
TAuth0ClientSecretRotationListItem,
|
||||||
TAuth0ClientSecretRotationWithConnection
|
TAuth0ClientSecretRotationWithConnection
|
||||||
} from "./auth0-client-secret";
|
} 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 {
|
import {
|
||||||
TMsSqlCredentialsRotation,
|
TMsSqlCredentialsRotation,
|
||||||
TMsSqlCredentialsRotationInput,
|
TMsSqlCredentialsRotationInput,
|
||||||
@ -27,26 +41,39 @@ import {
|
|||||||
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
||||||
import { SecretRotation } from "./secret-rotation-v2-enums";
|
import { SecretRotation } from "./secret-rotation-v2-enums";
|
||||||
|
|
||||||
export type TSecretRotationV2 = TPostgresCredentialsRotation | TMsSqlCredentialsRotation | TAuth0ClientSecretRotation;
|
export type TSecretRotationV2 =
|
||||||
|
| TPostgresCredentialsRotation
|
||||||
|
| TMsSqlCredentialsRotation
|
||||||
|
| TAuth0ClientSecretRotation
|
||||||
|
| TLdapPasswordRotation
|
||||||
|
| TAwsIamUserSecretRotation;
|
||||||
|
|
||||||
export type TSecretRotationV2WithConnection =
|
export type TSecretRotationV2WithConnection =
|
||||||
| TPostgresCredentialsRotationWithConnection
|
| TPostgresCredentialsRotationWithConnection
|
||||||
| TMsSqlCredentialsRotationWithConnection
|
| TMsSqlCredentialsRotationWithConnection
|
||||||
| TAuth0ClientSecretRotationWithConnection;
|
| TAuth0ClientSecretRotationWithConnection
|
||||||
|
| TLdapPasswordRotationWithConnection
|
||||||
|
| TAwsIamUserSecretRotationWithConnection;
|
||||||
|
|
||||||
export type TSecretRotationV2GeneratedCredentials =
|
export type TSecretRotationV2GeneratedCredentials =
|
||||||
| TSqlCredentialsRotationGeneratedCredentials
|
| TSqlCredentialsRotationGeneratedCredentials
|
||||||
| TAuth0ClientSecretRotationGeneratedCredentials;
|
| TAuth0ClientSecretRotationGeneratedCredentials
|
||||||
|
| TLdapPasswordRotationGeneratedCredentials
|
||||||
|
| TAwsIamUserSecretRotationGeneratedCredentials;
|
||||||
|
|
||||||
export type TSecretRotationV2Input =
|
export type TSecretRotationV2Input =
|
||||||
| TPostgresCredentialsRotationInput
|
| TPostgresCredentialsRotationInput
|
||||||
| TMsSqlCredentialsRotationInput
|
| TMsSqlCredentialsRotationInput
|
||||||
| TAuth0ClientSecretRotationInput;
|
| TAuth0ClientSecretRotationInput
|
||||||
|
| TLdapPasswordRotationInput
|
||||||
|
| TAwsIamUserSecretRotationInput;
|
||||||
|
|
||||||
export type TSecretRotationV2ListItem =
|
export type TSecretRotationV2ListItem =
|
||||||
| TPostgresCredentialsRotationListItem
|
| TPostgresCredentialsRotationListItem
|
||||||
| TMsSqlCredentialsRotationListItem
|
| TMsSqlCredentialsRotationListItem
|
||||||
| TAuth0ClientSecretRotationListItem;
|
| TAuth0ClientSecretRotationListItem
|
||||||
|
| TLdapPasswordRotationListItem
|
||||||
|
| TAwsIamUserSecretRotationListItem;
|
||||||
|
|
||||||
export type TSecretRotationV2Raw = NonNullable<Awaited<ReturnType<TSecretRotationV2DALFactory["findById"]>>>;
|
export type TSecretRotationV2Raw = NonNullable<Awaited<ReturnType<TSecretRotationV2DALFactory["findById"]>>>;
|
||||||
|
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
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 { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-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", [
|
export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
||||||
PostgresCredentialsRotationSchema,
|
PostgresCredentialsRotationSchema,
|
||||||
MsSqlCredentialsRotationSchema,
|
MsSqlCredentialsRotationSchema,
|
||||||
Auth0ClientSecretRotationSchema
|
Auth0ClientSecretRotationSchema,
|
||||||
|
LdapPasswordRotationSchema,
|
||||||
|
AwsIamUserSecretRotationSchema
|
||||||
]);
|
]);
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export * from "./password-requirements-schema";
|
@ -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";
|
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,
|
length: 48,
|
||||||
required: {
|
required: {
|
||||||
lowercase: 1,
|
lowercase: 1,
|
||||||
@ -11,9 +22,9 @@ const DEFAULT_PASSWORD_REQUIREMENTS = {
|
|||||||
allowedSymbols: "-_.~!*"
|
allowedSymbols: "-_.~!*"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generatePassword = () => {
|
export const generatePassword = (passwordRequirements?: TPasswordRequirements) => {
|
||||||
try {
|
try {
|
||||||
const { length, required, allowedSymbols } = DEFAULT_PASSWORD_REQUIREMENTS;
|
const { length, required, allowedSymbols } = passwordRequirements ?? DEFAULT_PASSWORD_REQUIREMENTS;
|
||||||
|
|
||||||
const chars = {
|
const chars = {
|
||||||
lowercase: "abcdefghijklmnopqrstuvwxyz",
|
lowercase: "abcdefghijklmnopqrstuvwxyz",
|
||||||
|
@ -1857,6 +1857,20 @@ export const AppConnections = {
|
|||||||
WINDMILL: {
|
WINDMILL: {
|
||||||
instanceUrl: "The Windmill instance URL to connect with (defaults to https://app.windmill.dev).",
|
instanceUrl: "The Windmill instance URL to connect with (defaults to https://app.windmill.dev).",
|
||||||
accessToken: "The access token to use to connect with Windmill."
|
accessToken: "The access token to use to connect with Windmill."
|
||||||
|
},
|
||||||
|
LDAP: {
|
||||||
|
provider: "The type of LDAP provider. Determines provider-specific behaviors.",
|
||||||
|
url: "The LDAP/LDAPS URL to connect to (e.g., 'ldap://domain-or-ip:389' or 'ldaps://domain-or-ip:636').",
|
||||||
|
dn: "The Distinguished Name (DN) of the principal to bind with (e.g., 'CN=John,CN=Users,DC=example,DC=com').",
|
||||||
|
password: "The password to bind with for authentication.",
|
||||||
|
sslRejectUnauthorized:
|
||||||
|
"Whether or not to reject unauthorized SSL certificates (true/false) when using ldaps://. Set to false only in test environments.",
|
||||||
|
sslCertificate:
|
||||||
|
"The SSL certificate (PEM format) to use for secure connection when using ldaps:// with a self-signed certificate."
|
||||||
|
},
|
||||||
|
TEAMCITY: {
|
||||||
|
instanceUrl: "The TeamCity instance URL to connect with.",
|
||||||
|
accessToken: "The access token to use to connect with TeamCity."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1996,6 +2010,10 @@ export const SecretSyncs = {
|
|||||||
WINDMILL: {
|
WINDMILL: {
|
||||||
workspace: "The Windmill workspace to sync secrets to.",
|
workspace: "The Windmill workspace to sync secrets to.",
|
||||||
path: "The Windmill workspace path to sync secrets to."
|
path: "The Windmill workspace path to sync secrets to."
|
||||||
|
},
|
||||||
|
TEAMCITY: {
|
||||||
|
project: "The TeamCity project to sync secrets to.",
|
||||||
|
buildConfig: "The TeamCity build configuration to sync secrets to."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -2060,6 +2078,26 @@ export const SecretRotations = {
|
|||||||
},
|
},
|
||||||
AUTH0_CLIENT_SECRET: {
|
AUTH0_CLIENT_SECRET: {
|
||||||
clientId: "The client ID of the Auth0 Application to rotate the client secret for."
|
clientId: "The client ID of the Auth0 Application to rotate the client secret for."
|
||||||
|
},
|
||||||
|
LDAP_PASSWORD: {
|
||||||
|
dn: "The Distinguished Name (DN) of the principal to rotate the password for."
|
||||||
|
},
|
||||||
|
GENERAL: {
|
||||||
|
PASSWORD_REQUIREMENTS: {
|
||||||
|
base: "The password requirements to use when generating the new password.",
|
||||||
|
length: "The length of the password to generate.",
|
||||||
|
required: {
|
||||||
|
digits: "The amount of digits to require in the generated password.",
|
||||||
|
lowercase: "The amount of lowercase characters to require in the generated password.",
|
||||||
|
uppercase: "The amount of uppercase characters to require in the generated password.",
|
||||||
|
symbols: "The amount of symbols to require in the generated password."
|
||||||
|
},
|
||||||
|
allowedSymbols: 'The allowed symbols to use in the generated password (defaults to "-_.~!*").'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AWS_IAM_USER_SECRET: {
|
||||||
|
userName: "The name of the client to rotate credentials for.",
|
||||||
|
region: "The AWS region the client is present in."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SECRETS_MAPPING: {
|
SECRETS_MAPPING: {
|
||||||
@ -2070,6 +2108,14 @@ export const SecretRotations = {
|
|||||||
AUTH0_CLIENT_SECRET: {
|
AUTH0_CLIENT_SECRET: {
|
||||||
clientId: "The name of the secret that the client ID will be mapped to.",
|
clientId: "The name of the secret that the client ID will be mapped to.",
|
||||||
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
|
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
|
||||||
|
},
|
||||||
|
LDAP_PASSWORD: {
|
||||||
|
dn: "The name of the secret that the Distinguished Name (DN) of the principal will be mapped to.",
|
||||||
|
password: "The name of the secret that the rotated password will be mapped to."
|
||||||
|
},
|
||||||
|
AWS_IAM_USER_SECRET: {
|
||||||
|
accessKeyId: "The name of the secret that the access key ID will be mapped to.",
|
||||||
|
secretAccessKey: "The name of the secret that the rotated secret access key will be mapped to."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
3
backend/src/lib/regex/index.ts
Normal file
3
backend/src/lib/regex/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const DistinguishedNameRegex =
|
||||||
|
// DN format, ie; CN=user,OU=users,DC=example,DC=com
|
||||||
|
/^(?:(?:[a-zA-Z0-9]+=[^,+="<>#;\\\\]+)(?:(?:\\+[a-zA-Z0-9]+=[^,+="<>#;\\\\]+)*)(?:,(?:[a-zA-Z0-9]+=[^,+="<>#;\\\\]+)(?:(?:\\+[a-zA-Z0-9]+=[^,+="<>#;\\\\]+)*))*)?$/;
|
@ -28,11 +28,16 @@ import {
|
|||||||
HumanitecConnectionListItemSchema,
|
HumanitecConnectionListItemSchema,
|
||||||
SanitizedHumanitecConnectionSchema
|
SanitizedHumanitecConnectionSchema
|
||||||
} from "@app/services/app-connection/humanitec";
|
} from "@app/services/app-connection/humanitec";
|
||||||
|
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
|
||||||
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
|
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
|
||||||
import {
|
import {
|
||||||
PostgresConnectionListItemSchema,
|
PostgresConnectionListItemSchema,
|
||||||
SanitizedPostgresConnectionSchema
|
SanitizedPostgresConnectionSchema
|
||||||
} from "@app/services/app-connection/postgres";
|
} from "@app/services/app-connection/postgres";
|
||||||
|
import {
|
||||||
|
SanitizedTeamCityConnectionSchema,
|
||||||
|
TeamCityConnectionListItemSchema
|
||||||
|
} from "@app/services/app-connection/teamcity";
|
||||||
import {
|
import {
|
||||||
SanitizedTerraformCloudConnectionSchema,
|
SanitizedTerraformCloudConnectionSchema,
|
||||||
TerraformCloudConnectionListItemSchema
|
TerraformCloudConnectionListItemSchema
|
||||||
@ -59,7 +64,9 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedMsSqlConnectionSchema.options,
|
...SanitizedMsSqlConnectionSchema.options,
|
||||||
...SanitizedCamundaConnectionSchema.options,
|
...SanitizedCamundaConnectionSchema.options,
|
||||||
...SanitizedWindmillConnectionSchema.options,
|
...SanitizedWindmillConnectionSchema.options,
|
||||||
...SanitizedAuth0ConnectionSchema.options
|
...SanitizedAuth0ConnectionSchema.options,
|
||||||
|
...SanitizedLdapConnectionSchema.options,
|
||||||
|
...SanitizedTeamCityConnectionSchema.options
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||||
@ -76,7 +83,9 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
MsSqlConnectionListItemSchema,
|
MsSqlConnectionListItemSchema,
|
||||||
CamundaConnectionListItemSchema,
|
CamundaConnectionListItemSchema,
|
||||||
WindmillConnectionListItemSchema,
|
WindmillConnectionListItemSchema,
|
||||||
Auth0ConnectionListItemSchema
|
Auth0ConnectionListItemSchema,
|
||||||
|
LdapConnectionListItemSchema,
|
||||||
|
TeamCityConnectionListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
@ -59,4 +59,40 @@ export const registerAwsConnectionRouter = async (server: FastifyZodProvider) =>
|
|||||||
return { kmsKeys };
|
return { kmsKeys };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/users`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
iamUsers: z
|
||||||
|
.object({
|
||||||
|
UserName: z.string(),
|
||||||
|
Arn: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const iamUsers = await server.services.appConnection.aws.listIamUsers(
|
||||||
|
{
|
||||||
|
connectionId
|
||||||
|
},
|
||||||
|
req.permission
|
||||||
|
);
|
||||||
|
|
||||||
|
return { iamUsers };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { registerAuth0ConnectionRouter } from "@app/server/routes/v1/app-connection-routers/auth0-connection-router";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import { registerAuth0ConnectionRouter } from "./auth0-connection-router";
|
||||||
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
||||||
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
|
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
|
||||||
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
|
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
|
||||||
@ -9,8 +9,10 @@ import { registerDatabricksConnectionRouter } from "./databricks-connection-rout
|
|||||||
import { registerGcpConnectionRouter } from "./gcp-connection-router";
|
import { registerGcpConnectionRouter } from "./gcp-connection-router";
|
||||||
import { registerGitHubConnectionRouter } from "./github-connection-router";
|
import { registerGitHubConnectionRouter } from "./github-connection-router";
|
||||||
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
||||||
|
import { registerLdapConnectionRouter } from "./ldap-connection-router";
|
||||||
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
||||||
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
|
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
|
||||||
|
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
|
||||||
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
|
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
|
||||||
import { registerVercelConnectionRouter } from "./vercel-connection-router";
|
import { registerVercelConnectionRouter } from "./vercel-connection-router";
|
||||||
import { registerWindmillConnectionRouter } from "./windmill-connection-router";
|
import { registerWindmillConnectionRouter } from "./windmill-connection-router";
|
||||||
@ -32,5 +34,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.MsSql]: registerMsSqlConnectionRouter,
|
[AppConnection.MsSql]: registerMsSqlConnectionRouter,
|
||||||
[AppConnection.Camunda]: registerCamundaConnectionRouter,
|
[AppConnection.Camunda]: registerCamundaConnectionRouter,
|
||||||
[AppConnection.Windmill]: registerWindmillConnectionRouter,
|
[AppConnection.Windmill]: registerWindmillConnectionRouter,
|
||||||
[AppConnection.Auth0]: registerAuth0ConnectionRouter
|
[AppConnection.Auth0]: registerAuth0ConnectionRouter,
|
||||||
|
[AppConnection.LDAP]: registerLdapConnectionRouter,
|
||||||
|
[AppConnection.TeamCity]: registerTeamCityConnectionRouter
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateLdapConnectionSchema,
|
||||||
|
SanitizedLdapConnectionSchema,
|
||||||
|
UpdateLdapConnectionSchema
|
||||||
|
} from "@app/services/app-connection/ldap";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerLdapConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.LDAP,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedLdapConnectionSchema,
|
||||||
|
createSchema: CreateLdapConnectionSchema,
|
||||||
|
updateSchema: UpdateLdapConnectionSchema
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,60 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateTeamCityConnectionSchema,
|
||||||
|
SanitizedTeamCityConnectionSchema,
|
||||||
|
UpdateTeamCityConnectionSchema
|
||||||
|
} from "@app/services/app-connection/teamcity";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerTeamCityConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.TeamCity,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedTeamCityConnectionSchema,
|
||||||
|
createSchema: CreateTeamCityConnectionSchema,
|
||||||
|
updateSchema: UpdateTeamCityConnectionSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
// The following endpoints are for internal Infisical App use only and not part of the public API
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/projects`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
buildTypes: z.object({
|
||||||
|
buildType: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
const projects = await server.services.appConnection.teamcity.listProjects(connectionId, req.permission);
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -79,7 +80,17 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
includeGroupMembers: z
|
includeGroupMembers: z
|
||||||
.enum(["true", "false"])
|
.enum(["true", "false"])
|
||||||
.default("false")
|
.default("false")
|
||||||
.transform((value) => value === "true")
|
.transform((value) => value === "true"),
|
||||||
|
roles: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.transform(decodeURIComponent)
|
||||||
|
.refine((value) => {
|
||||||
|
if (!value) return true;
|
||||||
|
const slugs = value.split(",");
|
||||||
|
return slugs.every((slug) => slugify(slug.trim(), { lowercase: true }) === slug.trim());
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
}),
|
}),
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@ -118,13 +129,15 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
const roles = (req.query.roles?.split(",") || []).filter(Boolean);
|
||||||
const users = await server.services.projectMembership.getProjectMemberships({
|
const users = await server.services.projectMembership.getProjectMemberships({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
includeGroupMembers: req.query.includeGroupMembers,
|
includeGroupMembers: req.query.includeGroupMembers,
|
||||||
projectId: req.params.workspaceId,
|
projectId: req.params.workspaceId,
|
||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId,
|
||||||
|
roles
|
||||||
});
|
});
|
||||||
|
|
||||||
return { users };
|
return { users };
|
||||||
|
@ -9,6 +9,7 @@ import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
|||||||
import { registerGcpSyncRouter } from "./gcp-sync-router";
|
import { registerGcpSyncRouter } from "./gcp-sync-router";
|
||||||
import { registerGitHubSyncRouter } from "./github-sync-router";
|
import { registerGitHubSyncRouter } from "./github-sync-router";
|
||||||
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
|
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
|
||||||
|
import { registerTeamCitySyncRouter } from "./teamcity-sync-router";
|
||||||
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
|
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
|
||||||
import { registerVercelSyncRouter } from "./vercel-sync-router";
|
import { registerVercelSyncRouter } from "./vercel-sync-router";
|
||||||
import { registerWindmillSyncRouter } from "./windmill-sync-router";
|
import { registerWindmillSyncRouter } from "./windmill-sync-router";
|
||||||
@ -27,5 +28,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
|
|||||||
[SecretSync.TerraformCloud]: registerTerraformCloudSyncRouter,
|
[SecretSync.TerraformCloud]: registerTerraformCloudSyncRouter,
|
||||||
[SecretSync.Camunda]: registerCamundaSyncRouter,
|
[SecretSync.Camunda]: registerCamundaSyncRouter,
|
||||||
[SecretSync.Vercel]: registerVercelSyncRouter,
|
[SecretSync.Vercel]: registerVercelSyncRouter,
|
||||||
[SecretSync.Windmill]: registerWindmillSyncRouter
|
[SecretSync.Windmill]: registerWindmillSyncRouter,
|
||||||
|
[SecretSync.TeamCity]: registerTeamCitySyncRouter
|
||||||
};
|
};
|
||||||
|
@ -23,6 +23,7 @@ import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/service
|
|||||||
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
|
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
|
||||||
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
||||||
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
|
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
|
||||||
|
import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
|
||||||
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
|
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
|
||||||
import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel";
|
import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel";
|
||||||
import { WindmillSyncListItemSchema, WindmillSyncSchema } from "@app/services/secret-sync/windmill";
|
import { WindmillSyncListItemSchema, WindmillSyncSchema } from "@app/services/secret-sync/windmill";
|
||||||
@ -39,7 +40,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
|||||||
TerraformCloudSyncSchema,
|
TerraformCloudSyncSchema,
|
||||||
CamundaSyncSchema,
|
CamundaSyncSchema,
|
||||||
VercelSyncSchema,
|
VercelSyncSchema,
|
||||||
WindmillSyncSchema
|
WindmillSyncSchema,
|
||||||
|
TeamCitySyncSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
||||||
@ -54,7 +56,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
|||||||
TerraformCloudSyncListItemSchema,
|
TerraformCloudSyncListItemSchema,
|
||||||
CamundaSyncListItemSchema,
|
CamundaSyncListItemSchema,
|
||||||
VercelSyncListItemSchema,
|
VercelSyncListItemSchema,
|
||||||
WindmillSyncListItemSchema
|
WindmillSyncListItemSchema,
|
||||||
|
TeamCitySyncListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import {
|
||||||
|
CreateTeamCitySyncSchema,
|
||||||
|
TeamCitySyncSchema,
|
||||||
|
UpdateTeamCitySyncSchema
|
||||||
|
} from "@app/services/secret-sync/teamcity";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerTeamCitySyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.TeamCity,
|
||||||
|
server,
|
||||||
|
responseSchema: TeamCitySyncSchema,
|
||||||
|
createSchema: CreateTeamCitySyncSchema,
|
||||||
|
updateSchema: UpdateTeamCitySyncSchema
|
||||||
|
});
|
@ -12,7 +12,9 @@ export enum AppConnection {
|
|||||||
MsSql = "mssql",
|
MsSql = "mssql",
|
||||||
Camunda = "camunda",
|
Camunda = "camunda",
|
||||||
Windmill = "windmill",
|
Windmill = "windmill",
|
||||||
Auth0 = "auth0"
|
Auth0 = "auth0",
|
||||||
|
LDAP = "ldap",
|
||||||
|
TeamCity = "teamcity"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AWSRegion {
|
export enum AWSRegion {
|
||||||
|
@ -41,8 +41,14 @@ import {
|
|||||||
HumanitecConnectionMethod,
|
HumanitecConnectionMethod,
|
||||||
validateHumanitecConnectionCredentials
|
validateHumanitecConnectionCredentials
|
||||||
} from "./humanitec";
|
} from "./humanitec";
|
||||||
|
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
|
||||||
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
|
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
|
||||||
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
|
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
|
||||||
|
import {
|
||||||
|
getTeamCityConnectionListItem,
|
||||||
|
TeamCityConnectionMethod,
|
||||||
|
validateTeamCityConnectionCredentials
|
||||||
|
} from "./teamcity";
|
||||||
import {
|
import {
|
||||||
getTerraformCloudConnectionListItem,
|
getTerraformCloudConnectionListItem,
|
||||||
TerraformCloudConnectionMethod,
|
TerraformCloudConnectionMethod,
|
||||||
@ -71,7 +77,9 @@ export const listAppConnectionOptions = () => {
|
|||||||
getMsSqlConnectionListItem(),
|
getMsSqlConnectionListItem(),
|
||||||
getCamundaConnectionListItem(),
|
getCamundaConnectionListItem(),
|
||||||
getWindmillConnectionListItem(),
|
getWindmillConnectionListItem(),
|
||||||
getAuth0ConnectionListItem()
|
getAuth0ConnectionListItem(),
|
||||||
|
getLdapConnectionListItem(),
|
||||||
|
getTeamCityConnectionListItem()
|
||||||
].sort((a, b) => a.name.localeCompare(b.name));
|
].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -135,7 +143,9 @@ export const validateAppConnectionCredentials = async (
|
|||||||
[AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Windmill]: validateWindmillConnectionCredentials as TAppConnectionCredentialsValidator
|
[AppConnection.Windmill]: validateWindmillConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.LDAP]: validateLdapConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.TeamCity]: validateTeamCityConnectionCredentials as TAppConnectionCredentialsValidator
|
||||||
};
|
};
|
||||||
|
|
||||||
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
|
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
|
||||||
@ -167,9 +177,12 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
case MsSqlConnectionMethod.UsernameAndPassword:
|
case MsSqlConnectionMethod.UsernameAndPassword:
|
||||||
return "Username & Password";
|
return "Username & Password";
|
||||||
case WindmillConnectionMethod.AccessToken:
|
case WindmillConnectionMethod.AccessToken:
|
||||||
|
case TeamCityConnectionMethod.AccessToken:
|
||||||
return "Access Token";
|
return "Access Token";
|
||||||
case Auth0ConnectionMethod.ClientCredentials:
|
case Auth0ConnectionMethod.ClientCredentials:
|
||||||
return "Client Credentials";
|
return "Client Credentials";
|
||||||
|
case LdapConnectionMethod.SimpleBind:
|
||||||
|
return "Simple Bind";
|
||||||
default:
|
default:
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
throw new Error(`Unhandled App Connection Method: ${method}`);
|
||||||
@ -214,5 +227,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
|||||||
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
|
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Vercel]: platformManagedCredentialsNotSupported,
|
[AppConnection.Vercel]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Windmill]: platformManagedCredentialsNotSupported,
|
[AppConnection.Windmill]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Auth0]: platformManagedCredentialsNotSupported
|
[AppConnection.Auth0]: platformManagedCredentialsNotSupported,
|
||||||
|
[AppConnection.LDAP]: platformManagedCredentialsNotSupported, // we could support this in the future
|
||||||
|
[AppConnection.TeamCity]: platformManagedCredentialsNotSupported
|
||||||
};
|
};
|
||||||
|
@ -14,5 +14,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|||||||
[AppConnection.MsSql]: "Microsoft SQL Server",
|
[AppConnection.MsSql]: "Microsoft SQL Server",
|
||||||
[AppConnection.Camunda]: "Camunda",
|
[AppConnection.Camunda]: "Camunda",
|
||||||
[AppConnection.Windmill]: "Windmill",
|
[AppConnection.Windmill]: "Windmill",
|
||||||
[AppConnection.Auth0]: "Auth0"
|
[AppConnection.Auth0]: "Auth0",
|
||||||
|
[AppConnection.LDAP]: "LDAP",
|
||||||
|
[AppConnection.TeamCity]: "TeamCity"
|
||||||
};
|
};
|
||||||
|
@ -43,8 +43,11 @@ import { ValidateGitHubConnectionCredentialsSchema } from "./github";
|
|||||||
import { githubConnectionService } from "./github/github-connection-service";
|
import { githubConnectionService } from "./github/github-connection-service";
|
||||||
import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
|
import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
|
||||||
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
||||||
|
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
|
||||||
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||||
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
|
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
|
||||||
|
import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity";
|
||||||
|
import { teamcityConnectionService } from "./teamcity/teamcity-connection-service";
|
||||||
import { ValidateTerraformCloudConnectionCredentialsSchema } from "./terraform-cloud";
|
import { ValidateTerraformCloudConnectionCredentialsSchema } from "./terraform-cloud";
|
||||||
import { terraformCloudConnectionService } from "./terraform-cloud/terraform-cloud-connection-service";
|
import { terraformCloudConnectionService } from "./terraform-cloud/terraform-cloud-connection-service";
|
||||||
import { ValidateVercelConnectionCredentialsSchema } from "./vercel";
|
import { ValidateVercelConnectionCredentialsSchema } from "./vercel";
|
||||||
@ -74,7 +77,9 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
|||||||
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
|
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
|
||||||
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema,
|
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema,
|
||||||
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
|
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
|
||||||
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema
|
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema,
|
||||||
|
[AppConnection.LDAP]: ValidateLdapConnectionCredentialsSchema,
|
||||||
|
[AppConnection.TeamCity]: ValidateTeamCityConnectionCredentialsSchema
|
||||||
};
|
};
|
||||||
|
|
||||||
export const appConnectionServiceFactory = ({
|
export const appConnectionServiceFactory = ({
|
||||||
@ -450,6 +455,7 @@ export const appConnectionServiceFactory = ({
|
|||||||
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
vercel: vercelConnectionService(connectAppConnectionById),
|
vercel: vercelConnectionService(connectAppConnectionById),
|
||||||
windmill: windmillConnectionService(connectAppConnectionById),
|
windmill: windmillConnectionService(connectAppConnectionById),
|
||||||
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService)
|
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
|
teamcity: teamcityConnectionService(connectAppConnectionById)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -57,12 +57,24 @@ import {
|
|||||||
THumanitecConnectionInput,
|
THumanitecConnectionInput,
|
||||||
TValidateHumanitecConnectionCredentialsSchema
|
TValidateHumanitecConnectionCredentialsSchema
|
||||||
} from "./humanitec";
|
} from "./humanitec";
|
||||||
|
import {
|
||||||
|
TLdapConnection,
|
||||||
|
TLdapConnectionConfig,
|
||||||
|
TLdapConnectionInput,
|
||||||
|
TValidateLdapConnectionCredentialsSchema
|
||||||
|
} from "./ldap";
|
||||||
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||||
import {
|
import {
|
||||||
TPostgresConnection,
|
TPostgresConnection,
|
||||||
TPostgresConnectionInput,
|
TPostgresConnectionInput,
|
||||||
TValidatePostgresConnectionCredentialsSchema
|
TValidatePostgresConnectionCredentialsSchema
|
||||||
} from "./postgres";
|
} from "./postgres";
|
||||||
|
import {
|
||||||
|
TTeamCityConnection,
|
||||||
|
TTeamCityConnectionConfig,
|
||||||
|
TTeamCityConnectionInput,
|
||||||
|
TValidateTeamCityConnectionCredentialsSchema
|
||||||
|
} from "./teamcity";
|
||||||
import {
|
import {
|
||||||
TTerraformCloudConnection,
|
TTerraformCloudConnection,
|
||||||
TTerraformCloudConnectionConfig,
|
TTerraformCloudConnectionConfig,
|
||||||
@ -97,6 +109,8 @@ export type TAppConnection = { id: string } & (
|
|||||||
| TCamundaConnection
|
| TCamundaConnection
|
||||||
| TWindmillConnection
|
| TWindmillConnection
|
||||||
| TAuth0Connection
|
| TAuth0Connection
|
||||||
|
| TLdapConnection
|
||||||
|
| TTeamCityConnection
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
||||||
@ -118,6 +132,8 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| TCamundaConnectionInput
|
| TCamundaConnectionInput
|
||||||
| TWindmillConnectionInput
|
| TWindmillConnectionInput
|
||||||
| TAuth0ConnectionInput
|
| TAuth0ConnectionInput
|
||||||
|
| TLdapConnectionInput
|
||||||
|
| TTeamCityConnectionInput
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput;
|
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput;
|
||||||
@ -144,7 +160,9 @@ export type TAppConnectionConfig =
|
|||||||
| TSqlConnectionConfig
|
| TSqlConnectionConfig
|
||||||
| TCamundaConnectionConfig
|
| TCamundaConnectionConfig
|
||||||
| TWindmillConnectionConfig
|
| TWindmillConnectionConfig
|
||||||
| TAuth0ConnectionConfig;
|
| TAuth0ConnectionConfig
|
||||||
|
| TLdapConnectionConfig
|
||||||
|
| TTeamCityConnectionConfig;
|
||||||
|
|
||||||
export type TValidateAppConnectionCredentialsSchema =
|
export type TValidateAppConnectionCredentialsSchema =
|
||||||
| TValidateAwsConnectionCredentialsSchema
|
| TValidateAwsConnectionCredentialsSchema
|
||||||
@ -160,7 +178,9 @@ export type TValidateAppConnectionCredentialsSchema =
|
|||||||
| TValidateTerraformCloudConnectionCredentialsSchema
|
| TValidateTerraformCloudConnectionCredentialsSchema
|
||||||
| TValidateVercelConnectionCredentialsSchema
|
| TValidateVercelConnectionCredentialsSchema
|
||||||
| TValidateWindmillConnectionCredentialsSchema
|
| TValidateWindmillConnectionCredentialsSchema
|
||||||
| TValidateAuth0ConnectionCredentialsSchema;
|
| TValidateAuth0ConnectionCredentialsSchema
|
||||||
|
| TValidateLdapConnectionCredentialsSchema
|
||||||
|
| TValidateTeamCityConnectionCredentialsSchema;
|
||||||
|
|
||||||
export type TListAwsConnectionKmsKeys = {
|
export type TListAwsConnectionKmsKeys = {
|
||||||
connectionId: string;
|
connectionId: string;
|
||||||
@ -168,6 +188,10 @@ export type TListAwsConnectionKmsKeys = {
|
|||||||
destination: SecretSync.AWSParameterStore | SecretSync.AWSSecretsManager;
|
destination: SecretSync.AWSParameterStore | SecretSync.AWSSecretsManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TListAwsConnectionIamUsers = {
|
||||||
|
connectionId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TAppConnectionCredentialsValidator = (
|
export type TAppConnectionCredentialsValidator = (
|
||||||
appConnection: TAppConnectionConfig
|
appConnection: TAppConnectionConfig
|
||||||
) => Promise<TAppConnection["credentials"]>;
|
) => Promise<TAppConnection["credentials"]>;
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
|
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
|
||||||
import AWS from "aws-sdk";
|
import AWS from "aws-sdk";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
import { AwsConnectionMethod } from "./aws-connection-enums";
|
import { AwsConnectionMethod } from "./aws-connection-enums";
|
||||||
@ -90,9 +92,20 @@ export const validateAwsConnectionCredentials = async (appConnection: TAwsConnec
|
|||||||
const sts = new AWS.STS(awsConfig);
|
const sts = new AWS.STS(awsConfig);
|
||||||
|
|
||||||
resp = await sts.getCallerIdentity().promise();
|
resp = await sts.getCallerIdentity().promise();
|
||||||
} catch (e: unknown) {
|
} catch (error: unknown) {
|
||||||
|
logger.error(error, "Error validating AWS connection credentials");
|
||||||
|
|
||||||
|
let message: string;
|
||||||
|
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
message = (error.response?.data?.message as string) || error.message || "verify credentials";
|
||||||
|
} else {
|
||||||
|
message = (error as Error)?.message || "verify credentials";
|
||||||
|
}
|
||||||
|
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: `Unable to validate connection: verify credentials`
|
message: `Unable to validate connection: ${message}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,10 @@ import AWS from "aws-sdk";
|
|||||||
|
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
import { TListAwsConnectionKmsKeys } from "@app/services/app-connection/app-connection-types";
|
import {
|
||||||
|
TListAwsConnectionIamUsers,
|
||||||
|
TListAwsConnectionKmsKeys
|
||||||
|
} from "@app/services/app-connection/app-connection-types";
|
||||||
import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns";
|
import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns";
|
||||||
import { TAwsConnection } from "@app/services/app-connection/aws/aws-connection-types";
|
import { TAwsConnection } from "@app/services/app-connection/aws/aws-connection-types";
|
||||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
@ -70,6 +73,23 @@ const listAwsKmsKeys = async (
|
|||||||
return kmsKeys;
|
return kmsKeys;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const listAwsIamUsers = async (appConnection: TAwsConnection) => {
|
||||||
|
const { credentials } = await getAwsConnectionConfig(appConnection);
|
||||||
|
|
||||||
|
const iam = new AWS.IAM({ credentials });
|
||||||
|
|
||||||
|
const userEntries: AWS.IAM.User[] = [];
|
||||||
|
let userMarker: string | undefined;
|
||||||
|
do {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const response = await iam.listUsers({ MaxItems: 100, Marker: userMarker }).promise();
|
||||||
|
userEntries.push(...(response.Users || []));
|
||||||
|
userMarker = response.Marker;
|
||||||
|
} while (userMarker);
|
||||||
|
|
||||||
|
return userEntries;
|
||||||
|
};
|
||||||
|
|
||||||
export const awsConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
export const awsConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
||||||
const listKmsKeys = async (
|
const listKmsKeys = async (
|
||||||
{ connectionId, region, destination }: TListAwsConnectionKmsKeys,
|
{ connectionId, region, destination }: TListAwsConnectionKmsKeys,
|
||||||
@ -82,7 +102,16 @@ export const awsConnectionService = (getAppConnection: TGetAppConnectionFunc) =>
|
|||||||
return kmsKeys;
|
return kmsKeys;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const listIamUsers = async ({ connectionId }: TListAwsConnectionIamUsers, actor: OrgServiceActor) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.AWS, connectionId, actor);
|
||||||
|
|
||||||
|
const iamUsers = await listAwsIamUsers(appConnection);
|
||||||
|
|
||||||
|
return iamUsers;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listKmsKeys
|
listKmsKeys,
|
||||||
|
listIamUsers
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
4
backend/src/services/app-connection/ldap/index.ts
Normal file
4
backend/src/services/app-connection/ldap/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./ldap-connection-enums";
|
||||||
|
export * from "./ldap-connection-fns";
|
||||||
|
export * from "./ldap-connection-schemas";
|
||||||
|
export * from "./ldap-connection-types";
|
@ -0,0 +1,7 @@
|
|||||||
|
export enum LdapConnectionMethod {
|
||||||
|
SimpleBind = "simple-bind"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum LdapProvider {
|
||||||
|
ActiveDirectory = "active-directory"
|
||||||
|
}
|
102
backend/src/services/app-connection/ldap/ldap-connection-fns.ts
Normal file
102
backend/src/services/app-connection/ldap/ldap-connection-fns.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import ldap from "ldapjs";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import { LdapConnectionMethod } from "./ldap-connection-enums";
|
||||||
|
import { TLdapConnectionConfig } from "./ldap-connection-types";
|
||||||
|
|
||||||
|
export const getLdapConnectionListItem = () => {
|
||||||
|
return {
|
||||||
|
name: "LDAP" as const,
|
||||||
|
app: AppConnection.LDAP as const,
|
||||||
|
methods: Object.values(LdapConnectionMethod) as [LdapConnectionMethod.SimpleBind]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const LDAP_TIMEOUT = 15_000;
|
||||||
|
|
||||||
|
export const getLdapConnectionClient = async ({
|
||||||
|
url,
|
||||||
|
dn,
|
||||||
|
password,
|
||||||
|
sslCertificate,
|
||||||
|
sslRejectUnauthorized = true
|
||||||
|
}: TLdapConnectionConfig["credentials"]) => {
|
||||||
|
await blockLocalAndPrivateIpAddresses(url);
|
||||||
|
|
||||||
|
const isSSL = url.startsWith("ldaps");
|
||||||
|
|
||||||
|
return new Promise<ldap.Client>((resolve, reject) => {
|
||||||
|
const client = ldap.createClient({
|
||||||
|
url,
|
||||||
|
timeout: LDAP_TIMEOUT,
|
||||||
|
connectTimeout: LDAP_TIMEOUT,
|
||||||
|
tlsOptions: isSSL
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: sslRejectUnauthorized,
|
||||||
|
ca: sslCertificate ? [sslCertificate] : undefined
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("error", (err: Error) => {
|
||||||
|
logger.error(err, "LDAP Error");
|
||||||
|
client.destroy();
|
||||||
|
reject(new Error(`Provider Error - ${err.message}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("connectError", (err: Error) => {
|
||||||
|
logger.error(err, "LDAP Connection Error");
|
||||||
|
client.destroy();
|
||||||
|
reject(new Error(`Provider Connect Error - ${err.message}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("connectRefused", (err: Error) => {
|
||||||
|
logger.error(err, "LDAP Connection Refused");
|
||||||
|
client.destroy();
|
||||||
|
reject(new Error(`Provider Connection Refused - ${err.message}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("connectTimeout", (err: Error) => {
|
||||||
|
logger.error(err, "LDAP Connection Timeout");
|
||||||
|
client.destroy();
|
||||||
|
reject(new Error(`Provider Connection Timeout - ${err.message}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("connect", () => {
|
||||||
|
client.bind(dn, password, (err) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(err, "LDAP Bind Error");
|
||||||
|
reject(new Error(`Bind Error: ${err.message}`));
|
||||||
|
client.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(client);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateLdapConnectionCredentials = async ({ credentials }: TLdapConnectionConfig) => {
|
||||||
|
let client: ldap.Client | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
client = await getLdapConnectionClient(credentials);
|
||||||
|
|
||||||
|
// this shouldn't occur as handle connection error events in client but here as fallback
|
||||||
|
if (!client.connected) {
|
||||||
|
throw new BadRequestError({ message: "Unable to connect to LDAP server" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials;
|
||||||
|
} catch (e: unknown) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Unable to validate connection: ${(e as Error).message || "verify credentials"}`
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
client?.destroy();
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,93 @@
|
|||||||
|
import RE2 from "re2";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { AppConnections } from "@app/lib/api-docs";
|
||||||
|
import { DistinguishedNameRegex } from "@app/lib/regex";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
BaseAppConnectionSchema,
|
||||||
|
GenericCreateAppConnectionFieldsSchema,
|
||||||
|
GenericUpdateAppConnectionFieldsSchema
|
||||||
|
} from "@app/services/app-connection/app-connection-schemas";
|
||||||
|
|
||||||
|
import { LdapConnectionMethod, LdapProvider } from "./ldap-connection-enums";
|
||||||
|
|
||||||
|
export const LdapConnectionSimpleBindCredentialsSchema = z.object({
|
||||||
|
provider: z.nativeEnum(LdapProvider).describe(AppConnections.CREDENTIALS.LDAP.provider),
|
||||||
|
url: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "URL required")
|
||||||
|
.regex(new RE2(/^ldaps?:\/\//))
|
||||||
|
.describe(AppConnections.CREDENTIALS.LDAP.url),
|
||||||
|
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(AppConnections.CREDENTIALS.LDAP.dn),
|
||||||
|
password: z.string().trim().min(1, "Password required").describe(AppConnections.CREDENTIALS.LDAP.password),
|
||||||
|
sslRejectUnauthorized: z.boolean().optional().describe(AppConnections.CREDENTIALS.LDAP.sslRejectUnauthorized),
|
||||||
|
sslCertificate: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.transform((value) => value || undefined)
|
||||||
|
.optional()
|
||||||
|
.describe(AppConnections.CREDENTIALS.LDAP.sslCertificate)
|
||||||
|
});
|
||||||
|
|
||||||
|
const BaseLdapConnectionSchema = BaseAppConnectionSchema.extend({
|
||||||
|
app: z.literal(AppConnection.LDAP)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LdapConnectionSchema = z.intersection(
|
||||||
|
BaseLdapConnectionSchema,
|
||||||
|
z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z.literal(LdapConnectionMethod.SimpleBind),
|
||||||
|
credentials: LdapConnectionSimpleBindCredentialsSchema
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SanitizedLdapConnectionSchema = z.discriminatedUnion("method", [
|
||||||
|
BaseLdapConnectionSchema.extend({
|
||||||
|
method: z.literal(LdapConnectionMethod.SimpleBind),
|
||||||
|
credentials: LdapConnectionSimpleBindCredentialsSchema.pick({
|
||||||
|
provider: true,
|
||||||
|
url: true,
|
||||||
|
dn: true,
|
||||||
|
sslRejectUnauthorized: true,
|
||||||
|
sslCertificate: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ValidateLdapConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z.literal(LdapConnectionMethod.SimpleBind).describe(AppConnections.CREATE(AppConnection.LDAP).method),
|
||||||
|
credentials: LdapConnectionSimpleBindCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.LDAP).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CreateLdapConnectionSchema = ValidateLdapConnectionCredentialsSchema.and(
|
||||||
|
GenericCreateAppConnectionFieldsSchema(AppConnection.LDAP)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateLdapConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
credentials: LdapConnectionSimpleBindCredentialsSchema.optional().describe(
|
||||||
|
AppConnections.UPDATE(AppConnection.LDAP).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.LDAP));
|
||||||
|
|
||||||
|
export const LdapConnectionListItemSchema = z.object({
|
||||||
|
name: z.literal("LDAP"),
|
||||||
|
app: z.literal(AppConnection.LDAP),
|
||||||
|
// the below is preferable but currently breaks with our zod to json schema parser
|
||||||
|
// methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]),
|
||||||
|
methods: z.nativeEnum(LdapConnectionMethod).array()
|
||||||
|
});
|
@ -0,0 +1,22 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { DiscriminativePick } from "@app/lib/types";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreateLdapConnectionSchema,
|
||||||
|
LdapConnectionSchema,
|
||||||
|
ValidateLdapConnectionCredentialsSchema
|
||||||
|
} from "./ldap-connection-schemas";
|
||||||
|
|
||||||
|
export type TLdapConnection = z.infer<typeof LdapConnectionSchema>;
|
||||||
|
|
||||||
|
export type TLdapConnectionInput = z.infer<typeof CreateLdapConnectionSchema> & {
|
||||||
|
app: AppConnection.LDAP;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateLdapConnectionCredentialsSchema = typeof ValidateLdapConnectionCredentialsSchema;
|
||||||
|
|
||||||
|
export type TLdapConnectionConfig = DiscriminativePick<TLdapConnection, "method" | "app" | "credentials"> & {
|
||||||
|
orgId: string;
|
||||||
|
};
|
@ -31,7 +31,8 @@ export const SanitizedMsSqlConnectionSchema = z.discriminatedUnion("method", [
|
|||||||
port: true,
|
port: true,
|
||||||
username: true,
|
username: true,
|
||||||
sslEnabled: true,
|
sslEnabled: true,
|
||||||
sslRejectUnauthorized: true
|
sslRejectUnauthorized: true,
|
||||||
|
sslCertificate: true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
@ -29,7 +29,8 @@ export const SanitizedPostgresConnectionSchema = z.discriminatedUnion("method",
|
|||||||
port: true,
|
port: true,
|
||||||
username: true,
|
username: true,
|
||||||
sslEnabled: true,
|
sslEnabled: true,
|
||||||
sslRejectUnauthorized: true
|
sslRejectUnauthorized: true,
|
||||||
|
sslCertificate: true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
4
backend/src/services/app-connection/teamcity/index.ts
Normal file
4
backend/src/services/app-connection/teamcity/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./teamcity-connection-enums";
|
||||||
|
export * from "./teamcity-connection-fns";
|
||||||
|
export * from "./teamcity-connection-schemas";
|
||||||
|
export * from "./teamcity-connection-types";
|
@ -0,0 +1,3 @@
|
|||||||
|
export enum TeamCityConnectionMethod {
|
||||||
|
AccessToken = "access-token"
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import { TeamCityConnectionMethod } from "./teamcity-connection-enums";
|
||||||
|
import {
|
||||||
|
TTeamCityConnection,
|
||||||
|
TTeamCityConnectionConfig,
|
||||||
|
TTeamCityListProjectsResponse
|
||||||
|
} from "./teamcity-connection-types";
|
||||||
|
|
||||||
|
export const getTeamCityInstanceUrl = async (config: TTeamCityConnectionConfig) => {
|
||||||
|
const instanceUrl = removeTrailingSlash(config.credentials.instanceUrl);
|
||||||
|
|
||||||
|
await blockLocalAndPrivateIpAddresses(instanceUrl);
|
||||||
|
|
||||||
|
return instanceUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTeamCityConnectionListItem = () => {
|
||||||
|
return {
|
||||||
|
name: "TeamCity" as const,
|
||||||
|
app: AppConnection.TeamCity as const,
|
||||||
|
methods: Object.values(TeamCityConnectionMethod) as [TeamCityConnectionMethod.AccessToken]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateTeamCityConnectionCredentials = async (config: TTeamCityConnectionConfig) => {
|
||||||
|
const instanceUrl = await getTeamCityInstanceUrl(config);
|
||||||
|
|
||||||
|
const { accessToken } = config.credentials;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await request.get(`${instanceUrl}/app/rest/server`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listTeamCityProjects = async (appConnection: TTeamCityConnection) => {
|
||||||
|
const instanceUrl = await getTeamCityInstanceUrl(appConnection);
|
||||||
|
const { accessToken } = appConnection.credentials;
|
||||||
|
|
||||||
|
const resp = await request.get<TTeamCityListProjectsResponse>(
|
||||||
|
`${instanceUrl}/app/rest/projects?fields=project(id,name,buildTypes(buildType(id,name)))`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Filter out the root project. Should not be seen by users.
|
||||||
|
return resp.data.project.filter((proj) => proj.id !== "_Root");
|
||||||
|
};
|
@ -0,0 +1,70 @@
|
|||||||
|
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 { TeamCityConnectionMethod } from "./teamcity-connection-enums";
|
||||||
|
|
||||||
|
export const TeamCityConnectionAccessTokenCredentialsSchema = z.object({
|
||||||
|
accessToken: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Access Token required")
|
||||||
|
.describe(AppConnections.CREDENTIALS.TEAMCITY.accessToken),
|
||||||
|
instanceUrl: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.url("Invalid Instance URL")
|
||||||
|
.min(1, "Instance URL required")
|
||||||
|
.describe(AppConnections.CREDENTIALS.TEAMCITY.instanceUrl)
|
||||||
|
});
|
||||||
|
|
||||||
|
const BaseTeamCityConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.TeamCity) });
|
||||||
|
|
||||||
|
export const TeamCityConnectionSchema = BaseTeamCityConnectionSchema.extend({
|
||||||
|
method: z.literal(TeamCityConnectionMethod.AccessToken),
|
||||||
|
credentials: TeamCityConnectionAccessTokenCredentialsSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SanitizedTeamCityConnectionSchema = z.discriminatedUnion("method", [
|
||||||
|
BaseTeamCityConnectionSchema.extend({
|
||||||
|
method: z.literal(TeamCityConnectionMethod.AccessToken),
|
||||||
|
credentials: TeamCityConnectionAccessTokenCredentialsSchema.pick({
|
||||||
|
instanceUrl: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ValidateTeamCityConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z
|
||||||
|
.literal(TeamCityConnectionMethod.AccessToken)
|
||||||
|
.describe(AppConnections.CREATE(AppConnection.TeamCity).method),
|
||||||
|
credentials: TeamCityConnectionAccessTokenCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.TeamCity).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CreateTeamCityConnectionSchema = ValidateTeamCityConnectionCredentialsSchema.and(
|
||||||
|
GenericCreateAppConnectionFieldsSchema(AppConnection.TeamCity)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateTeamCityConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
credentials: TeamCityConnectionAccessTokenCredentialsSchema.optional().describe(
|
||||||
|
AppConnections.UPDATE(AppConnection.TeamCity).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.TeamCity));
|
||||||
|
|
||||||
|
export const TeamCityConnectionListItemSchema = z.object({
|
||||||
|
name: z.literal("TeamCity"),
|
||||||
|
app: z.literal(AppConnection.TeamCity),
|
||||||
|
methods: z.nativeEnum(TeamCityConnectionMethod).array()
|
||||||
|
});
|
@ -0,0 +1,28 @@
|
|||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import { listTeamCityProjects } from "./teamcity-connection-fns";
|
||||||
|
import { TTeamCityConnection } from "./teamcity-connection-types";
|
||||||
|
|
||||||
|
type TGetAppConnectionFunc = (
|
||||||
|
app: AppConnection,
|
||||||
|
connectionId: string,
|
||||||
|
actor: OrgServiceActor
|
||||||
|
) => Promise<TTeamCityConnection>;
|
||||||
|
|
||||||
|
export const teamcityConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
||||||
|
const listProjects = async (connectionId: string, actor: OrgServiceActor) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.TeamCity, connectionId, actor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const projects = await listTeamCityProjects(appConnection);
|
||||||
|
return projects;
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
listProjects
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,43 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { DiscriminativePick } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateTeamCityConnectionSchema,
|
||||||
|
TeamCityConnectionSchema,
|
||||||
|
ValidateTeamCityConnectionCredentialsSchema
|
||||||
|
} from "./teamcity-connection-schemas";
|
||||||
|
|
||||||
|
export type TTeamCityConnection = z.infer<typeof TeamCityConnectionSchema>;
|
||||||
|
|
||||||
|
export type TTeamCityConnectionInput = z.infer<typeof CreateTeamCityConnectionSchema> & {
|
||||||
|
app: AppConnection.TeamCity;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateTeamCityConnectionCredentialsSchema = typeof ValidateTeamCityConnectionCredentialsSchema;
|
||||||
|
|
||||||
|
export type TTeamCityConnectionConfig = DiscriminativePick<
|
||||||
|
TTeamCityConnectionInput,
|
||||||
|
"method" | "app" | "credentials"
|
||||||
|
> & {
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTeamCityProject = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTeamCityProjectWithBuildTypes = TTeamCityProject & {
|
||||||
|
buildTypes: {
|
||||||
|
buildType: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTeamCityListProjectsResponse = {
|
||||||
|
project: TTeamCityProjectWithBuildTypes[];
|
||||||
|
};
|
@ -12,6 +12,7 @@ import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
|
|||||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { getUserPrivateKey } from "@app/lib/crypto/srp";
|
import { getUserPrivateKey } from "@app/lib/crypto/srp";
|
||||||
import { BadRequestError, DatabaseError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { getUserAgentType } from "@app/server/plugins/audit-log";
|
import { getUserAgentType } from "@app/server/plugins/audit-log";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
@ -39,7 +40,6 @@ import {
|
|||||||
AuthTokenType,
|
AuthTokenType,
|
||||||
MfaMethod
|
MfaMethod
|
||||||
} from "./auth-type";
|
} from "./auth-type";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
|
||||||
|
|
||||||
type TAuthLoginServiceFactoryDep = {
|
type TAuthLoginServiceFactoryDep = {
|
||||||
userDAL: TUserDALFactory;
|
userDAL: TUserDALFactory;
|
||||||
|
@ -13,7 +13,7 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
|||||||
// special query
|
// special query
|
||||||
const findAllProjectMembers = async (
|
const findAllProjectMembers = async (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
filter: { usernames?: string[]; username?: string; id?: string } = {}
|
filter: { usernames?: string[]; username?: string; id?: string; roles?: string[] } = {}
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const docs = await db
|
const docs = await db
|
||||||
@ -31,6 +31,29 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
|||||||
if (filter.id) {
|
if (filter.id) {
|
||||||
void qb.where(`${TableName.ProjectMembership}.id`, filter.id);
|
void qb.where(`${TableName.ProjectMembership}.id`, filter.id);
|
||||||
}
|
}
|
||||||
|
if (filter.roles && filter.roles.length > 0) {
|
||||||
|
void qb.whereExists((subQuery) => {
|
||||||
|
void subQuery
|
||||||
|
.select("role")
|
||||||
|
.from(TableName.ProjectUserMembershipRole)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.ProjectRoles,
|
||||||
|
`${TableName.ProjectRoles}.id`,
|
||||||
|
`${TableName.ProjectUserMembershipRole}.customRoleId`
|
||||||
|
)
|
||||||
|
.whereRaw("??.?? = ??.??", [
|
||||||
|
TableName.ProjectUserMembershipRole,
|
||||||
|
"projectMembershipId",
|
||||||
|
TableName.ProjectMembership,
|
||||||
|
"id"
|
||||||
|
])
|
||||||
|
.where((subQb) => {
|
||||||
|
void subQb
|
||||||
|
.whereIn(`${TableName.ProjectUserMembershipRole}.role`, filter.roles as string[])
|
||||||
|
.orWhereIn(`${TableName.ProjectRoles}.slug`, filter.roles as string[]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.join<TUserEncryptionKeys>(
|
.join<TUserEncryptionKeys>(
|
||||||
TableName.UserEncryptionKey,
|
TableName.UserEncryptionKey,
|
||||||
|
@ -79,7 +79,8 @@ export const projectMembershipServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
includeGroupMembers,
|
includeGroupMembers,
|
||||||
projectId
|
projectId,
|
||||||
|
roles
|
||||||
}: TGetProjectMembershipDTO) => {
|
}: TGetProjectMembershipDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
@ -91,7 +92,7 @@ export const projectMembershipServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId);
|
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId, { roles });
|
||||||
|
|
||||||
// projectMembers[0].project
|
// projectMembers[0].project
|
||||||
if (includeGroupMembers) {
|
if (includeGroupMembers) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
export type TGetProjectMembershipDTO = { includeGroupMembers?: boolean } & TProjectPermission;
|
export type TGetProjectMembershipDTO = { includeGroupMembers?: boolean; roles?: string[] } & TProjectPermission;
|
||||||
export type TLeaveProjectDTO = Omit<TProjectPermission, "actorOrgId" | "actorAuthMethod">;
|
export type TLeaveProjectDTO = Omit<TProjectPermission, "actorOrgId" | "actorAuthMethod">;
|
||||||
export enum ProjectUserMembershipTemporaryMode {
|
export enum ProjectUserMembershipTemporaryMode {
|
||||||
Relative = "relative"
|
Relative = "relative"
|
||||||
|
@ -10,7 +10,8 @@ export enum SecretSync {
|
|||||||
TerraformCloud = "terraform-cloud",
|
TerraformCloud = "terraform-cloud",
|
||||||
Camunda = "camunda",
|
Camunda = "camunda",
|
||||||
Vercel = "vercel",
|
Vercel = "vercel",
|
||||||
Windmill = "windmill"
|
Windmill = "windmill",
|
||||||
|
TeamCity = "teamcity"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretSyncInitialSyncBehavior {
|
export enum SecretSyncInitialSyncBehavior {
|
||||||
|
@ -27,6 +27,7 @@ import { GCP_SYNC_LIST_OPTION } from "./gcp";
|
|||||||
import { GcpSyncFns } from "./gcp/gcp-sync-fns";
|
import { GcpSyncFns } from "./gcp/gcp-sync-fns";
|
||||||
import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec";
|
import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec";
|
||||||
import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns";
|
import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns";
|
||||||
|
import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity";
|
||||||
import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud";
|
import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud";
|
||||||
import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel";
|
import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel";
|
||||||
import { WINDMILL_SYNC_LIST_OPTION, WindmillSyncFns } from "./windmill";
|
import { WINDMILL_SYNC_LIST_OPTION, WindmillSyncFns } from "./windmill";
|
||||||
@ -43,7 +44,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
|
|||||||
[SecretSync.TerraformCloud]: TERRAFORM_CLOUD_SYNC_LIST_OPTION,
|
[SecretSync.TerraformCloud]: TERRAFORM_CLOUD_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Camunda]: CAMUNDA_SYNC_LIST_OPTION,
|
[SecretSync.Camunda]: CAMUNDA_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Vercel]: VERCEL_SYNC_LIST_OPTION,
|
[SecretSync.Vercel]: VERCEL_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Windmill]: WINDMILL_SYNC_LIST_OPTION
|
[SecretSync.Windmill]: WINDMILL_SYNC_LIST_OPTION,
|
||||||
|
[SecretSync.TeamCity]: TEAMCITY_SYNC_LIST_OPTION
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listSecretSyncOptions = () => {
|
export const listSecretSyncOptions = () => {
|
||||||
@ -140,6 +142,8 @@ export const SecretSyncFns = {
|
|||||||
return VercelSyncFns.syncSecrets(secretSync, secretMap);
|
return VercelSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
case SecretSync.Windmill:
|
case SecretSync.Windmill:
|
||||||
return WindmillSyncFns.syncSecrets(secretSync, secretMap);
|
return WindmillSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
|
case SecretSync.TeamCity:
|
||||||
|
return TeamCitySyncFns.syncSecrets(secretSync, secretMap);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
@ -199,6 +203,9 @@ export const SecretSyncFns = {
|
|||||||
case SecretSync.Windmill:
|
case SecretSync.Windmill:
|
||||||
secretMap = await WindmillSyncFns.getSecrets(secretSync);
|
secretMap = await WindmillSyncFns.getSecrets(secretSync);
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.TeamCity:
|
||||||
|
secretMap = await TeamCitySyncFns.getSecrets(secretSync);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
@ -252,6 +259,8 @@ export const SecretSyncFns = {
|
|||||||
return VercelSyncFns.removeSecrets(secretSync, secretMap);
|
return VercelSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
case SecretSync.Windmill:
|
case SecretSync.Windmill:
|
||||||
return WindmillSyncFns.removeSecrets(secretSync, secretMap);
|
return WindmillSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
|
case SecretSync.TeamCity:
|
||||||
|
return TeamCitySyncFns.removeSecrets(secretSync, secretMap);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
|
@ -13,7 +13,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
|||||||
[SecretSync.TerraformCloud]: "Terraform Cloud",
|
[SecretSync.TerraformCloud]: "Terraform Cloud",
|
||||||
[SecretSync.Camunda]: "Camunda",
|
[SecretSync.Camunda]: "Camunda",
|
||||||
[SecretSync.Vercel]: "Vercel",
|
[SecretSync.Vercel]: "Vercel",
|
||||||
[SecretSync.Windmill]: "Windmill"
|
[SecretSync.Windmill]: "Windmill",
|
||||||
|
[SecretSync.TeamCity]: "TeamCity"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||||
@ -28,5 +29,6 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
|||||||
[SecretSync.TerraformCloud]: AppConnection.TerraformCloud,
|
[SecretSync.TerraformCloud]: AppConnection.TerraformCloud,
|
||||||
[SecretSync.Camunda]: AppConnection.Camunda,
|
[SecretSync.Camunda]: AppConnection.Camunda,
|
||||||
[SecretSync.Vercel]: AppConnection.Vercel,
|
[SecretSync.Vercel]: AppConnection.Vercel,
|
||||||
[SecretSync.Windmill]: AppConnection.Windmill
|
[SecretSync.Windmill]: AppConnection.Windmill,
|
||||||
|
[SecretSync.TeamCity]: AppConnection.TeamCity
|
||||||
};
|
};
|
||||||
|
@ -356,8 +356,11 @@ export const secretSyncQueueFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (Object.hasOwn(secretMap, key)) {
|
if (Object.hasOwn(secretMap, key)) {
|
||||||
secretsToUpdate.push(secret);
|
// Only update secrets if the source value is not empty
|
||||||
if (importBehavior === SecretSyncImportBehavior.PrioritizeDestination) importedSecretMap[key] = secretData;
|
if (value) {
|
||||||
|
secretsToUpdate.push(secret);
|
||||||
|
if (importBehavior === SecretSyncImportBehavior.PrioritizeDestination) importedSecretMap[key] = secretData;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
secretsToCreate.push(secret);
|
secretsToCreate.push(secret);
|
||||||
importedSecretMap[key] = secretData;
|
importedSecretMap[key] = secretData;
|
||||||
|
@ -61,6 +61,12 @@ import {
|
|||||||
THumanitecSyncListItem,
|
THumanitecSyncListItem,
|
||||||
THumanitecSyncWithCredentials
|
THumanitecSyncWithCredentials
|
||||||
} from "./humanitec";
|
} from "./humanitec";
|
||||||
|
import {
|
||||||
|
TTeamCitySync,
|
||||||
|
TTeamCitySyncInput,
|
||||||
|
TTeamCitySyncListItem,
|
||||||
|
TTeamCitySyncWithCredentials
|
||||||
|
} from "./teamcity/teamcity-sync-types";
|
||||||
import {
|
import {
|
||||||
TTerraformCloudSync,
|
TTerraformCloudSync,
|
||||||
TTerraformCloudSyncInput,
|
TTerraformCloudSyncInput,
|
||||||
@ -81,7 +87,8 @@ export type TSecretSync =
|
|||||||
| TTerraformCloudSync
|
| TTerraformCloudSync
|
||||||
| TCamundaSync
|
| TCamundaSync
|
||||||
| TVercelSync
|
| TVercelSync
|
||||||
| TWindmillSync;
|
| TWindmillSync
|
||||||
|
| TTeamCitySync;
|
||||||
|
|
||||||
export type TSecretSyncWithCredentials =
|
export type TSecretSyncWithCredentials =
|
||||||
| TAwsParameterStoreSyncWithCredentials
|
| TAwsParameterStoreSyncWithCredentials
|
||||||
@ -95,7 +102,8 @@ export type TSecretSyncWithCredentials =
|
|||||||
| TTerraformCloudSyncWithCredentials
|
| TTerraformCloudSyncWithCredentials
|
||||||
| TCamundaSyncWithCredentials
|
| TCamundaSyncWithCredentials
|
||||||
| TVercelSyncWithCredentials
|
| TVercelSyncWithCredentials
|
||||||
| TWindmillSyncWithCredentials;
|
| TWindmillSyncWithCredentials
|
||||||
|
| TTeamCitySyncWithCredentials;
|
||||||
|
|
||||||
export type TSecretSyncInput =
|
export type TSecretSyncInput =
|
||||||
| TAwsParameterStoreSyncInput
|
| TAwsParameterStoreSyncInput
|
||||||
@ -109,7 +117,8 @@ export type TSecretSyncInput =
|
|||||||
| TTerraformCloudSyncInput
|
| TTerraformCloudSyncInput
|
||||||
| TCamundaSyncInput
|
| TCamundaSyncInput
|
||||||
| TVercelSyncInput
|
| TVercelSyncInput
|
||||||
| TWindmillSyncInput;
|
| TWindmillSyncInput
|
||||||
|
| TTeamCitySyncInput;
|
||||||
|
|
||||||
export type TSecretSyncListItem =
|
export type TSecretSyncListItem =
|
||||||
| TAwsParameterStoreSyncListItem
|
| TAwsParameterStoreSyncListItem
|
||||||
@ -123,7 +132,8 @@ export type TSecretSyncListItem =
|
|||||||
| TTerraformCloudSyncListItem
|
| TTerraformCloudSyncListItem
|
||||||
| TCamundaSyncListItem
|
| TCamundaSyncListItem
|
||||||
| TVercelSyncListItem
|
| TVercelSyncListItem
|
||||||
| TWindmillSyncListItem;
|
| TWindmillSyncListItem
|
||||||
|
| TTeamCitySyncListItem;
|
||||||
|
|
||||||
export type TSyncOptionsConfig = {
|
export type TSyncOptionsConfig = {
|
||||||
canImportSecrets: boolean;
|
canImportSecrets: boolean;
|
||||||
|
4
backend/src/services/secret-sync/teamcity/index.ts
Normal file
4
backend/src/services/secret-sync/teamcity/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./teamcity-sync-constants";
|
||||||
|
export * from "./teamcity-sync-fns";
|
||||||
|
export * from "./teamcity-sync-schemas";
|
||||||
|
export * from "./teamcity-sync-types";
|
@ -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 TEAMCITY_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||||
|
name: "TeamCity",
|
||||||
|
destination: SecretSync.TeamCity,
|
||||||
|
connection: AppConnection.TeamCity,
|
||||||
|
canImportSecrets: true
|
||||||
|
};
|
183
backend/src/services/secret-sync/teamcity/teamcity-sync-fns.ts
Normal file
183
backend/src/services/secret-sync/teamcity/teamcity-sync-fns.ts
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { getTeamCityInstanceUrl } from "@app/services/app-connection/teamcity";
|
||||||
|
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||||
|
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
import {
|
||||||
|
TDeleteTeamCityVariable,
|
||||||
|
TPostTeamCityVariable,
|
||||||
|
TTeamCityListVariables,
|
||||||
|
TTeamCityListVariablesResponse,
|
||||||
|
TTeamCitySyncWithCredentials
|
||||||
|
} from "@app/services/secret-sync/teamcity/teamcity-sync-types";
|
||||||
|
|
||||||
|
// Note: Most variables won't be returned with a value due to them being a "password" type (starting with "env.").
|
||||||
|
// TeamCity API returns empty string for password-type variables for security reasons.
|
||||||
|
const listTeamCityVariables = async ({ instanceUrl, accessToken, project, buildConfig }: TTeamCityListVariables) => {
|
||||||
|
const { data } = await request.get<TTeamCityListVariablesResponse>(
|
||||||
|
buildConfig
|
||||||
|
? `${instanceUrl}/app/rest/buildTypes/${encodeURIComponent(buildConfig)}/parameters`
|
||||||
|
: `${instanceUrl}/app/rest/projects/id:${encodeURIComponent(project)}/parameters`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Strips out "env." from map key, but the "name" field still has the original unaltered key.
|
||||||
|
return Object.fromEntries(
|
||||||
|
data.property.map((variable) => [
|
||||||
|
variable.name.startsWith("env.") ? variable.name.substring(4) : variable.name,
|
||||||
|
{ ...variable, value: variable.value || "" } // Password values will be empty strings from the API for security
|
||||||
|
])
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create and update both use the same method
|
||||||
|
const updateTeamCityVariable = async ({
|
||||||
|
instanceUrl,
|
||||||
|
accessToken,
|
||||||
|
project,
|
||||||
|
buildConfig,
|
||||||
|
key,
|
||||||
|
value
|
||||||
|
}: TPostTeamCityVariable) => {
|
||||||
|
return request.post(
|
||||||
|
buildConfig
|
||||||
|
? `${instanceUrl}/app/rest/buildTypes/${encodeURIComponent(buildConfig)}/parameters`
|
||||||
|
: `${instanceUrl}/app/rest/projects/id:${encodeURIComponent(project)}/parameters`,
|
||||||
|
{
|
||||||
|
name: key,
|
||||||
|
value,
|
||||||
|
type: {
|
||||||
|
rawValue: "password display='hidden'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteTeamCityVariable = async ({
|
||||||
|
instanceUrl,
|
||||||
|
accessToken,
|
||||||
|
project,
|
||||||
|
buildConfig,
|
||||||
|
key
|
||||||
|
}: TDeleteTeamCityVariable) => {
|
||||||
|
return request.delete(
|
||||||
|
buildConfig
|
||||||
|
? `${instanceUrl}/app/rest/buildTypes/${encodeURIComponent(buildConfig)}/parameters/${encodeURIComponent(key)}`
|
||||||
|
: `${instanceUrl}/app/rest/projects/id:${encodeURIComponent(project)}/parameters/${encodeURIComponent(key)}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TeamCitySyncFns = {
|
||||||
|
syncSecrets: async (secretSync: TTeamCitySyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
destinationConfig: { project, buildConfig }
|
||||||
|
} = secretSync;
|
||||||
|
|
||||||
|
const instanceUrl = await getTeamCityInstanceUrl(connection);
|
||||||
|
const { accessToken } = connection.credentials;
|
||||||
|
|
||||||
|
for await (const entry of Object.entries(secretMap)) {
|
||||||
|
const [key, { value }] = entry;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
instanceUrl,
|
||||||
|
accessToken,
|
||||||
|
project,
|
||||||
|
buildConfig,
|
||||||
|
key: `env.${key}`,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Replace every secret since TeamCity does not return secret values that we can cross-check
|
||||||
|
// No need to differenciate create / update because TeamCity uses the same method for both
|
||||||
|
await updateTeamCityVariable(payload);
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secretSync.syncOptions.disableSecretDeletion) return;
|
||||||
|
|
||||||
|
const variables = await listTeamCityVariables({ instanceUrl, accessToken, project, buildConfig });
|
||||||
|
|
||||||
|
for await (const [key, variable] of Object.entries(variables)) {
|
||||||
|
if (!(key in secretMap)) {
|
||||||
|
try {
|
||||||
|
await deleteTeamCityVariable({
|
||||||
|
key: variable.name, // We use variable.name instead of key because key is stripped of "env." prefix in listTeamCityVariables().
|
||||||
|
instanceUrl,
|
||||||
|
accessToken,
|
||||||
|
project,
|
||||||
|
buildConfig
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeSecrets: async (secretSync: TTeamCitySyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
destinationConfig: { project, buildConfig }
|
||||||
|
} = secretSync;
|
||||||
|
|
||||||
|
const instanceUrl = await getTeamCityInstanceUrl(connection);
|
||||||
|
const { accessToken } = connection.credentials;
|
||||||
|
|
||||||
|
const variables = await listTeamCityVariables({ instanceUrl, accessToken, project, buildConfig });
|
||||||
|
|
||||||
|
for await (const [key, variable] of Object.entries(variables)) {
|
||||||
|
if (key in secretMap) {
|
||||||
|
try {
|
||||||
|
await deleteTeamCityVariable({
|
||||||
|
key: variable.name, // We use variable.name instead of key because key is stripped of "env." prefix in listTeamCityVariables().
|
||||||
|
instanceUrl,
|
||||||
|
accessToken,
|
||||||
|
project,
|
||||||
|
buildConfig
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getSecrets: async (secretSync: TTeamCitySyncWithCredentials) => {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
destinationConfig: { project, buildConfig }
|
||||||
|
} = secretSync;
|
||||||
|
|
||||||
|
const instanceUrl = await getTeamCityInstanceUrl(connection);
|
||||||
|
const { accessToken } = connection.credentials;
|
||||||
|
|
||||||
|
return listTeamCityVariables({ instanceUrl, accessToken, project, buildConfig });
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,44 @@
|
|||||||
|
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 TeamCitySyncDestinationConfigSchema = z.object({
|
||||||
|
project: z.string().trim().min(1, "Project required").describe(SecretSyncs.DESTINATION_CONFIG.TEAMCITY.project),
|
||||||
|
buildConfig: z.string().trim().optional().describe(SecretSyncs.DESTINATION_CONFIG.TEAMCITY.buildConfig)
|
||||||
|
});
|
||||||
|
|
||||||
|
const TeamCitySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||||
|
|
||||||
|
export const TeamCitySyncSchema = BaseSecretSyncSchema(SecretSync.TeamCity, TeamCitySyncOptionsConfig).extend({
|
||||||
|
destination: z.literal(SecretSync.TeamCity),
|
||||||
|
destinationConfig: TeamCitySyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateTeamCitySyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.TeamCity,
|
||||||
|
TeamCitySyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: TeamCitySyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateTeamCitySyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.TeamCity,
|
||||||
|
TeamCitySyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: TeamCitySyncDestinationConfigSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const TeamCitySyncListItemSchema = z.object({
|
||||||
|
name: z.literal("TeamCity"),
|
||||||
|
connection: z.literal(AppConnection.TeamCity),
|
||||||
|
destination: z.literal(SecretSync.TeamCity),
|
||||||
|
canImportSecrets: z.literal(true)
|
||||||
|
});
|
@ -0,0 +1,46 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TTeamCityConnection } from "@app/services/app-connection/teamcity";
|
||||||
|
|
||||||
|
import { CreateTeamCitySyncSchema, TeamCitySyncListItemSchema, TeamCitySyncSchema } from "./teamcity-sync-schemas";
|
||||||
|
|
||||||
|
export type TTeamCitySync = z.infer<typeof TeamCitySyncSchema>;
|
||||||
|
|
||||||
|
export type TTeamCitySyncInput = z.infer<typeof CreateTeamCitySyncSchema>;
|
||||||
|
|
||||||
|
export type TTeamCitySyncListItem = z.infer<typeof TeamCitySyncListItemSchema>;
|
||||||
|
|
||||||
|
export type TTeamCitySyncWithCredentials = TTeamCitySync & {
|
||||||
|
connection: TTeamCityConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTeamCityVariable = {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
inherited?: boolean;
|
||||||
|
type: {
|
||||||
|
rawValue: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTeamCityListVariablesResponse = {
|
||||||
|
property: (TTeamCityVariable & { value?: string })[];
|
||||||
|
count: number;
|
||||||
|
href: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTeamCityListVariables = {
|
||||||
|
accessToken: string;
|
||||||
|
instanceUrl: string;
|
||||||
|
project: string;
|
||||||
|
buildConfig?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TPostTeamCityVariable = TTeamCityListVariables & {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeleteTeamCityVariable = TTeamCityListVariables & {
|
||||||
|
key: string;
|
||||||
|
};
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Available"
|
||||||
|
openapi: "GET /api/v1/app-connections/ldap/available"
|
||||||
|
---
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/app-connections/ldap"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [LDAP Connections](/integrations/app-connections/ldap) to learn how to obtain
|
||||||
|
the required credentials.
|
||||||
|
</Note>
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/app-connections/ldap/{connectionId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/app-connections/ldap/{connectionId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/app-connections/ldap/connection-name/{connectionName}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/app-connections/ldap"
|
||||||
|
---
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/app-connections/ldap/{connectionId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [LDAP Connections](/integrations/app-connections/ldap) to learn how to obtain
|
||||||
|
the required credentials.
|
||||||
|
</Note>
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Available"
|
||||||
|
openapi: "GET /api/v1/app-connections/teamcity/available"
|
||||||
|
---
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/app-connections/teamcity"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [TeamCity Connections](/integrations/app-connections/teamcity) to learn how to obtain
|
||||||
|
the required credentials.
|
||||||
|
</Note>
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/app-connections/teamcity/{connectionId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/app-connections/teamcity/{connectionId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/app-connections/teamcity/connection-name/{connectionName}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/app-connections/teamcity"
|
||||||
|
---
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/app-connections/teamcity/{connectionId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [TeamCity Connections](/integrations/app-connections/teamcity) to learn how to obtain
|
||||||
|
the required credentials.
|
||||||
|
</Note>
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v2/secret-rotations/aws-iam-user-secret"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [AWS IAM User Secret Rotations](/documentation/platform/secret-rotation/aws-iam-user-secret) to learn how to obtain the
|
||||||
|
required parameters.
|
||||||
|
</Note>
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v2/secret-rotations/aws-iam-user-secret/{rotationId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/aws-iam-user-secret/{rotationId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/aws-iam-user-secret/rotation-name/{rotationName}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get Credentials by ID"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/aws-iam-user-secret/{rotationId}/generated-credentials"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/aws-iam-user-secret"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Rotate Secrets"
|
||||||
|
openapi: "POST /api/v2/secret-rotations/aws-iam-user-secret/{rotationId}/rotate-secrets"
|
||||||
|
---
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v2/secret-rotations/aws-iam-user-secret/{rotationId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [AWS IAM User Secret Rotations](/documentation/platform/secret-rotation/aws-iam-user-secret) to learn how to obtain the
|
||||||
|
required parameters.
|
||||||
|
</Note>
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v2/secret-rotations/ldap-password"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [LDAP Password Rotations](/documentation/platform/secret-rotation/ldap-password) to learn how to obtain the
|
||||||
|
required parameters.
|
||||||
|
</Note>
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v2/secret-rotations/ldap-password/{rotationId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/ldap-password/{rotationId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/ldap-password/rotation-name/{rotationName}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get Credentials by ID"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/ldap-password/{rotationId}/generated-credentials"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/ldap-password"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Rotate Secrets"
|
||||||
|
openapi: "POST /api/v2/secret-rotations/ldap-password/{rotationId}/rotate-secrets"
|
||||||
|
---
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v2/secret-rotations/ldap-password/{rotationId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [LDAP Rotations](/documentation/platform/secret-rotation/ldap-password) to learn how to obtain the
|
||||||
|
required parameters.
|
||||||
|
</Note>
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/teamcity"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/secret-syncs/teamcity/{syncId}"
|
||||||
|
---
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user