macula_owner_only_file (macula v11.4.0)
View SourceFiles only their owner can read, written atomically.
For the secrets macula keeps on disk: identity keys, the distribution cookie and the certificates it generates.
write/2 never lets another user of the host see the content and never writes through a symlink. A missing parent directory is created, the innermost one with mode 0700; an existing parent is used as it is. The content goes into a new file inside a new private directory next to the target. That directory is restricted to mode 0700 before the file is created, because a file starts with the default mode and a descriptor another user opens before a chmod keeps reading after it. The file is restricted to 0600 before any content is written, synced, and renamed over the target, which replaces a symlink at the target instead of writing through it. The private directory is removed afterwards, and on any error the file with it.
read/1 follows symlinks and accepts a regular file that gives its group and others no access, 0600 or 0400, as Erlang's own cookie check does, and that belongs to the user the node runs as (macula_node_user), so another user of the host cannot give the node a secret of their own. A host without user ids skips the owner check. A path that is not a regular file is refused without being opened, and the mode and the owner are checked on the opened handle, so the file checked is the file read.
A refusal names the file, what was found and what is required: the mode as an octal binary in file_permissions, the owner's user id and the node's in file_owner, the file type in file_type.
Summary
Functions
Read Path when it is a regular file its group and others have no access to, following symlinks.
Write Content to Path so that only its owner can read it, replacing whatever is at Path. On an error nothing is left behind and Path is unchanged.
Types
-type file_error() :: file:posix() | badarg | terminated | system_limit.
-type refusal() :: {file_permissions, #{file := file:name_all(), mode := binary(), required := binary()}} | {file_owner, #{file := file:name_all(), owner := non_neg_integer(), required := non_neg_integer()}} | {file_type, #{file := file:name_all(), type := atom(), required := regular}}.
Functions
-spec read(file:name_all()) -> {ok, binary()} | {error, refusal() | file_error()}.
Read Path when it is a regular file its group and others have no access to, following symlinks.
-spec write(file:name_all(), iodata()) -> ok | {error, file_error()}.
Write Content to Path so that only its owner can read it, replacing whatever is at Path. On an error nothing is left behind and Path is unchanged.