Adapted from the https://github.com/distribution/distribution/pull/3943

	-mi

From 9b4f2caaf89c63b015bbed3e710501df5cf2f75a Mon Sep 17 00:00:00 2001
From: Alex Lavallee <73203142+lavalleeale@users.noreply.github.com>
Date: Sun, 18 Jun 2023 09:52:12 -0700
Subject: [PATCH] Add support for modern ACME

Signed-off-by: Alex Lavallee <73203142+lavalleeale@users.noreply.github.com>
---
 configuration/configuration.go                |    4 +
 configuration/configuration_test.go           |   14 +-
 docs/configuration.md                         |   12 +-
 registry/registry.go                          |   29 +-
...

--- configuration/configuration.go
+++ configuration/configuration.go
@@ -129,6 +129,10 @@ type Configuration struct {
 				// Hosts specifies the hosts which are allowed to obtain Let's
 				// Encrypt certificates.
 				Hosts []string `yaml:"hosts,omitempty"`
+
+				// DirectoryURL points to the CA directory endpoint.
+				// If empty, LetsEncrypt is used.
+				DirectoryURL string `yaml:"directoryurl,omitempty"`
 			} `yaml:"letsencrypt,omitempty"`
 		} `yaml:"tls,omitempty"`
 
--- configuration/configuration_test.go
+++ configuration/configuration_test.go
@@ -89,9 +89,10 @@ var configStruct = Configuration{
 			MinimumTLS   string   `yaml:"minimumtls,omitempty"`
 			CipherSuites []string `yaml:"ciphersuites,omitempty"`
 			LetsEncrypt  struct {
-				CacheFile string   `yaml:"cachefile,omitempty"`
-				Email     string   `yaml:"email,omitempty"`
-				Hosts     []string `yaml:"hosts,omitempty"`
+				CacheFile    string   `yaml:"cachefile,omitempty"`
+				Email        string   `yaml:"email,omitempty"`
+				Hosts        []string `yaml:"hosts,omitempty"`
+				DirectoryURL string   `yaml:"directoryurl,omitempty"`
 			} `yaml:"letsencrypt,omitempty"`
 		} `yaml:"tls,omitempty"`
 		Headers http.Header `yaml:"headers,omitempty"`
@@ -113,9 +114,10 @@ var configStruct = Configuration{
 			MinimumTLS   string   `yaml:"minimumtls,omitempty"`
 			CipherSuites []string `yaml:"ciphersuites,omitempty"`
 			LetsEncrypt  struct {
-				CacheFile string   `yaml:"cachefile,omitempty"`
-				Email     string   `yaml:"email,omitempty"`
-				Hosts     []string `yaml:"hosts,omitempty"`
+				CacheFile    string   `yaml:"cachefile,omitempty"`
+				Email        string   `yaml:"email,omitempty"`
+				Hosts        []string `yaml:"hosts,omitempty"`
+				DirectoryURL string   `yaml:"directoryurl,omitempty"`
 			} `yaml:"letsencrypt,omitempty"`
 		}{
 			ClientCAs: []string{"/path/to/ca.pem"},
--- docs/configuration.md
+++ docs/configuration.md
@@ -229,6 +229,7 @@ http:
       cachefile: /path/to/cache-file
       email: emailused@letsencrypt.com
       hosts: [myregistryaddress.org]
+      directoryurl: https://acme-v02.api.letsencrypt.org/directory
   debug:
     addr: localhost:5001
     prometheus:
@@ -879,11 +880,12 @@ TLS certificates provided by
 > that are valid for this registry to avoid trying to get certificates for random
 > hostnames due to malicious clients connecting with bogus SNI hostnames.
 
-| Parameter | Required | Description                                           |
-|-----------|----------|-------------------------------------------------------|
-| `cachefile` | yes    | Absolute path to a file where the Let's Encrypt agent can cache data. |
-| `email`   | yes      | The email address used to register with Let's Encrypt. |
-| `hosts`   | no       | The hostnames allowed for Let's Encrypt certificates. |
+| Parameter      | Required | Description                                                           |
+|----------------|----------|-----------------------------------------------------------------------|
+| `cachefile`    | yes      | Absolute path to a file where the Let's Encrypt agent can cache data. |
+| `email`        | yes      | The email address used to register with Let's Encrypt.                |
+| `hosts`        | no       | The hostnames allowed for Let's Encrypt certificates.                 |
+| `directoryurl` | no       | The url to use for the ACME server.                                   |
 
 ### `debug`
 
--- registry/registry.go
+++ registry/registry.go
@@ -13,7 +13,8 @@ import (
 	"syscall"
 	"time"
 
-	"rsc.io/letsencrypt"
+	"golang.org/x/crypto/acme"
+	"golang.org/x/crypto/acme/autocert"
 
 	logrus_bugsnag "github.com/Shopify/logrus-bugsnag"
 
@@ -210,6 +211,14 @@ func getCipherSuiteNames(ids []uint16) []string {
 	return names
 }
 
+// set ACME-server/DirectoryURL, if provided
+func setDirectoryURL(directoryurl string) *acme.Client {
+	if len(directoryurl) > 0 {
+		return &acme.Client{DirectoryURL: directoryurl}
+	}
+	return nil
+}
+
 // ListenAndServe runs the registry's HTTP server.
 func (registry *Registry) ListenAndServe() error {
 	config := registry.config
@@ -247,19 +256,15 @@ func (registry *Registry) ListenAndServe() error {
 			if config.HTTP.TLS.Certificate != "" {
 				return fmt.Errorf("cannot specify both certificate and Let's Encrypt")
 			}
-			var m letsencrypt.Manager
-			if err := m.CacheFile(config.HTTP.TLS.LetsEncrypt.CacheFile); err != nil {
-				return err
-			}
-			if !m.Registered() {
-				if err := m.Register(config.HTTP.TLS.LetsEncrypt.Email, nil); err != nil {
-					return err
-				}
-			}
-			if len(config.HTTP.TLS.LetsEncrypt.Hosts) > 0 {
-				m.SetHosts(config.HTTP.TLS.LetsEncrypt.Hosts)
+			m := &autocert.Manager{
+				HostPolicy: autocert.HostWhitelist(config.HTTP.TLS.LetsEncrypt.Hosts...),
+				Cache:      autocert.DirCache(config.HTTP.TLS.LetsEncrypt.CacheFile),
+				Email:      config.HTTP.TLS.LetsEncrypt.Email,
+				Prompt:     autocert.AcceptTOS,
+				Client:     setDirectoryURL(config.HTTP.TLS.LetsEncrypt.DirectoryURL),
 			}
 			tlsConf.GetCertificate = m.GetCertificate
+			tlsConf.NextProtos = append(tlsConf.NextProtos, acme.ALPNProto)
 		} else {
 			tlsConf.Certificates = make([]tls.Certificate, 1)
 			tlsConf.Certificates[0], err = tls.LoadX509KeyPair(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key)
