Skip to content

Commit

Permalink
buildkitd: Frontend restriction support
Browse files Browse the repository at this point in the history
This commit adds [frontend."dockerfile.v0"] and
[frontend."gateway.v0"] buildkitd.toml configuration sections.  Each
frontend can individually be disabled by setting `enabled = false`
(both frontends are enabled by default).

The [frontend."gateway.v0"] section has an `allowedSources` setting.
If `allowedSources` is empty (the default), all gateway sources are
allowed.  Otherwise, only sources that match the patterns in this list
will be allowed.  Patterns are matched using
<https://pkg.go.dev/github.com/moby/buildkit/util/wildcard>.  Note
that implicit references to docker.io should not be used in the
patterns since matching occurs on a fully expanded image name (for
example "docker/dockerfile" expands to "docker.io/docker/dockerfile").

Change-Id: Ia484401709ef6c13cf3e5a2e4d0e1c6bd0c47d13
Signed-off-by: Ahmon Dancy <adancy@wikimedia.org>
  • Loading branch information
Ahmon Dancy committed May 8, 2024
1 parent 51d85d7 commit 85a6178
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 6 deletions.
14 changes: 14 additions & 0 deletions cmd/buildkitd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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"`
AllowedSources []string `toml:"allowedSources"`
}
13 changes: 11 additions & 2 deletions cmd/buildkitd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.AllowedSources)
if err != nil {
return nil, err
}
frontends["gateway.v0"] = gwfe
}

cacheStorage, err := bboltcachestorage.NewStore(filepath.Join(cfg.Root, "cache.db"))
if err != nil {
Expand Down
23 changes: 23 additions & 0 deletions docs/buildkitd.toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,27 @@ 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 allowedSources is empty, all gateway sources are allowed.
# Otherwise, only sources that match the patterns in this list will
# be allowed.
#
# NOTES:
# * Only the image name (without tag) is compared.
# * Patterns are matched using <https://pkg.go.dev/github.com/moby/buildkit/util/wildcard>.
# * Implicit references to docker.io should not be used in
# the patterns since matching occurs on a fully expanded image name
# (for example "docker/dockerfile" expands to "docker.io/docker/dockerfile").
#
# Example:
# allowedSources = [ "docker-registry.wikimedia.org/repos/releng/blubber/buildkit" ]
allowedSources = []

```
50 changes: 46 additions & 4 deletions frontend/gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (
"github.com/moby/buildkit/util/grpcerrors"
"github.com/moby/buildkit/util/stack"
"github.com/moby/buildkit/util/tracing"
"github.com/moby/buildkit/util/wildcard"
"github.com/moby/buildkit/worker"
dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
"github.com/moby/sys/signal"
Expand All @@ -67,14 +68,26 @@ const (
keyDevel = "gateway-devel"
)

func NewGatewayFrontend(w worker.Infos) frontend.Frontend {
return &gatewayFrontend{
workers: w,
func NewGatewayFrontend(workers worker.Infos, allowedSources []string) (frontend.Frontend, error) {
var wildcards []*wildcard.Wildcard

for _, allowedSource := range allowedSources {
wild, err := wildcard.New(allowedSource)
if err != nil {
return nil, err
}
wildcards = append(wildcards, wild)
}

return &gatewayFrontend{
workers: workers,
allowedSources: wildcards,
}, nil
}

type gatewayFrontend struct {
workers worker.Infos
workers worker.Infos
allowedSources []*wildcard.Wildcard
}

func filterPrefix(opts map[string]string, pfx string) map[string]string {
Expand All @@ -87,6 +100,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.allowedSources) == 0 {
// No source restrictions in place
return nil
}

sourceRef, err := reference.ParseNormalizedNamed(source)
if err != nil {
return err
}

taglessSource := reference.TrimNamed(sourceRef).Name()

for _, allowedSource := range gf.allowedSources {
if allowedSource.Match(taglessSource) != nil {
// 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 {
Expand All @@ -101,6 +138,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{
Expand Down
57 changes: 57 additions & 0 deletions frontend/gateway/gateway_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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"})
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{"implicit-docker-io-reference", "docker/dockerfile"})
assert.NoError(t, err)
// This source will be rejected because after parsing it becomes
// "docker.io/library/implicit-docker-io-reference" which does not match
// the allowed source of "implicit-docker-io-reference".
err = gw.checkSourceIsAllowed("implicit-docker-io-reference")
assert.Error(t, err)
// "docker/dockerfile" expands to "docker.io/docker/dockerfile", so no match here.
err = gw.checkSourceIsAllowed("docker/dockerfile")
assert.Error(t, err)

gw, err = makeGatewayFrontend([]string{"docker-registry.wikimedia.org/*"})
assert.NoError(t, err)
err = gw.checkSourceIsAllowed("docker-registry.wikimedia.org/something-else")
assert.NoError(t, err)
err = gw.checkSourceIsAllowed("docker-registry.wikimedia.org/topdir/below")
assert.NoError(t, err)

_, err = makeGatewayFrontend([]string{"docker-registry.wikimedia.org/**"}) // Invalid wildcard
assert.Error(t, err)
}

0 comments on commit 85a6178

Please sign in to comment.