Skip to content

Commit

Permalink
exec-file and exec-env subcommands, take two (getsops#532)
Browse files Browse the repository at this point in the history
* first pass: add --exec flag

* fix spacing

* subcommand for exec as well as other bits n bobs

--placeholder to pass files to child procs (similar to `find(1)`'s -exec flag)
--background to background processes if you don't need them to be interactive

* break the 2 execs into 2 subcommands

* add a non-fifo option for people who like files instead

* added a setuid flag just in case

* oups, used the wrong functions

* Update README.rst

* typo

* first attempt at separating out windows/unix functionality

* add the caveat about windows

* windows: make sure --no-fifo is being used and warn when it's not

* stray fixes

* switch to logrus, break out the command builder, and remove /tmp/ default
  • Loading branch information
ancat authored and ajvb committed Sep 24, 2019
1 parent 19cc1bc commit 5663d27
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 68 deletions.
17 changes: 9 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -844,9 +844,9 @@ instead. By default ``sops`` will use a FIFO to pass the contents of the
decrypted file to the new program. Using a FIFO, secrets are only passed in
memory which has two benefits: the plaintext secrets never touch the disk, and
the child process can only read the secrets once. In contexts where this won't
work, such as platforms where FIFOs are not available or secret files need to be
available to the child process longer term, the ``--no-fifo`` flag can be used
to instruct ``sops`` to use a traditional temporary file that will get cleaned
work, eg platforms like Windows where FIFOs unavailable or secret files that need
to be available to the child process longer term, the ``--no-fifo`` flag can be
used to instruct ``sops`` to use a traditional temporary file that will get cleaned
up once the process is finished executing. ``exec-file`` behaves similar to
``find(1)`` in that ``{}`` is used as a placeholder in the command which will be
substituted with the temporary file path (whether a FIFO or an actual file).
Expand Down Expand Up @@ -879,11 +879,12 @@ substituted with the temporary file path (whether a FIFO or an actual file).
$ cat /tmp/.sops506055069/tmp-file291138648
cat: /tmp/.sops506055069/tmp-file291138648: No such file or directory
Additionally, both ``exec-env`` and ``exec-file`` support dropping privileges
before executing the new program via the ``--user <username>`` flag. This is
particularly useful in cases where the encrypted file is only readable by root,
but the target program does not need root privileges to function. This flag
should be used where possible for added security.
Additionally, on unix-like platforms, both ``exec-env`` and ``exec-file``
support dropping privileges before executing the new program via the
``--user <username>`` flag. This is particularly useful in cases where the
encrypted file is only readable by root, but the target program does not
need root privileges to function. This flag should be used where possible
for added security.
.. code:: bash
Expand Down
76 changes: 16 additions & 60 deletions cmd/sops/subcommand/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ package exec

import (
"bytes"
"log"
"io/ioutil"
"syscall"
"path/filepath"
"os"
"os/exec"
"os/user"
"strconv"
"runtime"
"strings"

"go.mozilla.org/sops/logging"

"github.com/sirupsen/logrus"
)

var log *logrus.Logger

func init() {
log = logging.NewLogger("EXEC")
}

type ExecOpts struct {
Expand All @@ -24,28 +26,6 @@ type ExecOpts struct {
User string
}

func WritePipe(pipe string, contents []byte) {
handle, err := os.OpenFile(pipe, os.O_WRONLY, 0600)

if err != nil {
os.Remove(pipe)
log.Fatal(err)
}

handle.Write(contents)
handle.Close()
}

func GetPipe(dir string) string {
tmpfn := filepath.Join(dir, "tmp-file")
err := syscall.Mkfifo(tmpfn, 0600)
if err != nil {
log.Fatal(err)
}

return tmpfn
}

func GetFile(dir string) *os.File {
handle, err := ioutil.TempFile(dir, "tmp-file")
if err != nil {
Expand All @@ -54,41 +34,17 @@ func GetFile(dir string) *os.File {
return handle
}

func SwitchUser(username string) {
user, err := user.Lookup(username)
if err != nil {
log.Fatal(err)
}

uid, _ := strconv.Atoi(user.Uid)

err = syscall.Setgid(uid)
if err != nil {
log.Fatal(err)
}

err = syscall.Setuid(uid)
if err != nil {
log.Fatal(err)
}

err = syscall.Setreuid(uid, uid)
if err != nil {
log.Fatal(err)
}

err = syscall.Setregid(uid, uid)
if err != nil {
log.Fatal(err)
}
}

func ExecWithFile(opts ExecOpts) {
if opts.User != "" {
SwitchUser(opts.User)
}

dir, err := ioutil.TempDir("/tmp/", ".sops")
if runtime.GOOS == "windows" && opts.Fifo {
log.Warn("no fifos on windows, use --no-fifo next time")
opts.Fifo = false
}

dir, err := ioutil.TempDir("", ".sops")
if err != nil {
log.Fatal(err)
}
Expand All @@ -108,7 +64,7 @@ func ExecWithFile(opts ExecOpts) {
}

placeholdered := strings.Replace(opts.Command, "{}", filename, -1)
cmd := exec.Command("/bin/sh", "-c", placeholdered)
cmd := BuildCommand(placeholdered)
cmd.Env = os.Environ()

if opts.Background {
Expand Down Expand Up @@ -138,7 +94,7 @@ func ExecWithEnv(opts ExecOpts) {
env = append(env, string(line))
}

cmd := exec.Command("/bin/sh", "-c", opts.Command)
cmd := BuildCommand(opts.Command)
cmd.Env = env

if opts.Background {
Expand Down
67 changes: 67 additions & 0 deletions cmd/sops/subcommand/exec/exec_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// +build !windows

package exec

import (
"syscall"
"path/filepath"
"os"
"os/user"
"os/exec"
"strconv"
)

func BuildCommand(command string) *exec.Cmd {
return exec.Command("/bin/sh", "-c", command)
}

func WritePipe(pipe string, contents []byte) {
handle, err := os.OpenFile(pipe, os.O_WRONLY, 0600)

if err != nil {
os.Remove(pipe)
log.Fatal(err)
}

handle.Write(contents)
handle.Close()
}

func GetPipe(dir string) string {
tmpfn := filepath.Join(dir, "tmp-file")
err := syscall.Mkfifo(tmpfn, 0600)
if err != nil {
log.Fatal(err)
}

return tmpfn
}

func SwitchUser(username string) {
user, err := user.Lookup(username)
if err != nil {
log.Fatal(err)
}

uid, _ := strconv.Atoi(user.Uid)

err = syscall.Setgid(uid)
if err != nil {
log.Fatal(err)
}

err = syscall.Setuid(uid)
if err != nil {
log.Fatal(err)
}

err = syscall.Setreuid(uid, uid)
if err != nil {
log.Fatal(err)
}

err = syscall.Setregid(uid, uid)
if err != nil {
log.Fatal(err)
}
}
22 changes: 22 additions & 0 deletions cmd/sops/subcommand/exec/exec_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package exec

import (
"os/exec"
)

func BuildCommand(command string) *exec.Cmd {
return exec.Command("cmd.exe", "/C", command)
}

func WritePipe(pipe string, contents []byte) {
log.Fatal("fifos are not available on windows")
}

func GetPipe(dir string) string {
log.Fatal("fifos are not available on windows")
return ""
}

func SwitchUser(username string) {
log.Fatal("user switching not available on windows")
}

0 comments on commit 5663d27

Please sign in to comment.