This guide explains how to serve the Cloudmersive Private Cloud Virus Scan API over HTTPS on Azure Kubernetes Service (AKS) using your own TLS certificate.
Two options are covered for supplying the certificate:
- Option A: Kubernetes Secret. You store the certificate and private key directly in the cluster. Simplest, and the same steps work on any Kubernetes cluster with an NGINX ingress controller.
- Option B: Azure Key Vault. You store the certificate in Key Vault and AKS pulls it into the cluster automatically. Recommended for production, because renewals happen in one place and the private key never has to be handled on a workstation.
All commands can be run from Azure Cloud Shell, which has az, kubectl, helm, and openssl pre-installed.
How it works
HTTPS is handled by an ingress controller running in the cluster. It holds your certificate, accepts HTTPS on port 443, and forwards requests to the Cloudmersive Service.
This guide uses the AKS application routing add-on, a Microsoft-managed NGINX ingress controller with built-in Azure Key Vault support. If your cluster already runs a different NGINX-based ingress controller, Option A works with it unchanged apart from the ingressClassName in the Ingress manifest.
Prerequisites
- The Cloudmersive Helm chart is installed and the pod is running.
- A DNS hostname that you control, such as
virusscan.contoso.com. The certificate must be issued for this name.
- A TLS certificate for that hostname, either as a
.pfx file or as PEM .crt / .key files.
kubectl access to the cluster and Contributor access to the AKS resource group.
Set the following variables in your shell before continuing. Every command below references them.
RG=<resource-group> # resource group containing the AKS cluster
CLUSTER=<cluster-name> # AKS cluster name
NS=default # namespace where the Cloudmersive chart was installed
HOST=virusscan.contoso.com # hostname the certificate was issued for
az aks get-credentials --resource-group $RG --name $CLUSTER
Step 1: Enable the ingress controller
Enable the application routing add-on. This is safe to run on an existing cluster and takes one to two minutes.
az aks approuting enable --resource-group $RG --name $CLUSTER
Confirm the controller is running and has been assigned a public IP:
kubectl get service nginx --namespace app-routing-system
Example output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.0.88.201 20.51.117.44 80:31650/TCP,443:30208/TCP 2m
If EXTERNAL-IP shows <pending>, wait a minute and try again.
Step 2: Point your DNS name at the ingress controller
HTTPS traffic now needs to arrive at the ingress controller's IP, not the Cloudmersive Service's IP. Create a DNS record for your hostname pointing at the EXTERNAL-IP from Step 1.
A record. In your DNS provider, create an A record for virusscan.contoso.com pointing at the IP. This is the simplest option.
CNAME to an Azure FQDN. Alternatively, give the ingress controller's IP an Azure DNS label and create a CNAME to the resulting *.cloudapp.azure.com name. This is the same mechanism described in Assigning an FQDN to Cloudmersive Private Cloud on AKS, applied to the ingress controller instead of the Cloudmersive Service:
kubectl patch nginxingresscontroller default --type merge \
-p '{"spec":{"loadBalancerAnnotations":{"service.beta.kubernetes.io/azure-dns-label-name":"<your-label>"}}}'
Then create a CNAME from virusscan.contoso.com to <your-label>.<region>.cloudapp.azure.com.
Confirm the name resolves before continuing:
nslookup $HOST
Step 3: Supply the certificate
Choose one of the two options below.
Option A: Kubernetes Secret
A.1 Convert the certificate to PEM format
Kubernetes expects the certificate and private key as separate PEM files. If you already have tls.crt and tls.key in PEM format, skip to A.2.
If you have a .pfx file, extract both parts. You will be prompted for the PFX password.
openssl pkcs12 -in certificate.pfx -nokeys -out tls.crt
openssl pkcs12 -in certificate.pfx -nocerts -nodes -out tls.key
The first command writes the certificate plus any intermediate chain certificates included in the PFX. The second writes the unencrypted private key. Keep tls.key secure and delete it from the workstation after the Secret is created.
If your certificate authority supplied the chain as a separate file, append it so the leaf certificate comes first:
cat leaf.crt intermediate.crt > tls.crt
A.2 Create the Secret
The Secret must be in the same namespace as the Cloudmersive chart.
kubectl create secret tls cloudmersive-tls \
--namespace $NS \
--cert=tls.crt \
--key=tls.key
Verify:
kubectl get secret cloudmersive-tls --namespace $NS
A.3 Create the Ingress
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cloudmersive-privatecloud-virusscanapi
namespace: $NS
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
spec:
ingressClassName: webapprouting.kubernetes.azure.com
tls:
- hosts:
- $HOST
secretName: cloudmersive-tls
rules:
- host: $HOST
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: cloudmersive-privatecloud-virusscanapi
port:
number: 80
EOF
The proxy-body-size annotation removes NGINX's default 1 MB upload limit, which would otherwise reject files sent for scanning. The timeout annotations allow up to ten minutes for a scan to complete.
Continue to Step 4.
Option B: Azure Key Vault
This option assumes you do not yet have a Key Vault. If you already have one containing the certificate, skip to B.3.
B.1 Create the Key Vault
KV=<key-vault-name> # globally unique, 3-24 lowercase letters, numbers, hyphens
LOCATION=$(az aks show --resource-group $RG --name $CLUSTER --query location -o tsv)
az keyvault create \
--resource-group $RG \
--name $KV \
--location $LOCATION \
--enable-rbac-authorization true
KV_ID=$(az keyvault show --name $KV --query id -o tsv)
Grant yourself permission to import certificates. With RBAC-enabled vaults, creating the vault does not automatically grant data-plane access.
az role assignment create \
--role "Key Vault Certificates Officer" \
--assignee $(az ad signed-in-user show --query id -o tsv) \
--scope $KV_ID
Role assignments can take up to five minutes to take effect. If the import in the next step fails with a permissions error, wait and retry.
B.2 Import the certificate
From a .pfx file:
CERT=cloudmersive-tls # name of the certificate inside Key Vault
az keyvault certificate import \
--vault-name $KV \
--name $CERT \
--file certificate.pfx \
--password '<pfx-password>'
From PEM files, combine the certificate chain and key into one file first:
cat tls.crt tls.key > certificate.pem
az keyvault certificate import --vault-name $KV --name $CERT --file certificate.pem
Verify:
az keyvault certificate show --vault-name $KV --name $CERT \
--query "{name:name, expires:attributes.expires}" -o table
B.3 Connect the ingress controller to Key Vault
This grants the ingress controller's managed identity read access to the vault and turns on certificate syncing.
az aks approuting update \
--resource-group $RG \
--name $CLUSTER \
--enable-kv \
--attach-kv $KV_ID
B.4 Create the Ingress
The Ingress references the certificate by its Key Vault URI. The add-on retrieves it and stores it as a Kubernetes Secret named keyvault-<ingress-name> in the same namespace. The secretName below must follow that convention.
CERT_URI=$(az keyvault certificate show --vault-name $KV --name $CERT --query id -o tsv | sed 's|/[^/]*$||')
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cloudmersive-privatecloud-virusscanapi
namespace: $NS
annotations:
kubernetes.azure.com/tls-cert-keyvault-uri: "$CERT_URI"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
spec:
ingressClassName: webapprouting.kubernetes.azure.com
tls:
- hosts:
- $HOST
secretName: keyvault-cloudmersive-privatecloud-virusscanapi
rules:
- host: $HOST
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: cloudmersive-privatecloud-virusscanapi
port:
number: 80
EOF
The CERT_URI command strips the version segment from the certificate ID, so the URI has the form https://<vault>.vault.azure.net/certificates/<name>. Referencing the certificate without a version means renewed versions are picked up automatically.
Confirm the add-on has synced the certificate into the cluster. This can take a minute or two:
kubectl get secret keyvault-cloudmersive-privatecloud-virusscanapi --namespace $NS
Continue to Step 4.
Step 4 (optional): Disable plain HTTP
The Cloudmersive Helm chart creates its Service as type LoadBalancer, which means the API remains reachable over unencrypted HTTP on its original public IP. If you want HTTPS to be the only way in, change the Service to ClusterIP so it has no public IP of its own. You can skip this step if you need to keep the HTTP endpoint.
helm upgrade cloudmersive-privatecloud-virusscanapi \
https://privatecloud.cloudmersive.com/download/helm/vs/cloudmersive-private-cloud-chart-<version>.tgz \
--namespace $NS \
--reuse-values \
--set service.type=ClusterIP
Use the same chart version you originally installed. The --reuse-values flag keeps your access key, image settings, and any other values from the original install.
Verify the Service no longer has an external IP:
kubectl get service cloudmersive-privatecloud-virusscanapi --namespace $NS
If you previously assigned an Azure DNS label to this Service, that label is released when the public IP is removed. The Step 2 record now serves as your DNS entry.
Step 5: Test
Check that the API responds over HTTPS:
curl https://$HOST/virus/status
Check the certificate being served:
openssl s_client -connect $HOST:443 -servername $HOST </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
The subject should show your hostname and the issuer should be your certificate authority. If the issuer shows Kubernetes Ingress Controller Fake Certificate, the ingress controller could not find the Secret. See Troubleshooting below.
Confirm plain HTTP redirects to HTTPS:
curl -I http://$HOST/virus/status
Expect a 308 Permanent Redirect with a Location: https://... header.
Update your Cloudmersive SDK or API client to use https://<your-hostname> as the base URL.
Renewing the certificate
Option A. Recreate the Secret with the new files. Using apply updates it in place, and the ingress controller reloads automatically within a few seconds.
kubectl create secret tls cloudmersive-tls --namespace $NS \
--cert=tls.crt --key=tls.key \
--dry-run=client -o yaml | kubectl apply -f -
Option B. Import the renewed certificate into Key Vault under the same name. This creates a new version, and because the Ingress references the certificate without a version, the add-on syncs it automatically.
az keyvault certificate import --vault-name $KV --name $CERT --file renewed.pfx --password '<pfx-password>'
The new certificate is typically served within a few minutes. Verify with the openssl s_client command from Step 5.