diff --git a/cmd/buildkitd/config/config.go b/cmd/buildkitd/config/config.go index 58dd2f8a047b..28c11acb983e 100644 --- a/cmd/buildkitd/config/config.go +++ b/cmd/buildkitd/config/config.go @@ -33,6 +33,11 @@ type Config struct { DNS *DNSConfig `toml:"dns"` History *HistoryConfig `toml:"history"` + + Frontends struct { + Dockerfile DockerfileFrontendConfig `toml:"dockerfile.v0"` + Gateway GatewayFrontendConfig `toml:"gateway.v0"` + } `toml:"frontend"` } type LogConfig struct { @@ -155,3 +160,12 @@ type HistoryConfig struct { MaxAge Duration `toml:"maxAge"` MaxEntries int64 `toml:"maxEntries"` } + +type DockerfileFrontendConfig struct { + Enabled *bool `toml:"enabled"` +} + +type GatewayFrontendConfig struct { + Enabled *bool `toml:"enabled"` + AllowedRepositories []string `toml:"allowedRepositories"` +} diff --git a/cmd/buildkitd/main.go b/cmd/buildkitd/main.go index cef6ad2a1f7e..f485ccc92082 100644 --- a/cmd/buildkitd/main.go +++ b/cmd/buildkitd/main.go @@ -759,8 +759,17 @@ func newController(c *cli.Context, cfg *config.Config) (*control.Controller, err return nil, err } frontends := map[string]frontend.Frontend{} - frontends["dockerfile.v0"] = forwarder.NewGatewayForwarder(wc.Infos(), dockerfile.Build) - frontends["gateway.v0"] = gateway.NewGatewayFrontend(wc.Infos()) + + if cfg.Frontends.Dockerfile.Enabled == nil || *cfg.Frontends.Dockerfile.Enabled { + frontends["dockerfile.v0"] = forwarder.NewGatewayForwarder(wc.Infos(), dockerfile.Build) + } + if cfg.Frontends.Gateway.Enabled == nil || *cfg.Frontends.Gateway.Enabled { + gwfe, err := gateway.NewGatewayFrontend(wc.Infos(), cfg.Frontends.Gateway.AllowedRepositories) + if err != nil { + return nil, err + } + frontends["gateway.v0"] = gwfe + } cacheStorage, err := bboltcachestorage.NewStore(filepath.Join(cfg.Root, "cache.db")) if err != nil { diff --git a/docs/buildkitd.toml.md b/docs/buildkitd.toml.md index 665d97f93605..794198c026ac 100644 --- a/docs/buildkitd.toml.md +++ b/docs/buildkitd.toml.md @@ -136,4 +136,21 @@ insecure-entitlements = [ "network.host", "security.insecure" ] # optionally mirror configuration can be done by defining it as a registry. [registry."yourmirror.local:5000"] http = true + +# Frontend control +[frontend."dockerfile.v0"] + enabled = true + +[frontend."gateway.v0"] + enabled = true + + # If allowedRepositories is empty, all gateway sources are allowed. + # Otherwise, only the listed repositories are allowed as a gateway source. + # + # NOTE: Only the repository name (without tag) is compared. + # + # Example: + # allowedRepositories = [ "docker-registry.wikimedia.org/repos/releng/blubber/buildkit" ] + allowedRepositories = [] + ``` diff --git a/frontend/gateway/gateway.go b/frontend/gateway/gateway.go index 49a29b8c155f..baf002b7e917 100644 --- a/frontend/gateway/gateway.go +++ b/frontend/gateway/gateway.go @@ -67,14 +67,26 @@ const ( keyDevel = "gateway-devel" ) -func NewGatewayFrontend(w worker.Infos) frontend.Frontend { - return &gatewayFrontend{ - workers: w, +func NewGatewayFrontend(workers worker.Infos, allowedRepositories []string) (frontend.Frontend, error) { + var parsedAllowedRepositories []string + + for _, allowedRepository := range allowedRepositories { + sourceRef, err := reference.ParseNormalizedNamed(allowedRepository) + if err != nil { + return nil, err + } + parsedAllowedRepositories = append(parsedAllowedRepositories, reference.TrimNamed(sourceRef).Name()) } + + return &gatewayFrontend{ + workers: workers, + allowedRepositories: parsedAllowedRepositories, + }, nil } type gatewayFrontend struct { - workers worker.Infos + workers worker.Infos + allowedRepositories []string } func filterPrefix(opts map[string]string, pfx string) map[string]string { @@ -87,6 +99,30 @@ func filterPrefix(opts map[string]string, pfx string) map[string]string { return m } +func (gf *gatewayFrontend) checkSourceIsAllowed(source string) error { + // Returns nil if the source is allowed. + // Returns an error if the source is not allowed. + if len(gf.allowedRepositories) == 0 { + // No source restrictions in place + return nil + } + + sourceRef, err := reference.ParseNormalizedNamed(source) + if err != nil { + return err + } + + taglessSource := reference.TrimNamed(sourceRef).Name() + + for _, allowedRepository := range gf.allowedRepositories { + if taglessSource == allowedRepository { + // Allowed + return nil + } + } + return errors.Errorf("'%s' is not an allowed gateway source", source) +} + func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBridge, exec executor.Executor, opts map[string]string, inputs map[string]*opspb.Definition, sid string, sm *session.Manager) (*frontend.Result, error) { source, ok := opts[keySource] if !ok { @@ -101,6 +137,11 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten var frontendDef *opspb.Definition + err := gf.checkSourceIsAllowed(source) + if err != nil { + return nil, err + } + if isDevel { devRes, err := llbBridge.Solve(ctx, frontend.SolveRequest{ diff --git a/frontend/gateway/gateway_test.go b/frontend/gateway/gateway_test.go new file mode 100644 index 000000000000..301c087b2b7b --- /dev/null +++ b/frontend/gateway/gateway_test.go @@ -0,0 +1,45 @@ +package gateway + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCheckSourceIsAllowed(t *testing.T) { + makeGatewayFrontend := func(sources []string) (*gatewayFrontend, error) { + gw, err := NewGatewayFrontend(nil, sources) + if err != nil { + return nil, err + } + gw1 := gw.(*gatewayFrontend) + return gw1, nil + } + + var gw *gatewayFrontend + var err error + + // no restrictions + gw, err = makeGatewayFrontend([]string{}) + assert.NoError(t, err) + err = gw.checkSourceIsAllowed("anything") + assert.NoError(t, err) + + gw, err = makeGatewayFrontend([]string{"docker-registry.wikimedia.org/repos/releng/blubber/buildkit:9.9.9"}) + assert.NoError(t, err) + err = gw.checkSourceIsAllowed("docker-registry.wikimedia.org/repos/releng/blubber/buildkit") + assert.NoError(t, err) + err = gw.checkSourceIsAllowed("docker-registry.wikimedia.org/repos/releng/blubber/buildkit:v1.2.3") + assert.NoError(t, err) + err = gw.checkSourceIsAllowed("docker-registry.wikimedia.org/something-else") + assert.Error(t, err) + + gw, err = makeGatewayFrontend([]string{"alpine"}) + assert.NoError(t, err) + err = gw.checkSourceIsAllowed("alpine") + assert.NoError(t, err) + err = gw.checkSourceIsAllowed("library/alpine") + assert.NoError(t, err) + err = gw.checkSourceIsAllowed("docker.io/library/alpine") + assert.NoError(t, err) +}