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 }