Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,16 @@ cluster, if available, by setting `KO_DOCKER_REPO=kind.local`. By default this
loads into the default KinD cluster name (`kind`). To load into another KinD
cluster, set `KIND_CLUSTER_NAME=my-other-cluster`.

## Setting POSIX capabilities on the binary executable in the container image

For a binary to perform certain privileged operations, it needs to be run with
the appropriate POSIX capabilities. `ko` supports setting the capability attribute
on the binary executable in the container image. This indicates to the operating
system that the binary should be run with the specified capabilities. In docker,
for example, this means that the binary has to be run with the appropriate
`--cap-add` flags. In Kubernetes, this means that the binary has to be run with
the appropriate `securityContext` settings.

```console
ko build --cap-add CAP_IPC_LOCK ./cmd/app
```
1 change: 1 addition & 0 deletions docs/reference/ko_apply.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ ko apply -f FILENAME [flags]
```
--bare Whether to just use KO_DOCKER_REPO without additional context (may not work properly with --tags).
-B, --base-import-paths Whether to use the base path without MD5 hash after KO_DOCKER_REPO (may not work properly with --tags).
--cap-add strings Which POSIX capabilities to set on the binary. Eg. CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER
--disable-optimizations Disable optimizations when building Go code. Useful when you want to interactively debug the created container.
-f, --filename strings Filename, directory, or URL to files to use to create the resource
-h, --help help for apply
Expand Down
1 change: 1 addition & 0 deletions docs/reference/ko_build.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ ko build IMPORTPATH... [flags]
```
--bare Whether to just use KO_DOCKER_REPO without additional context (may not work properly with --tags).
-B, --base-import-paths Whether to use the base path without MD5 hash after KO_DOCKER_REPO (may not work properly with --tags).
--cap-add strings Which POSIX capabilities to set on the binary. Eg. CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER
--disable-optimizations Disable optimizations when building Go code. Useful when you want to interactively debug the created container.
-h, --help help for build
--image-label strings Which labels (key=value) to add to the image.
Expand Down
1 change: 1 addition & 0 deletions docs/reference/ko_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ ko create -f FILENAME [flags]
```
--bare Whether to just use KO_DOCKER_REPO without additional context (may not work properly with --tags).
-B, --base-import-paths Whether to use the base path without MD5 hash after KO_DOCKER_REPO (may not work properly with --tags).
--cap-add strings Which POSIX capabilities to set on the binary. Eg. CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER
--disable-optimizations Disable optimizations when building Go code. Useful when you want to interactively debug the created container.
-f, --filename strings Filename, directory, or URL to files to use to create the resource
-h, --help help for create
Expand Down
1 change: 1 addition & 0 deletions docs/reference/ko_resolve.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ ko resolve -f FILENAME [flags]
```
--bare Whether to just use KO_DOCKER_REPO without additional context (may not work properly with --tags).
-B, --base-import-paths Whether to use the base path without MD5 hash after KO_DOCKER_REPO (may not work properly with --tags).
--cap-add strings Which POSIX capabilities to set on the binary. Eg. CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER
--disable-optimizations Disable optimizations when building Go code. Useful when you want to interactively debug the created container.
-f, --filename strings Filename, directory, or URL to files to use to create the resource
-h, --help help for resolve
Expand Down
1 change: 1 addition & 0 deletions docs/reference/ko_run.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ ko run IMPORTPATH [flags]
```
--bare Whether to just use KO_DOCKER_REPO without additional context (may not work properly with --tags).
-B, --base-import-paths Whether to use the base path without MD5 hash after KO_DOCKER_REPO (may not work properly with --tags).
--cap-add strings Which POSIX capabilities to set on the binary. Eg. CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER
--disable-optimizations Disable optimizations when building Go code. Useful when you want to interactively debug the created container.
-h, --help help for run
--image-label strings Which labels (key=value) to add to the image.
Expand Down
13 changes: 13 additions & 0 deletions integration_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ fi
echo "7. On outside the module should fail."
pushd .. || exit 1
GO111MODULE=on ./ko/ko build --local github.com/go-training/helloworld && exit 1
echo "Test PASSED"
popd || exit 1

echo "8. On outside with build config specifying the test module builds."
Expand All @@ -110,6 +111,18 @@ for app in foo bar ; do
done
popd || exit 1

echo "9. Auto inside the module without vendoring & run with requesteed capabilities should output TEST"
RESULT="$(GO111MODULE=auto GOFLAGS="" ./ko build --cap-add CAP_IPC_LOCK --local github.com/go-training/helloworld | grep "$FILTER" | xargs -I% docker run --cap-add CAP_IPC_LOCK %)"
if [[ "$RESULT" != *"TEST"* ]]; then
echo "Test FAILED. Saw $RESULT" && exit 1
else
echo "Test PASSED"
fi

echo "10. Auto inside the module without vendoring & run with too little capabilities should fail"
(GO111MODULE=auto GOFLAGS="" ./ko build --cap-add CAP_CHOWN --local github.com/go-training/helloworld | grep "$FILTER" | xargs -I% docker run --cap-drop CAP_CHOWN %) && exit 1
echo "Test PASSED"

popd || exit 1
popd || exit 1

Expand Down
52 changes: 48 additions & 4 deletions pkg/build/gobuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"archive/tar"
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
gb "go/build"
Expand Down Expand Up @@ -81,6 +82,7 @@ type gobuild struct {
disableOptimizations bool
trimpath bool
buildConfigs map[string]Config
capabilities []Cap
platformMatcher *platformMatcher
dir string
labels map[string]string
Expand All @@ -103,6 +105,7 @@ type gobuildOpener struct {
disableOptimizations bool
trimpath bool
buildConfigs map[string]Config
capabilities []Cap
platforms []string
labels map[string]string
dir string
Expand Down Expand Up @@ -131,6 +134,7 @@ func (gbo *gobuildOpener) Open() (Interface, error) {
disableOptimizations: gbo.disableOptimizations,
trimpath: gbo.trimpath,
buildConfigs: gbo.buildConfigs,
capabilities: gbo.capabilities,
labels: gbo.labels,
dir: gbo.dir,
platformMatcher: matcher,
Expand Down Expand Up @@ -488,7 +492,7 @@ func appFilename(importpath string) string {
// owner: BUILTIN/Users group: BUILTIN/Users ($sddlValue="O:BUG:BU")
const userOwnerAndGroupSID = "AQAAgBQAAAAkAAAAAAAAAAAAAAABAgAAAAAABSAAAAAhAgAAAQIAAAAAAAUgAAAAIQIAAA=="

func tarBinary(name, binary string, platform *v1.Platform) (*bytes.Buffer, error) {
func tarBinary(name, binary string, platform *v1.Platform, caps []Cap) (*bytes.Buffer, error) {
buf := bytes.NewBuffer(nil)
tw := tar.NewWriter(buf)
defer tw.Close()
Expand Down Expand Up @@ -543,6 +547,12 @@ func tarBinary(name, binary string, platform *v1.Platform) (*bytes.Buffer, error
header.PAXRecords = map[string]string{
"MSWINDOWS.rawsd": userOwnerAndGroupSID,
}
} else if len(caps) > 0 {
// see: https://github.com/moby/moby/blob/9b9348ce86fc85798e67319f2b78439408074746/pkg/archive/archive.go#L503-L504
header.PAXRecords = map[string]string{
"SCHILY.xattr.security.capability": string(capabilityValue(caps)),
}
header.Format = tar.FormatPAX
}
// write the header to the tarball archive
if err := tw.WriteHeader(header); err != nil {
Expand All @@ -556,6 +566,40 @@ func tarBinary(name, binary string, platform *v1.Platform) (*bytes.Buffer, error
return buf, nil
}

func capabilityValue(caps []Cap) []byte {
vfsCapVer2 := uint32(0x02000000)
vfsCapFlageffective := uint32(0x000001)

// This is the full encoded capbility set for CAP_IPC_LOCK
// 02 00 00 01 (version 2, effective)
// XX XX XX XX (permitted_v1)
// 00 00 00 00 (inheritable_v1: 0)
// XX XX XX XX (permitted_v2)
// 00 00 00 00 (inheritable_v2: 0)

permitted_v1 := uint32(0)
inheritable_v1 := uint32(0)
permitted_v2 := uint32(0)
inheritable_v2 := uint32(0)

for _, cap := range caps {
if cap > 32 {
permitted_v2 |= 1 << (cap - 32)
} else {
permitted_v1 |= 1 << cap
}
}

capability := make([]byte, 0, 20)
capability = binary.LittleEndian.AppendUint32(capability, vfsCapVer2|vfsCapFlageffective)
capability = binary.LittleEndian.AppendUint32(capability, permitted_v1)
capability = binary.LittleEndian.AppendUint32(capability, inheritable_v1)
capability = binary.LittleEndian.AppendUint32(capability, permitted_v2)
capability = binary.LittleEndian.AppendUint32(capability, inheritable_v2)

return capability
}

func (g *gobuild) kodataPath(ref reference) (string, error) {
dir := filepath.Clean(g.dir)
if dir == "." {
Expand Down Expand Up @@ -865,7 +909,7 @@ func (g *gobuild) buildOne(ctx context.Context, refStr string, base v1.Image, pl
appPath := path.Join(appDir, appFileName)

miss := func() (v1.Layer, error) {
return buildLayer(appPath, file, platform, layerMediaType)
return buildLayer(appPath, file, platform, layerMediaType, g.capabilities)
}

binaryLayer, err := g.cache.get(ctx, file, miss)
Expand Down Expand Up @@ -948,9 +992,9 @@ func (g *gobuild) buildOne(ctx context.Context, refStr string, base v1.Image, pl
return si, nil
}

func buildLayer(appPath, file string, platform *v1.Platform, layerMediaType types.MediaType) (v1.Layer, error) {
func buildLayer(appPath, file string, platform *v1.Platform, layerMediaType types.MediaType, caps []Cap) (v1.Layer, error) {
// Construct a tarball with the binary and produce a layer.
binaryLayerBuf, err := tarBinary(appPath, file, platform)
binaryLayerBuf, err := tarBinary(appPath, file, platform, caps)
if err != nil {
return nil, fmt.Errorf("tarring binary: %w", err)
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/build/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,11 @@ func WithSBOMDir(dir string) Option {
return nil
}
}

// WithPOSIXCapabilities is a functional option for overriding the POSIX capabilities encoded in the binary file.
Copy link
Member

Choose a reason for hiding this comment

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

Small nit: How would you feel about WithPOSIXCapabilities(caps ...Cap) so that callers don't have to wrap it in a []Cap themselves?

Copy link
Author

Choose a reason for hiding this comment

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

Because WithPOSIXCapabilities overwrites the capabilities slice, I prefer the []Cap argument.
...Cap makes me think that the function will append the capabilities to the slice.
Maybe it's just me who thinks that. Please let me know if that is the case & I'll update the code.

func WithPOSIXCapabilities(capabilities []Cap) Option {
return func(gbo *gobuildOpener) error {
gbo.capabilities = capabilities
return nil
}
}
Loading