Skip to content

Commit

Permalink
internal/https: support non-Let's Encrypt ACME endpoints
Browse files Browse the repository at this point in the history
In particular, this should allow us to use the GTS ACME service to get
certificates.

Change-Id: Ifb2f1be12fb0f810b22311ef75a8c1d27255538f
Reviewed-on: https://go-review.googlesource.com/c/build/+/593735
Reviewed-by: Carlos Amedee <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
  • Loading branch information
rolandshoemaker committed Jan 6, 2025
1 parent 5aca588 commit c463678
Showing 1 changed file with 44 additions and 9 deletions.
53 changes: 44 additions & 9 deletions internal/https/https.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,34 @@ import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"flag"
"fmt"
"log"
"math/big"
"net/http"
"strings"
"time"

"cloud.google.com/go/storage"
"golang.org/x/build/autocertcache"
"golang.org/x/build/internal/secret"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
)

type Options struct {
// Specifies the ACME directory to use to retrieve certificates.
AutocertDirectory string
// Specifies the ACME external account binding token to use. This should be
// a JSON-encoded acme.ExternalAccountBinding struct.
AutocertEAB string
// Specifies the GCS bucket to use with AutocertAddr.
AutocertBucket string
// If non-empty, listen on this address and serve HTTPS using a Let's Encrypt cert stored in AutocertBucket.
// If non-empty, listen on this address and serve HTTPS using a cert stored in AutocertBucket.
AutocertAddr string
// Specifies the email address to use when creating an ACME account.
AutocertEmail string
// If non-empty, listen on this address and serve HTTPS using a self-signed cert.
SelfSignedAddr string
// If non-empty, listen on this address and serve HTTP.
Expand All @@ -45,11 +56,24 @@ var DefaultOptions = &Options{}
// Typical usage is to call RegisterFlags at the beginning of main, then
// ListenAndServe at the end.
func RegisterFlags(set *flag.FlagSet) {
// This is slightly hacky, but necessary since some callers may have already
// called secret.InitFlagSupport, and others may not have, and there is not
// a perfect way to know (and calling it twice would stomp on already defined
// secret flags).
if secret.DefaultResolver.Context == nil {
if err := secret.InitFlagSupport(context.Background()); err != nil {
log.Fatalln(err)
}
}
secret.DefaultResolver.FlagVar(set, &DefaultOptions.AutocertEAB, "autocert-eab", "specifies the ACME external account binding to use")

set.StringVar(&DefaultOptions.AutocertDirectory, "autocert-directory", "", "specifies the ACME directory to use")
set.StringVar(&DefaultOptions.AutocertBucket, "autocert-bucket", "", "specifies the GCS bucket to use with autocert-addr")
set.StringVar(&DefaultOptions.AutocertAddr, "listen-https-autocert", "", "if non-empty, listen on this address and serve HTTPS using a Let's Encrypt cert stored in autocert-bucket")
set.StringVar(&DefaultOptions.AutocertAddr, "listen-https-autocert", "", "if non-empty, listen on this address and serve HTTPS using a cert stored in autocert-bucket")
set.StringVar(&DefaultOptions.SelfSignedAddr, "listen-https-selfsigned", "", "if non-empty, listen on this address and serve HTTPS using a self-signed cert")
set.StringVar(&DefaultOptions.HTTPAddr, "listen-http", "", "if non-empty, listen on this address and serve HTTP")
set.StringVar(&DefaultOptions.HealthPath, "health-path", "/healthz", "if non-empty, respond unconditionally with 200 OK to requests on this path")
set.StringVar(&DefaultOptions.AutocertEmail, "autocert-email", "", "specifies the contact email to use when creating an ACME account")
}

// ListenAndServe runs the servers configured by DefaultOptions. It always
Expand Down Expand Up @@ -85,7 +109,7 @@ func ListenAndServeOpts(ctx context.Context, handler http.Handler, opts *Options
if opts.AutocertBucket == "" {
return fmt.Errorf("must specify autocert-bucket with listen-https-autocert")
}
server, err := autocertServer(ctx, opts.AutocertBucket, opts.AutocertAddr, handler)
server, err := autocertServer(ctx, opts, handler)
if err != nil {
return err
}
Expand All @@ -105,10 +129,9 @@ func ListenAndServeOpts(ctx context.Context, handler http.Handler, opts *Options
return <-errc
}

// autocertServer returns an http.Server that is configured to serve
// HTTPS on addr using a Let's Encrypt certificate cached in the GCS
// bucket specified by bucket.
func autocertServer(ctx context.Context, bucket, addr string, handler http.Handler) (*http.Server, error) {
// autocertServer returns an http.Server that is configured to serve HTTPS on
// addr using a certificate cached in the GCS bucket specified by bucket.
func autocertServer(ctx context.Context, opts *Options, handler http.Handler) (*http.Server, error) {
sc, err := storage.NewClient(ctx)
if err != nil {
return nil, fmt.Errorf("storage.NewClient: %v", err)
Expand All @@ -123,10 +146,22 @@ func autocertServer(ctx context.Context, bucket, addr string, handler http.Handl
}
return nil
},
Cache: autocertcache.NewGoogleCloudStorageCache(sc, bucket),
Cache: autocertcache.NewGoogleCloudStorageCache(sc, opts.AutocertBucket),
Email: opts.AutocertEmail,
}
if opts.AutocertDirectory != "" {
m.Client = new(acme.Client)
m.Client.DirectoryURL = opts.AutocertDirectory
}
if opts.AutocertEAB != "" {
var eab acme.ExternalAccountBinding
if err := json.Unmarshal([]byte(opts.AutocertEAB), &eab); err != nil {
return nil, fmt.Errorf("failed to unmarshal -autocert-eab: %s", err)
}
m.ExternalAccountBinding = &eab
}
server := &http.Server{
Addr: addr,
Addr: opts.AutocertAddr,
Handler: handler,
TLSConfig: m.TLSConfig(),
}
Expand Down

0 comments on commit c463678

Please sign in to comment.