Schedule icon
Search icon
Script icon
Loop icon
SlackIncomingWebhook icon
Queries icon
If icon
Write icon
IonToLdif icon
Modify icon
Log icon
Upload icon

Quarterly Entitlement Recertification Campaign

Recertify standing LDAP access each quarter with Kestra. Enumerate entitlements, flag stale and dormant accounts, notify managers, and archive evidence.

Categories
BusinessInfrastructure

Quarterly access reviews are a standing obligation for any organization that answers to SOC 2, ISO 27001, or an internal separation of duties policy, and the work is usually a spreadsheet that gets emailed around and reconciled by hand. This flow runs the recertification as code instead. It reads group entitlements and account activity straight from the directory, scores each account against stale, dormant, and over-privileged rules, pushes every flagged account to its manager with a decision row for the audit trail, revokes what should no longer be held, and leaves a JSON evidence pack in object storage. The scope here is the recertification of standing access that people already hold every quarter. It is deliberately not a data subject erasure workflow, which removes personal data on request, because nothing in this campaign deletes records, it only certifies or revokes group membership.

How it works

  1. quarterly_review_window (io.kestra.plugin.core.trigger.Schedule, cron 0 6 1 1,4,7,10 *, shipped with disabled: true) opens the campaign on the first day of January, April, July, and October once you enable it.
  2. enumerate_group_entitlements (io.kestra.plugin.ldap.Search) reads every entry under ou=groups with its cn, description, member, and managedBy attributes, while enumerate_account_activity reads ou=people for the logon and status attributes the rules need. Both searches store their results as internal storage files, so no directory data leaves the worker.
  3. assess_entitlement_risk (io.kestra.plugin.scripts.python.Script) loads both result sets, scores every account that holds at least one group membership, and publishes the flagged list through the ::json:: output protocol.
  4. notify_flagged_accounts (io.kestra.plugin.core.flow.Loop) iterates the flagged accounts. Each iteration posts a manager notification with io.kestra.plugin.slack.notifications.SlackIncomingWebhook and appends a decision row with io.kestra.plugin.jdbc.postgresql.Queries.
  5. revocation_gate (io.kestra.plugin.core.flow.If) applies the revocation path only when flagged_count is greater than zero. It renders Ion modify records with io.kestra.plugin.core.storage.Write, converts them through io.kestra.plugin.ldap.IonToLdif, and applies them with io.kestra.plugin.ldap.Modify; the clean path only logs a message.
  6. render_evidence_pack writes access_review_report.json, upload_evidence_pack (io.kestra.plugin.aws.s3.Upload) archives it under a campaign prefix, and campaign_summary posts the verdict. The errors handler posts to Slack if any step fails.

What you get

  • A per campaign verdict of clean or action_required, plus the count of accounts reviewed and flagged, available as declared flow outputs.
  • Three concrete risk flags on each account, stale_logon, dormant_account, and over_privileged, with a medium or high risk rating that orders the notification loop.
  • A manager notification in Slack and a matching row in access_review_decisions for every flagged account, so the review is queryable and not trapped in a chat thread.
  • Automatic revocation of the flagged group membership through the same Ion to LDIF to ldap.Modify path that the offboarding blueprint uses.
  • An access_review_report.json evidence pack in S3, keyed by a slug of the campaign name, that an auditor can pull for the quarter.
  • A distinct Slack alert on failure, because a certification that stopped halfway cannot be filed as evidence.

Who it's for

  • Security and identity teams who run quarterly access certification for SOC 2, ISO 27001, PCI DSS, or an internal least privilege policy.
  • Platform teams that maintain an on premises OpenLDAP or ApacheDS directory as the group membership source for VPN, SSH, and internal applications.
  • Compliance owners who need an evidence artifact per quarter rather than a spreadsheet with manual sign offs.
  • IT administrators who already automate offboarding and want the standing access side of the lifecycle covered as well.

Why orchestrate this with Kestra

The assessment itself is a script, but a script cannot open a quarterly window, run two directory searches, fan out one Slack request and one database insert per flagged account, branch on the result, convert Ion records to LDIF, and archive the evidence, all in one run that leaves an execution history. Those handoffs are exactly where manual reviews break down, since the spreadsheet, the chat thread, and the ticket system drift apart within a week. Kestra keeps the enumeration, the risk rules, the manager loop, the revocation gate, and the evidence pack in a single versioned YAML file, so a rule change is a reviewed pull request instead of a tribal knowledge update. Failed runs surface as a red execution with a Slack alert, and every run keeps the inputs, the flagged list, and the archived object address together for the audit file.

Prerequisites

  • An LDAP directory reachable from the Kestra worker, with an admin bind account that can search ou=people and ou=groups and modify group entries.
  • An objectClass=groupOfNames style group tree whose member attribute holds full account DNs, plus inetOrgPerson people entries that carry lastLogonTimestamp and accountStatus.
  • A Postgres database with an access_review_decisions table holding campaign_name, account_uid, manager_dn, risk, flags, membership_count, review_status, and created_at columns.
  • An Amazon S3 bucket for evidence packs and AWS credentials allowed to put objects into it.
  • A Slack incoming webhook for manager notifications, campaign summaries, and failure alerts.
  • Docker or a Python runner available to Kestra for the two io.kestra.plugin.scripts.python.Script tasks.

Secrets

  • LDAP_HOSTNAME: hostname of the LDAP server.
  • LDAP_ADMIN_DN / LDAP_ADMIN_PASSWORD: bind credentials for an account that can search and modify entries.
  • SLACK_WEBHOOK_URL: Slack incoming webhook URL.
  • POSTGRES_URL / POSTGRES_USER / POSTGRES_PASSWORD: connection settings for the decision log database.
  • AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_DEFAULT_REGION: credentials and region for the S3 upload.
  • S3_EVIDENCE_BUCKET: name of the bucket that stores the quarterly evidence packs.

Quick start

  1. Add every secret listed above to the company.team namespace in Kestra under Settings, then Secrets.
  2. Create the access_review_decisions table in Postgres with the columns listed under Prerequisites.
  3. Point base_dn at your directory and adjust the two filter values if your schema uses different object classes, then enable the trigger when you are ready to run on schedule.
  4. The schedule block to enable is this one, shipped disabled so nothing fires during review:
triggers:
  - id: quarterly_review_window
    type: io.kestra.plugin.core.trigger.Schedule
    cron: "0 6 1 1,4,7,10 *"
    disabled: true
  1. Run the flow once by hand with campaign_name set to the current quarter, then confirm the Slack notices, the decision rows, and the access_review_report.json object in the bucket.

How to extend

  • Raise or lower STALE_DAYS, DORMANT_DAYS, and MAX_PRIVILEGED_GROUPS inside assess_entitlement_risk to match your own recertification policy, since those constants are the whole rule set.
  • Add another io.kestra.plugin.ldap.Search against pwdAccountLockedTime or an application entitlement store if some access lives outside the directory.
  • Replace the Slack manager message with an approval request that writes certified or rejected back to review_status, so the decision loop closes in the database instead of in a thread.
  • Split the revocation path by flag, for example revoking only over_privileged memberships while leaving stale_logon accounts for a human decision in the revocation_gate branch.
  • Load the archived evidence pack into a warehouse table with io.kestra.plugin.jdbc.postgresql.CopyIn to build a multi quarter trend of flagged and revoked access.
  • Run the same assessment monthly instead of quarterly by changing the cron to 0 6 1 * *, which turns the campaign into a drift check on standing access.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.