-
Notifications
You must be signed in to change notification settings - Fork 495
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2666 from tonistiigi/bake-entitlements
bake: enable support for entitlements
- Loading branch information
Showing
6 changed files
with
324 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package bake | ||
|
||
import ( | ||
"bufio" | ||
"context" | ||
"fmt" | ||
"io" | ||
"os" | ||
"slices" | ||
"strings" | ||
|
||
"github.com/containerd/console" | ||
"github.com/docker/buildx/build" | ||
"github.com/moby/buildkit/util/entitlements" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type EntitlementKey string | ||
|
||
const ( | ||
EntitlementKeyNetworkHost EntitlementKey = "network.host" | ||
EntitlementKeySecurityInsecure EntitlementKey = "security.insecure" | ||
EntitlementKeyFSRead EntitlementKey = "fs.read" | ||
EntitlementKeyFSWrite EntitlementKey = "fs.write" | ||
EntitlementKeyFS EntitlementKey = "fs" | ||
EntitlementKeyImagePush EntitlementKey = "image.push" | ||
EntitlementKeyImageLoad EntitlementKey = "image.load" | ||
EntitlementKeyImage EntitlementKey = "image" | ||
EntitlementKeySSH EntitlementKey = "ssh" | ||
) | ||
|
||
type EntitlementConf struct { | ||
NetworkHost bool | ||
SecurityInsecure bool | ||
FSRead []string | ||
FSWrite []string | ||
ImagePush []string | ||
ImageLoad []string | ||
SSH bool | ||
} | ||
|
||
func ParseEntitlements(in []string) (EntitlementConf, error) { | ||
var conf EntitlementConf | ||
for _, e := range in { | ||
switch e { | ||
case string(EntitlementKeyNetworkHost): | ||
conf.NetworkHost = true | ||
case string(EntitlementKeySecurityInsecure): | ||
conf.SecurityInsecure = true | ||
case string(EntitlementKeySSH): | ||
conf.SSH = true | ||
default: | ||
k, v, _ := strings.Cut(e, "=") | ||
switch k { | ||
case string(EntitlementKeyFSRead): | ||
conf.FSRead = append(conf.FSRead, v) | ||
case string(EntitlementKeyFSWrite): | ||
conf.FSWrite = append(conf.FSWrite, v) | ||
case string(EntitlementKeyFS): | ||
conf.FSRead = append(conf.FSRead, v) | ||
conf.FSWrite = append(conf.FSWrite, v) | ||
case string(EntitlementKeyImagePush): | ||
conf.ImagePush = append(conf.ImagePush, v) | ||
case string(EntitlementKeyImageLoad): | ||
conf.ImageLoad = append(conf.ImageLoad, v) | ||
case string(EntitlementKeyImage): | ||
conf.ImagePush = append(conf.ImagePush, v) | ||
conf.ImageLoad = append(conf.ImageLoad, v) | ||
default: | ||
return conf, errors.Errorf("uknown entitlement key %q", k) | ||
} | ||
|
||
// TODO: dedupe slices and parent paths | ||
} | ||
} | ||
return conf, nil | ||
} | ||
|
||
func (c EntitlementConf) Validate(m map[string]build.Options) (EntitlementConf, error) { | ||
var expected EntitlementConf | ||
|
||
for _, v := range m { | ||
if err := c.check(v, &expected); err != nil { | ||
return EntitlementConf{}, err | ||
} | ||
} | ||
|
||
return expected, nil | ||
} | ||
|
||
func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) error { | ||
for _, e := range bo.Allow { | ||
switch e { | ||
case entitlements.EntitlementNetworkHost: | ||
if !c.NetworkHost { | ||
expected.NetworkHost = true | ||
} | ||
case entitlements.EntitlementSecurityInsecure: | ||
if !c.SecurityInsecure { | ||
expected.SecurityInsecure = true | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (c EntitlementConf) Prompt(ctx context.Context, out io.Writer) error { | ||
var term bool | ||
if _, err := console.ConsoleFromFile(os.Stdin); err == nil { | ||
term = true | ||
} | ||
|
||
var msgs []string | ||
var flags []string | ||
|
||
if c.NetworkHost { | ||
msgs = append(msgs, " - Running build containers that can access host network") | ||
flags = append(flags, "network.host") | ||
} | ||
if c.SecurityInsecure { | ||
msgs = append(msgs, " - Running privileged containers that can make system changes") | ||
flags = append(flags, "security.insecure") | ||
} | ||
|
||
if len(msgs) == 0 { | ||
return nil | ||
} | ||
|
||
fmt.Fprintf(out, "Your build is requesting privileges for following possibly insecure capabilities:\n\n") | ||
for _, m := range msgs { | ||
fmt.Fprintf(out, "%s\n", m) | ||
} | ||
|
||
for i, f := range flags { | ||
flags[i] = "--allow=" + f | ||
} | ||
|
||
if term { | ||
fmt.Fprintf(out, "\nIn order to not see this message in the future pass %q to grant requested privileges.\n", strings.Join(flags, " ")) | ||
} else { | ||
fmt.Fprintf(out, "\nPass %q to grant requested privileges.\n", strings.Join(flags, " ")) | ||
} | ||
|
||
args := append([]string(nil), os.Args...) | ||
if v, ok := os.LookupEnv("DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND"); ok && v != "" { | ||
args[0] = v | ||
} | ||
idx := slices.Index(args, "bake") | ||
|
||
if idx != -1 { | ||
fmt.Fprintf(out, "\nYour full command with requested privileges:\n\n") | ||
fmt.Fprintf(out, "%s %s %s\n\n", strings.Join(args[:idx+1], " "), strings.Join(flags, " "), strings.Join(args[idx+1:], " ")) | ||
} | ||
|
||
if term { | ||
fmt.Fprintf(out, "Do you want to grant requested privileges and continue? [y/N] ") | ||
reader := bufio.NewReader(os.Stdin) | ||
answerCh := make(chan string, 1) | ||
go func() { | ||
answer, _, _ := reader.ReadLine() | ||
answerCh <- string(answer) | ||
close(answerCh) | ||
}() | ||
|
||
select { | ||
case <-ctx.Done(): | ||
case answer := <-answerCh: | ||
if strings.ToLower(string(answer)) == "y" { | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
return errors.Errorf("additional privileges requested") | ||
} |
Oops, something went wrong.