diff options
| author | Heschi Kreinick <heschi@google.com> | 2022-09-27 15:44:27 -0400 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2022-09-27 21:05:40 +0000 |
| commit | cf701bc4fcdb07be27854bacbe6dc3947cdf2f46 (patch) | |
| tree | 38be4762e3ffd33de021465c3b6c1f355e1b7748 /cmd | |
| parent | aff3925441277a08f37b9ff4676b48c871ddac26 (diff) | |
| download | go-x-website-cf701bc4fcdb07be27854bacbe6dc3947cdf2f46.tar.xz | |
cmd/admingolangorg: run under IAP and support playground removals
In locking down our permissions, I blocked access to this site and
x/build/rmplaysnippet. Put the site behind IAP, and add playground
removal support.
Change-Id: I7722f5911f31a140c93780fe0417dd2368276926
Reviewed-on: https://go-review.googlesource.com/c/website/+/435455
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Heschi Kreinick <heschi@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/admingolangorg/README.md | 3 | ||||
| -rw-r--r-- | cmd/admingolangorg/app.yaml | 2 | ||||
| -rw-r--r-- | cmd/admingolangorg/index.html | 11 | ||||
| -rw-r--r-- | cmd/admingolangorg/main.go | 101 | ||||
| -rw-r--r-- | cmd/admingolangorg/snippet.html | 15 |
5 files changed, 127 insertions, 5 deletions
diff --git a/cmd/admingolangorg/README.md b/cmd/admingolangorg/README.md index af4f7b39..dc80664b 100644 --- a/cmd/admingolangorg/README.md +++ b/cmd/admingolangorg/README.md @@ -1,7 +1,6 @@ # admingolangorg -This app serves as the [admin interface](https://admin-dot-golang-org.appspot.com) for the go.dev/s link -shortener. Its functionality may be expanded in the future. +This app serves as the [admin interface](https://admin-dot-golang-org.appspot.com) for the go.dev/s link shortener. It can also remove unwanted playground snippets. ## Deployment: diff --git a/cmd/admingolangorg/app.yaml b/cmd/admingolangorg/app.yaml index 9c77bf90..732c0200 100644 --- a/cmd/admingolangorg/app.yaml +++ b/cmd/admingolangorg/app.yaml @@ -5,12 +5,12 @@ main: ./cmd/admingolangorg env_variables: GOLANGORG_REDIS_ADDR: 10.0.0.4:6379 # instance "gophercache" DATASTORE_PROJECT_ID: golang-org + IAP_AUDIENCE: /projects/397748307997/apps/golang-org handlers: - url: .* script: auto secure: always - login: admin # THIS MUST BE SET vpc_access_connector: name: 'projects/golang-org/locations/us-central1/connectors/golang-vpc-connector' diff --git a/cmd/admingolangorg/index.html b/cmd/admingolangorg/index.html new file mode 100644 index 00000000..31a6f28b --- /dev/null +++ b/cmd/admingolangorg/index.html @@ -0,0 +1,11 @@ +<!-- + Copyright 2022 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. +--> + +<!doctype HTML> +<html lang="en"> + <p><a href="/shortlink">administer short links</a></p> + <p><a href="/snippet">remove a snippet</a></p> +</html> diff --git a/cmd/admingolangorg/main.go b/cmd/admingolangorg/main.go index a8dd26ab..fb358d61 100644 --- a/cmd/admingolangorg/main.go +++ b/cmd/admingolangorg/main.go @@ -8,18 +8,34 @@ package main import ( "context" + _ "embed" + "fmt" "log" "net/http" "os" "strings" + "time" "cloud.google.com/go/datastore" "golang.org/x/website/internal/memcache" "golang.org/x/website/internal/short" + "google.golang.org/api/idtoken" ) +//go:embed index.html +var index string + func main() { - http.HandleFunc("/", short.AdminHandler(getClients())) + audience := os.Getenv("IAP_AUDIENCE") + dsClient, mcClient := getClients() + mux := http.NewServeMux() + mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + w.Write([]byte(index)) + return + })) + mux.Handle("/shortlink", short.AdminHandler(dsClient, mcClient)) + mux.Handle("/snippet", &snippetHandler{dsClient}) port := os.Getenv("PORT") if port == "" { port = "8080" @@ -27,7 +43,58 @@ func main() { } log.Printf("Listening on port %s", port) - log.Fatal(http.ListenAndServe(":"+port, nil)) + log.Fatal(http.ListenAndServe(":"+port, iapAuth(audience, mux))) +} + +type snippetHandler struct { + ds *datastore.Client +} + +//go:embed snippet.html +var snippetForm string + +func (h *snippetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + snippetLink := r.FormValue("snippet") + if snippetLink == "" { + w.Header().Set("Content-Type", "text/html") + w.Write([]byte(snippetForm)) + return + } + + prefixes := []string{ + "https://play.golang.org/p/", + "http://play.golang.org/p/", + "https://go.dev/play/p/", + "http://go.dev/play/p/", + } + var snippetID string + for _, p := range prefixes { + if strings.HasPrefix(snippetLink, p) { + snippetID = strings.TrimPrefix(snippetLink, p) + break + } + } + if !strings.Contains(snippetLink, "/") { + snippetID = snippetLink + } + if snippetID == "" { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "must specify snippet URL or ID\n") + return + } + + k := datastore.NameKey("Snippet", snippetID, nil) + if h.ds.Get(r.Context(), k, new(struct{})) == datastore.ErrNoSuchEntity { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, "Snippet with ID %q does not exist\n", snippetID) + return + } + if err := h.ds.Delete(r.Context(), k); err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Unable to delete Snippet with ID %q: %v\n", snippetID, err) + return + } + w.Write([]byte("snippet deleted\n")) } func getClients() (*datastore.Client, *memcache.Client) { @@ -49,3 +116,33 @@ func getClients() (*datastore.Client, *memcache.Client) { return datastoreClient, memcacheClient } + +func iapAuth(audience string, h http.Handler) http.Handler { + // https://cloud.google.com/iap/docs/signed-headers-howto#verifying_the_jwt_payload + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + jwt := r.Header.Get("x-goog-iap-jwt-assertion") + if jwt == "" { + w.WriteHeader(http.StatusUnauthorized) + fmt.Fprintf(w, "must run under IAP\n") + return + } + + payload, err := idtoken.Validate(r.Context(), jwt, audience) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + log.Printf("JWT validation error: %v", err) + return + } + if payload.Issuer != "https://cloud.google.com/iap" { + w.WriteHeader(http.StatusUnauthorized) + log.Printf("Incorrect issuer: %q", payload.Issuer) + return + } + if payload.Expires+30 < time.Now().Unix() || payload.IssuedAt-30 > time.Now().Unix() { + w.WriteHeader(http.StatusUnauthorized) + log.Printf("Bad JWT times: expires %v, issued %v", time.Unix(payload.Expires, 0), time.Unix(payload.IssuedAt, 0)) + return + } + h.ServeHTTP(w, r) + }) +} diff --git a/cmd/admingolangorg/snippet.html b/cmd/admingolangorg/snippet.html new file mode 100644 index 00000000..fc51718b --- /dev/null +++ b/cmd/admingolangorg/snippet.html @@ -0,0 +1,15 @@ +<!-- + Copyright 2022 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. +--> + +<!doctype HTML> +<html lang="en"> +<p>Delete a snippet</p> +<form method="POST"> +<label for="snippet">Snippet URL</label> +<input type="text" name="snippet" id="snippet" placeholder="https://go.dev/play/p/XXXXX" required> +<input type="submit" name="submit" value="Remove"> +</form> +</html> |
