Skip to content

Commit

Permalink
1
Browse files Browse the repository at this point in the history
  • Loading branch information
iQQBot committed Sep 20, 2023
1 parent c700636 commit 825aff2
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 7 deletions.
3 changes: 2 additions & 1 deletion components/image-builder-bob/cmd/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ var proxyCmd = &cobra.Command{
}

auth := func() docker.Authorizer { return docker.NewDockerAuthorizer(docker.WithAuthCreds(authP.Authorize)) }
mirrorAuth := func() docker.Authorizer { return docker.NewDockerAuthorizer(docker.WithAuthCreds(authA.Authorize)) }
prx, err := proxy.NewProxy(&url.URL{Host: "localhost:8080", Scheme: "http"}, map[string]proxy.Repo{
"base": {
Host: reference.Domain(baseref),
Expand All @@ -72,7 +73,7 @@ var proxyCmd = &cobra.Command{
Tag: targettag,
Auth: auth,
},
})
}, mirrorAuth)
if err != nil {
log.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion components/image-builder-bob/leeway.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Licensed under the GNU Affero General Public License (AGPL).
# See License.AGPL.txt in the project root for license information.

FROM moby/buildkit:v0.12.2
FROM eu.gcr.io/gitpod-core-dev/build/buildkit:v0.12.2-gitpod

USER root
RUN apk --no-cache add sudo bash \
Expand Down
18 changes: 17 additions & 1 deletion components/image-builder-bob/pkg/proxy/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,22 @@ package proxy
import (
"encoding/base64"
"encoding/json"
"regexp"
"strings"

"github.com/gitpod-io/gitpod/common-go/log"
"github.com/sirupsen/logrus"
)

var ecrRegistryRegexp = regexp.MustCompile(`\d{12}.dkr.ecr.\w+-\w+-\w+.amazonaws.com`)

const DummyECRRegistryDomain = "000000000000.dkr.ecr.dummy-host-zone.amazonaws.com"

// isECRRegistry returns true if the registry domain is an ECR registry
func isECRRegistry(domain string) bool {
return ecrRegistryRegexp.MatchString(domain)
}

// authConfig configures authentication for a single host
type authConfig struct {
Username string `json:"username"`
Expand All @@ -32,7 +42,13 @@ func (a MapAuthorizer) Authorize(host string) (user, pass string, err error) {

res, ok := a[host]
if !ok {
return
if !isECRRegistry(host) {
return
}
res, ok = a[DummyECRRegistryDomain]
if !ok {
return
}
}

user, pass = res.Username, res.Password
Expand Down
117 changes: 114 additions & 3 deletions components/image-builder-bob/pkg/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (

const authKey = "authKey"

func NewProxy(host *url.URL, aliases map[string]Repo) (*Proxy, error) {
func NewProxy(host *url.URL, aliases map[string]Repo, mirrorAuth func() docker.Authorizer) (*Proxy, error) {
if host.Host == "" || host.Scheme == "" {
return nil, fmt.Errorf("host Host or Scheme are missing")
}
Expand All @@ -41,8 +41,9 @@ type Proxy struct {
Host url.URL
Aliases map[string]Repo

mu sync.Mutex
proxies map[string]*httputil.ReverseProxy
mu sync.Mutex
proxies map[string]*httputil.ReverseProxy
mirrorAuth func() docker.Authorizer
}

type Repo struct {
Expand Down Expand Up @@ -126,6 +127,23 @@ func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break
}
}

// get mirror host
if host := r.URL.Query().Get("ns"); host != "" && (r.Method == http.MethodGet || r.Method == http.MethodHead) {
if host == "docker.io" {
host = "registry-1.docker.io"
}
r.URL.Host = host
r.Host = host

auth := proxy.mirrorAuth
r = r.WithContext(context.WithValue(ctx, authKey, auth))

r.RequestURI = ""
proxy.mirror(host).ServeHTTP(w, r)
return
}

if repo == nil {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
Expand Down Expand Up @@ -273,3 +291,96 @@ func (proxy *Proxy) reverse(alias string) *httputil.ReverseProxy {
proxy.proxies[alias] = rp
return rp
}

// mirror produces an authentication-adding reverse proxy for given host
func (proxy *Proxy) mirror(host string) *httputil.ReverseProxy {
proxy.mu.Lock()
defer proxy.mu.Unlock()

if rp, ok := proxy.proxies[host]; ok {
return rp
}

rp := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "https", Host: host})

client := retryablehttp.NewClient()
client.RetryMax = 3
client.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) {
if err != nil {
log.WithError(err).Warn("saw error during CheckRetry")
return false, err
}
auth, ok := ctx.Value(authKey).(docker.Authorizer)
if !ok || auth == nil {
return false, nil
}
if resp.StatusCode == http.StatusUnauthorized {
// the docker authorizer only refreshes OAuth tokens after two
// successive 401 errors for the same URL. Rather than issue the same
// request multiple times to tickle the token-refreshing logic, just
// provide the same response twice to trick it into refreshing the
// cached OAuth token. Call AddResponses() twice, first to invalidate
// the existing token (with two responses), second to fetch a new one
// (with one response).
// TODO: fix after one of these two PRs are merged and available:
// https://github.com/containerd/containerd/pull/8735
// https://github.com/containerd/containerd/pull/8388
err := auth.AddResponses(ctx, []*http.Response{resp, resp})
if err != nil {
log.WithError(err).WithField("URL", resp.Request.URL.String()).Warn("cannot add responses although response was Unauthorized")
return false, nil
}

err = auth.AddResponses(ctx, []*http.Response{resp})
if err != nil {
log.WithError(err).WithField("URL", resp.Request.URL.String()).Warn("cannot add responses although response was Unauthorized")
return false, nil
}
return true, nil
}
if resp.StatusCode == http.StatusBadRequest {
log.WithField("URL", resp.Request.URL.String()).Warn("bad request")
return true, nil
}

return false, nil
}
client.RequestLogHook = func(l retryablehttp.Logger, r *http.Request, i int) {
// Total hack: we need a place to modify the request before retrying, and this log
// hook seems to be the only place. We need to modify the request, because
// maybe we just added the host authorizer in the previous CheckRetry call.
//
// The ReverseProxy sets the X-Forwarded-For header with the host machine
// address. If on a cluster with IPV6 enabled, this will be "::1" (IPV6 equivalent
// of "127.0.0.1"). This can have the knock-on effect of receiving an IPV6
// URL, e.g. auth.ipv6.docker.com instead of auth.docker.com which may not
// exist. By forcing the value to be "127.0.0.1", we ensure consistency
// across clusters.
//
// @link https://golang.org/src/net/http/httputil/reverseproxy.go
r.Header.Set("X-Forwarded-For", "127.0.0.1")

auth, ok := r.Context().Value(authKey).(docker.Authorizer)
if !ok || auth == nil {
return
}
_ = auth.Authorize(r.Context(), r)
}
client.ResponseLogHook = func(l retryablehttp.Logger, r *http.Response) {}

rp.Transport = &retryablehttp.RoundTripper{
Client: client,
}
rp.ModifyResponse = func(r *http.Response) error {
if r.StatusCode == http.StatusBadGateway {
// BadGateway makes containerd retry - we don't want that because we retry the upstream
// requests internally.
r.StatusCode = http.StatusInternalServerError
r.Status = http.StatusText(http.StatusInternalServerError)
}

return nil
}
proxy.proxies[host] = rp
return rp
}
2 changes: 2 additions & 0 deletions components/image-builder-mk3/pkg/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ func (a *Authentication) Empty() bool {

var ecrRegistryRegexp = regexp.MustCompile(`\d{12}.dkr.ecr.\w+-\w+-\w+.amazonaws.com`)

const DummyECRRegistryDomain = "000000000000.dkr.ecr.dummy-host-zone.amazonaws.com"

// isECRRegistry returns true if the registry domain is an ECR registry
func isECRRegistry(domain string) bool {
return ecrRegistryRegexp.MatchString(domain)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ func (o *Orchestrator) Build(req *protocol.BuildRequest, resp protocol.ImageBuil
wsref, err := reference.ParseNamed(wsrefstr)
var additionalAuth []byte
if err == nil {
ath := reqauth.GetImageBuildAuthFor(ctx, o.Auth, []string{reference.Domain(pbaseref)}, []string{
ath := reqauth.GetImageBuildAuthFor(ctx, o.Auth, []string{reference.Domain(pbaseref), auth.DummyECRRegistryDomain}, []string{
reference.Domain(wsref),
})
additionalAuth, err = json.Marshal(ath)
Expand Down

0 comments on commit 825aff2

Please sign in to comment.