golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/https/https.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package https contains helpers for starting an HTTP/HTTPS server.
     6  package https // import "golang.org/x/build/internal/https"
     7  
     8  import (
     9  	"context"
    10  	"crypto/ecdsa"
    11  	"crypto/elliptic"
    12  	"crypto/rand"
    13  	"crypto/tls"
    14  	"crypto/x509"
    15  	"crypto/x509/pkix"
    16  	"flag"
    17  	"fmt"
    18  	"math/big"
    19  	"net/http"
    20  	"strings"
    21  	"time"
    22  
    23  	"cloud.google.com/go/storage"
    24  	"golang.org/x/build/autocertcache"
    25  	"golang.org/x/crypto/acme/autocert"
    26  )
    27  
    28  type Options struct {
    29  	// Specifies the GCS bucket to use with AutocertAddr.
    30  	AutocertBucket string
    31  	// If non-empty, listen on this address and serve HTTPS using a Let's Encrypt cert stored in AutocertBucket.
    32  	AutocertAddr string
    33  	// If non-empty, listen on this address and serve HTTPS using a self-signed cert.
    34  	SelfSignedAddr string
    35  	// If non-empty, listen on this address and serve HTTP.
    36  	HTTPAddr string
    37  	// If non-empty, respond unconditionally with 200 OK to requests on this path.
    38  	HealthPath string
    39  }
    40  
    41  var DefaultOptions = &Options{}
    42  
    43  // RegisterFlags registers flags that control DefaultOptions, which will be
    44  // used with ListenAndServe below.
    45  // Typical usage is to call RegisterFlags at the beginning of main, then
    46  // ListenAndServe at the end.
    47  func RegisterFlags(set *flag.FlagSet) {
    48  	set.StringVar(&DefaultOptions.AutocertBucket, "autocert-bucket", "", "specifies the GCS bucket to use with autocert-addr")
    49  	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")
    50  	set.StringVar(&DefaultOptions.SelfSignedAddr, "listen-https-selfsigned", "", "if non-empty, listen on this address and serve HTTPS using a self-signed cert")
    51  	set.StringVar(&DefaultOptions.HTTPAddr, "listen-http", "", "if non-empty, listen on this address and serve HTTP")
    52  	set.StringVar(&DefaultOptions.HealthPath, "health-path", "/healthz", "if non-empty, respond unconditionally with 200 OK to requests on this path")
    53  }
    54  
    55  // ListenAndServe runs the servers configured by DefaultOptions. It always
    56  // returns a non-nil error.
    57  func ListenAndServe(ctx context.Context, handler http.Handler) error {
    58  	return ListenAndServeOpts(ctx, handler, DefaultOptions)
    59  }
    60  
    61  // ListenAndServeOpts runs the servers configured by opts. It always
    62  // returns a non-nil error.
    63  func ListenAndServeOpts(ctx context.Context, handler http.Handler, opts *Options) error {
    64  	errc := make(chan error, 3)
    65  
    66  	if opts.HealthPath != "" {
    67  		wrapped := handler
    68  		handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    69  			if r.URL.Path == opts.HealthPath {
    70  				w.WriteHeader(http.StatusOK)
    71  				w.Write([]byte("ok"))
    72  			} else {
    73  				wrapped.ServeHTTP(w, r)
    74  			}
    75  		})
    76  	}
    77  
    78  	if opts.HTTPAddr != "" {
    79  		server := &http.Server{Addr: opts.HTTPAddr, Handler: handler}
    80  		defer server.Close()
    81  		go func() { errc <- server.ListenAndServe() }()
    82  	}
    83  
    84  	if opts.AutocertAddr != "" {
    85  		if opts.AutocertBucket == "" {
    86  			return fmt.Errorf("must specify autocert-bucket with listen-https-autocert")
    87  		}
    88  		server, err := autocertServer(ctx, opts.AutocertBucket, opts.AutocertAddr, handler)
    89  		if err != nil {
    90  			return err
    91  		}
    92  		defer server.Close()
    93  		go func() { errc <- server.ListenAndServeTLS("", "") }()
    94  	}
    95  
    96  	if opts.SelfSignedAddr != "" {
    97  		server, err := selfSignedServer(opts.SelfSignedAddr, handler)
    98  		if err != nil {
    99  			return err
   100  		}
   101  		defer server.Close()
   102  		go func() { errc <- server.ListenAndServeTLS("", "") }()
   103  	}
   104  
   105  	return <-errc
   106  }
   107  
   108  // autocertServer returns an http.Server that is configured to serve
   109  // HTTPS on addr using a Let's Encrypt certificate cached in the GCS
   110  // bucket specified by bucket.
   111  func autocertServer(ctx context.Context, bucket, addr string, handler http.Handler) (*http.Server, error) {
   112  	sc, err := storage.NewClient(ctx)
   113  	if err != nil {
   114  		return nil, fmt.Errorf("storage.NewClient: %v", err)
   115  	}
   116  	const hostSuffix = ".golang.org"
   117  	m := autocert.Manager{
   118  		Prompt: autocert.AcceptTOS,
   119  		HostPolicy: func(ctx context.Context, host string) error {
   120  			if !strings.HasSuffix(host, hostSuffix) {
   121  				return fmt.Errorf("refusing to serve autocert on provided domain (%q), must have the suffix %q",
   122  					host, hostSuffix)
   123  			}
   124  			return nil
   125  		},
   126  		Cache: autocertcache.NewGoogleCloudStorageCache(sc, bucket),
   127  	}
   128  	server := &http.Server{
   129  		Addr:      addr,
   130  		Handler:   handler,
   131  		TLSConfig: m.TLSConfig(),
   132  	}
   133  	return server, nil
   134  }
   135  
   136  // selfSignedServer returns an http.Server that is configured to serve
   137  // self-signed HTTPS on addr.
   138  func selfSignedServer(addr string, handler http.Handler) (*http.Server, error) {
   139  	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
   145  	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
   146  	if err != nil {
   147  		return nil, fmt.Errorf("failed to generate serial number: %v", err)
   148  	}
   149  	template := &x509.Certificate{
   150  		SerialNumber: serialNumber,
   151  		Subject: pkix.Name{
   152  			Organization: []string{"Go build system"},
   153  		},
   154  		NotBefore:   time.Now().Add(-time.Minute),
   155  		NotAfter:    time.Now().Add(10 * 365 * 24 * time.Hour),
   156  		KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
   157  		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
   158  		IsCA:        true,
   159  	}
   160  
   161  	derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	s := &http.Server{
   166  		Addr:    addr,
   167  		Handler: handler,
   168  		TLSConfig: &tls.Config{
   169  			Certificates: []tls.Certificate{{
   170  				Certificate: [][]byte{derBytes},
   171  				PrivateKey:  priv,
   172  			}},
   173  		},
   174  	}
   175  	return s, nil
   176  }