6

On OpenBSD and with OpenBSD's cron and crontab, is it possible to store the crontab(5) of a user in a git repository of the same user?

What would be the proper way to accomplish something like this?

(To steer the answer into the correct direction, I would not be opposed to changing some permissions in the system, although I'd rather not have to recompile the binaries, nor violate any good security paradigms.)

  • I can't speak for OpenBSD, but every other Unix I know stores the user crontabs centrally; on the assumption that OpenBSD's stock cron does not support git natively, and given that you don't want to recompile binaries, it seems to me that you're stuck having to store copies of user crontabs in git, knowing that they could stray from the actual crontabs. – Jeff Schaller Sep 13 '15 at 20:41
  • @JeffSchaller, so, is it not allowed to have symlinks or some such? the reason I'm asking at all is that the cron directory's permissions are set in such a way that the users themselves cannot access their own crontabs through the filesystem. – cnst Sep 13 '15 at 21:20
  • Based on my understanding of cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr.sbin/cron/… it looks to me like crontab(1) wants to manage that central file itself. – Jeff Schaller Sep 13 '15 at 22:05
6

All users' crontabs are stored in a single directory, and users can't access that directory directly, they need to use the privileged command crontab.

Instead of storing the actual crontab file in version control, write a commit hook that runs crontab to push the latest version.

crontab "$HOSTNAME.crontab"

The simplest hook would be a post-commit hook. Run git rev-parse --abbrev-ref HEAD to find the current branch and git show --format=format: --name-status HEAD.

#!/bin/sh
commit=$(git rev-parse HEAD)
branch=$(git rev-parse --name-status "$commit")
git show --format=format: --name-status "$commit" |
while read -r status filename; do
  if [ "$branch" = "master" ] &&
     [ "$status" = "A" -o "$status" = "M" ] &&
     [ "$filename" = "crontabs/$HOSTNAME.crontab" ]; then
    crontab "$filename"
  fi
done

This doesn't handle merges or rebases, and doesn't register anything in the history if crontab fails. There's a bit of a paradigm clash here since git fundamentally has multiple branches but there's only a single crontab on a given machine at any given time. For added robustness, you might prefer to have a dedicated branch for live crontabs, and merge to that branch when you change the crontab file on your working branch.

  • Great idea! Do you by any chance have any advice of what's going to happen if there are any errors present? E.g., how would you make sure that the file is syntactically correct without doing a commit (ideally either to git or to crontab), or to make sure git doesn't commit if the file is rejected by crontab? Ideally, it'd be great if all of this was sync'ed such that the commit time could make it relatively easy to determine (in most cases apart from the race conditions) whether or certain jobs were running at certain times. Also, wouldn't your example cause all entries be appended? – cnst Sep 14 '15 at 0:36
  • @cnst I'm afraid OpenBSD crontab doesn't have a verify option. You could fake it with a non-setuid copy of /usr/bin/crontab. The crontab is completely rewritten each time, there's no appending; any entries modified outside of git are lost. If you want to detect modifications outside of git, you could compare the previous content with the git history, but this will be difficult to track if you change branches. – Gilles Sep 14 '15 at 7:49
  • Are you sure that all modifications would be lost? Wouldn't the second parameter to VISUAL be the filename of the temporary file with the present version of crontab? E.g., wouldn't cat append the two files? – cnst Sep 14 '15 at 8:30
  • @cnst Oh, right, sorry. That's not the command I'd meant to write. Of course you can change the command if you do want to merge in some way. – Gilles Sep 14 '15 at 8:55
  • @Jasen I hadn't noticed this was supported on OpenBSD, thanks. – Gilles Sep 14 '15 at 10:10
2

Complicated, as cron looks for crontab files under a single directory, which would be unsuitable for per-user git repositories. I suppose you could replace /usr/bin/crontab with code that, in addition to emulating crontab(1), figures out who the user is, retrieves-or-creates their git-stored cron data, commits any changes, then makes that data available to cron(8), either by calling the original crontab(1) (due to the setgid bit), or being setgid itself (danger danger security warning!). System updates would also be complicated, as during an upgrade one would need to let the updated /usr/bin/crontab be installed, then move that aside to install your git-enabled wrapper (and likewise for any patches that touch crontab).

Users who figured out where the original crontab(1) is would be able to bypass the git stuff; to avoid that, your implementation would have to be setgid crontab, which may open the door to arbitrary-overwrite-of-any-users-crontab-file security problems or in other words a great way to allow total system compromise (attacker writes to root crontab file, they win!) should your code contain any security problems.

Also complicated is if the user figures out where their per-cron git repository is, then screws around with that; if that's a concern, those would need to be owned by some other user, and the custom crontab(5) client would then talk to some daemon that does have rights to those repositories (e.g. how sshd does privsep).

(rcs may suffice if you merely need to track changes over time, and offers less rope to hang yourself with than git.)

  • I think you're completely missing my point, and even the whole idea behind it. The whole purpose is to have the USER be able to easily version THEIR OWN crontab in THEIR OWN git repository. How is any kind of second-user-to-avoid tampering logic applies here? (Besides, if I simply wanted to have a per-user repository without the user having access to it, I could already simply have root maintain it.) Nor am I interested in implementing my own crontab daemon, either, which was already stated in the question. This answer is complete non-sequitur and makes absolutely zero sense. – cnst Sep 13 '15 at 20:18
  • Uh, in that case, just have a hook that pipes some file in a repo over to crontab(1). – thrig Sep 13 '15 at 20:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.