Configuration
Overview#
The different configuration files serve different purposes:
Each file has a specific role in the authentication and user management pipeline, working together to provide OIDC-based SSH access.
/etc/motley_cue/motley_cue.conf: Main configuration (WHO can access) (general | authorisation | assurance)/etc/motley_cue/feudal_adapter.conf: Backend user management config (HOW users are created)/etc/motley_cue/flaat.conf: OIDC validation config (WHICH OPs are supported)/etc/nginx/sites-available/nginx.motley_cue: WHERE service is accessible-
/etc/pam.d/: PAM configs Multiple files for optional PAM integration -
/etc/gunicorn/gunicorn.conf.py: Gunicorn server config
motley_cue.conf#
The main configuration file is generally self documenting: it contains examples for typical use. Configuration is structured into several sections as follows.
General config#
[mapper] - Core service settings:
log_level: Logging level (default: WARNING)log_file: Log file location (default: /dev/stderr)enable_docs: Enable Swagger/ReDoc documentation (default: False)docs_url,redoc_url: Documentation endpointsapi_version: API version to use (default: v1)
[mapper.otp] - One-Time Password settings:
This section configures creation and storage of short lived one time passwords (OTPs).
When using PAM authentication in ssh-oidc, the OIDC access token is passed by the ssh-client through the password field. Some ssh-client have a length limitation of 1kb for the password field. Hence, Motley Cue supports the dynamic creation of one time passwords, that clients can use instead of large access tokens:
use_otp: Use OTP instead of tokens as SSH password (default: True)backend: Storage backend - memory, sqlite, sqlitedict (default: memory)db_location: Token database location (default: /tmp/tokenmap.db)keyfile: Encryption key file (default: /tmp/motley_cue.key)
Example:
[mapper.otp]
# Enable OTP for tokens > 1KB
use_otp = True
# Backend for storing OTP-AT mapping
backend = sqlite
# Database location
db_location = /run/motley_cue/tokenmap.db
# Encryption key file
keyfile = /run/motley_cue/motley_cue.key
[privacy] - Privacy policy:
privacy_contact: Service operator email (MUST be configured)privacy_file: Privacy statement location (default: /etc/motley_cue/privacystatement.md)
Authorisation#
Authorisation is configured in one or more [authorisation.*] sections. It contains per-OP authorisation.
Defaults for the authorisation.* sections can be set in the magic
section [DEFAULT]. These options are available and explained below.
op_url: OpenID Provider URL (required)scopes: Required OIDC scopesauthorise_all: Allow all users from this OP (default: False)authorised_users: List of authorized users by ‘sub’ claimauthorised_vos: List of authorized Virtual Organizationsvo_claim: OIDC claim containing VOs (default: eduperson_entitlement)vo_match: How many VOs to match - all, one, or integer (default: one)audience: Audience claim for this service (optional)authorised_admins: Admin users who can suspend/resume accountsauthorise_admins_for_all_ops: Allow admins to manage users from any OP
Note
- The ‘op_url’ field is mandatory for each authorisation section
- Unknown claims in tokens are ignored (won’t cause authorisation failure)
- We use delegated Access Tokens I.e. there is no need to register
a
client_id/client_secretwith a provider. - JWT access tokens are supported natively (issuer is read from the token).
Non-JWT access tokens are also supported: oinit will prompt the user
for the issuer URL in that case. When using
oidc-agentwith a non-JWT token, oidc-agent automatically supplies the issuer.
[DEFAULT] section#
The [DEFAULT] section sets the defaults for all
[authorisation.<provider_name>] sections. These options are supported:
-
scopes- The required scopes from the OP to access the service
default:scopes = ["openid", "profile", "email", "eduperson_entitlement"] -
authorise_all- Authorise all users from trusted OP.
default:authorise_all = False -
authorised_vos- List of VOs whose members are authorised to use the service
default:authorised_vos [] -
vo_claim- the OIDC claim containing the VOs specified above
default:vo_claim = eduperson_entitlement -
vo_match- how many VOs need to be matched from the list, valid options: all, one, or an int
default:vo_match = one -
authorised_users= list of individual users authorised to use the
service specified through OIDC ‘sub’, relative to the section’s OP defaults to empy list if not specified
default:authorised_users = []Info
You can find out your
suband VOs you are a member of by using flaat:where$ pip install flaat $ flaat-userinfo $TOKEN$TOKENcontains an Access Token from the OP you are interested it.A commandline tool to retrieve access tokens is: oidc-agent
-
audience- audience claim specific to this service (OPTIONAL); it can
be a string or a list of strings if empty or not specified, audience checking will not be used for authorisation
Be aware that many OPs do not support this feature, in which case it will be ignored, and not be used for authorisation.
default:audience = "" -
authorised_admins- Admin AuthorisationInfo
Each OP includes authorisation for the
/adminendpoint, which allows suspending and resuming access for individual users.
Only individual admins can be authorised (by ‘sub’ claim). This is meant for infrastructure security contacts. In the future, we plan to support this feature for VO managers too, to suspend members of their own VO (by using VO roles encoded in AARC-G002 entitlements).default:
authorised_admins = []
[authorisation.<provider_name>]#
Each [authorisation.<provider_name>] section is used to configure one OP
(OpenID Provider). You can set the provider_name yourself.
Each [authorisation.<provider_name>] section overrides the
any of the [DEFAULT] options. the op_url must be specified.
op_url- OIDC provider URL (mandatory)
Examples#
Basic example#
Here is an example for how to configure the EGI Checkin OP to authorise any user of
the VO identified by its entitlement
"urn:mace:egi.eu:group:my-group:role=member". In addition, also allow th
user "ea1b8e21-3654-4178-bd04-98c6adf58951" known at the EGI OP.
[authorisation.egi]
# The complete URL for the OIDC provider (MANDATORY)
op_url = https://aai.egi.eu/auth/realms/egi
# Authorise based on Virtual Organisation membership (entitlements)
authorised_vos = ["urn:mace:egi.eu:group:my-group:role=member"]
vo_claim = eduperson_entitlement
# Authorise specific individual users
authorised_users = ["ea1b8e21-3654-4178-bd04-98c6adf58951"]
To treat users who have shown their passport at their Home-IdP differently
from those who have not, use
assurance-based shell tiers. They are
configured in the same [DEFAULT] / [authorisation.*] sections:
assurance_based_shell_tier_full = profile/espresso | profile/cappuccino
assurance_based_shell_tier_restricted = *
Users matching the first expression get the normal shell; everyone else gets the restricted one.
Multiple Identity Providers#
You can configure different authorisation rules for each provider:
[authorisation.kit]
op_url = https://oidc.scc.kit.edu/auth/realms/kit
# Only allow members of KIT
vo_claim = eduperson_scoped_affiliation
authorised_vos = ["member@kit.edu", "student@kit.edu"]
[authorisation.google]
op_url = https://accounts.google.com
# Only allow specific email domains
vo_claim = email
vo_list = [".*@example\.com"]
[authorisation.helmholtz]
op_url = https://login.helmholtz.de/oauth2/
# Require specific entitlements (from the default claim: eduperson_entitlement)
authorised_vos = ["urn:geant:helmholtz.de:group:my-project:role=member"]
Assurance-based shell tiers#
Not every federated identity is equally trustworthy. A user who logged in with a second factor at a well-run Home-IdP is a different proposition from one who self-registered with an email address. Assurance-based shell tiers let you accept both, but give them different login shells.
motley-cue evaluates each user’s assurance and MFA claims against a set of
boolean expressions, arriving at one of three tiers – full, limited or
restricted. The tier is handed to feudalAdapter, which maps it to a shell in
feudal_adapter.conf.
Assurance does not reject anyone
Every user who passes authorisation is deployed. The tier only decides which shell they get. This is deliberate: a user with weak assurance receives an account and an explanatory message rather than an opaque login failure.
This moved out of feudal_adapter.conf
Earlier versions configured a single global [assurance] require
expression in feudal_adapter.conf, which rejected users who did not
match. That mechanism is gone. Assurance is now configured per OP in
motley_cue.conf, because only motley-cue has the per-OP context and sees
the full token. If you have a require = line in your
feudal_adapter.conf, port it to assurance_based_shell_tier_full as
shown below.
Configuration#
The options live in the [authorisation.*] sections of
/etc/motley_cue/motley_cue.conf, with [DEFAULT] supplying the values an OP
does not override. So you can set a policy once globally and adjust it for
individual providers – which is the whole point, since OPs differ widely in
what assurance they can actually assert.
-
assurance_prefix- prefix prepended to relative claim values
default:assurance_prefix = https://refeds.org/assurance/ -
assurance_claims- token claims whose values are collected into the assurance set
default:assurance_claims = [assurance, eduperson_assurance, acr] -
assurance_based_shell_tier_full- expression for thefulltier
default: empty (never matches) -
assurance_based_shell_tier_limited- expression for thelimitedtier
default: empty (never matches) -
assurance_based_shell_tier_restricted- expression for therestrictedtier
default: empty (never matches) -
assurance_based_shell_default_tier- tier used when no expression matches. Left unset it is decided per OP:fullfor an OP with no tier expressions at all, the least privileged tier (restricted) for an OP that does have a policy. Set it explicitly to override in either direction.
default: empty, i.e. decided automatically -
assurance_based_shell_max_tier- caps the resulting tier; it can only ever lower a tier, never raise one. Most useful per OP.
default: empty, i.e. no cap
The feature is opt-in, and enabling it is per OP
With the defaults above, all three tier expressions are empty, so nothing
matches and every user resolves to full. feudalAdapter then uses its
normal shell. In other words: if you configure nothing, behaviour is
exactly as it was before shell tiers existed. You enable the feature by
writing expressions.
Enabling follows ordinary [DEFAULT] inheritance. A policy in [DEFAULT]
applies to every OP, including one that has no section of its own. A
policy in a single [authorisation.<op>] section applies to that OP
only and leaves the others exactly as they were.
Once an OP has a policy, unmatched users fail closed
For an OP that does have a policy, a user matching none of its
expressions gets the least privileged tier, not full. This matters
because the most likely mistake – an expression that can never match, such
as the MFA prefix trap below – would otherwise
silently grant everyone a full shell. Set
assurance_based_shell_default_tier explicitly if you want something else.
The expression grammar#
E -> E "&" E | E "|" E | "(" E ")" | string
& (AND) binds more strongly than | (OR); use parentheses to override.
This is the same grammar as the old feudal assurance.require, so existing
expressions carry over unchanged.
A bare string matches if the user’s assurance set contains it verbatim,
or contains it prefixed with assurance_prefix. That dual match is what
makes both worlds reachable from one option:
profile/cappuccinomatches the REFEDS URLhttps://refeds.org/assurance/profile/cappuccino1matches anacrclaim whose literal value is1mfamatches anamrvalue ofmfa
A string starting with http:// or https:// is matched verbatim only and
never prefixed – so https://refeds.org/profile/mfa works even though it does
not live under the assurance prefix.
REFEDS MFA is not under the assurance prefix
The REFEDS MFA profile is https://refeds.org/profile/mfa, while the
REFEDS Assurance Framework lives under
https://refeds.org/assurance/. They are different namespaces.
Writing the relative token profile/mfa therefore expands to
https://refeds.org/assurance/profile/mfa, which no provider ever
asserts – the expression silently never matches, and the user falls
through to assurance_based_shell_default_tier (which defaults to
full). Always write MFA as the absolute URL:
# correct
assurance_based_shell_tier_full = https://refeds.org/profile/mfa & profile/cappuccino
# WRONG -- can never match, everyone falls through to the default tier
assurance_based_shell_tier_full = profile/mfa & profile/cappuccino
Alternatively widen assurance_prefix to https://refeds.org/ and write
profile/mfa and assurance/profile/cappuccino.
Two special tokens:
+matches if the user has any assurance claim at all*always matches
An empty expression never matches.
Which claims are inspected#
Each claim named in assurance_claims is looked up in all three token
sources and the results are combined:
- the userinfo endpoint
- the access token body, when it is a JWT
- token introspection
The union of everything found is what the expressions are evaluated
against. This matters: a provider may put the assurance profile in
eduperson_assurance at the userinfo endpoint while the MFA signal rides in
acr inside the access token. An expression requiring both is only satisfiable
if the two sources are merged, which is why motley-cue does not simply take the
first source that carries a claim.
A claim’s value may be a list, a single string, or a space-separated list of
values (as acr often is) – all are handled.
How the tier is chosen#
- The assurance set is collected as described above.
- The tier expressions are tested in order of decreasing privilege:
full, thenlimited, thenrestricted. The first match wins. - If none matches,
assurance_based_shell_default_tieris used – which, left unset, is the least privileged tier for an OP that has a policy, andfullfor one that does not. - Finally
assurance_based_shell_max_tieris applied, if set. It can only lower the result.
Expressions are parsed once at startup, so a syntax error surfaces immediately
as a configuration error naming the OP and the offending option, rather than
failing later on a user’s login. The same applies to
assurance_based_shell_default_tier and assurance_based_shell_max_tier: an
unrecognised tier name is rejected at startup rather than ignored, because
ignoring it would hand the user more privilege than intended.
Still configure a catch-all
Setting assurance_based_shell_tier_restricted = * is recommended even
though unmatched users now fail closed by themselves: it makes the intent
explicit in the config rather than relying on the fallback.
Mapping tiers to shells#
The tier names mean nothing by themselves; feudal_adapter.conf turns them
into actual shells:
[backend.local_unix]
# the "full" tier / default shell
shell = /bin/bash
# the lower tiers
shell_limited = /etc/motley_cue/shells/mc-rbash
shell_restricted = /etc/motley_cue/shells/mc-message
Two shells ship with motley-cue for this purpose:
mc-rbash– a restricted bash permitting only a safe whitelist of commands (ls,id,whoami, …)mc-message– prints an explanatory message and exits
shell_limited and shell_restricted both default to /sbin/nologin, i.e.
they fail closed until you configure them.
LDAP backends
With the ldap backends the login shell is written into the directory, so
the tier shells must exist, be executable, and usually be listed in
/etc/shells on every target system the user logs in to – not just on
the host running motley-cue. Note also that /etc is mounted noexec on
some hosts; install the shells elsewhere in that case, for example under
/usr/lib/motley-cue/shells/, and adjust the paths.
Examples#
A three-tier policy#
Full shell only with MFA and a REFEDS cappuccino profile; a restricted shell for cappuccino without MFA; a message-and-exit shell for everyone else.
[DEFAULT]
assurance_based_shell_tier_full = https://refeds.org/profile/mfa & profile/cappuccino
assurance_based_shell_tier_limited = profile/cappuccino
assurance_based_shell_tier_restricted = *
Capping a weak provider#
Google asserts no REFEDS assurance at all, so its users would be classified by
whatever [DEFAULT] says. Cap the OP so none of them can ever reach the full
shell, without touching the global policy:
[authorisation.google]
op_url = https://accounts.google.com/
scopes = ["openid", "profile"]
assurance_based_shell_max_tier = restricted
An OP that signals MFA in acr#
Some providers express the authentication context as a bare acr value rather
than a REFEDS URL. Match it directly:
[authorisation.example]
op_url = https://op.example.org/
assurance_claims = [acr, amr, eduperson_assurance]
assurance_based_shell_tier_full = 2 | mfa
assurance_based_shell_tier_restricted = *
Migrating from the old assurance.require#
The old feudal_adapter.conf stanza:
[assurance]
prefix = https://refeds.org/assurance/
require = profile/espresso | profile/cappuccino
becomes, in motley_cue.conf:
[DEFAULT]
assurance_prefix = https://refeds.org/assurance/
assurance_based_shell_tier_full = profile/espresso | profile/cappuccino
assurance_based_shell_tier_restricted = *
The expression is unchanged. The difference is the outcome: users who do not
match are no longer rejected, they get the restricted shell. To keep them out
entirely, point shell_restricted at /sbin/nologin.
Unknown providers#
A token from an OP that has no [authorisation.*] section of its own is not
authorised anyway, but should it ever be evaluated, the [DEFAULT] policy
applies. A configured assurance_based_shell_tier_restricted = * catch-all
therefore also covers unknown providers, rather than granting them full.
Troubleshooting#
Set log_level = DEBUG in the [mapper] section of motley_cue.conf. The
evaluation then logs, for every request, each configured claim and the values
found for it per source, followed by the tier that was selected and any cap
that was applied. That tells you directly whether a claim you expected is
missing, or is simply arriving under a name or in a source you have not
configured.
If a user’s claims look right but the shell does not change, check that the OP
is actually being asked for the relevant scopes (scopes in the same
[authorisation.*] section – eduperson_assurance in particular), and that
feudalAdapter is recent enough (see below).
Version requirements
Assurance-based shell tiers need feudalAdapter >= 2.0.0 on the
receiving side; the ldap backends gained tier support in 2.0.1. An
older feudalAdapter silently ignores the tier and gives every user the
default shell.
feudal_adapter.conf#
Feudal andles the backend user management configuration and the just-in-time provisioning of accounts. It is very flexible.
Feudal supports several so called “backends” for the account creation. Also, different strategies for generating local usernames is supported. Existing usernames are always preferred, and are automatically found, when an existing mapping to them exists.
To support use-cases in which an adminstrator needs to approve new users before admins triggers a provisioning flow themselves, a so called “approval flow” is supported.
Provisioning#
feudal_adapter.conf is structured in sections as follows.
[ldf_adapter]#
Core backend settings of the leudal_adapter (also ldf_adapter).
backend: Backend type - local_unix, bwidm, ldap (default: local_unix)backend_supports_preferring_existing_user: Detect and reuse existing usernames (default: False) To be deprecatedprimary_group: Default primary group for usersfallback_group: Group for users without groups (default: nogroup)additional_groups: Extra groups to add users tointeractive: Deprecated; AlwaysFalse
[messages]#
Select which information will be logged
log_file: Log file location (default: /var/log/feudal/feudal.log)log_level: Logging level (default: INFO)log_to_console: Console logging controllog_name_changes: Log username/group name normalization (default: True)log_primary_group_definition: Log primary group selection (default: True)log_username_creation: Log username generation attempts (default: False)
[approval]#
Configure the approval workflow for deploying local accounts. When active, instead of adding the user to a backend, the administrator is notified and asked to create the account manually.
Until the admin approved the account, pending users will be shown a message to return later.
enabled: Require admin approval for accounts (default: False)user_db_location: Pending requests database, sqlite (/var/lib/feudal/pending_users.db)notifier: Notification method (default: email)
[notifier.email]#
Email notification settings
smtp_server: SMTP server hostname (default: localhost)smtp_port: SMTP server port (default: 25)use_ssl: Enable SSL/TLS (default: False)sent_from: Sender email address (default: admin@localhost)sent_from_password: Sender password for authadmin_email: Admin recipient (default: admin@localhost)templates_dir: Email template directory (default: /etc/feudal/templates)
[assurance]#
Moved to motley_cue.conf
Assurance used to be configured here, as a single global require
expression that rejected users who did not match. That mechanism has
been replaced by per-OP
assurance-based shell tiers in
motley_cue.conf, which classify users instead of turning them away.
See migrating from the old assurance.require.
What remains here is the shell each tier maps to, configured per backend –
shell, shell_limited and shell_restricted, see
backend.local_unix.
verified_undeploy: Block undeployment for unauthorized users (default: false)
[username_generator]#
We support several different username creation strategies.
These modes are currently available:
- friendly: Friendly implements a list of strategies that are tried one
after another. The input is based on different claims of the incoming
userinfo object:
- preferred_username claim (if present)
- combine a varying number of letters of given_name + family_name
- email-address if all else fails
-
pooled: Pooled implements the pool-account behaviour known from “the grid”. By default, we use the primary group name and append the digits, in the order of incoming users. In practice, group names may be much longer. Therefore, a
prefixmay be configured. -
mode: Username generation modefriendly: Try preferred_username, then given_name+family_name combinations, then emailpooled: Use group prefix + sequential numbers (grid-style)
pool_prefix: Prefix for pooled accounts (default: primary group name)pool_digits: Number of digits for pooled accounts (default: 3)strip_sub_groups: Remove subgroups from long federated names (default: no) This may be useful in situations where you receive very long group names. (See the [groups] section for a better way.)
[groups]#
Configure how entitlements and groups are being translated into local
unix names, and whether to create them at all. This is probably the most
specific part about feudal.
Warning
If you change the group mapping after the first groups have been
created, new groups according to the new configuration could be
created. Please carefully review your configuration e.g. by using
tools such as feudal-simulator.
policy: Group creation policyall: Create all groups from user attributes (potentially many)listed: Only create groups in supported_entitlements/supported_groups
supported_entitlements: Regex list of allowed entitlement-based groupssupported_groups: Regex list of allowed group namesmethod: Group name mapping methodclassic: Default mapping (pre-feudal-v1 compatibility)regex: Custom regex-based mapping rules
mapping: List of regex replacement rules (for method=regex)
Example: Only allow specific entitlements#
[groups]
policy = listed
supported_entitlements =
urn:mace:egi.eu:group:eosc-synergy.eu.*
urn:mace:egi.eu:group:vo.example.org:role=member
urn:geant:helmholtz.de:group:.*
Example: Only allow specific group names#
[groups]
policy = listed
supported_groups =
users
developers
test-vo-.*
wlcg-.*
Example: Allow any group, but map it#
For this we use regular expressions in the mapping option. This allows
automatically generating all groups that come in, but make sure they are
mapped to short and digestable names.
[groups]
policy = all
method = regex
mapping =
# Remove role information
:role=(owner|member) ->
# Shorten long prefixes
urn:mace:egi.eu:group: -> egi_
urn:geant:helmholtz.de:group: -> helm_
# Replace dots and colons with hyphens
[\.:] -> -
# Specific renamings
eosc-synergy.eu -> synergy
data.kit.edu -> kit
# Shorten admin groups
:admins -> :adm
Example: Map supported entitlements to groups#
[groups]
policy = listed
supported_entitlements =
urn:mace:egi.eu:group:eosc-synergy.eu.*
urn:mace:egi.eu:group:vo.example.org:role=member
urn:geant:helmholtz.de:group:.*
method = regex
mapping =
urn:mace:egi.eu:group:eosc-synergy.eu. -> synergy_
urn:mace:egi.eu:group:vo.example.org:role=member -> example
urn:geant:helmholtz.de:group:.* -> helmholtz
login_info#
User login information display
description: Service description shown to userslogin_help: Login instructions template- Additional custom fields supported
backend.local_unix#
Local Unix backend
shell: User shell (default: /bin/bash)home_base: Home directory base (default: /home)deploy_user_ssh_keys: Deploy SSH keys from OIDC claims (default: no)punch4nfdi: Use PUNCH4NFDI-specific group name translation (default: no)post_create_script: Script to run after user creationshadow_compatibility_function: Username compatibility method (default/v044/punch)
backend.ldap#
LDAP backend modes and settings
mode: LDAP access moderead_only: Read existing mapped accounts onlypre_created: Map existing unmapped accountsfull_access: Create and manage accounts
host: LDAP server hostname (default: localhost)port: LDAP server port (default: 1389, 636 for TLS)tls: Enable LDAPS protocol (default: False)admin_user: Admin DN for write operationsadmin_password: Admin passworduser_base: User search base (default: ou=users,dc=example)group_base: Group search base (default: ou=groups,dc=example)attribute_oidc_uid: OIDC UID attribute (default: gecos)attribute_local_uid: Local UID attribute (default: uid)- For full_access mode:
shell: User shell (default: /bin/sh)home_base: Home directory base (default: /home)uid_min,uid_max: UID range (default: 1000-60000)gid_min,gid_max: GID range (default: 1000-60000)post_create_script: Post-creation script
feudal-simulator#
This is a small tool that accepts a provisioning or deprovisioning json on stdin, and shows to which user and groups this would be mapped.
Example:
cat examples/marcus-deploy-egi.json | feudal-simulator -c feudal_adapter_template.conf
caddy reverse proxy setup#
We recommend using this Caddyfile and a set of handlers:
Caddyfile#
{
auto_https off
admin off
}
:80 {
import /config_files/handlers/*.caddy
}
:443 {
# log { # log in a more traditional way
# format transform "{common_log}"
# }
tls /etc/ssl/caddy/caddy.crt /etc/ssl/caddy/caddy.key
import /config_files/handlers/*.caddy
}
Motley-cue API handler#
@motley_cue path /motley_cue /motley_cue/*
handle @motley_cue {
uri strip_prefix /motley_cue
# CORS headers
header Access-Control-Allow-Origin *
header Access-Control-Allow-Credentials true
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Accept, Authorization"
# Handle preflight requests
@options method OPTIONS
header @options Access-Control-Allow-Origin *
header @options Access-Control-Allow-Credentials true
header @options Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header @options Access-Control-Allow-Headers "Content-Type, Accept, Authorization"
header @options Content-Type "text/plain charset=UTF-8"
respond @options "" 204
# Proxy to Unix socket
reverse_proxy unix//run/motley_cue/motley-cue.sock
}
nginx reverse proxy setup#
Our packages ship with a default configuration file that installs to
/etc/nginx/sites-enabled/nginx.motley_cue
server {
listen 8080;
listen [::]:8080;
server_name _;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, since we set the Host: header above already.
proxy_redirect off;
proxy_pass http://unix:/run/motley_cue/motley-cue.sock;
}
}
Please refer to the nginx documentation for setting up further aspects:
- HTTPS termination
- Port configuration
- Host certificates
- URL routing to motley-cue service
PAM integration with SSH#
PAM integration with SSH is done via pam-ssh-oidc
Note
The PAM integration requires our PAM module on the ssh serverside. If
this is not acceptable, the oinit set of tools (also part of our
tools) works with ssh-certificates and does not require PAM modules.
Last change: Aug 11, 2026 10:58:31