TLS certificates
X.509 certificates for TLS are managed by the Traefik instance running on the local node. For more details, see Proxy and certificates in the core documentation.
Web applications
Web applications should not handle TLS certificates directly, as the Traefik HTTP proxy listens on TCP port 443 for HTTPS requests. HTTP backends must be configured only for plaintext HTTP connections.
If the module needs to set up a hostname-based HTTP route, it is possible to instruct Traefik to obtain a TLS certificate for it in a single action.
agent.tasks.run(
agent_id=agent.resolve_agent_id('traefik@node'), # e.g., module/traefik1
action='set-route',
data={
'instance': os.environ['MODULE_ID'],
'url': f'http://127.0.0.1:{os.environ["TCP_PORT"]}',
'host': host_fqdn,
'lets_encrypt': True, # request certificate for host_fqdn
'http2https': True, # redirect http:// URL scheme to https://
},
)
Note that instance
must be a unique route identifier. If the module
uses multiple HTTP routes, add a prefix to MODULE_ID
to distinguish them.
TLS for other applications
Applications that require .pem
, .key
, .crt
, or similar files for
their configuration must be granted Traefik’s fulladm
role to run
privileged actions. Add the following authorization request to your app’s
image labels:
org.nethserver.authorizations = traefik@node:fulladm
A certificate request for Let’s Encrypt can be issued using the
set-certificate
action.
The application can then listen for the certificate-changed
event. This
event is triggered when a TLS certificate is uploaded via the UI or
obtained/renewed from Let’s Encrypt.
Upon receiving a certificate-changed
event, invoke the get-certificate
action to obtain the PEM-encoded key and certificate.
For example, to issue a Let’s Encrypt certificate request for
SERVICE_FQDN
in the background, run:
agent.tasks.run_nowait( # _nowait runs in the background
agent_id=agent.resolve_agent_id('traefik@node'), # e.g., module/traefik1
action='set-certificate',
data={
"fqdn": os.environ["SERVICE_FQDN"],
},
parent='', # Run as a root task
extra={
'title': 'set-certificate',
'isNotificationHidden': False, # Show the task progress in the UI
},
)
If you prefer a synchronous call, run:
response = agent.tasks.run(
agent_id=agent.resolve_agent_id('traefik@node'), # e.g., module/traefik1
action='set-certificate',
data={
"fqdn": os.environ["SERVICE_FQDN"],
"sync_timeout": 60, # default timeout is 30
},
)
# Check the action exit code:
if response['exit_code'] != 0:
# decide what to do if the certificate was not obtained
To handle the event, create an executable script under
$AGENT_INSTALL_DIR/events/certificate-changed
. The event handler:
- Checks if the event is relevant to the running service.
- Restarts the service.
The following Python code implements these steps using the agent
library and the get-certificate
command:
import agent
import json
import sys
event = json.load(sys.stdin)
myname = os.environ["SERVICE_FQDN"]
if not agent.certificate_event_matches(event, myname):
sys.exit(0) # ignore event if not relevant
agent.run_helper('systemctl', '--user', 'try-restart', 'myservice')
It is recommended to run get-certificate
before the service starts or is
fully restarted from the handler, to ensure the correct certificate is
used by the service. There can be different ways to achieve this behavior,
but it is recommended to define a separate Systemd unit, to avoid repeated
get-certificate
invocations if the service enters a crash-loop.
This is an example of get-certificate.service
definition that also
generates a PEM file with both private key and the complete certificate
chain:
[Unit]
Description=Get TLS certificate from Traefik
[Service]
Type=oneshot
EnvironmentFile=%S/state/environment
WorkingDirectory=%S/state
SyslogIdentifier=%N
ExecStart=-mkdir -vp tls-certs
ExecStart=-runagent get-certificate --cert-file=tls-certs/server.pem --key-file=tls-certs/server.key $POSTFIX_HOSTNAME
ExecStart=-bash -c 'cd tls-certs ; umask 177 ; cat server.key server.pem > fullchain.pem ; touch -r server.pem fullchain.pem'
Add the following lines to myservice.service
Systemd unit, to run
get-certificate.service
before the service is started or restarted:
[Unit]
# ...existing lines
Wants=get-certificate.service
After=get-certificate.service
To avoid a full service restart when the certificate changes, the
myservice.service
unit can provide a reload operation. In this case,
modify the certificate-changed
event handler as follows.
- Checks if the event is relevant to the running service.
- Obtains the certificate and its private key for the service FQDN.
- If the certificate was changed, reloads the running service.
import agent
import json
import sys
event = json.load(sys.stdin)
myname = os.environ["SERVICE_FQDN"]
if not agent.certificate_event_matches(event, myname):
sys.exit(0) # ignore event if not relevant
# Obtain the changed certificate and its private key
get_proc = agent.run_helper('get-certificate', '--cert-file=service.pem', '--key-file=service.key', myname)
# Reload the running service if the certificate differs
if get_proc.returncode == 0:
agent.run_helper('systemctl', '--user', 'reload', 'myservice')