distroless: rewrite docker-entrypoint.sh in go

See go doc ./cmd/docker-entrypoint for why.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
This commit is contained in:
Andrew Keesler 2022-01-18 12:24:05 -05:00
parent ba1bd65c10
commit 764ce711b6
No known key found for this signature in database
GPG Key ID: 27CE0444346F9413
5 changed files with 208 additions and 35 deletions

View File

@ -54,13 +54,12 @@ COPY --from=builder /usr/local/src/dex/go.mod /usr/local/src/dex/go.sum /usr/loc
COPY --from=builder /usr/local/src/dex/api/v2/go.mod /usr/local/src/dex/api/v2/go.sum /usr/local/src/dex/api/v2/
COPY --from=builder /go/bin/dex /usr/local/bin/dex
COPY --from=builder /go/bin/docker-entrypoint /usr/local/bin/docker-entrypoint
COPY --from=builder /usr/local/src/dex/web /srv/dex/web
COPY --from=gomplate /usr/local/bin/gomplate /usr/local/bin/gomplate
USER 1001:1001
COPY docker-entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT ["/usr/local/bin/docker-entrypoint"]
CMD ["dex", "serve", "/etc/dex/config.docker.yaml"]

View File

@ -48,6 +48,7 @@ bin/example-app:
.PHONY: release-binary
release-binary: generate
@go build -o /go/bin/dex -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex
@go build -o /go/bin/docker-entrypoint -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/docker-entrypoint
docker-compose.override.yaml:
cp docker-compose.override.yaml.dist docker-compose.override.yaml

View File

@ -0,0 +1,92 @@
// Package main provides a utility program to launch the Dex container process with an optional
// templating step (provided by gomplate).
//
// This was originally written as a shell script, but we rewrote it as a Go program so that it could
// run as a raw binary in a distroless container.
package main
import (
"fmt"
"os"
"os/exec"
"strings"
"syscall"
)
func main() {
// Note that this docker-entrypoint program is args[0], and it is provided with the true process
// args.
args := os.Args[1:]
if err := run(args, realExec, realWhich); err != nil {
fmt.Println("error:", err.Error())
os.Exit(1)
}
}
func realExec(fork bool, args ...string) error {
if fork {
if output, err := exec.Command(args[0], args[1:]...).CombinedOutput(); err != nil {
return fmt.Errorf("cannot fork/exec command %s: %w (output: %q)", args, err, string(output))
}
return nil
}
argv0, err := exec.LookPath(args[0])
if err != nil {
return fmt.Errorf("cannot lookup path for command %s: %w", args[0], err)
}
if err := syscall.Exec(argv0, args, os.Environ()); err != nil {
return fmt.Errorf("cannot exec command %s (%q): %w", args, argv0, err)
}
return nil
}
func realWhich(path string) string {
fullPath, err := exec.LookPath(path)
if err != nil {
return ""
}
return fullPath
}
func run(args []string, execFunc func(bool, ...string) error, whichFunc func(string) string) error {
if args[0] != "dex" && args[0] != whichFunc("dex") {
return execFunc(false, args...)
}
if args[1] != "serve" {
return execFunc(false, args...)
}
newArgs := []string{}
for _, tplCandidate := range args {
if hasSuffixes(tplCandidate, ".tpl", ".tmpl", ".yaml") {
tmpFile, err := os.CreateTemp("/tmp", "dex.config.yaml-*")
if err != nil {
return fmt.Errorf("cannot create temp file: %w", err)
}
if err := execFunc(true, "gomplate", "-f", tplCandidate, "-o", tmpFile.Name()); err != nil {
return err
}
newArgs = append(newArgs, tmpFile.Name())
} else {
newArgs = append(newArgs, tplCandidate)
}
}
return execFunc(false, newArgs...)
}
func hasSuffixes(s string, suffixes ...string) bool {
for _, suffix := range suffixes {
if strings.HasSuffix(s, suffix) {
return true
}
}
return false
}

View File

@ -0,0 +1,113 @@
package main
import (
"strings"
"testing"
)
type execArgs struct {
fork bool
argPrefixes []string
}
func TestRun(t *testing.T) {
tests := []struct {
name string
args []string
execReturns error
whichReturns string
wantExecArgs []execArgs
wantErr error
}{
{
name: "executable not dex",
args: []string{"tuna", "fish"},
wantExecArgs: []execArgs{{fork: false, argPrefixes: []string{"tuna", "fish"}}},
},
{
name: "executable is full path to dex",
args: []string{"/usr/local/bin/dex", "marshmallow", "zelda"},
whichReturns: "/usr/local/bin/dex",
wantExecArgs: []execArgs{{fork: false, argPrefixes: []string{"/usr/local/bin/dex", "marshmallow", "zelda"}}},
},
{
name: "command is not serve",
args: []string{"dex", "marshmallow", "zelda"},
wantExecArgs: []execArgs{{fork: false, argPrefixes: []string{"dex", "marshmallow", "zelda"}}},
},
{
name: "no templates",
args: []string{"dex", "serve", "config.yaml.not-a-template"},
wantExecArgs: []execArgs{{fork: false, argPrefixes: []string{"dex", "serve", "config.yaml.not-a-template"}}},
},
{
name: "no templates",
args: []string{"dex", "serve", "config.yaml.not-a-template"},
wantExecArgs: []execArgs{{fork: false, argPrefixes: []string{"dex", "serve", "config.yaml.not-a-template"}}},
},
{
name: ".tpl template",
args: []string{"dex", "serve", "config.tpl"},
wantExecArgs: []execArgs{
{fork: true, argPrefixes: []string{"gomplate", "-f", "config.tpl", "-o", "/tmp/dex.config.yaml-"}},
{fork: false, argPrefixes: []string{"dex", "serve", "/tmp/dex.config.yaml-"}},
},
},
{
name: ".tmpl template",
args: []string{"dex", "serve", "config.tmpl"},
wantExecArgs: []execArgs{
{fork: true, argPrefixes: []string{"gomplate", "-f", "config.tmpl", "-o", "/tmp/dex.config.yaml-"}},
{fork: false, argPrefixes: []string{"dex", "serve", "/tmp/dex.config.yaml-"}},
},
},
{
name: ".yaml template",
args: []string{"dex", "serve", "some/path/config.yaml"},
wantExecArgs: []execArgs{
{fork: true, argPrefixes: []string{"gomplate", "-f", "some/path/config.yaml", "-o", "/tmp/dex.config.yaml-"}},
{fork: false, argPrefixes: []string{"dex", "serve", "/tmp/dex.config.yaml-"}},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var gotExecForks []bool
var gotExecArgs [][]string
fakeExec := func(fork bool, args ...string) error {
gotExecForks = append(gotExecForks, fork)
gotExecArgs = append(gotExecArgs, args)
return test.execReturns
}
fakeWhich := func(_ string) string { return test.whichReturns }
gotErr := run(test.args, fakeExec, fakeWhich)
if (test.wantErr == nil) != (gotErr == nil) {
t.Errorf("wanted error %s, got %s", test.wantErr, gotErr)
}
if !execArgsMatch(test.wantExecArgs, gotExecForks, gotExecArgs) {
t.Errorf("wanted exec args %+v, got %+v %+v", test.wantExecArgs, gotExecForks, gotExecArgs)
}
})
}
}
func execArgsMatch(wantExecArgs []execArgs, gotForks []bool, gotExecArgs [][]string) bool {
if len(wantExecArgs) != len(gotForks) {
return false
}
for i := range wantExecArgs {
if wantExecArgs[i].fork != gotForks[i] {
return false
}
for j := range wantExecArgs[i].argPrefixes {
if !strings.HasPrefix(gotExecArgs[i][j], wantExecArgs[i].argPrefixes[j]) {
return false
}
}
}
return true
}

View File

@ -1,32 +0,0 @@
#!/bin/sh -e
### Usage: /docker-entrypoint.sh <command> <args>
function main() {
executable=$1
command=$2
if [[ "$executable" != "dex" ]] && [[ "$executable" != "$(which dex)" ]]; then
exec $@
fi
if [[ "$command" != "serve" ]]; then
exec $@
fi
for tpl_candidate in $@ ; do
case "$tpl_candidate" in
*.tpl|*.tmpl|*.yaml)
tmp_file=$(mktemp /tmp/dex.config.yaml-XXXXXX)
gomplate -f "$tpl_candidate" -o "$tmp_file"
args="${args} ${tmp_file}"
;;
*)
args="${args} ${tpl_candidate}"
;;
esac
done
exec $args
}
main $@