What this is about
Whoever manages permissions can be wrong in two ways. They keep someone out who should be allowed in. Or they let someone in who should not be. The first mistake is annoying and shows up immediately. The second often does not show up at all, because everything works.
So in every case of doubt, CIAS chooses the first mistake. This is called fail closed. It applies in two places:
- at startup: if a setting that decides about permissions is missing, the application does not start. The error message names what is missing.
- on every request: if CIAS cannot answer a question with certainty, it refuses instead of assuming a convenient answer.
Missing, empty or named
A setting like “which roles administer the platform?” can be in three states. CIAS treats them differently, and the difference is deliberate.
- Nobody wrote anything down.
- The application does not start.
- The message names the missing setting or the missing type.
- The installation writes down an empty list.
- The application starts.
- The rule applies to nobody.
- The installation names roles.
- The application starts.
- The rule applies to everyone who holds one of these roles.
“Empty” and “missing” look similar in code, but they mean different things. An empty list is a decision: “nobody should be able to do this through the API.” A missing setting is an oversight. CIAS wants to see the oversight at startup, not with the first customer.
Empty always means nobody, never everybody. An empty list of platform administrator roles means: nobody is a platform administrator.
What an installation has to state
Some settings are beans. A bean is an object that Spring puts together at startup. If no library supplies it, the installation has to create it itself. Other settings are entries in application.yml. Neither kind has a default value.
| Setting | What it is for | When it is required |
|---|---|---|
Bean PlatformAdministrators | which roles administer the platform | as soon as an administration module is switched on |
Bean RegistrationRoles | which roles a registration grants: for founders, for members, without a tenant | when registration is switched on |
codamai.cias.tenancy.lookup | where the tenant gate gets its answers from: local or remote | always, when the CIAS filter chain runs |
codamai.cias.tenancy.client.base-url and .token | where CIAS can be reached and with which token it is asked | with lookup: remote |
codamai.cias.tenancy.lookup-roles | who may ask the tenant endpoint for services | when codamai.cias.tenancy.lookup-rest is on |
codamai.cias.user.lookup-roles | who may read the attribute values per tenant for services | when codamai.cias.user.lookup-rest is on |
codamai.cdms.cias.reader-roles | who may read GET /cias/fetch in CDMS, that is the list of all roles and attributes | always in CDMS |
codamai.cias.iam-provider | which identity provider CIAS talks to: keycloak or memory | in the starter |
codamai.cias.notification.mail | how mail leaves the process: smtp or log | when the mail module is switched on |
codamai.cias.notification.mail.from | the sender of the mails | with mail: smtp |
cias_client | the client that module roles live on | when registration is switched on |
| public base URL of the registration | where the links in the mails point to | when registration is switched on |
The reasons are always the same. Two examples:
lookupwithout a default. Iflocalwere the default, a CDMS service that forgot the setting would ask a CIAS in its own process that is not there at all. Ifremotewere the default, a standalone CIAS would ask itself over HTTP. Both are worse than a startup that does not succeed in the first place.mailwithout a default. Iflogwere the default, an installation would report every mail as sent and deliver none. You would only notice when customers complain.
Further checks at startup
Besides missing settings, CIAS catches a few mistakes at startup that would otherwise silently open permissions:
| What is noticed at startup | Consequence |
|---|---|
A module wants to publish a public path like /** or / | Startup refused. Such a path would switch off authentication for the whole application. |
| A module declares an attribute per tenant, but nothing can read the values | Startup refused. Otherwise the value from the token would silently apply, the same in every tenant. |
| A time is negative, a cache smaller than 1, a timeout 0 | Startup refused |
| A throttling limit names a counter that does not exist (typo) | Startup refused. Otherwise the limit would silently have no effect. |
| Two modules in the declaration list have the same name, or one lacks a name or client | Startup refused |
| An address for the first platform administrator is set, but no administrator role, or Keycloak does not know the address | Startup refused |
Every application with CIAS also checks at startup whether development settings were left behind. This applies to standalone CIAS just as to every application that embeds CIAS. If one of these settings is present, the application does not start and names each of them:
| Setting | Why it is dangerous in front of customers |
|---|---|
codamai.cias.iam-provider: memory | an in-memory identity provider that forgets every account on restart |
codamai.cias.notification.mail: log, when mail is switched on | mails end up in the log instead of with the recipient |
codamai.cias.migration.enabled: false | nothing creates or upgrades the CIAS tables |
spring.jpa.hibernate.ddl-auto not validate (standalone CIAS only) | Hibernate changes the schema itself instead of reporting differences |
Whether a finding stops the start is decided by one setting: codamai.safety.mode (as an environment variable CODAMAI_SAFETY_MODE).
| Value | What happens | For |
|---|---|---|
enforce (applies when nothing is set) | start refused, every setting is named | every installation in front of customers: stage, production, blue/green |
warn | the start goes ahead, every setting is logged as a warning | only a developer’s machine and tests |
The value says what happens, not where the application runs. That is why no environment needs a value of its own. Standalone CIAS sets warn only in its profile local.
At runtime: reject when in doubt
On every single request the same applies: what CIAS does not know for sure is refused. The table shows the most important places.
| Situation | What CIAS does not do | What happens |
|---|---|---|
Operating mode MULTI, the token names no tenant | pick a default tenant | 403 cias.authentication.tenant-required |
| The person belongs to several organizations, nothing says which one is meant | take the first one | 403 cias.authentication.tenant-unresolved |
| The tenant gate does not know the tenant, or CIAS does not answer and nothing is remembered | let it through | 403 cias.authentication.tenant-not-served |
| The person’s attribute values in this tenant cannot be read | carry on with empty values | 403 cias.authentication.tenant-not-served |
| Keycloak returns no token during the token exchange | assume an identity | the request continues without an identity, every role check refuses |
| A caller without roles, or the list of administrator roles is empty | treat them as an administrator | they are not a platform administrator |
| A role has no delegation | let everyone grant it | only a platform administrator grants it |
POST /cias/admin/roles without scope | assume “platform wide” or “in the tenant” | 400, the request is invalid |
POST /cias/admin/tenants without type | assume “dynamic” or “static” | 400, the request is invalid |
| A registration flow is not configured | use a generous default flow | the flow does not exist, the call is refused |
Why does CIAS refuse when it cannot read the attribute values, instead of simply assuming “no values”? Because in CDMS “no values” is dangerously close to “all values”: a single * switches an attribute filter off entirely. So empty values would not be a safe assumption, but a bet.
When the request is refused, the filter chain writes nothing into the RequestContext. Even code that overlooked the refusal would find no tenant and no roles there.
Where there are defaults
Not everything in CIAS has to be configured. There are defaults for everything that grants no permissions:
| Setting | Default |
|---|---|
codamai.cias.tenant-gate.ttl: how long the tenant gate remembers an answer | 30 seconds |
codamai.cias.attribute-lookup.ttl: how long attribute values per tenant are remembered | 30 seconds |
codamai.cias.token-exchange.ttl: how long an exchanged token is reused | 5 minutes |
| Timeout when asking a standalone CIAS | 2 seconds each for connecting and for the answer |
| Size of the caches | 10,000 entries |
These times are security relevant too, because a suspension can take effect that much later. But a wrong value only makes the application slower or chattier, it does not open anything.
And some things are deliberately not allowed to stop the startup:
- A module declaration that CIAS cannot accept refuses only that one module. Its roles are then not written. The application still starts, because otherwise a single faulty module could bring down the whole platform.
- A role reconciliation at startup that does not complete is in the log with all details. The application starts. Otherwise it would not come up just because another service happens to be restarting.
- Missing metrics change nothing about the behavior. The tenant gate works the same way without measurements.