Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
20e4ab2
first draft of virtualconnections 2026-09-01
cmriachi Aug 11, 2026
a5c7e8a
change auth value from mi to managedIdentity
cmriachi Aug 13, 2026
009752b
fix some validation errors
cmriachi Aug 13, 2026
92c31dc
add avoidadditionalproperties directive
cmriachi Aug 13, 2026
5873ec9
address remaining errors
cmriachi Aug 14, 2026
97da8fa
fix
cmriachi Aug 14, 2026
2a8a4bc
fix example errors
cmriachi Aug 14, 2026
4cfb77a
fix tsp errors
cmriachi Aug 14, 2026
0ac74a1
Merge remote-tracking branch 'origin/main' into feature/microsoft-web…
Copilot Aug 14, 2026
272529b
Regenerate AppService swagger and service.yaml via tsp compile
Copilot Aug 14, 2026
605fe07
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Aug 14, 2026
886dc25
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Aug 17, 2026
97661f9
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Aug 18, 2026
e0c26b7
Address VirtualConnections review feedback
cmriachi Aug 21, 2026
2eb33c4
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Aug 21, 2026
5ca6f72
Emit provisioning states as read-only
cmriachi Aug 24, 2026
575b4f3
Merge branch 'feature/microsoft-web-virtualconnections-2026-09-01' of…
cmriachi Aug 24, 2026
04309c3
Revert "Emit provisioning states as read-only"
cmriachi Aug 24, 2026
19ab9cb
Reapply "Emit provisioning states as read-only"
cmriachi Aug 24, 2026
9902043
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Aug 24, 2026
264b873
Merge branch 'feature/microsoft-web-virtualconnections-2026-09-01' of…
cmriachi Sep 1, 2026
ffb6b06
Remove stale Java API version pin
cmriachi Sep 1, 2026
7e18e1c
implement the rename
cmriachi Sep 21, 2026
597de62
fixes to match implementation
cmriachi Sep 22, 2026
ee32e63
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Sep 22, 2026
5b7a2b9
ignoreapi egress config from siteconfig
cmriachi Sep 23, 2026
b328fb8
Clarify egress suppression rationales with RP precedent
cmriachi Sep 24, 2026
2d525e7
Use PascalCase egress enum values
cmriachi Sep 29, 2026
3dc3a39
Remove stale C# SDK API version pin
cmriachi Sep 29, 2026
8fca085
Close egress validation unions
cmriachi Sep 29, 2026
461a603
Document egress setting key constraints
cmriachi Sep 29, 2026
d0c41a7
Format egress TypeSpec decorators
cmriachi Sep 29, 2026
c0125ef
Document egress write validation
cmriachi Sep 29, 2026
7e482a4
remove validate endpoint
cmriachi Oct 6, 2026
a985531
update api operations
cmriachi Oct 6, 2026
fed7ff8
Remove obsolete egress GET suppression
cmriachi Oct 6, 2026
a2646b4
Remove obsolete egress action-path suppression
cmriachi Oct 6, 2026
c518744
Clarify typed egress dictionary suppression rationale
cmriachi Oct 6, 2026
89fef84
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Oct 6, 2026
166f5f7
make most enums open
cmriachi Oct 14, 2026
6749315
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Oct 14, 2026
1f46498
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Oct 15, 2026
10c2fa7
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Oct 16, 2026
4a4afd4
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Oct 16, 2026
33eea96
Preserve published AppService status-schema mutability
cmriachi Oct 19, 2026
32bbf6a
Address Egress API review warnings
cmriachi Oct 20, 2026
9fb22e2
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Oct 20, 2026
c626632
Merge branch 'main' into feature/microsoft-web-virtualconnections-202…
cmriachi Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -0,0 +1,372 @@
import "@azure-tools/typespec-azure-core";
import "@azure-tools/typespec-azure-resource-manager";
import "@typespec/rest";
import "@typespec/versioning";
import "./models.tsp";
import "./Site.tsp";

using TypeSpec.Rest;
using Azure.ResourceManager;
using TypeSpec.Http;
using TypeSpec.Versioning;

namespace Microsoft.Web;

/**
* Egress configuration for a web app. Declares which app settings
* and connection strings are virtualized so secrets never enter the application
* sandbox at runtime.
*
* This is a proxy-only configuration singleton following the established pattern
* of other config sub-resources (authsettingsV2, azurestorageaccounts).
*/
@added(Versions.v2026_09_01)
@singleton("egress")
@parentResource(Site)
model EgressConfig
is Azure.ResourceManager.ProxyResource<EgressConfigProperties> {
...ResourceNameParameter<
Resource = EgressConfig,
KeyName = "config",
SegmentName = "config",
NamePattern = ""
>;
}

/** Properties of the egress configuration. */
#suppress "@azure-tools/typespec-azure-resource-manager/arm-resource-provisioning-state" "egress is a proxy-only sites/config singleton that is written synchronously and does not track a provisioning state, consistent with the other Microsoft.Web config singletons such as authsettingsV2."

Check warning on line 37 in specification/web/resource-manager/Microsoft.Web/AppService/EgressConfig.tsp

View workflow job for this annotation

GitHub Actions / TypeSpec Suppressions - Analyze Code

⚠️ New Suppression

Authors should avoid adding new suppressions and prefer fixing the underlying issue; reviewers should approve only when there is a clear, compelling justification and no reasonable alternative.
@added(Versions.v2026_09_01)
model EgressConfigProperties {
/** Whether virtual connections are enabled for this site. */
enabled?: EgressEnabledState;

/**
* All virtualized connections. Key is a customer-chosen connection name
* (e.g., "sql", "cache", "storage") of at most 256 characters, which must not
* end in whitespace and must be unique within the site regardless of casing.
*
* This collection is authoritative: a write replaces the stored connections in
* full, and omitting it (or supplying an empty object) deletes the egress
* configuration for the site.
*/
#suppress "@azure-tools/typespec-azure-resource-manager/arm-no-record" "Connection names are arbitrary customer-chosen keys and cannot be represented as a fixed set of model properties."

Check warning on line 52 in specification/web/resource-manager/Microsoft.Web/AppService/EgressConfig.tsp

View workflow job for this annotation

GitHub Actions / TypeSpec Suppressions - Analyze Code

⚠️ New Suppression

Authors should avoid adding new suppressions and prefer fixing the underlying issue; reviewers should approve only when there is a clear, compelling justification and no reasonable alternative.
connections?: Record<VirtualConnection>;
}

/** Enabled state for virtual connections. */
#suppress "@azure-tools/typespec-azure-core/no-closed-literal-union" "The 2026-09-01 service rejects unknown egress enabled states, so this union intentionally models a closed set."

Check warning on line 57 in specification/web/resource-manager/Microsoft.Web/AppService/EgressConfig.tsp

View workflow job for this annotation

GitHub Actions / TypeSpec Suppressions - Analyze Code

⚠️ New Suppression

Authors should avoid adding new suppressions and prefer fixing the underlying issue; reviewers should approve only when there is a clear, compelling justification and no reasonable alternative.
@added(Versions.v2026_09_01)
Comment thread
cmriachi marked this conversation as resolved.
union EgressEnabledState {
/** Virtual connections are enabled — proxy intercepts configured settings. */
Enabled: "Enabled",

/** Virtual connections are disabled — all settings pass through unchanged. */
Disabled: "Disabled",
}

/**
* Declares one backend connection to virtualize. Maps one or more app settings
* (or connection strings) to a proxy configuration with a specific protocol
* and authentication strategy.
*/
@added(Versions.v2026_09_01)
model VirtualConnection {
/**
* Settings that compose this connection. Key is the app setting
* (or connection string) name; value describes its role in the connection.
* Setting keys must be nonempty, must not end in whitespace, and must be
* unique within the connection regardless of casing.
*
* All referenced setting names must already exist in the selected source
* when the egress configuration is written. Missing app settings, missing
* connection strings, or ambiguous connection-string references cause
* create/update validation to fail.
*/
#suppress "@azure-tools/typespec-azure-resource-manager/arm-no-record" "Setting names are arbitrary customer-chosen keys and cannot be represented as a fixed set of model properties."

Check warning on line 85 in specification/web/resource-manager/Microsoft.Web/AppService/EgressConfig.tsp

View workflow job for this annotation

GitHub Actions / TypeSpec Suppressions - Analyze Code

⚠️ New Suppression

Authors should avoid adding new suppressions and prefer fixing the underlying issue; reviewers should approve only when there is a clear, compelling justification and no reasonable alternative.
settings: Record<VirtualConnectionSetting>;

/**
* Where to look up the settings. Default: "AppSettings". Each connection pulls
* from exactly one source — create separate connections if you need settings
* from both sources.
*/
source?: VirtualConnectionSettingSource = VirtualConnectionSettingSource.AppSettings;

/** Protocol for the backend service. */
protocol: VirtualConnectionProtocol;

/**
* Authentication strategy for the backend connection.
* "Credential" — proxy injects the original secret (from setting value).
* "ManagedIdentity" — proxy acquires a managed identity token for upstream auth.
*/
auth: VirtualConnectionAuthType;

/**
* Managed Identity scope for token acquisition. Only relevant when auth=ManagedIdentity.
* Optional — the platform infers a default scope from protocol + host.
* Specify explicitly for custom hosts or non-standard resource URIs.
*/
identityScope?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NEW] 🟠 Warning [Section 12 — Types and Formats] specification/web/resource-manager/Microsoft.Web/AppService/EgressConfig.tsp — line 104; emitted OpenAPI line 57418 / JSON path $.definitions.VirtualConnection.properties.identityScope — identityScope is documented as a resource URI, but it is modeled as plain string, so emitted OpenAPI lacks format: uri and generated SDKs lose URI validation and typing.

Classification reasoning: VirtualConnection.identityScope is new in this PR.

Suggested fix: Change the TypeSpec property to identityScope?: url; and regenerate the OpenAPI.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're keeping identityScope as string intentionally. It identifies the target token resource/audience, not an endpoint URL. The proxy passes this value as an opaque resource identifier for App Service MI and IMDS; the OIDC provider separately handles /.default scope formatting.

Microsoft Entra also supports application-ID-based scope strings—for example, Redis's documented acca5fbb-b7e4-4009-81f1-37e38fd66d78/.default form—which are not absolute URIs (Microsoft documentation). A URI-only constraint would narrow the intended contract. The description should distinguish token resource/scope semantics from endpoint URLs rather than change the property type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scope-inference validation is intentionally deferred until Control Plane and Secretless Proxy share the protocol/target inference behavior.


/**
* Per-connection override for the user-assigned managed identity resource ID.
* Used when multiple connections target different backends that require
* different identities.
*
* Only relevant when auth=ManagedIdentity. If omitted, create/update uses
* the app's system-assigned identity and rejects the request if that identity
* does not exist. If supplied, the referenced identity must already be
* assigned to the app; otherwise create/update validation fails.
*/
identityResourceId?: Azure.Core.armResourceIdentifier<[
{
type: "Microsoft.ManagedIdentity/userAssignedIdentities";
}
]>;

/**
* How to inject a standalone credential into upstream HTTP requests.
* Required when protocol=Http + auth=Credential and the credential is a
* separate setting. Not needed for role="Value" or auth=ManagedIdentity.
*/
credentialInjection?: VirtualConnectionCredentialInjection;
}

/**
* Describes how the proxy injects a standalone credential setting into upstream
* HTTP requests. Only applies when protocol=Http and the credential is a
* separate setting (not embedded in a value/URL).
*/
@added(Versions.v2026_09_01)
model VirtualConnectionCredentialInjection {
/** HTTP header name to inject the credential into (e.g., "Authorization", "X-API-Key"). */
header: string;

/**
* Format string for the header value. Use {value} as placeholder for the
* credential. Default: raw credential value (no formatting).
* Examples: "Bearer {value}", "Basic {value}", "ApiKey {value}".
*/
format?: string;
}

/**
* Describes the role of a single app setting or connection string within a
* virtual connection.
*/
@added(Versions.v2026_09_01)
model VirtualConnectionSetting {
/**
* The role this setting plays in the connection. The platform uses this to
* determine runtime behavior:
* - "Credential" settings are masked (app never sees the real value).
* - "Host"/"Value" settings identify the intercept target.
* - "Username" settings feed protocol auth construction.
*/
role: VirtualConnectionSettingRole;
}

/** Where to resolve settings from. */
@added(Versions.v2026_09_01)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this enumeration will never add new values in a new api-version, please assert that in the justification. If it might add new values in new api-versions in the future, then it should be an open literal union (meaning you should add string as one of the variants.

union VirtualConnectionSettingSource {
Comment thread
cmriachi marked this conversation as resolved.
string,

/** App Settings (config/appsettings). */
AppSettings: "AppSettings",

/** Connection Strings (config/connectionstrings). */
ConnectionStrings: "ConnectionStrings",
}

/** Protocol for the backend service. */
@added(Versions.v2026_09_01)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this enumeration will never add new values in a new api-version, please assert that in the justification. If it might add new values in new api-versions in the future, then it should be an open literal union (meaning you should add string as one of the variants.

union VirtualConnectionProtocol {
string,

/** HTTP/HTTPS (Azure Storage, Cosmos DB REST, Key Vault, custom APIs). */
Http: "Http",

/** TDS (Azure SQL, SQL Server). */
Tds: "Tds",

/** RESP (Azure Cache for Redis). */
Redis: "Redis",

/** PostgreSQL wire protocol (Azure Database for PostgreSQL). */
Postgres: "Postgres",
}

/** Authentication strategy for upstream connections. */
@added(Versions.v2026_09_01)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this enumeration will never add new values in a new api-version, please assert that in the justification. If it might add new values in new api-versions in the future, then it should be an open literal union (meaning you should add string as one of the variants.

union VirtualConnectionAuthType {
string,

/** Proxy injects the original credential from the setting value. */
Credential: "Credential",

/** Proxy acquires a Managed Identity token for upstream auth. */
ManagedIdentity: "ManagedIdentity",
}

/** The role a setting plays in a connection. */
@added(Versions.v2026_09_01)
union VirtualConnectionSettingRole {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this enumeration will never add new values in a new api-version, please assert that in the justification. If it might add new values in new api-versions in the future, then it should be an open literal union (meaning you should add string as one of the variants.

string,

/** Plain hostname or IP — identifies the intercept target. */
Host: "Host",

/** Standalone secret (password, key, SAS token) — masked from app, used or replaced by proxy. */
Credential: "Credential",

/** Standalone username — used for protocol auth construction (e.g., Redis ACL OID). */
Username: "Username",

/** Composite value (connection string, SAS URL, DSN, Redis URI) — proxy parses based on protocol. */
Value: "Value",
}

// ─────────────────────────────────────────────────────────────────────────────
// Operations — production slot: .../sites/{name}/config/egress
// ─────────────────────────────────────────────────────────────────────────────

@added(Versions.v2026_09_01)
@armResourceOperations
interface EgressConfigOps
extends Azure.ResourceManager.Legacy.LegacyOperations<
{
...ApiVersionParameter,
...SubscriptionIdParameter,
...ResourceGroupParameter,
...Azure.ResourceManager.Legacy.Provider,

/** Name of the app. */
@path
@segment("sites")
@key
name: string,
},
{
/** Name of the configuration singleton. */
@path
@segment("config")
@key
egress: "egress",
},
ResourceName = "EgressConfig"
> {}

@added(Versions.v2026_09_01)
@tag("WebApps")
@armResourceOperations(#{ omitTags: true })
interface EgressConfigs {
/**
* Creates or replaces the egress configuration for an app. The supplied
* connections replace the stored ones in full, so connections omitted from
* the request are deleted; omitting connections entirely, or supplying an
* empty object, deletes the configuration for the app. Returns 201 when no
* configuration was stored for the app beforehand and 200 otherwise, with the
* stored configuration in the response body.
*
* The request is normalized and then structurally and semantically validated
* before persistence. Invalid configurations are rejected, including requests
* whose referenced setting names appear to contain credentials, which return
* 400 Bad Request.
*/
@summary("Creates or replaces the egress configuration for an app.")
createOrUpdateEgressConfig is EgressConfigOps.CreateOrUpdateSync<
EgressConfig,
Response =
| ArmResourceUpdatedResponse<EgressConfig>
| (CreatedResponse & {
@bodyRoot body: EgressConfig;
}),
OverrideErrorType = DefaultErrorResponse
>;

/**
* Gets the egress configuration stored for an app, including its connections.
* Returns an empty configuration when none is stored.
*/
@summary("Gets the stored egress configuration for an app.")
getEgressConfig is EgressConfigOps.Read<
EgressConfig,
OverrideErrorType = DefaultErrorResponse
>;
}

// ─────────────────────────────────────────────────────────────────────────────
// Operations — deployment slot: .../sites/{name}/slots/{slot}/config/egress
// ─────────────────────────────────────────────────────────────────────────────

@added(Versions.v2026_09_01)
@armResourceOperations
interface EgressConfigSlotOps
extends Azure.ResourceManager.Legacy.LegacyOperations<
{
...ApiVersionParameter,
...SubscriptionIdParameter,
...ResourceGroupParameter,
...Azure.ResourceManager.Legacy.Provider,

/** Name of the app. */
@path
@segment("sites")
@key
name: string,

/** Name of the deployment slot. */
@path
@segment("slots")
@key
slot: string,
},
{
/** Name of the configuration singleton. */
@path
@segment("config")
@key
egress: "egress",
},
ResourceName = "EgressConfigSlot"
> {}

@added(Versions.v2026_09_01)
@tag("WebApps")
@armResourceOperations(#{ omitTags: true })
interface EgressConfigSlotConfigs {
/**
* Creates or replaces the egress configuration for a deployment slot. The
* supplied connections replace the stored ones in full, so connections
* omitted from the request are deleted; omitting connections entirely, or
* supplying an empty object, deletes the configuration for the slot. Returns
* 201 when no configuration was stored for the slot beforehand and 200
* otherwise, with the stored configuration in the response body.
*
* The request is normalized and then structurally and semantically validated
* before persistence. Invalid configurations are rejected, including requests
* whose referenced setting names appear to contain credentials, which return
* 400 Bad Request.
*/
@summary("Creates or replaces the egress configuration for a deployment slot.")
createOrUpdateEgressConfigSlot is EgressConfigSlotOps.CreateOrUpdateSync<
EgressConfig,
Response =
| ArmResourceUpdatedResponse<EgressConfig>
| (CreatedResponse & {
@bodyRoot body: EgressConfig;
}),
OverrideErrorType = DefaultErrorResponse
>;

/**
* Gets the egress configuration stored for a deployment slot, including its
* connections. Returns an empty configuration when none is stored.
*/
@summary("Gets the stored egress configuration for a deployment slot.")
getEgressConfigSlot is EgressConfigSlotOps.Read<
EgressConfig,
OverrideErrorType = DefaultErrorResponse
>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ using Azure.Core;

namespace Customizations;

@@clientLocation(EgressConfigs.createOrUpdateEgressConfig, WebApps, "!csharp");
@@clientLocation(EgressConfigs.getEgressConfig, WebApps, "!csharp");
@@clientLocation(
EgressConfigSlotConfigs.createOrUpdateEgressConfigSlot,
WebApps,
"!csharp"
);
@@clientLocation(
EgressConfigSlotConfigs.getEgressConfigSlot,
WebApps,
"!csharp"
);

// python
@@clientName(Microsoft.Web, "WebSiteManagementClient", "python, javascript");
@@clientName(WebApps.swapSlotSlot, "swapSlot", "python,go,javascript");
Expand Down
Loading
Loading