support for device entitlement in build and bake

Allow access to CDI Devices in Buildkit v0.20.0+ for
devices that are not automatically allowed to be used by
everyone in BuildKit configuration.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
Tõnis Tiigi
2025-02-14 11:31:51 +01:00
committed by CrazyMax
parent ef73c64d2c
commit 0c296fe857
16 changed files with 205 additions and 55 deletions

View File

@ -7,6 +7,7 @@ import (
"io"
"maps"
"os"
"slices"
"strings"
"time"
@ -24,7 +25,6 @@ import (
"github.com/moby/buildkit/solver/pb"
spb "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/moby/buildkit/util/bklog"
"github.com/moby/buildkit/util/entitlements"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/tonistiigi/fsutil"
@ -45,7 +45,7 @@ type SolveOpt struct {
CacheExports []CacheOptionsEntry
CacheImports []CacheOptionsEntry
Session []session.Attachable
AllowedEntitlements []entitlements.Entitlement
AllowedEntitlements []string
SharedSession *session.Session // TODO: refactor to better session syncing
SessionPreInitialized bool // TODO: refactor to better session syncing
Internal bool
@ -277,7 +277,7 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG
FrontendAttrs: frontendAttrs,
FrontendInputs: frontendInputs,
Cache: &cacheOpt.options,
Entitlements: entitlementsToPB(opt.AllowedEntitlements),
Entitlements: slices.Clone(opt.AllowedEntitlements),
Internal: opt.Internal,
SourcePolicy: opt.SourcePolicy,
})
@ -553,11 +553,3 @@ func prepareMounts(opt *SolveOpt) (map[string]fsutil.FS, error) {
}
return mounts, nil
}
func entitlementsToPB(entitlements []entitlements.Entitlement) []string {
clone := make([]string, len(entitlements))
for i, e := range entitlements {
clone[i] = string(e)
}
return clone
}

View File

@ -77,8 +77,9 @@ type OTELConfig struct {
}
type CDIConfig struct {
Disabled *bool `toml:"disabled"`
SpecDirs []string `toml:"specDirs"`
Disabled *bool `toml:"disabled"`
SpecDirs []string `toml:"specDirs"`
AutoAllowed []string `toml:"autoAllowed"`
}
type GCConfig struct {

View File

@ -1,31 +1,119 @@
package entitlements
import (
"strings"
"github.com/pkg/errors"
"github.com/tonistiigi/go-csvvalue"
)
type Entitlement string
func (e Entitlement) String() string {
return string(e)
}
const (
EntitlementSecurityInsecure Entitlement = "security.insecure"
EntitlementNetworkHost Entitlement = "network.host"
EntitlementDevice Entitlement = "device"
)
var all = map[Entitlement]struct{}{
EntitlementSecurityInsecure: {},
EntitlementNetworkHost: {},
EntitlementDevice: {},
}
func Parse(s string) (Entitlement, error) {
type EntitlementsConfig interface {
Merge(EntitlementsConfig) error
}
type DevicesConfig struct {
Devices map[string]string
All bool
}
var _ EntitlementsConfig = &DevicesConfig{}
func ParseDevicesConfig(s string) (*DevicesConfig, error) {
if s == "" {
return &DevicesConfig{All: true}, nil
}
fields, err := csvvalue.Fields(s, nil)
if err != nil {
return nil, err
}
deviceName := fields[0]
var deviceAlias string
for _, field := range fields[1:] {
k, v, ok := strings.Cut(field, "=")
if !ok {
return nil, errors.Errorf("invalid device config %q", field)
}
switch k {
case "alias":
deviceAlias = v
default:
return nil, errors.Errorf("unknown device config key %q", k)
}
}
cfg := &DevicesConfig{Devices: map[string]string{}}
if deviceAlias != "" {
cfg.Devices[deviceAlias] = deviceName
} else {
cfg.Devices[deviceName] = ""
}
return cfg, nil
}
func (c *DevicesConfig) Merge(in EntitlementsConfig) error {
c2, ok := in.(*DevicesConfig)
if !ok {
return errors.Errorf("cannot merge %T into %T", in, c)
}
if c2.All {
c.All = true
return nil
}
for k, v := range c2.Devices {
if c.Devices == nil {
c.Devices = map[string]string{}
}
c.Devices[k] = v
}
return nil
}
func Parse(s string) (Entitlement, EntitlementsConfig, error) {
var cfg EntitlementsConfig
key, rest, _ := strings.Cut(s, "=")
switch Entitlement(key) {
case EntitlementDevice:
s = key
var err error
cfg, err = ParseDevicesConfig(rest)
if err != nil {
return "", nil, err
}
default:
}
_, ok := all[Entitlement(s)]
if !ok {
return "", errors.Errorf("unknown entitlement %s", s)
return "", nil, errors.Errorf("unknown entitlement %s", s)
}
return Entitlement(s), nil
return Entitlement(s), cfg, nil
}
func WhiteList(allowed, supported []Entitlement) (Set, error) {
m := map[Entitlement]struct{}{}
m := map[Entitlement]EntitlementsConfig{}
var supm Set
if supported != nil {
@ -37,7 +125,7 @@ func WhiteList(allowed, supported []Entitlement) (Set, error) {
}
for _, e := range allowed {
e, err := Parse(string(e))
e, cfg, err := Parse(string(e))
if err != nil {
return nil, err
}
@ -46,13 +134,19 @@ func WhiteList(allowed, supported []Entitlement) (Set, error) {
return nil, errors.Errorf("granting entitlement %s is not allowed by build daemon configuration", e)
}
}
m[e] = struct{}{}
if prev, ok := m[e]; ok && prev != nil {
if err := prev.Merge(cfg); err != nil {
return nil, err
}
} else {
m[e] = cfg
}
}
return Set(m), nil
}
type Set map[Entitlement]struct{}
type Set map[Entitlement]EntitlementsConfig
func (s Set) Allowed(e Entitlement) bool {
_, ok := s[e]
@ -77,4 +171,5 @@ func (s Set) Check(v Values) error {
type Values struct {
NetworkHost bool
SecurityInsecure bool
Devices map[string]struct{}
}