Skip to content

Commit

Permalink
Support exposing private registries
Browse files Browse the repository at this point in the history
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
  • Loading branch information
ahmetb committed Apr 3, 2019
1 parent d06fdc6 commit 6e050dd
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 11 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
key.json
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@ RUN go build -o /app
FROM alpine
RUN apk add --no-cache ca-certificates
COPY --from=build /app /app

# uncomment the following two lines if you're exposing a private GCR registry
# COPY key.json /app/key.json
# ENV GOOGLE_APPLICATION_CREDENTIALS /app/key.json

ENTRYPOINT [ "/app" ]
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,27 @@ some 15-20 minutes to actually provision TLS certificates for your domain name.
> ⚠️ This will make images in your private GCR registries publicly accessible on
> the internet.
// TODO(ahmetb): Add instructions once feature is ready.
1. Create an [IAM Service
Account](https://cloud.google.com/iam/docs/creating-managing-service-accounts#creating_a_service_account).

1. [Give it
permissions](https://cloud.google.com/container-registry/docs/access-control)
to access the GCR registry GCS Bucket. (Or simply, you can give it the
project-wide `Storage Object Viewer` role.)

1. Copy your service account JSON key into the root of the repository as
`key.json`.

1. (Not ideal, but whatever) Rebuild the docker image with your service account
key JSON in it. This will require editing `Dockerfile` to add `COPY` and
`ENV` directives like:

COPY key.json /app/key.json
ENV GOOGLE_APPLICATION_CREDENTIALS /app/key.json
ENTRYPOINT [...]

then refer to "Building" on how to build/push this new image and
refer to "Deploying" on how to deploy it.

### Advanced Customization

Expand Down
38 changes: 28 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package main

import (
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
Expand Down Expand Up @@ -44,11 +46,21 @@ func main() {
projectID: gcrProjectID,
}

var authHeader string
if keyPath := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"); keyPath != "" {
b, err := ioutil.ReadFile(keyPath)
if err != nil {
log.Fatalf("could not read key file from %s: %+v", keyPath, err)
}
log.Printf("using specified service account json key to authenticate proxied requests")
authHeader = "Basic " + base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("_json_key:%s", string(b))))
}

addr := ":" + port
if browserRedirects {
http.Handle("/", browserRedirectHandler(gcr))
}
http.Handle("/v2/", registryAPIMux(gcr))
http.Handle("/v2/", registryAPIMux(gcr, authHeader))
log.Printf("starting to listen on %s", addr)
if err := http.ListenAndServe(addr, nil); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen error: %+v", err)
Expand All @@ -68,10 +80,12 @@ func browserRedirectHandler(c gcrConfig) http.HandlerFunc {
// registryAPIMux returns a handler for Docker Registry v2 API requests
// (/v2/). Request to path=/v2/ is handled-locally, other /v2/* requests are
// proxied back to GCR endpoint.
func registryAPIMux(c gcrConfig) http.HandlerFunc {
func registryAPIMux(c gcrConfig, authHeader string) http.HandlerFunc {
reverseProxy := &httputil.ReverseProxy{
Transport: roundtripperFunc(gcrRoundtripper),
Director: rewriteRegistryV2(c),
Director: rewriteRegistryV2URL(c),
Transport: &gcrRoundtripper{
authHeader: authHeader,
},
}

return func(w http.ResponseWriter, req *http.Request) {
Expand All @@ -89,9 +103,9 @@ func handleRegistryAPIVersion(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "ok")
}

// rewriteRegistryV2 rewrites request.URL like /v2/* that come into the server
// rewriteRegistryV2URL rewrites request.URL like /v2/* that come into the server
// into https://[GCR_HOST]/v2/[PROJECT_ID]/*. It leaves /v2/ as is.
func rewriteRegistryV2(c gcrConfig) func(*http.Request) {
func rewriteRegistryV2URL(c gcrConfig) func(*http.Request) {
return func(req *http.Request) {
u := req.URL.String()
req.Host = c.host
Expand All @@ -104,13 +118,17 @@ func rewriteRegistryV2(c gcrConfig) func(*http.Request) {
}
}

type roundtripperFunc func(*http.Request) (*http.Response, error)

func (f roundtripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req) }
type gcrRoundtripper struct {
authHeader string
}

func gcrRoundtripper(req *http.Request) (*http.Response, error) {
func (g *gcrRoundtripper) RoundTrip(req *http.Request) (*http.Response, error) {
log.Printf("request received. url=%s", req.URL)

if g.authHeader != "" {
req.Header.Set("Authorization", g.authHeader)
}

// TODO(ahmetb) remove after internal bug 129780113 is fixed.
req.Header.Set("accept", "*/*")

Expand Down

0 comments on commit 6e050dd

Please sign in to comment.