diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7928ca4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +# Override for Makefile +[{Makefile, makefile, GNUmakefile}] +indent_style = tab +indent_size = 4 + +[Makefile.*] +indent_style = tab +indent_size = 4 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff369ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.prof + +.idea +*.iml + +.vagrant + +github-pam + +glide.lock +.glide +vendor/ + +release/ +build-harness/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a51522a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,45 @@ +sudo: required +language: go +go: + - 1.7.x +addons: + apt: + packages: + - git + - make + - curl + +install: +- make init +- make go:deps-build +- make go:deps-dev + +script: +- make go:deps +- make go:test +- make go:lint +- make go:build-all + +deploy: + provider: releases + api_key: $GITHUB_OAUTH_TOKEN + file: + - release/$APP_darwin_386 + - release/$APP_darwin_amd64 + - release/$APP_freebsd_386 + - release/$APP_freebsd_amd64 + - release/$APP_freebsd_arm + - release/$APP_linux_386 + - release/$APP_linux_amd64 + - release/$APP_linux_arm + - release/$APP_netbsd_386 + - release/$APP_netbsd_amd64 + - release/$APP_netbsd_arm + - release/$APP_openbsd_386 + - release/$APP_openbsd_amd64 + - release/$APP_windows_386.exe + - release/$APP_windows_amd64.exe + skip_cleanup: true + on: + tags: true + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4421819 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +COPYRIGHT_SOFTWARE:=Sudo Shell +COPYRIGHT_SOFTWARE_DESCRIPTION:=Sudo Shell provides a login shell that can be used to audit sessions + +PATH:=$(PATH):$(GOPATH)/bin + +include $(shell curl --silent -O "https://raw.githubusercontent.com/cloudposse/build-harness/master/templates/Makefile.build-harness"; echo Makefile.build-harness) +- make travis:docker-tag-and-push + +setup: + make init go:deps-build go:deps-dev go:deps go:lint + +build: + make go:build diff --git a/README.md b/README.md index a1f6628..50affd5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,80 @@ -# sudosh -Shell wrapper to run a login shell with `sudo` for the purpose of audit logging +# Sudo Shell + +Sudo Shell is a wrapper to run a login shell with `sudo` for the purpose of session audit logging. + +[![Build Status](https://travis-ci.org/cloudposse/sudosh.svg?branch=master)](https://travis-ci.org/cloudposse/sudosh) +[![GitHub Stars](https://img.shields.io/github/stars/cloudposse/sudosh.svg)](https://github.com/cloudposse/sudosh/stargazers) +[![GitHub Issues](https://img.shields.io/github/issues/cloudposse/sudosh.svg)](https://github.com/cloudposse/sudosh/issues) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/cloudposse/sudosh.svg)](http://isitmaintained.com/project/cloudposse/sudosh "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/cloudposse/sudosh.svg)](http://isitmaintained.com/project/cloudposse/sudosh "Percentage of issues still open") +[![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](https://github.com/cloudposse/sudosh/pulls) +[![License](https://img.shields.io/badge/license-APACHE%202.0%20-brightgreen.svg)](https://github.com/cloudposse/sudosh/blob/master/LICENSE) + + +## Purpose + +The `sudo` command provides built-in session logging. Combined with [`sudoreplay`](https://www.sudo.ws/man/1.8.13/sudoreplay.man.html) it provides an easy way to review session logs on a [bastion](https://github.com/cloudposse/bastion/) host. When used as a system login shell, it will force session logging. + +[Another common pattern](https://aws.amazon.com/blogs/security/how-to-record-ssh-sessions-established-through-a-bastion-host/) is to use the OpenSSH `ForceCommand` directive in `sshd_config` combined with the `script` command to log sessions. This is ineffective because the [user can easily bypass](http://serverfault.com/a/639814) it. Using `sudosh` provides a more secure alternative that cannot be bypassed since it does not depend on `ForceCommand`. + +## Usage + +Here's how to use it in 3 easy steps. Checkout the [precompiled releases](https://github.com/cloudposse/sudosh/releases) if you don't want to build it yourself.. + +1. Enable `sudo` logging. Edit `/etc/sudoers.d/audit-logs`: + + ``` + Defaults log_output + Defaults!/usr/bin/sudoreplay !log_output + Defaults!/sbin/reboot !log_output + ``` + +2. Add this command to `/etc/shells`: + + ``` + /usr/bin/sudosh + ``` + + **Tip**: to prevent users from using other shells to login, remove those shells from `/etc/shells`. + + +3. Update the user `foobar` to use the `sudosh` shell. + + ``` + chsh -s /usr/bin/sudosh foobar + ``` + + +## Other Tricks + +If you want to change the default shell from `bash` to something else (e.g. `zsh`), you can symlink `sudosh` to a different name. + +To change the default shell to `zsh`, you could do: + +``` +ln -s /usr/bin/sudosh /usr/bin/sudosh.zsh +``` + +Then set the user's shell to `/usr/bin/sudosh.zsh` and add the shell to `/etc/shells`. + +## About + + +The `sudosh` utility is maintained and funded by [Cloud Posse, LLC][website]. Like it? Please let us know at + +We love Open Source Software! + +See [our other projects][community] +or [hire us][hire] to help build your next cloud-platform. + + [website]: https://cloudposse.com/ + [community]: https://github.com/cloudposse/ + [hire]: https://cloudposse.com/contact/ + +### Contributors + +[![Erik Osterman](http://s.gravatar.com/avatar/88c480d4f73b813904e00a5695a454cb?s=144)](https://osterman.com) + +[Erik Osterman](https://github.com/osterman) + + diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..e69de29 diff --git a/main.go b/main.go new file mode 100644 index 0000000..2ac2747 --- /dev/null +++ b/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "log" + "os" + "os/exec" + "os/user" + "path/filepath" + "runtime" + "strings" + "syscall" +) + +func init() { + // make sure we only have one process and that it runs on the main thread + // (so that ideally, when we Exec, we keep our user switches and stuff) + runtime.GOMAXPROCS(1) + runtime.LockOSThread() +} + +func main() { + log.SetFlags(0) // no timestamps on our logs + + // Args that we pass to sudo + var args []string + + var shell string + var ext string + + // The command that was executed + cmd := os.Args[0] + + ext = strings.TrimLeft(filepath.Ext(cmd), ".") + // If no extension, default to bash + if ext == "" { + ext = "bash" + } + + // Resolve extension to a shell + shellFound, shellPathErr := exec.LookPath(ext) + if shellPathErr != nil { + log.Fatalf("error: find to find shell %v: %v", ext, shellPathErr) + } + shell = shellFound + + // Shell is always launched as current user + user, userErr := user.Current() + if userErr != nil { + log.Fatalf("error: unable to determine current user: %v", userErr) + } + + // Fetch environment + env := os.Environ() + + // Lookup path for `sudo` + binary, lookPathErr := exec.LookPath("sudo") + if lookPathErr != nil { + log.Fatalf("error: find to find sudo: %v", lookPathErr) + } + + // Prepare `sudo` args + if len(os.Args) <= 2 { + args = []string{"sudo", "-u", user.Username, "-s", shell, "-l"} + } else { + args = append([]string{"sudo", "-u", user.Username, "--"}, os.Args[1:]...) + } + + execErr := syscall.Exec(binary, args, env) + if execErr != nil { + log.Fatalf("error: exec failed: %v", execErr) + } +}