Skip to content

Commit 225b18b

Browse files
committed
devapp/devappserver: support TLS for local dev and prod via autocert
Updates golang/go#20691 Change-Id: If0f160713496b1c9ebceacb4975e9afa44539e5a Reviewed-on: https://go-review.googlesource.com/46720 Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 5c6aa2c commit 225b18b

File tree

4 files changed

+114
-23
lines changed

4 files changed

+114
-23
lines changed

devapp/devappserver/Dockerfile.0

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ RUN go get -d github.com/kylelemons/godebug/pretty && \
2020
cd /go/src/github.com/kylelemons/godebug && git reset --hard a616ab194758ae0a11290d87ca46ee8c440117b0
2121

2222
RUN go get -d golang.org/x/net/context && \
23+
go get -d golang.org/x/net/http2 && \
2324
cd /go/src/golang.org/x/net && git reset --hard fe686d45ea04bc1bd4eff6a52865ce8757320325
2425

2526
RUN go get -d golang.org/x/oauth2 && \
@@ -29,8 +30,12 @@ RUN go get -d golang.org/x/sync/errgroup && \
2930
cd /go/src/golang.org/x/sync && git reset --hard f52d1811a62927559de87708c8913c1650ce4f26
3031

3132
RUN go get -d cloud.google.com/go/compute/metadata && \
33+
go get -d cloud.google.com/go/storage && \
3234
cd /go/src/cloud.google.com/go && git reset --hard 23179f286bc31e07fba2eddaa540fb999b1b1fd9
3335

36+
RUN go get -d golang.org/x/crypto/acme/autocert && \
37+
cd /go/src/golang.org/x/crypto && git reset --hard adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d
38+
3439
# Optimization to speed COPY+go install steps later. This go install
3540
# isn't required for correctness.
3641
RUN go install github.com/aclements/go-gg/generic/slice \

devapp/devappserver/deployment-dev.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ spec:
1313
- name: devappserver
1414
image: gcr.io/go-dashboard-dev/devappserver:latest
1515
imagePullPolicy: Always
16+
command: ["/devappserver", "-listen=:80"]
1617
ports:
1718
- containerPort: 80
1819
- containerPort: 443

devapp/devappserver/deployment-prod.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ spec:
1313
- name: devappserver
1414
image: gcr.io/symbolic-datum-552/devappserver:latest
1515
imagePullPolicy: Always
16+
command: ["/devappserver", "-listen=:80", "-autocert-bucket=golang-devapp-autocert"]
1617
ports:
1718
- containerPort: 80
1819
- containerPort: 443

devapp/devappserver/main.go

Lines changed: 107 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,134 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// Devapp generates the dashboard that powers dev.golang.org. This binary is
6-
// designed to be run outside of an App Engine context.
7-
//
8-
// Usage:
9-
//
10-
// devappserver --http=:8081
11-
//
12-
// By default devappserver listens on port 80.
13-
//
14-
// For the moment, Github issues and Gerrit CL's are stored in memory
15-
// in the running process. To trigger an initial download, visit
16-
// http://localhost:8081/update and/or http://localhost:8081/update/stats in
17-
// your browser.
5+
// Devapp generates the dashboard that powers dev.golang.org.
186

197
package main
208

219
import (
10+
"context"
11+
"crypto/tls"
12+
"errors"
2213
"flag"
2314
"fmt"
2415
"log"
2516
"net"
2617
"net/http"
18+
"net/http/httptest"
2719
"os"
20+
"strconv"
21+
"strings"
22+
"time"
23+
24+
"cloud.google.com/go/storage"
25+
"golang.org/x/build/autocertcache"
26+
"golang.org/x/crypto/acme/autocert"
27+
"golang.org/x/net/http2"
2828

2929
_ "golang.org/x/build/devapp" // registers HTTP handlers
3030
)
3131

3232
func init() {
3333
flag.Usage = func() {
34-
os.Stderr.WriteString(`usage: devappserver [-http=addr]
35-
36-
devappserver generates the dashboard that powers dev.golang.org.
37-
`)
34+
os.Stderr.WriteString("devappserver generates the dashboard that powers dev.golang.org.\n")
35+
flag.PrintDefaults()
3836
}
3937
}
4038

4139
func main() {
42-
httpAddr := flag.String("http", ":80", "HTTP service address (e.g., ':8080')")
40+
var (
41+
listen = flag.String("listen", "localhost:6343", "listen address")
42+
devTLSPort = flag.Int("dev-tls-port", 0, "if non-zero, port number to run localhost self-signed TLS server")
43+
autocertBucket = flag.String("autocert-bucket", "", "if non-empty, listen on port 443 and serve a LetsEncrypt TLS cert using this Google Cloud Storage bucket as a cache")
44+
)
4345
flag.Parse()
44-
ln, err := net.Listen("tcp", *httpAddr)
46+
47+
ln, err := net.Listen("tcp", *listen)
48+
if err != nil {
49+
log.Fatalf("Error listening on %s: %v\n", *listen, err)
50+
}
51+
log.Printf("Listening on %s\n", ln.Addr())
52+
53+
errc := make(chan error)
54+
if ln != nil {
55+
go func() { errc <- fmt.Errorf("http.Serve = %v", http.Serve(ln, nil)) }()
56+
}
57+
if *autocertBucket != "" {
58+
go func() { errc <- serveAutocertTLS(*autocertBucket) }()
59+
}
60+
if *devTLSPort != 0 {
61+
go func() { errc <- serveDevTLS(*devTLSPort) }()
62+
}
63+
64+
log.Fatal(<-errc)
65+
}
66+
67+
func serveDevTLS(port int) error {
68+
ln, err := net.Listen("tcp", "localhost:"+strconv.Itoa(port))
69+
if err != nil {
70+
return err
71+
}
72+
defer ln.Close()
73+
log.Printf("Serving self-signed TLS at https://%s", ln.Addr())
74+
// Abuse httptest for its localhost TLS setup code:
75+
ts := httptest.NewUnstartedServer(http.DefaultServeMux)
76+
// Ditch the provided listener, replace with our own:
77+
ts.Listener.Close()
78+
ts.Listener = ln
79+
ts.TLS = &tls.Config{
80+
NextProtos: []string{"h2", "http/1.1"},
81+
InsecureSkipVerify: true,
82+
}
83+
ts.StartTLS()
84+
85+
select {}
86+
}
87+
88+
func serveAutocertTLS(bucket string) error {
89+
ln, err := net.Listen("tcp", ":443")
90+
if err != nil {
91+
return err
92+
}
93+
defer ln.Close()
94+
sc, err := storage.NewClient(context.Background())
95+
if err != nil {
96+
return fmt.Errorf("storage.NewClient: %v", err)
97+
}
98+
m := autocert.Manager{
99+
Prompt: autocert.AcceptTOS,
100+
HostPolicy: func(ctx context.Context, host string) error {
101+
if !strings.HasSuffix(host, ".golang.org") {
102+
return errors.New("refusing to serve autocert on provided domain")
103+
}
104+
return nil
105+
},
106+
Cache: autocertcache.NewGoogleCloudStorageCache(sc, bucket),
107+
}
108+
config := &tls.Config{
109+
GetCertificate: m.GetCertificate,
110+
NextProtos: []string{"h2", "http/1.1"},
111+
}
112+
tlsLn := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
113+
server := &http.Server{
114+
Addr: ln.Addr().String(),
115+
}
116+
if err := http2.ConfigureServer(server, nil); err != nil {
117+
log.Fatalf("http2.ConfigureServer: %v", err)
118+
}
119+
log.Printf("Serving TLS at %s", tlsLn.Addr())
120+
return server.Serve(tlsLn)
121+
}
122+
123+
type tcpKeepAliveListener struct {
124+
*net.TCPListener
125+
}
126+
127+
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
128+
tc, err := ln.AcceptTCP()
45129
if err != nil {
46-
fmt.Fprintf(os.Stderr, "Error listening on %s: %v\n", *httpAddr, err)
47-
os.Exit(2)
130+
return
48131
}
49-
fmt.Fprintf(os.Stderr, "Serving at %s\n", ln.Addr().String())
50-
log.Fatal(http.Serve(ln, nil))
132+
tc.SetKeepAlive(true)
133+
tc.SetKeepAlivePeriod(3 * time.Minute)
134+
return tc, nil
51135
}

0 commit comments

Comments
 (0)