Skip to content

Add ACME issuance and renewal runtime#76

Merged
vansour merged 3 commits intomainfrom
feat/acme-phase2-phase4
Apr 30, 2026
Merged

Add ACME issuance and renewal runtime#76
vansour merged 3 commits intomainfrom
feat/acme-phase2-phase4

Conversation

@vansour
Copy link
Copy Markdown
Owner

@vansour vansour commented Apr 30, 2026

Summary

  • add an instant-acme based runtime module for account persistence, order processing, HTTP-01 challenge handling, and atomic certificate/key writes
  • add rginx acme issue --once for cold-start certificate issuance and allow managed TLS identities to compile before the first issuance
  • add runtime reconcile/renewal wiring with HTTP-01 challenge short-circuiting and TCP TLS acceptor refresh after managed certificate updates

Verification

  • cargo test --workspace
  • python3 ./scripts/run-modularization-gate.py
  • ./scripts/run-clippy-gate.sh

Copilot AI review requested due to automatic review settings April 30, 2026 10:01
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Warning

Rate limit exceeded

@vansour has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 27 minutes and 46 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: d85eede9-c4c1-4d16-9a4f-95d126768171

📥 Commits

Reviewing files that changed from the base of the PR and between 0acb487 and 648b215.

📒 Files selected for processing (7)
  • crates/rginx-config/src/compile/mod.rs
  • crates/rginx-config/src/compile/tests/acme.rs
  • crates/rginx-config/src/compile/vhost.rs
  • crates/rginx-runtime/src/acme/challenge.rs
  • crates/rginx-runtime/src/acme/storage.rs
  • crates/rginx-runtime/src/acme/tests.rs
  • crates/rginx-runtime/src/acme/types.rs
📝 Walkthrough

Walkthrough

此PR为rginx项目添加ACME(自动证书管理环境)支持,包括CLI命令处理、配置编译调整、HTTP-01挑战响应、账户管理、证书颁发流程和后台调度器集成。

Changes

Cohort / File(s) Summary
根包和CLI
Cargo.toml, crates/rginx-app/src/cli.rs, crates/rginx-app/src/cli/tests.rs, crates/rginx-app/src/admin_cli/mod.rs, crates/rginx-app/src/main.rs
添加instant-acme依赖,引入Acme命令及嵌套的Issue子命令和--once标志。扩展main.rs以拦截ACME子命令并执行一次性颁发路径(run_acme_issue_once)。
配置编译层
crates/rginx-config/src/compile/mod.rs, crates/rginx-config/src/compile/server.rs, crates/rginx-config/src/compile/server/listener.rs, crates/rginx-config/src/compile/server/listener_managed_identity.rs, crates/rginx-config/src/compile/server/fields.rs, crates/rginx-config/src/compile/server/tls.rs, crates/rginx-config/src/compile/server/tls/identity.rs, crates/rginx-config/src/compile/vhost.rs, crates/rginx-config/src/lib.rs
实现CompileOptionsallow_missing_managed_tls_identity选项,支持ACME颁发场景下的配置编译。线程化托管身份对集合及缺失检查标志,新增compile_with_base_and_optionsload_and_compile_for_acme_issue入口点。
编译测试
crates/rginx-config/src/compile/tests/acme.rs
添加ACME颁发场景的编译测试,验证在缺失托管TLS身份文件时编译成功。
HTTP处理和状态
crates/rginx-http/src/handler/dispatch/acme.rs, crates/rginx-http/src/handler/dispatch/mod.rs, crates/rginx-http/src/handler/tests/routing/handle.rs, crates/rginx-http/src/state/mod.rs, crates/rginx-http/src/state/lifecycle.rs, crates/rginx-http/src/state/lifecycle/acme.rs
为HTTP-01挑战响应添加acme调度函数,绕过常规路由逻辑。在SharedState中添加acme_http01_challenges映射及管理方法。
运行时ACME核心
crates/rginx-runtime/src/acme/account.rs, crates/rginx-runtime/src/acme/challenge.rs, crates/rginx-runtime/src/acme/mod.rs, crates/rginx-runtime/src/acme/order.rs, crates/rginx-runtime/src/acme/scheduler.rs, crates/rginx-runtime/src/acme/storage.rs, crates/rginx-runtime/src/acme/types.rs, crates/rginx-runtime/src/acme/tests.rs
实现完整的ACME流程:账户加载/创建、HTTP-01挑战服务、订单处理、证书存储、后台调度器、状态计划和单元测试。
运行时整合
crates/rginx-runtime/src/bootstrap/mod.rs, crates/rginx-runtime/src/bootstrap/shutdown.rs, crates/rginx-runtime/src/bootstrap/shutdown/tests.rs, crates/rginx-runtime/src/lib.rs, crates/rginx-runtime/Cargo.toml
将ACME任务集成到运行时启动和关闭序列。添加httpinstant-acme依赖及tempfile开发依赖。

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as CLI / main
    participant Config as Config<br/>Compiler
    participant State as SharedState
    participant Challenge as Temporary<br/>Challenge Server
    participant Account as ACME<br/>Account
    participant Order as ACME<br/>Order
    participant Storage as Storage

    User->>CLI: acme issue --once
    CLI->>Config: load_and_compile_for_acme_issue
    Config->>Config: compile_with_base_and_options<br/>(allow_missing=true)
    Config-->>CLI: ConfigSnapshot
    CLI->>State: Create from config
    CLI->>Challenge: bind_for_config
    Challenge->>Challenge: Listen on :80
    Challenge-->>CLI: TemporaryChallengeServer
    CLI->>Account: load_or_create_account
    Account->>Storage: load_account_credentials
    alt No stored credentials
        Account->>Account: Create new account
        Account->>Storage: store_account_credentials
    end
    Account-->>CLI: Account ready
    
    loop For each managed certificate
        CLI->>Order: issue_and_store_managed_certificate
        Order->>Account: Create order
        Order->>Account: Get authorizations
        Order->>Order: Select HTTP-01 challenge
        Order->>Challenge: register challenge token
        Order->>Order: Set challenge ready
        Order->>Order: Poll until ready
        loop Token registered
            User-->>Challenge: GET /.well-known/acme-challenge/{token}
            Challenge->>State: acme_http01_response(token)
            State-->>Challenge: Key authorization
            Challenge-->>User: 200 text/plain
        end
        Order->>Order: Finalize with CSR
        Order->>Order: Poll for certificate
        Order->>Storage: write_certificate_pair
        Storage-->>Order: Cert written
        Order->>Challenge: unregister challenge token
        Order-->>CLI: Success
    end
    
    CLI->>Challenge: shutdown
    Challenge->>Challenge: Stop listening
    Challenge-->>CLI: Done
    CLI-->>User: IssueSummary
Loading
sequenceDiagram
    participant Client as ACME<br/>Client
    participant Handler as HTTP<br/>Handler
    participant Dispatcher as Request<br/>Dispatcher
    participant AcmeModule as ACME<br/>Module
    participant SharedState as SharedState

    Client->>Handler: GET /.well-known/acme-challenge/{token}
    Handler->>Dispatcher: dispatch(request_path)
    Dispatcher->>AcmeModule: http01_response(request_path)
    AcmeModule->>AcmeModule: Parse token from path
    AcmeModule->>SharedState: acme_http01_response(token)
    SharedState->>SharedState: Lookup in acme_http01_challenges
    SharedState-->>AcmeModule: Option<key_authorization>
    alt Token registered
        AcmeModule-->>Dispatcher: Some(key_authorization)
        Dispatcher->>Dispatcher: Create 200 text/plain response
        Dispatcher-->>Handler: Response
    else Token not found
        AcmeModule-->>Dispatcher: None
        Dispatcher->>Dispatcher: Continue normal routing
    end
    Handler-->>Client: HTTP Response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 一只兔子跳来跳去
配置编译在日光下闪耀
HTTP-01挑战如春风般轻盈
证书在文件夹里安家
ACME调度器轻声低鸣
🔐✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题清晰准确地总结了主要变更,即添加ACME颁发和续期运行时功能,与代码改动一致。
Description check ✅ Passed 描述详细介绍了三个主要改动方面(account persistence、CLI命令、renewal wiring)与代码摘要高度相关且准确。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/acme-phase2-phase4

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 27 minutes and 46 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@amazon-q-developer amazon-q-developer Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR adds comprehensive ACME certificate issuance and renewal functionality with HTTP-01 challenge support. The implementation includes account persistence, atomic certificate writes, and runtime reconciliation with exponential backoff retry logic.

Critical Issue

Security Vulnerability (Must Fix): Race condition in atomic_write on non-Unix systems where private keys may be created with insecure default permissions before content is written, potentially exposing credentials during the write window (CWE-378).

Architecture Review

The implementation demonstrates solid engineering practices:

  • Proper separation of concerns with dedicated modules for account management, challenge handling, and order processing
  • Atomic file writes with fsync for durability guarantees
  • Exponential backoff retry logic (60s → 6h max) for failed certificate issuances
  • Challenge token cleanup in defer-style pattern ensuring unregistration even on errors
  • Integration with existing HTTP dispatch for ACME challenge responses
  • Comprehensive test coverage for reconcile planning and certificate persistence

The issue_once command provides a safe cold-start flow, allowing managed TLS identities to compile before the first certificate issuance. The runtime reconciler properly handles configuration reloads and refreshes TLS acceptors after certificate updates.

Recommendation

Approval contingent on fixing the security vulnerability. After addressing the file permission race condition on non-Unix platforms, this PR is ready to merge.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.


⚠️ This PR contains more than 30 files. Amazon Q is better at reviewing smaller PRs, and may miss issues in larger changesets.

Comment thread crates/rginx-runtime/src/acme/storage.rs
@vansour
Copy link
Copy Markdown
Owner Author

vansour commented Apr 30, 2026

Addressed the Amazon Q review concern in crates/rginx-runtime/src/acme/storage.rs: the non-Unix fallback no longer creates ACME temp files with platform-default permissions and now fails explicitly instead. Re-ran cargo test -p rginx-runtime acme::tests and ./scripts/run-clippy-gate.sh after the change.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0acb48713a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/rginx-runtime/src/acme/storage.rs Outdated
Comment thread crates/rginx-config/src/compile/vhost.rs Outdated
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements automated ACME certificate management, including a new acme CLI subcommand for manual issuance and a background service for automatic renewals. The implementation introduces a temporary HTTP-01 challenge server, ACME account and order orchestration via the instant-acme crate, and logic for atomic certificate storage. Feedback suggests improving the robustness of the temporary challenge listener by handling transient accept errors and refining the directory creation logic in the storage module to handle edge cases in path resolution.

Comment thread crates/rginx-runtime/src/acme/challenge.rs
Comment thread crates/rginx-runtime/src/acme/storage.rs Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an ACME runtime subsystem to rginx to support managed certificate issuance/renewal (HTTP-01) and a one-shot issuance CLI path, while wiring HTTP-01 handling into the request dispatcher and relaxing compile-time TLS identity checks for the cold-start issuance workflow.

Changes:

  • Introduces a new rginx-runtime::acme module (instant-acme based) with account persistence, issuance, reconciliation scheduling, and HTTP-01 challenge handling.
  • Adds rginx acme issue --once plus a config compile mode that allows managed TLS identities to compile before the first issuance.
  • Wires HTTP-01 challenge short-circuiting into the HTTP dispatcher and adds lifecycle state for challenge token storage.

Reviewed changes

Copilot reviewed 34 out of 35 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/rginx-runtime/src/lib.rs Exposes the new acme runtime module.
crates/rginx-runtime/src/bootstrap/mod.rs Spawns the ACME background task during runtime startup.
crates/rginx-runtime/src/bootstrap/shutdown.rs Adds ACME task to graceful shutdown/join/abort flow.
crates/rginx-runtime/src/bootstrap/shutdown/tests.rs Extends shutdown tests to cover the ACME task.
crates/rginx-runtime/src/acme/mod.rs ACME module entrypoints: background run + one-shot issuance.
crates/rginx-runtime/src/acme/account.rs Loads/creates ACME account with persisted credentials.
crates/rginx-runtime/src/acme/challenge.rs Temporary HTTP-01 challenge server + runtime challenge backend.
crates/rginx-runtime/src/acme/order.rs ACME order flow: authz, HTTP-01 readiness, finalize, fetch cert, write to disk.
crates/rginx-runtime/src/acme/scheduler.rs Periodic reconcile/renew loop with retry backoff and TLS acceptor refresh.
crates/rginx-runtime/src/acme/storage.rs Persists account creds; writes certificate/key material to disk.
crates/rginx-runtime/src/acme/types.rs Reconcile planning helpers + listener addr discovery.
crates/rginx-runtime/src/acme/tests.rs Unit tests for listener detection, reconcile planning, and storage writes.
crates/rginx-runtime/Cargo.toml Adds instant-acme (+ http) and tempfile dev-dependency.
crates/rginx-http/src/state/mod.rs Adds in-memory ACME HTTP-01 challenge map to SharedState.
crates/rginx-http/src/state/lifecycle.rs Registers new lifecycle submodule for ACME helpers.
crates/rginx-http/src/state/lifecycle/acme.rs SharedState APIs to register/unregister/fetch HTTP-01 challenge responses.
crates/rginx-http/src/handler/dispatch/mod.rs Short-circuits requests to serve ACME HTTP-01 tokens before routing.
crates/rginx-http/src/handler/dispatch/acme.rs Parses ACME HTTP-01 request path and fetches response from SharedState.
crates/rginx-http/src/handler/tests/routing/handle.rs Adds test verifying ACME short-circuit overrides route handling.
crates/rginx-config/src/lib.rs Adds load_and_compile_for_acme_issue helper for one-shot issuance mode.
crates/rginx-config/src/compile/mod.rs Adds CompileOptions and plumbs option through compile pipeline.
crates/rginx-config/src/compile/vhost.rs Allows missing managed TLS identity for ACME vhosts when compile option enabled.
crates/rginx-config/src/compile/server.rs Plumbs allow-missing-managed-identity options into server compilation.
crates/rginx-config/src/compile/server/fields.rs Adds allow_missing_tls_identity into server field compilation.
crates/rginx-config/src/compile/server/listener.rs Allows missing identities only for listeners using managed (ACME) cert/key pairs.
crates/rginx-config/src/compile/server/listener_managed_identity.rs Helper to detect whether a listener/server TLS identity is ACME-managed.
crates/rginx-config/src/compile/server/tls.rs Plumbs allow-missing flag into TLS compilation.
crates/rginx-config/src/compile/server/tls/identity.rs Skips cert/key/OCSP existence checks when allow-missing is enabled.
crates/rginx-config/src/compile/tests/acme.rs Adds test for compile mode that allows missing managed identity files.
crates/rginx-app/src/cli.rs Adds rginx acme issue --once clap command shape.
crates/rginx-app/src/cli/tests.rs Adds CLI parsing test for one-shot ACME issuance.
crates/rginx-app/src/main.rs Early-dispatches ACME one-shot command and runs issuance without full runtime startup.
crates/rginx-app/src/admin_cli/mod.rs Treats acme as non-admin command for the admin CLI pathway.
Cargo.toml Adds workspace dependency on instant-acme.
Cargo.lock Locks new dependencies pulled in by instant-acme / runtime changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/rginx-runtime/src/acme/storage.rs
Comment thread crates/rginx-runtime/src/acme/types.rs
Comment thread crates/rginx-config/src/compile/vhost.rs Outdated
@vansour
Copy link
Copy Markdown
Owner Author

vansour commented Apr 30, 2026

Addressed the remaining review threads in 648b215:

  • rollback the certificate update if private-key persistence fails, so ordinary write errors do not leave a new cert paired with an old key
  • normalize bare relative output paths to the current directory before creating parents
  • retry transient accept() failures in the temporary HTTP-01 listener instead of stopping immediately
  • treat a missing managed private key as a reconcile trigger
  • allow shared managed TLS identities during rginx acme issue --once compile mode and remove the extra vhost TLS clone

Validation rerun on this commit:

  • cargo test --workspace
  • ./scripts/run-clippy-gate.sh
  • python3 ./scripts/run-modularization-gate.py

Note on the cross-file atomicity comments: with separate configured cert/key paths, we can make ordinary failure handling consistent via rollback, but a truly crash-atomic two-file swap would require changing the on-disk identity layout. I kept this PR to the rollback-based fix.

@vansour vansour enabled auto-merge April 30, 2026 10:22
@vansour vansour merged commit fba73f8 into main Apr 30, 2026
6 of 7 checks passed
@vansour vansour deleted the feat/acme-phase2-phase4 branch April 30, 2026 10:25
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 648b215475

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

tls_runtime_snapshot_for_config(config)
.certificates
.into_iter()
.map(|status| (status.scope.clone(), status))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Normalize status keys before managed-certificate lookup

certificate_status_index stores snapshots under raw status.scope values (e.g. vhost:servers[0] from TLS runtime), but managed ACME specs are compiled with unprefixed scopes (e.g. servers[0]), so plan_reconcile(..., certificate_statuses.get(&spec.scope), ...) never finds an existing status. In practice this makes every managed certificate look missing on each reconcile/acme issue --once run, which repeatedly re-issues already-valid certificates and can quickly hit ACME rate limits.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants