Add ACME support#165
Conversation
|
Thanks for the PR. I will try to get the testing fixed when I find time. When you are ready for me to take a look just mark the PR as ready ^^ |
9c2190e to
2734a3e
Compare
|
I modified the workflow to not push in a PR. That's what kept failing for the test. I think we're good to go here, let me know if you have any questions or need anything changed. I've tested this with traefik and acme.sh, both http and dns challenges are working. Since an account has to be made by an admin, use the right credentials to bind, and it's limited to specific domain names, there is an option to bypass the challenges. I found in testing, and likely in half a year with production, tables would get to long so I've also added pagination and a toggle to hide acme certs. Personally I have 40 certs that would renew quarterly. |
|
Thanks I will take a look when I get time. Could you split out the changes to the Github Workflow into a new PR so I can already merge that? |
|
Could you rebase this PR please? |
7ritn
left a comment
There was a problem hiding this comment.
I didn't have time yet to look into the main ACME changes, since I will need quite a lot of time due to lack of experience. Please move pagination changes into a seperate PR. Thanks for the PR.
| /// Applies the standard end-entity X509 extensions and signs the certificate. | ||
| /// Used by both `build_common` (PKCS#12 output) and `build_server_pem` (PEM output). | ||
| fn apply_end_entity_extensions(x509: &mut X509Builder, ca_cert: &X509, ca_key: &PKey<Private>) -> Result<()> { | ||
| let basic_constraints = BasicConstraints::new().build()?; | ||
| x509.append_extension(basic_constraints)?; | ||
|
|
||
| let key_usage = KeyUsage::new() | ||
| .digital_signature() | ||
| .key_encipherment() | ||
| .build()?; | ||
| x509.append_extension(key_usage)?; | ||
|
|
||
| x509.set_issuer_name(ca_cert.subject_name())?; | ||
|
|
||
| let subject_key_identifier = SubjectKeyIdentifier::new() | ||
| .build(&x509.x509v3_context(None, None))?; | ||
| x509.append_extension(subject_key_identifier)?; | ||
|
|
||
| let authority_key_identifier = AuthorityKeyIdentifier::new() | ||
| .keyid(true) | ||
| .build(&x509.x509v3_context(Some(ca_cert), None))?; | ||
| x509.append_extension(authority_key_identifier)?; | ||
|
|
||
| x509.sign(ca_key, MessageDigest::sha256())?; | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
While sensible, please do not include refactoring changes that are not necessary / move them to a new PR. Refactoring changes should be in a separate commit and clearly marked as not changing function.
There was a problem hiding this comment.
I'm happy to move refactoring to separate commits in this PR. Or would you prefer I keep the certificate issuance logic separated for ACME? I was trying to re-use as much code as possible to make it easier for auditing.
| ) | ||
|
|
||
|
|
||
| def run_acme_flow( |
There was a problem hiding this comment.
I would appreciate more comments in this function since I am not super experienced with ACME
There was a problem hiding this comment.
I've added more comments
7ritn
left a comment
There was a problem hiding this comment.
Okay I finally managed to perform a full review, just took me a few hours. Anyway the implementation in general is looking very good and I (and I am sure also a few users) am excited to get this merged. Thanks for the work you put in.
| let new_status: &str = if account.auto_validate { | ||
| "valid" | ||
| } else if challenge_type == "dns-01" { |
There was a problem hiding this comment.
Can we add some logging here? Since auto_validate can be quite popular adding a warning that we did not vaildate would be good. In general having a bit more debug logging would help us if a user reports an issue.
There was a problem hiding this comment.
I've added a warn log here. I haven't had a chance to think through where other log statements would be helpful yet
| Ok(()) | ||
| } | ||
|
|
||
| "RS256" => { |
There was a problem hiding this comment.
I am not a super big fan of supporting RSA based cryptography, but I think for compatibility it makes sense.
| let (header, _protected_bytes, payload_bytes, signature) = parse_jws(&body.0)?; | ||
|
|
||
| let nonce = header.nonce.as_deref().unwrap_or(""); | ||
| let valid = state.db.validate_and_delete_nonce(nonce.to_string()).await |
There was a problem hiding this comment.
I would have probably used an in-memory cache, but since we don't really care about that extra bit of performance I think it is fine to leave as is.
| pub struct CreateAcmeAccountRequest { | ||
| pub name: String, | ||
| pub allowed_domains: Vec<String>, | ||
| pub ca_id: Option<i64>, |
There was a problem hiding this comment.
What is your reason for making this optional? I think we should be explicit here.
There was a problem hiding this comment.
I was torn here and happy to hear your input. I realize that the default CA timespan is 10 years and decently long, but I was trying to think through a renewal strategy. With the assumption that we're not trying to create multiple domains with one VaulTLS instance... Lets say a CA timespan is 1 year. If I have test.io for 2 years, I can create a new CA cert slightly before that and the acme account will migrate to the latest test.io certificate without manual intervention. If using multiple domains, this could be an issue unless you specify the ID.
|
Thanks for the feedback, I'm definitely excited for this capability to get merged as well. I'm working through the comments and will get a new commit pushed soon. |
… require save before settings change in sidebar.
…catchers for rocket to convey error information. Add tests for the guard
Add ACME support. Currently it supports the HTTP-01 challenge. I intend to also add the DNS-01 challenge before making this ready to merge. I wanted to open a PR so it's available to review the general methodology, but need some more time to continue working the DNS-01 and do some touching up.
This feature must be manually enabled, either through the settings page or with an ENV variable. Admins can choose to be notified on a new ACME certificate issuance. I've tried to re-use the issuance code where I can, but the ACME protocal leverages CSRs so the private key never leaves the server requesting the certificate and that required some flow changes.
Each ACME account has to be manually created, which will create an External Account Binding (EAB) for different clients. Each client can be limited to specific domains, to include a wildcard. Wildcards do not allow another level (i.e. no dots).
For transparency, I have leveraged Claude to speed up development of this PR in terms of the README, testing, and ensuring that this PR's logic is sound and does not implement any vulnerabilities.