diff --git a/backend/src/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-fns.ts b/backend/src/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-fns.ts index 5d0b7b109..7363e18b2 100644 --- a/backend/src/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-fns.ts +++ b/backend/src/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-fns.ts @@ -1,3 +1,5 @@ +import { AxiosError } from "axios"; + import { AzureAddPasswordResponse, TAzureClientSecretRotationGeneratedCredentials, @@ -11,7 +13,7 @@ import { TRotationFactoryRotateCredentials } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types"; import { request } from "@app/lib/config/request"; -import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; +import { BadRequestError } from "@app/lib/errors"; import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-client-secrets"; const GRAPH_API_BASE = "https://graph.microsoft.com/v1.0"; @@ -23,8 +25,7 @@ export const azureClientSecretRotationFactory: TRotationFactory< const { connection, parameters: { appId }, - secretsMapping, - rotationInterval + secretsMapping } = secretRotation; /** @@ -34,11 +35,6 @@ export const azureClientSecretRotationFactory: TRotationFactory< const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService); const endpoint = `${GRAPH_API_BASE}/applications/${appId}/addPassword`; - await blockLocalAndPrivateIpAddresses(endpoint); - - const endDateTime = new Date(); - endDateTime.setDate(endDateTime.getDate() + rotationInterval); - const now = new Date(); const formattedDate = `${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart( 2, @@ -50,8 +46,7 @@ export const azureClientSecretRotationFactory: TRotationFactory< endpoint, { passwordCredential: { - displayName: `Infisical Rotated Secret (${formattedDate})`, - endDateTime: endDateTime.toISOString() + displayName: `Infisical Rotated Secret (${formattedDate})` } }, { @@ -70,9 +65,15 @@ export const azureClientSecretRotationFactory: TRotationFactory< clientSecret: data.secretText, clientId: data.keyId }; - } catch (err: unknown) { - const message = err instanceof Error ? err.message : String(err); - throw new Error(`Failed to add client secret to Azure app ${appId}: ${message}`); + } catch (error: unknown) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to add client secret to Azure app ${appId}: ${error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to validate connection: verify credentials" + }); } }; @@ -83,8 +84,6 @@ export const azureClientSecretRotationFactory: TRotationFactory< const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService); const endpoint = `${GRAPH_API_BASE}/applications/${appId}/removePassword`; - await blockLocalAndPrivateIpAddresses(endpoint); - try { await request.post( endpoint, @@ -96,9 +95,17 @@ export const azureClientSecretRotationFactory: TRotationFactory< } } ); - } catch (err: unknown) { - const message = err instanceof Error ? err.message : String(err); - throw new Error(`Failed to remove client secret with keyId ${clientId} from app ${appId}: ${message}`); + } catch (error: unknown) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to remove client secret with keyId ${clientId} from app ${appId}: ${ + error.message || "Unknown error" + }` + }); + } + throw new BadRequestError({ + message: "Unable to validate connection: verify credentials" + }); } }; diff --git a/backend/src/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-schemas.ts b/backend/src/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-schemas.ts index af5448467..b8a313a86 100644 --- a/backend/src/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-schemas.ts +++ b/backend/src/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-schemas.ts @@ -21,11 +21,7 @@ export const AzureClientSecretRotationGeneratedCredentialsSchema = z const AzureClientSecretRotationParametersSchema = z.object({ appId: z.string().trim().min(1, "App ID Required").describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.appId), - appName: z - .string() - .trim() - .min(1, "App Name Required") - .describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.appName) + appName: z.string().trim().describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.appName).optional() }); const AzureClientSecretRotationSecretsMappingSchema = z.object({ diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index b8ea6bf7e..9a5f1a48f 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -1875,6 +1875,10 @@ export const AppConnections = { TEAMCITY: { instanceUrl: "The TeamCity instance URL to connect with.", accessToken: "The access token to use to connect with TeamCity." + }, + AZURE_CLIENT_SECRETS: { + code: "The OAuth code to use to connect with Azure Client Secrets.", + tenantId: "The Tenant ID to use to connect with Azure Client Secrets." } } }; diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts index 962125b5a..2b4e65a13 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts @@ -11,12 +11,16 @@ import { import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums"; export const AzureClientSecretsConnectionOAuthInputCredentialsSchema = z.object({ - code: z.string().trim().min(1, "OAuth code required"), - tenantId: z.string().trim().optional() + code: z.string().trim().min(1, "OAuth code required").describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.code), + tenantId: z + .string() + .trim() + .min(1, "Tenant ID required") + .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.tenantId) }); export const AzureClientSecretsConnectionOAuthOutputCredentialsSchema = z.object({ - tenantId: z.string().optional(), + tenantId: z.string(), accessToken: z.string(), refreshToken: z.string(), expiresAt: z.number() diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-service.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-service.ts index e95178973..336c48d58 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-service.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-service.ts @@ -1,6 +1,5 @@ import { request } from "@app/lib/config/request"; import { OrgServiceActor } from "@app/lib/types"; -import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns"; @@ -26,7 +25,6 @@ const listAzureRegisteredApps = async ( const accessToken = await getAzureConnectionAccessToken(appConnection.id, appConnectionDAL, kmsService); const graphEndpoint = `https://graph.microsoft.com/v1.0/applications`; - await blockLocalAndPrivateIpAddresses(graphEndpoint); const apps: TAzureRegisteredApp[] = []; let nextLink = graphEndpoint; diff --git a/docs/api-reference/endpoints/app-connections/azure-client-secret/create.mdx b/docs/api-reference/endpoints/app-connections/azure-client-secret/create.mdx index 16a384949..c1c6bd6a8 100644 --- a/docs/api-reference/endpoints/app-connections/azure-client-secret/create.mdx +++ b/docs/api-reference/endpoints/app-connections/azure-client-secret/create.mdx @@ -4,6 +4,7 @@ openapi: "POST /api/v1/app-connections/azure-client-secrets" --- - Check out the configuration docs for [Azure Client Secret Connections](/integrations/app-connections/azure-client-secrets) to learn how to obtain the - required credentials. + Azure Client Secret Connections must be created through the Infisical UI. + Check out the configuration docs for [Azure Client Secret Connections](/integrations/app-connections/azure-client-secrets) for a step-by-step + guide. \ No newline at end of file diff --git a/docs/api-reference/endpoints/app-connections/azure-client-secret/update.mdx b/docs/api-reference/endpoints/app-connections/azure-client-secret/update.mdx index 05248e5f1..f60993285 100644 --- a/docs/api-reference/endpoints/app-connections/azure-client-secret/update.mdx +++ b/docs/api-reference/endpoints/app-connections/azure-client-secret/update.mdx @@ -4,6 +4,7 @@ openapi: "PATCH /api/v1/app-connections/azure-client-secrets/{connectionId}" --- - Check out the configuration docs for [Azure Client Secret Connections](/integrations/app-connections/azure-client-secrets) to learn how to obtain the - required credentials. - \ No newline at end of file + Azure Client Secret Connections must be updated through the Infisical UI. + Check out the configuration docs for [Azure Client Secret Connections](/integrations/app-connections/azure-client-secrets) for a step-by-step + guide. + diff --git a/docs/documentation/platform/secret-rotation/azure-client-secret.mdx b/docs/documentation/platform/secret-rotation/azure-client-secret.mdx index 4997e80d0..d377e0667 100644 --- a/docs/documentation/platform/secret-rotation/azure-client-secret.mdx +++ b/docs/documentation/platform/secret-rotation/azure-client-secret.mdx @@ -5,7 +5,7 @@ description: "Learn how to automatically rotate Azure Client Secrets." ## Prerequisites -- Create an [Azure Client Secret Connection](/integrations/app-connections/azure-client-secrets) with the required **Secret Rotation** audience and permissions +- Create an [Azure Client Secret Connection](/integrations/app-connections/azure-client-secrets). ## Create an Azure Client Secret Rotation in Infisical diff --git a/docs/images/app-connections/azure/client-secrets/config-credentials-1.png b/docs/images/app-connections/azure/client-secrets/config-credentials-1.png new file mode 100644 index 000000000..954f8aeb9 Binary files /dev/null and b/docs/images/app-connections/azure/client-secrets/config-credentials-1.png differ diff --git a/docs/images/secret-rotations-v2/azure-client-secret/azure-app-client-id.png b/docs/images/secret-rotations-v2/azure-client-secret/azure-app-client-id.png index 488263071..916f0224f 100644 Binary files a/docs/images/secret-rotations-v2/azure-client-secret/azure-app-client-id.png and b/docs/images/secret-rotations-v2/azure-client-secret/azure-app-client-id.png differ diff --git a/docs/integrations/app-connections/azure-client-secrets.mdx b/docs/integrations/app-connections/azure-client-secrets.mdx index 61749d64d..7e50348d2 100644 --- a/docs/integrations/app-connections/azure-client-secrets.mdx +++ b/docs/integrations/app-connections/azure-client-secrets.mdx @@ -42,9 +42,9 @@ Infisical currently only supports one method for connecting to Azure, which is O - Obtain the **Application (Client) ID** in Overview and generate a **Client Secret** in Certificate & secrets for your Azure application. + Obtain the **Application (Client) ID** and **Directory (Tenant) ID** in Overview and generate a **Client Secret** in Certificate & secrets for your Azure application. - ![Azure client secrets](../../images/integrations/azure-app-configuration/config-credentials-1.png) + ![Azure client secrets](../../images/app-connections/azure/client-secrets/config-credentials-1.png) ![Azure client secrets](../../images/integrations/azure-app-configuration/config-credentials-2.png) ![Azure client secrets](../../images/integrations/azure-app-configuration/config-credentials-3.png) diff --git a/docs/mint.json b/docs/mint.json index 9753d6a68..80e725b6f 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -433,8 +433,8 @@ "integrations/app-connections/auth0", "integrations/app-connections/aws", "integrations/app-connections/azure-app-configuration", - "integrations/app-connections/azure-key-vault", "integrations/app-connections/azure-client-secrets", + "integrations/app-connections/azure-key-vault", "integrations/app-connections/camunda", "integrations/app-connections/databricks", "integrations/app-connections/gcp", diff --git a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ParametersFields/AzureClientSecretRotationParametersFields.tsx b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ParametersFields/AzureClientSecretRotationParametersFields.tsx index ceee60dec..982e2a392 100644 --- a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ParametersFields/AzureClientSecretRotationParametersFields.tsx +++ b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ParametersFields/AzureClientSecretRotationParametersFields.tsx @@ -38,8 +38,8 @@ export const AzureClientSecretRotationParametersFields = () => { content={ <> Ensure that your connection has the{" "} - read_clients permission and the application - exists in the connection's audience. + Application.ReadWrite.All permission and + the application exists in Azure. } > diff --git a/frontend/src/helpers/secretRotationsV2.ts b/frontend/src/helpers/secretRotationsV2.ts index fd96e5a83..451658135 100644 --- a/frontend/src/helpers/secretRotationsV2.ts +++ b/frontend/src/helpers/secretRotationsV2.ts @@ -23,7 +23,7 @@ export const SECRET_ROTATION_MAP: Record< [SecretRotation.AzureClientSecret]: { name: "Azure Client Secret", image: "Microsoft Azure.png", - size: 35 + size: 65 }, [SecretRotation.LdapPassword]: { name: "LDAP Password", diff --git a/frontend/src/hooks/api/appConnections/types/azure-client-secrets-connection.ts b/frontend/src/hooks/api/appConnections/types/azure-client-secrets-connection.ts index a7587d3ff..04ad167d2 100644 --- a/frontend/src/hooks/api/appConnections/types/azure-client-secrets-connection.ts +++ b/frontend/src/hooks/api/appConnections/types/azure-client-secrets-connection.ts @@ -11,6 +11,6 @@ export type TAzureClientSecretsConnection = TRootAppConnection & { method: AzureClientSecretsConnectionMethod.OAuth; credentials: { code: string; - tenantId?: string; + tenantId: string; }; }; diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx index 8af319fc7..4b7071f61 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx @@ -27,7 +27,7 @@ type Props = { const formSchema = genericAppConnectionFieldsSchema.extend({ app: z.literal(AppConnection.AzureClientSecrets), method: z.nativeEnum(AzureClientSecretsConnectionMethod), - tenantId: z.string().trim().optional() + tenantId: z.string().trim() }); type FormData = z.infer; @@ -97,10 +97,9 @@ export const AzureClientSecretsConnectionForm = ({ appConnection }: Props) => { control={control} render={({ field, fieldState: { error } }) => (