Skip to content

Add nginx basic auth

In this tutorial, you'll protect an /admin/ path on the site container with HTTP basic auth. The password file is an encrypted secret in the repository, which syslet syncs to podman and podman mounts into the container.

Prerequisites

1. Create a secret

Create creds-site.enc.yaml with the password file as its only key:

creds-site.enc.yaml
htpasswd: admin:{PLAIN}hunter2

The file must be a flat mapping of string values, and key names may only contain a-z, 0-9 and -. Each key becomes one podman secret named <spec name>-<key>, here site-htpasswd.

Encrypt it in place before you do anything else with it:

Bash
sops -e -i creds-site.enc.yaml

The key stays readable and the value is replaced by ciphertext, followed by the sops metadata with one entry per recipient:

creds-site.enc.yaml
htpasswd: ENC[AES256_GCM,data:Qj4Ka7Spe9tZ0cVAM6b7fkR+cIa2qiaewwY=,iv:...,tag:...,type:str]
sops:
    age:
        - enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            ...
          recipient: age14msfdsrwxl32yr7xj6g9f7q2l7m89ynfq5tt4nv3lrftm0wu3d6sfufjnv
        - enc: |
            ...
          recipient: age1jv7kev8rtaxsay2x89wrmahdx9csqprpx2sqer3qud9vkt0jashszyxed5
    ...

From now on, sops edit creds-site.enc.yaml opens the file decrypted in your editor and re-encrypts it on save.

The CUE config from the previous tutorial already turns this file into the secret spec site.

2. Use the secret in a container

Reference the secret from the container's Secret option in web01.cue, and protect /admin/ in the nginx config:

web01.cue
package syslet

import syslettools "github.com/xchangeee/syslet/schema/tools@v0"

sysdef: containers: site: spec: {
    unit: Container: {
        Image: "docker.io/library/nginx:1.27"
        PublishPort: ["8080:80"]
        Secret: {
            "site-htpasswd": "uid=101,gid=101,mode=0400"
        }
    }
    configFiles: [{
        mountPath: "/etc/nginx/conf.d/default.conf"
        mode:      "0644"
        content: """
            server {
                listen 80;
                root /usr/share/nginx/html;
                location /healthz { return 200 "ok"; }
                location /admin/ {
                    auth_basic "admin";
                    auth_basic_user_file /run/secrets/site-htpasswd;
                }
            }
            """
    }]
}

sysdef: (syslettools.#SysdefLock & {in: containers: ["site"]}).out

Each entry maps a podman secret name to the options of quadlet's Secret=, and syslet renders it as Secret=site-htpasswd,uid=101,gid=101,mode=0400. The reference is a plain config string: the container spec only names the podman secret and never contains its value. podman mounts the secret as a file at /run/secrets/site-htpasswd, readable only by nginx's worker user.

syslet checks every Secret= reference against the keys of the secret specs before decrypting anything, so a typo in the name fails the plan.

Preview it:

Bash
cue cmd plan

The plan lists the unit and config changes, the podman secrets syslet will create, and a restart of site.container:

Text Only
Unit file changes:

--- site.container
added:
  [Container] Secret=site-htpasswd,uid=101,gid=101,mode=0400

Config file changes:
...

Secret changes:
  site:
    + htpasswd=(secret)

...

Summary:
UNIT                                     STATUS     CHANGES
site.container                           updated    unit updated, config updated, secret updated, restarted (desired: running)

The plan hides the value. Run the diff with SYSLET_SHOW_SECRETS=1 in front of syslet to check it.

Apply and commit:

Bash
cue cmd apply
git add -A && git commit -m "add site admin auth"

Check that /admin/ now asks for credentials:

Bash
curl -i http://web01.example.com:8080/admin/
curl -i -u admin:hunter2 http://web01.example.com:8080/admin/

The first request returns 401 Unauthorized; the second gets past the auth check and returns 404, since there is no page there yet.

3. Rotate the secret

Change the password:

Bash
sops edit creds-site.enc.yaml
cue cmd apply

The summary shows secret updated for site.container: syslet updated the podman secret and restarted the container, so nginx now reads the new password.

The host's secrets now live in the repository alongside its config, and only you and syslet on web01 can decrypt them.

Continue with Set up GitOps with webhookd.