authkeys 0.4.0__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.
Files changed (50) hide show
  1. authkeys-0.4.0/.gitignore +29 -0
  2. authkeys-0.4.0/CHANGELOG.md +196 -0
  3. authkeys-0.4.0/CONTRIBUTING.md +93 -0
  4. authkeys-0.4.0/LICENSE +21 -0
  5. authkeys-0.4.0/PKG-INFO +170 -0
  6. authkeys-0.4.0/README.md +121 -0
  7. authkeys-0.4.0/docs/api/authkeys.md +15 -0
  8. authkeys-0.4.0/docs/api/cache.md +3 -0
  9. authkeys-0.4.0/docs/api/config.md +3 -0
  10. authkeys-0.4.0/docs/api/reference.md +10 -0
  11. authkeys-0.4.0/docs/api/server.md +3 -0
  12. authkeys-0.4.0/docs/api/sources.md +9 -0
  13. authkeys-0.4.0/docs/changelog.md +3 -0
  14. authkeys-0.4.0/docs/guide/cli.md +50 -0
  15. authkeys-0.4.0/docs/guide/configuration.md +75 -0
  16. authkeys-0.4.0/docs/guide/server.md +63 -0
  17. authkeys-0.4.0/docs/guide/sources.md +96 -0
  18. authkeys-0.4.0/docs/index.md +60 -0
  19. authkeys-0.4.0/examples/authkeys-serve.service +66 -0
  20. authkeys-0.4.0/examples/authkeys.conf +96 -0
  21. authkeys-0.4.0/mkdocs.yml +61 -0
  22. authkeys-0.4.0/pyproject.toml +62 -0
  23. authkeys-0.4.0/requirements.txt +1 -0
  24. authkeys-0.4.0/requirements_dev.txt +0 -0
  25. authkeys-0.4.0/src/authkeys/__init__.py +382 -0
  26. authkeys-0.4.0/src/authkeys/__main__.py +10 -0
  27. authkeys-0.4.0/src/authkeys/cache.py +211 -0
  28. authkeys-0.4.0/src/authkeys/cli.py +326 -0
  29. authkeys-0.4.0/src/authkeys/config.py +107 -0
  30. authkeys-0.4.0/src/authkeys/groupmembers.py +10 -0
  31. authkeys-0.4.0/src/authkeys/py.typed +0 -0
  32. authkeys-0.4.0/src/authkeys/server.py +135 -0
  33. authkeys-0.4.0/src/authkeys/sources/__init__.py +53 -0
  34. authkeys-0.4.0/src/authkeys/sources/file.py +28 -0
  35. authkeys-0.4.0/src/authkeys/sources/github.py +42 -0
  36. authkeys-0.4.0/src/authkeys/sources/http.py +67 -0
  37. authkeys-0.4.0/src/authkeys/sources/ldap.py +134 -0
  38. authkeys-0.4.0/src/authkeys/usermaps.py +9 -0
  39. authkeys-0.4.0/src/authkeys/utils.py +49 -0
  40. authkeys-0.4.0/tests/test_authkeys.py +265 -0
  41. authkeys-0.4.0/tests/test_cli_check.py +47 -0
  42. authkeys-0.4.0/tests/test_cli_commands.py +126 -0
  43. authkeys-0.4.0/tests/test_cli_completion.py +20 -0
  44. authkeys-0.4.0/tests/test_config.py +78 -0
  45. authkeys-0.4.0/tests/test_plan_execution.py +272 -0
  46. authkeys-0.4.0/tests/test_review_fixes.py +225 -0
  47. authkeys-0.4.0/tests/test_server.py +133 -0
  48. authkeys-0.4.0/tests/test_sources_file.py +60 -0
  49. authkeys-0.4.0/tests/test_sources_github.py +109 -0
  50. authkeys-0.4.0/tests/test_sources_registry.py +42 -0
@@ -0,0 +1,29 @@
1
+ # Build/dist output and generated docs site
2
+ dist/
3
+ build/
4
+ site/
5
+ *.egg-info/
6
+
7
+ # Agent/harness tooling (never commit these)
8
+ AGENTS.md
9
+ .agents/
10
+ CLAUDE.md
11
+ CLAUDE.local.md
12
+ .claude/
13
+
14
+ # Python
15
+ __pycache__/
16
+ *.py[cod]
17
+ .pytest_cache/
18
+ .mypy_cache/
19
+ .ruff_cache/
20
+ .coverage
21
+ .coverage.*
22
+ htmlcov/
23
+ .venv/
24
+
25
+ # Project-specific
26
+ /.pyenv
27
+ /.env
28
+ /_*
29
+ /src/_*
@@ -0,0 +1,196 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.4.0] - 2026-07-20
8
+
9
+ Feature release with fixes — no breaking changes.
10
+
11
+ ### Fixed
12
+ - **`--config` with a Windows absolute path was silently ignored.** The
13
+ colon-separated path list was split naively, so `C:\path\authkeys.conf` became
14
+ `["C", "\path\authkeys.conf"]` and the file was never read — the caller got an
15
+ empty config with no error. For `authkeys serve` this also skipped the
16
+ fail-closed `api_key` check and left the server running unauthenticated.
17
+ Drive-letter prefixes are now preserved; POSIX `a.conf:b.conf` lists still split.
18
+ - CI reliability (test-only, no runtime code changed):
19
+ - The HTTP key-server tests now bypass any proxy configured in the environment
20
+ (GitHub's Windows runners set `HTTP(S)_PROXY`), which otherwise routed the
21
+ localhost test requests through an unreachable proxy and hung.
22
+ - A concurrency test joined its worker threads without a timeout; the join is
23
+ now bounded and the threads are daemons.
24
+ - A hard per-test `timeout` (via `pytest-timeout`) is configured so any future
25
+ socket/thread stall fails fast with a traceback instead of hanging the job.
26
+
27
+ ### Added
28
+ - **Options-prefixed `authorized_keys` lines are now preserved verbatim.** A
29
+ real-world line like `command="...",no-pty ssh-rsa AAAA bob` was previously
30
+ misparsed (the options list was read as the key type). `AuthorizedKey` gains
31
+ an `options: str = ""` field; `parse` now detects an options prefix (a first
32
+ token containing `=`, or not matching a recognized key-type prefix) and
33
+ keeps it separate from `type`/`key`/`comment`, round-tripping byte-for-byte
34
+ through `resolve`/`serve` output. Deduplication now keys on
35
+ `(options, type, key)`, so an option-bearing key is kept distinct from the
36
+ bare form of the same key.
37
+ - **`--format authorized_keys|json`** on `authkeys resolve` and
38
+ `authkeys check`. `authorized_keys` (default) is unchanged; `json` prints a
39
+ single JSON array of `{"type", "key", "comment", "options"}` objects instead,
40
+ for scripting/tooling consumers. `serve` is unaffected (always sshd wire
41
+ format).
42
+ - **`examples/authkeys-serve.service`**: a template systemd unit for running
43
+ `authkeys serve`, with hardening directives (`DynamicUser=`,
44
+ `NoNewPrivileges=true`, `ProtectSystem=strict`, `PrivateTmp=true`, a minimal
45
+ `RestrictAddressFamilies=`, dropped capabilities) and both ways to supply
46
+ `AUTHKEYS_APIKEY` without putting it in `authkeys.conf`. Documented in
47
+ `docs/guide/server.md`, including why `ProtectHome=` is `read-only` rather
48
+ than stricter (the service needs to read users' `~/.ssh`).
49
+
50
+ ### Changed
51
+ - **Internal refactor**: `Source` is now a `@dataclass` instead of an
52
+ `argparse.Namespace` subclass. Same fields (`cached`/`enabled`/`backend`/
53
+ `sanitize`/`expire`), same `from_config` signature, no behavior change.
54
+
55
+ ## [0.3.0] - 2026-07-19
56
+
57
+ Additive feature release — no breaking changes.
58
+
59
+ ### Added
60
+ - **`authkeys.sources.github`**: fetch a user's public keys from GitHub's
61
+ plain-text `.keys` endpoint (`https://github.com/{username}.keys` by
62
+ default). The `url` template can be overridden to point at any forge with
63
+ the same convention (e.g. GitLab), so no separate source class is needed.
64
+ Uses the existing `[http]` extra (`requests`); no new extra.
65
+ - **`authkeys check <user>`**: a debugging counterpart to `resolve` that
66
+ prints the same resolved keys to stdout while surfacing the per-source
67
+ resolution trace (which source served/cached which key) on stderr.
68
+ - **`authkeys completion [bash|zsh|fish]`**: prints a self-contained shell
69
+ completion script (generated from the CLI's own argument parser via
70
+ `duho`'s completion support) for the given shell.
71
+ - **Source registry**: `authkeys.sources.register_source(alias, cls)` and
72
+ `get_source(alias)` for programmatic lookup of source classes by short
73
+ name. The four built-in sources (`authorizedkeys`, `http`, `github`,
74
+ `ldap`) are registered through it; existing `backend =
75
+ authkeys.sources.<alias>` config resolution is unchanged.
76
+
77
+ ### Changed
78
+ - `KeyServer.__init__` gained a `require_auth: bool = False` parameter: when
79
+ `True` and `api_key` is falsy, construction raises `ValueError` instead of
80
+ silently starting with authentication disabled. `authkeys serve` now passes
81
+ `require_auth=True` whenever `[serve]` configures an `api_key`, in addition
82
+ to its existing friendly `SystemExit` message — the user-facing CLI
83
+ behavior is unchanged, but the fail-closed invariant now also holds for
84
+ direct `KeyServer` construction (e.g. embedding authkeys as a library).
85
+
86
+ ## [0.2.0] - 2026-07-19
87
+
88
+ A security and robustness release from a second review pass. **One breaking
89
+ change** in the LDAP source (see below).
90
+
91
+ ### Security
92
+ - **LDAP now verifies the server certificate by default** (breaking). Previously
93
+ the LDAPS connection trusted any peer certificate. A new `tls_verify` option
94
+ controls it: unset/`system` verifies against the OS trust store (default), a
95
+ path verifies against that CA file/directory, and `none` disables verification
96
+ (insecure; logs a warning). **Deployments that relied on no verification must
97
+ set `tls_verify = none`.** `tls_cert`/`tls_key` are now documented as the client
98
+ mutual-TLS credentials, not server verification.
99
+ - The HTTP key server no longer 500s (with a traceback) on a non-ASCII `apikey`;
100
+ it returns a clean 401.
101
+ - `authkeys serve` caps the number of `?username=` parameters per request
102
+ (`max_usernames`, default 16), bounding the work an unauthenticated request can
103
+ force.
104
+
105
+ ### Fixed
106
+ - A single malformed line in a source (e.g. a corrupt `authorized_keys`) is now
107
+ skipped and logged instead of discarding **all** of that user's keys — which,
108
+ with `expired_on_error`, could previously serve stale/revoked keys.
109
+ - The HTTP source now raises on a non-2xx status, so a transient 5xx/403 routes
110
+ through `expired_on_error` (stale fallback) instead of being cached as "this
111
+ user has no keys". Errored fetches are never cached as empty.
112
+ - The resolve path never emits a traceback: on a config/internal error it logs one
113
+ line to stderr and exits non-zero (exit 3), keeping `auth.log` clean.
114
+ - Bounded LDAP connection/receive timeouts, and the resolver no longer holds its
115
+ lock across the upstream fetch, so one slow/hung source can't stall other
116
+ logins (or, in `serve`, other concurrent requests).
117
+ - Emitted keys are deduplicated by `(type, key)`, so the same public key coming
118
+ from two sources is no longer emitted twice with different comments.
119
+
120
+ ### Added
121
+ - **Negative caching** with its own `negative_expire` TTL for keyless principals.
122
+ - **Per-source `expire`** overriding `[cache] expire` (`0` = don't cache a source).
123
+ - **Atomic file-cache writes** (temp file + rename) with `0600`/`0700` perms.
124
+ - **`authkeys cache`** subcommand: `show`, `purge` (`--user`/`--source`/
125
+ `--expired`/`--all`), and `warm <users...>`; plus optional `max_age`/
126
+ `max_entries` eviction bounds for the file backend.
127
+
128
+ ### Changed
129
+ - `authkeys.__version__` is now read from the installed distribution metadata
130
+ (single source of truth with `pyproject.toml`).
131
+
132
+ ## [0.1.1] - 2026-07-18
133
+
134
+ Hardening release from a code-review pass. No API breaks.
135
+
136
+ ### Security
137
+ - LDAP source now escapes the username in the search filter
138
+ (`escape_filter_chars`), preventing LDAP filter injection when the username is
139
+ attacker-controlled (e.g. via the HTTP key server).
140
+ - HTTP source percent-encodes the username before templating it into the request
141
+ URL, preventing path/query tampering of the upstream key service.
142
+ - `authkeys serve` now fails closed: if `[serve] api_key` is present but resolves
143
+ empty (e.g. an unset `${env:...}`), it refuses to start instead of silently
144
+ disabling authentication. A truly absent `api_key` still runs unauthenticated
145
+ with a warning.
146
+
147
+ ### Fixed
148
+ - HTTP source `verify` now interprets bool-like values (`true`/`false`/...) as a
149
+ boolean; other values remain a CA-bundle path. Previously `verify = false` was
150
+ passed to requests as the string `"false"` (a CA path) and did not disable
151
+ verification.
152
+ - The key server (`ThreadingHTTPServer`) now resolves under a lock, so concurrent
153
+ requests no longer race on the shared cache backend or issue duplicate upstream
154
+ fetches for the same key.
155
+ - A malformed cache `expire` value falls back to the default TTL instead of
156
+ disabling caching entirely (and no longer masks genuine backend errors).
157
+
158
+ ### Changed
159
+ - `authkeys serve` now honors each user's `~/.ssh/authkeys.conf` user/group
160
+ delegation, matching `authkeys resolve` (both go through the new
161
+ `AuthKeys.resolve()`).
162
+ - `authkeys <user>` default-command insertion is more robust: a leading flag
163
+ before a bare username (`authkeys -v alice`) is routed to the `resolve`
164
+ subparser instead of erroring.
165
+
166
+ ## [0.1.0] - 2026-07-18
167
+
168
+ Initial packaged release: a `src/` layout, a `duho`-based CLI, and a published
169
+ `authkeys` console script.
170
+
171
+ ### Added
172
+ - Declarative CLI (`authkeys resolve` / `authkeys serve`) built on `duho`, with
173
+ `--verbose`/`--quiet`/`--loglevel` and `--version`. `resolve` is the default
174
+ command so `authkeys %u` works directly as an `AuthorizedKeysCommand`.
175
+ - Config-driven HTTP key server (`authkeys serve`): bind/port/path/API key from a
176
+ `[serve]` section, constant-time API-key comparison, threaded server with clean
177
+ shutdown, and `${env:VAR}` interpolation to keep secrets off disk.
178
+ - `file`, `http`, and `ldap` key sources with lazy imports of their optional
179
+ dependencies (`requests`, `ldap3`, `cryptography`); `http` gains request
180
+ timeouts and configurable TLS verification.
181
+ - Packaging: `pyproject.toml` with `http`/`ldap`/`all`/`dev` extras, `py.typed`,
182
+ MIT license, and PyPI classifiers. Supports Python 3.9–3.14.
183
+
184
+ ### Fixed
185
+ - In-memory and file cache backends now store and retrieve entries by
186
+ `(uid, source)` and carry a timestamp, so cache lookups actually hit and the
187
+ TTL is honored (the previous mem backend never returned a cached value).
188
+ - `Source.from_config` is a real `classmethod`; source defaults (e.g. the default
189
+ sanitizer) are applied when a section omits them.
190
+ - Group resolution failures during user-config loading are logged and skipped
191
+ instead of aborting key resolution.
192
+ - Blank lines in `authorized_keys` files are no longer emitted as empty keys.
193
+
194
+ ### Changed
195
+ - Replaced the shell `bin/authkeys` wrapper and standalone `keygrabber` script
196
+ with the unified `authkeys` CLI and `authkeys serve` subcommand.
@@ -0,0 +1,93 @@
1
+ # Contributing to authkeys
2
+
3
+ Thanks for your interest in contributing! Here's how to get started.
4
+
5
+ ## Development Setup
6
+
7
+ ```bash
8
+ # Clone the repo
9
+ git clone https://github.com/jose-pr/authkeys.git
10
+ cd authkeys
11
+
12
+ # Create a virtual environment
13
+ python -m venv .venv
14
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
15
+
16
+ # Install in development mode with test dependencies and the optional extras
17
+ # whose tests you want to run (the LDAP test skips without ldap3).
18
+ pip install -e ".[dev,http,ldap]"
19
+ ```
20
+
21
+ > authkeys resolves keys via the POSIX `pwd`/`grp` databases, so key resolution
22
+ > only works on a POSIX host. The package still imports and the test suite still
23
+ > runs on any platform (the file source's `pwd` use is faked in tests).
24
+
25
+ ## Running Tests
26
+
27
+ ```bash
28
+ pytest
29
+ ```
30
+
31
+ Run with coverage:
32
+
33
+ ```bash
34
+ pytest --cov=src/authkeys tests/
35
+ ```
36
+
37
+ Use `pytest -rs` to see which optional-extra tests were skipped.
38
+
39
+ ## Documentation
40
+
41
+ ```bash
42
+ pip install -e ".[docs]"
43
+ mkdocs serve # live preview at http://127.0.0.1:8000
44
+ mkdocs build --strict # what CI runs
45
+ ```
46
+
47
+ ## Code Style
48
+
49
+ - Follow PEP 8
50
+ - Use type hints (quote runtime `X | Y` unions, or add
51
+ `from __future__ import annotations`, for the Python 3.9 floor)
52
+ - Keep functions focused and well-named
53
+ - Import optional dependencies (`requests`, `ldap3`, `cryptography`) lazily so the
54
+ core stays dependency-free and importable everywhere
55
+
56
+ ## Commit Guidelines
57
+
58
+ Follow the format: `type: description`
59
+
60
+ - `feat:` New feature
61
+ - `fix:` Bug fix
62
+ - `docs:` Documentation
63
+ - `test:` Test additions/improvements
64
+ - `ci:` CI/workflow changes
65
+ - `chore:` Build or tooling changes
66
+
67
+ Examples:
68
+ - `feat: add an HTTP key server subcommand`
69
+ - `fix: escape the username in the LDAP filter`
70
+ - `docs: document the serve fail-closed behavior`
71
+
72
+ ## Pull Request Process
73
+
74
+ 1. Create a feature branch: `git checkout -b feature/my-feature`
75
+ 2. Make your changes and add tests
76
+ 3. Run `pytest` to ensure all tests pass (on the oldest supported Python if you
77
+ can — 3.9)
78
+ 4. Commit with a clear message (see guidelines above)
79
+ 5. Push to your fork and open a pull request
80
+
81
+ ## Reporting Issues
82
+
83
+ When reporting bugs, please include:
84
+
85
+ - Python version
86
+ - authkeys version
87
+ - Which source(s) are configured
88
+ - Minimal config that reproduces the issue
89
+ - Expected vs. actual behavior
90
+
91
+ ## Questions?
92
+
93
+ Open a discussion or issue on GitHub!
authkeys-0.4.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jose A.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: authkeys
3
+ Version: 0.4.0
4
+ Summary: Pluggable AuthorizedKeysCommand provider for OpenSSH (file/HTTP/LDAP sources, TTL cache, optional key server)
5
+ Project-URL: Homepage, https://github.com/jose-pr/authkeys/
6
+ Project-URL: Documentation, https://jose-pr.github.io/authkeys/
7
+ Project-URL: Issues, https://github.com/jose-pr/authkeys/issues
8
+ Author: Jose A.
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: authorized_keys,cli,ldap,openssh,ssh
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Operating System :: POSIX
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.9
26
+ Requires-Dist: duho
27
+ Provides-Extra: all
28
+ Requires-Dist: cryptography; extra == 'all'
29
+ Requires-Dist: ldap3; extra == 'all'
30
+ Requires-Dist: requests; extra == 'all'
31
+ Provides-Extra: dev
32
+ Requires-Dist: build; extra == 'dev'
33
+ Requires-Dist: hatchling; extra == 'dev'
34
+ Requires-Dist: pytest; extra == 'dev'
35
+ Requires-Dist: pytest-cov; extra == 'dev'
36
+ Requires-Dist: pytest-timeout; extra == 'dev'
37
+ Requires-Dist: requests; extra == 'dev'
38
+ Requires-Dist: twine; extra == 'dev'
39
+ Provides-Extra: docs
40
+ Requires-Dist: mkdocs; extra == 'docs'
41
+ Requires-Dist: mkdocs-material; extra == 'docs'
42
+ Requires-Dist: mkdocstrings[python]; extra == 'docs'
43
+ Provides-Extra: http
44
+ Requires-Dist: requests; extra == 'http'
45
+ Provides-Extra: ldap
46
+ Requires-Dist: cryptography; extra == 'ldap'
47
+ Requires-Dist: ldap3; extra == 'ldap'
48
+ Description-Content-Type: text/markdown
49
+
50
+ # authkeys
51
+
52
+ [![PyPI version](https://img.shields.io/pypi/v/authkeys.svg)](https://pypi.org/project/authkeys/)
53
+ [![Python versions](https://img.shields.io/pypi/pyversions/authkeys.svg)](https://pypi.org/project/authkeys/)
54
+ [![Documentation](https://img.shields.io/badge/docs-jose--pr.github.io%2Fauthkeys-blue.svg)](https://jose-pr.github.io/authkeys/)
55
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/jose-pr/authkeys/blob/main/LICENSE)
56
+
57
+ A pluggable OpenSSH [`AuthorizedKeysCommand`](https://man.openbsd.org/sshd_config#AuthorizedKeysCommand)
58
+ provider. It resolves a user's authorized SSH keys from one or more configured
59
+ **sources** — local key files, an HTTP endpoint, or LDAP-stored X.509
60
+ certificates — with optional TTL caching, user/group aliasing, and a small
61
+ unattended HTTP key server for hosts that fetch keys centrally.
62
+
63
+ Built on the [duho](https://github.com/jose-pr/duho) declarative CLI framework.
64
+
65
+ ## Install
66
+
67
+ ```bash
68
+ pip install authkeys # core (file source only)
69
+ pip install authkeys[http] # + HTTP source (requests)
70
+ pip install authkeys[ldap] # + LDAP cert source (ldap3, cryptography)
71
+ pip install authkeys[all] # everything
72
+ ```
73
+
74
+ > authkeys targets POSIX systems (it reads the system user/group databases via
75
+ > `pwd`/`grp`). The package imports on any platform for testing, but resolution
76
+ > requires a POSIX host.
77
+
78
+ ## Usage
79
+
80
+ ```bash
81
+ authkeys resolve alice # print alice's authorized keys
82
+ authkeys alice # 'resolve' is the default command
83
+ authkeys serve --bind 0.0.0.0 --port 8090
84
+ ```
85
+
86
+ Wire it into `sshd_config`:
87
+
88
+ ```
89
+ AuthorizedKeysCommand /usr/bin/authkeys resolve %u
90
+ AuthorizedKeysCommandUser nobody
91
+ ```
92
+
93
+ ## Configuration
94
+
95
+ authkeys reads (in order) `/etc/authkeys.conf`,
96
+ `/etc/authkeys/authkeys.conf`, `/etc/ssh/authkeys.conf`, or a colon-separated
97
+ list passed with `--config`. A per-user `~/.ssh/authkeys.conf` may authorize
98
+ additional users/groups. See [`examples/authkeys.conf`](examples/authkeys.conf)
99
+ for a fully commented example.
100
+
101
+ ```ini
102
+ [cache]
103
+ backend = authkeys.cache.AuthKeysCacheMemBackend
104
+ expire = 3600
105
+ expired_on_error = 1
106
+
107
+ [source:files]
108
+ backend = authkeys.sources.authorizedkeys
109
+ paths =
110
+ authorized_keys
111
+ authorized_keys2
112
+
113
+ [source:ldap]
114
+ enabled = 1
115
+ backend = authkeys.sources.ldap
116
+ server = ldaps://ldap.example.com:636
117
+ basedn = o=Example,c=US
118
+ ```
119
+
120
+ ### Sources
121
+
122
+ | Alias (`backend =`) | Reads keys from | Extra dep |
123
+ | ---------------------------------- | -------------------------------------------- | --------- |
124
+ | `authkeys.sources.authorizedkeys` | `~/.ssh/authorized_keys*` files | — |
125
+ | `authkeys.sources.http` | an HTTP URL (`{username}` templated) | `requests` |
126
+ | `authkeys.sources.ldap` | X.509 certs in an LDAP directory | `ldap3`, `cryptography` |
127
+
128
+ Each `[source:<name>]` section supports `enabled` and `cached` (both default
129
+ `true`) and an optional `sanitize` callable to rewrite/drop keys.
130
+
131
+ ### Caching
132
+
133
+ - `AuthKeysCacheMemBackend` — per-process, in-memory (default).
134
+ - `AuthKeysCacheFileBackend` — on-disk under `path`, shared across invocations.
135
+
136
+ With `expired_on_error = 1`, a source failure falls back to the last-known
137
+ (expired) cached keys instead of returning nothing — useful when the SSH login
138
+ must not be blocked by a transient LDAP/HTTP outage.
139
+
140
+ ### User / group aliasing
141
+
142
+ A per-user `~/.ssh/authkeys.conf` may grant other principals' keys:
143
+
144
+ ```ini
145
+ [authorized]
146
+ users =
147
+ alice
148
+ bob
149
+ groups =
150
+ admins
151
+ ```
152
+
153
+ ## HTTP key server (`authkeys serve`)
154
+
155
+ For hosts that can't run the command locally, `authkeys serve` exposes the same
156
+ resolution over HTTP:
157
+
158
+ ```
159
+ GET /keys?username=alice&apikey=<key>
160
+ ```
161
+
162
+ Bind address, port, path, and API key come from the `[serve]` config section
163
+ (or `--bind`/`--port`). The API key is compared in constant time; pull it from
164
+ the environment with `api_key = ${env:AUTHKEYS_APIKEY}` to keep it off disk. If
165
+ no API key is configured, authentication is disabled — only bind to a trusted
166
+ interface in that case.
167
+
168
+ ## License
169
+
170
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,121 @@
1
+ # authkeys
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/authkeys.svg)](https://pypi.org/project/authkeys/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/authkeys.svg)](https://pypi.org/project/authkeys/)
5
+ [![Documentation](https://img.shields.io/badge/docs-jose--pr.github.io%2Fauthkeys-blue.svg)](https://jose-pr.github.io/authkeys/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/jose-pr/authkeys/blob/main/LICENSE)
7
+
8
+ A pluggable OpenSSH [`AuthorizedKeysCommand`](https://man.openbsd.org/sshd_config#AuthorizedKeysCommand)
9
+ provider. It resolves a user's authorized SSH keys from one or more configured
10
+ **sources** — local key files, an HTTP endpoint, or LDAP-stored X.509
11
+ certificates — with optional TTL caching, user/group aliasing, and a small
12
+ unattended HTTP key server for hosts that fetch keys centrally.
13
+
14
+ Built on the [duho](https://github.com/jose-pr/duho) declarative CLI framework.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install authkeys # core (file source only)
20
+ pip install authkeys[http] # + HTTP source (requests)
21
+ pip install authkeys[ldap] # + LDAP cert source (ldap3, cryptography)
22
+ pip install authkeys[all] # everything
23
+ ```
24
+
25
+ > authkeys targets POSIX systems (it reads the system user/group databases via
26
+ > `pwd`/`grp`). The package imports on any platform for testing, but resolution
27
+ > requires a POSIX host.
28
+
29
+ ## Usage
30
+
31
+ ```bash
32
+ authkeys resolve alice # print alice's authorized keys
33
+ authkeys alice # 'resolve' is the default command
34
+ authkeys serve --bind 0.0.0.0 --port 8090
35
+ ```
36
+
37
+ Wire it into `sshd_config`:
38
+
39
+ ```
40
+ AuthorizedKeysCommand /usr/bin/authkeys resolve %u
41
+ AuthorizedKeysCommandUser nobody
42
+ ```
43
+
44
+ ## Configuration
45
+
46
+ authkeys reads (in order) `/etc/authkeys.conf`,
47
+ `/etc/authkeys/authkeys.conf`, `/etc/ssh/authkeys.conf`, or a colon-separated
48
+ list passed with `--config`. A per-user `~/.ssh/authkeys.conf` may authorize
49
+ additional users/groups. See [`examples/authkeys.conf`](examples/authkeys.conf)
50
+ for a fully commented example.
51
+
52
+ ```ini
53
+ [cache]
54
+ backend = authkeys.cache.AuthKeysCacheMemBackend
55
+ expire = 3600
56
+ expired_on_error = 1
57
+
58
+ [source:files]
59
+ backend = authkeys.sources.authorizedkeys
60
+ paths =
61
+ authorized_keys
62
+ authorized_keys2
63
+
64
+ [source:ldap]
65
+ enabled = 1
66
+ backend = authkeys.sources.ldap
67
+ server = ldaps://ldap.example.com:636
68
+ basedn = o=Example,c=US
69
+ ```
70
+
71
+ ### Sources
72
+
73
+ | Alias (`backend =`) | Reads keys from | Extra dep |
74
+ | ---------------------------------- | -------------------------------------------- | --------- |
75
+ | `authkeys.sources.authorizedkeys` | `~/.ssh/authorized_keys*` files | — |
76
+ | `authkeys.sources.http` | an HTTP URL (`{username}` templated) | `requests` |
77
+ | `authkeys.sources.ldap` | X.509 certs in an LDAP directory | `ldap3`, `cryptography` |
78
+
79
+ Each `[source:<name>]` section supports `enabled` and `cached` (both default
80
+ `true`) and an optional `sanitize` callable to rewrite/drop keys.
81
+
82
+ ### Caching
83
+
84
+ - `AuthKeysCacheMemBackend` — per-process, in-memory (default).
85
+ - `AuthKeysCacheFileBackend` — on-disk under `path`, shared across invocations.
86
+
87
+ With `expired_on_error = 1`, a source failure falls back to the last-known
88
+ (expired) cached keys instead of returning nothing — useful when the SSH login
89
+ must not be blocked by a transient LDAP/HTTP outage.
90
+
91
+ ### User / group aliasing
92
+
93
+ A per-user `~/.ssh/authkeys.conf` may grant other principals' keys:
94
+
95
+ ```ini
96
+ [authorized]
97
+ users =
98
+ alice
99
+ bob
100
+ groups =
101
+ admins
102
+ ```
103
+
104
+ ## HTTP key server (`authkeys serve`)
105
+
106
+ For hosts that can't run the command locally, `authkeys serve` exposes the same
107
+ resolution over HTTP:
108
+
109
+ ```
110
+ GET /keys?username=alice&apikey=<key>
111
+ ```
112
+
113
+ Bind address, port, path, and API key come from the `[serve]` config section
114
+ (or `--bind`/`--port`). The API key is compared in constant time; pull it from
115
+ the environment with `api_key = ${env:AUTHKEYS_APIKEY}` to keep it off disk. If
116
+ no API key is configured, authentication is disabled — only bind to a trusted
117
+ interface in that case.
118
+
119
+ ## License
120
+
121
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,15 @@
1
+ # authkeys
2
+
3
+ ::: authkeys
4
+
5
+ ## Usermaps
6
+
7
+ ::: authkeys.usermaps
8
+
9
+ ## Group members
10
+
11
+ ::: authkeys.groupmembers
12
+
13
+ ## Utilities
14
+
15
+ ::: authkeys.utils
@@ -0,0 +1,3 @@
1
+ # Cache
2
+
3
+ ::: authkeys.cache
@@ -0,0 +1,3 @@
1
+ # Config
2
+
3
+ ::: authkeys.config
@@ -0,0 +1,10 @@
1
+ # API Reference
2
+
3
+ Generated from docstrings, organized by module:
4
+
5
+ - **[authkeys](authkeys.md)** — the core `AuthKeys` resolver, `AuthorizedKey`,
6
+ `Source`/`AuthkeysSource`, and the usermap/groupmembers/utils helpers.
7
+ - **[Sources](sources.md)** — the built-in file, HTTP, and LDAP key sources.
8
+ - **[Server](server.md)** — the `authkeys serve` HTTP key server.
9
+ - **[Cache](cache.md)** — the TTL cache and its in-memory / file backends.
10
+ - **[Config](config.md)** — `AuthkeysConfig` and config loading.
@@ -0,0 +1,3 @@
1
+ # Server
2
+
3
+ ::: authkeys.server
@@ -0,0 +1,9 @@
1
+ # Sources
2
+
3
+ ::: authkeys.sources.file
4
+
5
+ ::: authkeys.sources.http
6
+
7
+ ::: authkeys.sources.github
8
+
9
+ ::: authkeys.sources.ldap