keyclerk 0.26.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- keyclerk-0.26.3/LICENSE +190 -0
- keyclerk-0.26.3/NOTICE +8 -0
- keyclerk-0.26.3/PKG-INFO +310 -0
- keyclerk-0.26.3/README.md +290 -0
- keyclerk-0.26.3/keyclerk/__init__.py +3 -0
- keyclerk-0.26.3/keyclerk/access_paths.py +409 -0
- keyclerk-0.26.3/keyclerk/access_state.py +127 -0
- keyclerk-0.26.3/keyclerk/anchors.py +177 -0
- keyclerk-0.26.3/keyclerk/api.py +652 -0
- keyclerk-0.26.3/keyclerk/apply_engine.py +241 -0
- keyclerk-0.26.3/keyclerk/approvals.py +310 -0
- keyclerk-0.26.3/keyclerk/attest.py +339 -0
- keyclerk-0.26.3/keyclerk/clock.py +37 -0
- keyclerk-0.26.3/keyclerk/config.py +90 -0
- keyclerk-0.26.3/keyclerk/connectors/__init__.py +4 -0
- keyclerk-0.26.3/keyclerk/connectors/contract.py +155 -0
- keyclerk-0.26.3/keyclerk/connectors/keycloak.py +383 -0
- keyclerk-0.26.3/keyclerk/connectors/physical.py +63 -0
- keyclerk-0.26.3/keyclerk/connectors/sources.py +112 -0
- keyclerk-0.26.3/keyclerk/console.py +365 -0
- keyclerk-0.26.3/keyclerk/delegation.py +1279 -0
- keyclerk-0.26.3/keyclerk/desk.py +3319 -0
- keyclerk-0.26.3/keyclerk/dpop.py +97 -0
- keyclerk-0.26.3/keyclerk/edge.py +802 -0
- keyclerk-0.26.3/keyclerk/edge_cli.py +311 -0
- keyclerk-0.26.3/keyclerk/edge_conformance.py +71 -0
- keyclerk-0.26.3/keyclerk/edge_records.py +299 -0
- keyclerk-0.26.3/keyclerk/edge_store.py +240 -0
- keyclerk-0.26.3/keyclerk/events.py +135 -0
- keyclerk-0.26.3/keyclerk/fullcheck.py +244 -0
- keyclerk-0.26.3/keyclerk/grants.py +218 -0
- keyclerk-0.26.3/keyclerk/graph.py +283 -0
- keyclerk-0.26.3/keyclerk/guard.py +209 -0
- keyclerk-0.26.3/keyclerk/health.py +206 -0
- keyclerk-0.26.3/keyclerk/httpserve.py +39 -0
- keyclerk-0.26.3/keyclerk/init.py +310 -0
- keyclerk-0.26.3/keyclerk/keys.py +154 -0
- keyclerk-0.26.3/keyclerk/leases.py +217 -0
- keyclerk-0.26.3/keyclerk/log_conformance.py +64 -0
- keyclerk-0.26.3/keyclerk/metrics.py +180 -0
- keyclerk-0.26.3/keyclerk/oidc.py +318 -0
- keyclerk-0.26.3/keyclerk/piam/__init__.py +20 -0
- keyclerk-0.26.3/keyclerk/piam/core.py +118 -0
- keyclerk-0.26.3/keyclerk/piam/osdp_controller.py +126 -0
- keyclerk-0.26.3/keyclerk/piam/pacs.py +116 -0
- keyclerk-0.26.3/keyclerk/places.py +194 -0
- keyclerk-0.26.3/keyclerk/policy/__init__.py +1 -0
- keyclerk-0.26.3/keyclerk/policy/access.rego +97 -0
- keyclerk-0.26.3/keyclerk/policy/access_test.rego +91 -0
- keyclerk-0.26.3/keyclerk/policy/ceiling.rego +55 -0
- keyclerk-0.26.3/keyclerk/policy/ceiling.wasm +0 -0
- keyclerk-0.26.3/keyclerk/policy/ceiling.wasm.build +6 -0
- keyclerk-0.26.3/keyclerk/policy/delegation.rego +170 -0
- keyclerk-0.26.3/keyclerk/policy/delegation_test.rego +75 -0
- keyclerk-0.26.3/keyclerk/policy/edge.rego +93 -0
- keyclerk-0.26.3/keyclerk/policy/edge.wasm +0 -0
- keyclerk-0.26.3/keyclerk/policy/edge.wasm.build +11 -0
- keyclerk-0.26.3/keyclerk/policy/edge_test.rego +54 -0
- keyclerk-0.26.3/keyclerk/policy/fallback.rego +93 -0
- keyclerk-0.26.3/keyclerk/policy/fallback_test.rego +28 -0
- keyclerk-0.26.3/keyclerk/policy/identity.rego +85 -0
- keyclerk-0.26.3/keyclerk/policy/identity.wasm +0 -0
- keyclerk-0.26.3/keyclerk/policy/identity.wasm.build +10 -0
- keyclerk-0.26.3/keyclerk/policy/identity_test.rego +56 -0
- keyclerk-0.26.3/keyclerk/policy/second_key.rego +88 -0
- keyclerk-0.26.3/keyclerk/policy/second_key.wasm +0 -0
- keyclerk-0.26.3/keyclerk/policy/second_key.wasm.build +6 -0
- keyclerk-0.26.3/keyclerk/policy/second_key_test.rego +54 -0
- keyclerk-0.26.3/keyclerk/policy/tree.rego +40 -0
- keyclerk-0.26.3/keyclerk/policy/tree.wasm +0 -0
- keyclerk-0.26.3/keyclerk/policy/tree.wasm.build +6 -0
- keyclerk-0.26.3/keyclerk/policy/tree_test.rego +42 -0
- keyclerk-0.26.3/keyclerk/policy/view.rego +70 -0
- keyclerk-0.26.3/keyclerk/policy/view.wasm +0 -0
- keyclerk-0.26.3/keyclerk/policy/view.wasm.build +6 -0
- keyclerk-0.26.3/keyclerk/policy/view_test.rego +50 -0
- keyclerk-0.26.3/keyclerk/policy/visits.rego +321 -0
- keyclerk-0.26.3/keyclerk/policy/visits.wasm +0 -0
- keyclerk-0.26.3/keyclerk/policy/visits.wasm.build +13 -0
- keyclerk-0.26.3/keyclerk/policy/visits_test.rego +192 -0
- keyclerk-0.26.3/keyclerk/policy_wasm.py +177 -0
- keyclerk-0.26.3/keyclerk/pools.py +92 -0
- keyclerk-0.26.3/keyclerk/qr.py +119 -0
- keyclerk-0.26.3/keyclerk/reconcile.py +560 -0
- keyclerk-0.26.3/keyclerk/records.py +379 -0
- keyclerk-0.26.3/keyclerk/relay.py +104 -0
- keyclerk-0.26.3/keyclerk/remedies.py +134 -0
- keyclerk-0.26.3/keyclerk/risk_port.py +99 -0
- keyclerk-0.26.3/keyclerk/schema/__init__.py +1 -0
- keyclerk-0.26.3/keyclerk/schema/api.openapi.json +8221 -0
- keyclerk-0.26.3/keyclerk/schema/config.schema.json +993 -0
- keyclerk-0.26.3/keyclerk/schema/records.schema.json +2295 -0
- keyclerk-0.26.3/keyclerk/schema/trust-input.schema.json +153 -0
- keyclerk-0.26.3/keyclerk/scope.py +699 -0
- keyclerk-0.26.3/keyclerk/signer.py +1148 -0
- keyclerk-0.26.3/keyclerk/situation.py +345 -0
- keyclerk-0.26.3/keyclerk/store/__init__.py +178 -0
- keyclerk-0.26.3/keyclerk/store/desk_store.py +69 -0
- keyclerk-0.26.3/keyclerk/store/follow.py +111 -0
- keyclerk-0.26.3/keyclerk/store/link.py +283 -0
- keyclerk-0.26.3/keyclerk/store/log_desk.py +421 -0
- keyclerk-0.26.3/keyclerk/store/log_reconcile.py +224 -0
- keyclerk-0.26.3/keyclerk/store/memory.py +215 -0
- keyclerk-0.26.3/keyclerk/store/merkle.py +57 -0
- keyclerk-0.26.3/keyclerk/store/migrate.py +468 -0
- keyclerk-0.26.3/keyclerk/store/replay.py +175 -0
- keyclerk-0.26.3/keyclerk/store/sqlite.py +521 -0
- keyclerk-0.26.3/keyclerk/store/working.py +288 -0
- keyclerk-0.26.3/keyclerk/ui/app.css +444 -0
- keyclerk-0.26.3/keyclerk/ui/app.js +1995 -0
- keyclerk-0.26.3/keyclerk/ui/apple-touch-icon.png +0 -0
- keyclerk-0.26.3/keyclerk/ui/favicon.ico +0 -0
- keyclerk-0.26.3/keyclerk/ui/favicon.svg +1 -0
- keyclerk-0.26.3/keyclerk/ui/index.html +35 -0
- keyclerk-0.26.3/keyclerk/ui/mark-dark.svg +1 -0
- keyclerk-0.26.3/keyclerk/ui/mark-light.svg +1 -0
- keyclerk-0.26.3/keyclerk/ui/personkey.js +85 -0
- keyclerk-0.26.3/keyclerk/ui/vendor/cytoscape.LICENSE +19 -0
- keyclerk-0.26.3/keyclerk/ui/vendor/cytoscape.min.js +32 -0
- keyclerk-0.26.3/keyclerk/ui/vendor/vendor.json +10 -0
- keyclerk-0.26.3/keyclerk/visits.py +407 -0
- keyclerk-0.26.3/keyclerk.egg-info/PKG-INFO +310 -0
- keyclerk-0.26.3/keyclerk.egg-info/SOURCES.txt +212 -0
- keyclerk-0.26.3/keyclerk.egg-info/dependency_links.txt +1 -0
- keyclerk-0.26.3/keyclerk.egg-info/requires.txt +10 -0
- keyclerk-0.26.3/keyclerk.egg-info/top_level.txt +1 -0
- keyclerk-0.26.3/pyproject.toml +37 -0
- keyclerk-0.26.3/setup.cfg +4 -0
- keyclerk-0.26.3/tests/test_access_paths.py +126 -0
- keyclerk-0.26.3/tests/test_access_state.py +194 -0
- keyclerk-0.26.3/tests/test_anchor_key_domains.py +21 -0
- keyclerk-0.26.3/tests/test_anchor_paths.py +149 -0
- keyclerk-0.26.3/tests/test_anchors.py +145 -0
- keyclerk-0.26.3/tests/test_api.py +425 -0
- keyclerk-0.26.3/tests/test_api_refusal_need_to_know.py +53 -0
- keyclerk-0.26.3/tests/test_api_scan_budget.py +58 -0
- keyclerk-0.26.3/tests/test_apply_engine.py +158 -0
- keyclerk-0.26.3/tests/test_approvals.py +231 -0
- keyclerk-0.26.3/tests/test_approvals_every_kind.py +63 -0
- keyclerk-0.26.3/tests/test_attest.py +120 -0
- keyclerk-0.26.3/tests/test_broker_features.py +153 -0
- keyclerk-0.26.3/tests/test_clock.py +50 -0
- keyclerk-0.26.3/tests/test_connectors.py +323 -0
- keyclerk-0.26.3/tests/test_console.py +131 -0
- keyclerk-0.26.3/tests/test_delegation.py +315 -0
- keyclerk-0.26.3/tests/test_delegation_tree.py +73 -0
- keyclerk-0.26.3/tests/test_desk.py +270 -0
- keyclerk-0.26.3/tests/test_desk_migration.py +109 -0
- keyclerk-0.26.3/tests/test_desk_scoped.py +90 -0
- keyclerk-0.26.3/tests/test_desk_store.py +65 -0
- keyclerk-0.26.3/tests/test_edge_api.py +286 -0
- keyclerk-0.26.3/tests/test_edge_attestation.py +105 -0
- keyclerk-0.26.3/tests/test_edge_ceiling_after_rotation.py +24 -0
- keyclerk-0.26.3/tests/test_edge_conformance.py +55 -0
- keyclerk-0.26.3/tests/test_edge_email_now.py +29 -0
- keyclerk-0.26.3/tests/test_edge_long_poll.py +130 -0
- keyclerk-0.26.3/tests/test_edge_primitives.py +148 -0
- keyclerk-0.26.3/tests/test_edge_relay_holder.py +43 -0
- keyclerk-0.26.3/tests/test_edge_rotation_keys.py +42 -0
- keyclerk-0.26.3/tests/test_edge_slice_d.py +137 -0
- keyclerk-0.26.3/tests/test_edges.py +190 -0
- keyclerk-0.26.3/tests/test_erased_proof_token.py +19 -0
- keyclerk-0.26.3/tests/test_events.py +88 -0
- keyclerk-0.26.3/tests/test_follow_never_forks.py +53 -0
- keyclerk-0.26.3/tests/test_fullcheck.py +331 -0
- keyclerk-0.26.3/tests/test_grants.py +115 -0
- keyclerk-0.26.3/tests/test_guard.py +77 -0
- keyclerk-0.26.3/tests/test_health.py +201 -0
- keyclerk-0.26.3/tests/test_identity.py +192 -0
- keyclerk-0.26.3/tests/test_identity_boundary.py +124 -0
- keyclerk-0.26.3/tests/test_init.py +162 -0
- keyclerk-0.26.3/tests/test_keys.py +169 -0
- keyclerk-0.26.3/tests/test_leak_gate.py +100 -0
- keyclerk-0.26.3/tests/test_leases.py +111 -0
- keyclerk-0.26.3/tests/test_log_conformance.py +99 -0
- keyclerk-0.26.3/tests/test_log_desk.py +170 -0
- keyclerk-0.26.3/tests/test_log_faults.py +166 -0
- keyclerk-0.26.3/tests/test_migrate.py +318 -0
- keyclerk-0.26.3/tests/test_next_steps.py +69 -0
- keyclerk-0.26.3/tests/test_no_deployment_names.py +45 -0
- keyclerk-0.26.3/tests/test_person_graph.py +95 -0
- keyclerk-0.26.3/tests/test_personkey_js.py +37 -0
- keyclerk-0.26.3/tests/test_piam.py +192 -0
- keyclerk-0.26.3/tests/test_pools.py +81 -0
- keyclerk-0.26.3/tests/test_proposer_proofs.py +107 -0
- keyclerk-0.26.3/tests/test_readme.py +57 -0
- keyclerk-0.26.3/tests/test_reconcile.py +275 -0
- keyclerk-0.26.3/tests/test_reconcile_cap_first_run.py +22 -0
- keyclerk-0.26.3/tests/test_reconcile_links.py +53 -0
- keyclerk-0.26.3/tests/test_reconcile_log.py +150 -0
- keyclerk-0.26.3/tests/test_reconcile_removal_by_member.py +20 -0
- keyclerk-0.26.3/tests/test_references.py +46 -0
- keyclerk-0.26.3/tests/test_review_062.py +82 -0
- keyclerk-0.26.3/tests/test_risk_port.py +73 -0
- keyclerk-0.26.3/tests/test_scope.py +359 -0
- keyclerk-0.26.3/tests/test_second_key_standing.py +206 -0
- keyclerk-0.26.3/tests/test_seed.py +66 -0
- keyclerk-0.26.3/tests/test_signer.py +98 -0
- keyclerk-0.26.3/tests/test_signer_entry_shape.py +16 -0
- keyclerk-0.26.3/tests/test_signer_replica.py +342 -0
- keyclerk-0.26.3/tests/test_signer_roles.py +50 -0
- keyclerk-0.26.3/tests/test_signer_rows_carry_no_subject.py +25 -0
- keyclerk-0.26.3/tests/test_signer_t2.py +200 -0
- keyclerk-0.26.3/tests/test_situation.py +182 -0
- keyclerk-0.26.3/tests/test_sod_scaling.py +131 -0
- keyclerk-0.26.3/tests/test_store.py +329 -0
- keyclerk-0.26.3/tests/test_supply_chain.py +48 -0
- keyclerk-0.26.3/tests/test_time_fields.py +41 -0
- keyclerk-0.26.3/tests/test_trust_store.py +16 -0
- keyclerk-0.26.3/tests/test_ui_static.py +116 -0
- keyclerk-0.26.3/tests/test_version.py +18 -0
- keyclerk-0.26.3/tests/test_visit_store.py +89 -0
- keyclerk-0.26.3/tests/test_visits.py +388 -0
- keyclerk-0.26.3/tests/test_working.py +204 -0
keyclerk-0.26.3/LICENSE
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
EUROPEAN UNION PUBLIC LICENCE v. 1.2
|
|
2
|
+
EUPL © the European Union 2007, 2016
|
|
3
|
+
|
|
4
|
+
This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined below) which is provided under the
|
|
5
|
+
terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such
|
|
6
|
+
use is covered by a right of the copyright holder of the Work).
|
|
7
|
+
The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following
|
|
8
|
+
notice immediately following the copyright notice for the Work:
|
|
9
|
+
Licensed under the EUPL
|
|
10
|
+
or has expressed by any other means his willingness to license under the EUPL.
|
|
11
|
+
|
|
12
|
+
1.Definitions
|
|
13
|
+
In this Licence, the following terms have the following meaning:
|
|
14
|
+
— ‘The Licence’:this Licence.
|
|
15
|
+
— ‘The Original Work’:the work or software distributed or communicated by the Licensor under this Licence, available
|
|
16
|
+
as Source Code and also as Executable Code as the case may be.
|
|
17
|
+
— ‘Derivative Works’:the works or software that could be created by the Licensee, based upon the Original Work or
|
|
18
|
+
modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work
|
|
19
|
+
required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in
|
|
20
|
+
the country mentioned in Article 15.
|
|
21
|
+
— ‘The Work’:the Original Work or its Derivative Works.
|
|
22
|
+
— ‘The Source Code’:the human-readable form of the Work which is the most convenient for people to study and
|
|
23
|
+
modify.
|
|
24
|
+
— ‘The Executable Code’:any code which has generally been compiled and which is meant to be interpreted by
|
|
25
|
+
a computer as a program.
|
|
26
|
+
— ‘The Licensor’:the natural or legal person that distributes or communicates the Work under the Licence.
|
|
27
|
+
— ‘Contributor(s)’:any natural or legal person who modifies the Work under the Licence, or otherwise contributes to
|
|
28
|
+
the creation of a Derivative Work.
|
|
29
|
+
— ‘The Licensee’ or ‘You’:any natural or legal person who makes any usage of the Work under the terms of the
|
|
30
|
+
Licence.
|
|
31
|
+
— ‘Distribution’ or ‘Communication’:any act of selling, giving, lending, renting, distributing, communicating,
|
|
32
|
+
transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential
|
|
33
|
+
functionalities at the disposal of any other natural or legal person.
|
|
34
|
+
|
|
35
|
+
2.Scope of the rights granted by the Licence
|
|
36
|
+
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for
|
|
37
|
+
the duration of copyright vested in the Original Work:
|
|
38
|
+
— use the Work in any circumstance and for all usage,
|
|
39
|
+
— reproduce the Work,
|
|
40
|
+
— modify the Work, and make Derivative Works based upon the Work,
|
|
41
|
+
— communicate to the public, including the right to make available or display the Work or copies thereof to the public
|
|
42
|
+
and perform publicly, as the case may be, the Work,
|
|
43
|
+
— distribute the Work or copies thereof,
|
|
44
|
+
— lend and rent the Work or copies thereof,
|
|
45
|
+
— sublicense rights in the Work or copies thereof.
|
|
46
|
+
Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the
|
|
47
|
+
applicable law permits so.
|
|
48
|
+
In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed
|
|
49
|
+
by law in order to make effective the licence of the economic rights here above listed.
|
|
50
|
+
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the
|
|
51
|
+
extent necessary to make use of the rights granted on the Work under this Licence.
|
|
52
|
+
|
|
53
|
+
3.Communication of the Source Code
|
|
54
|
+
The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as
|
|
55
|
+
Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with
|
|
56
|
+
each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to
|
|
57
|
+
the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to
|
|
58
|
+
distribute or communicate the Work.
|
|
59
|
+
|
|
60
|
+
4.Limitations on copyright
|
|
61
|
+
Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the
|
|
62
|
+
exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations
|
|
63
|
+
thereto.
|
|
64
|
+
|
|
65
|
+
5.Obligations of the Licensee
|
|
66
|
+
The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those
|
|
67
|
+
obligations are the following:
|
|
68
|
+
|
|
69
|
+
Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to
|
|
70
|
+
the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the
|
|
71
|
+
Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work
|
|
72
|
+
to carry prominent notices stating that the Work has been modified and the date of modification.
|
|
73
|
+
|
|
74
|
+
Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this
|
|
75
|
+
Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless
|
|
76
|
+
the Original Work is expressly distributed only under this version of the Licence — for example by communicating
|
|
77
|
+
‘EUPL v. 1.2 only’. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the
|
|
78
|
+
Work or Derivative Work that alter or restrict the terms of the Licence.
|
|
79
|
+
|
|
80
|
+
Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both
|
|
81
|
+
the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done
|
|
82
|
+
under the terms of this Compatible Licence. For the sake of this clause, ‘Compatible Licence’ refers to the licences listed
|
|
83
|
+
in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with
|
|
84
|
+
his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail.
|
|
85
|
+
|
|
86
|
+
Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide
|
|
87
|
+
a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available
|
|
88
|
+
for as long as the Licensee continues to distribute or communicate the Work.
|
|
89
|
+
Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names
|
|
90
|
+
of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and
|
|
91
|
+
reproducing the content of the copyright notice.
|
|
92
|
+
|
|
93
|
+
6.Chain of Authorship
|
|
94
|
+
The original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or
|
|
95
|
+
licensed to him/her and that he/she has the power and authority to grant the Licence.
|
|
96
|
+
Each Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or
|
|
97
|
+
licensed to him/her and that he/she has the power and authority to grant the Licence.
|
|
98
|
+
Each time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions
|
|
99
|
+
to the Work, under the terms of this Licence.
|
|
100
|
+
|
|
101
|
+
7.Disclaimer of Warranty
|
|
102
|
+
The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work
|
|
103
|
+
and may therefore contain defects or ‘bugs’ inherent to this type of development.
|
|
104
|
+
For the above reason, the Work is provided under the Licence on an ‘as is’ basis and without warranties of any kind
|
|
105
|
+
concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or
|
|
106
|
+
errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this
|
|
107
|
+
Licence.
|
|
108
|
+
This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work.
|
|
109
|
+
|
|
110
|
+
8.Disclaimer of Liability
|
|
111
|
+
Except in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be
|
|
112
|
+
liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the
|
|
113
|
+
Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss
|
|
114
|
+
of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However,
|
|
115
|
+
the Licensor will be liable under statutory product liability laws as far such laws apply to the Work.
|
|
116
|
+
|
|
117
|
+
9.Additional agreements
|
|
118
|
+
While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services
|
|
119
|
+
consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole
|
|
120
|
+
responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify,
|
|
121
|
+
defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by
|
|
122
|
+
the fact You have accepted any warranty or additional liability.
|
|
123
|
+
|
|
124
|
+
10.Acceptance of the Licence
|
|
125
|
+
The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ placed under the bottom of a window
|
|
126
|
+
displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of
|
|
127
|
+
applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms
|
|
128
|
+
and conditions.
|
|
129
|
+
Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You
|
|
130
|
+
by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution
|
|
131
|
+
or Communication by You of the Work or copies thereof.
|
|
132
|
+
|
|
133
|
+
11.Information to the public
|
|
134
|
+
In case of any Distribution or Communication of the Work by means of electronic communication by You (for example,
|
|
135
|
+
by offering to download the Work from a remote location) the distribution channel or media (for example, a website)
|
|
136
|
+
must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence
|
|
137
|
+
and the way it may be accessible, concluded, stored and reproduced by the Licensee.
|
|
138
|
+
|
|
139
|
+
12.Termination of the Licence
|
|
140
|
+
The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms
|
|
141
|
+
of the Licence.
|
|
142
|
+
Such a termination will not terminate the licences of any person who has received the Work from the Licensee under
|
|
143
|
+
the Licence, provided such persons remain in full compliance with the Licence.
|
|
144
|
+
|
|
145
|
+
13.Miscellaneous
|
|
146
|
+
Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the
|
|
147
|
+
Work.
|
|
148
|
+
If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or
|
|
149
|
+
enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid
|
|
150
|
+
and enforceable.
|
|
151
|
+
The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of
|
|
152
|
+
the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence.
|
|
153
|
+
New versions of the Licence will be published with a unique version number.
|
|
154
|
+
All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take
|
|
155
|
+
advantage of the linguistic version of their choice.
|
|
156
|
+
|
|
157
|
+
14.Jurisdiction
|
|
158
|
+
Without prejudice to specific agreement between parties,
|
|
159
|
+
— any litigation resulting from the interpretation of this License, arising between the European Union institutions,
|
|
160
|
+
bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice
|
|
161
|
+
of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union,
|
|
162
|
+
— any litigation arising between other parties and resulting from the interpretation of this License, will be subject to
|
|
163
|
+
the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business.
|
|
164
|
+
|
|
165
|
+
15.Applicable Law
|
|
166
|
+
Without prejudice to specific agreement between parties,
|
|
167
|
+
— this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat,
|
|
168
|
+
resides or has his registered office,
|
|
169
|
+
— this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside
|
|
170
|
+
a European Union Member State.
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
Appendix
|
|
174
|
+
|
|
175
|
+
‘Compatible Licences’ according to Article 5 EUPL are:
|
|
176
|
+
— GNU General Public License (GPL) v. 2, v. 3
|
|
177
|
+
— GNU Affero General Public License (AGPL) v. 3
|
|
178
|
+
— Open Software License (OSL) v. 2.1, v. 3.0
|
|
179
|
+
— Eclipse Public License (EPL) v. 1.0
|
|
180
|
+
— CeCILL v. 2.0, v. 2.1
|
|
181
|
+
— Mozilla Public Licence (MPL) v. 2
|
|
182
|
+
— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
|
|
183
|
+
— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software
|
|
184
|
+
— European Union Public Licence (EUPL) v. 1.1, v. 1.2
|
|
185
|
+
— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+).
|
|
186
|
+
|
|
187
|
+
The European Commission may update this Appendix to later versions of the above licences without producing
|
|
188
|
+
a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the
|
|
189
|
+
covered Source Code from exclusive appropriation.
|
|
190
|
+
All other changes or additions to this Appendix require the production of a new EUPL version.
|
keyclerk-0.26.3/NOTICE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
KeyClerk
|
|
2
|
+
Copyright 2026 VDHome and the KeyClerk contributors
|
|
3
|
+
|
|
4
|
+
Licensed under the European Union Public Licence, version 1.2 or later (EUPL-1.2), the text of which is in LICENSE.
|
|
5
|
+
Each file states its licence with an SPDX identifier, or is covered by REUSE.toml.
|
|
6
|
+
|
|
7
|
+
Third-party code shipped with KeyClerk keeps its own licence: keyclerk/ui/vendor/cytoscape.min.js (MIT, see
|
|
8
|
+
keyclerk/ui/vendor/cytoscape.LICENSE).
|
keyclerk-0.26.3/PKG-INFO
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: keyclerk
|
|
3
|
+
Version: 0.26.3
|
|
4
|
+
Summary: Access leases, delegated administration and access paths for Keycloak, decided by policy and driven by configuration
|
|
5
|
+
License-Expression: EUPL-1.2
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: NOTICE
|
|
10
|
+
Requires-Dist: pyyaml>=6
|
|
11
|
+
Requires-Dist: jsonschema>=4
|
|
12
|
+
Requires-Dist: cryptography>=42
|
|
13
|
+
Requires-Dist: segno>=1.6
|
|
14
|
+
Requires-Dist: wasmtime>=49
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
17
|
+
Requires-Dist: hypothesis==6.170.8; extra == "dev"
|
|
18
|
+
Requires-Dist: libosdp==3.2.7; extra == "dev"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<picture>
|
|
23
|
+
<source media="(prefers-color-scheme: dark)" srcset="brand/wordmark/keyclerk-lockup-dark.svg">
|
|
24
|
+
<img src="brand/wordmark/keyclerk-lockup-light.svg" alt="KeyClerk" width="360">
|
|
25
|
+
</picture>
|
|
26
|
+
</p>
|
|
27
|
+
<p align="center"><em>Every key signed. Every key returned.</em></p>
|
|
28
|
+
<p align="center">
|
|
29
|
+
<a href="https://github.com/keyclerk/keyclerk/actions/workflows/ci.yml"><img src="https://github.com/keyclerk/keyclerk/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
30
|
+
<a href="https://scorecard.dev/viewer/?uri=github.com/keyclerk/keyclerk"><img src="https://api.scorecard.dev/projects/github.com/keyclerk/keyclerk/badge" alt="OpenSSF Scorecard"></a>
|
|
31
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/licence-EUPL--1.2-blue" alt="Licence: EUPL-1.2"></a>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
# KeyClerk
|
|
35
|
+
|
|
36
|
+
Access leases for Keycloak. KeyClerk receives people, checks who they are, and hands out keys that end by
|
|
37
|
+
themselves: for employees, contractors, visitors and partners, for applications, privileged roles and doors.
|
|
38
|
+
|
|
39
|
+
> Status: engine 0.26.3 (2026-10-26). The source is public; no package is released yet. It runs on one host for a demo
|
|
40
|
+
> and a local test stack; nobody else should run it in production yet. What follows separates what runs today, what exists as a tested library that nothing runs yet,
|
|
41
|
+
> and what is designed only. A claim moves up a column only with the code and a test.
|
|
42
|
+
|
|
43
|
+
## Why
|
|
44
|
+
|
|
45
|
+
Keycloak signs people in very well. It does not answer the governance questions around that: who asked for this
|
|
46
|
+
access, who approved it, until when, is it still needed, and is it really gone when it should be. Full identity
|
|
47
|
+
governance suites answer them, but they are large. KeyClerk is the small piece in between: grants as records, decided
|
|
48
|
+
by policy, applied by one writer, ending on time. What it does that such suites usually do not, and how each part is
|
|
49
|
+
meant to grow: [docs/what-sets-keyclerk-apart.md](docs/what-sets-keyclerk-apart.md).
|
|
50
|
+
|
|
51
|
+
## What runs today
|
|
52
|
+
|
|
53
|
+
- **Signed records.** Every grant, lease, group, delegation and approval is a record in one YAML document, checked
|
|
54
|
+
against `keyclerk/schema/records.schema.json`. The desk writes it: each change becomes a new revision, signed with the
|
|
55
|
+
desk's key, with its log line signed and hash-chained. History is append-only (`records.guard`): a revoked grant
|
|
56
|
+
never comes back. A deployment may keep the document in git; KeyClerk does not require it and has no merge-request
|
|
57
|
+
flow of its own. From 0.26.0 a deployment can choose the signed change log on SQLite instead (`store.kind: sqlite`,
|
|
58
|
+
decision 0022): the desk proposes each change through the checking signer, which judges it and signs a checkpoint
|
|
59
|
+
per change; personal data stays outside the log, which carries internal anchors only (decision 0028). A running
|
|
60
|
+
YAML desk moves with `keyclerk.store.migrate` (docs/migrate-to-change-log.md).
|
|
61
|
+
- **Policy decides, in process.** The Rego in `keyclerk/policy/` is compiled to WebAssembly and evaluated in process
|
|
62
|
+
(wasmtime); no OPA server runs. Some policies (`access.rego`, `delegation.rego`, `fallback.rego`) are still mirrored in
|
|
63
|
+
Python and their Rego is only tested, not evaluated at run time.
|
|
64
|
+
- **One writer, per host.** The reconciler (`keyclerk.reconcile`) is the only component that changes Keycloak: it
|
|
65
|
+
compares the records with the realm, adds what is due, removes what has ended, never touches a membership it did not
|
|
66
|
+
create, and stops when a run would remove more than the configured cap. The writer lock is a local file: two
|
|
67
|
+
deployments with their own state would both write (decision 0013 records the fix, not built).
|
|
68
|
+
- **State is derived.** Whether a grant is pending, active, ended or revoked is computed from the records and the clock
|
|
69
|
+
(`keyclerk/access_state.py`). Clock skew can only narrow access.
|
|
70
|
+
- **The desk and its API** (`keyclerk.api`): people sign in at the broker and see what they hold and manage and why;
|
|
71
|
+
deputies change groups, and each change is applied, held for a second key, or refused with its reasons. Approvals can
|
|
72
|
+
be the approver's own signature at the broker (decision 0006). What that proof does not cover is written in
|
|
73
|
+
`keyclerk/approvals.py`: a desk compromised while it serves its pages could show one change and have another signed.
|
|
74
|
+
A high-risk change also carries its proposer's own signature, which the checking signer requires (decision 0015);
|
|
75
|
+
a low-risk change's proposer is vouched for by the desk alone.
|
|
76
|
+
- **The read-only console** (`keyclerk.console`) and need-to-know views: the persona graph and the delegation tree.
|
|
77
|
+
- **Leases for externals:** lease records, lease groups, and generated Keycloak Workflow definitions for reminders and
|
|
78
|
+
blocking (`keyclerk/leases.py`); the Workflows themselves run in Keycloak.
|
|
79
|
+
- **Visits** (decision 0009, 0010): person-first visits, QR codes and badges at the desk. Frozen since 2026-10-23 until
|
|
80
|
+
the core is complete. A visit is its own file beside the records, vouched for by the signed log, and erased
|
|
81
|
+
`visits.retention_days` (default 30) after it closes (decision 0009, amendment).
|
|
82
|
+
- **Personas** (decision 0011, 0011a change 1): daily and admin personas, workloads and agents; the persona rule is on
|
|
83
|
+
`enforce` by default since 0.16.1 (set `identity.persona_rule: warn` to see the impact first).
|
|
84
|
+
|
|
85
|
+
- **Edges** (decision 0014): site appliances that enforce leases on a bastion while they may be cut off. The desk
|
|
86
|
+
enrols them (the root with the security officer's key), cuts a signed bundle per edge inside the ceiling its officer
|
|
87
|
+
signed, takes signed requests from the edge agent only (`keyclerk/edge.py`), relays ciphertexts to person keys that a
|
|
88
|
+
passkey derives through WebAuthn PRF and that the desk cannot read (`keyclerk/relay.py`, `keyclerk/ui/personkey.js`),
|
|
89
|
+
and acknowledges escrowed break-glass halves and evidence chains. The edge policy ships as compiled WebAssembly with a
|
|
90
|
+
conformance set (`tests/conformance/edge.json`). The bundle key is still a file of its own; moving every granting key into the checking signer is decision 0021,
|
|
91
|
+
not built. Since edge protocol 1.3 a bundle carries no e-mail address: an edge fetches it sealed when it needs it.
|
|
92
|
+
|
|
93
|
+
## Tested libraries that nothing runs yet
|
|
94
|
+
|
|
95
|
+
`keyclerk/apply_engine.py` (the highly available apply engine with leader leases and a reduction journal),
|
|
96
|
+
`keyclerk/events.py` (CloudEvents carrying OCSF, an outbox), `keyclerk/access_paths.py`, `keyclerk/keys.py`,
|
|
97
|
+
`keyclerk/pools.py` (access paths, custody, pools and fallback ladders), and `keyclerk/piam/` (physical access through a
|
|
98
|
+
PACS or OSDP). Each has unit tests; none is wired into the reconciler, the desk or the console.
|
|
99
|
+
|
|
100
|
+
## Designed, not built
|
|
101
|
+
|
|
102
|
+
- A separate confirmation page that shows the change before the approver signs (decision 0006).
|
|
103
|
+
- Proof of identity normalised into OpenID Identity Assurance, and trust per kind of proof.
|
|
104
|
+
- Privacy registry (purpose, legal basis, retention class per field), sealed proof copies, crypto-erasure.
|
|
105
|
+
- Reviews (event-driven and signed, not campaigns), dormancy, impact diffs in approvals, SoD exceptions, holder
|
|
106
|
+
bounds, outliers, orphan accounts, agents bounded by their person, a witness and leaver proof (decision 0020); joiners,
|
|
107
|
+
movers and leavers from HR (decision 0004).
|
|
108
|
+
- SIEM and SOAR interfaces, identity visibility and Shared Signals (0018 H4 to H6), off-site backup with restore drills
|
|
109
|
+
and restore safety (0019), every granting key in the checking signer (0021).
|
|
110
|
+
- Many targets per deployment and one writer enforced by credential (decision 0013), persona history (0012), admin
|
|
111
|
+
personas by plane changes 2 to 5 (0011a), invitations and access in hours (0010).
|
|
112
|
+
- For the signed change log (decision 0022, running since 0.26.0): PostgreSQL as a replica, signed snapshots, the
|
|
113
|
+
witness co-signing the per-change checkpoints (0021).
|
|
114
|
+
|
|
115
|
+
How KeyClerk is kept secure, and how to report a problem privately: [SECURITY.md](SECURITY.md) and the threat model
|
|
116
|
+
([docs/threat-model.md](docs/threat-model.md)). Why it works as it does: the decision records in
|
|
117
|
+
[docs/decisions](docs/decisions); what changed in each release: [CHANGELOG.md](CHANGELOG.md); the standards it
|
|
118
|
+
implements and the projects it stands on: [docs/references.md](docs/references.md).
|
|
119
|
+
|
|
120
|
+
## Footprint
|
|
121
|
+
|
|
122
|
+
One image, three commands: `keyclerk.reconcile`, `keyclerk.console` and `keyclerk.api` (the desk). No database server; the desk
|
|
123
|
+
keeps its store on a volume: the YAML document, or the change log in one SQLite file. All state is files in the
|
|
124
|
+
desk's store and the signer's and the reconciler's state directories. The image is built from a tag with hash-locked dependencies.
|
|
125
|
+
|
|
126
|
+
Measured on 0.23.0 (2026-10-24, x86-64, the test fixture's 12 people):
|
|
127
|
+
|
|
128
|
+
| | Today | Minimal image (measured, not adopted) |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| Image, unpacked / compressed | 192 MB / 64 MB | 110 MB / 36 MB |
|
|
131
|
+
| Desk process ready, resident memory | 0.59 s, 80 MB | 0.49 s, 69 MB |
|
|
132
|
+
| Signer, reconciler resident memory | 28 MB, 31 MB | about the same |
|
|
133
|
+
|
|
134
|
+
Of the image, KeyClerk itself is under 3 MB; wasmtime (32 MB) and cryptography (16 MB) are the largest packages.
|
|
135
|
+
Size today: the engine checks are near linear since 0017 (`tools/bench_checks.py`: 1,000 extra people 4.4 s, 5,000 in
|
|
136
|
+
29 s; 136 s at 50 people before). On the YAML store the records are one document, loaded and signed whole on every
|
|
137
|
+
change. That fits up to about 1,000 people: at the desk (`tools/bench_desk.py`) a change takes 7 s at 200 extra people and 32 s at
|
|
138
|
+
1,000, a preview 9 s and 45 s, a desk start 8 s and 41 s; at 5,000 the document alone is 19 MB and takes about 25 s to
|
|
139
|
+
parse and validate. The signed change log on SQLite (decision 0022, from 0.26.0) is built for 500,000 identities:
|
|
140
|
+
at 20,000 employees a desk change takes about 3.4 s and a desk start 52 s (docs/testing/realistic-load.md).
|
|
141
|
+
|
|
142
|
+
## Configuration as code
|
|
143
|
+
|
|
144
|
+
The engine holds no rules of its own. Everything it applies comes from one configuration document that a deployment
|
|
145
|
+
keeps in its own repository, validated against `keyclerk/schema/config.schema.json`:
|
|
146
|
+
|
|
147
|
+
| Section | What it sets |
|
|
148
|
+
|---|---|
|
|
149
|
+
| `roles`, `grant_limits` | roles with plane and class, activation and functional lifetimes, subject kinds, scope levels |
|
|
150
|
+
| `external_access` | lease categories and their limits, reminders, retention |
|
|
151
|
+
| `delegation`, `attributes` | delegated administration of nested groups; the attribute registry with pedigree and freshness |
|
|
152
|
+
| `risk_port` | trust floors per risk class and the limits on risk acceptance |
|
|
153
|
+
| `situation` | what each situation level restricts, and what it may switch on |
|
|
154
|
+
| `places` | the kinds of physical place and how they nest |
|
|
155
|
+
| `access_paths`, `availability` | independent layers per label, scenarios, obligations; pools and fallback ladders |
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from keyclerk import config, delegation
|
|
159
|
+
config.configure("deployment/keyclerk.yaml") # or set KEYCLERK_CONFIG
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Records (grants, leases, groups, delegations, situations) follow `keyclerk/schema/records.schema.json`; trust from a
|
|
163
|
+
risk and trust source follows `keyclerk/schema/trust-input.schema.json`. The Rego in `keyclerk/policy/` reads its data
|
|
164
|
+
under `data.keyclerk`.
|
|
165
|
+
|
|
166
|
+
## Layout
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
keyclerk/ the engine (Python): records, grants, leases, delegation, places, situation levels, risk port,
|
|
170
|
+
the reconciler, the desk and console, connectors; libraries not yet wired in (see above)
|
|
171
|
+
keyclerk/schema/ configuration, records and trust-input schemas
|
|
172
|
+
keyclerk/policy/ the Rego that decides at the enforcement points, with its tests
|
|
173
|
+
tests/ unit tests on a neutral test tenant (tests/fixtures)
|
|
174
|
+
docs/decisions/ why the engine is built the way it is
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Deployments, their configuration, their own policy packs and their examples live in their own repositories; a test
|
|
178
|
+
fails if this repository names one.
|
|
179
|
+
|
|
180
|
+
## Running
|
|
181
|
+
|
|
182
|
+
One image, three commands: the reconciler (the one writer of the broker's managed groups), the read-only console and
|
|
183
|
+
the desk (`keyclerk.api`). Build from a reviewed commit or a signed tag; the image installs only the hash-locked
|
|
184
|
+
`requirements.lock`.
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
git archive HEAD | podman build -t keyclerk:0.26.3 - # or a signed release tag, once releases are published
|
|
188
|
+
podman run --read-only -v ./keyclerk.yaml:/etc/keyclerk/keyclerk.yaml:ro -v ./records.yaml:/etc/keyclerk/records.yaml:ro -v ./records.yaml.sig:/etc/keyclerk/records.yaml.sig:ro -v ./records.pub:/etc/keyclerk/records.pub:ro -v ./writer:/run/secrets/writer:ro -v ./reader:/run/secrets/reader:ro -v keyclerk-state:/var/lib/keyclerk keyclerk:0.26.3 keyclerk.reconcile --config /etc/keyclerk/keyclerk.yaml --records /etc/keyclerk/records.yaml --signing-key /etc/keyclerk/records.pub --keycloak https://id.example --realm example --secret-file /run/secrets/writer --reader-client keyclerk-reader --reader-secret-file /run/secrets/reader --state /var/lib/keyclerk --apply
|
|
189
|
+
podman run --read-only -p 127.0.0.1:8090:8090 ... keyclerk:0.26.3 keyclerk.console --host 0.0.0.0 --allowed-host console.example --oidc-public-issuer https://id.example/realms/example --oidc-redirect https://console.example/callback ...
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Without `--apply` a run is a dry run. Records are applied only when they pass every check, carry a valid signature
|
|
193
|
+
(or the run says `--allow-unsigned`, for a local demo) and do not rewrite history; a run that would remove more than the
|
|
194
|
+
configured cap stops until an override names that exact plan.
|
|
195
|
+
|
|
196
|
+
Keycloak (26.8 and later) needs, per realm: SCIM switched on (`scimApiEnabled`); an admin-only user-profile attribute
|
|
197
|
+
`anchor` annotated `kc.scim.schema.attribute: externalId`, so people are found by their source's immutable id while
|
|
198
|
+
usernames stay human; a writer client (SCIM, `manage-users`), a reader client (`view-users`, `query-users`,
|
|
199
|
+
`query-groups`), both confidential with full scope off and the realm's SCIM base URL as an audience; a public console
|
|
200
|
+
client with PKCE and a `keyclerk-viewer` role for the people allowed to look.
|
|
201
|
+
|
|
202
|
+
## The desk and its API
|
|
203
|
+
|
|
204
|
+
`python -m keyclerk.api` serves the desk: people sign in at the broker and see what they hold and manage and why;
|
|
205
|
+
deputies add and remove members, nest groups, create subgroups and delegate, each change applied, held for a second
|
|
206
|
+
key, or refused by the engine with its reasons. Applied changes are signed: on the YAML store as record revisions, on the change log as links the checking signer
|
|
207
|
+
judges and signs; the reconciler applies either.
|
|
208
|
+
|
|
209
|
+
The front end in `keyclerk/ui` is only one client of the API. Build your own against `/api/v1` (contract:
|
|
210
|
+
`keyclerk/schema/api.openapi.json`, also served at `/api/v1/openapi.json`): a browser front end on the desk's own
|
|
211
|
+
origin uses the session and sends the CSRF token from `/api/v1/me` in `X-KeyClerk-CSRF`; any other front end
|
|
212
|
+
registers its own broker client, is named with `--api-client`, and sends its user's access token as a bearer token.
|
|
213
|
+
The broker must put the person's anchor in the token (an `anchor` claim). See decision 0005.
|
|
214
|
+
|
|
215
|
+
The contract is generated: change `tools/gen_api_contract.py`, run it, and commit both (a test holds them equal).
|
|
216
|
+
It explains itself (every change field and error code carries its meaning), and the bundled front end's API page is
|
|
217
|
+
built from it: every call, what it answers, how to call it from your own front end, reads tried with your own access,
|
|
218
|
+
changes previewed through `POST /changes/preview` (nothing applied, held or logged) and made only after a confirm.
|
|
219
|
+
|
|
220
|
+
Approvals can be the approver's own signature (decision 0006): with `approvals.proofs: required`, approving
|
|
221
|
+
means signing in again at the broker with the change's statement as the nonce; the reconciler verifies every proof
|
|
222
|
+
(pass `--approval-issuer` and `--approval-audience`) and refuses a revision with an approval nobody signed.
|
|
223
|
+
|
|
224
|
+
On Keycloak (decision 0007): give the writer fine-grained permissions for group membership only and set
|
|
225
|
+
`reconcile.logout_on_removal: false`; register `/backchannel-logout` as the back-channel logout URL of the desk's and
|
|
226
|
+
the console's clients; turn on admin events so manual changes are attributed; bind a passkey to an authentication level
|
|
227
|
+
and name it in `approvals.acr`; name clients that must use DPoP with `--dpop-client`.
|
|
228
|
+
|
|
229
|
+
Reads are need-to-know: a viewer and the root see everything; anyone else sees the groups they manage or belong to
|
|
230
|
+
(and the names above them), their own delegation chain and what is below it, and the changes they made, approved,
|
|
231
|
+
or that touch a group they manage or themselves. The directory and the reconciler's runs are for people who manage.
|
|
232
|
+
Writes are limited per person (30 in 10 minutes) and so are requests at once (4); both answer 429 `rate_limited`.
|
|
233
|
+
A change that needs several keys collects them one approval at a time (`approvals` on a pending change).
|
|
234
|
+
|
|
235
|
+
One desk process owns a store (a lock file). Every commit is written ahead and its log line is signed with the
|
|
236
|
+
desk's key; at start the desk repairs what a crash left (a cut log line, an unreadable pending file, an uncommitted
|
|
237
|
+
revision file, half-published records), only towards a revision a signed log line vouches for, and records what it
|
|
238
|
+
did in the log. Anything else stops it with a StoreFault: restore the store from a backup. A store written by 0.4.3 or
|
|
239
|
+
earlier has unsigned log lines and does not start: seed a new one.
|
|
240
|
+
|
|
241
|
+
On the signed change log (`store.kind: sqlite`, decision 0022) the desk holds no signing key: it runs with
|
|
242
|
+
`--signer unix:/run/keyclerk/signer.sock` (or `tcp:` with `--signer-token`), proposes every change to the checking
|
|
243
|
+
signer (`python -m keyclerk.signer --key ... --state ... --config ... --listen ...`), and follows the signer's log every
|
|
244
|
+
15 s. The reconciler talks to the signer on a listener of its own (`--reconciler-listen` with its own
|
|
245
|
+
`--reconciler-token`): only there is a person's sign-in account linked to them, never from the desk. With the signer
|
|
246
|
+
down, reads go on and writes are refused, never queued; readiness says why. A running YAML desk
|
|
247
|
+
moves with `python -m keyclerk.store.migrate`: the runbook, with the way back, is
|
|
248
|
+
[docs/migrate-to-change-log.md](docs/migrate-to-change-log.md).
|
|
249
|
+
|
|
250
|
+
## Health and metrics
|
|
251
|
+
|
|
252
|
+
Decision 0018 (H1 to H3). On the desk's own port, answered for any Host because an orchestrator probes by address:
|
|
253
|
+
`/healthz` says `ok` while the process serves; `/readyz` says `ready` (200) or `not ready` (503) and nothing more.
|
|
254
|
+
Ready means the store opens and its change log verifies, the policy modules load, the checking signer answers, and
|
|
255
|
+
the reconciler's last run started within `reconcile.ready_max_age_seconds` (default 900; only when `--broker-state` is
|
|
256
|
+
given). A broker that is down never makes the desk unready: it shows in the metrics. The answer is computed at most
|
|
257
|
+
every 5 seconds, however often it is asked.
|
|
258
|
+
|
|
259
|
+
The management listener is off unless `--mgmt-port` is given (default host 127.0.0.1), with its own bearer token from
|
|
260
|
+
`--mgmt-token-file` (at least 32 characters), read only. `/metrics` is Prometheus text, standard library only, from a
|
|
261
|
+
fixed list (`keyclerk/metrics.py`, each metric naming its alert or budget); labels come from fixed value lists, and no
|
|
262
|
+
person, group or edge id is ever a label. `/readyz` there gives each check with its reason. A push stack uses an
|
|
263
|
+
OpenTelemetry Collector that scrapes this page. The signer answers `{"op": "ping"}`; the reconciler's liveness is the
|
|
264
|
+
age of its last run.
|
|
265
|
+
|
|
266
|
+
## Developing
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
pip install -e ".[dev]"
|
|
270
|
+
git config core.hooksPath hooks # the pre-push gate
|
|
271
|
+
pytest
|
|
272
|
+
opa test keyclerk/policy/
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Prior art and acknowledgements
|
|
276
|
+
|
|
277
|
+
KeyClerk did not start from a blank page. Before building it we searched GitHub, GitLab, npm and PyPI (about a
|
|
278
|
+
hundred queries, October 2026) and studied the projects below in depth. We borrowed ideas, not code.
|
|
279
|
+
|
|
280
|
+
- **[discord/access](https://github.com/discord/access)** (Apache-2.0). KeyClerk's access-request, owner-approval and
|
|
281
|
+
expiring-access-review flows are informed by Discord's open-source Access: constraint tags with strictest-wins
|
|
282
|
+
limits, derived grants that never outlive their source, approver sets computed from owners, the owner's review of
|
|
283
|
+
expiring access, and a typed audit event list. KeyClerk differs by keeping grants in git, writing Keycloak through
|
|
284
|
+
one reconciler and signing approvals.
|
|
285
|
+
- **[GoogleCloudPlatform/jit-groups](https://github.com/GoogleCloudPlatform/jit-groups)** (Apache-2.0). KeyClerk's
|
|
286
|
+
policy shape (a hierarchy with inherited access lists, join and approve constraints, a mandatory expiry) and its
|
|
287
|
+
split between joining, approving yourself and approving others follow ideas from Google Cloud's JIT Groups, as does
|
|
288
|
+
re-evaluating policy at approval time. KeyClerk differs by deciding with OPA and keeping a record of every request
|
|
289
|
+
and approval.
|
|
290
|
+
- **[aws-samples/iam-identity-center-team](https://github.com/aws-samples/iam-identity-center-team)** (MIT-0).
|
|
291
|
+
KeyClerk's request lifecycle, the split between eligibility and approver policy, scheduled elevation, failing closed
|
|
292
|
+
when no approver exists and linking session activity to the elevation window draw on AWS's TEAM for IAM Identity
|
|
293
|
+
Center. KeyClerk derives these states from records and the clock instead of storing them in a workflow engine.
|
|
294
|
+
|
|
295
|
+
Thanks to the teams at Discord, Google Cloud and AWS for publishing clear designs. Also standing on the shoulders of:
|
|
296
|
+
[Keycloak](https://www.keycloak.org/), [Open Policy Agent](https://www.openpolicyagent.org/),
|
|
297
|
+
[keycloak-config-cli](https://github.com/adorsys/keycloak-config-cli), [Keycloakify](https://www.keycloakify.dev/),
|
|
298
|
+
[OpenID Identity Assurance](https://openid.net/specs/openid-ida-verified-claims-1_0-final.html),
|
|
299
|
+
[midPoint](https://evolveum.com/midpoint/) (a full identity governance suite KeyClerk can work next to),
|
|
300
|
+
[libosdp](https://github.com/goToMain/libosdp) and [NATS](https://nats.io/). The closest earlier attempt we found,
|
|
301
|
+
[4nass/keycloak-access-requests](https://github.com/4nass/keycloak-access-requests), confirmed the need and the rule
|
|
302
|
+
that a governance tool never removes what it did not create.
|
|
303
|
+
|
|
304
|
+
## Licence
|
|
305
|
+
|
|
306
|
+
EUPL-1.2 (European Union Public Licence, version 1.2): see LICENSE and NOTICE.
|
|
307
|
+
|
|
308
|
+
## Security
|
|
309
|
+
|
|
310
|
+
Please report vulnerabilities privately as described in SECURITY.md, never in a public issue.
|