Altinn / Altinn/app-lib-dotnet

Backfill: revoke orphaned signee instance delegations in production

Open
#1,810 0 comments 0 reactions 1 assignee Claimed by @olavsorl View on GitHub
squad/data
Dominant language
C#
Stars
8
Forks
27
Avg merge
1h 21m
Merged PRs (30d)
7

Description

Background

Automatic revocation of delegated signee rights on signing task end was added in
[app-lib-dotnet#1800](https://github.com/Altinn/app-lib-dotnet/pull/1800) (closes
#18310). Before that fix shipped, user-delegated (brukerstyrt) signing tasks
completed without ever revoking the instance delegation. As a result, production
has accumulated active instance delegations whose signing task has already
ended, and the PDP still returns permit for these signees, which is the confusion
described in #18310.

The forward fix only handles new task-ends. Existing stale delegations must be
cleaned up separately. This issue tracks that one-time backfill.

Goal

Identify and revoke all instance delegations on user-delegated signing apps where
the associated signing task is no longer active, without touching delegations for
signing tasks still in progress.

Decision: revoke via API, not via DB

Revocation must go through
POST /accessmanagement/api/v1/app/delegationrevoke/resource/{resourceId}/instance/{instanceGuid},
not by editing Access Management tables directly.

AM stores delegations as an append-only change log plus generated XACML policy
files in blob storage, with a PDP cache in front. A revoke is a new change event +
policy rewrite + cache invalidation - not a row deletion. Hand-editing the tables
would desync the policy blob and PDP cache from the change log, corrupt the audit
trail, and carries unacceptable blast radius. The API
performs all of this atomically and the operation is idempotent, which makes the
backfill safely re-runnable.

The revoke body maps 1:1 from the GET delegations response (from, to,
rights[].action + rights[].resource), so revocation can be fully GET-driven —
no need to reconstruct rights or reverse-engineer task IDs.

Plan

1. Discovery (read-only)

Source of truth for what is delegated — Access Management. With the AM team,
enumerate all currently-active instance delegations on the relevant resources.
Candidate resources = any app_* resource with active instance delegations
carrying a sign action; cross-check against apps configuring SigneeProviderId.
Source of truth for whether revoke is safe is in Storage. For each
(resource → app, instanceGuid, task), look up the instance process state:

**process ended, OR current task ≠ signing task → orphan → revoke**

**instance archived / absent → signing cannot be ongoing → revoke**

**current task == signing task AND process not ended → leave (live flow)**

Secondary confirmation: signee-state data element with isAccessDelegated: true
on a completed task.
Output: a reviewable plan file (resource, instanceId, from, to, rights, decision, reason).

2. Revoke

Per candidate instance:

GET delegations → build revoke body directly from the response,
POST delegationrevoke with a platform access token for that app's org/app,
GET again → assert data: [].

Implementation notes:

Idempotent + re-runnable (already-revoked is a benign no-op).
Throttle / batch / run off-peak; agree acceptable QPS with the AM team
(we have prior 429 experience on shared platform services).
Do not assume from/to — self-delegations (instance owner → self) occur;
echoing the GET response handles all shapes.
Platform access tokens are per org/app — group the loop by app.

3. Execution discipline

Dry run producing the full plan file first; review with the AM team before any
mutation.
Run smallest-blast-radius first (one app, a few confirmed-ended instances),
verify, then widen.
Verify each revoke: GET returns empty + PDP decision for the signee on that
instance returns Deny/NotApplicable.
Log every revoke for audit.

Dependencies / blockers

Coordinate with the Authorization / AM team — this is their data. Confirm
whether an internal bulk / maintenance revoke (revoke-all-for-instance or a
batch endpoint) exists; preferred over iterating the public per-pair app endpoint
across the full prod backlog.
Abandon-path fix. #1800 only fixes the task-end path. The abandon
path still revokes after deleting signee state, so it no-ops and abandoned
signings keep orphaning delegations. This must be fixed (in #1800 or a follow-up)
before the backfill, otherwise the backlog keeps growing and the backfill needs
re-running.

Acceptance criteria

Candidate resources/apps enumerated and confirmed with AM
Discovery script produces a reviewed dry-run plan (revoke/leave + reason per record)
Active in-progress signing tasks confirmed excluded
Backfill executed in batches with per-batch verification
Post-run: GET delegations empty and PDP returns Deny for revoked records
Abandon-path revocation fixed so this is a one-time cleanup

References

#18310 — original revoke-on-task-end task
[app-lib-dotnet#1800](https://github.com/Altinn/app-lib-dotnet/pull/1800) — forward fix
AM revoke endpoint: POST /accessmanagement/api/v1/app/delegationrevoke/resource/{resourceId}/instance/{instanceGuid}
AM list endpoint: GET /accessmanagement/api/v1/app/delegations/resource/{resourceId}/instance/{instanceId}

CREATED WITH CLAUDE

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.