Skip to content

Commit 9941ef4

Browse files
Merge pull request #241195 from tomvcassidy/caddyAciPublicPr
Caddy ACI Public PR
2 parents 17a83a3 + 75aee71 commit 9941ef4

File tree

4 files changed

+312
-0
lines changed

4 files changed

+312
-0
lines changed

articles/container-instances/TOC.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@
133133
href: container-instances-gpu.md
134134
- name: Enable SSL endpoint in sidecar
135135
href: container-instances-container-group-ssl.md
136+
- name: Enable automatic HTTPS with Caddy as reverse proxy
137+
href: container-instances-container-group-automatic-ssl.md
136138
- name: Expose static IP with App Gateway
137139
href: container-instances-application-gateway.md
138140
- name: Use Azure Firewall for ingress and egress
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
---
2+
title: Enable automatic HTTPS with Caddy as a sidecar container
3+
description: This guide describes how Caddy can be used as a reverse proxy to enhance your application with automatic HTTPS
4+
ms.author: tomcassidy
5+
author: tomvcassidy
6+
ms.service: container-instances
7+
services: container-instances
8+
ms.topic: how-to
9+
ms.date: 06/12/2023
10+
---
11+
12+
# Enable automatic HTTPS with Caddy in a sidecar container
13+
14+
This article describes how Caddy can be used as a sidecar container in a [container group](container-instances-container-groups.md) acting as a reverse proxy to provide an automatically managed HTTPS endpoint for your application.
15+
16+
Caddy is a powerful, enterprise-ready, open source web server with automatic HTTPS written in Go and represents an alternative to Nginx.
17+
18+
The automatization of certificates is possible because Caddy supports the ACMEv2 API ([RFC 8555](https://www.rfc-editor.org/rfc/rfc8555)) that interacts with [Let's Encrypt](https://letsencrypt.org/) to issue certificates.
19+
20+
In this example, only the Caddy container gets exposed on ports 80/TCP and 443/TCP. The application behind the reverse proxy remains private. The network communication between Caddy and your application happens via localhost.
21+
22+
> [!NOTE]
23+
> This stands in contrast to the intra container group communication known from docker compose, where containers can be referenced by name.
24+
25+
The example mounts the [Caddyfile](https://caddyserver.com/docs/caddyfile), which is required to configure the reverse proxy, from a file share hosted on an Azure Storage account.
26+
27+
> [!NOTE]
28+
> For production deployments, most users will want to bake the Caddyfile into a custom docker image based on [caddy](https://hub.docker.com/_/caddy). This way, there is no need to mount files into the container.
29+
30+
[!INCLUDE [azure-cli-prepare-your-environment.md](~/articles/reusable-content/azure-cli/azure-cli-prepare-your-environment.md)]
31+
32+
- This article requires version 2.0.55 or later of the Azure CLI. If using Azure Cloud Shell, the latest version is already installed.
33+
34+
## Prepare the Caddyfile
35+
36+
Create a file called `Caddyfile` and paste the following configuration. This configuration creates a reverse proxy configuration, pointing to your application container listening on 5000/TCP.
37+
38+
```console
39+
my-app.westeurope.azurecontainer.io {
40+
reverse_proxy http://localhost:5000
41+
}
42+
```
43+
44+
It's important to note, that the configuration references a domain name instead of an IP address. Caddy needs to be reachable by this URL to carry out the challenge step required by the ACME protocol and to successfully retrieve a certificate from Let's Encrypt.
45+
46+
> [!NOTE]
47+
> For production deployment, users might want to use a domain name they control, e.g., `api.company.com` and create a CNAME record pointing to e.g. `my-app.westeurope.azurecontainer.io`. If so, it needs to be ensured, that the custom domain name is also used in the Caddyfile, instead of the one assigned by Azure (e.g., `*.westeurope.azurecontainer.io`). Further, the custom domain name, needs to be referenced in the ACI YAML configuration described later in this example.
48+
49+
## Prepare storage account
50+
51+
Create a storage account
52+
53+
```azurecli
54+
az storage account create \
55+
--name <storage-account> \
56+
--resource-group <resource-group> \
57+
--location westeurope
58+
```
59+
60+
Store the connection string to an environment variable
61+
62+
```azurecli
63+
AZURE_STORAGE_CONNECTION_STRING=$(az storage account show-connection-string --name <storage-account> --resource-group <resource-group> --output tsv)
64+
```
65+
66+
Create the file shares required to store the container state and caddy configuration.
67+
68+
```azurecli
69+
az storage share create \
70+
--name proxy-caddyfile \
71+
--account-name <storage-account>
72+
73+
az storage share create \
74+
--name proxy-config \
75+
--account-name <storage-account>
76+
77+
az storage share create \
78+
--name proxy-data \
79+
--account-name <storage-account>
80+
```
81+
82+
Retrieve the storage account keys and make a note for later use
83+
84+
```azurecli
85+
az storage account keys list -g <resource-group> -n <storage-account>
86+
```
87+
88+
## Deploy container group
89+
90+
### Create YAML file
91+
92+
Create a file called `ci-my-app.yaml` and paste the following content. Ensure to replace `<account-key>` with one of the access keys previously received and `<storage-account>` accordingly.
93+
94+
This YAML file defines two containers `reverse-proxy` and `my-app`. The `reverse-proxy` container mounts the three previously created file shares. The configuration also exposes port 80/TCP and 443/TCP of the `reverse-proxy` container. The communication between both containers happens on localhost only.
95+
96+
>[!NOTE]
97+
> It's important to note, that the `dnsNameLabel` key, defines the public DNS name, under which the container instance group will be reachable, it needs to match the FQDN defined in the `Caddyfile`
98+
99+
```yml
100+
name: ci-my-app
101+
apiVersion: "2021-10-01"
102+
location: westeurope
103+
properties:
104+
containers:
105+
- name: reverse-proxy
106+
properties:
107+
image: caddy:2.6
108+
ports:
109+
- protocol: TCP
110+
port: 80
111+
- protocol: TCP
112+
port: 443
113+
resources:
114+
requests:
115+
memoryInGB: 1.0
116+
cpu: 1.0
117+
limits:
118+
memoryInGB: 1.0
119+
cpu: 1.0
120+
volumeMounts:
121+
- name: proxy-caddyfile
122+
mountPath: /etc/caddy
123+
- name: proxy-data
124+
mountPath: /data
125+
- name: proxy-config
126+
mountPath: /config
127+
- name: my-app
128+
properties:
129+
image: mcr.microsoft.com/azuredocs/aci-helloworld
130+
ports:
131+
- port: 5000
132+
protocol: TCP
133+
environmentVariables:
134+
- name: PORT
135+
value: 5000
136+
resources:
137+
requests:
138+
memoryInGB: 1.0
139+
cpu: 1.0
140+
limits:
141+
memoryInGB: 1.0
142+
cpu: 1.0
143+
ipAddress:
144+
ports:
145+
- protocol: TCP
146+
port: 80
147+
- protocol: TCP
148+
port: 443
149+
type: Public
150+
dnsNameLabel: my-app
151+
osType: Linux
152+
volumes:
153+
- name: proxy-caddyfile
154+
azureFile:
155+
shareName: proxy-caddyfile
156+
storageAccountName: "<storage-account>"
157+
storageAccountKey: "<account-key>"
158+
- name: proxy-data
159+
azureFile:
160+
shareName: proxy-data
161+
storageAccountName: "<storage-account>"
162+
storageAccountKey: "<account-key>"
163+
- name: proxy-config
164+
azureFile:
165+
shareName: proxy-config
166+
storageAccountName: "<storage-account>"
167+
storageAccountKey: "<account-key>"
168+
```
169+
170+
### Deploy the container group
171+
172+
Create a resource group with the [az group create](/cli/azure/group#az-group-create) command:
173+
174+
```azurecli
175+
az group create --name <resource-group> --location westeurope
176+
```
177+
178+
Deploy the container group with the [az container create](/cli/azure/container#az-container-create) command, passing the YAML file as an argument.
179+
180+
```azurecli
181+
az container create --resource-group <resource-group> --file ci-my-app.yaml
182+
```
183+
184+
### View the deployment state
185+
186+
To view the state of the deployment, use the following [az container show](/cli/azure/container#az-container-show) command:
187+
188+
```azurecli
189+
az container show --resource-group <resource-group> --name ci-my-app --output table
190+
```
191+
192+
### Verify TLS connection
193+
194+
Before verifying if everything went well, give the container group some time to fully start and for Caddy to request a certificate.
195+
196+
#### OpenSSL
197+
198+
We can use the `s_client` subcommand of OpenSSL for that purpose.
199+
200+
```bash
201+
echo "Q" | openssl s_client -connect my-app.westeurope.azurecontainer.io:443
202+
```
203+
204+
```console
205+
CONNECTED(00000188)
206+
---
207+
Certificate chain
208+
0 s:CN = my-app.westeurope.azurecontainer.io
209+
i:C = US, O = Let's Encrypt, CN = R3
210+
1 s:C = US, O = Let's Encrypt, CN = R3
211+
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
212+
2 s:C = US, O = Internet Security Research Group, CN = ISRG Root X1
213+
i:O = Digital Signature Trust Co., CN = DST Root CA X3
214+
---
215+
Server certificate
216+
-----BEGIN CERTIFICATE-----
217+
MIIEgTCCA2mgAwIBAgISAxxidSnpH4vVuCZk9UNG/pd2MA0GCSqGSIb3DQEBCwUA
218+
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
219+
EwJSMzAeFw0yMzA0MDYxODAzMzNaFw0yMzA3MDUxODAzMzJaMC4xLDAqBgNVBAMT
220+
I215LWFwcC53ZXN0ZXVyb3BlLmF6dXJlY29udGFpbmVyLmlvMFkwEwYHKoZIzj0C
221+
AQYIKoZIzj0DAQcDQgAEaaN/wGyFcimM+1O4WzbFgO6vIlXxXqp9vgmLZHpFrNwV
222+
aO8JbaB7hE+M5EAg34LDY80RyHgY+Ff4vTh2Z96rVqOCAl4wggJaMA4GA1UdDwEB
223+
/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/
224+
BAIwADAdBgNVHQ4EFgQUoL5DP+4PWiyE79hL5o+v8uymHdAwHwYDVR0jBBgwFoAU
225+
FC6zF7dYVsuuUAlA5h+vnYsUwsYwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUFBzAB
226+
hhVodHRwOi8vcjMuby5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9yMy5p
227+
LmxlbmNyLm9yZy8wLgYDVR0RBCcwJYIjbXktYXBwLndlc3RldXJvcGUuYXp1cmVj
228+
b250YWluZXIuaW8wTAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEw
229+
KDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEEBgor
230+
BgEEAdZ5AgQCBIH1BIHyAPAAdgC3Pvsk35xNunXyOcW6WPRsXfxCz3qfNcSeHQmB
231+
Je20mQAAAYdX8+CQAAAEAwBHMEUCIQC9Ztqd3DXoJhOIHBW+P7ketGrKlVA6nPZl
232+
9CiOrn6t8gIgXHcrbBqItemndRMv+UJ3DaBfTkYOqECecOJCgLhSYNUAdgDoPtDa
233+
PvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYdX8+CAAAAEAwBHMEUCIBJ1
234+
24z44vKFUOLCi1a7ymVuWErkmLb/GtysvcxILaj0AiEAr49hyKfen4BbSTwC8Fg4
235+
/LgZnn2F3uHI+9p+ZMO9xTAwDQYJKoZIhvcNAQELBQADggEBACqxa21eiW3JrZwk
236+
FHgpd6SxhUeecrYXxFNva1Y6G//q2qCmGeKK3GK+ZGPqDtcoASH5t5ghV4dIT4WU
237+
auVDLFVywXzR8PT6QUu3W8QxU+W7406twBf23qMIgrF8PIWhStI5mn1uCpeqlnf5
238+
HpRaj2f5/5n19pcCZcrRx94G9qhPYdMzuy4mZRhxXRqrpIsabqX3DC2ld8dszCvD
239+
pkV61iuARgm3MIQz1yL/x5Bn4nywjnhYZA4KFktC0Ti55cPRh1mkzGQAsYQDdWrq
240+
dVav+U9dOLQ4Sq4suaDmzDzApr+hpQSJhwgRN16+tLMyZ6INAU2JWKDxiyDTdOuH
241+
jz456og=
242+
-----END CERTIFICATE-----
243+
subject=CN = my-app.westeurope.azurecontainer.io
244+
245+
issuer=C = US, O = Let's Encrypt, CN = R3
246+
247+
---
248+
No client certificate CA names sent
249+
Peer signing digest: SHA256
250+
Peer signature type: ECDSA
251+
Server Temp Key: X25519, 253 bits
252+
---
253+
SSL handshake has read 4208 bytes and written 401 bytes
254+
Verification error: unable to get local issuer certificate
255+
---
256+
New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256
257+
Server public key is 256 bit
258+
Secure Renegotiation IS NOT supported
259+
Compression: NONE
260+
Expansion: NONE
261+
No ALPN negotiated
262+
Early data was not sent
263+
Verify return code: 20 (unable to get local issuer certificate)
264+
---
265+
---
266+
Post-Handshake New Session Ticket arrived:
267+
SSL-Session:
268+
Protocol : TLSv1.3
269+
Cipher : TLS_AES_128_GCM_SHA256
270+
Session-ID: 85F1A4290F99A0DD28C8CB21EF4269E7016CC5D23485080999A8548057729B24
271+
Session-ID-ctx:
272+
Resumption PSK: 752D438C19A5DBDBF10781F863D5E5D9A8859230968A9EAFFF7BBA86937D004F
273+
PSK identity: None
274+
PSK identity hint: None
275+
SRP username: None
276+
TLS session ticket lifetime hint: 604800 (seconds)
277+
TLS session ticket:
278+
0000 - 2f 25 98 90 9d 46 9b 01-03 78 db bd 4d 64 b3 a6 /%...F...x..Md..
279+
0010 - 52 c0 7a 8a b6 3d b8 4b-c0 d7 fc 04 e8 63 d4 bb R.z..=.K.....c..
280+
0020 - 15 b3 25 b7 be 64 3d 30-2b d7 dc 7a 1a d1 22 63 ..%..d=0+..z.."c
281+
0030 - 42 30 90 65 6b b5 e1 83-a3 6c 76 c8 f6 ae e9 31 B0.ek....lv....1
282+
0040 - 45 91 33 57 8e 9f 4b 6a-2e 2c 9b f9 87 5f 71 1d E.3W..Kj.,..._q.
283+
0050 - 5a 84 59 50 17 31 1f 62-2b 0e 1e e5 70 03 d9 e9 Z.YP.1.b+...p...
284+
0060 - 50 1c 5d 1f a4 3c 8a 0e-f4 c5 7d ce 9e 5c 98 de P.]..<....}..\..
285+
0070 - e5 .
286+
287+
Start Time: 1680808973
288+
Timeout : 7200 (sec)
289+
Verify return code: 20 (unable to get local issuer certificate)
290+
Extended master secret: no
291+
Max Early Data: 0
292+
---
293+
read R BLOCK
294+
```
295+
296+
#### Chrome browser
297+
298+
Navigate to https://my-app.westeurope.azurecontainer.io and verify the certificate by clicking on the padlock next to the URL.
299+
300+
:::image type="content" source="media/container-instances-container-group-automatic-ssl/url-padlock.png" alt-text="Screenshot highlighting the padlock next to the URL that verifies the certificate.":::
301+
302+
To see the certificate details, click on "Connection is secure" followed by "certificate is valid".
303+
304+
:::image type="content" source="media/container-instances-container-group-automatic-ssl/lets-encrypt-certificate.png" alt-text="Screenshot of the certificate issued by Let's Encrypt":::
305+
306+
## Next steps
307+
- [Caddy documentation](https://caddyserver.com/docs/)
308+
- [GitHub aci-helloworld](https://github.com/Azure-Samples/aci-helloworld)
309+
- [YAML reference: Azure Container Instances](container-instances-reference-yaml.md)
310+
- [Secure your codeless REST API with automatic HTTPS using Data API builder and Caddy](https://www.azureblue.io/secure-your-codeless-rest-api-with-automatic-https-using-data-api-builder-and-caddy/)
Loading
86.8 KB
Loading

0 commit comments

Comments
 (0)