Giter Club home page Giter Club logo

valb3r / letsencrypt-helper Goto Github PK

View Code? Open in Web Editor NEW
30.0 1.0 3.0 180 KB

Generates and keeps up-to-date your Spring Boot applications' Let's Encrypt or other ACME compliant SSL certificates. Pure Java in a single file of library code. An automated embedded alternative to Certbot and docker-sidecars. No JVM restart is needed on certificate update.

Home Page: https://valb3r.github.io/letsencrypt-helper

License: MIT License

Java 100.00%
spring-boot java tomcat letsencrypt letsencrypt-certificates ssl acme ssl-certificate autorenew

letsencrypt-helper's Introduction

What is this

If you have ever tried getting Let's Encrypt certificate for Spring Boot application, you know that it is painful as it involves using either CertBot or Docker-sidecar/Cron-job to manage certificate lifecycle, especially if it is small pet application. This library solves these problems by managing certificate lifecycle directly in Java code with the help of awesome Acme4j library.

Note: This version is for Spring Boot 3.x and Spring 6.x, for older Spring/Spring Boot versions see Release 0.2.5

Key features:

  1. Obtain Let's Encrypt certificate on fresh start (or from other ACME compliant certificate provider)
  2. Store generated keys and certificate into single KeyStore (server.ssl.keystore)
  3. Renew Let's Encrypt certificate (it watches for certificate expiration date and updates it to new before old is expired)
  4. No JVM restart needed when certificate gets updated

Application requirements

To perform HTTP-01 ACME (Automatic Certificate Management Environment) challenge, the application must listen on port 80, this library will automatically create Tomcat connector to this port, so the only thing needed on your side is to open 80 port for the application.

Servlet containers supported (embedded)

Usage

From JitPack maven repository

1. Import this library:

For Tomcat:

Gradle:
 allprojects {
     repositories {
         ...
         maven { url 'https://jitpack.io' }
     }
 }

dependencies {
   implementation 'com.github.valb3r.letsencrypt-helper:letsencrypt-helper-tomcat:0.4.0'
}
Maven:
<repositories>
     <repository>
         <id>jitpack.io</id>
         <url>https://jitpack.io</url>
     </repository>
 </repositories>

<dependencies>
   <dependency>
      <groupId>com.github.valb3r.letsencrypt-helper</groupId>
      <artifactId>letsencrypt-helper-tomcat</artifactId>
      <version>0.4.0</version>
   </dependency>
</dependencies>

For Jetty:

Gradle:
 allprojects {
     repositories {
         ...
         maven { url 'https://jitpack.io' }
     }
 }

dependencies {
   implementation 'com.github.valb3r.letsencrypt-helper:letsencrypt-helper-jetty:0.4.0'
}
Maven:
<repositories>
     <repository>
         <id>jitpack.io</id>
         <url>https://jitpack.io</url>
     </repository>
 </repositories>

<dependencies>
   <dependency>
      <groupId>com.github.valb3r.letsencrypt-helper</groupId>
      <artifactId>letsencrypt-helper-jetty</artifactId>
      <version>0.4.0</version>
   </dependency>
</dependencies>

2. Declare on your configuration

Tomcat:

@Import(TomcatWellKnownLetsEncryptChallengeEndpointConfig.class)

Jetty:

@Import(JettyWellKnownLetsEncryptChallengeEndpointConfig.class)

3. Define following properties in your application configuration or environment:

  1. lets-encrypt-helper.domain the domain to issue certificate for
  2. lets-encrypt-helper.contact your contact for Let's Encrypt (i.e. your email in format mailto:[email protected])

4. Configure SSL as usual for Tomcat+TLS using server.ssl.keystore for certificate and keys storage

5. Ensure your security layer (i.e. Spring security) allows anonymous access to /.well-known/acme-challenge/* paths

Configuration

Property Description Default value, if any
server.ssl.key-store Path to the KeyStore, where Let's Encrypt certificates and account key are to be stored (or are already there)
server.ssl.key-store KeyStore type (i.e. PKCS12)
server.ssl.key-store-pasword Password for KeyStore with Let's Encrypt certificate and account key
server.ssl.key-alias Let's Encrypt certificate key alias in the keystore
server.port Port (secure SSL/TLS) on which your application is deployed
lets-encrypt-helper.domain Your applications' domain (i.e. example.com)
lets-encrypt-helper.contact The contact of person responsible for the domain (i.e. mailto:[email protected])
lets-encrypt-helper.account-key-alias Account key alias letsencrypt-user
lets-encrypt-helper.letsencrypt-server Let's Encrypt server to use acme://letsencrypt.org
lets-encrypt-helper.key-size Certificate and Account key RSA key size 2048
lets-encrypt-helper.update-before-expiry Start trying to update certificate this time before expiration P30D (30 days)
lets-encrypt-helper.busy-wait-interval Busy wait interval for thread that checks if the certificate is valid PT1M (1 minute)
lets-encrypt-helper.account-cert-validity Validity duration for Account key P3650D (3650 days)
lets-encrypt-helper.store-cert-chain Store entire trust chain or only domain certificate (for browsers domain ceritificate is enough) true
lets-encrypt-helper.enabled Is the helper enabled true
lets-encrypt-helper.return-null-model If challenge endpoint should return null model (i.e. true is sane default for cases with Thymeleaf rendering the page) true
lets-encrypt-helper.development-only.http01-challenge-port For development only, port for HTTP-01 ACME challenge 80

Example configuration

Launch your application with -Dspring.profiles.active=ssl

application-ssl.yaml:

server:
  port: 443
  ssl:
    key-store: file:/home/user/letsencrypt/application-keystore # Path to KeyStore with certificates and keys
    key-store-password: change-me # Password for KeyStore protection
    key-store-type: PKCS12
    key-alias: tomcat # Certificate name in KeyStore
    enabled: true # Important to place this explicitly
lets-encrypt-helper:
  domain: my-domain.example.com # Domain to issue certificate for
  contact: mailto:[email protected] # Your contact for Let's Encrypt

Note: On your server ensure you have opened port 80 for Java (i.e. in Firewall) and Java can bind to it (i.e. follow Linux allow listening to low port without sudo to open ports 80,443 for java)

Example project with SSL and Let's Encrypt management using this library is located here

Alternative

The library is itself just 1 Java class. You can add library dependencies and:

Testing locally

The library has integration tests in:

directories. One can adapt these tests according to own needs, as they use Pebble - LetsEncrypt testing server.

letsencrypt-helper's People

Contributors

suchorski avatar valb3r avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

letsencrypt-helper's Issues

Order status invalid

I get always this error org.shredzone.acme4j.exception.AcmeServerException: Order's status ("invalid") is not acceptable for finalization at finalizeOrder. Certbot with same ports as standalone works fine and Spring Boot App delivers at port 80. Any suggestions?

Instructions/code on how to redirect http to https would be helpful

Hi! This is a useful project, thanks!

Instructions/code on how to redirect http to https would be helpful especially given that this is for small pet projects. I've been able to apply it to my pet project and now I don't have this 301 https redirect. Googling gave me too many ways and it's interesting what would be the best way for spring-boot.

subscriber agreement error - "WARN lKnownLetsEncryptChallengeEndpointConfig : Please review carefully and accept TOS https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf"

I'm testing on a server with an already existing certificate from letsencrypt. However, the sample program cannot be run. I am attaching the log.
``:: Spring Boot :: (v2.3.4.RELEASE)

2022-07-30 09:25:36.481 INFO 895693 --- [ main] example.SpringBootApp : Starting SpringBootApp on **** with PID 895693 (/home/alex/letsencrypt-helper-example-0.1.3-SNAPSHOT.jar started by root in /home/)
2022-07-30 09:25:36.490 INFO 895693 --- [ main] example.SpringBootApp : No active profile set, falling back to default profiles: default
2022-07-30 09:25:39.248 INFO 895693 --- [ main] lKnownLetsEncryptChallengeEndpointConfig : KeyStore exists: /etc/letsencrypt/live/
*********/keystore.p12
2022-07-30 09:25:39.571 INFO 895693 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 443 (https) 80 (http)
2022-07-30 09:25:39.659 INFO 895693 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-07-30 09:25:39.660 INFO 895693 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.38]
2022-07-30 09:25:39.960 INFO 895693 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-07-30 09:25:39.964 INFO 895693 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 3338 ms
2022-07-30 09:25:40.556 WARN 895693 --- [ificate Watcher] lKnownLetsEncryptChallengeEndpointConfig : Please review carefully and accept TOS https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf
2022-07-30 09:25:40.608 WARN 895693 --- [ificate Watcher] lKnownLetsEncryptChallengeEndpointConfig : Failed updating KeyStore

java.lang.NullPointerException: null
at com.github.valb3r.letsencrypthelper.tomcat.TomcatWellKnownLetsEncryptChallengeEndpointConfig.updateCertificateAndKeystore(TomcatWellKnownLetsEncryptChallengeEndpointConfig.java:395) ~[letsencrypt-helper-tomcat-0.2.4.jar!/:na]
at com.github.valb3r.letsencrypthelper.tomcat.TomcatWellKnownLetsEncryptChallengeEndpointConfig.executeCheckCertValidityAndRotateIfNeeded(TomcatWellKnownLetsEncryptChallengeEndpointConfig.java:336) ~[letsencrypt-helper-tomcat-0.2.4.jar!/:na]
at com.github.valb3r.letsencrypthelper.tomcat.TomcatWellKnownLetsEncryptChallengeEndpointConfig.letsEncryptCheckCertValidityAndRotateIfNeeded(TomcatWellKnownLetsEncryptChallengeEndpointConfig.java:308) ~[letsencrypt-helper-tomcat-0.2.4.jar!/:na]
at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na]`
The library and its functionality is what I need. I would love to resolve any issues.

Is that support for spring boot 3?

I am getting this message when trying with spring boot 3.0.4:

An attempt was made to call a method that does not exist. The attempt was made from the following location:

com.github.valb3r.letsencrypthelper.tomcat.TomcatWellKnownLetsEncryptChallengeEndpointConfig.matchesCertFilePathAndPassword(TomcatWellKnownLetsEncryptChallengeEndpointConfig.java:252)

The following method did not exist:

'java.lang.String org.apache.tomcat.util.net.SSLHostConfig.getCertificateKeystoreFile()'

The calling method's class, com.github.valb3r.letsencrypthelper.tomcat.TomcatWellKnownLetsEncryptChallengeEndpointConfig, was loaded from the following location:

jar:file:/app.jar!/BOOT-INF/lib/letsencrypt-helper-tomcat-0.2.5.jar!/com/github/valb3r/letsencrypthelper/tomcat/TomcatWellKnownLetsEncryptChallengeEndpointConfig.class

The called method's class, org.apache.tomcat.util.net.SSLHostConfig, is available from the following locations:

jar:file:/app.jar!/BOOT-INF/lib/tomcat-embed-core-10.1.5.jar!/org/apache/tomcat/util/net/SSLHostConfig.class

The called method's class hierarchy was loaded from the following locations:

org.apache.tomcat.util.net.SSLHostConfig: jar:file:/app.jar!/BOOT-INF/lib/tomcat-embed-core-10.1.5.jar!/

Improve logging

Add more clarity to logging. I.e. if key was not found in Keystore - log the message instead of NPE

Doc request for Docker

Hi, thanks for the library. I am working with docker image and the readme is not enough to set it up for docker containers, as it gives errors all the time that connection is refused. Thanks in advance!

{
  "identifier": {
    "type": "dns",
    "value": "test.MYDOMAIN.com"
  },
  "status": "invalid",
  "expires": "2023-11-24T20:01:14Z",
  "challenges": [
    {
      "type": "http-01",
      "status": "invalid",
      "error": {
        "type": "urn:ietf:params:acme:error:connection",
        "detail": "165.227.94.87: Fetching http://test.MYDOMAIN.com/.well-known/acme-challenge/pJ1AeqaCnxtWCqPZEqkRMTjNct9pCGho1QnrYeKQl0o: Connection refused",
        "status": 400
      },
      "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/284808795676/Px-9Og",
      "token": "pJ1AeqaCnxtWCqPZEqkRMTjNct9pCGho1QnrYeKQl0o",
      "validationRecord": [
        {
          "url": "http://test.MYDOMAINcom/.well-known/acme-challenge/pJ1AeqaCnxtWCqPZEqkRMTjNct9pCGho1QnrYeKQl0o",
          "hostname": "test.MYDOMAINcom",
          "port": "80",
          "addressesResolved": [
            "165.227.94.87"
          ],
          "addressUsed": "165.227.94.87"
        }
      ],
      "validated": "2023-11-17T20:01:15Z"
    }
  ]
}

Certificate chain stored?

The issued certificate works great when using a browser, but it seems that some intermediate certificates are missing, this the certificate chain is not stored in the keystore. Could this be? I'm not that deeply familiar with the topic...
When checking against openssl, I retrieve these issues:

openssl s_client -connect www.example.com:443
CONNECTED(00000005)
depth=0 CN = www.example.com
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = www.example.com
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
 0 s:CN = www.example.com
   i:C = US, O = Let's Encrypt, CN = R3

Anybody could help me?

java.lang.NullPointerException: Cannot invoke "java.security.KeyStore$PrivateKeyEntry.getCertificate()" because "accountKeyEntry" is null
	at com.github.valb3r.letsencrypthelper.tomcat.TomcatWellKnownLetsEncryptChallengeEndpointConfig.updateCertificateAndKeystore(TomcatWellKnownLetsEncryptChallengeEndpointConfig.java:395) ~[letsencrypt-helper-tomcat-0.2.4.jar:na]
	at com.github.valb3r.letsencrypthelper.tomcat.TomcatWellKnownLetsEncryptChallengeEndpointConfig.executeCheckCertValidityAndRotateIfNeeded(TomcatWellKnownLetsEncryptChallengeEndpointConfig.java:336) ~[letsencrypt-helper-tomcat-0.2.4.jar:na]
	at com.github.valb3r.letsencrypthelper.tomcat.TomcatWellKnownLetsEncryptChallengeEndpointConfig.letsEncryptCheckCertValidityAndRotateIfNeeded(TomcatWellKnownLetsEncryptChallengeEndpointConfig.java:308) ~[letsencrypt-helper-tomcat-0.2.4.jar:na]
	at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

Protocol org.apache.coyote.http11.Http11NioProtocol:443 has different KeyStore file

I've been trying all day to try and get this working but I keep encountering the same issue. I've setup my application.properties file as below:

server.port=443
server.ssl.enabled=true
server.ssl.protocol=TLS
server.ssl.key-store=/path/to/custom/file.keystore
server.ssl.key-store-type=pkcs12
server.ssl.key-store-password=xxx
server.ssl.key-alias=name

lets-encrypt-helper.domain=xxx
lets-encrypt-helper.contact=xxx

When the application boots up it seems to get to:

    var sslConfig = Arrays.stream(protocol.findSslHostConfigs())
        .filter(it -> null != it.getCertificateKeystoreFile())
        .filter(it -> it.getCertificateKeystoreFile().contains(serverProperties.getSsl().getKeyStore()))
        .filter(it -> serverProperties.getSsl().getKeyStorePassword().equals(it.getCertificateKeystorePassword()))
        .findFirst()
        .orElse(null);

And this method chain always returns null. I added the class file to my project, added additional logging and rebuilt and by printing out the value of the SSL host configs I get:

Keystore file for _default_ (protocols TLSv1,TLSv1.3,TLSv1.2,SSLv2Hello,TLSv1.1) = null

Where _default_ is the SSL host config getHostName(). I've followed all the steps verbatim from the readme but I'm unable to work out how to fix this problem. Do you have any idea whether additional configuration is needed? I even generated a dummy certificate with the matching alias name but it still produces a null keystore file.

Edit

Worth noting that this is Spring Boot 4.7.2

Thanks

Explicitly store LE account ID

Due to some issues on LE side it is useful to be able to access LE accountID in unencrypted way for diagnostics (to prevent future issues):
Example issue as motivation (it does not affect this lib)

Hello,

Please immediately renew your TLS certificate(s) that were issued from
Let's Encrypt using the TLS-ALPN-01 validation method and the following
ACME registration (account) ID(s):

 111111111  22222222 

We've determined that an error made it possible for TLS-ALPN-01
challenges, completed before today, to not comply with certificate
issuance requirements. We have remediated this problem and will revoke
all unexpired certificates that used this validation method at 16:00 UTC
on 28 January 2022. Please renew your certificates now to ensure an
uninterrupted experience for your site visitors.

We apologize for any inconvenience this may cause. If you need support
in the renewal process, please comment on our forum post. Our staff and
community members are available to help:

ID 111111111 should be accessible for diagnostics

Latest version 3.0.0 not working with Spring boot 3.1.0, Getting exception

2023-08-03 12:26:16 [main] [ERROR] SpringApplication - Application run failed
java.lang.IllegalStateException: Failed to configure LetsEncrypt
at com.github.valb3r.letsencrypthelper.tomcat.TomcatWellKnownLetsEncryptChallengeEndpointConfig.onApplicationEvent(TomcatWellKnownLetsEncryptChallengeEndpointConfig.java:218)
at com.github.valb3r.letsencrypthelper.tomcat.TomcatWellKnownLetsEncryptChallengeEndpointConfig.onApplicationEvent(TomcatWellKnownLetsEncryptChallengeEndpointConfig.java:84)
at com.github.valb3r.letsencrypthelper.tomcat.TomcatWellKnownLetsEncryptChallengeEndpointConfig$$SpringCGLIB$$0.onApplicationEvent()
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:437)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:370)
at org.springframework.boot.context.event.EventPublishingRunListener.ready(EventPublishingRunListener.java:109)
at org.springframework.boot.SpringApplicationRunListeners.lambda$ready$6(SpringApplicationRunListeners.java:80)
at java.base/java.lang.Iterable.forEach(Unknown Source)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112)
at org.springframework.boot.SpringApplicationRunListeners.ready(SpringApplicationRunListeners.java:80)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:330)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1305)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1294)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.