Cool things learned while writing a rock image

Last week I had to continue the work that someone else had started on the creation of a Rock image. In particular, the OpenTelemetry Collector image.

It had been a while since I had written a Rock image and this time I wanted to ‘play’ a bit with the permissions and ownership of the configuration and binary files that the image uses.

User who runs the service

This time I wanted the service to be run by a user with as few permissions as possible. This way root and ubuntu users were ruled out.

Fortunately, we can do that easily: The run-user directive let us use the _daemon_ user.

So if we define the service like this:

services:
  otelcol:
    override: replace
    summary: "Entry point for opentelemetry-collector oci-image"
    startup: enabled
    command: "/usr/bin/otelcol --config /etc/otelcol/config.yaml"

run-user: _daemon_

The Pebble and the services are run by the _daemon_ user :muscle: :

_daemon_@76699472ac6f:/$ ps axu | head -n 3
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
_daemon_       1  0.0  0.0 1232928 9696 ?        Ssl  22:46   0:00 /usr/bin/pebble enter
_daemon_      15  0.2  0.5 1336192 80932 ?       Sl   22:46   0:00 /usr/bin/otelcol --config /etc/otelcol/config.yaml

Permissions and Ownership

The OpenTelemetry Collector image has two fundamental files:

  • The binary that Pebble runs: /usr/bin/otelcol
  • The config file: /etc/otelcol/config.yaml

Which I wanted to be owned by _daemon_ as well. I also wanted to set specific permissions for each one:

  • 500 to /usr/bin/otelcol
  • 600 to /etc/otelcol/config.yaml

Permissions

In other rock images we have set permissions like this, as an argument to the install command in the part that build the image:

parts:
  otelcol:
...
    override-build: |
      make build DISTRIBUTIONS="otelcol"
      install -D -m500 distributions/otelcol/_build/otelcol ${CRAFT_PART_INSTALL}/opt/otelcol/otelcol
      install -D -m600 distributions/otelcol/config.yaml ${CRAFT_PART_INSTALL}/opt/otelcol/config.yaml
 
...

This works, but it is an (ugly and) imperative way of doing it. Can’t it be declarative? Sure, the permissions key to the rescue:

parts:
  otelcol:
...
    override-build: |
      make build DISTRIBUTIONS="otelcol"
      install -D distributions/otelcol/_build/otelcol ${CRAFT_PART_INSTALL}/opt/otelcol/otelcol
      install -D distributions/otelcol/config.yaml ${CRAFT_PART_INSTALL}/opt/otelcol/config.yaml
    organize:
      opt/otelcol/otelcol: usr/bin/otelcol
      opt/otelcol/config.yaml: etc/otelcol/config.yaml

    permissions:
        # _daemon_ user has UID/GID = 584792
        # Ref: https://documentation.ubuntu.com/rockcraft/en/stable/reference/rockcraft.yaml/#run-user
      - path: etc/otelcol/config.yaml
        owner: 584792
        group: 584792
        mode: "600"
      - path: usr/bin/otelcol
        owner: 584792
        group: 584792
        mode: "500"

Note that owner and group keys are integers. I’ve set 584792 since it is the _daemon_ UID/GUI. Now you may ask:

Can I assume that always the _daemon_ user will have the 584792 id?

@cjdc has the answer to that!

Yes, that’s precisely the concept behind shared usernames - anywhere, everywhere :stuck_out_tongue: that UID is reserved for that username

You can verify in the container that everything is OK:

_daemon_@164bde59aa53:/$ ls -l /usr/bin/otelcol
-r-x------ 1 _daemon_ _daemon_ 110141592 Jan 13 22:43 /usr/bin/otelcol

_daemon_@164bde59aa53:/$ ls -l /etc/otelcol/config.yaml
-rw------- 1 _daemon_ _daemon_ 1461 Jan 13 22:43 /etc/otelcol/config.yaml
2 Likes

Super nice :slight_smile:

YOU ROCK! a close up of a minion wearing glasses