I'm trying to create an Hybrid Connection on a webapp using Bicep.
The documentation, unfortunately, has no descriptions in the properties of RelayServiceConnectionEntityProperties:
https://learn.microsoft.com/en-us/azure/templates/microsoft.web/sites/hybridconnection?tabs=bicep
This is what I tried:
resource webappHcm 'Microsoft.Web/sites/hybridconnection#2021-02-01' = {
name: 'hcm'
parent: webapp
properties: {
entityConnectionString: 'Endpoint=sb://xxxxxxx.servicebus.windows.net/;SharedAccessKeyName=defaultListener;SharedAccessKey=XXXXXXXXXXX;EntityPath=xxxxxxxxxxxxxxx'
entityName: 'xxxxxxxxxxxxxxxxx'
hostname: 'xxxxxxxxxxxxxxxxx.hostname.internal'
port: 12345
// resourceConnectionString: 'string'
// resourceType: 'string'
}
}
However, when I try to deploy, I get this error:
Required parameter EntityName, EntityConnectionString, ResoureType, ResourceConnectionString, Hostname, or BiztalkUri is missing.
I have no idea what to put in resourceConnectionString, resourceType nor biztalkUri.
Any ideas where I can find those, or what am I doing wrong?
Unfortunately, doing it on the Azure Portal manually, and then "Export template", the export doesn't have anything related to the hybrid connection (whether it is in the Webapp, or in the Hybrid Connection itself)
for creating Hybrid-Connection for webs you need to have bicep file like:
param appServiceName string
var cfg = json(loadTextContent('../../bicepConsts.json'))
var hc = cfg.HybridConnection
resource appService 'Microsoft.Web/sites#2021-02-01' existing = {
name: appServiceName
}
var relayId = resourceId(hc.ResourceGroup, 'Microsoft.Relay/namespaces', hc.Namespace)
var connectionId = '${relayId}/hybridConnections/${hc.Name}'
var senderKeyName = 'defaultSender'
var key = listKeys('${connectionId}/authorizationRules/${senderKeyName}', '2017-04-01').primaryKey
resource hybridConnection 'Microsoft.Web/sites/hybridConnectionNamespaces/relays#2021-02-01' = {
name: '${appService.name}/${hc.Namespace}/${hc.Name}'
location: hc.NamespaceLocation
dependsOn: [
appService
]
properties: {
serviceBusNamespace: hc.Namespace
relayName: hc.Name
relayArmUri: connectionId
hostname: hc.Host
port: hc.Port
sendKeyName: senderKeyName
sendKeyValue: key
serviceBusSuffix: '.servicebus.windows.net'
}
}
Where this bicepConsts file contains think like:
{
"..." : "...",
"HybridConnection": {
"ResourceGroup": "resource group of your HybridConnection from Azure",
"Name": "Name of hybrid connection",
"Namespace": "Namespace of hybrid connection",
"NamespaceLocation": "Location (e.g. 'West US 2') of your hybrid connection namespace",
"Host": "Host of your hybrid connection",
"Port": "Port of your hybrid connection AS INTEGER!",
}
}
Related
I am trialling the use of Bicep and container apps in my organisation and we have separated out concerns within the SAME tenant but in different subscriptions like so:
Development
Production
Management
I want to be able to deploy each of these subscriptions using Bicep scripts (individual ones per subscription) and ideally only use managed identity for security.
Within the management subscription we have an ACR which has the admin account intentionally disabled as I don't want to pull via username/password. Question one, should this be possible? As it seems that we should be able to configure an AcrPull role against the container app(s) without too much trouble.
The idea being that the moment the container app is deployed it pulls from the Acr and is actively useable. I don't want an intermediary such as Azure DevOps handling the orchestration for example.
In bicep I've successfully configured the workspace, container environment but upon deploying my actual app I'm a bit stuck - it fails for some incomprehensible error message which I'm still digging into. I've found plenty of examples using the admin/password approach but documentation for alternatives appears lacking which makes me worry if I'm after something that isn't feasible. Perhaps user identity is my solution?
My bicep script (whilst testing against admin/password) looks like this:
name: containerAppName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
managedEnvironmentId: containerAppEnvId
configuration: {
secrets: [
{
name: 'container-registry-password'
value: containerRegistry.listCredentials().passwords[0].value
}
]
ingress: {
external: true
targetPort: targetPort
allowInsecure: false
traffic: [
{
latestRevision: true
weight: 100
}
]
}
registries: [
{
server: '${registryName}.azurecr.io'
username: containerRegistry.listCredentials().username
passwordSecretRef: 'container-registry-password'
}
]
}
template: {
revisionSuffix: 'firstrevision'
containers: [
{
name: containerAppName
image: containerImage
resources: {
cpu: json(cpuCore)
memory: '${memorySize}Gi'
}
}
]
scale: {
minReplicas: minReplicas
maxReplicas: maxReplicas
}
}
}
}
However this is following an admin/password approach. For using managed identity, firstly do I need to put a registry entry in there?
``` registries: [
{
server: '${registryName}.azurecr.io'
username: containerRegistry.listCredentials().username
passwordSecretRef: 'container-registry-password'
}
]
If so, the listCredentials().username obviously won't work with admin/password disabled. Secondly, what would I then need in the containers section
containers: [
{
name: containerAppName
image: containerImage ??
resources: {
cpu: json(cpuCore)
memory: '${memorySize}Gi'
}
}
]
As there appears to be no mention of the need for pointing at a repository, or indeed specifying anything other than a password/admin account. Is it that my requirement is impossible as the container app needs to be provisioned before managed identity can be applied to it? Is this a chicken vs egg problem?
You could use a user-assigned identity:
Create a user assigned identity
Grant permission to the user-assigned identity
Assign the identity to the container app
# container-registry-role-assignment.bicep
param registryName string
param roleId string
param principalId string
// Get a reference to the existing registry
resource registry 'Microsoft.ContainerRegistry/registries#2021-06-01-preview' existing = {
name: registryName
}
// Create role assignment
resource roleAssignment 'Microsoft.Authorization/roleAssignments#2020-04-01-preview' = {
name: guid(registry.id, roleId, principalId)
scope: registry
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleId)
principalId: principalId
principalType: 'ServicePrincipal'
}
}
Then from your main:
param name string
param identityName string
param environmentName string
param containerImage string
param location string = resourceGroup().location
param containerRegistrySubscriptionId string = subscription().subscriptionId
param containerRegistryResourceGroupName string = resourceGroup().name
param containerRegistryName string
// Create identtiy
resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities#2022-01-31-preview' = {
name: identityName
location: location
}
// Assign AcrPull permission
module roleAssignment 'container-registry-role-assignment.bicep' = {
name: 'container-registry-role-assignment'
scope: resourceGroup(containerRegistrySubscriptionId, containerRegistryResourceGroupName)
params: {
roleId: '7f951dda-4ed3-4680-a7ca-43fe172d538d' // AcrPull
principalId: identity.properties.principalId
registryName: containerRegistryName
}
}
// Get a reference to the container app environment
resource managedEnvironment 'Microsoft.App/managedEnvironments#2022-03-01' existing = {
name: environmentName
}
// create the container app
resource containerapp 'Microsoft.App/containerApps#2022-03-01' = {
dependsOn:[
roleAssignment
]
name: name
...
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${identity.id}': {}
}
}
properties: {
managedEnvironmentId: managedEnvironment.id
configuration: {
...
registries: [
{
server: '${containerRegistryName}.azurecr.io'
identity: identity.id
}
]
}
template: {
...
containers: [
{
name: name
image: '${containerRegistryName}.azurecr.io/${containerImage}'
...
}
]
}
}
}
trying, using pulumi, to setup and AKS cluster with a public ip as load-balancer and a domain name.
I tried several options using the following project : https://github.com/hostettler/pulumi-az-test
I end up with a working AKS cluster (great!) , that has a load-balancer (amazing!), on a public ip (fantastic!), but with an fqdn that is not linked to the public ip of the load-balancer (huh!).
I tried to
extract the public ip to force the domain but I did not manage to do it. I get the resource id of the public id created but I cannot cast it
as a public ip object to set the domain. Meaning how to load a public ip object from a public ip resource-id.
"/subscriptions/XXXXXX-XXX-XX-XXXx-XXXXXXXXXXXX/resourceGroups/MC_XXXXX_XXXXX_aksCluster5632b82_eastus/providers/Microsoft.Network/publicIPAddresses/XXXXX-XXXX-XXX-XXX-XXXXXXXXX"
tries to add a public ip to the cluster but that ip, while having a good fqdn is not used by the load-balancer
Any idea on how to address? Solving either would be ok with me.
Thanks a lot in advance
found the problem (or rather a work around). The problem stems from the helm chart I was using. The bitnami nginx chart seemed to ignored the controller.service.loadBalanceIP. Therefore, I replaced it by the ingress-nginx.
Thus, replacing
chart: "nginx",
version: "13.2.10",
fetchOpts: {
repo: "https://charts.bitnami.com/bitnami",
},
by
chart: "ingress-nginx",
version: "4.3.0",
fetchOpts: {
repo: "https://kubernetes.github.io/ingress-nginx",
},
solved the problem. Instead of recreating an external ip address for each deployment, it used the one provided in controller.service.loadBalanceIP that is static and with the proper A record.
import * as azure from "#pulumi/azure";
import * as k8s from "#pulumi/kubernetes";
import * as pulumi from "#pulumi/pulumi";
import * as config from "./config";
import * as azuread from "#pulumi/azuread";
const current = azuread.getClientConfig({});
// Create a Virtual Network for the cluster
const aksVnet = new azure.network.VirtualNetwork("aks-net", {
resourceGroupName: config.resourceGroup.name,
addressSpaces: ["10.2.0.0/16"],
});
// Create a Subnet for the cluster
const aksSubnetId = new azure.network.Subnet("aks-net", {
resourceGroupName: config.resourceGroup.name,
virtualNetworkName: aksVnet.name,
addressPrefixes: ["10.2.1.0/24"],
serviceEndpoints : ["Microsoft.Sql"],
},
{dependsOn: [aksVnet]}
);
// Now allocate an AKS cluster.
export const k8sCluster = new azure.containerservice.KubernetesCluster("aksCluster", {
resourceGroupName: config.resourceGroup.name,
location: config.location,
defaultNodePool: {
name: "aksagentpool",
nodeCount: config.nodeCount,
vmSize: config.nodeSize,
vnetSubnetId: aksSubnetId.id,
},
dnsPrefix: `${pulumi.getStack()}-reg-engine`,
linuxProfile: {
adminUsername: "aksuser",
sshKey: {
keyData: config.sshPublicKey,
},
},
networkProfile: {
networkPlugin : "azure",
loadBalancerProfile: {
managedOutboundIpCount: 1,
},
loadBalancerSku: "standard",
outboundType: "loadBalancer",
},
identity: {
type: "SystemAssigned",
},
},
{dependsOn: [aksSubnetId]}
);
const publicIp = new azure.network.PublicIp("app-engine-ip", {
resourceGroupName: k8sCluster.nodeResourceGroup,
allocationMethod: "Static",
domainNameLabel : "app-engine",
sku : "Standard",
tags: {
service: "kubernetes-api-loadbalancer",
},
},
{dependsOn: [k8sCluster]}
);
// Expose a K8s provider instance using our custom cluster instance.
export const k8sProvider = new k8s.Provider("aksK8s", {
kubeconfig: k8sCluster.kubeConfigRaw,
});
const clusterSvcsNamespace = new k8s.core.v1.Namespace(
config.namespace,
undefined,
{ provider: k8sProvider });
export const clusterSvcsNamespaceName = clusterSvcsNamespace.metadata.name;
const regEngineIngress = new k8s.helm.v3.Chart(
"sample-engine-ingress",
{
repo: "kubernetes.github.io",
chart: "ingress-nginx",
version: "4.3.0",
fetchOpts: {
repo: "https://kubernetes.github.io/ingress-nginx",
},
namespace: clusterSvcsNamespace.metadata.name,
values: {
controller: {
replicaCount: 2,
nodeSelector: {
"kubernetes\.io/os": "linux",
},
service: {
loadBalancerIP: publicIp.ipAddress,
}
}
},
},
{ provider: k8sProvider },
);
export let cluster = k8sCluster.name;
export let kubeConfig = k8sCluster.kubeConfigRaw;
export let k8sFqdn = k8sCluster.fqdn;
export let externalIp = publicIp.ipAddress;
I'm trying to add a new Traffic Manager Profile via a Network/trafficManagerProfiles.bicep module which I invoke in my main.bicep file.
This works well.
The main.bicep file is creating multiple Function Apps using their own little module, which is invoked similar like this
module functionAppModule 'Web/functions.bicep' = {
dependsOn: [
appServicePlanModule
webApiStorageAccount
]
name: 'functionAppModule'
params: {
environmentName: environmentName
systemName: systemName
azureRegion: azureRegion
appServicePlanId: appServicePlanModule.outputs.id
}
}
Now I'm trying to add the necessary endpoints of my web applications (Azure Functions) to the Traffic Manager Profile, which is also possible by using the endpoints property.
However, this would mean I need to add a parameter to this file accepting an array of objects containing all information about my App Services, or I would need to resolve them over here (by retrieving the instances with the existing keyword). This doesn't sound like the way to implement this, because all those resources are already available/referenced in the main.bicep file.
The Traffic Manager Profile module now looks like this:
param systemName string
#allowed([
'dev'
'test'
'acc'
'prod'
])
param environmentName string
param relativeLiveEndpoint string = '/api/Live'
var trafficManagerProfileName = '${systemName}${environmentName}'
resource trafficManagerProfile 'Microsoft.Network/trafficmanagerprofiles#2018-08-01' = {
name: trafficManagerProfileName
location: 'global'
properties: {
allowedEndpointRecordTypes: [
'DomainName'
]
dnsConfig: {
relativeName: trafficManagerProfileName
ttl: 60
}
profileStatus: 'Enabled'
trafficRoutingMethod: 'Performance'
monitorConfig: {
profileMonitorStatus: 'Online'
protocol: 'HTTPS'
port: 443
path: relativeLiveEndpoint
timeoutInSeconds: 10
intervalInSeconds: 30
toleratedNumberOfFailures: 3
}
endpoints: [
{
id: 'the resource id'
name: 'the resource name'
type: 'the resource type'
properties: {
endpointStatus: 'Enabled'
endpointMonitorStatus: 'Online'
targetResourceId: 'the resource id'
target: 'mysite.azurewebsites.net'
endpointLocation: 'West Europe'
weight: 1
priority: 1
}
}
// All other app services
]
}
}
According to some answers here on Stack Overflow, the endpoints can also be created via a child resource in ARM templates (something like Microsoft.Network/trafficmanagerprofiles/endpoints), however, this doesn't appear to be available in Bicep (or just isn't available in the autocomplete).
What I'd like to do is something like this in my main.bicep file, because that way I can reference all App Services I want to add.
var trafficManagerProfileEndpoints 'Microsoft.Network/trafficmanagerprofiles/endpoints#2050-01-01` = {
name: '${trafficManagerProfile.Name}/endpoints'
endpoints: [
// All of my endpoints
]
}
Somewhat similar compared to what's available via Microsoft.Web/sites/config#2020-12-01 for configuration settings in an App Service.
Any ideas on this?
I just ran through your question at lightning speed, forgive me if I don't fully understand it, but it looks like you need to declare your web app as a resource again, but as existing. This allows you to access all the props you need from your app service.
This blog explains how to use existing resources in Bicep: https://hexmaster.nl/posts/bicep-existing/
This is possible through Bicep ARM but the Microsoft.Network/trafficManagerProfiles api doesn't have types available for endpoints so it will generate a warning even if the deployment works perfectly.
If you define a traffic manager profile without the endpoints property:
resource trafficManager 'Microsoft.Network/trafficmanagerprofiles#2018-08-01' = {
name: '<name>'
location: 'global'
properties: {
profileStatus: 'Enabled'
trafficRoutingMethod: '<trafficRoutingMethod>'
dnsConfig: {
...
}
monitorConfig: {
...
}
}
}
You can then add endpoints like that:
// Azure endpoints: cloud service, app service, app service slots, public ip
resource trafficManagerAzureEndpoint 'Microsoft.Network/trafficManagerProfiles/azureEndpoints#2018-08-01' = {
name: '${trafficManagerName}/${name}'
properties: {
...
}
}
// External endpoint
resource trafficManagerExternalEndpoint 'Microsoft.Network/trafficManagerProfiles/externalEndpoints#2018-08-01' = {
name: '${trafficManagerName}/${name}'
properties: {
...
}
}
// Nested endpoints
resource trafficManagerNestedEndpoint 'Microsoft.Network/trafficManagerProfiles/nestedEndpoints#2018-08-01' = {
name: '${trafficManagerName}/${name}'
properties: {
...
}
}
The Pulumi doc Create subnet with a delegation is wrong (it did not set SubnetArgs.Delegations property).
I tried to delegate subnet with following code:
//
// Managed Insatnce subnet must be delegated
//
var spokeManagedInstanceSubnet = new Subnet($"{SpokeVirtualNetwork}.{ManagedInstanceSubnet}", new AzureNative.Network.SubnetArgs {
SubnetName = ManagedInstanceSubnet,
AddressPrefix = spokeSubnetCidrs[ManagedInstanceSubnet],
VirtualNetworkName = spokeVnet.Name,
ResourceGroupName = mainResourceGroup.Name,
Delegations = new InputList<DelegationArgs> { new DelegationArgs {
ServiceName = "Microsoft.Sql/managedInstances",
Type = "Microsoft.Network/virtualNetworks/subnets/delegations"
}
}
}, new CustomResourceOptions { DependsOn = { spokeVnet } });
But getting the following error:
error: Code="InvalidRequestFormat" Message="Cannot parse the request." Details=[]
How to delegate subnet for managed Instance ?
I think what you're missing is the Name input in the DelegationArgs. So it should look something like
var spokeManagedInstanceSubnet = new Subnet($"{SpokeVirtualNetwork}.{ManagedInstanceSubnet}", new AzureNative.Network.SubnetArgs {
SubnetName = ManagedInstanceSubnet,
AddressPrefix = spokeSubnetCidrs[ManagedInstanceSubnet],
VirtualNetworkName = spokeVnet.Name,
ResourceGroupName = mainResourceGroup.Name,
Delegations = new InputList<DelegationArgs> { new DelegationArgs {
ServiceName = "Microsoft.Sql/managedInstances",
Type = "Microsoft.Network/virtualNetworks/subnets/delegations",
Name = "" // name of delegation. Doesn't have to be the name of the managed instance
}
}
}, new CustomResourceOptions { DependsOn = { spokeVnet } });
I also think you don't need Type in there either.
When I say "think", I should say that I've not done this with C# and managed instances, but I've done similar with Typescript and container groups.
That looked something like this:
const subnet = new network.Subnet(`subnet`, {
resourceGroupName: resourceGroup.name,
virtualNetworkName: vnet.name,
addressPrefix: "10.0.0.0/24",
serviceEndpoints: [{
service: "Microsoft.Sql"
}],
delegations: [{
serviceName: "Microsoft.Containerinstance/containerGroups",
name: `snet-delegation-containergroups`
}]
}, { parent: vnet });
I am trying to implement Azure Front Door Premium with a Web Application Firewall connection. I am able to create the Front Door both manually and through Bicep. However, when I try to connect to a WAF through Bicep, I'm not sure if it completely works.
The Bicep resource for my WAF looks like:
resource profiles_gbt_nprod_sandbox_FrontDoorTest_name_AzureFDTest_ac196269 'Microsoft.Cdn/profiles/securitypolicies#2020-09-01' = {
parent: profiles_gbt_nprod_sandbox_FrontDoorTest_name_resource
name: 'AzureFDTest-ac196269'
properties: {
parameters: {
wafPolicy: {
id: frontdoorwebapplicationfirewallpolicies_AzureFDTest_externalid
}
associations: [
{
domains: [
{
id: profiles_gbt_nprod_sandbox_FrontDoorTest_name_TestFDEndpoint.id
}
]
patternsToMatch: [
'/*'
]
}
]
type: 'WebApplicationFirewall'
}
}
}
To get: AzureFDTest-ac196269 I created the Front Door through Bicep, then manually connected the AzureFDTest policy and it generated this name.
When this is run, it looks like it connects to my Front Door in the Endpoint Manager:
But when I click on the AzureFDTest WAF policy it looks like:
And AzureFDTest is not listed. If I was to manually connect the WAF, this drop down menu would say AzureFDTest. Is this still working as expected or is there an issue with the way I have the resource written?
You can connect an Azure Front Door Premium to a WAF in Bicep via a security policy as follows:
var frontdoorName = 'frontDoor'
var frontDoorSkuName = 'Premium_AzureFrontDoor'
var endpointName = 'endpoint'
var wafPolicyName = 'wafPolicy'
var securityPolicyName = 'securityPolicy'
param tags object
// Front Door CDN profile
resource profile 'Microsoft.Cdn/profiles#2020-09-01' = {
name: frontdoorName
location: 'global'
sku: {
name: frontDoorSkuName
}
tags: tags
}
// Azure Front Door endpoint
resource endpoint 'Microsoft.Cdn/profiles/afdEndpoints#2020-09-01' = {
parent: profile
name: endpointName
location: 'Global'
tags: tags
properties: {
originResponseTimeoutSeconds: 60
enabledState: 'Enabled'
}
}
// WAF policy using Azure managed rule sets
resource wafPolicy 'Microsoft.Network/FrontDoorWebApplicationFirewallPolicies#2020-11-01' = {
name: wafPolicyName
location: 'global'
tags: tags
sku: {
name: frontDoorSkuName
}
properties: {
policySettings: {
enabledState: 'Enabled'
mode: 'Prevention'
}
managedRules: {
managedRuleSets: [
{
ruleSetType: 'Microsoft_DefaultRuleSet'
ruleSetVersion: '1.1'
}
{
ruleSetType: 'Microsoft_BotManagerRuleSet'
ruleSetVersion: '1.0'
}
]
}
}
}
// Security policy for Front Door which defines the WAF policy linking
resource securityPolicy 'Microsoft.Cdn/profiles/securityPolicies#2020-09-01' = {
parent: profile
name: securityPolicyName
properties: {
parameters: {
type: 'WebApplicationFirewall'
wafPolicy: {
id: wafPolicy.id
}
associations: [
{
domains: [
{
id: endpoint.id
}
]
patternsToMatch: [
'/*'
]
}
]
}
}
}
There is also an azure-quickstart-template available for this scenario:
Front Door Premium with Web Application Firewall and Microsoft-managed rule sets