mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-19 19:03:36 +00:00
Add Azure Client Secrets Auth to Azure App Connections
This commit is contained in:
+2
-1
@@ -1,3 +1,4 @@
|
||||
export enum AzureAppConfigurationConnectionMethod {
|
||||
OAuth = "oauth"
|
||||
OAuth = "oauth",
|
||||
ClientSecret = "client-secret"
|
||||
}
|
||||
|
||||
+101
-57
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-case-declarations */
|
||||
import { AxiosError, AxiosResponse } from "axios";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
@@ -19,7 +20,10 @@ export const getAzureAppConfigurationConnectionListItem = () => {
|
||||
return {
|
||||
name: "Azure App Configuration" as const,
|
||||
app: AppConnection.AzureAppConfiguration as const,
|
||||
methods: Object.values(AzureAppConfigurationConnectionMethod) as [AzureAppConfigurationConnectionMethod.OAuth],
|
||||
methods: Object.values(AzureAppConfigurationConnectionMethod) as [
|
||||
AzureAppConfigurationConnectionMethod.OAuth,
|
||||
AzureAppConfigurationConnectionMethod.ClientSecret
|
||||
],
|
||||
oauthClientId: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID
|
||||
};
|
||||
};
|
||||
@@ -35,71 +39,111 @@ export const validateAzureAppConfigurationConnectionCredentials = async (
|
||||
SITE_URL
|
||||
} = getConfig();
|
||||
|
||||
if (
|
||||
!INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID ||
|
||||
!INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET
|
||||
) {
|
||||
throw new InternalServerError({
|
||||
message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured`
|
||||
});
|
||||
}
|
||||
|
||||
let tokenResp: AxiosResponse<ExchangeCodeAzureResponse> | null = null;
|
||||
let tokenError: AxiosError | null = null;
|
||||
|
||||
try {
|
||||
tokenResp = await request.post<ExchangeCodeAzureResponse>(
|
||||
IntegrationUrls.AZURE_TOKEN_URL.replace("common", inputCredentials.tenantId || "common"),
|
||||
new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
code: inputCredentials.code,
|
||||
scope: `openid offline_access https://azconfig.io/.default`,
|
||||
client_id: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID,
|
||||
client_secret: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET,
|
||||
redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback`
|
||||
})
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof AxiosError) {
|
||||
tokenError = e;
|
||||
} else {
|
||||
throw new BadRequestError({
|
||||
message: `Unable to validate connection: verify credentials`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenError) {
|
||||
if (tokenError instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to get access token: ${
|
||||
(tokenError?.response?.data as { error_description?: string })?.error_description || "Unknown error"
|
||||
}`
|
||||
});
|
||||
} else {
|
||||
throw new InternalServerError({
|
||||
message: "Failed to get access token"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!tokenResp) {
|
||||
throw new InternalServerError({
|
||||
message: `Failed to get access token: Token was empty with no error`
|
||||
});
|
||||
}
|
||||
|
||||
switch (method) {
|
||||
case AzureAppConfigurationConnectionMethod.OAuth:
|
||||
if (
|
||||
!INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID ||
|
||||
!INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET
|
||||
) {
|
||||
throw new InternalServerError({
|
||||
message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured`
|
||||
});
|
||||
}
|
||||
|
||||
let tokenResp: AxiosResponse<ExchangeCodeAzureResponse> | null = null;
|
||||
let tokenError: AxiosError | null = null;
|
||||
const oauthCredentials = inputCredentials as { code: string; tenantId?: string };
|
||||
try {
|
||||
tokenResp = await request.post<ExchangeCodeAzureResponse>(
|
||||
IntegrationUrls.AZURE_TOKEN_URL.replace("common", oauthCredentials.tenantId || "common"),
|
||||
new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
code: oauthCredentials.code,
|
||||
scope: `openid offline_access https://azconfig.io/.default`,
|
||||
client_id: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID,
|
||||
client_secret: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET,
|
||||
redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback`
|
||||
})
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof AxiosError) {
|
||||
tokenError = e;
|
||||
} else {
|
||||
throw new BadRequestError({
|
||||
message: `Unable to validate connection: verify credentials`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenError) {
|
||||
if (tokenError instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to get access token: ${
|
||||
(tokenError?.response?.data as { error_description?: string })?.error_description || "Unknown error"
|
||||
}`
|
||||
});
|
||||
} else {
|
||||
throw new InternalServerError({
|
||||
message: "Failed to get access token"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!tokenResp) {
|
||||
throw new InternalServerError({
|
||||
message: `Failed to get access token: Token was empty with no error`
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
tenantId: inputCredentials.tenantId,
|
||||
tenantId: oauthCredentials.tenantId,
|
||||
accessToken: tokenResp.data.access_token,
|
||||
refreshToken: tokenResp.data.refresh_token,
|
||||
expiresAt: Date.now() + tokenResp.data.expires_in * 1000
|
||||
};
|
||||
|
||||
case AzureAppConfigurationConnectionMethod.ClientSecret:
|
||||
const { tenantId, clientId, clientSecret } = inputCredentials as {
|
||||
tenantId: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
};
|
||||
|
||||
try {
|
||||
const { data: clientData } = await request.post<ExchangeCodeAzureResponse>(
|
||||
IntegrationUrls.AZURE_TOKEN_URL.replace("common", tenantId || "common"),
|
||||
new URLSearchParams({
|
||||
grant_type: "client_credentials",
|
||||
scope: `https://azconfig.io/.default`,
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
tenantId,
|
||||
accessToken: clientData.access_token,
|
||||
expiresAt: Date.now() + clientData.expires_in * 1000,
|
||||
clientId,
|
||||
clientSecret
|
||||
};
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to get access token: ${
|
||||
(e?.response?.data as { error_description?: string })?.error_description || "Unknown error"
|
||||
}`
|
||||
});
|
||||
} else {
|
||||
throw new InternalServerError({
|
||||
message: "Failed to get access token"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InternalServerError({
|
||||
message: `Unhandled Azure connection method: ${method as AzureAppConfigurationConnectionMethod}`
|
||||
message: `Unhandled Azure App Configuration connection method: ${method as AzureAppConfigurationConnectionMethod}`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
+49
-3
@@ -22,6 +22,29 @@ export const AzureAppConfigurationConnectionOAuthOutputCredentialsSchema = z.obj
|
||||
expiresAt: z.number()
|
||||
});
|
||||
|
||||
export const AzureAppConfigurationConnectionClientSecretInputCredentialsSchema = z.object({
|
||||
clientId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.trim()
|
||||
.min(1, "Client ID required")
|
||||
.max(50, "Client ID must be at most 50 characters long"),
|
||||
clientSecret: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Client Secret required")
|
||||
.max(50, "Client Secret must be at most 50 characters long"),
|
||||
tenantId: z.string().uuid().trim().min(1, "Tenant ID required")
|
||||
});
|
||||
|
||||
export const AzureAppConfigurationConnectionClientSecretOutputCredentialsSchema = z.object({
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string(),
|
||||
tenantId: z.string(),
|
||||
accessToken: z.string(),
|
||||
expiresAt: z.number()
|
||||
});
|
||||
|
||||
export const ValidateAzureAppConfigurationConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z
|
||||
@@ -30,6 +53,14 @@ export const ValidateAzureAppConfigurationConnectionCredentialsSchema = z.discri
|
||||
credentials: AzureAppConfigurationConnectionOAuthInputCredentialsSchema.describe(
|
||||
AppConnections.CREATE(AppConnection.AzureAppConfiguration).credentials
|
||||
)
|
||||
}),
|
||||
z.object({
|
||||
method: z
|
||||
.literal(AzureAppConfigurationConnectionMethod.ClientSecret)
|
||||
.describe(AppConnections.CREATE(AppConnection.AzureAppConfiguration).method),
|
||||
credentials: AzureAppConfigurationConnectionClientSecretInputCredentialsSchema.describe(
|
||||
AppConnections.CREATE(AppConnection.AzureAppConfiguration).credentials
|
||||
)
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -39,9 +70,13 @@ export const CreateAzureAppConfigurationConnectionSchema = ValidateAzureAppConfi
|
||||
|
||||
export const UpdateAzureAppConfigurationConnectionSchema = z
|
||||
.object({
|
||||
credentials: AzureAppConfigurationConnectionOAuthInputCredentialsSchema.optional().describe(
|
||||
AppConnections.UPDATE(AppConnection.AzureAppConfiguration).credentials
|
||||
)
|
||||
credentials: z
|
||||
.union([
|
||||
AzureAppConfigurationConnectionOAuthInputCredentialsSchema,
|
||||
AzureAppConfigurationConnectionClientSecretInputCredentialsSchema
|
||||
])
|
||||
.optional()
|
||||
.describe(AppConnections.UPDATE(AppConnection.AzureAppConfiguration).credentials)
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AzureAppConfiguration));
|
||||
|
||||
@@ -55,6 +90,10 @@ export const AzureAppConfigurationConnectionSchema = z.intersection(
|
||||
z.object({
|
||||
method: z.literal(AzureAppConfigurationConnectionMethod.OAuth),
|
||||
credentials: AzureAppConfigurationConnectionOAuthOutputCredentialsSchema
|
||||
}),
|
||||
z.object({
|
||||
method: z.literal(AzureAppConfigurationConnectionMethod.ClientSecret),
|
||||
credentials: AzureAppConfigurationConnectionClientSecretOutputCredentialsSchema
|
||||
})
|
||||
])
|
||||
);
|
||||
@@ -65,6 +104,13 @@ export const SanitizedAzureAppConfigurationConnectionSchema = z.discriminatedUni
|
||||
credentials: AzureAppConfigurationConnectionOAuthOutputCredentialsSchema.pick({
|
||||
tenantId: true
|
||||
})
|
||||
}),
|
||||
BaseAzureAppConfigurationConnectionSchema.extend({
|
||||
method: z.literal(AzureAppConfigurationConnectionMethod.ClientSecret),
|
||||
credentials: AzureAppConfigurationConnectionClientSecretOutputCredentialsSchema.pick({
|
||||
clientId: true,
|
||||
tenantId: true
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
|
||||
+5
@@ -4,6 +4,7 @@ import { DiscriminativePick } from "@app/lib/types";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import {
|
||||
AzureAppConfigurationConnectionClientSecretOutputCredentialsSchema,
|
||||
AzureAppConfigurationConnectionOAuthOutputCredentialsSchema,
|
||||
AzureAppConfigurationConnectionSchema,
|
||||
CreateAzureAppConfigurationConnectionSchema,
|
||||
@@ -39,3 +40,7 @@ export type ExchangeCodeAzureResponse = {
|
||||
export type TAzureAppConfigurationConnectionCredentials = z.infer<
|
||||
typeof AzureAppConfigurationConnectionOAuthOutputCredentialsSchema
|
||||
>;
|
||||
|
||||
export type TAzureAppConfigurationConnectionClientSecretCredentials = z.infer<
|
||||
typeof AzureAppConfigurationConnectionClientSecretOutputCredentialsSchema
|
||||
>;
|
||||
|
||||
Reference in New Issue
Block a user