0

I have a Golang binary that runs every 5 mins. It is supposed to create & update a text file which needs to be write restricted. To run this binary I created a systemd service and a systemd timer unit. Systemd service uses a DynamicUser. To achieve access restriction i use CacheDirectory directive in systemd so that only DynamicUser can write that file and it only exists as long as user exists. Also set CacheDirectoryMode=644 to allow only owner with write permissions.

When systemd service runs, it is failing with failed to read output file: lstat /var/cache/monitor/output_file.txt: permission denied>

Question: Although service unit will create a dynamic user & run an executable that creates/updates/reads the file, why that executable itself get Permission Denied when trying to read the file when systemd service runs?

file-monitor.go compiled to produce /usr/local/bin/file-monitor binary

package main

import (
    "fmt"
    "os"
)

function foo() {
    var outputFile = os.Getenv("CACHE_DIRECTORY") + "/output_file.txt"
    
    outputFileBytes, err := os.ReadFile(outputFile)
    if err != nil {
        return fmt.Errorf("failed to read output file %s: %v\n", outputFile, err)
    }
}

function main() {
    foo()
}

file-updater.service

[Unit]
Description="description"
After=file-updater.service

[Service]
DynamicUser=yes
User=monitor
Group=monitor

CacheDirectory=monitor
CacheDirectoryMode=644

ExecStart=/usr/local/bin/file-monitor <arg1>

Type=oneshot

[Install]
WantedBy=multi-user.target
0

1 Answer 1

1
CacheDirectoryMode=644

This allows to read the directory list. Not to interact with a file within this directory list: the eXecute bit is required to further traverse the path and access files within the directory. This makes the write access for user monitor also useless.

Change this parameter into:

CacheDirectoryMode=755

which is the default (ie: you can remove this parameter instead). This now allows to access files within this directory, for reading or for writing for user monitor.

The behavior is linked from systemd's documentation ( RuntimeDirectoryMode=, StateDirectoryMode=, CacheDirectoryMode=, LogsDirectoryMode=, ConfigurationDirectoryMode= ) to the manual for path_resolution(7) which include all the details about basic Unix access, especially in Step 2: walk along the path and in the Permission paragraphs:

If the process does not have search permission on the current lookup directory, an EACCES error is returned ("Permission denied").

Of the three bits used, the first bit determines read permission, the second write permission, and the last execute permission in case of ordinary files, or search permission in case of directories.

7
  • I'm not sure if directory is owned by User=monitor or by the DynamicUser. These aren't the same. Regardless of that, you're right about the 755.
    – Stewart
    Jan 30 at 16:15
  • @Stewart freedesktop.org/software/systemd/man/latest/… . It appears that the user is really dynamic and transcient. I didn't know this was possible before this. I understand that means the user monitor will have a different uid on each run.
    – A.B
    Jan 30 at 16:16
  • indeed that was the problem. Changing to 744 and it worked. @A.B follow up - I want to provide read access to this output file created by monitor user to another (static) user/group that already exists on the host. I tried adding SupplementaryGroups=<OtherGroup> to unit file, but that group user still can't read the file. Is it just not possible for non-Dynamic users to get any access to this path?
    – Korba
    Jan 30 at 17:57
  • @Korba my answer says use 755, not 744. Try again.
    – A.B
    Jan 30 at 17:59
  • For restriction, I guess you need a static group not named monitor to be provided as group. Forget about supplementarygroups. Anyway have it first working before trying again restrictions.
    – A.B
    Jan 30 at 18:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .