mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 01:26:09 +00:00
Address PR comments for Azure Client Secret Rotation
This commit is contained in:
+25
-18
@@ -1,3 +1,5 @@
|
|||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AzureAddPasswordResponse,
|
AzureAddPasswordResponse,
|
||||||
TAzureClientSecretRotationGeneratedCredentials,
|
TAzureClientSecretRotationGeneratedCredentials,
|
||||||
@@ -11,7 +13,7 @@ import {
|
|||||||
TRotationFactoryRotateCredentials
|
TRotationFactoryRotateCredentials
|
||||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||||
import { request } from "@app/lib/config/request";
|
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";
|
import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-client-secrets";
|
||||||
|
|
||||||
const GRAPH_API_BASE = "https://graph.microsoft.com/v1.0";
|
const GRAPH_API_BASE = "https://graph.microsoft.com/v1.0";
|
||||||
@@ -23,8 +25,7 @@ export const azureClientSecretRotationFactory: TRotationFactory<
|
|||||||
const {
|
const {
|
||||||
connection,
|
connection,
|
||||||
parameters: { appId },
|
parameters: { appId },
|
||||||
secretsMapping,
|
secretsMapping
|
||||||
rotationInterval
|
|
||||||
} = secretRotation;
|
} = secretRotation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,11 +35,6 @@ export const azureClientSecretRotationFactory: TRotationFactory<
|
|||||||
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
|
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
|
||||||
const endpoint = `${GRAPH_API_BASE}/applications/${appId}/addPassword`;
|
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 now = new Date();
|
||||||
const formattedDate = `${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(
|
const formattedDate = `${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(
|
||||||
2,
|
2,
|
||||||
@@ -50,8 +46,7 @@ export const azureClientSecretRotationFactory: TRotationFactory<
|
|||||||
endpoint,
|
endpoint,
|
||||||
{
|
{
|
||||||
passwordCredential: {
|
passwordCredential: {
|
||||||
displayName: `Infisical Rotated Secret (${formattedDate})`,
|
displayName: `Infisical Rotated Secret (${formattedDate})`
|
||||||
endDateTime: endDateTime.toISOString()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -70,9 +65,15 @@ export const azureClientSecretRotationFactory: TRotationFactory<
|
|||||||
clientSecret: data.secretText,
|
clientSecret: data.secretText,
|
||||||
clientId: data.keyId
|
clientId: data.keyId
|
||||||
};
|
};
|
||||||
} catch (err: unknown) {
|
} catch (error: unknown) {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
if (error instanceof AxiosError) {
|
||||||
throw new Error(`Failed to add client secret to Azure app ${appId}: ${message}`);
|
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 accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
|
||||||
const endpoint = `${GRAPH_API_BASE}/applications/${appId}/removePassword`;
|
const endpoint = `${GRAPH_API_BASE}/applications/${appId}/removePassword`;
|
||||||
|
|
||||||
await blockLocalAndPrivateIpAddresses(endpoint);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await request.post(
|
await request.post(
|
||||||
endpoint,
|
endpoint,
|
||||||
@@ -96,9 +95,17 @@ export const azureClientSecretRotationFactory: TRotationFactory<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err: unknown) {
|
} catch (error: unknown) {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
if (error instanceof AxiosError) {
|
||||||
throw new Error(`Failed to remove client secret with keyId ${clientId} from app ${appId}: ${message}`);
|
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"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-5
@@ -21,11 +21,7 @@ export const AzureClientSecretRotationGeneratedCredentialsSchema = z
|
|||||||
|
|
||||||
const AzureClientSecretRotationParametersSchema = z.object({
|
const AzureClientSecretRotationParametersSchema = z.object({
|
||||||
appId: z.string().trim().min(1, "App ID Required").describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.appId),
|
appId: z.string().trim().min(1, "App ID Required").describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.appId),
|
||||||
appName: z
|
appName: z.string().trim().describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.appName).optional()
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.min(1, "App Name Required")
|
|
||||||
.describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.appName)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const AzureClientSecretRotationSecretsMappingSchema = z.object({
|
const AzureClientSecretRotationSecretsMappingSchema = z.object({
|
||||||
|
|||||||
@@ -1875,6 +1875,10 @@ export const AppConnections = {
|
|||||||
TEAMCITY: {
|
TEAMCITY: {
|
||||||
instanceUrl: "The TeamCity instance URL to connect with.",
|
instanceUrl: "The TeamCity instance URL to connect with.",
|
||||||
accessToken: "The access token to use to connect with TeamCity."
|
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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+7
-3
@@ -11,12 +11,16 @@ import {
|
|||||||
import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums";
|
import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums";
|
||||||
|
|
||||||
export const AzureClientSecretsConnectionOAuthInputCredentialsSchema = z.object({
|
export const AzureClientSecretsConnectionOAuthInputCredentialsSchema = z.object({
|
||||||
code: z.string().trim().min(1, "OAuth code required"),
|
code: z.string().trim().min(1, "OAuth code required").describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.code),
|
||||||
tenantId: z.string().trim().optional()
|
tenantId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Tenant ID required")
|
||||||
|
.describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.tenantId)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AzureClientSecretsConnectionOAuthOutputCredentialsSchema = z.object({
|
export const AzureClientSecretsConnectionOAuthOutputCredentialsSchema = z.object({
|
||||||
tenantId: z.string().optional(),
|
tenantId: z.string(),
|
||||||
accessToken: z.string(),
|
accessToken: z.string(),
|
||||||
refreshToken: z.string(),
|
refreshToken: z.string(),
|
||||||
expiresAt: z.number()
|
expiresAt: z.number()
|
||||||
|
|||||||
-2
@@ -1,6 +1,5 @@
|
|||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
|
||||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
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";
|
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 accessToken = await getAzureConnectionAccessToken(appConnection.id, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
const graphEndpoint = `https://graph.microsoft.com/v1.0/applications`;
|
const graphEndpoint = `https://graph.microsoft.com/v1.0/applications`;
|
||||||
await blockLocalAndPrivateIpAddresses(graphEndpoint);
|
|
||||||
|
|
||||||
const apps: TAzureRegisteredApp[] = [];
|
const apps: TAzureRegisteredApp[] = [];
|
||||||
let nextLink = graphEndpoint;
|
let nextLink = graphEndpoint;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ openapi: "POST /api/v1/app-connections/azure-client-secrets"
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Check out the configuration docs for [Azure Client Secret Connections](/integrations/app-connections/azure-client-secrets) to learn how to obtain the
|
Azure Client Secret Connections must be created through the Infisical UI.
|
||||||
required credentials.
|
Check out the configuration docs for [Azure Client Secret Connections](/integrations/app-connections/azure-client-secrets) for a step-by-step
|
||||||
|
guide.
|
||||||
</Note>
|
</Note>
|
||||||
@@ -4,6 +4,7 @@ openapi: "PATCH /api/v1/app-connections/azure-client-secrets/{connectionId}"
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Check out the configuration docs for [Azure Client Secret Connections](/integrations/app-connections/azure-client-secrets) to learn how to obtain the
|
Azure Client Secret Connections must be updated through the Infisical UI.
|
||||||
required credentials.
|
Check out the configuration docs for [Azure Client Secret Connections](/integrations/app-connections/azure-client-secrets) for a step-by-step
|
||||||
</Note>
|
guide.
|
||||||
|
</Note>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: "Learn how to automatically rotate Azure Client Secrets."
|
|||||||
|
|
||||||
## Prerequisites
|
## 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
|
## Create an Azure Client Secret Rotation in Infisical
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 566 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 551 KiB After Width: | Height: | Size: 576 KiB |
@@ -42,9 +42,9 @@ Infisical currently only supports one method for connecting to Azure, which is O
|
|||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Add your application credentials to Infisical">
|
<Step title="Add your application credentials to Infisical">
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -433,8 +433,8 @@
|
|||||||
"integrations/app-connections/auth0",
|
"integrations/app-connections/auth0",
|
||||||
"integrations/app-connections/aws",
|
"integrations/app-connections/aws",
|
||||||
"integrations/app-connections/azure-app-configuration",
|
"integrations/app-connections/azure-app-configuration",
|
||||||
"integrations/app-connections/azure-key-vault",
|
|
||||||
"integrations/app-connections/azure-client-secrets",
|
"integrations/app-connections/azure-client-secrets",
|
||||||
|
"integrations/app-connections/azure-key-vault",
|
||||||
"integrations/app-connections/camunda",
|
"integrations/app-connections/camunda",
|
||||||
"integrations/app-connections/databricks",
|
"integrations/app-connections/databricks",
|
||||||
"integrations/app-connections/gcp",
|
"integrations/app-connections/gcp",
|
||||||
|
|||||||
+2
-2
@@ -38,8 +38,8 @@ export const AzureClientSecretRotationParametersFields = () => {
|
|||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
Ensure that your connection has the{" "}
|
Ensure that your connection has the{" "}
|
||||||
<span className="font-semibold">read_clients</span> permission and the application
|
<span className="font-semibold">Application.ReadWrite.All</span> permission and
|
||||||
exists in the connection's audience.
|
the application exists in Azure.
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const SECRET_ROTATION_MAP: Record<
|
|||||||
[SecretRotation.AzureClientSecret]: {
|
[SecretRotation.AzureClientSecret]: {
|
||||||
name: "Azure Client Secret",
|
name: "Azure Client Secret",
|
||||||
image: "Microsoft Azure.png",
|
image: "Microsoft Azure.png",
|
||||||
size: 35
|
size: 65
|
||||||
},
|
},
|
||||||
[SecretRotation.LdapPassword]: {
|
[SecretRotation.LdapPassword]: {
|
||||||
name: "LDAP Password",
|
name: "LDAP Password",
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ export type TAzureClientSecretsConnection = TRootAppConnection & {
|
|||||||
method: AzureClientSecretsConnectionMethod.OAuth;
|
method: AzureClientSecretsConnectionMethod.OAuth;
|
||||||
credentials: {
|
credentials: {
|
||||||
code: string;
|
code: string;
|
||||||
tenantId?: string;
|
tenantId: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-3
@@ -27,7 +27,7 @@ type Props = {
|
|||||||
const formSchema = genericAppConnectionFieldsSchema.extend({
|
const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||||
app: z.literal(AppConnection.AzureClientSecrets),
|
app: z.literal(AppConnection.AzureClientSecrets),
|
||||||
method: z.nativeEnum(AzureClientSecretsConnectionMethod),
|
method: z.nativeEnum(AzureClientSecretsConnectionMethod),
|
||||||
tenantId: z.string().trim().optional()
|
tenantId: z.string().trim()
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormData = z.infer<typeof formSchema>;
|
type FormData = z.infer<typeof formSchema>;
|
||||||
@@ -97,10 +97,9 @@ export const AzureClientSecretsConnectionForm = ({ appConnection }: Props) => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
tooltipText="The active Directory (Entra ID) Tenant ID."
|
tooltipText="The Active Directory (Entra ID) Tenant ID."
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
label="Tenant ID"
|
label="Tenant ID"
|
||||||
isOptional
|
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
>
|
>
|
||||||
<Input {...field} placeholder="e4f34ea5-ad23-4291-8585-66d20d603cc8" />
|
<Input {...field} placeholder="e4f34ea5-ad23-4291-8585-66d20d603cc8" />
|
||||||
|
|||||||
Reference in New Issue
Block a user