acmex

AcmeX

Crates.io Documentation License Rust Version

AcmeX is a modular, enterprise-grade ACME v2 (RFC 8555) client and server ecosystem written in Rust. It is designed for high performance, reliability, and extensibility, supporting various DNS providers, storage backends, and cryptographic libraries. AcmeX enables automated certificate lifecycle management with advanced features like OCSP verification, multi-provider DNS-01 challenges, and a RESTful management API.

πŸ— Architecture

AcmeX follows a layered design to ensure separation of concerns and ease of maintenance:

πŸš€ Key Features

πŸ›  Installation

Add AcmeX to your Cargo.toml:

[dependencies]
acmex = "0.8.0"

Feature Flags

Enable optional features as needed:

[dependencies.acmex]
version = "0.8.0"
features = ["dns-cloudflare", "redis"]

Available features:

πŸ“– Quick Start

Basic Certificate Issuance

use acmex::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    // 1. Configure the client
    let config = AcmeConfig::lets_encrypt_staging()
        .with_contact(Contact::email("admin@example.com"))
        .with_tos_agreed(true);

    let mut client = AcmeClient::new(config)?;

    // 2. Set up challenge solvers
    let mut solver_registry = ChallengeSolverRegistry::new();
    // For DNS-01 challenge with Cloudflare (enable dns-cloudflare feature)
    // solver_registry.register(Box::new(CloudflareSolver::new(api_token, zone_id)?));
    // For HTTP-01 challenge
    // solver_registry.register(Box::new(Http01Solver::new()));

    // 3. Issue a certificate
    let domains = vec!["example.com".to_string(), "www.example.com".to_string()];
    let bundle = client.issue_certificate(domains, &mut solver_registry).await?;

    // 4. Save the certificate
    bundle.save_to_files("cert.pem", "key.pem")?;

    Ok(())
}

Note (v0.9.0): issue_certificate(Vec<String>, _) and NewOrderRequest::new(Vec<String>) are DNS-only compatibility entries β€” every string is treated as a DNS name (validated and normalized). For wildcard and IP (RFC 8738) identifiers use the strong-typed entries: Identifier::try_dns("*.example.com"), Identifier::try_ip("2001:db8::1") with client.issue_identifiers(...) / NewOrderRequest::from_identifiers(...). See docs/roadmap/v0.9.0/T01_MIGRATION_NOTES.md for the full migration record.

The acmex CLI

The binary wraps the same durable pipeline as the library (no duplicated logic β€” every command goes through the Application Service and the workflow engine):

# Scaffold a project: validated acmex.toml + storage directories.
# Refuses to overwrite an existing configuration.
acmex init --dir . --ca letsencrypt --env staging --challenge dns-01 --email ops@example.com

# Request a certificate. Default mode submits the operation and exits
# (a worker executes it); --wait drives the engine in-process.
acmex obtain --domains example.com --email ops@example.com --challenge dns-01 --wait

# Renewal daemon: workflow worker + ARI-first renewal scanning, no HTTP API.
# Ctrl+C / SIGTERM stops both loops.
acmex daemon --config acmex.toml --check-interval 3600

# REST API + embedded workflow worker + metrics endpoint.
acmex serve --config acmex.toml --addr 127.0.0.1:8080

# Reference remote delivery agent: serves the server side of the
# HttpAgentSink deployment protocol on its own host. Token comes from a
# SecretRef (`env:VAR` or `file:/path`); bare strings are rejected.
acmex agent serve --listen 127.0.0.1:9460 --token-ref env:AGENT_TOKEN

Repository durability is configurable in acmex.toml:

[repository.file]
path = ".acmex/repository"
fsync = "always"        # default: crash-safe per write
# fsync = "interval"    # opt-in group commit (like Redis AOF everysec)
# fsync_interval_ms = 100

Account-key secrets under .acmex/secrets are always fsynced immediately, regardless of the mode.

Optional OpenTelemetry tracing: set OTEL_EXPORTER_OTLP_ENDPOINT before starting any command; exporter failures degrade to plain logs.

Running the API Server

# Build and run the server
cargo run -- --config acmex.toml

Example acmex.toml:

[server]
host = "0.0.0.0"
port = 8080
api_key = "your-secret-api-key"

[storage]
backend = "file"
path = "./data"

[acme]
directory_url = "https://acme-v02.api.letsencrypt.org/directory"
contact_email = "admin@example.com"

πŸ›  Development

Prerequisites

Building

cargo build

Running Tests

cargo test

Examples

Explore the examples/ directory for more usage patterns:

πŸ“„ Documentation

Detailed documentation is available in the docs directory:

API documentation: docs.rs/acmex

🀝 Contributing

We welcome contributions! Please see our Contributing Guide for details on how to get started.

Reporting Issues

πŸ“œ License

Licensed under either of:

at your option.