# Upgrading from rc.11 to rc.12

This page lists what you must change, and what behaves differently, when you move a HelixIAM install from
`v1.0.0-rc.11` to `v1.0.0-rc.12`. The source is the rc.12 release notes and the security review (findings H4 and H5).

!!! info "What rc.12 is"
    rc.12 adds bring-your-own realm signing keys and certificates, with ES256 alongside RS256. It also closes two admin
    privilege findings (H4 and H5) and protects everything administrators sign in through. Most of the changes below
    tighten who may do what. One adds a start-up report for you to review.

## Before you upgrade: checklist

| # | Do this | Applies if |
| --- | --- | --- |
| 1 | Optionally run the [pre-upgrade query](#1-administrators-who-sign-in-through-an-identity-provider) to see which administrators sign in through an external identity provider | you use identity providers |
| 2 | After the upgrade, review the [start-up report](#2-the-start-up-report-privileged_legacy_link), confirm the `AMBIGUOUS` links that are right and revoke trust in any link you do not recognise | the report lists anyone |
| 3 | Tell administrators that [new links](#3-new-links-for-administrators) are made from the account console | administrators sign in through an identity provider |
| 4 | Give [`realm-admin`](#4-actions-that-now-need-realm-admin) to the people who do the actions listed there | you delegate administration with narrower permissions |
| 5 | Review [realm keys and signing](#5-realm-signing-keys-es256-and-the-staging-time) before you activate an ES256 key | you plan to bring your own key or use ES256 |
| 6 | Nothing to do for [Flyway V86](#6-flyway-v86): it is applied automatically on start-up | always (informational) |

---

## 1. Administrators who sign in through an identity provider

**What changed.** Each federated link now records how it was made. An administrator signs in through a link only when
the link is trusted:

| Provenance | Meaning | Signs in an administrator |
| --- | --- | --- |
| `LEGACY` | existed before the upgrade | yes |
| `JIT` | the sign-in created the account | yes |
| `SELF` | linked by the user from the account console | yes |
| `WALLET` | an EU Digital Identity Wallet linked from the account console | yes |
| `ADMIN_CONFIRMED` | confirmed by a realm admin | yes |
| `AUTO` | matched by a verified e-mail address, or trust revoked | **no** |

**Links belong to a realm.** A link now signs its user in only in the realm it belongs to. V86 places each existing
link in the realm whose identity provider has that alias. If the alias is used in several realms, or the provider comes
from the realm config folder (which the migration cannot see), it holds the link as `UNRESOLVED` and logs how many
there are. At the first start, after the realm config folder is loaded, each such link is resolved:

- **The alias now belongs to exactly one realm:** the link moves there and keeps working (`LEGACY`). This is audited
  as `LEGACY_LINK_RESOLVED`.
- **No stored provider has the alias, and exactly one static `helix.federation.*` provider does (it serves every
  realm):** the link stays in the user's home realm and keeps working (`LEGACY`), audited the same way.
- **Anything else** (the alias is stored in several realms, a stored and a static provider share it, or the
  definitions differ): the link goes to the user's home realm and becomes `AUTO`. It still works for ordinary users,
  but it signs no administrator in until a realm admin confirms it. It is reported as `AMBIGUOUS` (see the next
  section). A link the
migration cannot place at all signs no one in and is logged at start-up; the user links the provider again.

**No lockout.** Every link that exists at the upgrade becomes `LEGACY` and keeps working as it did in rc.11. Wallet
links become `WALLET`. The attack these links were open to, a lower admin pointing the identity provider at their own
issuer, now needs the linked administrators' own permissions.

**To check in advance (optional).** Run this on the rc.11 database. It lists every user who holds the `admin` role, or
a role with admin permissions, and has a federated link:

```sql
SELECT DISTINCT r.tenant_id AS realm, u.username, f.idp_alias
  FROM federated_link f
  JOIN user_credentials u ON u.user_id = f.user_id
  JOIN user_in_role ur    ON ur.user_id = f.user_id
  JOIN user_roles r       ON r.role_id = ur.role_id
 WHERE r.name = 'admin'
    OR EXISTS (SELECT 1 FROM admin_role_permission p WHERE p.role_id = r.role_id)
 ORDER BY realm, u.username, f.idp_alias;
```

Admin permissions that come through a group (`user_group_member` and `user_group_role`) are not in this query. The
start-up report below includes them.

## 2. The start-up report: `PRIVILEGED_LEGACY_LINK`

**What changed.** At every start, the server reports each user who is an administrator in any realm (or in master) and
has one of these links:

- a `LEGACY` link (it still signs them in): a WARN and a `PRIVILEGED_LEGACY_LINK` audit event;
- an `AMBIGUOUS` link (it signs them in only after a realm admin confirms it): a WARN and a
  `PRIVILEGED_AMBIGUOUS_LINK` audit event.

Each entry names the realm of the link, the username, the identity provider and the realms where the user is an
administrator. The report never blocks anything.

**What you must do.** Review the report. In the console: **Events**, filter on type `PRIVILEGED_LEGACY_LINK`. Over the
API: `GET /admin/realms/{realm}/events?type=PRIVILEGED_LEGACY_LINK`. For each entry, confirm that the account at the
identity provider belongs to that administrator. Confirm an `AMBIGUOUS` link that is theirs (see the next section).
If you are unsure about a `LEGACY` link, revoke trust in it:

- **Console:** **Users → (user) → Linked identities → Revoke trust**.
- **API:** `POST /admin/realms/{realm}/users/{userId}/federated-identities/{alias}/revoke-trust` with body
  `{"externalSubject": "…"}`. A user can have several links to one provider, so each request names one link.

Revoking trust needs `realm-admin`, and the user's admin permissions in every other realm they belong to (so only a
master admin can act on a master admin's link). It is audited as `FEDERATED_LINK_TRUST_REVOKED`. The link then counts as `AUTO`:
it still signs in an ordinary user, but never an administrator. The administrator can link the provider again
themselves (see the next section). `GET /admin/realms/{realm}/users/{userId}/federated-identities` lists a user's links
with their provenance.

## 3. New links for administrators

**What changed.** A link made after the upgrade by matching a verified e-mail address never signs in an administrator.
Each refusal is audited as `PRIVILEGED_LINK_REFUSED`. An administrator gets a trusted link in one of two ways:

- **Themselves, from the account console.** **Connected accounts → Link** next to the provider. If they have not
  signed in within the step-up window (`helix.account.step-up-max-age-seconds`, 300 s by default), they sign in again
  first. Then they sign in at the provider, and the link is stored as `SELF`. The flow is bound to their session; an
  identity already linked to someone else is refused. It is audited as `ACCOUNT_IDENTITY_LINK`.
- **By a realm admin.** For an administrator who cannot use the account console:
  **Users → (user) → Linked identities → Confirm link**, or
  `POST /admin/realms/{realm}/users/{userId}/federated-identities/{alias}/confirm` with body
  `{"externalSubject": "…"}` (one link). This needs `realm-admin` and is
  audited as `FEDERATED_LINK_CONFIRMED`. Like revoking trust, it also needs the user's admin permissions in every
other realm they belong to.

Ordinary users are not affected: e-mail linking and just-in-time accounts work as before.

## 4. Actions that now need `realm-admin`

**What changed.** These used to be open to narrower permissions. See [Admin roles (RBAC)](admin-roles.md).

- **Signing keys and settings:** every realm key write and `PUT /admin/realms/{realm}/settings/signing`. Reads keep
  `manage-realm`.
- **Admin grants:** changing a role's admin permissions. Assigning a role, through any path, also needs the role's
  admin permissions to be your own (H5).
- **Acting on someone more privileged:** acting on a user or client that holds admin permissions you do not hold
  needs those permissions, for example a password reset, impersonation or a client secret.
- **Sign-in surfaces:** creating `helix-console` or `helix-cli`, and changing their Application, mappers or allowed
  resources. Also the realm's `browser` flow and the flows bound to the sign-in clients.
- **Identity providers that administrators are linked to:** changing or deleting one needs the linked administrators'
  permissions. Changing a trusted list their wallet providers use needs the same.
- **Messaging and sign-in policy:** messaging providers and templates, the MFA policy, magic links, the subject claim,
  and realm settings other than the display name, the legacy branding and self-registration.
- **Groups and roles:** deleting a role or group, or moving a group, needs the admin permissions it grants.
- **Push devices:** registering one for a user needs that user's admin permissions.
- **Unknown routes:** any `/admin` route the permission catalogue does not name.

Also: `manage-users`, `manage-clients`, `manage-identity-providers` and `manage-events` now include their `view-`
counterparts. A percent-encoded realm id or route name in an `/admin` path is refused with `400` (H4).

`helix-cli` is now created in every realm. On every start, a `helix-cli` that allows more than the built-in client is
corrected: a redirect URI that is not a loopback address is dropped, as are grant types outside the built-in set, a
secret, or a confidential setting. Each correction is logged and audited as `SIGN_IN_CLIENT_RECONCILED`. Narrower
choices are kept, such as dropping `device_code` or a custom loopback port or path.

## 5. Realm signing keys, ES256 and the staging time

**What changed.** See [Realm keys](realm-keys.md).

- You can import a key and its certificate chain, or have the server generate a key and a CSR.
- New keys start `PENDING`: published, but not signing. You activate them after `helix.keys.min-staging` (5 minutes by
  default), or earlier with `?force=true`.
- An ES256 key can be active alongside the RS256 key. Every realm keeps an active RS256 key, and retiring it is refused.
- ID tokens use each client's `id_token_signed_response_alg`. Other tokens use the realm's `signing.defaultAlg`
  (RS256 by default).

**What you must do.** If a client asks for an ID-token algorithm that has no active key, for example RS384, change it
before its next save. During a rolling upgrade you can import and stage keys, but activate an **ES256** key only once
every replica runs rc.12. Realm exports still contain no keys, certificates or signing settings.

## 6. Flyway V86

**What changed.** V86 (and `schema.sql`) changes the following:

- **`realm_key`:** adds the certificate columns and a unique index on the public key, and rewrites the algorithm from
  `RSA` to `RS256`. Existing keys keep their kid and keep signing. Duplicate rows that an rc.11 race could leave are
  removed. If the same key is stored in two realms, the migration stops and names them.
- **New table:** adds `realm_signing_settings`.
- **`federated_link`:** adds `realm_id` and `created_via`, backfills both as described in section 1, and keys links by
  realm, provider and subject.

**What you must do.** Nothing; it runs on start-up.
