Python client library and CLI for the ACMEEH Admin API
Project description
acmeeh-admin
Python client library and CLI for the ACMEEH Admin API.
ACMEEH is a production-ready ACME server (RFC 8555) for internal PKI management, supporting pluggable CA backends, HTTP-01/DNS-01/TLS-ALPN-01 challenges, EAB, CSR profiles, CRL generation, and certificate lifecycle hooks. This package provides both a Python client and a CLI for its token-authenticated Admin REST API.
- Library: Programmatic access via
AcmeehAdminClient - CLI:
acmeeh-admincommand-line tool for operators and scripts
Table of Contents
- Requirements
- Installation
- Dependencies
- Python Library
- CLI
- REST API Endpoints
- Architecture
- Testing
- Security Considerations
- Troubleshooting
- Contributing
- License
Requirements
- Python 3.10+
- A running ACMEEH server with the admin module enabled
Installation
pip install acmeeh-admin
Or install from source:
git clone <repo-url>
cd acmeeh-admin
pip install .
For development (includes pytest and responses):
pip install -e ".[dev]"
Dependencies
| Package | Version | Purpose |
|---|---|---|
requests |
>= 2.28, < 3 | HTTP client |
click |
>= 8.1, < 9 | CLI framework |
Python Library
Public API
The package exports the following symbols from acmeeh_admin:
from acmeeh_admin import (
AcmeehAdminClient, # Main client class
AcmeehAdminError, # Base exception for HTTP 4xx/5xx
AcmeehAuthenticationError, # 401/403 errors
AcmeehConnectionError, # Unreachable server / timeout
__version__, # Package version string (e.g. "0.1.0")
)
Quick Start
from acmeeh_admin import AcmeehAdminClient
# Authenticate with an existing token
client = AcmeehAdminClient("https://acme.internal", token="your-bearer-token")
# Or authenticate interactively
client = AcmeehAdminClient("https://acme.internal")
client.login("admin", "password")
# Use the API
users = client.users.list()
cert = client.certificates.get_by_serial("01:AB:CD:EF")
# Clean up
client.logout()
Constructor
AcmeehAdminClient(
base_url: str,
*,
token: str | None = None, # Bearer token for authentication
verify_ssl: bool = True, # Set False for self-signed certs
timeout: int = 30, # Request timeout in seconds
api_prefix: str = "/api", # Admin API path prefix
)
Authentication
| Method | Description |
|---|---|
client.login(username, password) |
Authenticate and store the token for subsequent requests. Returns the login response dict (includes token). |
client.logout() |
Revoke the current token and clear it from the session. Returns the server response dict. |
client.token |
Property to get/set the bearer token directly. |
Resource Reference
The client exposes the following resource objects, each mapping to a group of Admin API endpoints.
client.users — Admin User Management
| Method | Description |
|---|---|
list() -> list[dict] |
List all admin users. |
create(username, email, role="auditor") -> dict |
Create a new admin user. Response includes the generated password. |
get(user_id) -> dict |
Get a specific admin user by ID. |
update(user_id, **kwargs) -> dict |
Update an admin user. Accepts enabled (bool) and role (str). |
delete(user_id) -> None |
Delete an admin user. |
me() -> dict |
Get the current (authenticated) user's profile. |
reset_password() -> dict |
Reset the current user's password. Response includes the new password. |
client.audit — Audit Log
| Method | Description |
|---|---|
list(*, limit, cursor, action, user_id, since, until) -> list[dict] |
Fetch a single page of audit log entries. All parameters are optional filters. |
list_all(*, limit, **filters) -> list[dict] |
Fetch all pages and return a flat list. Accepts the same filters as list() except cursor. |
export(*, action, user_id, since, until) -> list[dict] |
Export audit log as parsed NDJSON entries (streamed). |
client.eab — External Account Binding (EAB) Credentials
| Method | Description |
|---|---|
list() -> list[dict] |
List all EAB credentials. |
create(kid, label="") -> dict |
Create an EAB credential. Response includes the generated HMAC key. |
get(cred_id) -> dict |
Get a specific EAB credential by ID. |
revoke(cred_id) -> dict |
Revoke an EAB credential. |
add_identifier(eab_id, identifier_id) -> None |
Link an allowed identifier to a credential. |
remove_identifier(eab_id, identifier_id) -> None |
Unlink an allowed identifier from a credential. |
list_identifiers(eab_id) -> list[dict] |
List allowed identifiers linked to a credential. |
assign_csr_profile(eab_id, profile_id) -> None |
Assign a CSR profile to a credential. |
unassign_csr_profile(eab_id, profile_id) -> None |
Remove the CSR profile assignment from a credential. |
get_csr_profile(eab_id) -> dict | None |
Get the CSR profile assigned to a credential. Returns None if no profile is assigned. |
client.identifiers — Allowed Identifier Management
| Method | Description |
|---|---|
list() -> list[dict] |
List all allowed identifiers. |
create(identifier_type, value) -> dict |
Create a new allowed identifier. identifier_type is "dns" or "ip". |
get(identifier_id) -> dict |
Get an allowed identifier with its associated accounts. |
delete(identifier_id) -> None |
Delete an allowed identifier. |
add_account(identifier_id, account_id) -> None |
Associate an identifier with an ACME account. |
remove_account(identifier_id, account_id) -> None |
Remove an identifier-account association. |
list_for_account(account_id) -> list[dict] |
List allowed identifiers for a specific ACME account. |
client.profiles — CSR Profile Management
| Method | Description |
|---|---|
list() -> list[dict] |
List all CSR profiles. |
create(name, profile_data, description="") -> dict |
Create a new CSR profile. profile_data is a dict of profile rules. |
get(profile_id) -> dict |
Get a specific CSR profile with associated accounts. |
update(profile_id, name, profile_data, description="") -> dict |
Update a CSR profile (full replacement). |
delete(profile_id) -> None |
Delete a CSR profile. |
validate(profile_id, csr_b64) -> dict |
Dry-run validate a base64-encoded CSR against a profile. |
assign_account(profile_id, account_id) -> None |
Assign a CSR profile to an ACME account. |
unassign_account(profile_id, account_id) -> None |
Remove a profile-account assignment. |
get_for_account(account_id) -> dict | None |
Get the CSR profile assigned to an ACME account. Returns None if no profile is assigned. |
client.certificates — Certificate Operations
| Method | Description |
|---|---|
search(**filters) -> list[dict] |
Search certificates. Filters: account_id, serial, fingerprint, status, domain, expiring_before, limit, offset. |
get_by_serial(serial) -> dict |
Get a certificate by serial number. |
get_by_fingerprint(fingerprint) -> dict |
Get a certificate by SHA-256 fingerprint (hex). |
bulk_revoke(filter, reason=None, dry_run=False) -> dict |
Revoke multiple certificates matching a filter. filter is a dict with keys such as domain, account_id, serial_numbers, etc. reason is an RFC 5280 revocation reason code (e.g., 4 = superseded). |
Note: The
fingerprintfilter is available in the library viaclient.certificates.search(fingerprint="...")but is not exposed as a CLI option. For fingerprint lookups in the CLI, usecertificates get-by-fingerprint <SHA256_HEX>instead.
client.notifications — Notification Management
| Method | Description |
|---|---|
list(*, status, limit, offset) -> list[dict] |
List notifications with optional filters. All parameters are optional. |
retry() -> dict |
Retry all failed notifications. Returns {"retried": count}. |
purge(days=30) -> dict |
Purge sent notifications older than days days. Returns {"purged": count}. |
client.crl — CRL Management
| Method | Description |
|---|---|
rebuild() -> dict |
Force a CRL rebuild. Returns health status. |
client.maintenance — Maintenance Mode
| Method | Description |
|---|---|
get_status() -> dict |
Get current maintenance mode status. |
set_status(enabled) -> dict |
Enable (True) or disable (False) maintenance mode. |
Error Handling
All API errors raise typed exceptions:
from acmeeh_admin import (
AcmeehAdminError, # Base: any HTTP 4xx/5xx
AcmeehAuthenticationError, # 401 Unauthorized / 403 Forbidden
AcmeehConnectionError, # Server unreachable / timeout
)
try:
client.users.get("nonexistent-id")
except AcmeehAuthenticationError as e:
print(f"Auth failed: {e.detail}") # Human-readable message
except AcmeehAdminError as e:
print(f"[{e.status_code}] {e.detail}")
print(f"RFC 7807 type: {e.error_type}")
print(f"Raw response: {e.response}")
Error attributes:
| Attribute | Type | Description |
|---|---|---|
status_code |
int |
HTTP status code (0 for connection errors) |
detail |
str |
Human-readable error message |
error_type |
str |
RFC 7807 problem type URI |
response |
requests.Response | None |
Raw response object |
Pagination
List endpoints that support pagination return data via a PaginatedIterator internally. The resource methods handle this transparently — list() methods return the first page, while list_all() (where available) collects all pages into a flat list.
The pagination follows the Link: <url>;rel="next" header convention. PaginatedIterator implements the Python iterator protocol — each call to __next__() fetches the next page and returns its items as a list. The collect() method fetches all pages and returns a flat list.
Note:
list_all()is currently only available on theclient.auditresource. Other resources provide single-pagelist()methods.
Thread Safety
AcmeehAdminClient is not thread-safe. Each thread should create its own client instance. The underlying requests.Session is shared across all resource objects within a single client, and concurrent access may lead to race conditions.
# Correct: separate client per thread
import threading
def worker(token):
client = AcmeehAdminClient("https://acme.internal", token=token)
users = client.users.list()
threads = [threading.Thread(target=worker, args=(token,)) for _ in range(4)]
Synchronous Only
The library is synchronous — all methods block until the HTTP response is received. There is no built-in async support. If you need async operations, run client calls in a thread pool:
import asyncio
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(max_workers=4)
async def list_users_async(client):
loop = asyncio.get_event_loop()
return await loop.run_in_executor(executor, client.users.list)
Connection Pooling and Proxies
The client uses a persistent requests.Session internally, which provides automatic connection pooling (keep-alive) across requests to the same server.
Proxy support is available via standard environment variables:
export HTTP_PROXY=http://proxy.internal:8080
export HTTPS_PROXY=http://proxy.internal:8080
export NO_PROXY=localhost,127.0.0.1
Retries
The library does not implement automatic retries or exponential backoff. If you need retry logic, wrap calls with a library like tenacity:
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential())
def get_users(client):
return client.users.list()
CLI
The acmeeh-admin CLI provides full access to the Admin API from the terminal.
Global Options
Usage: acmeeh-admin [OPTIONS] COMMAND [ARGS]...
Options:
--server TEXT Server URL (env: ACMEEH_ADMIN_URL)
--token TEXT Bearer token (env: ACMEEH_ADMIN_TOKEN)
--format [table|json] Output format (default: table)
--no-verify-ssl Disable SSL certificate verification
--api-prefix TEXT Admin API path prefix (env: ACMEEH_ADMIN_API_PREFIX, default: /api)
--version Show version and exit
--help Show help and exit
Configuration
The CLI stores configuration in ~/.acmeeh-admin.json. Values are resolved in this priority order:
- CLI flags (
--server,--token,--no-verify-ssl,--api-prefix) - Environment variables (
ACMEEH_ADMIN_URL,ACMEEH_ADMIN_TOKEN,ACMEEH_ADMIN_VERIFY_SSL,ACMEEH_ADMIN_API_PREFIX) - Config file (
~/.acmeeh-admin.json)
Config file format (written automatically by acmeeh-admin login). Login saves server_url, token, api_prefix, and verify_ssl:
{
"server_url": "https://acme.internal",
"token": "your-bearer-token",
"api_prefix": "/api",
"verify_ssl": true
}
Environment variables:
| Variable | Description |
|---|---|
ACMEEH_ADMIN_URL |
Server URL |
ACMEEH_ADMIN_TOKEN |
Bearer token |
ACMEEH_ADMIN_VERIFY_SSL |
SSL verification (true/false/0/1/yes/no) |
ACMEEH_ADMIN_API_PREFIX |
Admin API path prefix (default: /api) |
Authentication
# Interactive login — prompts for username and password (and server URL if not already set)
acmeeh-admin login
# All subsequent commands use the saved token
acmeeh-admin users list
# Logout (revokes token on server and removes it from config; server URL is preserved)
acmeeh-admin logout
# Or pass credentials per-command
acmeeh-admin --server https://acme.internal --token <TOKEN> users list
Command Reference
users — Admin User Management
acmeeh-admin users list # List all admin users
acmeeh-admin users create <USERNAME> <EMAIL> # Create user (default role: auditor)
acmeeh-admin users create <USERNAME> <EMAIL> --role admin
acmeeh-admin users get <USER_ID> # Get user details
acmeeh-admin users update <USER_ID> --role admin # Update user role
acmeeh-admin users update <USER_ID> --enabled # Enable user
acmeeh-admin users update <USER_ID> --disabled # Disable user
acmeeh-admin users delete <USER_ID> # Delete user (with confirmation)
acmeeh-admin users delete <USER_ID> --yes # Delete without confirmation (scripting)
acmeeh-admin users me # Current user's profile
acmeeh-admin users reset-password # Reset current user's password
audit — Audit Log
acmeeh-admin audit list # List recent audit entries
acmeeh-admin audit list --action login --since 2024-01-01 # Filter by action and date
acmeeh-admin audit list --user-id <ID> --limit 50 # Filter by user with page size
acmeeh-admin audit list --until 2024-06-01 --cursor <TOKEN> # Paginate with cursor
acmeeh-admin audit export # Export full log as NDJSON
acmeeh-admin audit export --action cert.revoke # Export filtered by action
acmeeh-admin audit export --user-id <ID> # Export filtered by user
acmeeh-admin audit export --since 2024-01-01 --until 2024-02-01 # Export date range
acmeeh-admin audit export -o audit.json # Write NDJSON to file
acmeeh-admin audit export --since 2024-01-01 --until 2024-02-01 -o audit.json # Export date range to file
eab — EAB Credential Management
acmeeh-admin eab list # List all EAB credentials
acmeeh-admin eab create <KID> # Create EAB credential
acmeeh-admin eab create <KID> --label "prod-01" # Create with label
acmeeh-admin eab get <CRED_ID> # Get credential details
acmeeh-admin eab revoke <CRED_ID> # Revoke credential
acmeeh-admin eab add-identifier <EAB_ID> <IDENTIFIER_ID> # Link an allowed identifier
acmeeh-admin eab remove-identifier <EAB_ID> <IDENTIFIER_ID> # Unlink an allowed identifier
acmeeh-admin eab list-identifiers <EAB_ID> # List linked identifiers
acmeeh-admin eab assign-profile <EAB_ID> <PROFILE_ID> # Assign a CSR profile
acmeeh-admin eab unassign-profile <EAB_ID> # Remove CSR profile assignment (auto-resolves profile ID)
acmeeh-admin eab get-profile <EAB_ID> # Get assigned CSR profile
identifiers — Allowed Identifier Management
acmeeh-admin identifiers list # List all allowed identifiers
acmeeh-admin identifiers create dns "*.example.com" # Allow a DNS identifier
acmeeh-admin identifiers create ip 10.0.0.1 # Allow an IP identifier
acmeeh-admin identifiers get <ID> # Get identifier details
acmeeh-admin identifiers delete <ID> # Delete (with confirmation)
acmeeh-admin identifiers delete <ID> --yes # Delete without confirmation (scripting)
acmeeh-admin identifiers add-account <ID> <ACCOUNT_ID> # Link to ACME account
acmeeh-admin identifiers remove-account <ID> <ACCOUNT_ID> # Unlink from account
acmeeh-admin identifiers list-for-account <ACCOUNT_ID> # List identifiers for account
profiles — CSR Profile Management
acmeeh-admin profiles list # List all profiles
acmeeh-admin profiles create "web-server" '{"key_type":"rsa"}' # Create profile
acmeeh-admin profiles create "web-server" '{"key_type":"rsa"}' --description "Web servers"
acmeeh-admin profiles get <PROFILE_ID> # Get profile details
acmeeh-admin profiles update <ID> "new-name" '{"key_type":"ec"}' # Full replacement
acmeeh-admin profiles update <ID> "new-name" '{"key_type":"ec"}' --description "EC profile"
acmeeh-admin profiles delete <PROFILE_ID> # Delete (with confirmation)
acmeeh-admin profiles delete <PROFILE_ID> --yes # Delete without confirmation (scripting)
acmeeh-admin profiles validate <PROFILE_ID> <CSR_BASE64> # Dry-run CSR validation
acmeeh-admin profiles assign-account <PROFILE_ID> <ACCOUNT_ID> # Assign to account
acmeeh-admin profiles unassign-account <PROFILE_ID> <ACCOUNT_ID> # Unassign from account
acmeeh-admin profiles get-for-account <ACCOUNT_ID> # Get account's profile
certificates — Certificate Operations
acmeeh-admin certificates search # List all certificates
acmeeh-admin certificates search --domain example.com # Filter by domain
acmeeh-admin certificates search --status active --limit 10 # Filter by status
acmeeh-admin certificates search --expiring-before 2024-12-31 # Expiring soon
acmeeh-admin certificates search --account-id <ACCT_ID> # Filter by account
acmeeh-admin certificates search --serial <SERIAL> # Filter by serial
acmeeh-admin certificates search --limit 20 --offset 40 # Paginate results
acmeeh-admin certificates get <SERIAL> # Get by serial number
acmeeh-admin certificates get-by-fingerprint <SHA256_HEX> # Get by fingerprint
acmeeh-admin certificates bulk-revoke '{"domain":"old.example.com"}' --dry-run # Preview
acmeeh-admin certificates bulk-revoke '{"domain":"old.example.com"}' --reason 4 # Revoke
notifications — Notification Management
acmeeh-admin notifications list # List all notifications
acmeeh-admin notifications list --status failed # Filter by status
acmeeh-admin notifications list --limit 20 --offset 40 # Paginate results
acmeeh-admin notifications retry # Retry failed notifications
acmeeh-admin notifications purge # Purge sent notifications (30 days)
acmeeh-admin notifications purge --days 7 # Purge older than 7 days
crl — CRL Management
acmeeh-admin crl rebuild # Force a CRL rebuild
maintenance — Maintenance Mode
acmeeh-admin maintenance status # Get current status
acmeeh-admin maintenance set on # Enable maintenance mode
acmeeh-admin maintenance set off # Disable maintenance mode
Output Formats
The CLI supports two output formats:
Table (default) — human-readable aligned columns:
ID USERNAME EMAIL ROLE ENABLED
------------------------------------ -------- ----------------- ------- -------
a1b2c3d4-e5f6-7890-abcd-ef1234567890 admin admin@example.com admin True
JSON (--format json) — machine-readable, suitable for piping to jq:
acmeeh-admin --format json users list | jq '.[].username'
Table format details:
- Column values longer than 60 characters are truncated with
... - Empty result sets display
(no results) - Single-record responses (e.g.,
users get,users me) are displayed as indented key-value pairs instead of a table - Column headers are automatically derived from JSON keys and displayed in uppercase
- The
--formatflag is a global option and must appear before the subcommand:acmeeh-admin --format json users list
CLI Output Messages
Mutation commands (create, update, delete, assign, etc.) print specific messages to stdout upon success. These are useful for scripting and verification:
| Command | Output on Success |
|---|---|
login |
Logged in as <username> |
logout |
Logged out. |
users delete |
User deleted. |
identifiers delete |
Identifier deleted. |
identifiers add-account |
Account associated. |
identifiers remove-account |
Account removed. |
profiles delete |
Profile deleted. |
profiles assign-account |
Profile assigned. |
profiles unassign-account |
Profile unassigned. |
profiles get-for-account |
No profile assigned. (when no profile exists) |
eab add-identifier |
OK |
eab remove-identifier |
OK |
eab assign-profile |
OK |
eab unassign-profile |
OK (or No CSR profile assigned to this EAB credential. if none exists) |
audit export -o <file> |
Exported <N> entries to <file> |
Commands that return structured data (create, get, list, search, me, reset-password, retry, purge, rebuild, status, set, revoke, etc.) print the response in the selected --format (table or JSON).
Exit Codes
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Error — API error, connection failure, or missing configuration |
All API errors and connection failures are caught by the handle_errors decorator and printed to stderr before exiting with code 1. Missing server URL also exits with code 1.
CLI Examples
The following walkthroughs show common operator workflows with sample terminal output.
First-Time Setup
# Log in interactively — saves credentials to ~/.acmeeh-admin.json
$ acmeeh-admin login
Server URL: https://acme.internal
Username: admin
Password: ********
Logged in as admin
# Verify your session
$ acmeeh-admin users me
id a1b2c3d4-e5f6-7890-abcd-ef1234567890
username admin
email admin@example.com
role admin
enabled True
Onboarding a New Operator
# Create an auditor account
$ acmeeh-admin users create jdoe jdoe@example.com
id f7e6d5c4-b3a2-1098-7654-321fedcba098
username jdoe
email jdoe@example.com
role auditor
enabled True
password Xk9#mP2$vL5nQ8
# Promote to admin later
$ acmeeh-admin users update f7e6d5c4-b3a2-1098-7654-321fedcba098 --role admin
id f7e6d5c4-b3a2-1098-7654-321fedcba098
username jdoe
email jdoe@example.com
role admin
enabled True
# Disable an account
$ acmeeh-admin users update f7e6d5c4-b3a2-1098-7654-321fedcba098 --disabled
id f7e6d5c4-b3a2-1098-7654-321fedcba098
username jdoe
email jdoe@example.com
role admin
enabled False
Provisioning EAB Credentials for a New ACME Client
# Create credentials for a production host
$ acmeeh-admin eab create web-prod-01 --label "Production web server"
id c9d8e7f6-5a4b-3c2d-1e0f-a9b8c7d6e5f4
kid web-prod-01
label Production web server
hmac_key base64url-encoded-hmac-key-here
revoked False
used False
created_at 2026-02-20T10:30:00Z
# List all credentials
$ acmeeh-admin eab list
ID KID LABEL REVOKED USED CREATED_AT
------------------------------------ ----------- ---------------------- ------- ----- --------------------
c9d8e7f6-5a4b-3c2d-1e0f-a9b8c7d6e5f4 web-prod-01 Production web server False False 2026-02-20T10:30:00Z
# Revoke a compromised credential
$ acmeeh-admin eab revoke c9d8e7f6-5a4b-3c2d-1e0f-a9b8c7d6e5f4
id c9d8e7f6-5a4b-3c2d-1e0f-a9b8c7d6e5f4
kid web-prod-01
revoked True
Managing Identifier Allowlists
# Allow a wildcard domain and a specific IP
$ acmeeh-admin identifiers create dns "*.example.com"
id a1234567-b890-cdef-1234-567890abcdef
identifier_type dns
identifier_value *.example.com
$ acmeeh-admin identifiers create ip 10.0.1.50
id b2345678-c901-def0-2345-678901bcdef0
identifier_type ip
identifier_value 10.0.1.50
# Restrict an identifier to a specific ACME account
$ acmeeh-admin identifiers add-account a1234567-b890-cdef-1234-567890abcdef acct-uuid-here
Account associated.
# View identifier details with linked accounts
$ acmeeh-admin identifiers get a1234567-b890-cdef-1234-567890abcdef
id a1234567-b890-cdef-1234-567890abcdef
identifier_type dns
identifier_value *.example.com
account_ids ['acct-uuid-here']
Setting Up CSR Profiles
# Create a profile enforcing RSA 2048+ keys
$ acmeeh-admin profiles create "web-server" \
'{"key_type":"rsa","min_key_size":2048,"allowed_sans":["dns"]}' \
--description "Standard web server profile"
id d4567890-ef12-3456-7890-abcdef123456
name web-server
description Standard web server profile
created_at 2026-02-20T11:00:00Z
# Assign the profile to an ACME account
$ acmeeh-admin profiles assign-account d4567890-ef12-3456-7890-abcdef123456 acct-uuid-here
Profile assigned.
# Check which profile is assigned to an account
$ acmeeh-admin profiles get-for-account acct-uuid-here
id d4567890-ef12-3456-7890-abcdef123456
name web-server
description Standard web server profile
Certificate Operations
# Search for certificates expiring within 30 days
$ acmeeh-admin certificates search --expiring-before 2026-03-22 --status active --limit 5
SERIAL_NUMBER SAN_VALUES NOT_AFTER REVOKED_AT
------------- ---------------------- -------------------- ----------
01:AB:CD:EF ['web.example.com'] 2026-03-15T00:00:00Z
02:DE:FA:01 ['api.example.com'] 2026-03-20T00:00:00Z
# Inspect a specific certificate
$ acmeeh-admin certificates get 01:AB:CD:EF
serial_number 01:AB:CD:EF
san_values ['web.example.com']
not_before 2025-03-15T00:00:00Z
not_after 2026-03-15T00:00:00Z
fingerprint a1b2c3d4e5f6...
account_id acct-uuid-here
order_id order-uuid-here
revoked_at None
revocation_reason None
# Preview a bulk revocation (dry run)
$ acmeeh-admin certificates bulk-revoke '{"domain":"old.example.com"}' --dry-run
matched 3
revoked 0
dry_run True
# Execute the bulk revocation (reason 4 = superseded)
$ acmeeh-admin certificates bulk-revoke '{"domain":"old.example.com"}' --reason 4
matched 3
revoked 3
dry_run False
Audit Log Review
# Recent audit entries
$ acmeeh-admin audit list --limit 5
ID ACTION USER_ID CREATED_AT DETAILS
------------------------------------ -------------- ------------------------------------ -------------------- -------
e1234567-... user.login a1b2c3d4-... 2026-02-20T09:00:00Z {}
e2345678-... cert.revoke a1b2c3d4-... 2026-02-20T09:15:00Z {"serial": "01:AB:CD:EF"}
e3456789-... eab.create a1b2c3d4-... 2026-02-20T10:30:00Z {"kid": "web-prod-01"}
# Export a date range to a file for compliance
$ acmeeh-admin audit export --since 2026-01-01 --until 2026-02-01 -o jan-audit.json
Exported 142 entries to jan-audit.json
Operational Tasks
# Enable maintenance mode before upgrades
$ acmeeh-admin maintenance set on
enabled True
message Maintenance mode is active
$ acmeeh-admin maintenance status
enabled True
message Maintenance mode is active
# Disable after upgrade
$ acmeeh-admin maintenance set off
enabled False
# Force a CRL rebuild after mass revocation
$ acmeeh-admin crl rebuild
status ok
last_update 2026-02-20T12:00:00Z
# Retry failed email notifications
$ acmeeh-admin notifications retry
retried 5
# Purge old notifications (older than 7 days)
$ acmeeh-admin notifications purge --days 7
purged 23
Scripting with JSON Output
# Pipe JSON output to jq for scripting
$ acmeeh-admin --format json users list | jq '.[].username'
"admin"
"jdoe"
# Extract expiring certificate serials into a variable
$ SERIALS=$(acmeeh-admin --format json certificates search \
--expiring-before 2026-03-22 --status active | jq -r '.[].serial_number')
# Loop over results
$ for serial in $SERIALS; do
echo "Notifying owner of $serial..."
done
# One-liner: count active EAB credentials
$ acmeeh-admin --format json eab list | jq '[.[] | select(.revoked == false)] | length'
3
# Use with environment variables (e.g., in CI/CD)
$ export ACMEEH_ADMIN_URL=https://acme.internal
$ export ACMEEH_ADMIN_TOKEN=your-bearer-token
$ acmeeh-admin --format json certificates search --status active | jq length
42
# Without SSL verification (self-signed dev certs)
$ acmeeh-admin --no-verify-ssl --server https://localhost:8443 users me
# Bypass confirmation prompts for automated deletions (--yes)
$ acmeeh-admin users delete $USER_ID --yes
$ acmeeh-admin identifiers delete $ID --yes
$ acmeeh-admin profiles delete $PROFILE_ID --yes
Architecture
acmeeh_admin/
__init__.py # Public API: AcmeehAdminClient, exceptions
client.py # AcmeehAdminClient — main entry point
_http.py # HttpSession — requests wrapper, auth, error parsing
_pagination.py # PaginatedIterator — Link header pagination
exceptions.py # AcmeehAdminError, AcmeehAuthenticationError, AcmeehConnectionError
resources/
_base.py # BaseResource — shared base for all resource classes
auth.py # AuthResource — login/logout
users.py # UsersResource — admin user CRUD
audit.py # AuditResource — audit log queries and export
eab.py # EabResource — EAB credential management
identifiers.py # IdentifiersResource — allowed identifier management
profiles.py # ProfilesResource — CSR profile management
certificates.py # CertificatesResource — certificate search and bulk ops
notifications.py # NotificationsResource — notification management
crl.py # CrlResource — CRL rebuild
maintenance.py # MaintenanceResource — maintenance mode toggle
cli/
main.py # CLI entry point (click group)
config.py # ~/.acmeeh-admin.json management
output.py # Table and JSON formatting
_helpers.py # pass_client, handle_errors decorators
auth.py # login/logout commands
users.py # users command group
audit.py # audit command group
eab.py # eab command group
identifiers.py # identifiers command group
profiles.py # profiles command group
certificates.py # certificates command group
notifications.py # notifications command group
crl.py # crl command group
maintenance.py # maintenance command group
The library follows a layered architecture:
HttpSessionhandles HTTP transport, base URL joining, bearer token injection, SSL config, timeouts, and RFC 7807 error parsing.- Resource classes map 1:1 to API endpoint groups. Each resource receives the
HttpSessionand exposes typed methods. AcmeehAdminClientis the top-level facade that composes all resources.- CLI uses Click decorators. The
pass_clienthelper constructs anAcmeehAdminClientfrom the resolved configuration, andhandle_errorsprovides clean error output.
REST API Endpoints
The following table lists every REST endpoint the library wraps. All paths are relative to the configured api_prefix (default: /api).
Authentication
| Method | Path | Library Method | CLI Command |
|---|---|---|---|
| POST | /auth/login |
client.login() |
acmeeh-admin login |
| POST | /auth/logout |
client.logout() |
acmeeh-admin logout |
Users
| Method | Path | Library Method | CLI Command |
|---|---|---|---|
| GET | /users |
client.users.list() |
users list |
| POST | /users |
client.users.create() |
users create |
| GET | /users/{id} |
client.users.get() |
users get |
| PATCH | /users/{id} |
client.users.update() |
users update |
| DELETE | /users/{id} |
client.users.delete() |
users delete |
| GET | /me |
client.users.me() |
users me |
| POST | /me/reset-password |
client.users.reset_password() |
users reset-password |
Audit Log
| Method | Path | Library Method | CLI Command |
|---|---|---|---|
| GET | /audit-log |
client.audit.list() |
audit list |
| GET | /audit-log (all) |
client.audit.list_all() |
— |
| POST | /audit-log/export |
client.audit.export() |
audit export |
EAB Credentials
| Method | Path | Library Method | CLI Command |
|---|---|---|---|
| GET | /eab |
client.eab.list() |
eab list |
| POST | /eab |
client.eab.create() |
eab create |
| GET | /eab/{id} |
client.eab.get() |
eab get |
| POST | /eab/{id}/revoke |
client.eab.revoke() |
eab revoke |
| PUT | /eab/{id}/allowed-identifiers/{identifier_id} |
client.eab.add_identifier() |
eab add-identifier |
| DELETE | /eab/{id}/allowed-identifiers/{identifier_id} |
client.eab.remove_identifier() |
eab remove-identifier |
| GET | /eab/{id}/allowed-identifiers |
client.eab.list_identifiers() |
eab list-identifiers |
| PUT | /eab/{id}/csr-profile/{profile_id} |
client.eab.assign_csr_profile() |
eab assign-profile |
| DELETE | /eab/{id}/csr-profile/{profile_id} |
client.eab.unassign_csr_profile() |
eab unassign-profile * |
| GET | /eab/{id}/csr-profile |
client.eab.get_csr_profile() |
eab get-profile |
* The
eab unassign-profileCLI command only takes<EAB_ID>. It automatically looks up the currently assigned profile and resolves theprofile_idbefore calling the DELETE endpoint. If no profile is assigned, it prints a message and exits without making a request.
Allowed Identifiers
| Method | Path | Library Method | CLI Command |
|---|---|---|---|
| GET | /allowed-identifiers |
client.identifiers.list() |
identifiers list |
| POST | /allowed-identifiers |
client.identifiers.create() |
identifiers create |
| GET | /allowed-identifiers/{id} |
client.identifiers.get() |
identifiers get |
| DELETE | /allowed-identifiers/{id} |
client.identifiers.delete() |
identifiers delete |
| PUT | /allowed-identifiers/{id}/accounts/{account_id} |
client.identifiers.add_account() |
identifiers add-account |
| DELETE | /allowed-identifiers/{id}/accounts/{account_id} |
client.identifiers.remove_account() |
identifiers remove-account |
| GET | /accounts/{account_id}/allowed-identifiers |
client.identifiers.list_for_account() |
identifiers list-for-account |
CSR Profiles
| Method | Path | Library Method | CLI Command |
|---|---|---|---|
| GET | /csr-profiles |
client.profiles.list() |
profiles list |
| POST | /csr-profiles |
client.profiles.create() |
profiles create |
| GET | /csr-profiles/{id} |
client.profiles.get() |
profiles get |
| PUT | /csr-profiles/{id} |
client.profiles.update() |
profiles update |
| DELETE | /csr-profiles/{id} |
client.profiles.delete() |
profiles delete |
| POST | /csr-profiles/{id}/validate |
client.profiles.validate() |
profiles validate |
| PUT | /csr-profiles/{id}/accounts/{account_id} |
client.profiles.assign_account() |
profiles assign-account |
| DELETE | /csr-profiles/{id}/accounts/{account_id} |
client.profiles.unassign_account() |
profiles unassign-account |
| GET | /accounts/{account_id}/csr-profile |
client.profiles.get_for_account() |
profiles get-for-account |
Certificates
| Method | Path | Library Method | CLI Command |
|---|---|---|---|
| GET | /certificates |
client.certificates.search() |
certificates search |
| GET | /certificates/{serial} |
client.certificates.get_by_serial() |
certificates get |
| GET | /certificates/by-fingerprint/{fp} |
client.certificates.get_by_fingerprint() |
certificates get-by-fingerprint |
| POST | /certificates/bulk-revoke |
client.certificates.bulk_revoke() |
certificates bulk-revoke |
Notifications
| Method | Path | Library Method | CLI Command |
|---|---|---|---|
| GET | /notifications |
client.notifications.list() |
notifications list |
| POST | /notifications/retry |
client.notifications.retry() |
notifications retry |
| POST | /notifications/purge |
client.notifications.purge() |
notifications purge |
CRL
| Method | Path | Library Method | CLI Command |
|---|---|---|---|
| POST | /crl/rebuild |
client.crl.rebuild() |
crl rebuild |
Maintenance
| Method | Path | Library Method | CLI Command |
|---|---|---|---|
| GET | /maintenance |
client.maintenance.get_status() |
maintenance status |
| POST | /maintenance |
client.maintenance.set_status() |
maintenance set |
Testing
Run the test suite:
pip install -e ".[dev]"
pytest tests/
Tests use the responses library to mock HTTP calls without hitting a real server.
Test Structure
tests/
conftest.py # Shared fixtures (mocked_responses, client)
test_client.py # AcmeehAdminClient composition and auth delegation
test_http.py # HttpSession transport, URL joining, error parsing
test_http_extended.py # Timeout, connection errors, SSL config
test_pagination.py # Link header parsing
test_pagination_iterator.py # PaginatedIterator multi-page collection
resources/
test_auth.py # Login/logout token handling
test_users.py # Admin user CRUD
test_audit.py # Audit log listing and export
test_audit_extended.py # Audit list_all pagination and export filters
test_eab.py # EAB credential management
test_eab_extended.py # EAB identifier/profile linkage
test_identifiers.py # Allowed identifier management
test_profiles.py # CSR profile management
test_certificates.py # Certificate search and bulk revoke
test_notifications.py # Notification list, retry, purge
test_notifications_extended.py # Notification filter edge cases
test_crl.py # CRL rebuild
test_maintenance.py # Maintenance mode toggle
cli/
conftest.py # CLI test fixtures (Click CliRunner)
test_main.py # CLI group and global options
test_config.py # Config file load/save/resolution
test_helpers.py # pass_client and handle_errors decorators
test_output.py # Table and JSON formatting
test_auth.py # login/logout commands
test_users.py # users command group
test_audit.py # audit command group
test_eab.py # eab command group
test_identifiers.py # identifiers command group
test_profiles.py # profiles command group
test_certificates.py # certificates command group
test_notifications.py # notifications command group
test_crl.py # crl command group
test_maintenance.py # maintenance command group
Run a specific test file or test:
pytest tests/test_client.py
pytest tests/resources/test_eab.py -k "test_revoke"
pytest tests/ -v # Verbose output
Security Considerations
Token Storage
The CLI stores bearer tokens in plaintext in ~/.acmeeh-admin.json. Ensure this file has restrictive permissions:
chmod 600 ~/.acmeeh-admin.json
On shared systems, prefer passing the token via the ACMEEH_ADMIN_TOKEN environment variable or the --token CLI flag instead of relying on the config file.
SSL Verification
When verify_ssl=False is set (or --no-verify-ssl is used), the library suppresses urllib3 InsecureRequestWarning messages. This is intended for development environments with self-signed certificates. Always enable SSL verification in production.
Sensitive Response Data
Certain API responses contain secrets that are only available once:
users create— returns the generated password in the responseeab create— returns the HMAC key in the responseusers reset-password— returns the new password in the response
These values are not retrievable again. Store them securely immediately after creation.
Troubleshooting
Connection Refused
Connection error: Cannot connect to https://acme.internal: ...
- Verify the server URL is correct and the ACMEEH server is running.
- Check firewall rules and network connectivity.
- If behind a proxy, set
HTTP_PROXY/HTTPS_PROXYenvironment variables.
SSL Certificate Errors
Connection error: Cannot connect to https://acme.internal: SSLError(...)
- For self-signed development certificates, use
--no-verify-ssl(CLI) orverify_ssl=False(library). - In production, install the CA certificate in the system trust store instead of disabling verification.
Authentication Failures
Error [401]: Unauthorized
- Token may have expired — run
acmeeh-admin loginto get a fresh token. - Verify the token is being resolved correctly: check
--tokenflag,ACMEEH_ADMIN_TOKENenv var, and~/.acmeeh-admin.json. - Confirm the user account is enabled:
acmeeh-admin users me.
Missing Server URL
Error: No server URL. Use --server, ACMEEH_ADMIN_URL, or 'acmeeh-admin login'.
- Run
acmeeh-admin loginto save the server URL to the config file, or pass--serverexplicitly, or setACMEEH_ADMIN_URL.
JSON Parse Errors in CLI
json.JSONDecodeError: ...
- When passing JSON arguments (e.g.,
bulk-revoke,profiles create), ensure the JSON string is valid and properly quoted for your shell:# Correct (single quotes around JSON) acmeeh-admin profiles create "name" '{"key_type":"rsa"}' # Windows cmd (escaped double quotes) acmeeh-admin profiles create "name" "{\"key_type\":\"rsa\"}"
Contributing
Development Setup
git clone <repo-url>
cd acmeeh-admin
pip install -e ".[dev]"
Running Tests
pytest tests/ # Full test suite
pytest tests/ -v # Verbose output
pytest tests/test_client.py # Single file
pytest tests/resources/test_eab.py -k "test_revoke" # Single test
Code Style
- Type annotations on all public methods (Python 3.10+ union syntax
X | Y) from __future__ import annotationsin all modules (except__init__.py)- Private modules prefixed with underscore (
_http.py,_pagination.py,_helpers.py) - Resource classes inherit from
BaseResource - CLI commands use
@handle_errorsand@pass_clientdecorators - Confirmation prompts on destructive CLI operations (
delete) via@click.confirmation_option(skippable with--yes)
Project Layout
- Source code lives in
src/acmeeh_admin/(src layout) - Tests mirror the source structure under
tests/ - Build configuration is in
pyproject.toml(setuptools backend)
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file acmeeh_admin-1.0.1.tar.gz.
File metadata
- Download URL: acmeeh_admin-1.0.1.tar.gz
- Upload date:
- Size: 59.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8018b7faa6bd67dd4a45d03e4b13ce9d14ebba2408d1975829ce81690ce272f7
|
|
| MD5 |
44d80615ba4ae1c6a1e9b73ae05fe5ec
|
|
| BLAKE2b-256 |
b5ecc17f93deaae77992a07d136cd23d00bea55eec5d8d961f7d0c2b8e509737
|
Provenance
The following attestation bundles were made for acmeeh_admin-1.0.1.tar.gz:
Publisher:
publish.yml on miichoow/ACMEEH-admin
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
acmeeh_admin-1.0.1.tar.gz -
Subject digest:
8018b7faa6bd67dd4a45d03e4b13ce9d14ebba2408d1975829ce81690ce272f7 - Sigstore transparency entry: 1019520102
- Sigstore integration time:
-
Permalink:
miichoow/ACMEEH-admin@735ac56f9b9153bdf0e5ccb55143e8959a1a99cc -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/miichoow
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@735ac56f9b9153bdf0e5ccb55143e8959a1a99cc -
Trigger Event:
push
-
Statement type:
File details
Details for the file acmeeh_admin-1.0.1-py3-none-any.whl.
File metadata
- Download URL: acmeeh_admin-1.0.1-py3-none-any.whl
- Upload date:
- Size: 40.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fdf5431afd7854b69b21885adcc2151443365b36091cafe0d0203f4bef4fd70b
|
|
| MD5 |
c708ae77808bc2e13a64b3134de95445
|
|
| BLAKE2b-256 |
70e3bfcfe37c45c8c716f59b39bf6a16d4f023373eb99e14150be95a097a910a
|
Provenance
The following attestation bundles were made for acmeeh_admin-1.0.1-py3-none-any.whl:
Publisher:
publish.yml on miichoow/ACMEEH-admin
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
acmeeh_admin-1.0.1-py3-none-any.whl -
Subject digest:
fdf5431afd7854b69b21885adcc2151443365b36091cafe0d0203f4bef4fd70b - Sigstore transparency entry: 1019520104
- Sigstore integration time:
-
Permalink:
miichoow/ACMEEH-admin@735ac56f9b9153bdf0e5ccb55143e8959a1a99cc -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/miichoow
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@735ac56f9b9153bdf0e5ccb55143e8959a1a99cc -
Trigger Event:
push
-
Statement type: