microsoft / azure-container-apps

Roadmap and issues for Azure Container Apps

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Managed Certificates

SophCarp opened this issue Β· comments

ETA: Public Preview by end of March 2023

Related to #509

This addresses the only shortfall ACA has vs. Cloud Run IME, so really looking forward to it!

Q: do you anticipate ACA managed certificates to have any limitations re: subdomain depth? Obviously "no" is ideal πŸ˜‰

@SophCarp - is there any update on when this feature will enter public preview?

We'll have portal support and announce public preview very soon. You can use the CLI if you want to try it now. Requires a publicly available container app.


Container Apps supports apex domains and subdomains. Each domain type requires a different DNS record type and validation method.

Domain type Record type Validation method Notes
Apex domain A record HTTP An apex domain is a domain at the root level of your domain. For example, if your DNS zone is contoso.com, then contoso.com is the apex domain.
Subdomain CNAME CNAME A subdomain is a domain that is part of another domain. For example, if your DNS zone is contoso.com, then www.contoso.com is an example of a subdomain that can be configured in the zone.
  1. Log in to Azure with the Azure CLI.

    az login
    
  2. Next, install the Azure Container Apps extension for the CLI.

    az extension add --name containerapp --upgrade
    
  3. Verify that your container app has HTTP ingress enabled.

    az containerapp ingress show -n <CONTAINER_APP_NAME> -g <RESOURCE_GROUP_NAME>
    

    If ingress isn't enabled, enable it with these steps:

    az containerapp ingress enable -n <CONTAINER_APP_NAME> -g <RESOURCE_GROUP_NAME> \
            --type external --target-port <TARGET_PORT> --transport auto
    

    Replace <CONTAINER_APP_NAME> with the name of your container app, <RESOURCE_GROUP_NAME> with the name of the resource group that contains your container app, and <TARGET_PORT> with the port that your container app is listening on.

  4. If you're configuring an apex domain, get the IP address of your Container Apps environment.

    az containerapp env show -n <ENVIRONMENT_NAME> -g <RESOURCE_GROUP_NAME> -o tsv --query "properties.staticIp"
    

    Replace <ENVIRONMENT_NAME> with the name of your environment, and <RESOURCE_GROUP_NAME> with the name of the resource group that contains your environment.

  5. If you're configuring a subdomain, get the automatically generated domain of your container app.

    az containerapp show -n <CONTAINER_APP_NAME> -g <RESOURCE_GROUP_NAME> -o tsv --query "properties.configuration.ingress.fqdn"
    

    Replace <CONTAINER_APP_NAME> with the name of your container app, and <RESOURCE_GROUP_NAME> with the name of the resource group that contains your container app.

  6. Get the domain verification code.

    az containerapp show -n <CONTAINER_APP_NAME> -g <RESOURCE_GROUP_NAME> -o tsv --query "properties.customDomainVerificationId"
    

    Replace <CONTAINER_APP_NAME> with the name of your container app, and <RESOURCE_GROUP_NAME> with the name of the resource group that contains your container app.

  7. Using the DNS provider that is hosting your domain, create DNS records based on the record type you selected using the values shown in the Domain validation section. The records point the domain to your container app and verify that you own it.

    • If you're configuring an apex domain, create the following DNS records:

      Record type Host Value
      A @ The IP address of your Container Apps environment
      TXT asuid The domain verification code
    • If you're configuring a subdomain, create the following DNS records:

      Record type Host Value
      CNAME The subdomain (for example, www) The automatically generated domain of your container app
      TXT asuid. followed by the subdomain (for example, asuid.www) The domain verification code
  8. Add the domain to your container app.

    az containerapp hostname add --hostname <DOMAIN_NAME> -g <RESOURCE_GROUP_NAME> -n <CONTAINER_APP_NAME>
    

    Replace <DOMAIN_NAME> with the domain name you want to add, <RESOURCE_GROUP_NAME> with the name of the resource group that contains your container app, and <CONTAINER_APP_NAME> with the name of your container app.

  9. Configure the managed certificate and bind the domain to your container app.

    az containerapp hostname bind --hostname <DOMAIN_NAME> -g <RESOURCE_GROUP_NAME> -n <CONTAINER_APP_NAME> --environment <ENVIRONMENT_NAME> --validation-method <VALIDATION_METHOD>
    

    Replace <DOMAIN_NAME> with the domain name you want to add, <RESOURCE_GROUP_NAME> with the name of the resource group that contains your container app, <CONTAINER_APP_NAME> with the name of your container app, and <ENVIRONMENT_NAME> with the name of your environment.

    • If you're configuring an A record, replace <VALIDATION_METHOD> with HTTP.
    • If you're configuring a CNAME, replace <VALIDATION_METHOD> with CNAME.

    It may take several minutes to issue the certificate and add the domain to your container app.

  10. Once the operation is complete, navigate to your domain to verify that it's accessible.


9. The command prompts you for a validation method.

Hit a wall here. It asked for the validation mechanism (TXT, CNAME, HTTP), and none worked. Got an error message in each case of Invalid validation method for domain 'XXXXXX'. Supported validation method(s) for the domain are: CNAME,HTTP,TXT..

Sorry it looks like an az containerapp hostname add step was missing. I've added it.

@anthonychu Thanks for that. I was able to get to the finish line, but I was not able to get the interactive validation method prompt to work; adding --validation-method CNAME to the bind step did work, though.

Any chance this is available via ARM/bicep yet?

Thanks @anthonychu .

However, I'm not sure where I'm going wrong, but I created a new environment and container app, both simply called containerapp, added the hostname successfully, but when I attempt to bind the certificate, it appears to initiate the process but then fail after a couple of seconds, claiming the environment can't be found. I've tried multiple validation methods:

az containerapp hostname bind --hostname <hostname> -n containerapp -g containerapp -e containerapp -v CNAME

Argument '--validation-method' is in preview and under development. Reference and support levels: https://aka.ms/CLI_refstatus
Creating managed certificate 'mc-containerapp-<hostname>-2779' for <hostname>.
It may take up to 20 minutes to create and issue a managed certificate.
(ManagedEnvironmentNotFound) Environment containerapp was not found.

UPDATE:

It appears this was because I hadn't set the --location parameter - there was a disparity between my configured defaults and where I'd provisioned the container app.

This does beg the question though, why does the location need to explicitly specified here, if it can be inferred from the container app. Indeed, why does the environment also need to specified, if this can be inferred in the same way?

Further to this, I tried using the REST API to provision a managed certificate (via Pulumi), but I believe one of the property names is incorrect in the specification - specifically, domainControlValidation should be validationMethod.

Used the CLI to create a managed certificate and bind to a new hostname as per steps above. It worked but the certificate chain seems incomplete. Only the leaf/final certificate is returned to all TLS validation tools (https://decoder.link/, https://www.sslshopper.com/ssl-checker.html, and https://www.geocerts.com/ssl-checker).

Is this a bug or by design? [I may have lost touch with how certificates and PKI works]

image

@mburumaxwell I think you're hitting #709

Does anybody know how can we configure managed certificate via bicep or ARM? I could not find any documentation.

The CLI didn't work for us, we had to use the UI to complete the Custom Domain/Managed Certificate. This was due to our Container App and Container App Environment not being in the same resource group and the CLI only accepting/looking in a single resource group.

I feel the CLI shouldn't require the Container App Environment name and/or resource group as the Container App has the references and a Container App only belongs to 1 Container App Environment.

@anthonychu seconding what @jurepurgar said above: can we have some documentation for the API to use managed certificates? They don't seem to be reflected in the Azure spec. The latest definition of CustomDomain has only a certificateId property.

@thomas11 it seems there is a separate resource for managed certificates 'Microsoft.App/managedEnvironments/managedCertificates@2022-11-01-preview'. I tried to use bellow bicep code to configure the managed certificate, but if failed with error: "Creating managed certificate requires hostname 'custom.domain.com' added as a custom hostname to a container app in environment 'envname'". It seems like a chicken and egg problem :)

resource cert 'Microsoft.App/managedEnvironments/managedCertificates@2022-11-01-preview' = {
  location: location
  parent: acaEnv
  name: 'cert-name'
  properties: {
    subjectName: 'custom.domain.com'
    domainControlValidation: 'TXT'
  }
}

resource containerApp 'Microsoft.App/containerApps@2022-10-01' = {
  name: 'app-name'
  location: location
  properties: {
    ...
    configuration: {
      ...
      ingress: {
        targetPort: 8080
        customDomains: [
          {
            certificateId: cert.id
            name: 'custom.domain.com'
          }
        ]
      }
    } 
  }
}

@jurepurgar
Yes, seems the app must be created first with:
customDomains: [{ bindingType: 'Disabled' name: customDomain }]
then managedCertificate.

But then I'm getting another error during ARM deployment (deployment hangs)

    "code": "ResourceDeploymentFailure",
    "target": "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.App/managedEnvironments/xxx/managedCertificates/xxx",
    "message": "The deployment operation status is unknown

But when I open the env in portal the certificate is created.

Seems it can set only via portal/API/cli?

@anthonychu
This appears like it might just be for managed certificates on a container.
What about managed certificates for the Custom DNS suffix on the ACA enviornment?

I tried to use bellow bicep code to configure the managed certificate, but if failed with error: "Creating managed certificate requires hostname 'custom.domain.com' added as a custom hostname to a container app in environment 'envname'". It seems like a chicken and egg problem

Also battling this. I successfully created a managed certificate and the associated domain name in the portal, but I'm drawing a blank on the Bicep approach. Tried to work it out from here:

But without success.

I did an "export template" in the portal and attempted to reverse engineer that into the same approach as @thomas11 but it failed with the same error:

Creating managed certificate requires hostname '....' added as a custom hostname to a container app in environment 'caenv-appname-dev'

Our managed certificate bicep looks like this:

resource managedEnvironmentManagedCertificate 'Microsoft.App/managedEnvironments/managedCertificates@2022-11-01-preview' = if (!empty(customDomainName)) {
  parent: managedEnvironment
  name: '${managedEnvironment.name}-certificate'
  location: location
  tags: tags
  properties: {
    subjectName: customDomainName
    domainControlValidation: 'CNAME'
  }
}

The custom domains portion of our container app Bicep looks like this:

customDomains: empty(customDomainName) ? [] : [
       {
         name: managedEnvironmentManagedCertificate.properties.subjectName
         certificateId: managedEnvironmentManagedCertificate.id
         bindingType: 'SniEnabled'
       }
     ]

Would very much appreciate some guidance on how to achieve this with Bicep

I did some experimentation based on @acuper's comment, and I've got a way to create a managed certificate and a custom domain with Bicep in multiple stages. It's not a joy to use, but let me share it:

  1. First of deploy a disabled custom domain with your container app; something like this: customDomains: [{ bindingType: 'Disabled', name: customDomainName }] (successfully)
  2. Next add deploying the managed certificate into your bicep. When run it will eventually error in the pipeline with something this:
{
    "status": "Failed",
    "error": {
        "code": "AuthorizationFailed",
        "message": "The client '42897a50-20c5-457d-af99-e7e5d2f94663' with object id '42897a50-20c5-457d-af99-e7e5d2f94663' does not have authorization to perform action 'Microsoft.App/locations/managedCertificateOperationStatuses/read' over scope '/subscriptions/5b380244-4d89-4218-a9ad-e6295602f103/providers/Microsoft.App/locations/West Europe/managedCertificateOperationStatuses/702e6197-8625-4700-b991-d0cec276c6a5' or the scope is invalid. If access was recently granted, please refresh your credentials."
    }
}

However - the certificate is created.

  1. Put your custom domain with reference to the managed certificate in place:
    customDomains: empty(customDomainName) ? [] : [
      {
        name: managedEnvironmentManagedCertificate.properties.subjectName
        certificateId: managedEnvironmentManagedCertificate.id
        bindingType: 'SniEnabled'
      }
    ]

This should deploy and give you the managed certificate and custom domain as desired.

So in summary:

  • you can use Bicep
  • but it will take 3 deployments to get you to the point you want.

I've raised a dedicated issue for this as it's distinct from managed certificates specifically: #796

I've written up the workaround way of deploying with Bicep here: https://johnnyreilly.com/azure-container-apps-bicep-managed-certificates-custom-domains

I did some experimentation based on @acuper's comment, and I've got a way to create a managed certificate and a custom domain with Bicep in multiple stages. It's not a joy to use, but let me share it:

  1. First of deploy a disabled custom domain with your container app; something like this: customDomains: [{ bindingType: 'Disabled', name: customDomainName }] (successfully)
  2. Next add deploying the managed certificate into your bicep. When run it will eventually error in the pipeline with something this:
{
    "status": "Failed",
    "error": {
        "code": "AuthorizationFailed",
        "message": "The client '42897a50-20c5-457d-af99-e7e5d2f94663' with object id '42897a50-20c5-457d-af99-e7e5d2f94663' does not have authorization to perform action 'Microsoft.App/locations/managedCertificateOperationStatuses/read' over scope '/subscriptions/5b380244-4d89-4218-a9ad-e6295602f103/providers/Microsoft.App/locations/West Europe/managedCertificateOperationStatuses/702e6197-8625-4700-b991-d0cec276c6a5' or the scope is invalid. If access was recently granted, please refresh your credentials."
    }
}

However - the certificate is created.

  1. Put your custom domain with reference to the managed certificate in place:
    customDomains: empty(customDomainName) ? [] : [
      {
        name: managedEnvironmentManagedCertificate.properties.subjectName
        certificateId: managedEnvironmentManagedCertificate.id
        bindingType: 'SniEnabled'
      }
    ]

This should deploy and give you the managed certificate and custom domain as desired.

So in summary:

  • you can use Bicep
  • but it will take 3 deployments to get you to the point you want.

I had the same issue, I had to do the same thing. The bug at step 2 is annoying, it would fail my pipeline if the certificate is not created. However, the second time I run the pipeline should be fine because the certificate is already created.

commented

@anthonychu - thanks for this wonderful feature.

I believe there's a bug in the Container Apps RP, which is why the folks above are having to deploy multiple times using declarative methods (ARM/Bicep), whereas imperative methods (your CLI instructions) work fine. Because imperatives poll the state of the Cert provisioning, whereas declarative looks for async headers AFAIK.

The RP doesn't seem to be implementing the ARM Async Headers contract for certificate provisioning.

So for example, I took an existing Container App, appended the ARM template bits to enable the Managed Cert (what the folks in this thread found via reverse engineering Portal).

Notice how ARM thinks the deployment status is unknown, and keeps spinning it's wheels for 13 minutes:
image

The actual cert was provisioned 2 mins in, the RP just isn't letting ARM know about it:
image

Should be an easy bug to fix I think to make ARM/Bicep deployments work from there

The problem @mdrakiburrahman mentioned might be the reason why my declarative deployment via Terraform's AzAPI provider doesn't work.

This is how I try to provision a managed certificate via AzAPI:

resource "azapi_resource" "managed_certificate" {
  depends_on = [
    azurerm_dns_a_record.main,
    azurerm_dns_txt_record.verification_id,
    azapi_resource.container_app
  ]
  type     = "Microsoft.App/ManagedEnvironments/managedCertificates@2022-11-01-preview"
  name = "some-certificate-resource-name"
  parent_id = var.dependencies.container_app_env.id
  location  = var.dependencies.container_app_env.resource_group_location

  body = jsonencode({
    properties = {
      subjectName             = "example.tld"
      domainControlValidation = "HTTP"
    }
  })

  response_export_values = ["*"]
}

Then the deployment always fails with this message:

Error: creating/updating "Resource: (ResourceId "/subscriptions/.../resourceGroups/.../providers/Microsoft.App/managedEnvironments/.../managedCertificates/some-certificate-resource-name" / Api Version "2022-11-01-preview")": the response did not contain a body

After a few minutes the certificate is successfully created in Azure though. :/

I'm using Pulumi and having the sames issues as bicep. So I can confirm.

commented

After testing this via Automated releases across 30+ Container App Envs (our Production clusters run per Azure region, see screenshot below), jotting a crisp summary of the problems with using ARM/Bicep/Terraform/Pulumi/other-declarative-deployment-product (they all use the same ARM APIs to poll declarative state, so let's simplify the problem by not making this tool specific and confusing the person that would need to actually fix this bug) .

Hopefully this helps the ACA team repro and solve this bug rapidly:

Summary of bugs with Managed Certs declarative (ARM template etc) deployment

Bug 1. Chicken and Egg:

Creating managed certificate requires hostname '....' added as a custom hostname to a container app in environment 'your-aca-env'

This is probably a error-handling a dev added in when they wrote this feature, to make sure you cannot use ACA to create random Managed Certs and abuse the feature, when you haven't already assigned a Custom Domain to a specific Container App.

The problem is, the dev probably expected Customer would click-click in the Portal to assign the Custom Domain - which validates the TXT record asuid.my-aca-app.

Current Deployment flow

  1. Create Managed Env
  2. Create Container App
  3. Click-click in UI/one-off az script, to enter Custom Domain, change your Container App
  4. Click-click to create Managed cert.
  5. Now, the Container App is bound to the managed cert.

It's impossible to do this click-click at-scale across 30+ clusters with hundreds of apps (3000+ click-clicks).

After Step 5, the feature works wonderfully.

In other words, this is probably the only assumption that makes the Managed Certificate feature not ready for Production yet, because you need a human being to do steps 3 and 4.

Alternative, scalable Deployment flow

  1. Let's say, I have no Container App, no managed env etc.
  2. Before I do anything, as a pre-req to using Managed Certs feature, I assign the asuid.my-aca-app TXT record to my Domain Registrar, to prove to ACA that I own mydomain.com
  3. Now, I deploy a single ARM template, and my Container App comes up at the end, listening with a managed cert on my-container-app.mydomain.com, with zero click-click, or zero az scripts.

Suggestion

To avoid the chicken-and-egg problem - while still having the safety check in place, instead of having the Managed Container env validate the custom hostname for a Container App that doesn't yet exist, if the Managed Container env instead, validated the asuid.my-aca-app directly, this solves this problem nicely - because the Managed Cert would be provisioned, and the App could use it right at deployment time.

Because, say I am about to deploy 100 Container Apps, the ARM template can:

  1. Spin up a Managed Env
  2. Create 100 Certs for my-app-1, 2, ...., 100, where it ensures I own the domain, by checking asuid.my-app-1, 2, ....
  3. In my ARM template, I declare a dependency on Step 2, none of my Apps will deploy, till the Certs are provisioned
  4. Certs are provisioned, all 100 Apps comes up with the custom hostname assigned, ingress works.
  5. Can do this at scale for many many clusters/apps

Bug 2. Cert provisioned, but ARM still spinning:

This is the bug I mentioned above: #607 (comment)

Should be a small code fix for this particular bug.

Suggestion

Whoever is in charge of this feature, please try to do quick test of using ARM templates to deploy a fresh Container App Managed Env + Hello World App deployment with Managed Cert, you'll repro this every time (I've reprod this a couple 100 times without fail):

image

Hi @anthonychu

can you clarify what is the plan to deal with this issue, and when we can expect a fix?
This Issue Blocks production usage

@johnnyreilly really helpful guide - what are you doing for

  1. dns zone - are you setting it up in bicep files?
param webContainerAppName string
param dnsZoneName string
param location string = resourceGroup().location
param verificationId string

resource dnsZone 'Microsoft.Network/dnsZones@2018-05-01' = {
  name: dnsZoneName
  location: location
}

resource cname 'Microsoft.Network/dnsZones/CNAME@2018-05-01' = {
  name: 'www'
  parent: dnsZone
  properties: {
    TTL: 3600
    CNAMERecord: {
      cname: '${webContainerAppName}.${location}.azurecontainer.io'
    }
  }
}

resource txtRecord 'Microsoft.Network/dnsZones/TXT@2018-05-01' = {
  name: '@'
  parent: dnsZone
  properties: {
    TTL: 3600
    TXTRecords: [
      {
        value: [
          verificationId
        ]
      }
    ]
  }
}

and what about godaddy - are you having to do anything there - when i made the custom domain work via the portal it was pretty simply and i just added 1. a record for the ip address and 2. the txt asuid
but i guess in your run through you are doing something with cname?

dns zone - are you setting it up in bicep files?

No - we use terraform for DNS. And we're not using GoDaddy or similar

i guess in your run through you are doing something with cname?

Yes

@johnnyreilly sorry one more question - following your plan did you find step 2 provisioning the managed certificate took ages? it's more than an hour so far...

Step 2 errors out, but the cert gets provisioned; and fairly quickly I think. Step 2 will officially fail though; but you should have a cert at the end of it

@anthonychu maybe this is a noob question but my understanding of the documentation is that domainControlValidation can be either CNAME, HTTP or TXT. https://learn.microsoft.com/en-us/azure/templates/microsoft.app/managedenvironments/managedcertificates?pivots=deployment-language-bicep
so then how com you suggest for a name records using HTTP rather than TXT? admittedly when i export the managed environment and decompile into bicep i can see the certificate that is created inside it corroborates your approach of setting HTTP - if so why does the TXT approach exist? when i try to deploy the certificate in step two i get a resourcedeploymentfailure error where the deployment operation status is unknown
i cannot find a way to export the container app bicep in portal however so any ideas on how to do this are also welcome

[update] managed to get it to work using the 3 step process outline by johnnyreilly (thank you so much!!) - weirdly i kept getting
InvalidTemplateDeployment: The template deployment 'web-container-app-update' is not valid according to the validation procedure. The tracking id is '13aac6e9-560b-47e4-8f96-201ce186686a'. See inner errors for details. ValidationForResourceFailed: Validation failed for a resource. Check 'Error.Details[0]' for more information. ContainerAppInvalidSchema: Invalid request body for container app. Path: $. Does not conform to Container App schema, please
errors when i tried to pass in the custom domain via containerAppsEnvironmentManagedCertificate.properties.subjectName and yet when i passed it as a param (the same way i passed it in for the managed certificate custom domain) it seemed to be happy with that (i.e. closer to what johnnyreilly did in his original approach rather than the updated one for the blogpost - everything else was the same as above although i got the same behaviour as mrdrakiburriman and acuper where i got the deployment unknown but actually if you wait long enough (with http) it does success in the second step rather than the AuthorizationFailed error johnnyreillly gets

although now when i load my webpage sometimes it works sometimes i get connection reset, or closed the connection - not sure why?

I'm wondering why certificate validation for Container Apps is so much difficult (literally broken) comparing to Static Web Apps, where validation works with one CNAME record pointed to Default Host name of Static App domain.

subdomain.custom-domain.tld -> static-web-app.azurestaticapps.net

If this records is exist in Azure Cloud DNS, certificate will be issued right away.

In my case, I have custom domain already on Azure Cloud DNS and I expected that Container Apps certificates should be easily created just by pointing custom (sub)domain to the CNAME of Managed Environment.

For me, this makes Container Apps totally unusable.

It's really unfortunate that this remains really very broken, and even worse that there's been zero comment AFAICT from MS staff here for three months after the feature was marked "done" for Preview purposes.

Having a feature like this be available via interactive methods (portal, CLI) is cute, but absent options for robust automation (obviously bicep, but ideally others ofc), I don't know how useful a "Preview" phase can be, as people simply won't be able to use it at scale / beyond tinkerings.

For those following along, our workaround has been to put all of our container apps behind Cloudflare, let it manage our edge certificates, and bind a single Cloudflare-issued "origin" cert to every container app. This is obviously not an ideal arrangement, but it at least allows us to fully automate deployments without shelling to CLIs, performing repeated deploys, or worrying about other related containerapp cert bugs like #709.

Hey, @cemerick. I also had an idea about pointing subdomains to Load Balancer with SNI support to keep Container Apps running under TLS. But not going outside of Azure.
Anyway, that's probably first time I can see that so important feature as Managed Certificates is not supported in Cloud API without chaining CLI commands or doing multi-apply with Terraform or so, really disappointing for me.

But not going outside of Azure.

Yeah, I'm not happy about it either. Obviously hurts latency, and just another point of failure.

If you (or anyone else) figures an in-Azure workaround that yields good bicep automation and doesn't involve provisioning yet another load balancer or whatever, please do post about it. πŸ˜„

We are currently chaining CLI AZ commands in our deploy pipeline (Non-Prod), although working, we are consequently wiping and reapplying certs on each Bicep deployment and burning through issued certs at some pace. We really need to be able to bake this into Bicep before we could use this in any greater capacity.

Agreed that we want to make Managed Certs deployment automatable. We are looking into our options and will provide an update within the next couple of days. Thanks.

This is solved in other services that use managed certificates by providing multiple resources, rather than putting everything in the single resource. See for instance https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service_managed_certificate. The same applies in API/bicep/etc since it's the underlying API that's split out.

Agreed that we want to make Managed Certs deployment automatable. We are looking into our options and will provide an update within the next couple of days. Thanks.

Any news here?

Agreed that we want to make Managed Certs deployment automatable. We are looking into our options and will provide an update within the next couple of days. Thanks.

Any news here?

+1

This is becoming a big blocker on our pipelines.

@Adamation we managed to work around this by using a small bash script in our deployment pipeline. This however has the consequence of a short downtime on each deployment.

# Deploying Managed Certificates via Bicep is not possible yet
# https://github.com/microsoft/azure-container-apps/issues/607

if az containerapp hostname list -g "$resourceGroup" -n frontend-"${shortEnvironmentName}" | jq -e '. | length == 0' >/dev/null; then
  if [[ $shortEnvironmentName == "prod" ]]; then
    domain="my-prod-domain.com"
  else
    domain="my-staging-domain.com"
  fi

  echo "Adding the hostname to the container app"
  az containerapp hostname add --hostname "$domain" -g "$resourceGroup" -n frontend-"${shortEnvironmentName}" >/dev/null
fi

if az containerapp env certificate list -g "$resourceGroup" -n "$appEnvironmentName" | jq -e '. | length == 0' >/dev/null; then
  echo "Creating the certificate in the app environment"

  az containerapp env certificate create --hostname "$domain" -n "$appEnvironmentName" -g "$resourceGroup" -v CNAME >/dev/null

  timeout_seconds=180
  end_time=$((SECONDS + timeout_seconds))
  while [ $SECONDS -lt $end_time ]; do
    if az containerapp env certificate list -g "$resourceGroup" --name "$appEnvironmentName" | jq -e '.[0].properties.provisioningState == "Succeeded"' >/dev/null; then
      echo "😌 Condition met: Certificate provisioning state is Succeeded."
      break
    else
      echo "😴 Waiting for the certificate to reach provisioning provisioningState 'Succeeded'..."
      sleep 10
    fi
  done

  if [ $SECONDS -ge $end_time ]; then
    echo "Timeout reached. Aborting after $timeout_seconds seconds."
  fi
fi

echo "Binding the hostname..."
az containerapp hostname bind --hostname "$domain" -g "$resourceGroup" -n frontend-"${shortEnvironmentName}" --environment "$appEnvironmentName" --validation-method CNAME

Thank you Deen, we are already doing something similar in our pipelines. Not ideal.

We have fixed an issue that caused the step of creating the managed certificate to take a long time and then fail (although the certificate was indeed successfully created.

With this fix, the number of deployment steps is reduced from 3 to 2. We are designing ways to be able to accomplish the whole process in a single deployment template (e.g., removing the need of creating a container app before creating a managed certificate for certain type of domain ownership validation). We don't have an ETA to share at this time.

Could this be solved by issuing a managed wildcard certificate in the CAE before deploying the apps?

We have fixed an issue that caused the step of creating the managed certificate to take a long time and then fail (although the certificate was indeed successfully created.

With this fix, the number of deployment steps is reduced from 3 to 2. We are designing ways to be able to accomplish the whole process in a single deployment template (e.g., removing the need of creating a container app before creating a managed certificate for certain type of domain ownership validation). We don't have an ETA to share at this time.

Sounds very promising, thank you for this update.
I have a question regarding domain validation process.

Azure has Static WebApp, which allows a customer to validate domain ownership using CNAME record pointed to Static WebApp default domain.

Why exactly the same logic can't be used with Container Apps?
E.g.:

  • customer has a domain on Azure DNS (which is reasonable if full automation is preferred)
  • CNAME record on custom domain, pointed to Managed Environment's default_domain is created
  • domain validation finished
  • Certificate is issued

With such logic in place, it will be (finally) possible to use normal Terraform deployment without any hacks.

Example of Static WebApps in Terraform which will deploy Static WebApp on custom domain and issue TLS certificate in one-time deployment:

resource "azurerm_resource_group" "myapp" {
  name     = local.name
  location = local.azurerm_resource_group_location
  tags     = local.tags
}

resource "azurerm_static_site" "myapp" {
  name                = local.name
  resource_group_name = azurerm_resource_group.myapp.name
  location            = local.app_location
  sku_tier            = local.app_sku.tier
  sku_size            = local.app_sku.size
  tags                = local.tags
}

resource "azurerm_static_site_custom_domain" "myapp" {
  static_site_id  = azurerm_static_site.myapp.id
  domain_name     = format("%s.%s", azurerm_dns_cname_record.myapp.name, azurerm_dns_zone.name)
  validation_type = "cname-delegation"
}

resource "azurerm_dns_zone" "myapp" {
  name                = local.dns_zone
  resource_group_name = azurerm_resource_group.myapp.name
  tags                = local.tags
}

resource "azurerm_dns_cname_record" "myapp" {
  name                = local.custom_domain
  zone_name           = azurerm_dns_zone.myapp.name
  resource_group_name = azurerm_resource_group.myapp.name
  ttl                 = local.default_ttl
  record              = azurerm_static_site.myapp.default_host_name
}

In the documentation, there is a phrase "Your container app has HTTP ingress enabled and is publicly accessible."

Is this true? And why this is not a requirement for other services in Azure and only for Container apps?

I was just following the official documentation which seems to specify parameters that aren't available in azure-cli 2.53.1 or in the Azure cloud shell:
MicrosoftDocs/azure-docs#116721

What's the status of this, is it not implemented yet? Seems odd to have it documented already.

In case someone's looking for some terraform code that uses a managed certificate, I managed to make it work using only the azurerm and azapi providers. Here's the code. It creates the resource group, container app environment, container app, the certificate and even the DNS record.

It seems to be working, the only issue I'm having is that the terraform destroy command fails when deleting the managed certificate (CertificateInUse error), because I'm using the azapi_update_resource to bind the certificate to the container app, which isn't undone when running terraform destroy. Unfortunately I found no way to fix this without using a provisioner.

I agree that it'd be best if we had a way to bind certificates in a similar way as App Services and Static Web Apps do it.

Since there is no way to easily setup an managedCertificate using Bicep.
Has anyone found a way of preventing the deletion of a manually added custom domain when redeploying the container app with a new image version? This would also help when you hosted zone is not managed in azure.

We have fixed an issue that caused the step of creating the managed certificate to take a long time and then fail (although the certificate was indeed successfully created.

With this fix, the number of deployment steps is reduced from 3 to 2. We are designing ways to be able to accomplish the whole process in a single deployment template (e.g., removing the need of creating a container app before creating a managed certificate for certain type of domain ownership validation). We don't have an ETA to share at this time.

@vinisoto is there any news regarding ETA on this?

The --validation-method flag mentioned in the still-current documentation here doesn't even exist in the az containerapp hostname bind subcommand at this point. I don't know how this is meant to be possible, but it sure doesn't look like it is.

Certificate name of custom domains is generated automatically in the format $domain_name-$resource_group_name[:8]-$datetime . And it's with resource_group_name[:8] that problems arise.
If the slice of 8 characters from the resource_group ends in -, then the code doesn't check this and as a result it generates a double dash in the name, and the resource name looks like this test.example-prod-ds--231211091022

Even according to the Microsoft guidelines, the resource name should look in the format my-super-resource-name
https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-naming

Also, many TF providers have checks for the correctness of the name, for example azurerm
https://github.com/hashicorp/terraform-provider-azurerm/blob/main/internal/services/batch/validate/certificate_name.go#L13

With Pulumi, I've provisioned afd domains and used a managed certificate, it works great. ACA needs that :)

@linxcat - there is a resolution here which is that the functionality is provided by an extension:

#MicrosoftDocs/azure-docs#116721

I think the baffling documentation is still an issue though.

Since there is no way to easily setup an managedCertificate using Bicep. Has anyone found a way of preventing the deletion of a manually added custom domain when redeploying the container app with a new image version? This would also help when you hosted zone is not managed in azure.

@kobeyy did you find the solution for this ?

@hoxton-webmaster I managed to find a work around by using the azure cli to first lookup the id of the managed certificate and fill it in using a shell script.
Since it was this difficult and hacky to get it working I decided to not use this Azure service as it is premature in my opinion. We halted our roll out to production because of this.

I've added the complete script below as text file.

...

# Workaround to prevent deletion of the custom domain binding by Bicep on a redeploy
get_custom_domain_id() {
    local subscription_id=$(az account show --query 'id' -o tsv)
    local managedEnvironments="/subscriptions/$subscription_id/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.App/managedEnvironments/"
    local firstContainerAppEnv=$(az resource show --ids "$managedEnvironments" --query 'value[0].id' -o tsv)
    local customDomainCertificateId=$(az resource show --ids "$firstContainerAppEnv/managedCertificates/" --query 'value[0].id' -o tsv)
    echo $customDomainCertificateId
}
get_custom_domain_name() {
    local subscription_id=$(az account show --query 'id' -o tsv)
    local managedEnvironments="/subscriptions/$subscription_id/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.App/managedEnvironments/"
    local firstContainerAppEnv=$(az resource show --ids "$managedEnvironments" --query 'value[0].id' -o tsv)
    local customDomainCertificateName=$(az resource show --ids "$firstContainerAppEnv/managedCertificates/" --query 'value[0].properties.subjectName' -o tsv)
    echo $customDomainCertificateName
}


# Function to deploy template
deploy_template() {
    TEMPLATE_FILE="$SCRIPT_DIR/$1"
    echo "Deploying template: $TEMPLATE_FILE"

    AZURE_COMMAND="az deployment group create --template-file \"$TEMPLATE_FILE\" \
                               --resource-group \"$RESOURCE_GROUP\" \
                               --parameters \"@$PARAMETER_FILE\""


    # Workaround to prevent deletion of the custom domain binding by Bicep on a redeploy
    # Inject the manually created custom domain binding into the deployment
    local customDomainCertId=$(get_custom_domain_id)
    if [ -n "$customDomainCertId" ]; then
        AZURE_COMMAND+=" customDomainCertificateId='$customDomainCertId'"
    fi

    local customDomainCertName=$(get_custom_domain_name)
    if [ -n "$customDomainCertName" ]; then
        AZURE_COMMAND+=" customDomainName='$customDomainCertName'"
    fi


    # Conditionally add parameters
    if [ -n "$CONTAINER_REGISTRY_PASSWORD" ]; then
        AZURE_COMMAND+=" containerRegistryPassword='$CONTAINER_REGISTRY_PASSWORD'"
    fi
    if [ -n "$CONTAINER_REGISTRY_USER" ]; then
        AZURE_COMMAND+=" containerRegistryUsername='$CONTAINER_REGISTRY_USER'"
    fi
    if [ -n "$PHP_CONTAINER_IMAGE" ]; then
        AZURE_COMMAND+=" phpContainerImage='$PHP_CONTAINER_IMAGE'"
    fi
    if [ -n "$NGINX_CONTAINER_IMAGE" ]; then
        AZURE_COMMAND+=" nginxContainerImage='$NGINX_CONTAINER_IMAGE'"
    fi
    if [ -n "$IMAGE_TAG" ]; then
        AZURE_COMMAND+=" imageTag='$IMAGE_TAG'"
    fi

    echo "Executing command: $AZURE_COMMAND"
    eval $AZURE_COMMAND
}

deploy_template "deploy.bicep"

full script