Authentication Configuration
Overview
SiteRM 1.6.0 introduces a fully replaced authentication system. All API calls — from human users via the Web UI, from Agents and Debuggers, and from external tools — use JWT Bearer tokens obtained through one of three authentication flows:
| Method | Who uses it | Endpoint |
|---|---|---|
| Username/password | Human users, Web UI | POST /auth/login |
| X.509 certificate M2M | Agents, Debuggers, automated services | POST /m2m/token → POST /m2m/token/{id} |
| Token refresh | Any authenticated session | POST /m2m/token/refresh |
Tokens are signed with RS256 (RSA 3072-bit, SHA-256). The signing key pair is auto-generated at Frontend startup. Standard OIDC discovery endpoints allow third-party tools to verify tokens:
GET /.well-known/jwks.json— JSON Web Key SetGET /.well-known/openid-configuration— OpenID Provider Metadata
Token Lifetime
| Token | Default lifetime | Configurable via |
|---|---|---|
| Access token (Bearer) | 60 minutes | OIDC_TOKEN_LIFETIME_MINUTES |
| Refresh token | 12 hours | REFRESH_TOKEN_TTL_HOURS |
Tokens are renewed automatically by Agents using the refresh flow. If a refresh token expires, the Agent re-authenticates using the X.509 certificate challenge.
User Management (siterm-usertool)
Human users and Web UI logins require a local account in the Frontend database. Accounts are managed with siterm-usertool inside the Frontend container.
Create a user
docker exec -it siterm-fe bash
siterm-usertool --action create --username admin --permissions admin
# Enter password when prompted (minimum 12 characters)
For Kubernetes:
kubectl exec -n sense <siterm-fe-pod> -- siterm-usertool --action create --username admin --permissions admin
Permission levels
| Level | Value | Access |
|---|---|---|
read |
1 | Read-only API access |
write |
2 | Write-only API access |
readwrite |
3 | Full read/write API access |
admin |
4 | Full access including user management |
Other user management commands
# List all users
siterm-usertool --action list
# Disable a user (blocks login, does not delete)
siterm-usertool --action disable --username someuser
# Re-enable a disabled user
siterm-usertool --action enable --username someuser
# Change a user's password
siterm-usertool --action setpassword --username someuser
# Delete a user permanently
siterm-usertool --action delete --username someuser
Password policy
- Minimum 12 characters
- Stored with Argon2id hashing (time cost: 3, memory: 64 MB, parallelism: 4)
- Hash parameters are configurable via environment variables (see below)
Username/Password Login Flow
Used by the Web UI and any human operator using the REST API directly.
# Login — returns access_token and refresh_token
curl -X POST https://<FE_HOST>/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "your-password"}'
# Response:
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"expires_at": 1742000000,
"refresh_token": "eyJ...",
"session_id": "abc123..."
}
# Use the token
curl -H "Authorization: Bearer eyJ..." https://<FE_HOST>/api/<site>/...
# Check your identity
curl -H "Authorization: Bearer eyJ..." https://<FE_HOST>/auth/whoami
The Web UI handles login automatically — click the Login button in the Swagger UI or navigate to the Frontend Web UI; you will be prompted to enter credentials.
X.509 Certificate M2M Flow
Used by Agents, Debuggers, and any service that authenticates using a host certificate and private key. This is a two-step challenge-response flow:
Step 1 — Submit certificate, receive challenge
curl -X POST https://<FE_HOST>/m2m/token \
-H "Content-Type: application/json" \
-d '{"certificate": "<PEM-encoded cert>", "session_id": "<unique-session-id>"}'
# Response:
{
"challenge": "<base64-encoded-random-bytes>",
"ref_url": "/m2m/token/<challenge-id>"
}
Step 2 — Sign the challenge with the private key, submit signature
import base64, hashlib
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, ec
# Load private key
with open("/etc/grid-security/hostkey.pem", "rb") as f:
private_key = serialization.load_pem_private_key(f.read(), password=None)
challenge_bytes = base64.b64decode(challenge)
# RSA key:
signature = private_key.sign(challenge_bytes, padding.PSS(
mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256())
# EC key:
# signature = private_key.sign(challenge_bytes, ec.ECDSA(hashes.SHA256()))
sig_b64 = base64.b64encode(signature).decode()
curl -X POST https://<FE_HOST>/m2m/token/<challenge-id> \
-H "Content-Type: application/json" \
-d '{"signature": "<base64-signature>", "session_id": "<same-session-id>"}'
# Response: same format as /auth/login — access_token + refresh_token
The Agent and Debugger perform this flow automatically using SiteRMLibs/HTTPLibrary.py. No manual configuration is needed beyond providing the host certificate and private key as usual.
Token Refresh Flow
Refresh tokens are valid for 12 hours. On each refresh, a new refresh token is issued (rotation) — the old one is revoked. Refresh tokens are bound to the client IP address: only the same IP that obtained the token can refresh it.
curl -X POST https://<FE_HOST>/m2m/token/refresh \
-H "Content-Type: application/json" \
-d '{"session_id": "<session-id>", "refresh_token": "<refresh-token>"}'
# Response: new access_token + new refresh_token (old refresh_token is revoked)
If the refresh token is expired or revoked, the Agent falls back to a full certificate challenge automatically.
Trust Store — Which CAs are Accepted
The Frontend validates X.509 certificates against a filtered trust store built from /etc/grid-security/certificates. By default, only certificates from the following CA issuers are accepted:
- Let’s Encrypt (
R*series) - InCommon RSA CA
- CERN Grid CAs
Customizing the trusted CAs
Create /etc/grid-security/allowed_truststore.yaml inside the Frontend container (or mount it via Docker volume / Kubernetes ConfigMap):
allowed_issuers:
- "Let's Encrypt"
- "InCommon RSA Server CA"
- "CERN Grid Certification Authority"
- "GEANT TCS" # Add your site's CA here
The truststore is rebuilt on each Frontend startup. Certificates from CAs not in this list will be rejected with a 403 error even if technically valid.
To check which CAs are currently trusted, inspect the container:
docker exec -it siterm-fe ls /etc/grid-security/truststore/
JWT Key Pair — Auto-generation and Rotation
The Frontend auto-generates an RSA-3072 key pair at startup if one does not already exist:
- Private key:
/opt/siterm/jwt_secrets/private_key.pem(mode 0600, owned by Apache) - Public key:
/opt/siterm/jwt_secrets/public_key.pem(mode 0644)
Key rotation: To rotate keys without a service interruption, place the new key as the active key and move the old key to the _prev paths:
/opt/siterm/jwt_secrets/private_key_prev.pem/opt/siterm/jwt_secrets/public_key_prev.pem
The Frontend validates tokens against both the current and previous key pair, so in-flight tokens remain valid during rotation.
To force key regeneration, delete the key files and restart the Frontend.
Configuring key size and location
| Environment variable | Default | Description |
|---|---|---|
RSA_DIR |
/opt/siterm/jwt_secrets |
Directory for JWT key files |
RSA_KEY_SIZE |
3072 |
RSA key size in bits |
RSA_PUBLIC_EXPONENT |
65537 |
RSA public exponent |
OIDC Configuration Parameters
These environment variables control token behavior. Set them in your Docker run.sh or Helm values.yaml:
| Variable | Default | Description |
|---|---|---|
OIDC_TOKEN_LIFETIME_MINUTES |
60 |
Access token lifetime in minutes |
REFRESH_TOKEN_TTL_HOURS |
12 |
Refresh token lifetime in hours |
OIDC_LEEWAY |
60 |
Clock skew tolerance in seconds |
OIDC_ALGORITHM |
RS256 |
JWT signing algorithm |
OIDC_APP_NAME |
SITERM Token Issuer |
Issuer name in token metadata |
OIDC_CA_DIR |
/etc/grid-security/truststore/ |
Trusted CA directory for X.509 M2M |
Argon2 password hashing parameters
| Variable | Default | Description |
|---|---|---|
ARGON2_TIME_COST |
3 |
Number of iterations |
ARGON2_MEMORY_COST |
65536 |
Memory in KiB (64 MB) |
ARGON2_PARALLELISM |
4 |
Parallel threads |
ARGON2_HASH_LEN |
32 |
Output hash length in bytes |
Rate Limiting
Auth endpoints are rate-limited to prevent brute-force attacks:
- Default: 1 request per second per client IP
- On receiving a
429 Too Many Requests, clients wait a random 10–30 seconds before retrying - This is handled automatically by the Agent’s HTTP library
Database Tables
Two new tables are created automatically by Alembic migration on Frontend startup:
users — local user accounts:
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
username |
String(128) | Unique login name |
password_hash |
Text | Argon2id hash |
display_name |
String(255) | Optional display name |
permissions |
Integer | 1=read, 2=write, 3=readwrite, 4=admin |
disabled |
Boolean | Account disabled flag |
created_at |
Integer | Unix timestamp |
updated_at |
Integer | Unix timestamp |
password_changed_at |
Integer | Unix timestamp |
refresh_tokens — active refresh token sessions:
| Column | Type | Description |
|---|---|---|
id |
Integer | Auto-increment primary key |
username |
String | Owning user |
client_ip |
String(64) | IP address that obtained the token |
token_hash |
String(64) | SHA-256 hash of the refresh token |
session_id |
String(64) | Session identifier |
expires_at |
Integer | Unix expiry timestamp |
permissions |
Integer | Permission level at issue time |
revoked |
Boolean | True if rotated or logged out |
rotated_from |
String(64) | Hash of the previous token (audit trail) |
Agent and Debugger Authentication
Agents and Debuggers authenticate to the Frontend automatically using the M2M certificate challenge flow. The host certificate and private key are used to sign challenges — the same files that were used for direct certificate auth in previous releases.
No changes are needed to Agent/Debugger configuration. The certificate paths remain the same:
- Certificate:
agent/conf/etc/grid-security/hostcert.pem - Private key:
agent/conf/etc/grid-security/hostkey.pem
The Agent’s HTTPLibrary.py handles the full flow:
- Checks if the current Bearer token is valid (expires within 2 minutes → refresh)
- If refresh token is available: posts session ID + refresh token to
/m2m/token/refresh - If refresh fails or no token: performs full X.509 challenge-response against
/m2m/token - Stores the new Bearer token and refresh token for subsequent calls
Note: Agent and Debugger containers no longer require host certificates for the purpose of API authentication alone. However, if your site uses certificates for other purposes (TLS, SNMP), keep them mounted as before.
Web UI Login
The Frontend Web UI uses token-based authentication for all API calls starting in 1.6.0.
- Navigate to
https://<FE_HOST>/ - Click Login in the top navigation
- Enter your username and password (created with
siterm-usertool) - The Web UI stores the Bearer token in the browser session and sends it with all API requests
- The Swagger UI (
https://<FE_HOST>/docs) also shows a Authorize button — click it and enterBearer <token>to authorize API calls from the Swagger interface
Tokens expire after 60 minutes. The Web UI will prompt you to log in again when the token expires.
Upgrading from 1.5.63
- Create at least one admin user before restarting the Frontend. Pull the new image, enter the container interactively (do not start services yet), and run:
siterm-usertool --action create --username admin --permissions admin -
Start the new Frontend. Alembic migrations run automatically and create the
usersandrefresh_tokenstables. - Verify auth is working:
curl -X POST https://<FE_HOST>/auth/login \ -d '{"username":"admin","password":"<your-password"}' \ -H "Content-Type: application/json" # Should return access_token -
No changes needed for Agents/Debuggers — they re-authenticate automatically using the M2M flow on next restart.
- Review your trust store if your site uses custom CAs for X.509 M2M. See the Trust Store section above.
Troubleshooting
| Symptom | Likely cause | Resolution |
|---|---|---|
401 Unauthorized on all API calls |
Token missing or expired | Log in again with siterm-usertool or re-issue credentials |
403 Forbidden on M2M auth |
Certificate CA not in trust store | Add CA to allowed_truststore.yaml and restart Frontend |
429 Too Many Requests on auth |
Rate limit hit | Clients back off automatically; reduce request frequency |
users table not found |
Alembic migration did not run | Check Frontend startup logs; ensure DB is accessible |
| Agent fails with auth error | Certificate or key path wrong | Verify hostcert.pem and hostkey.pem are mounted and readable |
| Web UI redirects to login repeatedly | Token lifetime too short | Increase OIDC_TOKEN_LIFETIME_MINUTES or check clock skew |
| Key pair not generated | RSA_DIR not writable by Apache |
Check directory permissions; must be owned by the Apache user |
See Also
- Enable Switch Control — Ansible inventory and SSH key configuration
- Frontend Configuration — Full
siterm.yamland environment variable reference - Release Notes — 1.6.0 upgrade instructions