Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changes:
- section: Bugs Fixed
description: Deployment IaC guidance now defaults to private networking, workload identity, and least-privilege application roles. Separate opt-in parameters permit public networking, all-Azure-services firewall exceptions, privileged application roles, and secret-based connections.
6 changes: 5 additions & 1 deletion servers/Azure.Mcp.Server/docs/azmcp-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -2798,7 +2798,11 @@ azmcp deploy architecture diagram generate --raw-mcp-tool-input <app-topology>
# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
azmcp deploy iac rules get --deployment-tool <deployment-tool> \
[--iac-type <iac-type>] \
[--resource-types <resource-types>]
[--resource-types <resource-types>] \
[--enable-public-network-access <true|false>] \
[--allow-azure-services <true|false>] \
[--allow-privileged-roles <true|false>] \
[--use-connection-strings <true|false>]

# Get the ci/cd pipeline guidance
# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
Expand Down
8 changes: 8 additions & 0 deletions servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,14 @@ The `Interaction` column describes whether a prompt can invoke its tool immediat
| deploy_app_logs_get | Show me the log of the application deployed by azd | context-required |
| deploy_architecture_diagram_generate | Generate the Azure architecture diagram for this application | context-required |
| deploy_iac_rules_get | Give me the IaC rules for deploying this application to Azure Container Apps using Azure CLI and Bicep | none |
| deploy_iac_rules_get | Get Azure CLI and Bicep rules for AKS, Cosmos DB, Storage, and Key Vault with private networking, workload identity, and least-privilege runtime roles | none |
| deploy_iac_rules_get | Get Azure CLI and Bicep rules for AKS, Cosmos DB, and Key Vault; explicitly permit public networking, all-Azure-services firewall exceptions, privileged application roles, and secret-based connections for this isolated test | none |
| deploy_iac_rules_get | Generate Bicep rules for a Function App with least-privilege storage roles and no owner-level application role | none |
| deploy_iac_rules_get | Generate Terraform rules for a Function App and explicitly allow the Storage Blob Data Owner application role | none |
| deploy_iac_rules_get | Get PostgreSQL and MySQL deployment rules using managed identity authentication without passwords or connection strings | none |
| deploy_iac_rules_get | Get PostgreSQL deployment rules and explicitly allow secret-based connection strings stored in Key Vault | none |
| deploy_iac_rules_get | Generate Azure SQL Database rules with public network access disabled and a private endpoint | none |
| deploy_iac_rules_get | Generate Azure SQL Database rules and explicitly permit public networking for approved client IPs | none |
| deploy_pipeline_guidance_get | Generate a CI/CD pipeline using GitHub Actions workflow to deploy my application to Azure with best practices | context-required |
| deploy_plan_get | Generate an Azure deployment plan for this project using Azure CLI and IaC templates | context-required |

Expand Down
1 change: 1 addition & 0 deletions tools/Azure.Mcp.Tools.Deploy/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ words:
- mycontainerapp
- mylog
- passwordless
- privatelink
- rgabcde
- serviceconnector
- sqlabcde
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ public override Task<CommandResponse> ExecuteAsync(CommandContext context, Rules
string iacRules = IaCRulesTemplateUtil.GetIaCRules(
options.DeploymentTool,
options.IacType ?? string.Empty,
resourceTypes ?? []);
resourceTypes ?? [],
options.EnablePublicNetworkAccess,
options.AllowAzureServices,
options.AllowPrivilegedRoles,
options.UseConnectionStrings);

context.Response.Message = iacRules;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public sealed class IaCRulesTemplateParameters
public string FinalInstructions { get; set; } = string.Empty;
public string RequiredTools { get; set; } = string.Empty;
public string AdditionalNotes { get; set; } = string.Empty;
public bool EnablePublicNetworkAccess { get; set; }
public bool AllowAzureServices { get; set; }
public bool AllowPrivilegedRoles { get; set; }
public bool UseConnectionStrings { get; set; }

/// <summary>
/// Converts the parameters to a dictionary for template processing.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,16 @@ public sealed class RulesGetOptions

[Option(Description = "Comma-separated list of Azure resource types to generate rules for. Get the value from context and use the same resources defined in plan. Valid value: 'appservice', 'containerapp', 'function', 'aks', 'azuredatabaseforpostgresql', 'azuredatabaseformysql', 'azuresqldatabase', 'azurecosmosdb', 'azurestorageaccount', 'azurekeyvault'")]
public string? ResourceTypes { get; set; }

[Option(Description = "Generate guidance permitting public network access (less secure). Defaults to false; private networking is recommended otherwise.")]
public bool EnablePublicNetworkAccess { get; set; }

[Option(Description = "Permit database firewall access from all Azure services, including other customers (insecure). Requires --enable-public-network-access. Defaults to false.")]
public bool AllowAzureServices { get; set; }

[Option(Description = "Recommend elevated application permissions such as Key Vault Secrets Officer (broad access). Defaults to false; least-privilege runtime roles are used otherwise.")]
public bool AllowPrivilegedRoles { get; set; }

[Option(Description = "Use secret-based connection strings instead of workload or managed identity where needed (less secure). Defaults to false; secrets must still be stored securely.")]
public bool UseConnectionStrings { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,26 @@ namespace Azure.Mcp.Tools.Deploy.Services.Util;
/// </summary>
public static class IaCRulesTemplateUtil
{
private static readonly string _databaseCommonRules = TemplateService.LoadTemplate("IaCRules/database-common-rules");

/// <summary>
/// Generates IaC rules using embedded templates.
/// </summary>
/// <param name="deploymentTool">The deployment tool (azd, azcli).</param>
/// <param name="iacType">The IaC type (bicep, terraform).</param>
/// <param name="resourceTypes">Array of resource types.</param>
/// <returns>A formatted IaC rules string.</returns>
public static string GetIaCRules(string deploymentTool, string iacType, string[] resourceTypes)
public static string GetIaCRules(string deploymentTool, string iacType, string[] resourceTypes,
bool enablePublicNetworkAccess = false, bool allowAzureServices = false,
bool allowPrivilegedRoles = false, bool useConnectionStrings = false)
{
if (allowAzureServices && !enablePublicNetworkAccess)
{
throw new ArgumentException("--allow-azure-services requires --enable-public-network-access.");
}
var parameters = CreateTemplateParameters(deploymentTool, iacType, resourceTypes);
parameters.EnablePublicNetworkAccess = enablePublicNetworkAccess;
parameters.AllowAzureServices = allowAzureServices;
parameters.AllowPrivilegedRoles = allowPrivilegedRoles;
parameters.UseConnectionStrings = useConnectionStrings;
// Default values for optional parameters
if (deploymentTool.Equals(DeploymentTool.Azd, StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(iacType))
{
Expand Down Expand Up @@ -127,7 +135,12 @@ private static string GenerateResourceSpecificRules(IaCRulesTemplateParameters p

if (parameters.ResourceTypes.Contains(AzureServiceNames.AzureDatabaseForMySql, StringComparer.OrdinalIgnoreCase))
{
rules.Add(GenerateMySqlRules());
rules.Add(GenerateMySqlRules(parameters));
}

if (parameters.ResourceTypes.Contains(AzureServiceNames.AzureSqlDatabase, StringComparer.OrdinalIgnoreCase))
{
rules.Add(GenerateAzureSqlRules(parameters));
}

if (parameters.ResourceTypes.Contains(AzureServiceNames.AzureCosmosDb, StringComparer.OrdinalIgnoreCase))
Expand Down Expand Up @@ -176,8 +189,16 @@ private static string GenerateAppServiceRules(IaCRulesTemplateParameters paramet

private static string GenerateFunctionAppRules(IaCRulesTemplateParameters parameters)
{
var bicepRules = TemplateService.LoadTemplate("IaCRules/functionapp-bicep-rules");
var tfRules = TemplateService.LoadTemplate("IaCRules/functionapp-tf-rules");
var bicepRules = TemplateService.ProcessTemplate("IaCRules/functionapp-bicep-rules", new Dictionary<string, string> {
{ "PrivilegedRoleRule", parameters.AllowPrivilegedRoles
? "- MANDATORY: Add a Microsoft.Authorization/roleAssignments resource to assign the Storage Blob Data Owner (b7e6dc6d-f1e8-4753-8033-0f276bb0955b) role to the user-assigned managed identity"
: string.Empty }
});
var tfRules = TemplateService.ProcessTemplate("IaCRules/functionapp-tf-rules", new Dictionary<string, string> {
{ "PrivilegedRoleRule", parameters.AllowPrivilegedRoles
? "- MANDATORY: Add a azurerm_role_assignment resource to assign the Storage Blob Data Owner (b7e6dc6d-f1e8-4753-8033-0f276bb0955b) role to the user-assigned managed identity"
: string.Empty }
});
return TemplateService.ProcessTemplate("IaCRules/functionapp-rules", new Dictionary<string, string> {
{ "ToolSpecificRules", GetToolSpecificResourceRules(parameters.IacType, bicepRules, tfRules, null)}
});
Expand All @@ -190,7 +211,12 @@ private static string GenerateAKSRules(IaCRulesTemplateParameters parameters)
var cliRules = TemplateService.LoadTemplate("IaCRules/aks-cli-rules");
return TemplateService.ProcessTemplate("IaCRules/aks-rules", new Dictionary<string, string> {
{ "ToolSpecificRules", GetToolSpecificResourceRules(parameters.IacType, bicepRules, tfRules, cliRules)},
{ "KeyvaultIntegrationRules", TemplateService.LoadTemplate("IaCRules/aks-kv-integration-rules") }
{ "AuthenticationRules", parameters.UseConnectionStrings
? "- Secret-based connection strings are explicitly permitted. Store credentials in Key Vault and consume them through secret references; never plaintext configuration."
: "- Use Azure Workload Identity for AKS connections to Azure services. Enable the OIDC issuer, workload identity, and a dedicated Kubernetes service account with federated credentials and least-privilege data roles." },
{ "KeyvaultIntegrationRules", parameters.UseConnectionStrings
? TemplateService.LoadTemplate("IaCRules/aks-kv-integration-rules")
: "- Access Key Vault using the workload identity and Key Vault Secrets User permissions. Do not share node identity credentials with application pods." }
});
}

Expand All @@ -199,33 +225,72 @@ private static string GeneratePostgreSqlRules(IaCRulesTemplateParameters paramet
var versionRules = parameters.IacType.Equals(IacType.Terraform, StringComparison.OrdinalIgnoreCase)
? "- PostgreSQL SKU name format: B_Standard_B1ms(Burstable tier), GP_Standard_D2s_v3(GeneralPurpose), MO_Standard_E4s_v3(MemoryOptimized)\n- For version, prefer to use '16'."
: "For version, use '17' or higher.";
var cliRules = "- If PostgreSQL server uses Azure AD authentication, use '--microsoft-entra-auth Enabled' when creating.\n- Azure CLI uses parameters '--name <server-name> --rule-name <rule-name>' for firewall rules creation.\n- IMPORTANT: **If using Azure AD authentication, you MUST ADD a database USER for the managed identity and GRANT all privileges to make the connection work.** Use 'az postgres flexible-server execute' command to run SQL commands to create the user and grant privileges.";
var cliRules = parameters.AllowPrivilegedRoles
? "- Elevated database privileges are explicitly permitted. Create the managed identity database user and grant the requested privileges only within the application's database."
: "- Create a database user for the managed identity. Grant only CONNECT, required schema USAGE, and the table operations required by the application; do not grant ALL privileges by default.";
return TemplateService.ProcessTemplate("IaCRules/postgresql-rules", new Dictionary<string, string> {
{ "VersionRules", versionRules },
{ "DatabaseCommonRules", _databaseCommonRules},
{ "DatabaseCommonRules", GenerateDatabaseCommonRules(parameters)},
{ "ToolSpecificRules", GetToolSpecificResourceRules(parameters.IacType, null, null, cliRules)}
});
}

private static string GenerateMySqlRules() =>
TemplateService.ProcessTemplate("IaCRules/mysql-rules", new Dictionary<string, string> { { "DatabaseCommonRules", _databaseCommonRules } });
private static string GenerateMySqlRules(IaCRulesTemplateParameters parameters) =>
TemplateService.ProcessTemplate("IaCRules/mysql-rules", new Dictionary<string, string> { { "DatabaseCommonRules", GenerateDatabaseCommonRules(parameters) } });

private static string GenerateAzureSqlRules(IaCRulesTemplateParameters parameters) =>
TemplateService.ProcessTemplate("IaCRules/azuresql-rules", new Dictionary<string, string> {
{ "DatabaseCommonRules", GenerateDatabaseCommonRules(parameters) },
{ "SqlNetworkRules", parameters.EnablePublicNetworkAccess
? "- Set publicNetworkAccess to Enabled on the Azure SQL logical server only when public networking is explicitly requested. Restrict server firewall access to approved IPs."
: "- Set publicNetworkAccess to Disabled on the Azure SQL logical server. Configure a private endpoint for the sqlServer subresource and the privatelink.database.windows.net private DNS zone for the application and deployment runner." }
});

private static string GenerateDatabaseCommonRules(IaCRulesTemplateParameters parameters) =>
TemplateService.ProcessTemplate("IaCRules/database-common-rules", new Dictionary<string, string> {
{ "NetworkRules", GenerateNetworkRules(parameters) },
{ "AuthenticationRules", parameters.UseConnectionStrings
? "- If username and password are required, you MUST leave them as params.\n- Create secrets in Key Vault to store the connection string or credentials, and assign `Key Vault Secrets User` role to the user-assigned managed identity."
: "- Use Microsoft Entra managed identity and passwordless database authentication. Create a database user mapped to the application identity with only the required permissions; do not request passwords or create connection-string secrets." }
});

private static string GenerateNetworkRules(IaCRulesTemplateParameters parameters) =>
!parameters.EnablePublicNetworkAccess
? "- Disable public network access. Configure supported private endpoints or virtual network integration, private DNS, and connectivity for the application and deployment runner."
: parameters.AllowAzureServices
? "- Public access and the all-Azure-services firewall exception (0.0.0.0) are explicitly permitted for databases. This includes other customers' subscriptions and is not tenant isolation."
: "- Public network access is explicitly permitted. Restrict firewall access to explicitly approved client IPs or subnets; do not add an all-Azure-services exception.";
Comment on lines +257 to +262

private static string GenerateCosmosDbRules(IaCRulesTemplateParameters parameters) =>
TemplateService.ProcessTemplate("IaCRules/cosmosdb-rules", new Dictionary<string, string> { { "ToolSpecificRules", GetToolSpecificResourceRules(parameters.IacType, null, null, null) } });
TemplateService.ProcessTemplate("IaCRules/cosmosdb-rules", new Dictionary<string, string> { { "NetworkRules", GenerateNetworkRules(parameters) } });

private static string GenerateStorageRules(IaCRulesTemplateParameters parameters)
{
var tfRules = "- Add `storage_use_azuread = true` in azurerm provider.";
return TemplateService.ProcessTemplate("IaCRules/storage-rules", new Dictionary<string, string> { { "ToolSpecificRules", GetToolSpecificResourceRules(parameters.IacType, null, tfRules, null) } });
return TemplateService.ProcessTemplate("IaCRules/storage-rules", new Dictionary<string, string> {
{ "ToolSpecificRules", GetToolSpecificResourceRules(parameters.IacType, null, tfRules, null) },
{ "NetworkRules", parameters.EnablePublicNetworkAccess
? "- Public network access is explicitly permitted; restrict access to approved networks."
: "- Disable public network access and configure private endpoints and private DNS." },
{ "AuthenticationRules", parameters.UseConnectionStrings
? "- Key authentication is explicitly permitted when required for secret-based connections. Store keys in Key Vault."
: "- Disable storage account local auth (key access); use Microsoft Entra ID." }
});
}

private static string GenerateKeyVaultRules(IaCRulesTemplateParameters parameters)
{
var bicepRules = "- Allow public access from all networks(set publicNetworkAccess = Enabled).";
var bicepRules = string.Empty;
var tfRules = "- Assign role 'Key Vault Secrets Officer (b86a8fe4-44ce-4948-aee5-eccb2c155cd7)' to current user.This is the dependency for key vault secret creation.";
var cliRules = "- IMPORTANT: Assign Key Vault Secrets Officer to current user. Add delay after RBAC role assignment to allow propagation before creating secrets.";

return TemplateService.ProcessTemplate("IaCRules/key-vault-rules", new Dictionary<string, string> { { "ToolSpecificRules", GetToolSpecificResourceRules(parameters.IacType, bicepRules, tfRules, cliRules) } });
return TemplateService.ProcessTemplate("IaCRules/key-vault-rules", new Dictionary<string, string> {
{ "ToolSpecificRules", GetToolSpecificResourceRules(parameters.IacType, bicepRules, tfRules, cliRules) },
{ "RuntimeRole", parameters.AllowPrivilegedRoles ? "Key Vault Secrets Officer" : "Key Vault Secrets User" },
{ "NetworkRules", parameters.EnablePublicNetworkAccess
? "- Public network access is explicitly permitted for Key Vault; restrict its firewall to approved networks."
: "- Set publicNetworkAccess to Disabled for Key Vault. Configure private endpoints and private DNS for both the application and deployment runner." }
});
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
- **CRITICAL** PREREQUISITE: You MUST use a stable kubernetesVersion, get by `az aks get-versions --query 'values[?isDefault].version' --location <> -o tsv`.
- For SKU, set based on appmod-get-available-region-sku output to get available VM size in the region, e.g. Standard_D2ds_v5.
- Node pool names must be 6 characters or less (e.g., 'sys', 'user', 'win01').
- For AKS connections to other Azure services (storage, databases, etc.) EXCEPT ACR, use connection strings/secrets directly instead of workload identity or service account, etc.
{{AuthenticationRules}}
{{ToolSpecificRules}}
{{KeyvaultIntegrationRules}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
=== Additional requirements for Azure SQL Database:
- Apply networking and authentication rules to the Microsoft.Sql/servers logical server hosting the database.
{{DatabaseCommonRules}}
{{SqlNetworkRules}}
Loading
Loading