Skip to content

Commit

Permalink
Add launch policy and stdout/stderr redirect
Browse files Browse the repository at this point in the history
  • Loading branch information
jkl73 committed May 3, 2022
1 parent a0a2373 commit 0f599c2
Show file tree
Hide file tree
Showing 5 changed files with 327 additions and 8 deletions.
25 changes: 20 additions & 5 deletions launcher/container_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,19 @@ func NewRunner(ctx context.Context, cdClient *containerd.Client, token oauth2.To
log.Printf("Operator Override Env Vars : %v\n", envs)
log.Printf("Operator Override Cmd : %v\n", launchSpec.Cmd)

imagelabels, err := getImageLabels(ctx, image)
imageLabels, err := getImageLabels(ctx, image)
if err != nil {
log.Printf("Failed to get image OCI labels %v\n", err)
}
log.Printf("Image Labels : %v\n", imagelabels)
log.Printf("Image Labels : %v\n", imageLabels)

launchPolicy, err := spec.GetLaunchPolicy(imageLabels)
if err != nil {
return nil, err
}
if err := launchPolicy.Verify(launchSpec); err != nil {
return nil, err
}

if imageConfig, err := image.Config(ctx); err != nil {
log.Println(err)
Expand Down Expand Up @@ -300,7 +308,16 @@ func (r *ContainerRunner) Run(ctx context.Context) error {
return fmt.Errorf("failed to fetch and write OIDC token: %v", err)
}

task, err := r.container.NewTask(ctx, cio.NewCreator(cio.WithStdio))
var stdoutIO io.Writer
var stderrIO io.Writer
if r.launchSpec.ContainerStderrEcho {
stdoutIO = os.Stderr
}
if r.launchSpec.ContainerStdoutEcho {
stderrIO = os.Stdout
}

task, err := r.container.NewTask(ctx, cio.NewCreator(cio.WithStreams(os.Stdin, stdoutIO, stderrIO)))
if err != nil {
return err
}
Expand Down Expand Up @@ -358,14 +375,12 @@ func getImageLabels(ctx context.Context, image containerd.Image) (map[string]str
if err != nil {
return nil, err
}

switch ic.MediaType {
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
if err != nil {
return nil, err
}

var ociimage v1.Image
if err := json.Unmarshal(p, &ociimage); err != nil {
return nil, err
Expand Down
96 changes: 96 additions & 0 deletions launcher/spec/launch_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package spec

import (
"fmt"
"strconv"
"strings"
)

// LaunchPolicy contains policies on starting the container.
// The policy comes from the labels of the image.
type LaunchPolicy struct {
AllowedEnvOverride []string
AllowedCmdOverride bool
AllowedStdoutEcho bool
AllowedStderrEcho bool
}

const (
envOverride = "tee.launch_policy.allow_env_override"
cmdOverride = "tee.launch_policy.allow_cmd_override"
stdoutEcho = "tee.launch_policy.allow_stdout_echo"
stderrEcho = "tee.launch_policy.allow_stderr_echo"
)

// GetLaunchPolicy takes in a map[string] string which should come from image labels,
// and will try to parse it into a LaunchPolicy. Extra fields will be ignored.
func GetLaunchPolicy(imageLabels map[string]string) (LaunchPolicy, error) {
var err error
launchPolicy := LaunchPolicy{}
if v, ok := imageLabels[envOverride]; ok {
envs := strings.Split(v, ",")
for _, env := range envs {
// strip out empty env name
if env != "" {
launchPolicy.AllowedEnvOverride = append(launchPolicy.AllowedEnvOverride, env)
}
}
}

if v, ok := imageLabels[cmdOverride]; ok {
if launchPolicy.AllowedCmdOverride, err = strconv.ParseBool(v); err != nil {
return LaunchPolicy{}, fmt.Errorf("value of LABEL %s of the image is not a boolean %s", cmdOverride, v)
}
} else {
// default is false
launchPolicy.AllowedCmdOverride = false
}

if v, ok := imageLabels[stdoutEcho]; ok {
if launchPolicy.AllowedStdoutEcho, err = strconv.ParseBool(v); err != nil {
return LaunchPolicy{}, fmt.Errorf("value of LABEL %s of the image is not a boolean %s", stdoutEcho, v)
}
} else {
launchPolicy.AllowedStdoutEcho = false
}

if v, ok := imageLabels[stderrEcho]; ok {
if launchPolicy.AllowedStderrEcho, err = strconv.ParseBool(v); err != nil {
return LaunchPolicy{}, fmt.Errorf("value of LABEL %s of the image is not a boolean %s", stderrEcho, v)
}
} else {
launchPolicy.AllowedStderrEcho = false
}

return launchPolicy, nil
}

// Verify will use the LaunchPolicy to verify the given LauncherSpec. If the verification passed, will return nil.
// If there is any issue, the function will return the error based on the first error.
func (p LaunchPolicy) Verify(lp LauncherSpec) error {
for _, e := range lp.Envs {
if !contains(p.AllowedEnvOverride, e.Name) {
return fmt.Errorf("env var %s is not allowed to be overridden on this image; allowed envs to be overridden: %v", e, p.AllowedEnvOverride)
}
}
if !p.AllowedCmdOverride && len(lp.Cmd) > 0 {
return fmt.Errorf("CMD is not allowed to be overridden on this image")
}
if !p.AllowedStdoutEcho && lp.ContainerStdoutEcho {
return fmt.Errorf("stdout is not allowed to be redirected")
}
if !p.AllowedStderrEcho && lp.ContainerStderrEcho {
return fmt.Errorf("stderr is not allowed to be redirected")
}

return nil
}

func contains(strs []string, target string) bool {
for _, s := range strs {
if s == target {
return true
}
}
return false
}
183 changes: 183 additions & 0 deletions launcher/spec/launch_policy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package spec

import (
"testing"

"github.com/google/go-cmp/cmp"
)

func TestLauncPolicy(t *testing.T) {
testCases := []struct {
testName string
imageLables map[string]string
expectedPolicy LaunchPolicy
}{
{
"case 1",
map[string]string{
envOverride: "foo",
cmdOverride: "true",
stdoutEcho: "true",
stderrEcho: "True",
},
LaunchPolicy{
AllowedEnvOverride: []string{"foo"},
AllowedCmdOverride: true,
AllowedStdoutEcho: true,
AllowedStderrEcho: true,
},
},
{
"case 2",
map[string]string{
envOverride: "foo,bar",
},
LaunchPolicy{
AllowedEnvOverride: []string{"foo", "bar"},
AllowedCmdOverride: false,
AllowedStdoutEcho: false,
AllowedStderrEcho: false,
},
},
{
"case 3 default case",
nil,
LaunchPolicy{
AllowedEnvOverride: nil,
AllowedCmdOverride: false,
AllowedStdoutEcho: false,
AllowedStderrEcho: false,
},
},
{
"case 4 empty string in env",
map[string]string{
envOverride: ",,,foo",
cmdOverride: "false",
stdoutEcho: "true",
stderrEcho: "True",
},
LaunchPolicy{
AllowedEnvOverride: []string{"foo"},
AllowedCmdOverride: false,
AllowedStdoutEcho: true,
AllowedStderrEcho: true,
},
},
}

for _, testcase := range testCases {
t.Run(testcase.testName, func(t *testing.T) {
got, err := GetLaunchPolicy(testcase.imageLables)
if err != nil {
t.Fatal(err)
}

if !cmp.Equal(got, testcase.expectedPolicy) {
t.Errorf("Launchspec got %+v, want %+v", got, testcase.expectedPolicy)
}
})
}
}

func TestVerify(t *testing.T) {
testCases := []struct {
testName string
policy LaunchPolicy
spec LauncherSpec
expectErr bool
}{
{
"allow everything",
LaunchPolicy{
AllowedEnvOverride: []string{"foo"},
AllowedCmdOverride: true,
AllowedStdoutEcho: true,
AllowedStderrEcho: true,
},
LauncherSpec{
Envs: []EnvVar{{Name: "foo", Value: "foo"}},
Cmd: []string{"foo"},
ContainerStdoutEcho: true,
ContainerStderrEcho: true,
},
false,
},
{
"default case",
LaunchPolicy{},
LauncherSpec{},
false,
},
{
"env override violation",
LaunchPolicy{
AllowedEnvOverride: []string{"foo"},
},
LauncherSpec{
Envs: []EnvVar{{Name: "bar", Value: ""}},
},
true,
},
{
"cmd violation",
LaunchPolicy{
AllowedCmdOverride: false,
},
LauncherSpec{
Cmd: []string{"foo"},
},
true,
},
{
"stdout echo violation",
LaunchPolicy{
AllowedCmdOverride: false,
},
LauncherSpec{
ContainerStdoutEcho: true,
},
true,
},
{
"stderr echo violation",
LaunchPolicy{
AllowedCmdOverride: false,
},
LauncherSpec{
ContainerStderrEcho: true,
},
true,
},
{
"allow everything",
LaunchPolicy{
AllowedEnvOverride: []string{"foo"},
AllowedCmdOverride: true,
AllowedStdoutEcho: true,
AllowedStderrEcho: true,
},
LauncherSpec{
Envs: []EnvVar{{Name: "foo", Value: "foo"}},
Cmd: []string{"foo"},
ContainerStdoutEcho: true,
ContainerStderrEcho: true,
},
false,
},
}
for _, testCase := range testCases {
t.Run(testCase.testName, func(t *testing.T) {
err := testCase.policy.Verify(testCase.spec)
if testCase.expectErr {
if err == nil {
t.Errorf("expected error, but got nil")
}
} else {
if err != nil {
t.Errorf("expected no error, but got %v", err)
}
}
})
}
}
Loading

0 comments on commit 0f599c2

Please sign in to comment.