-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Prepare the repository for open-sourcing
- Loading branch information
Showing
20 changed files
with
747 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
language: rust | ||
|
||
rust: | ||
- stable | ||
- beta | ||
- nightly | ||
|
||
script: | ||
- cargo build | ||
- cargo test | ||
- 'if [[ "${TRAVIS_RUST_VERSION}" = "nightly" ]]; then cargo install --force clippy; fi' | ||
- 'if [[ "${TRAVIS_RUST_VERSION}" = "nightly" ]]; then cargo clippy; fi' | ||
|
||
cache: cargo | ||
|
||
matrix: | ||
allow_failures: | ||
- rust: nightly | ||
fast_finish: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Serious about security | ||
====================== | ||
|
||
Square recognizes the important contributions the security research community | ||
can make. We therefore encourage reporting security issues with the code | ||
contained in this repository. | ||
|
||
If you believe you have discovered a security vulnerability, please follow the | ||
guidelines at <https://bugcrowd.com/squareopensource>. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
sudo_pair | ||
========= | ||
|
||
[](https://travis-ci.org/square/sudo_pair) | ||
|
||
`sudo_pair` is a [plugin for sudo][sudo_plugin_man] that requires another | ||
human to approve and monitor privileged sudo sessions. | ||
|
||
<p align="center"> | ||
<img width="982" alt="a demonstrated sudo_pair session" src="https://raw.githubusercontent.com/square/sudo_pair/master/demo.gif?token=AAAQ8nqdmjg9ZdBK3dGwl5plM_3IagRVks5a9dxmwA%3D%3D"> | ||
</p> | ||
|
||
## About | ||
|
||
`sudo` is used by engineers daily to run commands as privileged users. | ||
But on some sensitive systems, you really want to ensure that no | ||
individual can act entirely autonomously. At Square, this includes | ||
applications that manage our internal access-control systems, store | ||
accounting ledgers, or even move around real money. This plugin allows | ||
us to ensure that no user can act entirely on their own authority within | ||
these systems. | ||
|
||
This plugin and its components are still in prerelease, as we want to | ||
get feedback from the open-source community before officially releasing | ||
1.0. | ||
|
||
## Installation | ||
|
||
### WARNING: Misconfiguring sudo can lock you out of your machine. Test this in a throwaway environment. | ||
|
||
For now, `sudo_pair` must be compiled from source. It is a standard | ||
Rust project, and the following should suffice to build it on any recent | ||
version of Rust: | ||
|
||
```sh | ||
git clone https://github.com/square/sudo_pair.git | ||
cd sudo_pair | ||
cargo build --release | ||
``` | ||
|
||
Once built, the plugin itself will need to be installed in a place where | ||
`sudo` can find it. Generally this is under `/usr/libexec/sudo` (on | ||
macOS hosts it's `/usr/local/libexec/sudo`). An appropriate approval | ||
script must be installed into the `PATH`. A directory must be created | ||
for `sudo_pair` to manage the sockets it uses for communication between | ||
plugin and client. And finally, `sudo` must be configured to load and | ||
use the plugin. | ||
|
||
```sh | ||
# WARNING: these files may not be set up in a way that is suitable | ||
# for your system. Proceed only on a throwaway host. | ||
|
||
# install the plugin shared library | ||
install -o root -g root -m 0644 ./target/release/libsudopair.dylib /usr/libexec/sudo | ||
|
||
# create a socket directory | ||
install -o root -g root -m 0644 -d /var/run/sudo_pair | ||
|
||
# install the approval script; as currently configured, it denies access | ||
# to users approving their own sudo session and may lock you out | ||
install -o root -g root -m 0755 ./sample/bin/sudo_approve /usr/bin/sudo_approve | ||
|
||
# your `/etc/sudo.conf` may already have entries necessary for sudo to | ||
# function correctly; if this is the case, the two files will need to be | ||
# merged | ||
install -o root -g root -m 0644 ./sample/etc/sudo.conf /etc/sudo.conf | ||
``` | ||
|
||
## Configuration | ||
|
||
The plugin can be provided several options to modify its behavior. These | ||
options are provided to the plugin by adding them to the end of the | ||
`Plugin` line in `/etc/sudo.conf`. | ||
|
||
Example: | ||
|
||
``` | ||
Plugin sudo_pair sudo_pair.so socket_dir=/var/tmp/sudo_pair gids_exempted=42,109 | ||
``` | ||
|
||
The full list of options are as follows: | ||
|
||
* `binary_path` (default: `/usr/bin/sudo_approve`) | ||
|
||
This is the location of the approval binary. The approval command itself needs to run under the privileges of the destination user or group, and this is done so using sudo, so it must be exempted from requiring its own pair approval. | ||
|
||
* `user_prompt_path` (default: `/etc/sudo_pair.prompt.user`) | ||
|
||
This is the location of the prompt template to display to the user invoking sudo; if no template is found at this location, an extremely minimal default will be printed. See the [Prompts][#prompts] section for more details. | ||
|
||
* `pair_prompt_path` (default: `/etc/sudo_pair.prompt.pair`) | ||
|
||
This is the location of the prompt template to display to the user being asked to approve the sudo session; if no template is found at this location, an extremely minimal default will be printed. See the [Prompts][#prompts] section for more details. | ||
|
||
* `socket_dir` (default: `/var/run/sudo_pair`) | ||
|
||
This is the path where this plugin will store sockets for sessions that are pending approval. This directory must be owned by root and only writable by root, or the plugin will abort. | ||
|
||
* `gids_enforced` (default: `0`) | ||
|
||
This is a comma-separated list of gids that sudo_pair will gate access to. If a user is `sudo`ing to a user that is a member of one of these groups, they will be required to have a pair approve their session. | ||
|
||
* `gids_exempted` (default: none) | ||
|
||
This is a comma-separated list of gids whose users will be exempted from the requirements of sudo_pair. Note that this is not the opposite of the `gids_enforced` flag. Whereas `gids_enforced` gates access *to* groups, `gids_exempted` exempts users sudoing *from* groups. For instance, this setting can be used to ensure that oncall sysadmins can respond to outages without needing to find a pair. | ||
|
||
Note that root is *always* exempt. | ||
|
||
## Prompts | ||
|
||
This plugin allows you to configure the prompts that are displayed to | ||
both users being asked to find a pair and users being asked to approve | ||
another user's `sudo` session. If prompts aren't | ||
[configured](#configuration) (or can't be found on the filesystem), | ||
extremely minimal ones are provided as a default. | ||
|
||
The contents of the prompt files are raw bytes that should be printed to | ||
the user's terminal. This allows fun things like terminal processing of | ||
ANSI escape codes for coloration, resizing terminals, and setting window | ||
titles, all of which are (ab)used in the sample prompts provided. | ||
|
||
These prompts also [implement](src/template.rs) a simple `%`-escaped | ||
templating language. Any known directive preceded by a `%` character is | ||
replaced by an expansion, and anything else is treated as a literal | ||
(e.g., `%%` is a literal `%`, and `%a` is a literal `a`). | ||
|
||
Available expansions: | ||
|
||
* `%b`: the name of the appoval _b_inary | ||
* `%B`: the full path to the approval _B_inary | ||
* `%C`: the full _C_ommand `sudo` was invoked as (recreated as best-effort) | ||
* `%d`: the cw_d_ of the command being run under `sudo` | ||
* `%h`: the _h_ostname of the machine `sudo` is being executed on | ||
* `%H`: the _H_eight of the invoking user's terminal, in rows | ||
* `%g`: the real _g_id of the user invoking `sudo` | ||
* `%p`: the _p_id of this `sudo` process | ||
* `%u`: the real _u_id of the user invoking `sudo` | ||
* `%U`: the _U_sername of the user running `sudo` | ||
* `%W`: the _W_idth of the invoking user's terminal, in columns | ||
|
||
## Approval Scripts | ||
|
||
The [provided approval script](sample/bin/sudo_approve) is just a small | ||
(but complete) example. As much functionality as possible has been moved | ||
into the plugin, with one (important, temporary) exception: currently, | ||
the script must verify that the user approving a `sudo` session is not | ||
the user who is requesting the session. | ||
|
||
Other than that, the only thing required of the "protocol" is to: | ||
|
||
* connect to a socket (as either the user or group being `sudo`ed to) | ||
* wire up the socket's input and output to the user's STDIN and STDOUT | ||
* send a `y` to approve, or anything else to decline | ||
* close the socket to terminate the session | ||
|
||
As it turns out, you can pretty much just do this with `socat`: | ||
|
||
```sh | ||
socat STDIO /path/to/socket | ||
``` | ||
|
||
The script incldued with this project isn't much more than this. It | ||
performs a few extra niceties (implicitly `sudo`s if necessary, turns | ||
off terminal echo, disables Ctrl-C, and kills the session on Ctrl-D), | ||
but not much more. Ctrl-C was disabled so a user who's forgotten that | ||
this is terminal is being used to monitor another user's session doesn't | ||
instinctively kill it with Ctrl-C. | ||
|
||
## Security Model | ||
|
||
This plugin allows users to `sudo -u ${user}` to become a user or | ||
`sudo -g ${group}` to gain an additional group. | ||
|
||
When a user does this, a socket is created that is owned and only | ||
writable by `${user}` (or `${group}`). To approve, that `${user}` (or | ||
someone in the `${group}`) must connect to this socket. The provided | ||
approval script checks if the invoking user has write access to the | ||
socket. If not, it also does a `sudo -u ${user}` (or `sudo -g ${group}`) | ||
and reruns itself (this is exempt from any pairing requirements). | ||
|
||
As a result, the only people who can approve a `sudo` session to a user | ||
or group must *also* be able to `sudo` as that user or group. | ||
|
||
Due to limitations of the POSIX filesystem permission model, a user may | ||
sudo to a new user (and gain its groups) or sudo to a new group | ||
(preserving their current user), but not both simultaneously. | ||
|
||
## Project Layout | ||
|
||
This project is composed of three Rust crates: | ||
|
||
* [`sudo_plugin-sys`](sudo_plugin-sys): raw Rust FFI bindings to the [`sudo_plugin(8)`](sudo_plugin_man) interface | ||
* [`sudo_plugin`](sudo_plugin): a set of Rust structs and macros to simplify writing plugins | ||
* [`sudo_pair`](sudo_pair): the implementation of this plugin | ||
|
||
## Dependencies | ||
|
||
Given the security-sensitive nature of this project, it is an explicit | ||
goal to have a minimal set of dependencies. Currently, those are: | ||
|
||
* [rust-lang/libc](libc) | ||
* [rust-lang-nursery/rust-bindgen](bindgen) | ||
* [rust-lang-nursery/error-chain](error-chain) | ||
|
||
## Contributions | ||
|
||
Contributions are welcome! This project should hopefully be small | ||
(~500loc for the plugin itself, ~1kloc for the wrappers around writing | ||
plugins) and well-documented enough for others to participate without | ||
difficulty. | ||
|
||
Pick a [TODO](sudo_pair/src/lib.rs) and get started! | ||
|
||
## License | ||
|
||
`sudo_pair` is distributed under the terms of both the Apache License | ||
(Version 2.0). | ||
|
||
See [LICENSE-APACHE](LICENSE-APACHE) for details. | ||
|
||
[sudo_plugin_man]: https://www.sudo.ws/man/1.8.22/sudo_plugin.man.html | ||
[libc]: https://github.com/rust-lang/libc | ||
[bindgen]: https://github.com/rust-lang-nursery/rust-bindgen | ||
[error-chain]: https://github.com/rust-lang-nursery/error-chain | ||
[cla]: https://docs.google.com/forms/d/e/1FAIpQLSeRVQ35-gq2vdSxD1kdh7CJwRdjmUA0EZ9gRXaWYoUeKPZEQQ/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
#!/usr/bin/env bash | ||
# | ||
# Copyright 2018 Square Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
# implied. See the License for the specific language governing | ||
# permissions and limitations under the License. | ||
|
||
set -o errexit # quit on first error | ||
set -o pipefail # quit on failures in pipes | ||
set -o nounset # quit on unset variables | ||
|
||
[[ ${TRACE:-} ]] && set -o xtrace # output subcommands if TRACE is set | ||
|
||
declare -r SUDO_SOCKET_PATH="/var/run/sudo_pair" | ||
|
||
pair() { | ||
declare -r socket="${1}" | ||
|
||
# restore TTY settings on exit | ||
# shellcheck disable=SC2064 | ||
trap "stty $(stty -g)" EXIT | ||
|
||
# disable line-buffering and local echo, so the pairer doesn't | ||
# get confused that their typing in the shell isn't doing | ||
# anything | ||
stty cbreak -echo | ||
|
||
# send SIGINT on Ctrl-D | ||
stty intr "^D" | ||
|
||
clear | ||
|
||
# prompt the user to approve | ||
socat STDIO unix-connect:"${socket}" | ||
} | ||
|
||
usage() { | ||
echo "Usage: $(basename -- "$0") uid pid" | ||
exit 1 | ||
} | ||
|
||
main() { | ||
declare -r socket_path="${1}" | ||
declare -ri uid="${2}" | ||
declare -ri pid="${3}" | ||
|
||
# if we're running this under `sudo`, we want to know the original | ||
# user's `uid` from `SUDO_UID`; if not, it's jsut their normal `uid` | ||
declare -i ruid | ||
ruid="${SUDO_UID:-$(id -u)}" | ||
declare -r ruid | ||
|
||
declare -r socket="${socket_path}/${uid}.${pid}.sock" | ||
|
||
declare -i socket_uid socket_gid | ||
socket_uid="$(stat -c '%u' "${socket}")" | ||
socket_gid="$(stat -c '%g' "${socket}")" | ||
declare -r socket_uid socket_gid | ||
|
||
declare socket_user socket_group socket_mode | ||
socket_user="$(getent passwd "${socket_uid}" | cut -d: -f1)" | ||
socket_group="$(getent group "${socket_gid}" | cut -d: -f1)" | ||
socket_mode="$(stat -c '%a' "${socket}")" | ||
declare -r socket_user socket_group socket_mode | ||
|
||
# if the user approving the command is the same as the user who | ||
# invoked `sudo` in the first place, abort | ||
# | ||
# another option would be to allow the session, but log it in a way | ||
# that it immediately pages oncall security engineers; such an | ||
# approach is useful in production systems in that it allows for a | ||
# in-case-of-fire-break-glass workaround so engineers can respond to | ||
# a outage in the middle of the night | ||
# | ||
# this responsibility will be moved into the plugin itself when time | ||
# allots | ||
if [[ "${uid}" -eq "${ruid}" ]]; then | ||
echo "Users may not approve their own sudo session" | ||
exit 1 | ||
fi | ||
|
||
# if we can write: pair | ||
# if user-owner can write: sudo to them and try again | ||
# if group-owner can write: sudo to them and try again | ||
# if none, die | ||
if [ -w "${socket}" ]; then | ||
pair "${socket}" | ||
elif [[ $(( 8#${socket_mode} & 8#200 )) -ne 0 ]]; then | ||
sudo -u "${socket_user}" "${0}" "${uid}" "${pid}" | ||
elif [[ $(( 8#${socket_mode} & 8#020 )) -ne 0 ]]; then | ||
sudo -g "${socket_group}" "${0}" "${uid}" "${pid}" | ||
else | ||
echo "The socket for this sudo session is neither user- nor group-writable." | ||
exit 2 | ||
fi | ||
} | ||
|
||
case "$#" in | ||
2) main "${SUDO_SOCKET_PATH}" "$1" "$2" ;; | ||
*) usage ;; | ||
esac |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# `sudo_pair` is the name of the plugin's entry point inside of the | ||
# shared object and should not be changed. | ||
# | ||
# `sudo_pair.so` is the name of the plugin's shared library, relative | ||
# to sudo's plugin directory (typically `/usr/libexec/sudo` or | ||
# `/usr/local/libexec/sudo`). | ||
# | ||
# Following these two required entries is the list of plugin-specific | ||
# options. These options are documented in the project README. | ||
Plugin sudo_pair sudo_pair.so |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
]0;squdo: <%U@%h:%d $ > %C[8;%H;%WtYou have been asked to approve and monitor the following squdo session: | ||
|
||
<[1m[34m%U[0m@[1m[31m%h[0m:[1m[34m%d[0m [31m$[0m > %C | ||
|
||
Once approved, this terminal will mirror all output from the active squdo session until its completion. | ||
|
||
[1mClosing this terminal, losing your network connection to this host, or explicitly ending the session by typing <Ctrl-D> will cause the command being run under elevated privileges to terminate immediately.[0m | ||
|
||
Approve? y/n [n]: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Due to security and compliance requirements, this `squdo` session will require approval and monitoring from another engineer. When finished, this session will be archived permanently for later retrieval and analysis. | ||
|
||
To continue, another engineer must run: | ||
|
||
ssh -t '[1m[31m%h[0m' 'sh -l -c "%b %u %p"' | ||
|
||
If a suitable engineer is not available and you have an immediate and urgent need to run this command (e.g., a payments outage or other serious system issue), you may run the above command to approve your own session. [1mNote that doing so will immediately page an oncall security engineer, so this capability should only be used in the event of an emergency.[0m |
Oops, something went wrong.