Skip to content

Instantly share code, notes, and snippets.

@apstndb
Last active June 17, 2019 04:37
Show Gist options
  • Save apstndb/664ce4dcf5b82ccabf5071eeb5394183 to your computer and use it in GitHub Desktop.
Save apstndb/664ce4dcf5b82ccabf5071eeb5394183 to your computer and use it in GitHub Desktop.
Cloud Run service-to-service auth example

setting

$ gcloud iam service-accounts create callee
$ gcloud iam service-accounts create caller
$ gcloud builds submit -t gcr.io/${PROJECT_ID}/oidctest .
$ gcloud beta run deploy --image=gcr.io/${PROJECT_ID}/oidctest --allow-unauthenticated \
    --service-account=caller@${PROJECT_ID}.iam.gserviceaccount.com caller 
$ gcloud beta run deploy --image=gcr.io/${PROJECT_ID}/oidctest --no-allow-unauthenticated \
    --service-account=callee@{PROJECT_ID}.iam.gserviceaccount.com callee
$ gcloud beta run services add-iam-policy-binding callee \
    --member=serviceAccount:caller@${PROJECT_ID}.iam.gserviceaccount.com \
    --role=roles/run.invoker

call

$ CALLER_URL=$(gcloud beta run services describe caller --format="value(status.domain)")
$ CALLEE_URL=$(gcloud beta run services describe callee --format="value(status.domain)")

# callee can't be called without auth
$ curl -i "${CALLEE_URL}"
HTTP/2 403
...

# callee can be called by caller
$ curl "${CALLER_URL}/call?destination=${CALLEE_URL}"
caller=caller: callee=callee

refs

FROM golang:1.11.5 as builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -v -o main ./
FROM gcr.io/distroless/static
COPY --from=builder /src/main /main
ENTRYPOINT ["/main"]
EXPOSE 8080
module github.com/apstndb/oidctest
go 1.12
require cloud.google.com/go v0.34.0
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"cloud.google.com/go/compute/metadata"
)
func main() {
mux := http.NewServeMux()
serviceName := os.Getenv("K_SERVICE")
mux.HandleFunc("/call", func(w http.ResponseWriter, r *http.Request) {
destination := r.URL.Query().Get("destination")
idToken, err := metadata.Get("instance/service-accounts/default/identity?audience=" + destination)
if err != nil {
http.Error(w, fmt.Sprintf("caller=%s: %s", serviceName, err.Error()), http.StatusInternalServerError)
return
}
req, _ := http.NewRequest(http.MethodGet, destination, nil)
req.Header.Set("Authorization", "Bearer "+idToken)
resp, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, fmt.Sprintf("caller=%s: %s", serviceName, err.Error()), http.StatusInternalServerError)
return
}
if resp.StatusCode != http.StatusOK {
http.Error(w, fmt.Sprintf("caller=%s: callee returns: %d\n", serviceName, resp.StatusCode), http.StatusInternalServerError)
io.Copy(w, resp.Body)
return
}
defer resp.Body.Close()
fmt.Fprintf(w, "caller=%s: ", serviceName)
io.Copy(w, resp.Body)
})
mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintf(w, "callee=%s", serviceName)
})
listenAndServe(mux)
}
func listenAndServe(handler http.Handler) {
port := "8080"
if s := os.Getenv("PORT"); s != "" {
port = s
}
if err := http.ListenAndServe(":"+port, handler); err != nil {
log.Fatalf("http.listenAndServe: %v", err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment