- Overview
- Transport Layer Security (TLS)
- Role-Based Access Control (RBAC)
- Credential Storage
- Rate Limiting
- Audit Logging
- Unauthenticated Endpoints
- Security Hardening Checklist
- Related Documentation
Security in AxonOps Schema Registry spans authentication, authorization, transport encryption, rate limiting, and audit logging. All security features are optional and can be enabled independently. When no security configuration is present, the registry operates in open mode with all endpoints accessible without credentials.
For detailed coverage of authentication methods (Basic Auth, API keys, LDAP, OIDC, JWT), user management, and the admin CLI, see the Authentication guide. mTLS is transport-level security (not an auth method) and is documented in Configuration. This document focuses on transport security, access control policies, rate limiting, audit logging, and operational hardening.
Enable HTTPS by providing a certificate and private key pair. The registry supports configurable minimum TLS versions, client certificate authentication (mTLS), and automatic certificate reloading.
security:
tls:
enabled: true
cert_file: /path/to/server.crt
key_file: /path/to/server.key
ca_file: /path/to/ca.crt
min_version: "TLS1.2"
client_auth: verify
auto_reload: true| Field | Description | Default |
|---|---|---|
enabled |
Enable HTTPS | false |
cert_file |
Path to PEM-encoded server certificate | (required when enabled) |
key_file |
Path to PEM-encoded private key | (required when enabled) |
ca_file |
Path to CA certificate for verifying client certificates | "" |
min_version |
Minimum TLS version (TLS1.0, TLS1.1, TLS1.2, TLS1.3) |
TLS1.2 |
client_auth |
Client certificate policy (see below) | none |
auto_reload |
Reload certificates from disk without restart | false |
| Mode | Behavior |
|---|---|
none |
No client certificate requested |
request |
Client certificate requested but not required |
require |
Client certificate required but not verified against CA |
verify |
Client certificate required and verified against the CA in ca_file |
Use verify for mutual TLS (mTLS) authentication. This requires a valid ca_file containing the certificate authority that signed the client certificates.
When auto_reload: true, the registry reloads the server certificate and key from disk on each new TLS handshake without requiring a process restart. This supports zero-downtime certificate rotation, which is useful with automated certificate management tools such as cert-manager or ACME clients.
If TLS is terminated at a reverse proxy or load balancer (e.g., NGINX, HAProxy, AWS ALB), disable TLS on the registry itself and configure the proxy to forward requests over plain HTTP. Ensure the proxy sets X-Forwarded-For and X-Real-IP headers so that rate limiting and audit logging record the correct client IP.
The registry uses a fixed set of four built-in roles with hierarchical permissions. Roles cannot be customized, but the super_admins list grants unrestricted access to specific usernames regardless of their assigned role.
| Role | Schema Read | Schema Write | Schema Delete | Config Read | Config Write | Mode Read | Mode Write | Import | User Mgmt |
|---|---|---|---|---|---|---|---|---|---|
super_admin |
Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Full |
admin |
Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Read only |
developer |
Yes | Yes | No | Yes | No | Yes | No | No | No |
readonly |
Yes | No | No | Yes | No | Yes | No | No | No |
Permissions are mapped to API endpoints as follows:
| Permission | Applies to |
|---|---|
schema:read |
GET /subjects/*, GET /schemas/*, POST /compatibility/* |
schema:write |
POST /subjects/*/versions |
schema:delete |
DELETE /subjects/* |
config:read |
GET /config, GET /config/* |
config:write |
PUT /config, DELETE /config, PUT /config/*, DELETE /config/* |
mode:read |
GET /mode, GET /mode/* |
mode:write |
PUT /mode, PUT /mode/* |
import:write |
POST /import/* |
admin:read |
GET /admin/* |
admin:write |
POST/PUT/DELETE /admin/* |
security:
auth:
rbac:
enabled: true
default_role: readonly
super_admins:
- admin
- ops-lead| Field | Description | Default |
|---|---|---|
enabled |
Enable RBAC enforcement | false |
default_role |
Role assigned when an authentication method does not provide one | "" |
super_admins |
Usernames with full access to all operations including user management | [] |
When RBAC is disabled, all authenticated users have unrestricted access. When enabled, users listed in super_admins bypass all permission checks. The default_role is applied to users authenticated via methods that do not inherently assign a role (e.g., config-based basic auth).
User passwords are stored as bcrypt hashes with the default cost factor (10). Plaintext passwords are NEVER written to disk or database. The golang.org/x/crypto/bcrypt package is used for hashing, and the cost factor is the bcrypt.DefaultCost constant (currently 10). Bcrypt is intentionally slow, which limits brute-force attack throughput.
API keys are stored as SHA-256 hashes by default. When the api_key.secret configuration value is set, the registry uses HMAC-SHA256 with that secret as a pepper, providing defense-in-depth: even if the database is compromised, the attacker cannot verify API keys without the secret.
security:
auth:
api_key:
secret: "${API_KEY_SECRET}"Key security properties:
- Hashed at rest -- keys are never stored in plaintext
- Shown once -- the raw key is returned only at creation time and CANNOT be retrieved afterward
- Pepper protection -- when
secretis configured, HMAC-SHA256 is used instead of plain SHA-256 - Cluster consistency -- if
secretis configured, all registry instances must use the same value - Expiration enforced -- key expiration is checked on every request
- Key prefixes -- configurable prefix (e.g.,
sr_live_) helps identify keys in logs and configuration
The secret value SHOULD be at least 32 bytes of cryptographically random data. Load it from an environment variable or secrets manager rather than hardcoding it in the configuration file.
Users and API keys can be stored in HashiCorp Vault separately from schema data by setting storage.auth_type: vault. This allows you to use one backend (e.g., PostgreSQL) for schemas while keeping credentials in Vault's KV secrets engine.
storage:
type: postgresql
auth_type: vault
vault:
address: "https://vault.example.com:8200"
token: "${VAULT_TOKEN}"
mount_path: "secret"
base_path: "schema-registry"See the Configuration guide for the full Vault configuration reference.
The registry implements a token bucket algorithm to protect API endpoints from excessive request volume. Rate limiting is applied as HTTP middleware and operates independently of authentication.
security:
rate_limiting:
enabled: true
requests_per_second: 100
burst_size: 200
per_client: true
per_endpoint: false| Field | Description | Default |
|---|---|---|
enabled |
Enable rate limiting | false |
requests_per_second |
Sustained request rate (token refill rate) | 0 |
burst_size |
Maximum burst capacity (token bucket size) | 0 |
per_client |
Maintain separate rate limits per source IP | false |
per_endpoint |
Maintain separate rate limits per API path | false |
When a request exceeds the rate limit, the registry responds with HTTP 429 Too Many Requests and includes the following headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Configured requests per second |
X-RateLimit-Remaining |
Tokens remaining in the bucket |
Retry-After |
Seconds until the client should retry (set to 1) |
The X-RateLimit-Limit and X-RateLimit-Remaining headers are included on all responses, not only rate-limited ones.
Rate limiting modes are mutually exclusive in order of precedence:
- Per-client (
per_client: true) -- each source IP gets its own token bucket. Client IP is determined fromX-Forwarded-For,X-Real-IP, orRemoteAddrin that order. - Per-endpoint (
per_endpoint: true) -- each combination of HTTP method and path gets its own bucket (e.g.,GET:/subjectsis separate fromPOST:/subjects/test/versions). - Global (both false) -- a single shared bucket applies to all requests.
The health check (GET /) and Prometheus metrics (GET /metrics) endpoints are served before the rate limiting middleware is applied and are never rate-limited.
When using per-client rate limiting, the registry provides a CleanupStaleClients method that removes token buckets for clients that have not made a request within a configurable duration. This prevents unbounded memory growth in environments with many transient clients.
The audit logger records security-relevant events to a structured JSON log. Each event captures who performed an action (actor identity, type, role, and authentication method), what was affected (target type and identifier), and whether the action succeeded or failed (outcome and reason codes). Events cover both the REST API and the MCP server.
For the complete audit logging reference — including the full event schema, all field descriptions, event types, actor types, authentication methods, reason codes, change integrity hashes, and example payloads — see the Audit Logging Guide.
security:
audit:
enabled: true
log_file: /var/log/axonops-schema-registry/audit.log
events: [] # empty = all security-relevant events (recommended)
include_body: false # truncated request body in audit entries{
"timestamp": "2026-03-11T14:30:00Z",
"duration_ms": 42,
"event_type": "schema_register",
"outcome": "success",
"actor_id": "jane",
"actor_type": "user",
"role": "developer",
"auth_method": "basic",
"target_type": "subject",
"target_id": "payments-value",
"schema_id": 42,
"version": 3,
"schema_type": "AVRO",
"before_hash": "sha256:8b7f...",
"after_hash": "sha256:2d91...",
"source_ip": "172.18.0.1",
"user_agent": "curl/8.1",
"method": "POST",
"path": "/subjects/payments-value/versions",
"status_code": 200,
"request_id": "localhost/abc-123-def"
}See the Audit Logging Guide for the complete field reference and more examples.
The following endpoints are always accessible without credentials, regardless of authentication configuration:
| Endpoint | Purpose |
|---|---|
GET / |
Health check |
GET /metrics |
Prometheus metrics |
When docs_enabled: true in the server configuration:
| Endpoint | Purpose |
|---|---|
GET /docs |
Swagger UI |
GET /openapi.yaml |
OpenAPI specification |
These endpoints are registered outside the authentication middleware chain and are also exempt from rate limiting.
The MCP server has its own security controls, independent from the REST API. For full details, see the MCP Guide.
- Bearer token authentication: Set
mcp.auth_tokento requireAuthorization: Bearer <token>on all MCP requests - Permission scopes: 14 scopes with 5 named presets (
readonly,developer,operator,admin,full) control which tools are visible - Read-only mode:
mcp.read_only: truehides all write/delete tools - Two-phase confirmations:
mcp.require_confirmations: truerequires dry-run preview before destructive operations - Origin validation:
mcp.allowed_originsvalidates theOriginheader per the MCP specification - Tool policy: Fine-grained allow_list/deny_list for individual tools
- Localhost binding: The MCP server binds to
127.0.0.1by default, not exposed to the network
- Enable TLS with a minimum version of TLS 1.2 (
min_version: "TLS1.2"), or terminate TLS at a load balancer and restrict the registry to private network traffic. - Enable authentication with at least one method (
basic,api_key,jwt, oroidc). - Enable RBAC with a restrictive
default_role(e.g.,readonly) and explicitly assign higher-privilege roles only where needed. - Use API keys with expiration for programmatic access. Rotate keys regularly via the admin API or CLI.
- Configure the API key HMAC secret (
api_key.secret) on all registry instances and store it in an environment variable or secrets manager. - Enable rate limiting to prevent abuse and reduce the impact of credential brute-force attempts.
- Enable audit logging and forward logs to a centralized system for monitoring and alerting.
- Use environment variables or Vault for secrets -- never hardcode passwords, API key secrets, or Vault tokens in configuration files. The registry supports
${ENV_VAR}substitution in YAML configuration. - Run as a non-root user -- the Docker image runs as UID/GID 1000 (
schemaregistryuser) by default. - Configure CORS appropriately if the registry serves browser-based clients.
- Restrict network access -- bind the registry to an internal interface or use firewall rules to limit access to trusted networks.
- Set
client_auth: verifywhen using mTLS to ensure client certificates are validated against your CA. - Review super_admins list regularly -- users in this list bypass all RBAC checks.
- Authentication -- authentication methods, user management, API key lifecycle, admin CLI
- Configuration -- full configuration reference including all security options
- Deployment -- production deployment guidance
- MCP Server -- MCP server security, permission scopes, tool policies