Micronaut Acme

Extensions to integrate Micronaut and Acme

Version:

1 Introduction

Micronaut supports ACME via the micronaut-acme module.

2 Release History

For this project, you can find a list of releases (with release notes) here:  https://github.com/micronaut-projects/micronaut-acme/releases

3 Configuration

Micronaut 1.3.0 or above is required and you must have the micronaut-acme dependency on your classpath:

implementation("io.micronaut.acme:micronaut-acme")
<dependency>
    <groupId>io.micronaut.acme</groupId>
    <artifactId>micronaut-acme</artifactId>
</dependency>

The micronaut-acme module transitively includes the org.shredzone.acme4j:acme4j-client and org.shredzone.acme4j:acme4j-utils dependency.

src/main/resources/application.yml
micronaut:
    server:
        port : 80 (1)
        dual-protocol: true (2)
        ssl:
            enabled: true (3)
acme:
    enabled: true (4)
    tos-agree: true (5)
    cert-location: /path/to/store/certificates (6)
    domains: (7)
      - stage.domain.com
      - test.domain.com
    refresh:
        delay: 1m (8)
        frequency: 24h (9)
    domain-key: | (10)
        -----BEGIN RSA PRIVATE KEY-----
        MIIEowIBAAKCAQEAi32GgrNvt5sYonmvFRs1lYMdUTsoFHz33knzsTvBRb+S1JCc
        al86zAx3dRdFiLyWw4/lXmS6oS5B/NT1w9R7nW3vd0oi4ump/QjWjOd8SxCBqMcR
        ....
        MIIEowIBAAKCAQEAi32GgrNvt5sYonmvFRs1lYMdUTsoFHz33knzsTvBRb+S1JCc
        al86zAx3dRdFiLyWw4/lXmS6oS5B/NT1w9R7nW3vd0oi4ump/QjWjOd8SxCBqMcR
        -----END RSA PRIVATE KEY-----
    account-key: | (11)
        -----BEGIN RSA PRIVATE KEY-----
        MIIEowIBAAKCAQEAi32GgrNvt5sYonmvFRs1lYMdUTsoFHz33knzsTvBRb+S1JCc
        al86zAx3dRdFiLyWw4/lXmS6oS5B/NT1w9R7nW3vd0oi4ump/QjWjOd8SxCBqMcR
        ....
        MIIEowIBAAKCAQEAi32GgrNvt5sYonmvFRs1lYMdUTsoFHz33knzsTvBRb+S1JCc
        al86zAx3dRdFiLyWw4/lXmS6oS5B/NT1w9R7nW3vd0oi4ump/QjWjOd8SxCBqMcR
        -----END RSA PRIVATE KEY-----
    acme-server: acme://server.com (12)
    order:
        pause: 3s (13)
        refresh-attempts: 10 (14)
    auth:
        pause: 1m (15)
        refresh-attempts: 10 (16)
    renew-within: 30 (17)
    challenge-type: tls (18)
    timeout: 10s (19)
1 Set the http port for micronaut. If using http challenge-type this must be set to port 80, unless using a load balancer or some other proxy as Let’s Encrypt for example only sends request to port 80.
2 Enables dual port mode that allows for both http and https to be bound. Default is false
3 Enables ssl for micronaut. Default is false
4 Enables ACME integration for micronaut. Default is false
5 Agrees to the Terms of Service of the ACME provider. Default is false
6 Location to store the certificate on the server.
7 Domain name(s) for the certificate. Can be a 1 or many domains or even a wildcard domain.
8 How long to wait until the server starts up the ACME background process. Default is 24 hours
9 How often the server will check for a new ACME cert and refresh it if needed. Default is 24 hours
10 Private key used to encrypt the certificate. Other options you can use here are classpath:/path/to/key.pem or file:/path/to/key.pem. It is advisable to not check this into source control as this is the secret to handle the domain encryption.
11 Private key used to when setting up your account with the ACME provider. Other options you can use here are classpath:/path/to/key.pem or file:/path/to/key.pem. It is advisable to not check this into source control as this is your account identifier.
12 Url of the ACME server (ex. acme://letsencrypt.org/staging)
13 Time to wait in between polling order status of the ACME server. Default is 3 seconds
14 Number of times to poll an order status of the ACME server. Default is 10
15 Time to wait in between polling authorization status of the ACME server. Default is 3 seconds
16 Number of times to poll an authorization status of the ACME server. Default is 10
17 Number of days before the process will start to try to refresh the certificate from the ACME provider. Default is 30 days
18 The challenge type you would like to use. Default is tls. Possible options : http, tls, dns
19 Sets the connection/read timeout when making http calls to the ACME server. Default comes from here https://shredzone.org/maven/acme4j/acme4j-client/apidocs/src-html/org/shredzone/acme4j/connector/NetworkSettings.html#line.61

4 Challenge Types

ACME supports 3 different challenge types which will be used to validate that you in fact own the domain before a certificate is issued.

4.1 HTTP-01

Utilizing http challenge type you will need to do one of the following two things :

  1. enable dual protocol support

  2. setup redirect from http → https in any load balancer/proxy server you have in front of your application.

The reason for this is that Let’s Encrypt for example will only call out to the http challenge type over http and nothing else but will follow redirects.

src/main/resources/application.yml
acme:
  challenge-type: 'http'

micronaut:
  server:
    dual-protocol: true

4.2 TLS-APLN-01

Utilizing tls challenge type is the simplest to configure and thus the default because you will only be required to open up the default secure port for the server to allow the challenge server to validate it.

src/main/resources/application.yml
acme:
  challenge-type: 'tls'

4.3 DNS-01

Utilizing dns challenge type allows validation to be done via entry of a DNS TXT record.

Manual Verification

By default, the application will log out a message that looks as follows.

DNS output
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
							CREATE DNS `TXT` ENTRY AS FOLLOWS
				_acme-challenge.example.com with value 79ZNJaxlcLYIFootHL6Rrbh2VUCfFGgPeurVyjoRrS8
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Once this is output you will need to log into your DNS provider and create a TXT entry with the following key and value.

  • key: _acme-challenge.example.com

  • value: 79ZNJaxlcLYIFootHL6Rrbh2VUCfFGgPeurVyjoRrS8

Since this is a manual process you will also want to bump up your acme.auth.pause duration so that there is enough time between retries and time take for the manual entry/DNS propagation.

src/main/resources/application.yml
acme:
  challenge-type: 'dns'
  auth:
    # Due to the current manual nature in which the dns validation is performed by default
    # we change the amount of time we wait before trying to authorize again to make sure there
    # is time for us logging into the dns interface, setting a TXT record and waiting for it
    # to propagate.
    pause: 2m

Automatic Verification

An implementation of DnsChallengeSolver can be provided to automate the creation and cleanup of the necessary DNS records.

CustomDnsChallengeSolver.java
@Singleton
@Replaces(DnsChallengeSolver.class)
class CustomDnsChallengeSolver implements DnsChallengeSolver {
    @Override
    public void createRecord(String domain, String digest) {
        // Create a TXT record for $domain with the key "_acme-challenge" and the value of $digest
    }

    @Override
    public void destroyRecord(String domain) {
        // Remove the TXT record for $domain with the key "_acme-challenge" if it exists
    }
}

5 CLI

To be able to secure your application using ACME there will be a few setup steps necessary before you can start using the new certificates. Support for ACME and this setup has been baked into micronaut-starter (https://github.com/micronaut-projects/micronaut-starter).

5.1 Usage

To use these functions you must first enable the acme feature in your app.

For a new app

Either at creation time you will need to select the acme feature

Using the Micronaut CLI select the acme feature on creation.

mn create-app --features acme hello-world

Or using Micronaut Launch https://micronaut.io/launch/ simply select acme feature before downloading your pre-built app.

For an existing app

Use the micronaut cli to do a feature-diff on an exiting app to show the changes needed to enable the feature.

ex. CLI Feature Diff

cd <project directory>
mn feature-diff --features acme

Creating keypairs

A utility to help with creating keypairs. This is akin to doing something like so with openssl

$ openssl genrsa -out /tmp/mydomain.com-key.pem 4096

These keypairs will be used for both ACME accounts as well as each domain will also need its own keypair defined.

Usage:

Usage: mn create-key [-fhvVx] [-k=<keyDir>] -n=<keyName> [-s=<keySize>]
Creates an keypair for use with ACME integration
  -f, --force                Whether to overwrite existing files
  -h, --help                 Show this help message and exit.
  -k, --key-dir=<keyDir>     Custom location on disk to put the key to be used
                               with this account.
                               Default: src/main/resources
  -n, --key-name=<keyName>   Name of the key to be created
  -s, --key-size=<keySize>   Size of the key to be generated
                               Default: 4096
  -v, --verbose              Create verbose output.
  -V, --version              Print version information and exit.
  -x, --stacktrace           Show full stack trace when exceptions occur.

Creating an Account

Creates a new account for a given ACME provider. This command will either create a new account keypair for you or you can pass the account keypair that you have generated using the mn create-key or via openssl or other means in as a parameter.

Certbot or many of the other tools out there can also accomplish this step if you dont want to use this tool.

Usage:

Usage: mn create-acme-account (-u=<serverUrl> | --lets-encrypt-prod | --lets-encrypt-staging)
                              [-fhvVx] -e=<email> [-k=<keyDir>] -n=<keyName> [-s=<keySize>]
Creates a new account on the given ACME server
  -e, --email=<email>        Email address to create account with.
  -f, --force                Whether to overwrite existing files
  -h, --help                 Show this help message and exit.
  -k, --key-dir=<keyDir>     Custom location on disk to put the key to be used with this
                               account.
                               Default: src/main/resources
  -n, --key-name=<keyName>   Name of the key to be created
  -s, --key-size=<keySize>   Size of the key to be generated
                               Default: 4096
  -v, --verbose              Create verbose output.
  -V, --version              Print version information and exit.
  -x, --stacktrace           Show full stack trace when exceptions occur.
ACME server URL
      --lets-encrypt-prod    Use the Let's Encrypt prod URL.
      --lets-encrypt-staging Use the Let's Encrypt staging URL
  -u, --url=<serverUrl>      URL of ACME server to use

Deactivating an Account

Deactivates a given account based on the account key that was used to create the account.

Usage:

Usage: mn deactivate-acme-account (-u=<serverUrl> | --lets-encrypt-prod |
                                  --lets-encrypt-staging) [-fhvVx] [-k=<keyDir>] [-n=<keyName>]
Deactivates an existing ACME account
  -f, --force                Whether to overwrite existing files
  -h, --help                 Show this help message and exit.
  -k, --key-dir=<keyDir>     Directory to find the key to be used for this account.
                               Default: src/main/resources
  -n, --key-name=<keyName>   Name of the key to be used
                               Default: null
  -v, --verbose              Create verbose output.
  -V, --version              Print version information and exit.
  -x, --stacktrace           Show full stack trace when exceptions occur.
ACME server URL
      --lets-encrypt-prod    Use the Let's Encrypt prod URL.
      --lets-encrypt-staging Use the Let's Encrypt staging URL
  -u, --url=<serverUrl>      URL of ACME server to use

6 Repository

You can find the source code of this project in this repository: