From cf701bc4fcdb07be27854bacbe6dc3947cdf2f46 Mon Sep 17 00:00:00 2001 From: Heschi Kreinick Date: Tue, 27 Sep 2022 15:44:27 -0400 Subject: 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 TryBot-Result: Gopher Robot Auto-Submit: Heschi Kreinick Reviewed-by: Jamal Carvalho --- cmd/admingolangorg/README.md | 3 +- cmd/admingolangorg/app.yaml | 2 +- cmd/admingolangorg/index.html | 11 +++++ cmd/admingolangorg/main.go | 101 +++++++++++++++++++++++++++++++++++++++- cmd/admingolangorg/snippet.html | 15 ++++++ 5 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 cmd/admingolangorg/index.html create mode 100644 cmd/admingolangorg/snippet.html (limited to 'cmd') 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 @@ + + + + +

administer short links

+

remove a snippet

+ 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 @@ + + + + +

Delete a snippet

+
+ + + +
+ -- cgit v1.3-6-g1900