// Copyright (c) 2021 Gitpod GmbH. All rights reserved. // Licensed under the GNU Affero General Public License (AGPL). // See License-AGPL.txt in the project root for license information. package builder import ( "crypto/aes" "crypto/cipher" "encoding/base64" "os" "path/filepath" "strings" "golang.org/x/xerrors" ) // Config configures a builder type Config struct { TargetRef string BaseRef string BaseContext string BuildBase bool BaseLayerAuth string WorkspaceLayerAuth string Dockerfile string ContextDir string ExternalBuildkitd string localCacheImport string } // GetConfigFromEnv extracts configuration from environment variables func GetConfigFromEnv() (*Config, error) { cfg := &Config{ TargetRef: os.Getenv("BOB_TARGET_REF"), 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"), ExternalBuildkitd: os.Getenv("BOB_EXTERNAL_BUILDKITD"), localCacheImport: os.Getenv("BOB_LOCAL_CACHE_IMPORT"), } if cfg.BaseRef == "" { cfg.BaseRef = "localhost:8080/base:latest" } if cfg.TargetRef == "" { cfg.TargetRef = "localhost:8080/target:latest" } if cfg.BuildBase { if cfg.Dockerfile == "" { return nil, xerrors.Errorf("When building the base image BOB_DOCKERFILE_PATH is mandatory") } var err error cfg.Dockerfile, err = filepath.Abs(cfg.Dockerfile) if err != nil { return nil, xerrors.Errorf("cannot make BOB_DOCKERFILE_PATH absolute: %w", err) } if !strings.HasPrefix(cfg.Dockerfile, "/workspace") { return nil, xerrors.Errorf("BOB_DOCKERFILE_PATH must begin with /workspace") } if stat, err := os.Stat(cfg.Dockerfile); err != nil || stat.IsDir() { 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) } } } 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 }