From 257baa1d46f927a4356b05b2f39b56b39159f6f2 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 27 Feb 2022 19:17:04 -0500 Subject: [PATCH] refactor: run crontab under sudo to support rhel systems RHEL blocks users from executing crontab as themselves Because Of Reasons so we need to use sudo instead. Also refactors file writing to properly support writing the permissions on sudoers files going forward. Closes #5020 --- docker/etc/sudoers.d/dokku-docker | 2 +- plugins/common/io.go | 23 ++++++++++++++++++++--- plugins/cron/.gitignore | 1 + plugins/cron/Makefile | 2 +- plugins/cron/functions.go | 6 +++--- plugins/cron/src/triggers/triggers.go | 2 ++ plugins/cron/triggers.go | 21 +++++++++++++++++++++ tests/unit/cron.bats | 7 +++++++ 8 files changed, 56 insertions(+), 8 deletions(-) diff --git a/docker/etc/sudoers.d/dokku-docker b/docker/etc/sudoers.d/dokku-docker index ae9a60f3f..8150da4a5 100644 --- a/docker/etc/sudoers.d/dokku-docker +++ b/docker/etc/sudoers.d/dokku-docker @@ -1 +1 @@ -dokku ALL=NOPASSWD:SETENV:/usr/bin/docker,/usr/bin/docker-image-labeler,/usr/bin/pack +dokku ALL=NOPASSWD:SETENV:/usr/bin/docker,/usr/bin/docker-image-labeler,/usr/bin/pack,/usr/bin/crontab diff --git a/plugins/common/io.go b/plugins/common/io.go index 1423bdb32..75d878ac2 100644 --- a/plugins/common/io.go +++ b/plugins/common/io.go @@ -183,6 +183,10 @@ func SetPermissions(path string, fileMode os.FileMode) error { systemGroup := GetenvWithDefault("DOKKU_SYSTEM_GROUP", "dokku") systemUser := GetenvWithDefault("DOKKU_SYSTEM_USER", "dokku") + if strings.HasPrefix("/etc/sudoers.d/", path) { + systemGroup = "root" + systemUser = "root" + } group, err := user.LookupGroup(systemGroup) if err != nil { @@ -207,7 +211,20 @@ func SetPermissions(path string, fileMode os.FileMode) error { // WriteSliceToFile writes a slice of strings to a file func WriteSliceToFile(filename string, lines []string) error { - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + mode := os.FileMode(0600) + if strings.HasPrefix("/etc/sudoers.d/", filename) { + // sudoers files should be either 0600 (rhel) or 0440 (debian) + defaultMode := map[string]bool{ + "centos": true, + "fedora": true, + "rhel": true, + } + if !defaultMode[os.Getenv("DOKKU_DISTRO")] { + mode = os.FileMode(0440) + } + } + + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode) if err != nil { return err } @@ -220,8 +237,8 @@ func WriteSliceToFile(filename string, lines []string) error { return err } - file.Chmod(0600) - SetPermissions(filename, 0600) + file.Chmod(mode) + SetPermissions(filename, mode) return nil } diff --git a/plugins/cron/.gitignore b/plugins/cron/.gitignore index 6be9e9546..949e5b1cd 100644 --- a/plugins/cron/.gitignore +++ b/plugins/cron/.gitignore @@ -3,5 +3,6 @@ /triggers/* /triggers /cron-* +/install /post-* /report diff --git a/plugins/cron/Makefile b/plugins/cron/Makefile index 5f3310f57..e0d6a5320 100644 --- a/plugins/cron/Makefile +++ b/plugins/cron/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/list subcommands/report -TRIGGERS = triggers/cron-write triggers/post-delete triggers/post-deploy triggers/report +TRIGGERS = triggers/cron-write triggers/install triggers/post-delete triggers/post-deploy triggers/report BUILD = commands subcommands triggers PLUGIN_NAME = cron diff --git a/plugins/cron/functions.go b/plugins/cron/functions.go index ebdd7fb61..a3733d0b7 100644 --- a/plugins/cron/functions.go +++ b/plugins/cron/functions.go @@ -81,13 +81,13 @@ func fetchCronEntries(appName string) ([]templateCommand, error) { } func deleteCrontab() error { - command := common.NewShellCmd("crontab -l -u dokku") + command := common.NewShellCmd("sudo /usr/bin/crontab -l -u dokku") command.ShowOutput = false if !command.Execute() { return nil } - command = common.NewShellCmd("crontab -r -u dokku") + command = common.NewShellCmd("sudo /usr/bin/crontab -r -u dokku") command.ShowOutput = false out, err := command.CombinedOutput() if err != nil { @@ -163,7 +163,7 @@ func writeCronEntries() error { return fmt.Errorf("Unable to template out schedule file: %v", err) } - command := common.NewShellCmd(fmt.Sprintf("crontab -u dokku %s", tmpFile.Name())) + command := common.NewShellCmd(fmt.Sprintf("sudo /usr/bin/crontab -u dokku %s", tmpFile.Name())) command.ShowOutput = false out, err := command.CombinedOutput() if err != nil { diff --git a/plugins/cron/src/triggers/triggers.go b/plugins/cron/src/triggers/triggers.go index f48bdc58d..43ae583f7 100644 --- a/plugins/cron/src/triggers/triggers.go +++ b/plugins/cron/src/triggers/triggers.go @@ -20,6 +20,8 @@ func main() { switch trigger { case "cron-write": err = cron.TriggerCronWrite() + case "install": + err = cron.TriggerInstall() case "post-delete": err = cron.TriggerPostDelete() case "post-deploy": diff --git a/plugins/cron/triggers.go b/plugins/cron/triggers.go index 74c18b19d..407c528ca 100644 --- a/plugins/cron/triggers.go +++ b/plugins/cron/triggers.go @@ -1,5 +1,26 @@ package cron +import ( + "os" + + "github.com/dokku/dokku/plugins/common" +) + +// TriggerInstall installs a sudoers file so we can execute crontab via sudo +func TriggerInstall() error { + lines := []string{"%dokku ALL=(ALL) NOPASSWD:/usr/bin/crontab"} + notty := map[string]bool{ + "centos": true, + "fedora": true, + "rhel": true, + } + if notty[os.Getenv("DOKKU_DISTRO")] { + lines = append(lines, "Defaults:dokku !requiretty") + } + + return common.WriteSliceToFile("/etc/sudoers.d/dokku-cron", lines) +} + // TriggerPostDelete updates the cron entries for all apps func TriggerPostDelete() error { return writeCronEntries() diff --git a/tests/unit/cron.bats b/tests/unit/cron.bats index 4595cf3b2..0a0125bc9 100644 --- a/tests/unit/cron.bats +++ b/tests/unit/cron.bats @@ -37,6 +37,13 @@ teardown() { assert_output "$help_output" } +@test "(cron) installed" { + run /bin/bash -c "test -f /etc/sudoers.d/dokku-cron" + echo "output: $output" + echo "status: $status" + assert_success +} + @test "(cron) invalid [missing-keys]" { run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_invalid echo "output: $output"