diff --git a/components/image-builder-bob/cmd/daemon.go b/components/image-builder-bob/cmd/daemon.go index 5cb1d5d534c8db..8abc91b85a1ca5 100644 --- a/components/image-builder-bob/cmd/daemon.go +++ b/components/image-builder-bob/cmd/daemon.go @@ -34,7 +34,7 @@ var daemonCmd = &cobra.Command{ } skt := args[0] - cl, teardown, err := builder.StartBuildkit(skt) + cl, teardown, err := builder.StartBuildkit(skt, os.Getenv("WORKSPACEKIT_BOBPROXY_ADDITIONALAUTH")) if err != nil { log.WithError(err).Fatal("cannot start daemon") } diff --git a/components/image-builder-bob/cmd/proxy.go b/components/image-builder-bob/cmd/proxy.go index 443d0f7ed36ba5..65e393a0fcc0ad 100644 --- a/components/image-builder-bob/cmd/proxy.go +++ b/components/image-builder-bob/cmd/proxy.go @@ -72,7 +72,7 @@ var proxyCmd = &cobra.Command{ Tag: targettag, Auth: auth, }, - }) + }, auth) if err != nil { log.Fatal(err) } diff --git a/components/image-builder-bob/pkg/builder/builder.go b/components/image-builder-bob/pkg/builder/builder.go index f3868507a9b45d..62d6f96b357417 100644 --- a/components/image-builder-bob/pkg/builder/builder.go +++ b/components/image-builder-bob/pkg/builder/builder.go @@ -5,8 +5,8 @@ package builder import ( - "bytes" "context" + "encoding/json" "fmt" "io/ioutil" "os" @@ -16,9 +16,8 @@ import ( "time" "github.com/gitpod-io/gitpod/common-go/log" + toml "github.com/pelletier/go-toml" - "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/config/types" "github.com/moby/buildkit/client" "golang.org/x/xerrors" ) @@ -49,10 +48,10 @@ func (b *Builder) Build() error { if err != nil { log.Warn("cannot connect to node-local buildkitd - falling back to pod-local one") - cl, teardown, err = StartBuildkit(buildkitdSocketPath) + cl, teardown, err = StartBuildkit(buildkitdSocketPath, b.Config.WorkspaceLayerAuth) } } else { - cl, teardown, err = StartBuildkit(buildkitdSocketPath) + cl, teardown, err = StartBuildkit(buildkitdSocketPath, b.Config.WorkspaceLayerAuth) } if err != nil { return err @@ -80,7 +79,7 @@ func (b *Builder) buildBaseLayer(ctx context.Context, cl *client.Client) error { } log.Info("building base image") - return buildImage(ctx, b.Config.ContextDir, b.Config.Dockerfile, b.Config.WorkspaceLayerAuth, b.Config.BaseRef) + return buildImage(ctx, b.Config.ContextDir, b.Config.Dockerfile, b.Config.BaseRef) } func (b *Builder) buildWorkspaceImage(ctx context.Context, cl *client.Client) (err error) { @@ -96,10 +95,10 @@ func (b *Builder) buildWorkspaceImage(ctx context.Context, cl *client.Client) (e return xerrors.Errorf("unexpected error creating temporal directory: %w", err) } - return buildImage(ctx, contextDir, filepath.Join(contextDir, "Dockerfile"), b.Config.WorkspaceLayerAuth, b.Config.TargetRef) + return buildImage(ctx, contextDir, filepath.Join(contextDir, "Dockerfile"), b.Config.TargetRef) } -func buildImage(ctx context.Context, contextDir, dockerfile, authLayer, target string) (err error) { +func buildImage(ctx context.Context, contextDir, dockerfile, target string) (err error) { log.Info("waiting for build context") waitctx, cancel := context.WithTimeout(ctx, 30*time.Minute) defer cancel() @@ -109,28 +108,6 @@ func buildImage(ctx context.Context, contextDir, dockerfile, authLayer, target s return err } - dockerConfig := "/tmp/config.json" - defer os.Remove(dockerConfig) - - if authLayer != "" { - configFile := configfile.ConfigFile{ - AuthConfigs: make(map[string]types.AuthConfig), - } - - err := configFile.LoadFromReader(bytes.NewReader([]byte(fmt.Sprintf(`{"auths": %v }`, authLayer)))) - if err != nil { - return xerrors.Errorf("unexpected error reading registry authentication: %w", err) - } - - f, _ := os.OpenFile(dockerConfig, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - defer f.Close() - - err = configFile.SaveToWriter(f) - if err != nil { - return xerrors.Errorf("unexpected error writing registry authentication: %w", err) - } - } - contextdir := contextDir if contextdir == "" { contextdir = "." @@ -200,7 +177,7 @@ func waitForBuildContext(ctx context.Context) error { } // StartBuildkit starts a local buildkit daemon -func StartBuildkit(socketPath string) (cl *client.Client, teardown func() error, err error) { +func StartBuildkit(socketPath string, authLayer string) (cl *client.Client, teardown func() error, err error) { stderr, err := ioutil.TempFile(os.TempDir(), "buildkitd_stderr") if err != nil { return nil, nil, xerrors.Errorf("cannot create buildkitd log file: %w", err) @@ -210,7 +187,27 @@ func StartBuildkit(socketPath string) (cl *client.Client, teardown func() error, return nil, nil, xerrors.Errorf("cannot create buildkitd log file: %w", err) } + buildkitCfg, err := createBuildkitConfig(authLayer) + if err != nil { + return nil, nil, xerrors.Errorf("cannot create buildkitd config: %w", err) + } + buildkitCfgData, err := toml.Marshal(buildkitCfg) + if err != nil { + return nil, nil, xerrors.Errorf("cannot marshal buildkitd config: %w", err) + } + buildkitCfgFile := "/etc/buildkitd.toml" + f, err := os.Create(buildkitCfgFile) + defer f.Close() + if err != nil { + return nil, nil, xerrors.Errorf("unexpected error creating buildkitd config file: %w", err) + } + _, err = f.Write(buildkitCfgData) + if err != nil { + return nil, nil, xerrors.Errorf("unexpected error writing buildkitd config file: %w", err) + } + cmd := exec.Command("buildkitd", + "--config"+buildkitCfgFile, "--debug", "--addr="+socketPath, "--oci-worker-net=host", @@ -292,3 +289,38 @@ func connectToBuildkitd(socketPath string) (cl *client.Client, err error) { return nil, xerrors.Errorf("cannot connect to buildkitd") } + +type BuildkitConfig struct { + Registry map[string]RegistryConfig `toml:"registry"` +} +type RegistryConfig struct { + Mirrors *[]string `toml:"mirrors"` + PlainHttp *bool `toml:"http"` +} + +func createBuildkitConfig(authLayer string) (buildkitConfig BuildkitConfig, err error) { + registryConfig := make(map[string]RegistryConfig) + + registries := []string{} + + err = json.Unmarshal([]byte(authLayer), ®istries) + if err != nil { + return buildkitConfig, xerrors.Errorf("unexpected error reading registry authentication: %w", err) + } + + t := true + for _, host := range registries { + if host != "" { + registryConfig[host] = RegistryConfig{ + Mirrors: &[]string{"localhost:8080"}, + } + } + } + + registryConfig["localhost:8080"] = RegistryConfig{ + PlainHttp: &t, + } + return BuildkitConfig{ + Registry: registryConfig, + }, nil +} diff --git a/components/image-builder-bob/pkg/builder/config.go b/components/image-builder-bob/pkg/builder/config.go index 01dddd578686af..1fd9a68abd8c8d 100644 --- a/components/image-builder-bob/pkg/builder/config.go +++ b/components/image-builder-bob/pkg/builder/config.go @@ -5,9 +5,6 @@ package builder import ( - "crypto/aes" - "crypto/cipher" - "encoding/base64" "os" "path/filepath" "strings" @@ -21,7 +18,6 @@ type Config struct { BaseRef string BaseContext string BuildBase bool - BaseLayerAuth string WorkspaceLayerAuth string Dockerfile string ContextDir string @@ -36,7 +32,6 @@ func GetConfigFromEnv() (*Config, error) { BaseRef: os.Getenv("BOB_BASE_REF"), BaseContext: os.Getenv("THEIA_WORKSPACE_ROOT"), BuildBase: os.Getenv("BOB_BUILD_BASE") == "true", - BaseLayerAuth: os.Getenv("BOB_BASELAYER_AUTH"), WorkspaceLayerAuth: os.Getenv("BOB_WSLAYER_AUTH"), Dockerfile: os.Getenv("BOB_DOCKERFILE_PATH"), ContextDir: os.Getenv("BOB_CONTEXT_DIR"), @@ -66,61 +61,9 @@ func GetConfigFromEnv() (*Config, error) { return nil, xerrors.Errorf("BOB_DOCKERFILE_PATH does not exist or isn't a file") } } - - var authKey = os.Getenv("BOB_AUTH_KEY") - if authKey != "" { - if len(authKey) != 32 { - return nil, xerrors.Errorf("BOB_AUTH_KEY must be exactly 32 bytes long") - } - - // we have an authkey, hence assume that the auth fields are base64 encoded and encrypted - if cfg.BaseLayerAuth != "" { - dec, err := base64.RawStdEncoding.DecodeString(cfg.BaseLayerAuth) - if err != nil { - return nil, xerrors.Errorf("BOB_BASELAYER_AUTH is not base64 encoded but BOB_AUTH_KEY is present") - } - cfg.BaseLayerAuth, err = decrypt(dec, authKey) - if err != nil { - return nil, xerrors.Errorf("cannot decrypt BOB_BASELAYER_AUTH: %w", err) - } - } - if cfg.WorkspaceLayerAuth != "" { - dec, err := base64.RawStdEncoding.DecodeString(cfg.WorkspaceLayerAuth) - if err != nil { - return nil, xerrors.Errorf("BOB_WSLAYER_AUTH is not base64 encoded but BOB_AUTH_KEY is present") - } - cfg.WorkspaceLayerAuth, err = decrypt(dec, authKey) - if err != nil { - return nil, xerrors.Errorf("cannot decrypt BOB_WSLAYER_AUTH: %w", err) - } - } + if cfg.WorkspaceLayerAuth == "" { + cfg.WorkspaceLayerAuth = "[]" } return cfg, nil } - -// source: https://astaxie.gitbooks.io/build-web-application-with-golang/en/09.6.html -func decrypt(ciphertext []byte, key string) (string, error) { - c, err := aes.NewCipher([]byte(key)) - if err != nil { - return "", err - } - - gcm, err := cipher.NewGCM(c) - if err != nil { - return "", err - } - - nonceSize := gcm.NonceSize() - if len(ciphertext) < nonceSize { - return "", xerrors.Errorf("ciphertext too short") - } - - nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] - res, err := gcm.Open(nil, nonce, ciphertext, nil) - if err != nil { - return "", err - } - - return string(res), nil -} diff --git a/components/image-builder-bob/pkg/proxy/proxy.go b/components/image-builder-bob/pkg/proxy/proxy.go index eef3c4eb70f5ee..b91f9155dd878c 100644 --- a/components/image-builder-bob/pkg/proxy/proxy.go +++ b/components/image-builder-bob/pkg/proxy/proxy.go @@ -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, defaultAuth func() docker.Authorizer) (*Proxy, error) { if host.Host == "" || host.Scheme == "" { return nil, fmt.Errorf("host Host or Scheme are missing") } @@ -31,9 +31,10 @@ func NewProxy(host *url.URL, aliases map[string]Repo) (*Proxy, error) { aliases[k] = v } return &Proxy{ - Host: *host, - Aliases: aliases, - proxies: make(map[string]*httputil.ReverseProxy), + Host: *host, + Aliases: aliases, + proxies: make(map[string]*httputil.ReverseProxy), + defaultAuth: defaultAuth, }, nil } @@ -41,8 +42,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 + defaultAuth func() docker.Authorizer } type Repo struct { @@ -126,14 +128,21 @@ func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } } + var auth docker.Authorizer + ns := r.URL.Query().Get("ns") if repo == nil { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - return + auth = proxy.defaultAuth() + if ns == "" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + log.Debug("no repo found, using direct proxy.") + } else { + auth = repo.Auth() } r.Host = r.URL.Host - auth := repo.Auth() r = r.WithContext(context.WithValue(ctx, authKey, auth)) err := auth.Authorize(ctx, r) @@ -146,11 +155,11 @@ func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { log.WithField("req", r.URL.Path).Info("serving request") r.RequestURI = "" - proxy.reverse(alias).ServeHTTP(w, r) + proxy.reverse(alias, ns).ServeHTTP(w, r) } // reverse produces an authentication-adding reverse proxy for a given repo alias -func (proxy *Proxy) reverse(alias string) *httputil.ReverseProxy { +func (proxy *Proxy) reverse(alias string, namespace string) *httputil.ReverseProxy { proxy.mu.Lock() defer proxy.mu.Unlock() @@ -159,12 +168,18 @@ func (proxy *Proxy) reverse(alias string) *httputil.ReverseProxy { } repo, ok := proxy.Aliases[alias] + var host string if !ok { // we don't have an alias, hence don't know what to do other than try and proxy. - // At this poing things will probably fail. - return nil + if namespace == "" { + // At this poing things will probably fail. + return nil + } + host = namespace + } else { + host = repo.Host } - rp := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "https", Host: repo.Host}) + rp := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "https", Host: host}) client := retryablehttp.NewClient() client.RetryMax = 3 diff --git a/components/image-builder-mk3/pkg/orchestrator/orchestrator.go b/components/image-builder-mk3/pkg/orchestrator/orchestrator.go index 9ffc5e6293adda..fc987be6af7f33 100644 --- a/components/image-builder-mk3/pkg/orchestrator/orchestrator.go +++ b/components/image-builder-mk3/pkg/orchestrator/orchestrator.go @@ -335,6 +335,7 @@ func (o *Orchestrator) Build(req *protocol.BuildRequest, resp protocol.ImageBuil } wsref, err := reference.ParseNamed(wsrefstr) var additionalAuth []byte + var additionalAuthRegistries []byte if err == nil { ath := reqauth.GetImageBuildAuthFor(ctx, o.Auth, []string{reference.Domain(pbaseref)}, []string{ reference.Domain(wsref), @@ -343,6 +344,17 @@ func (o *Orchestrator) Build(req *protocol.BuildRequest, resp protocol.ImageBuil if err != nil { return xerrors.Errorf("cannot marshal additional auth: %w", err) } + + registries := []string{} + for reg := range ath { + if reg != "" { + registries = append(registries, reg) + } + } + additionalAuthRegistries, err = json.Marshal(registries) + if err != nil { + return xerrors.Errorf("cannot marshal additional auth registries: %w", err) + } } var swr *wsmanapi.StartWorkspaceResponse @@ -375,6 +387,10 @@ func (o *Orchestrator) Build(req *protocol.BuildRequest, resp protocol.ImageBuil {Name: "BOB_BUILD_BASE", Value: buildBase}, {Name: "BOB_DOCKERFILE_PATH", Value: dockerfilePath}, {Name: "BOB_CONTEXT_DIR", Value: contextPath}, + { + Name: "BOB_WSLAYER_AUTH", + Value: string(additionalAuthRegistries), + }, {Name: "GITPOD_TASKS", Value: `[{"name": "build", "init": "sudo -E /app/bob build"}]`}, {Name: "WORKSPACEKIT_RING2_ENCLAVE", Value: "/app/bob proxy"}, {Name: "WORKSPACEKIT_BOBPROXY_BASEREF", Value: baseref},