Skip to content

Releases: SUNET/inmor

Release 0.3.0

27 Feb 08:50
v0.3.0
0dcfe57

Choose a tag to compare

0.3.0 — 2026-02-27

Added

  • POST /subordinates/fetch-config API endpoint to fetch and
    self-validate OpenID Federation entity configurations

  • Handle network errors gracefully (DNS failures, timeouts, 404s,
    invalid JWTs) with user-friendly error messages

  • Frontend for the Admins

  • JSON editor for all JSON fields

  • API_KEYS for API access #122.

  • MFA support #123

  • Management commands for Trust Mark Types and Subordinates

  • Enforce kid header in Trust Anchor JWT verification #150

  • Migrate Subordinate metadata/jwks fields from CharField to JSONField #158

  • Refetch metadata button to the frontend #164

  • renewal API and frontend update #166

  • Adds /collection endpoint back #170

  • Management command for subordinate renewal #174

  • apikey management command #178

  • production docker compose #137

  • granian to run admin django application #61

Fixed

  • Explicitly reject alg: none in JWT verification #151

  • /trust_mark_list returns JSON error responses instead of plain text #148

  • /fetch returns 400 instead of 500 on invalid entity configuration #147

  • /list with trust_marked=false no longer incorrectly filters subordinates #146

  • Authenticated API calls pass credentials correctly #152

  • Minimal exp claim handling in trust mark JWTs #145

  • don't include null metadata in policy document

  • pass correct policy object to apply_policy and upgrade oidfpolicy to 0.2.0 #185

  • /list endpoint fix #187

Upgrade Plan: Inmor 0.2.0 → 0.3.0 (Production)

This document covers upgrading a production Inmor deployment from 0.2.0 to 0.3.0.
Frontend is not covered — this plan assumes you are not running the frontend service.


1. Backup

1.1 PostgreSQL

docker compose exec db pg_dump -U postgres postgres > backup_0.2.0.sql

1.2 Redis

docker compose exec redis redis-cli save
docker cp <redis_container>:/data/dump.rdb ./backup_0.2.0.rdb

1.3 Configuration files

cp docker-compose.yml docker-compose.yml.bak
cp taconfig.toml taconfig.toml.bak

1.4 Verify backups

# Check SQL dump is non-empty
ls -lh backup_0.2.0.sql

# Check Redis dump
ls -lh backup_0.2.0.rdb

2. Update taconfig.toml

Two changes from 0.2.0:

# Was: domain = "http://localhost:8080"
domain = "https://your-domain:8080"
redis_uri = "redis://redis:6379"
tls_cert = "tls.pem"         # must match the container path in docker-compose.yml
tls_key = "tls-key.pem"      # must match the container path in docker-compose.yml
# New in 0.3.0 — set to false for production (default)
allow_http = false
  • The domain scheme changed from http to https.
  • allow_http = false is new and enables SSRF protection (blocks private IPs and
    non-HTTPS federation requests).

3. Generate secrets

# MFA encryption key (required even if you don't use MFA yet — the app expects it)
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

# Django SECRET_KEY (if you don't already have one in localsettings.py)
python3 -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

# Pick a PostgreSQL password

4. Create or update localsettings.py

This file is now mounted into the container (no longer optional). Create it at the
repo root:

SECRET_KEY = "your-generated-secret-key"
DEBUG = False
ALLOWED_HOSTS = ["your-domain.example.com", "admin"]

# HTTPS security
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# Reverse proxy settings — REQUIRED when running behind nginx/Caddy/Apache.
# The proxy terminates TLS and forwards plain HTTP to Django. Without these
# settings Django sees http:// requests and issues 301 redirects, which
# breaks POST requests.
SECURE_SSL_REDIRECT = False                                   # proxy handles HTTPS redirect
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") # trust proxy header

# CORS — only needed if something external hits the admin API
CORS_ALLOWED_ORIGINS = ["https://your-domain.example.com"]
CSRF_TRUSTED_ORIGINS = ["https://your-domain.example.com"]

# MFA encryption key
MFA_ENCRYPTION_KEY = "your-generated-fernet-key"

# If you switched to password auth for PostgreSQL:
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "postgres",
        "USER": "postgres",
        "PASSWORD": "your-postgres-password",
        "HOST": "db",
        "PORT": 5432,
    }
}

5. Replace docker-compose.yml

The new production compose file has significant changes from 0.2.0:

Area 0.2.0 0.3.0
TA image inmor:0.2.2 inmor:0.3.0
Admin image inmor-admin:0.2.2 inmor-admin:0.3.0
TA binary debug build release build
Admin WSGI Django dev server granian (production)
TA healthcheck CMD true (no-op) Actual curl to /health
DB volumes bind mount ./db named volume postgres_data
Redis volumes bind mount ./redis named volume redis_data
DB ports 5432:5432 exposed not exposed
Admin ports 8000:8000 exposed not exposed (reverse proxy)
DB auth POSTGRES_HOST_AUTH_METHOD=trust POSTGRES_PASSWORD (recommended)
TLS mounts ./localhost+2.pem /etc/ssl/certs/inmor.pem (your cert path)
Volume flags none :ro on all config/key mounts
Admin volumes source bind mounts baked into image; only localsettings.py, private.json, publickeys/, historical_keys/ mounted
New env vars PRODUCTION=true, RUST_LOG=info
Templates not mounted mounted as :ro on TA

After copying the new docker-compose.yml from the repo, edit it:

  • Set your TLS cert paths (the TA service volume mounts).
  • Set POSTGRES_PASSWORD.
  • Uncomment MFA_ENCRYPTION_KEY and set it.

If you are currently using bind-mount ./db and ./redis: The new compose uses
named volumes. You can either migrate the data to named volumes, or keep using bind
mounts by changing postgres_data:/var/lib/postgresql/data back to
./db:/var/lib/postgresql/data (and similarly for Redis).


6. Database migration plan

The entrypoint runs these automatically on startup, but here is what happens:

Step 1 — pre_migrate_check (new command): Converts any empty strings in the
metadata and forced_metadata CharField columns to valid JSON ({}) before the
schema migration runs. This prevents the CharField → JSONField migration from
failing.

Step 2 — migrate applies these new migrations:

Migration What it does
entities.0003_subordinate_metadata_jsonfields Converts metadata and forced_metadata from CharField to JSONField
apikeys.0001_initial Creates APIKey table
apikeys.0002_add_tenant_field Adds tenant column to APIKey
auditlog.0001_initial Creates AuditLogEntry table with 4 indexes
django-allauth migrations Creates allauth account_* and mfa_* tables
django.contrib.sites Creates django_site table (required by allauth, SITE_ID = 1)

Step 3 — collectstatic (new): Collects static files for whitenoise serving.

Step 4 — reload_issued_tms (unchanged): Reloads trust marks into Redis.


7. New Django apps and middleware

INSTALLED_APPS gained:

  • apikeys.apps.ApikeysConfig
  • auditlog.apps.AuditlogConfig
  • django.contrib.sites
  • django.contrib.humanize
  • corsheaders
  • allauth, allauth.account, allauth.mfa

New middleware (order matters):

  • whitenoise.middleware.WhiteNoiseMiddleware — after SecurityMiddleware
  • corsheaders.middleware.CorsMiddleware — after WhiteNoiseMiddleware
  • allauth.account.middleware.AccountMiddleware — after AuthenticationMiddleware

URL config adds: path("accounts/", include("allauth.urls"))

These are all baked into the Docker image — no action required unless you have a
custom localsettings.py that overrides INSTALLED_APPS or MIDDLEWARE.


8. New Python dependencies

Added to the admin image:

Package Purpose
django-allauth Authentication framework + MFA
django-cors-headers CORS headers for API
fido2 WebAuthn support
whitenoise Static file serving in production
qrcode QR codes for TOTP setup
cryptography MFA secret encryption
granian Production WSGI server (replaces Django runserver)

All baked into the Docker image — no manual installation needed.


9. Rust/TA changes

Security hardening (built into the new binary)

  • SSRF protection: Federation outbound requests block private IPs and require HTTPS
    (controlled by allow_http in taconfig.toml).
  • HTTP timeouts: 5s connect, 10s total, 2MB response limit, 5 redirect hops max.
  • Recursion depth limits: Tree walking capped to prevent DoS.
  • Redis key sanitization: Validates all user input used in Redis key construction.
  • kid header enforcement: Rejects JWTs without kid or with alg: none.
  • JSON error responses: /trust_mark_list and /fetch now return JSON errors
    instead of plain text or 500s.

New features

  • /health endpoint: Verifies Redis connectivity (used by Docker healthcheck).
  • /status endpoint: Detailed operational status (keys, subordinates, trust marks,
    collection counts).
  • /collection endpoint: Entity collection data (populated by inmor-collection
    CLI).
  • inmor-collection binary: New CLI that walks federation trees and populates
    Redis. Run it periodically if you use the /collection end...
Read more

Release 0.2.1

06 Feb 08:57
v0.2.1
abc0668

Choose a tag to compare

This is a bug fix release for v0.2.0,

  • TA bind address: Fixed the TA server bind address when TLS is not configured (#136).
  • Docker: Fixed missing common module in admin container and corrected version tags in docker-compose.yml.

Release 0.2.0

13 Jan 08:23
v0.2.0
3acbcbb

Choose a tag to compare

Inmor 0.2.0 Release Notes

Release Date: 2026-01-13

Breaking Changes

Public Keys Directory (CRITICAL)

The single public.json file has been replaced with a publickeys/ directory that can contain multiple public keys. This affects both the TA and Admin services.

Migration Steps:

  1. Create a publickeys/ directory in your deployment folder
  2. Move your existing public.json into publickeys/ (keep the same filename)
  3. Create an empty historical_keys/ directory for future key rotation support
mkdir -p publickeys historical_keys
mv public.json publickeys/

Settings Changes (Admin)

If you have a custom localsettings.py:

  • SIGNING_PUBLIC_KEY is now SIGNING_PUBLIC_KEYS (a list, auto-populated from publickeys/ directory)
  • Remove metadata from POLICY_DOCUMENT if present - forced metadata is now per-subordinate in the database

Docker Compose Updates

If you wrote your own docker-compose.yml based on 0.1.3, make these changes:

1. Update Image Tags

# Change from:
image: docker.sunet.se/inmor:0.1.3
image: docker.sunet.se/inmor-admin:0.1.3

# To:
image: docker.sunet.se/inmor:0.2.0
image: docker.sunet.se/inmor-admin:0.2.0

2. Update TA Service Volumes

# REMOVE this line:
- ./public.json:/app/public.json

# ADD these lines:
- ./publickeys:/app/publickeys
- ./historical_keys:/app/historical_keys

# OPTIONAL - for TLS support:
- ./your-cert.pem:/app/your-cert.pem
- ./your-key.pem:/app/your-key.pem

3. Update Admin Service Volumes

# REMOVE this line:
- ./public.json:/app/public.json

# ADD these lines:
- ./publickeys:/app/publickeys
- ./historical_keys:/app/historical_keys

Complete Volume Diff

TA service volumes (before → after):

 volumes:
   - ./taconfig.toml:/app/taconfig.toml
   - ./private.json:/app/private.json
-  - ./public.json:/app/public.json
+  - ./publickeys:/app/publickeys
+  - ./historical_keys:/app/historical_keys
+  # Optional TLS certificates:
+  - ./localhost+2.pem:/app/localhost+2.pem
+  - ./localhost+2-key.pem:/app/localhost+2-key.pem

Admin service volumes (before → after):

 volumes:
   - ./admin/private.json:/app/private.json
-  - ./public.json:/app/public.json
+  - ./publickeys:/app/publickeys
+  - ./historical_keys:/app/historical_keys

New Features

  • Historical Keys Endpoint - New /historical_keys endpoint for key rotation. Use add_historical_key.py script to mark keys as expired/revoked.
  • TLS Support - TA can now serve over TLS. Add tls_cert and tls_key to taconfig.toml:
    tls_cert = "your-cert.pem"
    tls_key = "your-key.pem"
  • TrustMarks for TA - The Trust Anchor itself can now have TrustMarks from external issuers
  • Multiple Key Types - Both TA and Admin now support different key algorithms (not just ES256)
  • entity_type Parameter - New parameter in /resolve endpoint
  • trust_mark_type in /list - Filter subordinates by trust mark type
  • Forced Metadata - Apply forced metadata per-subordinate in entity statements

Fixes

  • /trust_mark_status now correctly accepts POST requests per OpenID Federation spec
  • /resolve output contains fixed metadata, with applied forced metadata
  • TA now verifies it is listed in authority_hints before issuing subordinate statements
  • Improved error handling throughout the Rust codebase