Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/sjinks/setcap-static

A statically linked lightweight version of setcap(8) to use in `scratch` images
https://github.com/sjinks/setcap-static

capabilities linux-capabilities scratch-image security security-tools setcap

Last synced: about 1 month ago
JSON representation

A statically linked lightweight version of setcap(8) to use in `scratch` images

Awesome Lists containing this project

README

        

# setcap-static

[![Build](https://github.com/sjinks/setcap-static/actions/workflows/build.yml/badge.svg)](https://github.com/sjinks/setcap-static/actions/workflows/build.yml)
[![Docker CI/CD](https://github.com/sjinks/setcap-static/actions/workflows/docker.yml/badge.svg)](https://github.com/sjinks/setcap-static/actions/workflows/docker.yml)
[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/sjinks/setcap-static.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/sjinks/setcap-static/context:cpp)
![Docker Image Size](https://img.shields.io/docker/image-size/wildwildangel/setcap-static/latest)

`setcap-static` is a statically linked trimmed down version of [setcap(8)](https://linux.die.net/man/8/setcap). It sets the capabilities of the given filename to the capabilities specified.

## Why

KubeSec security guidelines suggest that the running image should be "run as a [non-root user to ensure the least privilege](https://kubesec.io/basics/containers-securitycontext-runasnonroot-true/)." However, if the containerized application needs some `root` privileges (like binding to a port less than 1024) and runs in a `scratch` image, this will not be straightforward.

The issue is that Docker's `COPY` command does not preserve the extended attributes; therefore, you cannot do something like this:

```Dockerfile
FROM alpine:3.13 as build

# ...

RUN \
apk add --no-cache libcap \
&& setcap 'cap_net_bind_service=+ep' my-cool-application \
&& apk del --no-cache libcap

# ...

FROM scratch
COPY --from=build /path/to/my-cool-application /my-cool-application
```

In the target image, `my-cool-application` will not have the capabilities set in the `build` image. Therefore, if you need to grant some capabilities to your application, you have to do it in the target image. You cannot just copy `setcap` from Alpine — because it is a dynamically linked executable (it depends on ld-musl, libcap, libc.musl).

Here comes `libcap-static`. It is a lightweight version of `libcap`: it can only set the capabilities on a file, it does not support all other options of `libcap`.

Unlike `libcap`, `libcap-static` has an option to delete itself: this can be handy for `scratch` images if you don't want to leave any other executables than your application visible to the user (or an attacker). If `libcap-static` detects that the first two characters of `argv[0]` are `/!`, it will delete itself after the successful operation.

For example,

```Dockerfile
FROM scratch
COPY --from=wildwildangel/setcap-static /setcap-static /!setcap-static
COPY --from=build /build/build/tiny-ssh-honeypot /tiny-ssh-honeypot
RUN ["/!setcap-static", "cap_net_bind_service=+ep", "/tiny-ssh-honeypot"]
```

After granting the `CAP_NET_BIND_SERVICE` capability to `tiny-ssh-honeypot`, `libcap-static` will delete itself.

## Build

Build dependencies:
* Alpine: cmake, make, libcap-dev, libcap-static
* Ubuntu: cmake, make, libcap-dev

```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=MinSizeRel
cmake --build build --config MinSizeRel
```

## Usage

```bash
setcap-static capabilities filename
```

* `capabilities` is the list of capabilities in the form supported by [`cap_from_text(3)`](https://linux.die.net/man/3/cap_from_text) (or by `setcap`)
* `filename` is the name of the file to operate on; it must not refer to a symlink.