Pulumi and AKS : Public IP not linked to AKS load-balancer - azure

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;

Related

AWS CDK - Allow both VPC and a single static IP address access to RDS

I am trying to allow my VPC to access my RDS database as well as my own personal IP address in AWS CDK. So far I have this:
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
interface SecurityGroupProps {
environment: string;
vpc: ec2.Vpc;
}
export default class SecurityGroup extends Construct {
defaultSecurityGroup: ec2.ISecurityGroup;
constructor(scope: Construct, id: string, props: SecurityGroupProps) {
super(scope, id);
const defaultSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId(
this,
'SG',
props.vpc.vpcDefaultSecurityGroup
);
defaultSecurityGroup.addEgressRule(
ec2.Peer.securityGroupId(defaultSecurityGroup.securityGroupId),
ec2.Port.tcp(5432),
'Opening RDS to VPC'
);
defaultSecurityGroup.addIngressRule(
ec2.Peer.ipv4('195.216.150.112'),
ec2.Port.tcp(5432),
'Opening RDS to my home IP'
);
this.defaultSecurityGroup = defaultSecurityGroup;
}
}
I am not sure if I have understood ingress and egress correctly or if this is indeed the way to go about achieving this? Here are my RDS instance props, not sure if these have been setup correctly?:
instanceProps: {
vpc: props.vpc,
vpcSubnets: props.vpc.selectSubnets({
subnetType: ec2.SubnetType.PUBLIC
}),
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MEDIUM
),
securityGroups: [props.defaultSecurityGroup],
publiclyAccessible: true
},

Azure Bicep - Additional IP Restrictions

I'd like to use a shared bicep module to add several ip security restriction records to existing app services.
This is the module I've come up with:
param appSvcName string
resource appSvc 'Microsoft.Web/sites#2021-02-01' existing = {
name: appSvcName
}
var proxyIpAddresses = ['xxx.xxx.xxx.250/32','xxx.xxx.xxx.245/32']
resource sitesConfig 'Microsoft.Web/sites/config#2021-02-01' = {
name: 'web'
parent: appSvc
properties: {
ipSecurityRestrictions: [for (ip,i) in proxyIpAddresses: {
ipAddress: ip
action: 'Allow'
tag: 'Default'
priority: 900 + i
name: 'ProxyIp_${i}'
description: 'Allow request from proxy ${i}'
}]
}
}
I call this from the main bicep as follows:
module ipRestrictions 'common.appSvc.ipSecurityRestrictions.bicep' = {
scope: resourceGroup(utrnRg)
name: 'ipRestrictionsDeploy'
params: {
appSvcName: functionAppName
}
dependsOn: [
functionAppDeploy
]
}
Prior to this, there's a call to a specific Azure Function module the provisions the function app and ip restrictions that are specific to that function app (typically subnets that it should allow traffic from)
The behaviour I see is that the function app gets created with its specific ip restrictions, but these get deleted and replaced with the two rules from the shared module.
Is there a way I can make the module add to existing ip restrictions rather than replacing them?
In you module, you would need to have a new parameter for the existings ip restrictions then you can add the new restirctions to it:
// common.appSvc.ipSecurityRestrictions.bicep
param appSvcName string
param existingIpSecurityRestrictions array = []
resource appSvc 'Microsoft.Web/sites#2021-02-01' existing = {
name: appSvcName
}
var proxyIpAddresses = ['xxx.xxx.xxx.250/32','xxx.xxx.xxx.245/32']
var additionalIpSecurityRestrictions = [for (ip,i) in proxyIpAddresses: {
ipAddress: ip
action: 'Allow'
tag: 'Default'
priority: 900 + i
name: 'ProxyIp_${i}'
description: 'Allow request from proxy ${i}'
}]
resource sitesConfig 'Microsoft.Web/sites/config#2021-02-01' = {
name: 'web'
parent: appSvc
properties: {
ipSecurityRestrictions: concat(existingIpSecurityRestrictions, additionalIpSecurityRestrictions)
}
}
Then invoke the module with the existing restrictions:
// main.bicep
param functionAppName string
module ipRestrictions 'common.appSvc.ipSecurityRestrictions.bicep' = {
scope: resourceGroup(utrnRg)
name: 'ipRestrictionsDeploy'
params: {
appSvcName: functionAppName
existingIpSecurityRestrictions: reference(resourceId('Microsoft.Web/sites/config', functionAppName, 'web'), '2021-02-01').ipSecurityRestrictions
}
dependsOn: [
functionAppDeploy
]
}

Azure Bicep - How to get the ip address of a Site for the main domain?

I am using bicep to configure the site and DNS. Currently, I can configure it when using the subdomain (e.g www.foilen-lab.me) by using a CNANE, but for the main (e.g foilen-lab.me), I cannot use a CNAME and must use the IP. How can I get the IP?
Currently for the "www":
resource siteWww 'Microsoft.Web/sites#2021-03-01' = {
name: 'www-foilen-lab-me'
location: location
kind: 'app,linux,container'
properties: {
serverFarmId: serverFarmsId
reserved: true
httpsOnly: true
siteConfig: {
alwaysOn: true
numberOfWorkers: 1
linuxFxVersion: 'DOCKER|foilen/az-docker-apache_php:7.4.9-3'
}
}
}
resource dnsWww 'Microsoft.Network/dnsZones/CNAME#2018-05-01' = {
parent: dnsZone
name: 'www'
properties: {
TTL: 3600
CNAMERecord: {
cname: '${siteWww.name}.azurewebsites.net'
}
}
}
And I would like to create something like:
resource dns 'Microsoft.Network/dnsZones/A#2018-05-01' = {
parent: dnsZone
name: '#'
properties: {
TTL: 3600
ARecords: [
{
ipv4Address: '${siteWww.xxxxxxxx}'
}
]
}
}
thanks
You should be able to use siteWww.properties.inboundIpAddress to get the current ipAddress.
As a general rule of thumb you can retrieve any property on a resource in bicep by using it's symbolic name and the JSON path of the GET from the REST api.
So for example, if you go to the portal for any resource and select the JSON View from the overview page... you can see what's possible to return via that syntax. e.g. siteWww.properties.customDomainVerificationId is also handy.

Deploying Hybrid Connection to webapp using bicep

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!",
}
}

Is there a way to connect an Azure firewall to a Front Door Premium Policy with Bicep?

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

Resources