Merge pull request #2378 from ankeesler/akeesler/distroless
distroless: Dockerfile works with distroless base image
This commit is contained in:
commit
75c27c8dba
5 changed files with 223 additions and 46 deletions
29
Dockerfile
29
Dockerfile
|
@ -1,8 +1,10 @@
|
||||||
|
ARG BASEIMAGE=alpine:3.15.0
|
||||||
|
|
||||||
FROM golang:1.17.6-alpine3.14 AS builder
|
FROM golang:1.17.6-alpine3.14 AS builder
|
||||||
|
|
||||||
WORKDIR /usr/local/src/dex
|
WORKDIR /usr/local/src/dex
|
||||||
|
|
||||||
RUN apk add --no-cache --update alpine-sdk
|
RUN apk add --no-cache --update alpine-sdk ca-certificates openssl
|
||||||
|
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
@ -20,6 +22,12 @@ COPY . .
|
||||||
|
|
||||||
RUN make release-binary
|
RUN make release-binary
|
||||||
|
|
||||||
|
FROM alpine:3.15.0 AS stager
|
||||||
|
|
||||||
|
RUN mkdir -p /var/dex
|
||||||
|
RUN mkdir -p /etc/dex
|
||||||
|
COPY config.docker.yaml /etc/dex/
|
||||||
|
|
||||||
FROM alpine:3.15.0 AS gomplate
|
FROM alpine:3.15.0 AS gomplate
|
||||||
|
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
|
@ -33,34 +41,29 @@ RUN wget -O /usr/local/bin/gomplate \
|
||||||
&& chmod +x /usr/local/bin/gomplate
|
&& chmod +x /usr/local/bin/gomplate
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.15.0
|
FROM $BASEIMAGE
|
||||||
|
|
||||||
# Dex connectors, such as GitHub and Google logins require root certificates.
|
# Dex connectors, such as GitHub and Google logins require root certificates.
|
||||||
# Proper installations should manage those certificates, but it's a bad user
|
# Proper installations should manage those certificates, but it's a bad user
|
||||||
# experience when this doesn't work out of the box.
|
# experience when this doesn't work out of the box.
|
||||||
#
|
#
|
||||||
# OpenSSL is required so wget can query HTTPS endpoints for health checking.
|
# See https://go.dev/src/crypto/x509/root_linux.go for Go root CA bundle locations.
|
||||||
RUN apk add --no-cache --update ca-certificates openssl
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
|
||||||
RUN mkdir -p /var/dex
|
COPY --from=stager --chown=1001:1001 /var/dex /var/dex
|
||||||
RUN chown -R 1001:1001 /var/dex
|
COPY --from=stager --chown=1001:1001 /etc/dex /etc/dex
|
||||||
|
|
||||||
RUN mkdir -p /etc/dex
|
|
||||||
COPY config.docker.yaml /etc/dex/
|
|
||||||
RUN chown -R 1001:1001 /etc/dex
|
|
||||||
|
|
||||||
# Copy module files for CVE scanning / dependency analysis.
|
# Copy module files for CVE scanning / dependency analysis.
|
||||||
COPY --from=builder /usr/local/src/dex/go.mod /usr/local/src/dex/go.sum /usr/local/src/dex/
|
COPY --from=builder /usr/local/src/dex/go.mod /usr/local/src/dex/go.sum /usr/local/src/dex/
|
||||||
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 /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/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=builder /usr/local/src/dex/web /srv/dex/web
|
||||||
|
|
||||||
COPY --from=gomplate /usr/local/bin/gomplate /usr/local/bin/gomplate
|
COPY --from=gomplate /usr/local/bin/gomplate /usr/local/bin/gomplate
|
||||||
|
|
||||||
USER 1001:1001
|
USER 1001:1001
|
||||||
|
|
||||||
COPY docker-entrypoint.sh /entrypoint.sh
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint"]
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
|
||||||
CMD ["dex", "serve", "/etc/dex/config.docker.yaml"]
|
CMD ["dex", "serve", "/etc/dex/config.docker.yaml"]
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -18,7 +18,7 @@ group=$(shell id -g -n)
|
||||||
|
|
||||||
export GOBIN=$(PWD)/bin
|
export GOBIN=$(PWD)/bin
|
||||||
|
|
||||||
LD_FLAGS="-w -X main.version=$(VERSION)"
|
LD_FLAGS="-w -X main.version=$(VERSION) -extldflags \"-static\""
|
||||||
|
|
||||||
# Dependency versions
|
# Dependency versions
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ bin/example-app:
|
||||||
.PHONY: release-binary
|
.PHONY: release-binary
|
||||||
release-binary: generate
|
release-binary: generate
|
||||||
@go build -o /go/bin/dex -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex
|
@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:
|
docker-compose.override.yaml:
|
||||||
cp docker-compose.override.yaml.dist docker-compose.override.yaml
|
cp docker-compose.override.yaml.dist docker-compose.override.yaml
|
||||||
|
|
92
cmd/docker-entrypoint/main.go
Normal file
92
cmd/docker-entrypoint/main.go
Normal 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
|
||||||
|
}
|
113
cmd/docker-entrypoint/main_test.go
Normal file
113
cmd/docker-entrypoint/main_test.go
Normal 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
|
||||||
|
}
|
|
@ -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 $@
|
|
Reference in a new issue