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.
- authkeys-0.4.0/.gitignore +29 -0
- authkeys-0.4.0/CHANGELOG.md +196 -0
- authkeys-0.4.0/CONTRIBUTING.md +93 -0
- authkeys-0.4.0/LICENSE +21 -0
- authkeys-0.4.0/PKG-INFO +170 -0
- authkeys-0.4.0/README.md +121 -0
- authkeys-0.4.0/docs/api/authkeys.md +15 -0
- authkeys-0.4.0/docs/api/cache.md +3 -0
- authkeys-0.4.0/docs/api/config.md +3 -0
- authkeys-0.4.0/docs/api/reference.md +10 -0
- authkeys-0.4.0/docs/api/server.md +3 -0
- authkeys-0.4.0/docs/api/sources.md +9 -0
- authkeys-0.4.0/docs/changelog.md +3 -0
- authkeys-0.4.0/docs/guide/cli.md +50 -0
- authkeys-0.4.0/docs/guide/configuration.md +75 -0
- authkeys-0.4.0/docs/guide/server.md +63 -0
- authkeys-0.4.0/docs/guide/sources.md +96 -0
- authkeys-0.4.0/docs/index.md +60 -0
- authkeys-0.4.0/examples/authkeys-serve.service +66 -0
- authkeys-0.4.0/examples/authkeys.conf +96 -0
- authkeys-0.4.0/mkdocs.yml +61 -0
- authkeys-0.4.0/pyproject.toml +62 -0
- authkeys-0.4.0/requirements.txt +1 -0
- authkeys-0.4.0/requirements_dev.txt +0 -0
- authkeys-0.4.0/src/authkeys/__init__.py +382 -0
- authkeys-0.4.0/src/authkeys/__main__.py +10 -0
- authkeys-0.4.0/src/authkeys/cache.py +211 -0
- authkeys-0.4.0/src/authkeys/cli.py +326 -0
- authkeys-0.4.0/src/authkeys/config.py +107 -0
- authkeys-0.4.0/src/authkeys/groupmembers.py +10 -0
- authkeys-0.4.0/src/authkeys/py.typed +0 -0
- authkeys-0.4.0/src/authkeys/server.py +135 -0
- authkeys-0.4.0/src/authkeys/sources/__init__.py +53 -0
- authkeys-0.4.0/src/authkeys/sources/file.py +28 -0
- authkeys-0.4.0/src/authkeys/sources/github.py +42 -0
- authkeys-0.4.0/src/authkeys/sources/http.py +67 -0
- authkeys-0.4.0/src/authkeys/sources/ldap.py +134 -0
- authkeys-0.4.0/src/authkeys/usermaps.py +9 -0
- authkeys-0.4.0/src/authkeys/utils.py +49 -0
- authkeys-0.4.0/tests/test_authkeys.py +265 -0
- authkeys-0.4.0/tests/test_cli_check.py +47 -0
- authkeys-0.4.0/tests/test_cli_commands.py +126 -0
- authkeys-0.4.0/tests/test_cli_completion.py +20 -0
- authkeys-0.4.0/tests/test_config.py +78 -0
- authkeys-0.4.0/tests/test_plan_execution.py +272 -0
- authkeys-0.4.0/tests/test_review_fixes.py +225 -0
- authkeys-0.4.0/tests/test_server.py +133 -0
- authkeys-0.4.0/tests/test_sources_file.py +60 -0
- authkeys-0.4.0/tests/test_sources_github.py +109 -0
- 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.
|
authkeys-0.4.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/authkeys/)
|
|
53
|
+
[](https://pypi.org/project/authkeys/)
|
|
54
|
+
[](https://jose-pr.github.io/authkeys/)
|
|
55
|
+
[](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).
|
authkeys-0.4.0/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# authkeys
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/authkeys/)
|
|
4
|
+
[](https://pypi.org/project/authkeys/)
|
|
5
|
+
[](https://jose-pr.github.io/authkeys/)
|
|
6
|
+
[](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,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.
|