Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Let's Encrypt / ACME certificate and reloading #41006

Closed
cescoffier opened this issue Jun 6, 2024 · 10 comments · Fixed by #42105
Closed

Add support for Let's Encrypt / ACME certificate and reloading #41006

cescoffier opened this issue Jun 6, 2024 · 10 comments · Fixed by #42105
Assignees
Labels
area/cli Related to quarkus cli (not maven/gradle/etc.) area/vertx
Milestone

Comments

@cescoffier
Copy link
Member

Description

This overall idea is to provide a CLI plugin that will handle the ACME protocol to:

  1. Generate a certificate
  2. Reload the certificate

Implementation ideas

If enabled, the CLI plugin will interact with the (running) Quarkus application (through the management interface) to configure the ACME challenge. Then, it would download the certificates and configure the application to use them.

The reloading follows the same idea. Once the challenge is completed, the new certificates are downloaded and copied to the correct location, and the application is notified to reload the certificate (the new TLS registry has a reload method)

The Elyton team has provided the code to deal with the challenge and the certificates' downloading. We plan to reuse that code and focus on the CLI part and integration with the Quarkus application.

The ACME protocol is not part of the application itself on purpose. It should still be an explicit administrative action.

@cescoffier cescoffier added area/housekeeping Issue type for generalized tasks not related to bugs or enhancements area/vertx area/cli Related to quarkus cli (not maven/gradle/etc.) and removed area/housekeeping Issue type for generalized tasks not related to bugs or enhancements labels Jun 6, 2024
@cescoffier cescoffier removed their assignment Jul 2, 2024
@sberyozkin
Copy link
Member

@cescoffier Starting analyzing it. Before going to doing CLI, I'd like to experiment with the protocol, etc, in the next few days

@sberyozkin
Copy link
Member

@cescoffier, @maxandersen, I've started with a little project today to see how it works,

https://github.com/sberyozkin/quarkus-quickstarts/pull/new/lets_encrypt

It is not meant to be merged, but just to get a feeling for it.

Getting a technical error, on the challenge verification, but it is a start

@sberyozkin
Copy link
Member

Turns out NGrok's own ACME client intercepts the challenge and breaks the flow, request to the NGROK support asking for options to workaround it has been sent

@sberyozkin
Copy link
Member

sberyozkin commented Jul 16, 2024

@cescoffier The demo I've prototyped is now working for the first certificate and I expect it to work for the renewal, though the flow I've prototyped there is a little bit different to what you suggested above, let's sync on it a bit later.

The flow there as follows, at the moment no CLI is used, but that can be factored in somehow later.

At the moment the demo depends on ManagementResource and AcmeChallenge JAX-RS endpoints to control certificate acquisition/renewal process. For example, you do mvn quarkus:dev -Daccount-email=your-email -Ddomain=ngrok-domain and then go to http://localhost:8080/acme/first-certificate and you get JSON representing a Base64-encoded X509 chain with at least 2 certificates and a private key for this chain, like this one:

chain	
0	"MIIFUzCCBDugAwIBAgISKwGeUIMnSPxqg0F...WbKQ="
1	"MIIFTTCCAzWgAwIBAgIRAIEbJZNMRe313pP...WOAg="
signingkey	"MIIEvQIBADANBgkqhkiG9w0BAQE...D0Tw="

At the moment it is returned in the browser but can be copied or HTTP posted to the target.

Now, if you do http://localhost:8080/acme/renew-certificate, then it should get a new chain/private key and then it will HTTP POST to a management port, with the basic authentication enabled. Or we can copy it to the local folder where Vertx will scan it from, but the idea of letting the management interface decide how it wants to handle it seems interesting enough to me, as Vertx may be able to update the runtime loaded cert and private key info...

Also, as far as I understand, we can't really copy this content into a folder since we have a private key alongside the chain, so this content will have to be inserted into a keystore which is controlled by Quarkus, I'm not sure we want to grant CLI the same kind of access to the server keystore.

That said, I see Vert.x can detect changes in the QUARKUS_HTTP_SSL_CERTIFICATE_FILES and QUARKUS_HTTP_SSL_CERTIFICATE_KEY_FILES files, so the new chain and key pair can be copied directly but I'm not sure if it will make it easier if CLI does it (will it have an access to that folder ?), instead of the management interface accepting this content and dropping it into that folder or may be even avoiding copying it all together and updating this info in memory...

The good news is that WildFly Elytron X509 Acme client works perfectly which has definitely saved us a lot of time. CC @fjuma @Skyllarr

@cescoffier
Copy link
Member Author

cescoffier commented Jul 17, 2024

Just had a quick call with @sberyozkin to align how this could be done.

We ended up proposing the following.

  1. The flow is implemented by a CLI (quarkus lets-encrypt ...) and the application.
  2. The CLI will prepare the application to handle the flow (set a self-signed certificate for the initial flow, enable let's encrypt, enable management interface (except mentioned otherwise)
  3. The CLI will trigger the flow, retrieve the challenge, pass the challenge to the app that will serve it, and retrieve the certificates
  4. The CLI writes the certificate on a well-known location on the file system (passed to the application during the preparation step) and asks the application to reload the certificate

So, for the user, it would be something like:

quarkus lets-encrypt prepare
quarkus lets-encrypt generate --url=https://my-domain-name.org/

(NOTE: The two commands could be merged)

Once this is done, the application will serve https://my-domain-name.org/ using a valid certificate.

For renewal, it's as simple as:

quarkus lets-encrypt generate --url=https://my-domain-name.org/

Once this is done, the application will use the updated certificate.

When let's encrypt is enabled in the application (build time config), it exposes (work done in https://github.com/quarkusio/quarkus/compare/main...cescoffier:quarkus:lets-encrypt?expand=1):

  • /.well-known/acme-challenge/* - on the primary HTTP interface. It serves the challenge when configured (200), 404 otherwise. 405 for any other method than GET

  • lets-encrypt/challenge - on the management interface.

    • GET lets-encrypt/challenge verifies that the application is configured correctly - 204 Ready, 200 Ready with the challenge set, others not ready to handle the Let's Encrypt flow, 404 - let's encrypt not enabled
    • POST lets-encrypt/challenge sets the challenge that will be served on /.well-known/acme-challenge. 204 when set, 400 if already set
      * DELETE lets-encrypt/challenge clears the configured challenge (204 - challenged cleared)
  • POST lets-encrypt/certs - on the management interface to reload the certificate once the challenge has been completed and the certificates written on the file system.

When a specific TLS configuration name is targetted, POST lets-encrypt/certs and GET lets-encrypt/challenge accept a key query parameter to set the TLS configuration name. When omitted, the default configuration is targeted.

The CLI plugin uses the Elytron Acme client and an implementation of the Acme client SPI. The implementation of the SPI will use an HTTP client to call the application.

The next steps are:

  • Clement will write a method to write. The certificate is written to the file system using the PEM format.
  • [] Sergei is looking at the flow and see how his current approach can use the HTTP protocol mentioned below
  • [] Clement will try to have a look at the prepare CLI command (if time allows :-))

@cescoffier
Copy link
Member Author

@sberyozkin came with a small modification of the protocol. When seting up the challenge, we must pass a token and the challenge. The token must be checked before serving the challenge.

Thus, now:

  • GET /.well-known/acme-challenge/* - on the primary HTTP interface. It serves the challenge when configured (200), 404 otherwise. 405 for any other method than GET. The * part must matches the configured token, otherwise a 404 response is returned.
  • POST lets-encrypt/challenge sets the challenge that will be served on /.well-known/acme-challenge. 204 when set, 400 if already set or invalid. The endpoint accepts a JSON object containing the challenge-content and challenge-resource (the token). If one of these fields (or both) are not set, the challenge is considered invalid and a 400 response is returned.

The https://github.com/quarkusio/quarkus/compare/main...cescoffier:quarkus:lets-encrypt?expand=1 branch has been updated.

CC @sberyozkin

@sberyozkin
Copy link
Member

Thanks for a quick update, @cescoffier, testing now

@sberyozkin
Copy link
Member

We have worked with @cescoffier on getting the first cut of Acme Client and supporting set of actions for CLI to be built, cescoffier#281

@cescoffier
Copy link
Member Author

We now have working commands, PR should be opened soon!

@sberyozkin
Copy link
Member

Thanks @cescoffier for wrapping it all into CLI 👍 , here is a doc PR, cescoffier#283.
Looking forward to your PR to main :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/cli Related to quarkus cli (not maven/gradle/etc.) area/vertx
Projects
Development

Successfully merging a pull request may close this issue.

2 participants