Merge pull request #3092 from Infisical/daniel/azure-app-connection
feat(secret-syncs): azure app config & key vault support
@@ -92,20 +92,24 @@ ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT=true
|
|||||||
|
|
||||||
# App Connections
|
# App Connections
|
||||||
|
|
||||||
# aws assume-role
|
# aws assume-role connection
|
||||||
INF_APP_CONNECTION_AWS_ACCESS_KEY_ID=
|
INF_APP_CONNECTION_AWS_ACCESS_KEY_ID=
|
||||||
INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY=
|
INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY=
|
||||||
|
|
||||||
# github oauth
|
# github oauth connection
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID=
|
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID=
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET=
|
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET=
|
||||||
|
|
||||||
#github app
|
#github app connection
|
||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID=
|
INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID=
|
||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET=
|
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET=
|
||||||
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY=
|
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY=
|
||||||
INF_APP_CONNECTION_GITHUB_APP_SLUG=
|
INF_APP_CONNECTION_GITHUB_APP_SLUG=
|
||||||
INF_APP_CONNECTION_GITHUB_APP_ID=
|
INF_APP_CONNECTION_GITHUB_APP_ID=
|
||||||
|
|
||||||
#gcp app
|
#gcp app connection
|
||||||
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL=
|
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL=
|
||||||
|
|
||||||
|
# azure app connection
|
||||||
|
INF_APP_CONNECTION_AZURE_CLIENT_ID=
|
||||||
|
INF_APP_CONNECTION_AZURE_CLIENT_SECRET=
|
||||||
@@ -1739,6 +1739,15 @@ export const SecretSyncs = {
|
|||||||
OWNER: "The name of the GitHub account owner of the repository.",
|
OWNER: "The name of the GitHub account owner of the repository.",
|
||||||
REPO: "The name of the GitHub repository.",
|
REPO: "The name of the GitHub repository.",
|
||||||
ENV: "The name of the GitHub environment."
|
ENV: "The name of the GitHub environment."
|
||||||
|
},
|
||||||
|
AZURE_KEY_VAULT: {
|
||||||
|
VAULT_BASE_URL:
|
||||||
|
"The base URL of the Azure Key Vault to sync secrets to. Example: https://example.vault.azure.net/"
|
||||||
|
},
|
||||||
|
AZURE_APP_CONFIGURATION: {
|
||||||
|
CONFIGURATION_URL:
|
||||||
|
"The URL of the Azure App Configuration to sync secrets to. Example: https://example.azconfig.io/",
|
||||||
|
LABEL: "An optional label to assign to secrets created in Azure App Configuration."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -204,6 +204,10 @@ const envSchema = z
|
|||||||
// gcp app
|
// gcp app
|
||||||
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL: zpStr(z.string().optional()),
|
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL: zpStr(z.string().optional()),
|
||||||
|
|
||||||
|
// azure app
|
||||||
|
INF_APP_CONNECTION_AZURE_CLIENT_ID: zpStr(z.string().optional()),
|
||||||
|
INF_APP_CONNECTION_AZURE_CLIENT_SECRET: zpStr(z.string().optional()),
|
||||||
|
|
||||||
/* CORS ----------------------------------------------------------------------------- */
|
/* CORS ----------------------------------------------------------------------------- */
|
||||||
|
|
||||||
CORS_ALLOWED_ORIGINS: zpStr(
|
CORS_ALLOWED_ORIGINS: zpStr(
|
||||||
|
|||||||
@@ -849,7 +849,8 @@ export const registerRoutes = async (
|
|||||||
secretVersionTagDAL,
|
secretVersionTagDAL,
|
||||||
secretVersionV2BridgeDAL,
|
secretVersionV2BridgeDAL,
|
||||||
secretVersionTagV2BridgeDAL,
|
secretVersionTagV2BridgeDAL,
|
||||||
resourceMetadataDAL
|
resourceMetadataDAL,
|
||||||
|
appConnectionDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretQueueService = secretQueueFactory({
|
const secretQueueService = secretQueueFactory({
|
||||||
|
|||||||
@@ -73,7 +73,13 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
|||||||
description: `List the ${appName} Connections the current user has permission to establish connections with.`,
|
description: `List the ${appName} Connections the current user has permission to establish connections with.`,
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
appConnections: z.object({ app: z.literal(app), name: z.string(), id: z.string().uuid() }).array()
|
appConnections: z
|
||||||
|
.object({
|
||||||
|
app: z.literal(app),
|
||||||
|
name: z.string(),
|
||||||
|
id: z.string().uuid()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
|||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AwsConnectionListItemSchema, SanitizedAwsConnectionSchema } from "@app/services/app-connection/aws";
|
import { AwsConnectionListItemSchema, SanitizedAwsConnectionSchema } from "@app/services/app-connection/aws";
|
||||||
|
import {
|
||||||
|
AzureAppConfigurationConnectionListItemSchema,
|
||||||
|
SanitizedAzureAppConfigurationConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-app-configuration";
|
||||||
|
import {
|
||||||
|
AzureKeyVaultConnectionListItemSchema,
|
||||||
|
SanitizedAzureKeyVaultConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-key-vault";
|
||||||
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
|
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
|
||||||
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
|
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -12,13 +20,17 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
|||||||
const SanitizedAppConnectionSchema = z.union([
|
const SanitizedAppConnectionSchema = z.union([
|
||||||
...SanitizedAwsConnectionSchema.options,
|
...SanitizedAwsConnectionSchema.options,
|
||||||
...SanitizedGitHubConnectionSchema.options,
|
...SanitizedGitHubConnectionSchema.options,
|
||||||
...SanitizedGcpConnectionSchema.options
|
...SanitizedGcpConnectionSchema.options,
|
||||||
|
...SanitizedAzureKeyVaultConnectionSchema.options,
|
||||||
|
...SanitizedAzureAppConfigurationConnectionSchema.options
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||||
AwsConnectionListItemSchema,
|
AwsConnectionListItemSchema,
|
||||||
GitHubConnectionListItemSchema,
|
GitHubConnectionListItemSchema,
|
||||||
GcpConnectionListItemSchema
|
GcpConnectionListItemSchema,
|
||||||
|
AzureKeyVaultConnectionListItemSchema,
|
||||||
|
AzureAppConfigurationConnectionListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateAzureAppConfigurationConnectionSchema,
|
||||||
|
SanitizedAzureAppConfigurationConnectionSchema,
|
||||||
|
UpdateAzureAppConfigurationConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-app-configuration";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerAzureAppConfigurationConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.AzureAppConfiguration,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedAzureAppConfigurationConnectionSchema,
|
||||||
|
createSchema: CreateAzureAppConfigurationConnectionSchema,
|
||||||
|
updateSchema: UpdateAzureAppConfigurationConnectionSchema
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateAzureKeyVaultConnectionSchema,
|
||||||
|
SanitizedAzureKeyVaultConnectionSchema,
|
||||||
|
UpdateAzureKeyVaultConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-key-vault";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerAzureKeyVaultConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.AzureKeyVault,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedAzureKeyVaultConnectionSchema,
|
||||||
|
createSchema: CreateAzureKeyVaultConnectionSchema,
|
||||||
|
updateSchema: UpdateAzureKeyVaultConnectionSchema
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
||||||
|
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
|
||||||
|
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
|
||||||
import { registerGcpConnectionRouter } from "./gcp-connection-router";
|
import { registerGcpConnectionRouter } from "./gcp-connection-router";
|
||||||
import { registerGitHubConnectionRouter } from "./github-connection-router";
|
import { registerGitHubConnectionRouter } from "./github-connection-router";
|
||||||
|
|
||||||
@@ -10,5 +12,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
{
|
{
|
||||||
[AppConnection.AWS]: registerAwsConnectionRouter,
|
[AppConnection.AWS]: registerAwsConnectionRouter,
|
||||||
[AppConnection.GitHub]: registerGitHubConnectionRouter,
|
[AppConnection.GitHub]: registerGitHubConnectionRouter,
|
||||||
[AppConnection.GCP]: registerGcpConnectionRouter
|
[AppConnection.GCP]: registerGcpConnectionRouter,
|
||||||
|
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
|
||||||
|
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
AzureAppConfigurationSyncSchema,
|
||||||
|
CreateAzureAppConfigurationSyncSchema,
|
||||||
|
UpdateAzureAppConfigurationSyncSchema
|
||||||
|
} from "@app/services/secret-sync/azure-app-configuration";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerAzureAppConfigurationSyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.AzureAppConfiguration,
|
||||||
|
server,
|
||||||
|
responseSchema: AzureAppConfigurationSyncSchema,
|
||||||
|
createSchema: CreateAzureAppConfigurationSyncSchema,
|
||||||
|
updateSchema: UpdateAzureAppConfigurationSyncSchema
|
||||||
|
});
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
AzureKeyVaultSyncSchema,
|
||||||
|
CreateAzureKeyVaultSyncSchema,
|
||||||
|
UpdateAzureKeyVaultSyncSchema
|
||||||
|
} from "@app/services/secret-sync/azure-key-vault";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerAzureKeyVaultSyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.AzureKeyVault,
|
||||||
|
server,
|
||||||
|
responseSchema: AzureKeyVaultSyncSchema,
|
||||||
|
createSchema: CreateAzureKeyVaultSyncSchema,
|
||||||
|
updateSchema: UpdateAzureKeyVaultSyncSchema
|
||||||
|
});
|
||||||
@@ -2,6 +2,8 @@ import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
|||||||
|
|
||||||
import { registerAwsParameterStoreSyncRouter } from "./aws-parameter-store-sync-router";
|
import { registerAwsParameterStoreSyncRouter } from "./aws-parameter-store-sync-router";
|
||||||
import { registerAwsSecretsManagerSyncRouter } from "./aws-secrets-manager-sync-router";
|
import { registerAwsSecretsManagerSyncRouter } from "./aws-secrets-manager-sync-router";
|
||||||
|
import { registerAzureAppConfigurationSyncRouter } from "./azure-app-configuration-sync-router";
|
||||||
|
import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-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";
|
||||||
|
|
||||||
@@ -11,5 +13,7 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
|
|||||||
[SecretSync.AWSParameterStore]: registerAwsParameterStoreSyncRouter,
|
[SecretSync.AWSParameterStore]: registerAwsParameterStoreSyncRouter,
|
||||||
[SecretSync.AWSSecretsManager]: registerAwsSecretsManagerSyncRouter,
|
[SecretSync.AWSSecretsManager]: registerAwsSecretsManagerSyncRouter,
|
||||||
[SecretSync.GitHub]: registerGitHubSyncRouter,
|
[SecretSync.GitHub]: registerGitHubSyncRouter,
|
||||||
[SecretSync.GCPSecretManager]: registerGcpSyncRouter
|
[SecretSync.GCPSecretManager]: registerGcpSyncRouter,
|
||||||
|
[SecretSync.AzureKeyVault]: registerAzureKeyVaultSyncRouter,
|
||||||
|
[SecretSync.AzureAppConfiguration]: registerAzureAppConfigurationSyncRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ import {
|
|||||||
AwsSecretsManagerSyncListItemSchema,
|
AwsSecretsManagerSyncListItemSchema,
|
||||||
AwsSecretsManagerSyncSchema
|
AwsSecretsManagerSyncSchema
|
||||||
} from "@app/services/secret-sync/aws-secrets-manager";
|
} from "@app/services/secret-sync/aws-secrets-manager";
|
||||||
|
import {
|
||||||
|
AzureAppConfigurationSyncListItemSchema,
|
||||||
|
AzureAppConfigurationSyncSchema
|
||||||
|
} from "@app/services/secret-sync/azure-app-configuration";
|
||||||
|
import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/services/secret-sync/azure-key-vault";
|
||||||
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";
|
||||||
|
|
||||||
@@ -20,14 +25,18 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
|||||||
AwsParameterStoreSyncSchema,
|
AwsParameterStoreSyncSchema,
|
||||||
AwsSecretsManagerSyncSchema,
|
AwsSecretsManagerSyncSchema,
|
||||||
GitHubSyncSchema,
|
GitHubSyncSchema,
|
||||||
GcpSyncSchema
|
GcpSyncSchema,
|
||||||
|
AzureKeyVaultSyncSchema,
|
||||||
|
AzureAppConfigurationSyncSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
||||||
AwsParameterStoreSyncListItemSchema,
|
AwsParameterStoreSyncListItemSchema,
|
||||||
AwsSecretsManagerSyncListItemSchema,
|
AwsSecretsManagerSyncListItemSchema,
|
||||||
GitHubSyncListItemSchema,
|
GitHubSyncListItemSchema,
|
||||||
GcpSyncListItemSchema
|
GcpSyncListItemSchema,
|
||||||
|
AzureKeyVaultSyncListItemSchema,
|
||||||
|
AzureAppConfigurationSyncListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
export enum AppConnection {
|
export enum AppConnection {
|
||||||
GitHub = "github",
|
GitHub = "github",
|
||||||
AWS = "aws",
|
AWS = "aws",
|
||||||
GCP = "gcp"
|
GCP = "gcp",
|
||||||
|
AzureKeyVault = "azure-key-vault",
|
||||||
|
AzureAppConfiguration = "azure-app-configuration"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AWSRegion {
|
export enum AWSRegion {
|
||||||
|
|||||||
@@ -20,10 +20,25 @@ import {
|
|||||||
} from "@app/services/app-connection/github";
|
} from "@app/services/app-connection/github";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AzureAppConfigurationConnectionMethod,
|
||||||
|
getAzureAppConfigurationConnectionListItem,
|
||||||
|
validateAzureAppConfigurationConnectionCredentials
|
||||||
|
} from "./azure-app-configuration";
|
||||||
|
import {
|
||||||
|
AzureKeyVaultConnectionMethod,
|
||||||
|
getAzureKeyVaultConnectionListItem,
|
||||||
|
validateAzureKeyVaultConnectionCredentials
|
||||||
|
} from "./azure-key-vault";
|
||||||
|
|
||||||
export const listAppConnectionOptions = () => {
|
export const listAppConnectionOptions = () => {
|
||||||
return [getAwsAppConnectionListItem(), getGitHubConnectionListItem(), getGcpAppConnectionListItem()].sort((a, b) =>
|
return [
|
||||||
a.name.localeCompare(b.name)
|
getAwsAppConnectionListItem(),
|
||||||
);
|
getGitHubConnectionListItem(),
|
||||||
|
getGcpAppConnectionListItem(),
|
||||||
|
getAzureKeyVaultConnectionListItem(),
|
||||||
|
getAzureAppConfigurationConnectionListItem()
|
||||||
|
].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const encryptAppConnectionCredentials = async ({
|
export const encryptAppConnectionCredentials = async ({
|
||||||
@@ -79,6 +94,10 @@ export const validateAppConnectionCredentials = async (
|
|||||||
return validateGitHubConnectionCredentials(appConnection);
|
return validateGitHubConnectionCredentials(appConnection);
|
||||||
case AppConnection.GCP:
|
case AppConnection.GCP:
|
||||||
return validateGcpConnectionCredentials(appConnection);
|
return validateGcpConnectionCredentials(appConnection);
|
||||||
|
case AppConnection.AzureKeyVault:
|
||||||
|
return validateAzureKeyVaultConnectionCredentials(appConnection);
|
||||||
|
case AppConnection.AzureAppConfiguration:
|
||||||
|
return validateAzureAppConfigurationConnectionCredentials(appConnection);
|
||||||
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 ${app}`);
|
throw new Error(`Unhandled App Connection ${app}`);
|
||||||
@@ -89,6 +108,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
switch (method) {
|
switch (method) {
|
||||||
case GitHubConnectionMethod.App:
|
case GitHubConnectionMethod.App:
|
||||||
return "GitHub App";
|
return "GitHub App";
|
||||||
|
case AzureKeyVaultConnectionMethod.OAuth:
|
||||||
|
case AzureAppConfigurationConnectionMethod.OAuth:
|
||||||
case GitHubConnectionMethod.OAuth:
|
case GitHubConnectionMethod.OAuth:
|
||||||
return "OAuth";
|
return "OAuth";
|
||||||
case AwsConnectionMethod.AccessKey:
|
case AwsConnectionMethod.AccessKey:
|
||||||
|
|||||||
@@ -3,5 +3,7 @@ import { AppConnection } from "./app-connection-enums";
|
|||||||
export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
||||||
[AppConnection.AWS]: "AWS",
|
[AppConnection.AWS]: "AWS",
|
||||||
[AppConnection.GitHub]: "GitHub",
|
[AppConnection.GitHub]: "GitHub",
|
||||||
[AppConnection.GCP]: "GCP"
|
[AppConnection.GCP]: "GCP",
|
||||||
|
[AppConnection.AzureKeyVault]: "Azure Key Vault",
|
||||||
|
[AppConnection.AzureAppConfiguration]: "Azure App Configuration"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import { githubConnectionService } from "@app/services/app-connection/github/git
|
|||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
|
||||||
import { TAppConnectionDALFactory } from "./app-connection-dal";
|
import { TAppConnectionDALFactory } from "./app-connection-dal";
|
||||||
|
import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration";
|
||||||
|
import { ValidateAzureKeyVaultConnectionCredentialsSchema } from "./azure-key-vault";
|
||||||
import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
|
import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
|
||||||
import { gcpConnectionService } from "./gcp/gcp-connection-service";
|
import { gcpConnectionService } from "./gcp/gcp-connection-service";
|
||||||
|
|
||||||
@@ -42,7 +44,9 @@ export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServic
|
|||||||
const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAppConnectionCredentials> = {
|
const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAppConnectionCredentials> = {
|
||||||
[AppConnection.AWS]: ValidateAwsConnectionCredentialsSchema,
|
[AppConnection.AWS]: ValidateAwsConnectionCredentialsSchema,
|
||||||
[AppConnection.GitHub]: ValidateGitHubConnectionCredentialsSchema,
|
[AppConnection.GitHub]: ValidateGitHubConnectionCredentialsSchema,
|
||||||
[AppConnection.GCP]: ValidateGcpConnectionCredentialsSchema
|
[AppConnection.GCP]: ValidateGcpConnectionCredentialsSchema,
|
||||||
|
[AppConnection.AzureKeyVault]: ValidateAzureKeyVaultConnectionCredentialsSchema,
|
||||||
|
[AppConnection.AzureAppConfiguration]: ValidateAzureAppConfigurationConnectionCredentialsSchema
|
||||||
};
|
};
|
||||||
|
|
||||||
export const appConnectionServiceFactory = ({
|
export const appConnectionServiceFactory = ({
|
||||||
|
|||||||
@@ -11,11 +11,35 @@ import {
|
|||||||
TValidateGitHubConnectionCredentials
|
TValidateGitHubConnectionCredentials
|
||||||
} from "@app/services/app-connection/github";
|
} from "@app/services/app-connection/github";
|
||||||
|
|
||||||
|
import {
|
||||||
|
TAzureAppConfigurationConnection,
|
||||||
|
TAzureAppConfigurationConnectionConfig,
|
||||||
|
TAzureAppConfigurationConnectionInput,
|
||||||
|
TValidateAzureAppConfigurationConnectionCredentials
|
||||||
|
} from "./azure-app-configuration";
|
||||||
|
import {
|
||||||
|
TAzureKeyVaultConnection,
|
||||||
|
TAzureKeyVaultConnectionConfig,
|
||||||
|
TAzureKeyVaultConnectionInput,
|
||||||
|
TValidateAzureKeyVaultConnectionCredentials
|
||||||
|
} from "./azure-key-vault";
|
||||||
import { TGcpConnection, TGcpConnectionConfig, TGcpConnectionInput, TValidateGcpConnectionCredentials } from "./gcp";
|
import { TGcpConnection, TGcpConnectionConfig, TGcpConnectionInput, TValidateGcpConnectionCredentials } from "./gcp";
|
||||||
|
|
||||||
export type TAppConnection = { id: string } & (TAwsConnection | TGitHubConnection | TGcpConnection);
|
export type TAppConnection = { id: string } & (
|
||||||
|
| TAwsConnection
|
||||||
|
| TGitHubConnection
|
||||||
|
| TGcpConnection
|
||||||
|
| TAzureKeyVaultConnection
|
||||||
|
| TAzureAppConfigurationConnection
|
||||||
|
);
|
||||||
|
|
||||||
export type TAppConnectionInput = { id: string } & (TAwsConnectionInput | TGitHubConnectionInput | TGcpConnectionInput);
|
export type TAppConnectionInput = { id: string } & (
|
||||||
|
| TAwsConnectionInput
|
||||||
|
| TGitHubConnectionInput
|
||||||
|
| TGcpConnectionInput
|
||||||
|
| TAzureKeyVaultConnectionInput
|
||||||
|
| TAzureAppConfigurationConnectionInput
|
||||||
|
);
|
||||||
|
|
||||||
export type TCreateAppConnectionDTO = Pick<
|
export type TCreateAppConnectionDTO = Pick<
|
||||||
TAppConnectionInput,
|
TAppConnectionInput,
|
||||||
@@ -26,9 +50,16 @@ export type TUpdateAppConnectionDTO = Partial<Omit<TCreateAppConnectionDTO, "met
|
|||||||
connectionId: string;
|
connectionId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAppConnectionConfig = TAwsConnectionConfig | TGitHubConnectionConfig | TGcpConnectionConfig;
|
export type TAppConnectionConfig =
|
||||||
|
| TAwsConnectionConfig
|
||||||
|
| TGitHubConnectionConfig
|
||||||
|
| TGcpConnectionConfig
|
||||||
|
| TAzureKeyVaultConnectionConfig
|
||||||
|
| TAzureAppConfigurationConnectionConfig;
|
||||||
|
|
||||||
export type TValidateAppConnectionCredentials =
|
export type TValidateAppConnectionCredentials =
|
||||||
| TValidateAwsConnectionCredentials
|
| TValidateAwsConnectionCredentials
|
||||||
| TValidateGitHubConnectionCredentials
|
| TValidateGitHubConnectionCredentials
|
||||||
| TValidateGcpConnectionCredentials;
|
| TValidateGcpConnectionCredentials
|
||||||
|
| TValidateAzureKeyVaultConnectionCredentials
|
||||||
|
| TValidateAzureAppConfigurationConnectionCredentials;
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export enum AzureAppConfigurationConnectionMethod {
|
||||||
|
OAuth = "oauth"
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { AxiosError, AxiosResponse } from "axios";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||||
|
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
|
||||||
|
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import { AzureAppConfigurationConnectionMethod } from "./azure-app-configuration-connection-enums";
|
||||||
|
import {
|
||||||
|
ExchangeCodeAzureResponse,
|
||||||
|
TAzureAppConfigurationConnectionConfig
|
||||||
|
} from "./azure-app-configuration-connection-types";
|
||||||
|
|
||||||
|
export const getAzureAppConfigurationConnectionListItem = () => {
|
||||||
|
const { INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig();
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "Azure App Configuration" as const,
|
||||||
|
app: AppConnection.AzureAppConfiguration as const,
|
||||||
|
methods: Object.values(AzureAppConfigurationConnectionMethod) as [AzureAppConfigurationConnectionMethod.OAuth],
|
||||||
|
oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_ID
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateAzureAppConfigurationConnectionCredentials = async (
|
||||||
|
config: TAzureAppConfigurationConnectionConfig
|
||||||
|
) => {
|
||||||
|
const { credentials: inputCredentials, method } = config;
|
||||||
|
|
||||||
|
const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, SITE_URL } = getConfig();
|
||||||
|
|
||||||
|
if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_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_CLIENT_ID,
|
||||||
|
client_secret: INF_APP_CONNECTION_AZURE_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:
|
||||||
|
return {
|
||||||
|
tenantId: inputCredentials.tenantId,
|
||||||
|
accessToken: tokenResp.data.access_token,
|
||||||
|
refreshToken: tokenResp.data.refresh_token,
|
||||||
|
expiresAt: Date.now() + tokenResp.data.expires_in * 1000
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: `Unhandled Azure connection method: ${method as AzureAppConfigurationConnectionMethod}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
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 { AzureAppConfigurationConnectionMethod } from "./azure-app-configuration-connection-enums";
|
||||||
|
|
||||||
|
export const AzureAppConfigurationConnectionOAuthInputCredentialsSchema = z.object({
|
||||||
|
code: z.string().trim().min(1, "OAuth code required"),
|
||||||
|
tenantId: z.string().trim().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureAppConfigurationConnectionOAuthOutputCredentialsSchema = z.object({
|
||||||
|
tenantId: z.string().optional(),
|
||||||
|
accessToken: z.string(),
|
||||||
|
refreshToken: z.string(),
|
||||||
|
expiresAt: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ValidateAzureAppConfigurationConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z
|
||||||
|
.literal(AzureAppConfigurationConnectionMethod.OAuth)
|
||||||
|
.describe(AppConnections.CREATE(AppConnection.AzureAppConfiguration).method),
|
||||||
|
credentials: AzureAppConfigurationConnectionOAuthInputCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.AzureAppConfiguration).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CreateAzureAppConfigurationConnectionSchema = ValidateAzureAppConfigurationConnectionCredentialsSchema.and(
|
||||||
|
GenericCreateAppConnectionFieldsSchema(AppConnection.AzureAppConfiguration)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateAzureAppConfigurationConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
credentials: AzureAppConfigurationConnectionOAuthInputCredentialsSchema.optional().describe(
|
||||||
|
AppConnections.UPDATE(AppConnection.AzureAppConfiguration).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AzureAppConfiguration));
|
||||||
|
|
||||||
|
const BaseAzureAppConfigurationConnectionSchema = BaseAppConnectionSchema.extend({
|
||||||
|
app: z.literal(AppConnection.AzureAppConfiguration)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureAppConfigurationConnectionSchema = z.intersection(
|
||||||
|
BaseAzureAppConfigurationConnectionSchema,
|
||||||
|
z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z.literal(AzureAppConfigurationConnectionMethod.OAuth),
|
||||||
|
credentials: AzureAppConfigurationConnectionOAuthOutputCredentialsSchema
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SanitizedAzureAppConfigurationConnectionSchema = z.discriminatedUnion("method", [
|
||||||
|
BaseAzureAppConfigurationConnectionSchema.extend({
|
||||||
|
method: z.literal(AzureAppConfigurationConnectionMethod.OAuth),
|
||||||
|
credentials: AzureAppConfigurationConnectionOAuthOutputCredentialsSchema.pick({
|
||||||
|
tenantId: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const AzureAppConfigurationConnectionListItemSchema = z.object({
|
||||||
|
name: z.literal("Azure App Configuration"),
|
||||||
|
app: z.literal(AppConnection.AzureAppConfiguration),
|
||||||
|
methods: z.nativeEnum(AzureAppConfigurationConnectionMethod).array(),
|
||||||
|
oauthClientId: z.string().optional()
|
||||||
|
});
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { DiscriminativePick } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import {
|
||||||
|
AzureAppConfigurationConnectionOAuthOutputCredentialsSchema,
|
||||||
|
AzureAppConfigurationConnectionSchema,
|
||||||
|
CreateAzureAppConfigurationConnectionSchema,
|
||||||
|
ValidateAzureAppConfigurationConnectionCredentialsSchema
|
||||||
|
} from "./azure-app-configuration-connection-schemas";
|
||||||
|
|
||||||
|
export type TAzureAppConfigurationConnection = z.infer<typeof AzureAppConfigurationConnectionSchema>;
|
||||||
|
|
||||||
|
export type TAzureAppConfigurationConnectionInput = z.infer<typeof CreateAzureAppConfigurationConnectionSchema> & {
|
||||||
|
app: AppConnection.AzureAppConfiguration;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateAzureAppConfigurationConnectionCredentials =
|
||||||
|
typeof ValidateAzureAppConfigurationConnectionCredentialsSchema;
|
||||||
|
|
||||||
|
export type TAzureAppConfigurationConnectionConfig = DiscriminativePick<
|
||||||
|
TAzureAppConfigurationConnectionInput,
|
||||||
|
"method" | "app" | "credentials"
|
||||||
|
> & {
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ExchangeCodeAzureResponse = {
|
||||||
|
token_type: string;
|
||||||
|
scope: string;
|
||||||
|
expires_in: number;
|
||||||
|
ext_expires_in: number;
|
||||||
|
access_token: string;
|
||||||
|
refresh_token: string;
|
||||||
|
id_token: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAzureAppConfigurationConnectionCredentials = z.infer<
|
||||||
|
typeof AzureAppConfigurationConnectionOAuthOutputCredentialsSchema
|
||||||
|
>;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./azure-app-configuration-connection-enums";
|
||||||
|
export * from "./azure-app-configuration-connection-fns";
|
||||||
|
export * from "./azure-app-configuration-connection-schemas";
|
||||||
|
export * from "./azure-app-configuration-connection-types";
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export enum AzureKeyVaultConnectionMethod {
|
||||||
|
OAuth = "oauth"
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import { AxiosError, AxiosResponse } from "axios";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import {
|
||||||
|
decryptAppConnectionCredentials,
|
||||||
|
encryptAppConnectionCredentials,
|
||||||
|
getAppConnectionMethodName
|
||||||
|
} from "@app/services/app-connection/app-connection-fns";
|
||||||
|
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
|
||||||
|
import { TAppConnectionDALFactory } from "../app-connection-dal";
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import { AzureKeyVaultConnectionMethod } from "./azure-key-vault-connection-enums";
|
||||||
|
import {
|
||||||
|
ExchangeCodeAzureResponse,
|
||||||
|
TAzureKeyVaultConnectionConfig,
|
||||||
|
TAzureKeyVaultConnectionCredentials
|
||||||
|
} from "./azure-key-vault-connection-types";
|
||||||
|
|
||||||
|
export const getAzureConnectionAccessToken = async (
|
||||||
|
connectionId: string,
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update">,
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
|
) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
if (!appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID || !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Azure environment variables have not been configured`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const appConnection = await appConnectionDAL.findById(connectionId);
|
||||||
|
|
||||||
|
if (!appConnection) {
|
||||||
|
throw new NotFoundError({ message: `Connection with ID '${connectionId}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appConnection.app !== AppConnection.AzureKeyVault && appConnection.app !== AppConnection.AzureAppConfiguration) {
|
||||||
|
throw new BadRequestError({ message: `Connection with ID '${connectionId}' is not an Azure Key Vault connection` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentials = (await decryptAppConnectionCredentials({
|
||||||
|
orgId: appConnection.orgId,
|
||||||
|
kmsService,
|
||||||
|
encryptedCredentials: appConnection.encryptedCredentials
|
||||||
|
})) as TAzureKeyVaultConnectionCredentials;
|
||||||
|
|
||||||
|
const { data } = await request.post<ExchangeCodeAzureResponse>(
|
||||||
|
IntegrationUrls.AZURE_TOKEN_URL.replace("common", credentials.tenantId || "common"),
|
||||||
|
new URLSearchParams({
|
||||||
|
grant_type: "refresh_token",
|
||||||
|
scope: `openid offline_access`,
|
||||||
|
client_id: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID,
|
||||||
|
client_secret: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
|
||||||
|
refresh_token: credentials.refreshToken
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const accessExpiresAt = new Date();
|
||||||
|
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
|
||||||
|
|
||||||
|
const updatedCredentials = {
|
||||||
|
...credentials,
|
||||||
|
accessToken: data.access_token,
|
||||||
|
expiresAt: accessExpiresAt.getTime(),
|
||||||
|
refreshToken: data.refresh_token
|
||||||
|
};
|
||||||
|
|
||||||
|
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||||
|
credentials: updatedCredentials,
|
||||||
|
orgId: appConnection.orgId,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
await appConnectionDAL.update(
|
||||||
|
{ id: connectionId },
|
||||||
|
{
|
||||||
|
encryptedCredentials
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: data.access_token
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAzureKeyVaultConnectionListItem = () => {
|
||||||
|
const { INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig();
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "Azure Key Vault" as const,
|
||||||
|
app: AppConnection.AzureKeyVault as const,
|
||||||
|
methods: Object.values(AzureKeyVaultConnectionMethod) as [AzureKeyVaultConnectionMethod.OAuth],
|
||||||
|
oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_ID
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateAzureKeyVaultConnectionCredentials = async (config: TAzureKeyVaultConnectionConfig) => {
|
||||||
|
const { credentials: inputCredentials, method } = config;
|
||||||
|
|
||||||
|
const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, SITE_URL } = getConfig();
|
||||||
|
|
||||||
|
if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_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://vault.azure.net/.default`,
|
||||||
|
client_id: INF_APP_CONNECTION_AZURE_CLIENT_ID,
|
||||||
|
client_secret: INF_APP_CONNECTION_AZURE_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 AzureKeyVaultConnectionMethod.OAuth:
|
||||||
|
return {
|
||||||
|
tenantId: inputCredentials.tenantId,
|
||||||
|
accessToken: tokenResp.data.access_token,
|
||||||
|
refreshToken: tokenResp.data.refresh_token,
|
||||||
|
expiresAt: Date.now() + tokenResp.data.expires_in * 1000
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: `Unhandled Azure connection method: ${method as AzureKeyVaultConnectionMethod}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
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 { AzureKeyVaultConnectionMethod } from "./azure-key-vault-connection-enums";
|
||||||
|
|
||||||
|
export const AzureKeyVaultConnectionOAuthInputCredentialsSchema = z.object({
|
||||||
|
code: z.string().trim().min(1, "OAuth code required"),
|
||||||
|
tenantId: z.string().trim().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureKeyVaultConnectionOAuthOutputCredentialsSchema = z.object({
|
||||||
|
tenantId: z.string().optional(),
|
||||||
|
accessToken: z.string(),
|
||||||
|
refreshToken: z.string(),
|
||||||
|
expiresAt: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ValidateAzureKeyVaultConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z
|
||||||
|
.literal(AzureKeyVaultConnectionMethod.OAuth)
|
||||||
|
.describe(AppConnections.CREATE(AppConnection.AzureKeyVault).method),
|
||||||
|
credentials: AzureKeyVaultConnectionOAuthInputCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.AzureKeyVault).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CreateAzureKeyVaultConnectionSchema = ValidateAzureKeyVaultConnectionCredentialsSchema.and(
|
||||||
|
GenericCreateAppConnectionFieldsSchema(AppConnection.AzureKeyVault)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateAzureKeyVaultConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
credentials: AzureKeyVaultConnectionOAuthInputCredentialsSchema.optional().describe(
|
||||||
|
AppConnections.UPDATE(AppConnection.AzureKeyVault).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AzureKeyVault));
|
||||||
|
|
||||||
|
const BaseAzureKeyVaultConnectionSchema = BaseAppConnectionSchema.extend({
|
||||||
|
app: z.literal(AppConnection.AzureKeyVault)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureKeyVaultConnectionSchema = z.intersection(
|
||||||
|
BaseAzureKeyVaultConnectionSchema,
|
||||||
|
z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z.literal(AzureKeyVaultConnectionMethod.OAuth),
|
||||||
|
credentials: AzureKeyVaultConnectionOAuthOutputCredentialsSchema
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SanitizedAzureKeyVaultConnectionSchema = z.discriminatedUnion("method", [
|
||||||
|
BaseAzureKeyVaultConnectionSchema.extend({
|
||||||
|
method: z.literal(AzureKeyVaultConnectionMethod.OAuth),
|
||||||
|
credentials: AzureKeyVaultConnectionOAuthOutputCredentialsSchema.pick({
|
||||||
|
tenantId: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const AzureKeyVaultConnectionListItemSchema = z.object({
|
||||||
|
name: z.literal("Azure Key Vault"),
|
||||||
|
app: z.literal(AppConnection.AzureKeyVault),
|
||||||
|
methods: z.nativeEnum(AzureKeyVaultConnectionMethod).array(),
|
||||||
|
oauthClientId: z.string().optional()
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { DiscriminativePick } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import {
|
||||||
|
AzureKeyVaultConnectionOAuthOutputCredentialsSchema,
|
||||||
|
AzureKeyVaultConnectionSchema,
|
||||||
|
CreateAzureKeyVaultConnectionSchema,
|
||||||
|
ValidateAzureKeyVaultConnectionCredentialsSchema
|
||||||
|
} from "./azure-key-vault-connection-schemas";
|
||||||
|
|
||||||
|
export type TAzureKeyVaultConnection = z.infer<typeof AzureKeyVaultConnectionSchema>;
|
||||||
|
|
||||||
|
export type TAzureKeyVaultConnectionInput = z.infer<typeof CreateAzureKeyVaultConnectionSchema> & {
|
||||||
|
app: AppConnection.AzureKeyVault;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateAzureKeyVaultConnectionCredentials = typeof ValidateAzureKeyVaultConnectionCredentialsSchema;
|
||||||
|
|
||||||
|
export type TAzureKeyVaultConnectionConfig = DiscriminativePick<
|
||||||
|
TAzureKeyVaultConnectionInput,
|
||||||
|
"method" | "app" | "credentials"
|
||||||
|
> & {
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ExchangeCodeAzureResponse = {
|
||||||
|
token_type: string;
|
||||||
|
scope: string;
|
||||||
|
expires_in: number;
|
||||||
|
ext_expires_in: number;
|
||||||
|
access_token: string;
|
||||||
|
refresh_token: string;
|
||||||
|
id_token: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAzureKeyVaultConnectionCredentials = z.infer<typeof AzureKeyVaultConnectionOAuthOutputCredentialsSchema>;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./azure-key-vault-connection-enums";
|
||||||
|
export * from "./azure-key-vault-connection-fns";
|
||||||
|
export * from "./azure-key-vault-connection-schemas";
|
||||||
|
export * from "./azure-key-vault-connection-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 AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||||
|
name: "Azure App Configuration",
|
||||||
|
destination: SecretSync.AzureAppConfiguration,
|
||||||
|
connection: AppConnection.AzureAppConfiguration,
|
||||||
|
canImportSecrets: true
|
||||||
|
};
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import https from "https";
|
||||||
|
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
|
import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-key-vault";
|
||||||
|
import { isAzureKeyVaultReference } from "@app/services/integration-auth/integration-sync-secret-fns";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
import { TAzureAppConfigurationSyncWithCredentials } from "./azure-app-configuration-sync-types";
|
||||||
|
|
||||||
|
type TAzureAppConfigurationSecretSyncFactoryDeps = {
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface AzureAppConfigKeyValue {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const azureAppConfigurationSecretSyncFactory = ({
|
||||||
|
kmsService,
|
||||||
|
appConnectionDAL
|
||||||
|
}: TAzureAppConfigurationSecretSyncFactoryDeps) => {
|
||||||
|
const $getCompleteAzureAppConfigValues = async (accessToken: string, baseURL: string, url: string) => {
|
||||||
|
let result: AzureAppConfigKeyValue[] = [];
|
||||||
|
let currentUrl = url;
|
||||||
|
|
||||||
|
while (currentUrl) {
|
||||||
|
const res = await request.get<{ items: AzureAppConfigKeyValue[]; ["@nextLink"]: string }>(currentUrl, {
|
||||||
|
baseURL,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
},
|
||||||
|
// we force IPV4 because docker setup fails with ipv6
|
||||||
|
httpsAgent: new https.Agent({
|
||||||
|
family: 4
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
result = result.concat(res.data.items);
|
||||||
|
currentUrl = res.data?.["@nextLink"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const $deleteAzureSecret = async (accessToken: string, configurationUrl: string, key: string, label?: string) => {
|
||||||
|
await request.delete(`${configurationUrl}/kv/${key}?api-version=2023-11-01`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
},
|
||||||
|
...(label &&
|
||||||
|
label.length > 0 && {
|
||||||
|
params: {
|
||||||
|
label
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
httpsAgent: new https.Agent({
|
||||||
|
family: 4
|
||||||
|
})
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncSecrets = async (secretSync: TAzureAppConfigurationSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
|
if (!secretSync.destinationConfig.configurationUrl.endsWith(".azconfig.io")) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid Azure App Configuration URL provided."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { accessToken } = await getAzureConnectionAccessToken(secretSync.connectionId, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
|
const azureAppConfigValuesUrl = `/kv?api-version=2023-11-01${
|
||||||
|
secretSync.destinationConfig.label ? `&label=${secretSync.destinationConfig.label}` : "&label=%00"
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const azureAppConfigValuesUrlAllSecrets = `/kv?api-version=2023-11-01`;
|
||||||
|
|
||||||
|
const azureAppConfigSecretsLabeled = Object.fromEntries(
|
||||||
|
(
|
||||||
|
await $getCompleteAzureAppConfigValues(
|
||||||
|
accessToken,
|
||||||
|
secretSync.destinationConfig.configurationUrl,
|
||||||
|
azureAppConfigValuesUrl
|
||||||
|
)
|
||||||
|
).map((entry) => [entry.key, entry.value])
|
||||||
|
);
|
||||||
|
|
||||||
|
const azureAppConfigSecrets = Object.fromEntries(
|
||||||
|
(
|
||||||
|
await $getCompleteAzureAppConfigValues(
|
||||||
|
accessToken,
|
||||||
|
secretSync.destinationConfig.configurationUrl,
|
||||||
|
azureAppConfigValuesUrlAllSecrets
|
||||||
|
)
|
||||||
|
).map((entry) => [
|
||||||
|
entry.key,
|
||||||
|
{
|
||||||
|
value: entry.value,
|
||||||
|
label: entry.label
|
||||||
|
}
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
// add the secrets to azure app config, that are in infisical
|
||||||
|
for await (const key of Object.keys(secretMap)) {
|
||||||
|
if (!(key in azureAppConfigSecretsLabeled) || secretMap[key]?.value !== azureAppConfigSecretsLabeled[key]) {
|
||||||
|
await request.put(
|
||||||
|
`${secretSync.destinationConfig.configurationUrl}/kv/${key}?api-version=2023-11-01`,
|
||||||
|
{
|
||||||
|
value: secretMap[key]?.value,
|
||||||
|
...(isAzureKeyVaultReference(secretMap[key]?.value || "") && {
|
||||||
|
content_type: "application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...(secretSync.destinationConfig.label && {
|
||||||
|
params: {
|
||||||
|
label: secretSync.destinationConfig.label
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
},
|
||||||
|
httpsAgent: new https.Agent({
|
||||||
|
family: 4
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const key of Object.keys(azureAppConfigSecrets)) {
|
||||||
|
const azureSecret = azureAppConfigSecrets[key];
|
||||||
|
if (
|
||||||
|
!(key in secretMap) ||
|
||||||
|
secretMap[key] === null ||
|
||||||
|
(azureSecret.label && azureSecret.label !== secretSync.destinationConfig.label) ||
|
||||||
|
(!azureSecret.label && secretSync.destinationConfig.label)
|
||||||
|
) {
|
||||||
|
await $deleteAzureSecret(accessToken, secretSync.destinationConfig.configurationUrl, key, azureSecret.label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeSecrets = async (secretSync: TAzureAppConfigurationSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
|
const { accessToken } = await getAzureConnectionAccessToken(secretSync.connectionId, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
|
const azureAppConfigValuesUrl = `/kv?api-version=2023-11-01${
|
||||||
|
secretSync.destinationConfig.label ? `&label=${secretSync.destinationConfig.label}` : "&label=%00"
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const azureAppConfigSecrets = Object.fromEntries(
|
||||||
|
(
|
||||||
|
await $getCompleteAzureAppConfigValues(
|
||||||
|
accessToken,
|
||||||
|
secretSync.destinationConfig.configurationUrl,
|
||||||
|
azureAppConfigValuesUrl
|
||||||
|
)
|
||||||
|
).map((entry) => [entry.key, entry.value])
|
||||||
|
);
|
||||||
|
|
||||||
|
for await (const infisicalKey of Object.keys(secretMap)) {
|
||||||
|
if (infisicalKey in azureAppConfigSecrets) {
|
||||||
|
await $deleteAzureSecret(
|
||||||
|
accessToken,
|
||||||
|
secretSync.destinationConfig.configurationUrl,
|
||||||
|
infisicalKey,
|
||||||
|
secretSync.destinationConfig.label
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSecrets = async (secretSync: TAzureAppConfigurationSyncWithCredentials) => {
|
||||||
|
const { accessToken } = await getAzureConnectionAccessToken(secretSync.connectionId, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
|
const secretMap: TSecretMap = {};
|
||||||
|
|
||||||
|
const azureAppConfigValuesUrl = `/kv?api-version=2023-11-01${
|
||||||
|
secretSync.destinationConfig.label ? `&label=${secretSync.destinationConfig.label}` : "&label=%00"
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const azureAppConfigSecrets = Object.fromEntries(
|
||||||
|
(
|
||||||
|
await $getCompleteAzureAppConfigValues(
|
||||||
|
accessToken,
|
||||||
|
secretSync.destinationConfig.configurationUrl,
|
||||||
|
azureAppConfigValuesUrl
|
||||||
|
)
|
||||||
|
).map((entry) => [entry.key, entry.value])
|
||||||
|
);
|
||||||
|
|
||||||
|
Object.keys(azureAppConfigSecrets).forEach((key) => {
|
||||||
|
secretMap[key] = {
|
||||||
|
value: azureAppConfigSecrets[key]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return secretMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
syncSecrets,
|
||||||
|
removeSecrets,
|
||||||
|
getSecrets
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
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 AzureAppConfigurationSyncDestinationConfigSchema = z.object({
|
||||||
|
configurationUrl: z
|
||||||
|
.string()
|
||||||
|
.min(1, "App Configuration URL required")
|
||||||
|
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_APP_CONFIGURATION.CONFIGURATION_URL),
|
||||||
|
label: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.AZURE_APP_CONFIGURATION.LABEL)
|
||||||
|
});
|
||||||
|
|
||||||
|
const AzureAppConfigurationSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||||
|
|
||||||
|
export const AzureAppConfigurationSyncSchema = BaseSecretSyncSchema(
|
||||||
|
SecretSync.AzureAppConfiguration,
|
||||||
|
AzureAppConfigurationSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destination: z.literal(SecretSync.AzureAppConfiguration),
|
||||||
|
destinationConfig: AzureAppConfigurationSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateAzureAppConfigurationSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.AzureAppConfiguration,
|
||||||
|
AzureAppConfigurationSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: AzureAppConfigurationSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateAzureAppConfigurationSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.AzureAppConfiguration,
|
||||||
|
AzureAppConfigurationSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: AzureAppConfigurationSyncDestinationConfigSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureAppConfigurationSyncListItemSchema = z.object({
|
||||||
|
name: z.literal("Azure App Configuration"),
|
||||||
|
connection: z.literal(AppConnection.AzureAppConfiguration),
|
||||||
|
destination: z.literal(SecretSync.AzureAppConfiguration),
|
||||||
|
canImportSecrets: z.literal(true)
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TAzureAppConfigurationConnection } from "@app/services/app-connection/azure-app-configuration";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AzureAppConfigurationSyncListItemSchema,
|
||||||
|
AzureAppConfigurationSyncSchema,
|
||||||
|
CreateAzureAppConfigurationSyncSchema
|
||||||
|
} from "./azure-app-configuration-sync-schemas";
|
||||||
|
|
||||||
|
export type TAzureAppConfigurationSync = z.infer<typeof AzureAppConfigurationSyncSchema>;
|
||||||
|
|
||||||
|
export type TAzureAppConfigurationSyncInput = z.infer<typeof CreateAzureAppConfigurationSyncSchema>;
|
||||||
|
|
||||||
|
export type TAzureAppConfigurationSyncListItem = z.infer<typeof AzureAppConfigurationSyncListItemSchema>;
|
||||||
|
|
||||||
|
export type TAzureAppConfigurationSyncWithCredentials = TAzureAppConfigurationSync & {
|
||||||
|
connection: TAzureAppConfigurationConnection;
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./azure-app-configuration-sync-constants";
|
||||||
|
export * from "./azure-app-configuration-sync-fns";
|
||||||
|
export * from "./azure-app-configuration-sync-schemas";
|
||||||
|
export * from "./azure-app-configuration-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 AZURE_KEY_VAULT_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||||
|
name: "Azure Key Vault",
|
||||||
|
destination: SecretSync.AzureKeyVault,
|
||||||
|
connection: AppConnection.AzureKeyVault,
|
||||||
|
canImportSecrets: true
|
||||||
|
};
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
|
import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-key-vault";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
import { SecretSyncError } from "../secret-sync-errors";
|
||||||
|
import { GetAzureKeyVaultSecret, TAzureKeyVaultSyncWithCredentials } from "./azure-key-vault-sync-types";
|
||||||
|
|
||||||
|
type TAzureKeyVaultSecretSyncFactoryDeps = {
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const azureKeyVaultSecretSyncFactory = ({
|
||||||
|
kmsService,
|
||||||
|
appConnectionDAL
|
||||||
|
}: TAzureKeyVaultSecretSyncFactoryDeps) => {
|
||||||
|
const $getAzureKeyVaultSecrets = async (accessToken: string, vaultBaseUrl: string) => {
|
||||||
|
const paginateAzureKeyVaultSecrets = async () => {
|
||||||
|
let result: GetAzureKeyVaultSecret[] = [];
|
||||||
|
|
||||||
|
let currentUrl = `${vaultBaseUrl}/secrets?api-version=7.3`;
|
||||||
|
|
||||||
|
while (currentUrl) {
|
||||||
|
const res = await request.get<{ value: GetAzureKeyVaultSecret; nextLink: string }>(currentUrl, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
result = result.concat(res.data.value);
|
||||||
|
currentUrl = res.data.nextLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAzureKeyVaultSecrets = await paginateAzureKeyVaultSecrets();
|
||||||
|
|
||||||
|
const enabledAzureKeyVaultSecrets = getAzureKeyVaultSecrets.filter((secret) => secret.attributes.enabled);
|
||||||
|
|
||||||
|
// disabled keys to skip sending updates to
|
||||||
|
const disabledAzureKeyVaultSecretKeys = getAzureKeyVaultSecrets
|
||||||
|
.filter(({ attributes }) => !attributes.enabled)
|
||||||
|
.map((getAzureKeyVaultSecret) => {
|
||||||
|
return getAzureKeyVaultSecret.id.substring(getAzureKeyVaultSecret.id.lastIndexOf("/") + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let lastSlashIndex: number;
|
||||||
|
const res = (
|
||||||
|
await Promise.all(
|
||||||
|
enabledAzureKeyVaultSecrets.map(async (getAzureKeyVaultSecret) => {
|
||||||
|
if (!lastSlashIndex) {
|
||||||
|
lastSlashIndex = getAzureKeyVaultSecret.id.lastIndexOf("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
const azureKeyVaultSecret = await request.get<GetAzureKeyVaultSecret>(
|
||||||
|
`${getAzureKeyVaultSecret.id}?api-version=7.3`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...azureKeyVaultSecret.data,
|
||||||
|
key: getAzureKeyVaultSecret.id.substring(lastSlashIndex + 1)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).reduce(
|
||||||
|
(obj, secret) => ({
|
||||||
|
...obj,
|
||||||
|
[secret.key]: secret
|
||||||
|
}),
|
||||||
|
{} as Record<string, GetAzureKeyVaultSecret>
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
vaultSecrets: res,
|
||||||
|
disabledAzureKeyVaultSecretKeys
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncSecrets = async (secretSync: TAzureKeyVaultSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
|
const { accessToken } = await getAzureConnectionAccessToken(secretSync.connection.id, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
|
const { vaultSecrets, disabledAzureKeyVaultSecretKeys } = await $getAzureKeyVaultSecrets(
|
||||||
|
accessToken,
|
||||||
|
secretSync.destinationConfig.vaultBaseUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
const setSecrets: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
const deleteSecrets: string[] = [];
|
||||||
|
|
||||||
|
Object.keys(secretMap).forEach((infisicalKey) => {
|
||||||
|
const hyphenatedKey = infisicalKey.replace(/_/g, "-");
|
||||||
|
if (!(hyphenatedKey in vaultSecrets)) {
|
||||||
|
// case: secret has been created
|
||||||
|
setSecrets.push({
|
||||||
|
key: hyphenatedKey,
|
||||||
|
value: secretMap[infisicalKey].value
|
||||||
|
});
|
||||||
|
} else if (secretMap[infisicalKey].value !== vaultSecrets[hyphenatedKey].value) {
|
||||||
|
// case: secret has been updated
|
||||||
|
setSecrets.push({
|
||||||
|
key: hyphenatedKey,
|
||||||
|
value: secretMap[infisicalKey].value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(vaultSecrets).forEach((key) => {
|
||||||
|
const underscoredKey = key.replace(/-/g, "_");
|
||||||
|
if (!(underscoredKey in secretMap)) {
|
||||||
|
deleteSecrets.push(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const setSecretAzureKeyVault = async ({ key, value }: { key: string; value: string }) => {
|
||||||
|
let isSecretSet = false;
|
||||||
|
let syncError: Error | null = null;
|
||||||
|
let maxTries = 6;
|
||||||
|
if (disabledAzureKeyVaultSecretKeys.includes(key)) return;
|
||||||
|
|
||||||
|
while (!isSecretSet && maxTries > 0) {
|
||||||
|
// try to set secret
|
||||||
|
try {
|
||||||
|
await request.put(
|
||||||
|
`${secretSync.destinationConfig.vaultBaseUrl}/secrets/${key}?api-version=7.3`,
|
||||||
|
{
|
||||||
|
value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
isSecretSet = true;
|
||||||
|
} catch (err) {
|
||||||
|
syncError = err as Error;
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
if (err.response?.data?.error?.innererror?.code === "ObjectIsDeletedButRecoverable") {
|
||||||
|
await request.post(
|
||||||
|
`${secretSync.destinationConfig.vaultBaseUrl}/deletedsecrets/${key}/recover?api-version=7.3`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 10_000);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 10_000);
|
||||||
|
});
|
||||||
|
maxTries -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSecretSet) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error: syncError,
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for await (const setSecret of setSecrets) {
|
||||||
|
const { key, value } = setSecret;
|
||||||
|
await setSecretAzureKeyVault({
|
||||||
|
key,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const deleteSecretKey of deleteSecrets.filter(
|
||||||
|
(secret) => !setSecrets.find((setSecret) => setSecret.key === secret)
|
||||||
|
)) {
|
||||||
|
await request.delete(`${secretSync.destinationConfig.vaultBaseUrl}/secrets/${deleteSecretKey}?api-version=7.3`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeSecrets = async (secretSync: TAzureKeyVaultSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
|
const { accessToken } = await getAzureConnectionAccessToken(secretSync.connection.id, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
|
const { vaultSecrets, disabledAzureKeyVaultSecretKeys } = await $getAzureKeyVaultSecrets(
|
||||||
|
accessToken,
|
||||||
|
secretSync.destinationConfig.vaultBaseUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
for await (const [key] of Object.entries(vaultSecrets)) {
|
||||||
|
const underscoredKey = key.replace(/-/g, "_");
|
||||||
|
|
||||||
|
if (underscoredKey in secretMap) {
|
||||||
|
if (!disabledAzureKeyVaultSecretKeys.includes(underscoredKey)) {
|
||||||
|
await request.delete(`${secretSync.destinationConfig.vaultBaseUrl}/secrets/${key}?api-version=7.3`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSecrets = async (secretSync: TAzureKeyVaultSyncWithCredentials) => {
|
||||||
|
const { accessToken } = await getAzureConnectionAccessToken(secretSync.connection.id, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
|
const { vaultSecrets, disabledAzureKeyVaultSecretKeys } = await $getAzureKeyVaultSecrets(
|
||||||
|
accessToken,
|
||||||
|
secretSync.destinationConfig.vaultBaseUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
const secretMap: TSecretMap = {};
|
||||||
|
|
||||||
|
Object.keys(vaultSecrets).forEach((key) => {
|
||||||
|
if (!disabledAzureKeyVaultSecretKeys.includes(key)) {
|
||||||
|
const underscoredKey = key.replace(/-/g, "_");
|
||||||
|
secretMap[underscoredKey] = {
|
||||||
|
value: vaultSecrets[key].value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return secretMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
syncSecrets,
|
||||||
|
removeSecrets,
|
||||||
|
getSecrets
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
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 AzureKeyVaultSyncDestinationConfigSchema = z.object({
|
||||||
|
vaultBaseUrl: z
|
||||||
|
.string()
|
||||||
|
.url("Invalid vault base URL format")
|
||||||
|
.min(1, "Vault base URL required")
|
||||||
|
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_KEY_VAULT.VAULT_BASE_URL)
|
||||||
|
});
|
||||||
|
|
||||||
|
const AzureKeyVaultSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||||
|
|
||||||
|
export const AzureKeyVaultSyncSchema = BaseSecretSyncSchema(
|
||||||
|
SecretSync.AzureKeyVault,
|
||||||
|
AzureKeyVaultSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destination: z.literal(SecretSync.AzureKeyVault),
|
||||||
|
destinationConfig: AzureKeyVaultSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateAzureKeyVaultSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.AzureKeyVault,
|
||||||
|
AzureKeyVaultSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: AzureKeyVaultSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateAzureKeyVaultSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.AzureKeyVault,
|
||||||
|
AzureKeyVaultSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: AzureKeyVaultSyncDestinationConfigSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureKeyVaultSyncListItemSchema = z.object({
|
||||||
|
name: z.literal("Azure Key Vault"),
|
||||||
|
connection: z.literal(AppConnection.AzureKeyVault),
|
||||||
|
destination: z.literal(SecretSync.AzureKeyVault),
|
||||||
|
canImportSecrets: z.literal(true)
|
||||||
|
});
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TAzureKeyVaultConnection } from "@app/services/app-connection/azure-key-vault";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AzureKeyVaultSyncListItemSchema,
|
||||||
|
AzureKeyVaultSyncSchema,
|
||||||
|
CreateAzureKeyVaultSyncSchema
|
||||||
|
} from "./azure-key-vault-sync-schemas";
|
||||||
|
|
||||||
|
export type TAzureKeyVaultSync = z.infer<typeof AzureKeyVaultSyncSchema>;
|
||||||
|
|
||||||
|
export type TAzureKeyVaultSyncInput = z.infer<typeof CreateAzureKeyVaultSyncSchema>;
|
||||||
|
|
||||||
|
export type TAzureKeyVaultSyncListItem = z.infer<typeof AzureKeyVaultSyncListItemSchema>;
|
||||||
|
|
||||||
|
export type TAzureKeyVaultSyncWithCredentials = TAzureKeyVaultSync & {
|
||||||
|
connection: TAzureKeyVaultConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface GetAzureKeyVaultSecret {
|
||||||
|
id: string; // secret URI
|
||||||
|
value: string;
|
||||||
|
attributes: {
|
||||||
|
enabled: boolean;
|
||||||
|
created: number;
|
||||||
|
updated: number;
|
||||||
|
recoveryLevel: string;
|
||||||
|
recoverableDays: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureKeyVaultSecret extends GetAzureKeyVaultSecret {
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./azure-key-vault-sync-constants";
|
||||||
|
export * from "./azure-key-vault-sync-fns";
|
||||||
|
export * from "./azure-key-vault-sync-schemas";
|
||||||
|
export * from "./azure-key-vault-sync-types";
|
||||||
@@ -2,7 +2,9 @@ export enum SecretSync {
|
|||||||
AWSParameterStore = "aws-parameter-store",
|
AWSParameterStore = "aws-parameter-store",
|
||||||
AWSSecretsManager = "aws-secrets-manager",
|
AWSSecretsManager = "aws-secrets-manager",
|
||||||
GitHub = "github",
|
GitHub = "github",
|
||||||
GCPSecretManager = "gcp-secret-manager"
|
GCPSecretManager = "gcp-secret-manager",
|
||||||
|
AzureKeyVault = "azure-key-vault",
|
||||||
|
AzureAppConfiguration = "azure-app-configuration"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretSyncInitialSyncBehavior {
|
export enum SecretSyncInitialSyncBehavior {
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ import {
|
|||||||
TSecretSyncWithCredentials
|
TSecretSyncWithCredentials
|
||||||
} from "@app/services/secret-sync/secret-sync-types";
|
} from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
|
||||||
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
|
import {
|
||||||
|
AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION,
|
||||||
|
azureAppConfigurationSecretSyncFactory
|
||||||
|
} from "./azure-app-configuration";
|
||||||
|
import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSecretSyncFactory } from "./azure-key-vault";
|
||||||
import { GCP_SYNC_LIST_OPTION } from "./gcp";
|
import { GCP_SYNC_LIST_OPTION } from "./gcp";
|
||||||
import { GcpSyncFns } from "./gcp/gcp-sync-fns";
|
import { GcpSyncFns } from "./gcp/gcp-sync-fns";
|
||||||
|
|
||||||
@@ -24,13 +31,20 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
|
|||||||
[SecretSync.AWSParameterStore]: AWS_PARAMETER_STORE_SYNC_LIST_OPTION,
|
[SecretSync.AWSParameterStore]: AWS_PARAMETER_STORE_SYNC_LIST_OPTION,
|
||||||
[SecretSync.AWSSecretsManager]: AWS_SECRETS_MANAGER_SYNC_LIST_OPTION,
|
[SecretSync.AWSSecretsManager]: AWS_SECRETS_MANAGER_SYNC_LIST_OPTION,
|
||||||
[SecretSync.GitHub]: GITHUB_SYNC_LIST_OPTION,
|
[SecretSync.GitHub]: GITHUB_SYNC_LIST_OPTION,
|
||||||
[SecretSync.GCPSecretManager]: GCP_SYNC_LIST_OPTION
|
[SecretSync.GCPSecretManager]: GCP_SYNC_LIST_OPTION,
|
||||||
|
[SecretSync.AzureKeyVault]: AZURE_KEY_VAULT_SYNC_LIST_OPTION,
|
||||||
|
[SecretSync.AzureAppConfiguration]: AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listSecretSyncOptions = () => {
|
export const listSecretSyncOptions = () => {
|
||||||
return Object.values(SECRET_SYNC_LIST_OPTIONS).sort((a, b) => a.name.localeCompare(b.name));
|
return Object.values(SECRET_SYNC_LIST_OPTIONS).sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TSyncSecretDeps = {
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
};
|
||||||
|
|
||||||
// const addAffixes = (secretSync: TSecretSyncWithCredentials, unprocessedSecretMap: TSecretMap) => {
|
// const addAffixes = (secretSync: TSecretSyncWithCredentials, unprocessedSecretMap: TSecretMap) => {
|
||||||
// let secretMap = { ...unprocessedSecretMap };
|
// let secretMap = { ...unprocessedSecretMap };
|
||||||
//
|
//
|
||||||
@@ -72,7 +86,11 @@ export const listSecretSyncOptions = () => {
|
|||||||
// };
|
// };
|
||||||
|
|
||||||
export const SecretSyncFns = {
|
export const SecretSyncFns = {
|
||||||
syncSecrets: (secretSync: TSecretSyncWithCredentials, secretMap: TSecretMap): Promise<void> => {
|
syncSecrets: (
|
||||||
|
secretSync: TSecretSyncWithCredentials,
|
||||||
|
secretMap: TSecretMap,
|
||||||
|
{ kmsService, appConnectionDAL }: TSyncSecretDeps
|
||||||
|
): Promise<void> => {
|
||||||
// const affixedSecretMap = addAffixes(secretSync, secretMap);
|
// const affixedSecretMap = addAffixes(secretSync, secretMap);
|
||||||
|
|
||||||
switch (secretSync.destination) {
|
switch (secretSync.destination) {
|
||||||
@@ -84,13 +102,26 @@ export const SecretSyncFns = {
|
|||||||
return GithubSyncFns.syncSecrets(secretSync, secretMap);
|
return GithubSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
case SecretSync.GCPSecretManager:
|
case SecretSync.GCPSecretManager:
|
||||||
return GcpSyncFns.syncSecrets(secretSync, secretMap);
|
return GcpSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
|
case SecretSync.AzureKeyVault:
|
||||||
|
return azureKeyVaultSecretSyncFactory({
|
||||||
|
appConnectionDAL,
|
||||||
|
kmsService
|
||||||
|
}).syncSecrets(secretSync, secretMap);
|
||||||
|
case SecretSync.AzureAppConfiguration:
|
||||||
|
return azureAppConfigurationSecretSyncFactory({
|
||||||
|
appConnectionDAL,
|
||||||
|
kmsService
|
||||||
|
}).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}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getSecrets: async (secretSync: TSecretSyncWithCredentials): Promise<TSecretMap> => {
|
getSecrets: async (
|
||||||
|
secretSync: TSecretSyncWithCredentials,
|
||||||
|
{ kmsService, appConnectionDAL }: TSyncSecretDeps
|
||||||
|
): Promise<TSecretMap> => {
|
||||||
let secretMap: TSecretMap;
|
let secretMap: TSecretMap;
|
||||||
switch (secretSync.destination) {
|
switch (secretSync.destination) {
|
||||||
case SecretSync.AWSParameterStore:
|
case SecretSync.AWSParameterStore:
|
||||||
@@ -105,6 +136,18 @@ export const SecretSyncFns = {
|
|||||||
case SecretSync.GCPSecretManager:
|
case SecretSync.GCPSecretManager:
|
||||||
secretMap = await GcpSyncFns.getSecrets(secretSync);
|
secretMap = await GcpSyncFns.getSecrets(secretSync);
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.AzureKeyVault:
|
||||||
|
secretMap = await azureKeyVaultSecretSyncFactory({
|
||||||
|
appConnectionDAL,
|
||||||
|
kmsService
|
||||||
|
}).getSecrets(secretSync);
|
||||||
|
break;
|
||||||
|
case SecretSync.AzureAppConfiguration:
|
||||||
|
secretMap = await azureAppConfigurationSecretSyncFactory({
|
||||||
|
appConnectionDAL,
|
||||||
|
kmsService
|
||||||
|
}).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}`
|
||||||
@@ -114,7 +157,11 @@ export const SecretSyncFns = {
|
|||||||
return secretMap;
|
return secretMap;
|
||||||
// return stripAffixes(secretSync, secretMap);
|
// return stripAffixes(secretSync, secretMap);
|
||||||
},
|
},
|
||||||
removeSecrets: (secretSync: TSecretSyncWithCredentials, secretMap: TSecretMap): Promise<void> => {
|
removeSecrets: (
|
||||||
|
secretSync: TSecretSyncWithCredentials,
|
||||||
|
secretMap: TSecretMap,
|
||||||
|
{ kmsService, appConnectionDAL }: TSyncSecretDeps
|
||||||
|
): Promise<void> => {
|
||||||
// const affixedSecretMap = addAffixes(secretSync, secretMap);
|
// const affixedSecretMap = addAffixes(secretSync, secretMap);
|
||||||
|
|
||||||
switch (secretSync.destination) {
|
switch (secretSync.destination) {
|
||||||
@@ -126,6 +173,16 @@ export const SecretSyncFns = {
|
|||||||
return GithubSyncFns.removeSecrets(secretSync, secretMap);
|
return GithubSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
case SecretSync.GCPSecretManager:
|
case SecretSync.GCPSecretManager:
|
||||||
return GcpSyncFns.removeSecrets(secretSync, secretMap);
|
return GcpSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
|
case SecretSync.AzureKeyVault:
|
||||||
|
return azureKeyVaultSecretSyncFactory({
|
||||||
|
appConnectionDAL,
|
||||||
|
kmsService
|
||||||
|
}).removeSecrets(secretSync, secretMap);
|
||||||
|
case SecretSync.AzureAppConfiguration:
|
||||||
|
return azureAppConfigurationSecretSyncFactory({
|
||||||
|
appConnectionDAL,
|
||||||
|
kmsService
|
||||||
|
}).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}`
|
||||||
|
|||||||
@@ -5,12 +5,16 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
|||||||
[SecretSync.AWSParameterStore]: "AWS Parameter Store",
|
[SecretSync.AWSParameterStore]: "AWS Parameter Store",
|
||||||
[SecretSync.AWSSecretsManager]: "AWS Secrets Manager",
|
[SecretSync.AWSSecretsManager]: "AWS Secrets Manager",
|
||||||
[SecretSync.GitHub]: "GitHub",
|
[SecretSync.GitHub]: "GitHub",
|
||||||
[SecretSync.GCPSecretManager]: "GCP Secret Manager"
|
[SecretSync.GCPSecretManager]: "GCP Secret Manager",
|
||||||
|
[SecretSync.AzureKeyVault]: "Azure Key Vault",
|
||||||
|
[SecretSync.AzureAppConfiguration]: "Azure App Configuration"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||||
[SecretSync.AWSParameterStore]: AppConnection.AWS,
|
[SecretSync.AWSParameterStore]: AppConnection.AWS,
|
||||||
[SecretSync.AWSSecretsManager]: AppConnection.AWS,
|
[SecretSync.AWSSecretsManager]: AppConnection.AWS,
|
||||||
[SecretSync.GitHub]: AppConnection.GitHub,
|
[SecretSync.GitHub]: AppConnection.GitHub,
|
||||||
[SecretSync.GCPSecretManager]: AppConnection.GCP
|
[SecretSync.GCPSecretManager]: AppConnection.GCP,
|
||||||
|
[SecretSync.AzureKeyVault]: AppConnection.AzureKeyVault,
|
||||||
|
[SecretSync.AzureAppConfiguration]: AppConnection.AzureAppConfiguration
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,11 +57,14 @@ import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secre
|
|||||||
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 { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
|
|
||||||
|
import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
|
||||||
|
|
||||||
export type TSecretSyncQueueFactory = ReturnType<typeof secretSyncQueueFactory>;
|
export type TSecretSyncQueueFactory = ReturnType<typeof secretSyncQueueFactory>;
|
||||||
|
|
||||||
type TSecretSyncQueueFactoryDep = {
|
type TSecretSyncQueueFactoryDep = {
|
||||||
queueService: Pick<TQueueServiceFactory, "queue" | "start">;
|
queueService: Pick<TQueueServiceFactory, "queue" | "start">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update">;
|
||||||
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">;
|
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">;
|
||||||
folderDAL: TSecretFolderDALFactory;
|
folderDAL: TSecretFolderDALFactory;
|
||||||
secretV2BridgeDAL: Pick<
|
secretV2BridgeDAL: Pick<
|
||||||
@@ -111,6 +114,7 @@ const getRequeueDelay = (failureCount?: number) => {
|
|||||||
export const secretSyncQueueFactory = ({
|
export const secretSyncQueueFactory = ({
|
||||||
queueService,
|
queueService,
|
||||||
kmsService,
|
kmsService,
|
||||||
|
appConnectionDAL,
|
||||||
keyStore,
|
keyStore,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
secretV2BridgeDAL,
|
secretV2BridgeDAL,
|
||||||
@@ -322,7 +326,10 @@ export const secretSyncQueueFactory = ({
|
|||||||
"Invalid Secret Sync source configuration: folder no longer exists. Please update source environment and secret path."
|
"Invalid Secret Sync source configuration: folder no longer exists. Please update source environment and secret path."
|
||||||
);
|
);
|
||||||
|
|
||||||
const importedSecrets = await SecretSyncFns.getSecrets(secretSync);
|
const importedSecrets = await SecretSyncFns.getSecrets(secretSync, {
|
||||||
|
appConnectionDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
if (!Object.keys(importedSecrets).length) return {};
|
if (!Object.keys(importedSecrets).length) return {};
|
||||||
|
|
||||||
@@ -434,7 +441,10 @@ export const secretSyncQueueFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await SecretSyncFns.syncSecrets(secretSyncWithCredentials, secretMap);
|
await SecretSyncFns.syncSecrets(secretSyncWithCredentials, secretMap, {
|
||||||
|
appConnectionDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
isSynced = true;
|
isSynced = true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -672,7 +682,11 @@ export const secretSyncQueueFactory = ({
|
|||||||
credentials
|
credentials
|
||||||
}
|
}
|
||||||
} as TSecretSyncWithCredentials,
|
} as TSecretSyncWithCredentials,
|
||||||
secretMap
|
secretMap,
|
||||||
|
{
|
||||||
|
appConnectionDAL,
|
||||||
|
kmsService
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
isSuccess = true;
|
isSuccess = true;
|
||||||
|
|||||||
@@ -23,27 +23,51 @@ import {
|
|||||||
TAwsParameterStoreSyncListItem,
|
TAwsParameterStoreSyncListItem,
|
||||||
TAwsParameterStoreSyncWithCredentials
|
TAwsParameterStoreSyncWithCredentials
|
||||||
} from "./aws-parameter-store";
|
} from "./aws-parameter-store";
|
||||||
|
import {
|
||||||
|
TAzureAppConfigurationSync,
|
||||||
|
TAzureAppConfigurationSyncInput,
|
||||||
|
TAzureAppConfigurationSyncListItem,
|
||||||
|
TAzureAppConfigurationSyncWithCredentials
|
||||||
|
} from "./azure-app-configuration";
|
||||||
|
import {
|
||||||
|
TAzureKeyVaultSync,
|
||||||
|
TAzureKeyVaultSyncInput,
|
||||||
|
TAzureKeyVaultSyncListItem,
|
||||||
|
TAzureKeyVaultSyncWithCredentials
|
||||||
|
} from "./azure-key-vault";
|
||||||
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
|
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
|
||||||
|
|
||||||
export type TSecretSync = TAwsParameterStoreSync | TAwsSecretsManagerSync | TGitHubSync | TGcpSync;
|
export type TSecretSync =
|
||||||
|
| TAwsParameterStoreSync
|
||||||
|
| TAwsSecretsManagerSync
|
||||||
|
| TGitHubSync
|
||||||
|
| TGcpSync
|
||||||
|
| TAzureKeyVaultSync
|
||||||
|
| TAzureAppConfigurationSync;
|
||||||
|
|
||||||
export type TSecretSyncWithCredentials =
|
export type TSecretSyncWithCredentials =
|
||||||
| TAwsParameterStoreSyncWithCredentials
|
| TAwsParameterStoreSyncWithCredentials
|
||||||
| TAwsSecretsManagerSyncWithCredentials
|
| TAwsSecretsManagerSyncWithCredentials
|
||||||
| TGitHubSyncWithCredentials
|
| TGitHubSyncWithCredentials
|
||||||
| TGcpSyncWithCredentials;
|
| TGcpSyncWithCredentials
|
||||||
|
| TAzureKeyVaultSyncWithCredentials
|
||||||
|
| TAzureAppConfigurationSyncWithCredentials;
|
||||||
|
|
||||||
export type TSecretSyncInput =
|
export type TSecretSyncInput =
|
||||||
| TAwsParameterStoreSyncInput
|
| TAwsParameterStoreSyncInput
|
||||||
| TAwsSecretsManagerSyncInput
|
| TAwsSecretsManagerSyncInput
|
||||||
| TGitHubSyncInput
|
| TGitHubSyncInput
|
||||||
| TGcpSyncInput;
|
| TGcpSyncInput
|
||||||
|
| TAzureKeyVaultSyncInput
|
||||||
|
| TAzureAppConfigurationSyncInput;
|
||||||
|
|
||||||
export type TSecretSyncListItem =
|
export type TSecretSyncListItem =
|
||||||
| TAwsParameterStoreSyncListItem
|
| TAwsParameterStoreSyncListItem
|
||||||
| TAwsSecretsManagerSyncListItem
|
| TAwsSecretsManagerSyncListItem
|
||||||
| TGitHubSyncListItem
|
| TGitHubSyncListItem
|
||||||
| TGcpSyncListItem;
|
| TGcpSyncListItem
|
||||||
|
| TAzureKeyVaultSyncListItem
|
||||||
|
| TAzureAppConfigurationSyncListItem;
|
||||||
|
|
||||||
export type TSyncOptionsConfig = {
|
export type TSyncOptionsConfig = {
|
||||||
canImportSecrets: boolean;
|
canImportSecrets: boolean;
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Available"
|
||||||
|
openapi: "GET /api/v1/app-connections/azure-app-configuration/available"
|
||||||
|
---
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/app-connections/azure-app-configuration"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Azure App Configuration Connections must be created through the Infisical UI.
|
||||||
|
Check out the configuration docs for [Azure App Configuration Connections](/integrations/app-connections/azure-app-configuration) for a step-by-step
|
||||||
|
guide.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/app-connections/azure-app-configuration/{connectionId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/app-connections/azure-app-configuration/{connectionId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/app-connections/azure-app-configuration/connection-name/{connectionName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/app-connections/azure-app-configuration"
|
||||||
|
---
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/app-connections/azure-app-configuration/{connectionId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Azure App Configuration Connections must be updated through the Infisical UI.
|
||||||
|
Check out the configuration docs for [Azure App Configuration Connections](/integrations/app-connections/azure-app-configuration) for a step-by-step
|
||||||
|
guide.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Available"
|
||||||
|
openapi: "GET /api/v1/app-connections/azure-key-vault/available"
|
||||||
|
---
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/app-connections/azure-key-vault"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Azure Key Vault Connections must be created through the Infisical UI.
|
||||||
|
Check out the configuration docs for [Azure Key Vault Connections](/integrations/app-connections/azure-key-vault) for a step-by-step
|
||||||
|
guide.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/app-connections/azure-key-vault/{connectionId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/app-connections/azure-key-vault/{connectionId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/app-connections/azure-key-vault/connection-name/{connectionName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/app-connections/azure-key-vault"
|
||||||
|
---
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/app-connections/azure-key-vault/{connectionId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Azure Key Vault Connections must be updated through the Infisical UI.
|
||||||
|
Check out the configuration docs for [Azure Key Vault Connections](/integrations/app-connections/azure-key-vault) for a step-by-step
|
||||||
|
guide.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/azure-app-configuration"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/secret-syncs/azure-app-configuration/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/azure-app-configuration/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/azure-app-configuration/sync-name/{syncName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Import Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/azure-app-configuration/{syncId}/import-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/azure-app-configuration"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Remove Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/azure-app-configuration/{syncId}/remove-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Sync Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/azure-app-configuration/{syncId}/sync-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/secret-syncs/azure-app-configuration/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/azure-key-vault"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/secret-syncs/azure-key-vault/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/azure-key-vault/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/azure-key-vault/sync-name/{syncName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Import Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/azure-key-vault/{syncId}/import-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/azure-key-vault"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Remove Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/azure-key-vault/{syncId}/remove-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Sync Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/azure-key-vault/{syncId}/sync-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/secret-syncs/azure-key-vault/{syncId}"
|
||||||
|
---
|
||||||
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 278 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 543 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 207 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 291 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 292 KiB |
@@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
title: "Azure App Configuration Connection"
|
||||||
|
description: "Learn how to configure a Azure App Configuration Connection for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
Infisical currently only supports one method for connecting to Azure, which is OAuth.
|
||||||
|
|
||||||
|
<Accordion title="Self-Hosted Instance">
|
||||||
|
Using the Azure App Configuration connection on a self-hosted instance of Infisical requires configuring an application in Azure
|
||||||
|
and registering your instance with it.
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
|
||||||
|
- Set up Azure and have an existing App Configuration instance.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Create an application in Azure">
|
||||||
|
Navigate to Azure Active Directory > App registrations to create a new application.
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
Azure Active Directory is now Microsoft Entra ID.
|
||||||
|
</Info>
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Create the application. As part of the form, set the **Redirect URI** to `https://your-domain.com/organization/app-connections/azure/oauth/callback`.
|
||||||
|
<Tip>
|
||||||
|
The domain you defined in the Redirect URI should be equivalent to the `SITE_URL` configured in your Infisical instance.
|
||||||
|
</Tip>
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Assign API permissions to the application">
|
||||||
|
|
||||||
|
For the Azure Connection to work with App Configuration, you need to assign multiple permissions to the application.
|
||||||
|
|
||||||
|
#### Azure App Configuration permissions
|
||||||
|
|
||||||
|
Set the API permissions of the Azure application to include the following Azure App Configuration permissions: `KeyValue.Delete`, `KeyValue.Read`, and `KeyValue.Write`.
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<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.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Back in your Infisical instance, add two new environment variables for the credentials of your Azure application.
|
||||||
|
|
||||||
|
- `INF_APP_CONNECTION_AZURE_CLIENT_ID`: The **Application (Client) ID** of your Azure application.
|
||||||
|
- `INF_APP_CONNECTION_AZURE_CLIENT_SECRET`: The **Client Secret** of your Azure application.
|
||||||
|
|
||||||
|
Once added, restart your Infisical instance and use the Azure App Configuration connection.
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
## Setup Azure Connection in Infisical
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Navigate to the App Connections">
|
||||||
|
Navigate to the **App Connections** tab on the **Organization Settings** page. 
|
||||||
|
</Step>
|
||||||
|
<Step title="Add Connection">
|
||||||
|
Select the **Azure Connection** option from the connection options modal. 
|
||||||
|
</Step>
|
||||||
|
<Step title="Authorize Connection">
|
||||||
|
You can optionally authenticate against a specific tenant by providing the Azure Tenant or Directory ID.
|
||||||
|
|
||||||
|
Now select the **OAuth** method and click **Connect to Azure**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step title="Grant Access">
|
||||||
|
You will then be redirected to Azure to grant Infisical access to your Azure account. Once granted,
|
||||||
|
you will redirect you back to Infisical's App Connections page. 
|
||||||
|
</Step>
|
||||||
|
<Step title="Connection Created">
|
||||||
|
Your **Azure App Configuration Connection** is now available for use. 
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
title: "Azure Key Vault Connection"
|
||||||
|
description: "Learn how to configure a Azure Key Vault Connection for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
Infisical currently only supports one method for connecting to Azure, which is OAuth.
|
||||||
|
|
||||||
|
<Accordion title="Self-Hosted Instance">
|
||||||
|
Using the Azure Key Vault connection on a self-hosted instance of Infisical requires configuring an application in Azure
|
||||||
|
and registering your instance with it.
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
|
||||||
|
- Set up Azure and have an existing Key Vault instance.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Create an application in Azure">
|
||||||
|
Navigate to Azure Active Directory > App registrations to create a new application.
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
Azure Active Directory is now Microsoft Entra ID.
|
||||||
|
</Info>
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Create the application. As part of the form, set the **Redirect URI** to `https://your-domain.com/organization/app-connections/azure/oauth/callback`.
|
||||||
|
<Tip>
|
||||||
|
The domain you defined in the Redirect URI should be equivalent to the `SITE_URL` configured in your Infisical instance.
|
||||||
|
</Tip>
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Assign API permissions to the application">
|
||||||
|
|
||||||
|
For the Azure Connection to work with Key Vault, you need to assign multiple permissions to the application.
|
||||||
|
|
||||||
|
#### Azure Key Vault permissions
|
||||||
|
|
||||||
|
Set the API permissions of the Azure application to include `user.impersonation` for the Key Vault API.
|
||||||
|

|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<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.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Back in your Infisical instance, add two new environment variables for the credentials of your Azure application.
|
||||||
|
|
||||||
|
- `INF_APP_CONNECTION_AZURE_CLIENT_ID`: The **Application (Client) ID** of your Azure application.
|
||||||
|
- `INF_APP_CONNECTION_AZURE_CLIENT_SECRET`: The **Client Secret** of your Azure application.
|
||||||
|
|
||||||
|
Once added, restart your Infisical instance and use the Azure Key Vault connection.
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
## Setup Azure Connection in Infisical
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Navigate to the App Connections">
|
||||||
|
Navigate to the **App Connections** tab on the **Organization Settings** page. 
|
||||||
|
</Step>
|
||||||
|
<Step title="Add Connection">
|
||||||
|
Select the **Azure Connection** option from the connection options modal. 
|
||||||
|
</Step>
|
||||||
|
<Step title="Authorize Connection">
|
||||||
|
You can optionally authenticate against a specific tenant by providing the Azure Tenant or Directory ID.
|
||||||
|
|
||||||
|
Now select the **OAuth** method and click **Connect to Azure**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step title="Grant Access">
|
||||||
|
You will then be redirected to Azure to grant Infisical access to your Azure account. Once granted,
|
||||||
|
you will redirect you back to Infisical's App Connections page. 
|
||||||
|
</Step>
|
||||||
|
<Step title="Connection Created">
|
||||||
|
Your **Azure Key Vault Connection** is now available for use. 
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||