diff --git a/config/config.go b/config/config.go index e8bf613a..f282133d 100644 --- a/config/config.go +++ b/config/config.go @@ -176,6 +176,8 @@ type ContainerConfig struct { RunCommand string // template string PullCommand string // template string StopCommand string // template string + EnableTags bool + Tags map[string]string } // HPCBackend describes the configuration for a HPC scheduler backend such as diff --git a/config/default.go b/config/default.go index 1db89782..a978d740 100644 --- a/config/default.go +++ b/config/default.go @@ -61,14 +61,30 @@ func DefaultConfig() Config { LogUpdateRate: Duration(time.Second * 5), LogTailSize: 10000, MaxParallelTransfers: 10, + // `docker run` command flags + // https://docs.docker.com/reference/cli/docker/container/run/ Container: ContainerConfig{ DriverCommand: "docker", RunCommand: "run -i --read-only " + + // Remove container after it exits "{{if .RemoveContainer}}--rm{{end}} " + - "{{range $k, $v := .Env}}-e {{$k}}={{$v}} {{end}} " + + + // Environment variables + "{{range $k, $v := .Env}}--env {{$k}}={{$v}} {{end}} " + + + // Tags/Labels + "{{range $k, $v := .Tags}}--label {{$k}}={{$v}} {{end}} " + + + // Container Name "{{if .Name}}--name {{.Name}}{{end}} " + - "{{if .Workdir}}-w {{.Workdir}}{{end}} " + - "{{range .Volumes}}-v {{.HostPath}}:{{.ContainerPath}}:{{if .Readonly}}ro{{else}}rw{{end}} {{end}} " + + + // Workdir + "{{if .Workdir}}--workdir {{.Workdir}}{{end}} " + + + // Volumes + "{{range .Volumes}}--volume {{.HostPath}}:{{.ContainerPath}}:{{if .Readonly}}ro{{else}}rw{{end}} {{end}} " + + + // Image and Command "{{.Image}} {{.Command}}", PullCommand: "pull {{.Image}}", StopCommand: "rm -f {{.Name}}", @@ -146,11 +162,6 @@ func DefaultConfig() Config { c.AWSBatch.ReconcileRate = reconcile c.AWSBatch.DisableReconciler = true - kubernetesTemplate := intern.MustAsset("config/kubernetes-template.yaml") - executorTemplate := intern.MustAsset("config/kubernetes-executor-template.yaml") - c.Kubernetes.Executor = "docker" - c.Kubernetes.Template = string(kubernetesTemplate) - c.Kubernetes.ExecutorTemplate = string(executorTemplate) c.Kubernetes.ReconcileRate = reconcile return c diff --git a/go.mod b/go.mod index 26289300..ded3b5c8 100644 --- a/go.mod +++ b/go.mod @@ -193,6 +193,12 @@ require ( github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/validate v0.24.0 // indirect + github.com/go-restruct/restruct v1.2.0-alpha // indirect + github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/glog v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect @@ -205,6 +211,7 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/google/wire v0.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect diff --git a/worker/container_engine.go b/worker/container_engine.go index b0a8915e..b0296072 100644 --- a/worker/container_engine.go +++ b/worker/container_engine.go @@ -47,6 +47,8 @@ type ContainerConfig struct { RunCommand string // template string PullCommand string // template string StopCommand string // template string + EnableTags bool + Tags map[string]string } type ContainerVersion struct { diff --git a/worker/worker.go b/worker/worker.go index a0f67bdd..815b9654 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "regexp" "time" "github.com/ohsu-comp-bio/funnel/config" @@ -174,6 +175,8 @@ func (r *DefaultWorker) Run(pctx context.Context) (runerr error) { containerConfig := ContainerConfig{ Image: d.Image, Command: d.Command, + // TODO: Where is d.Env set? + // Do we need to sanitize these values as well? Env: d.Env, Volumes: mapper.Volumes, Workdir: d.Workdir, @@ -186,6 +189,15 @@ func (r *DefaultWorker) Run(pctx context.Context) (runerr error) { containerConfig.RunCommand = r.Conf.Container.RunCommand containerConfig.PullCommand = r.Conf.Container.PullCommand containerConfig.StopCommand = r.Conf.Container.StopCommand + + // Hide this behind explicit flag/option in configuration + if r.Conf.Container.EnableTags { + for k, v := range task.Tags { + safeTag := r.sanitizeValues(v) + containerConfig.Tags[k] = safeTag + } + } + containerEngine, err := f.NewContainerEngine(containerConfig) if err != nil { run.syserr = err @@ -307,6 +319,14 @@ func (r *DefaultWorker) validate(mapper *FileMapper) error { return nil } +// Sanitizes the input string to avoid command injection. +// Only allows alphanumeric characters, dashes, underscores, and dots. +func (r *DefaultWorker) sanitizeValues(value string) string { + // Replace anything that is not an alphanumeric character, dash, underscore, or dot + re := regexp.MustCompile(`[^a-zA-Z0-9-_\.]`) + return re.ReplaceAllString(value, "") +} + func (r *DefaultWorker) pollForCancel(pctx context.Context, cancelCallback func()) context.Context { taskctx, cancel := context.WithCancel(pctx)