Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for running cortex through docker image without root uid #4107

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 16 additions & 1 deletion cmd/cortex/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
FROM alpine:3.13
RUN apk add --no-cache ca-certificates
RUN apk add --no-cache ca-certificates libcap

# create cortex user to run cortex as non-root
RUN addgroup -g 10000 -S cortex && \
adduser -u 10000 -S cortex -G cortex

RUN mkdir /data && \
pracucci marked this conversation as resolved.
Show resolved Hide resolved
chown cortex:cortex /data

VOLUME /data
WORKDIR /data

COPY migrations /migrations/
COPY cortex /bin/cortex

# allow cortex to bind to port 80 as non-root
RUN setcap 'cap_net_bind_service=+ep' /bin/cortex
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[question] Do you see any problem with this change when running the image in K8S?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the way it should work is by specifying in the manifest:

      securityContext:
        capabilities:
          add:
            - NET_BIND_SERVICE

However it looks like this doesn't work: kubernetes/kubernetes#56374

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be an idea to add a USER line, so it no longer defaults to root?
I guess this might cause trouble upgrading an installation, which has existing files in persistent volumes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I eventually would like to get to, but in a first step, I want users of the docker image to opt in to not run as root. Otherwise we will need to ensure mounted in persistent volumes are chowned first.

securityContext: ...

I think if we rely on the container orchestration to set it up (k8s in that case), we will break users that are using bare docker. I think without that setcap we probably need to change the default port 80 to something > 1024


EXPOSE 80
ENTRYPOINT [ "/bin/cortex" ]

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/aws/aws-sdk-go v1.38.35
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
github.com/cespare/xxhash v1.1.0
github.com/coreos/go-semver v0.3.0
github.com/dustin/go-humanize v1.0.0
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb
github.com/felixge/fgprof v0.9.1
Expand Down
29 changes: 29 additions & 0 deletions integration/backward_compatibility_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ package integration

import (
"fmt"
"strings"
"testing"
"time"

"github.com/coreos/go-semver/semver"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -33,6 +35,27 @@ var (
}
)

// Cortex before 1.10 does not support running docker images as non-root. This
// will make sure that we run cortex version before 1.10 with a root user.
func useRootUserForOlderImages(image string, svc *e2ecortex.CortexService) {

// find versioned image tag
needle := ":v"
pos := strings.Index(image, needle)

// return early if needle not found (most likely :latest) image, which we want to run as non-root user
if pos == -1 {
return
}

// if version is before 1.10 set an explicict root user
ver := semver.New(image[pos+len(needle):])
v1_10 := semver.New("1.10.0")
if ver.LessThan(*v1_10) {
svc.SetUser("root:root")
}
}

func preCortex14Flags(flags map[string]string) map[string]string {
return e2e.MergeFlagsWithoutRemovingEmpty(flags, map[string]string{
// Blocks storage CLI flags removed the "experimental" prefix in 1.4.
Expand Down Expand Up @@ -101,6 +124,7 @@ func runBackwardCompatibilityTestWithChunksStorage(t *testing.T, previousImage s

// Start other Cortex components (ingester running on previous version).
ingester1 := e2ecortex.NewIngester("ingester-1", consul.NetworkHTTPEndpoint(), flagsForOldImage, previousImage)
useRootUserForOlderImages(previousImage, ingester1)
distributor := e2ecortex.NewDistributor("distributor", consul.NetworkHTTPEndpoint(), ChunksStorageFlags(), "")
require.NoError(t, s.StartAndWaitReady(distributor, ingester1))

Expand Down Expand Up @@ -169,6 +193,9 @@ func runNewDistributorsCanPushToOldIngestersWithReplication(t *testing.T, previo
ingester1 := e2ecortex.NewIngester("ingester-1", consul.NetworkHTTPEndpoint(), flagsForPreviousImage, previousImage)
ingester2 := e2ecortex.NewIngester("ingester-2", consul.NetworkHTTPEndpoint(), flagsForPreviousImage, previousImage)
ingester3 := e2ecortex.NewIngester("ingester-3", consul.NetworkHTTPEndpoint(), flagsForPreviousImage, previousImage)
useRootUserForOlderImages(previousImage, ingester1)
useRootUserForOlderImages(previousImage, ingester2)
useRootUserForOlderImages(previousImage, ingester3)
distributor := e2ecortex.NewDistributor("distributor", consul.NetworkHTTPEndpoint(), flagsForNewImage, "")
require.NoError(t, s.StartAndWaitReady(distributor, ingester1, ingester2, ingester3))

Expand Down Expand Up @@ -231,6 +258,7 @@ func checkQueries(
t.Run(name, func(t *testing.T) {
// Start query-frontend.
queryFrontend := e2ecortex.NewQueryFrontend("query-frontend", c.queryFrontendFlags, c.queryFrontendImage)
useRootUserForOlderImages(c.queryFrontendImage, queryFrontend)
require.NoError(t, s.Start(queryFrontend))
defer func() {
require.NoError(t, s.Stop(queryFrontend))
Expand All @@ -240,6 +268,7 @@ func checkQueries(
querier := e2ecortex.NewQuerier("querier", consul.NetworkHTTPEndpoint(), e2e.MergeFlagsWithoutRemovingEmpty(c.querierFlags, map[string]string{
"-querier.frontend-address": queryFrontend.NetworkGRPCEndpoint(),
}), c.querierImage)
useRootUserForOlderImages(c.querierImage, querier)

require.NoError(t, s.Start(querier))
defer func() {
Expand Down
4 changes: 3 additions & 1 deletion integration/ca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ func (ca *CA) WriteCertificate(template *x509.Certificate, certPath string, keyP
return err
}

if err := writeExclusivePEMFile(keyPath, "PRIVATE KEY", 0600, keyBytes); err != nil {
// TODO: private keys should not be worldreadable. This is required when
pracucci marked this conversation as resolved.
Show resolved Hide resolved
// the container is run as non-root user
if err := writeExclusivePEMFile(keyPath, "PRIVATE KEY", 0644, keyBytes); err != nil {
return err
}

Expand Down
6 changes: 6 additions & 0 deletions integration/e2e/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,5 +167,11 @@ func GetTempDirectory() (string, error) {
return "", err
}

// change mode of temporary directory to support non-root containers.
if err := os.Chmod(absDir, 0777); err != nil {
_ = os.RemoveAll(tmpDir)
return "", err
}

return absDir, nil
}
5 changes: 4 additions & 1 deletion integration/e2ecortex/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ func NewCortexService(
grpcPort int,
otherPorts ...int,
) *CortexService {
return &CortexService{
svc := &CortexService{
HTTPService: e2e.NewHTTPService(name, image, command, readiness, httpPort, append(otherPorts, grpcPort)...),
grpcPort: grpcPort,
}
// run all tests as non-root user
svc.SetUser("cortex:cortex")
return svc
}

func (s *CortexService) GRPCEndpoint() string {
Expand Down
1 change: 1 addition & 0 deletions integration/e2ecortex/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ func NewRuler(name string, consulAddress string, flags map[string]string, image
// Configure the ingesters ring backend
"-ring.store": "consul",
"-consul.hostname": consulAddress,
"-ruler.rule-path": "/data/rules",
}, flags))...),
e2e.NewHTTPReadinessProbe(httpPort, "/ready", 200, 299),
httpPort,
Expand Down
1 change: 1 addition & 0 deletions vendor/modules.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.