├── .gitlab-ci.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bintest ├── WARNING.md ├── make_user.sh ├── test_1 │ ├── please.d │ │ ├── 00_ed.ini │ │ └── 00_root.ini │ ├── please.ini │ └── test.sh ├── test_10_file_exists │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_11_backslash │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_11_exact │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_11_exact_name_vs_name │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_11_exact_permit_hostname │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_11_exact_permit_hostname_as_any │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_11_exact_permit_localhost │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_11_exact_prevent_hostname │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_11_exact_prevent_name │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_11_exact_target │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_11_exact_target_vs_target │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_11_internal_backslash │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_11_parameters │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_12_edit_keep_defined │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_12_edit_keep_rule │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_12_edit_mode │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_13_empty_config │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_13_include │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_13_includedir │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_13_includedir_broken_config │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_14_include │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_broken_regex │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_config_read_error │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_different_fs │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_dir_change │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_edit_fail │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_empty_please_ini │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_escaped_arguments │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_file_existence │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_list_reason │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_misc_batch │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_network_dir_umask │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_password_list_network │ ├── .please.ini.swp │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_password_read_network │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_purge_token │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_require_password │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_secure_path_test │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_15_target_edit_as_bob │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_16_command_not_found │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_16_secure_path │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_17_token_timeout_network │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_2 │ ├── please.d │ │ ├── 00_ed.ini │ │ └── 00_root.ini │ ├── please.ini │ └── test.sh ├── test_3_group_rules │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_4_default │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_4_default_no_permit │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_4_default_permit_false │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_4_default_syslog │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_4_edit │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_5_group │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_6_environment │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_6_environment_set │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_7_internal_backslash_as_class │ ├── .test.sh.swp │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_7_internal_backslash_as_hex │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_7_internal_space │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_7_literal_%{USER}_as_class │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_7_literal_%{USER}_as_hex │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_7_no_internal_space │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_8_pleaseedit_env │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_9_env_assign │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_9_pass_env │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── test_9_regex_error_line │ ├── please.d │ │ └── 00_please.ini │ ├── please.ini │ └── test.sh └── test_9_syslog │ ├── please.d │ └── 00_please.ini │ ├── please.ini │ └── test.sh ├── completions ├── bash │ └── please └── zsh │ └── _please ├── examples ├── pam │ ├── debian │ ├── fedora │ ├── macos │ └── suse └── please.ini ├── man ├── please.1 └── please.ini.5 ├── please.ini.md ├── please.md ├── src ├── bin │ ├── please.rs │ └── pleaseedit.rs └── lib.rs └── tests ├── basic_ro.rs ├── defaults.rs ├── exact.rs ├── reason.rs ├── search_path.rs ├── target_group.rs └── tests.rs /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # This file is a template, and might need editing before it works on your project. 2 | # Official language image. Look for the different tagged releases at: 3 | # https://hub.docker.com/r/library/rust/tags/ 4 | image: "rust:latest" 5 | 6 | # Optional: Pick zero or more services to be used on all builds. 7 | # Only needed when using a docker container to run your tests in. 8 | # Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service 9 | # services: 10 | # - mysql:latest 11 | # - redis:latest 12 | # - postgres:latest 13 | 14 | # Optional: Install a C compiler, cmake and git into the container. 15 | # You will often need this when you (or any of your dependencies) depends on C code. 16 | before_script: 17 | - apt-get update -yqq 18 | - apt-get install -yqq --no-install-recommends build-essential libpam0g-dev libpam0g 19 | 20 | # Use cargo to test the project 21 | test:cargo: 22 | script: | 23 | rustc --version && cargo --version # Print version info for debugging 24 | cargo test --workspace --verbose 25 | install -oroot -groot -m4755 target/debug/please target/debug/pleaseedit /usr/bin 26 | ls -al /usr/bin/please /usr/bin/pleaseedit 27 | sh bintest/make_user.sh 28 | set +e 29 | find bintest -type f -name test.sh | sort -rn | while IFS= read -r F; do 30 | echo "$F" 31 | D=`echo "$F" | sed -e 's_/test.sh__g'` 32 | /bin/rm -rf /etc/please.ini /etc/please.d 33 | /bin/cp -R ${D}/please* /etc 34 | sh "${F}" >/tmp/out 2>/tmp/err 35 | if test $? -ne 0; then 36 | echo "Failed:" 37 | cat /tmp/out /tmp/err 38 | exit 1 39 | fi 40 | done 41 | 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.5.6 2 | 3 | * uzers >= 10 @nc7s (gitlab#15) 4 | * set PLEASE_EDIT_FILE environment when editing 5 | * nix >= 0.28 6 | 7 | 0.5.5 8 | 9 | * and_hms -> and_hms_opt 10 | * macos beta support 11 | * optionally resume when pleasedit exitcmd fails 12 | * bump nix to 0.27 and rpassword to 7 13 | * users -> uzers 14 | 15 | 0.5.4 16 | 17 | * check mode can run when the please binary is not setuid (github#4) 18 | * improve documentation around default sections 19 | * add search_path to search directories for binary 20 | * add token_timeout to configure token expiration 21 | * zsh tab completion from @Mynacol (gitlab!62) 22 | * bash tab completion (experimental) 23 | * bump regex to 1.7, nix to 0.25, rpassword to 6.0 (dkg) 24 | 25 | 0.5.3 26 | 27 | * [fix] require_pass handling spotted by voidpin 28 | 29 | 0.5.2 30 | 31 | * do not read config files that have already been processed 32 | * permit reason (-r) strings as regex matches 33 | * %{HOSTNAME} expands to hostname in regex rules 34 | * suggest -d when invoked with cd and cd is not located 35 | * new option of timeout for password prompt 36 | * new option of target_group for run/edit 37 | 38 | 0.5.1 39 | 40 | * editmode=keep now default if no other mode is specified 41 | * only include files in includedir if they do not start with . 42 | * trimmed error when unable to communicate with syslog 43 | 44 | 0.5.0 45 | 46 | * backslashes within arguments now require escaping 47 | * editmode=keep to preserve the file permission bits from an existing file 48 | * exact_{rule,target,name,hostname,dir} which are literal 49 | * nix bump to 0.23.0 50 | * deprecating regex term in favour of rule 51 | 52 | 0.4.2 53 | 54 | * allow environments to pass through 55 | * allow some environment variables to be forced 56 | * handle tstp from shell to editor 57 | 58 | 0.4.1 59 | 60 | * condensing clock and fixing for 32bit 61 | * merging syslog version dependency 62 | * pam conversation separation for netbsd 63 | 64 | 0.4.0 65 | 66 | * Changing chmod in pleaseedit to use fd 67 | * splitting do_environment into set and clean 68 | * umask into set_environment 69 | * renaming reset and eprivs to esc and drop 70 | * fchown on fd 71 | * search_path and do_dir_changes print os errors 72 | * use seteuid/setguid 73 | * use nofollow 74 | * dir should be limited to range, or excluded if not specified 75 | * use rand characters in temp file names 76 | * limit config processing to 10MB 77 | * valid token uses both wall and monotonic clock 78 | * pam follows conversation 79 | * failed edits are now cleaned upon editor exit 80 | 81 | Thanks to Matthias Gerstner for these recommendations 82 | 83 | 0.3.22 84 | 85 | * [fix] spaces within arguments should be escaped 86 | * -u should alias -t 87 | * please and pleaseedit should output help when run without arguments 88 | 89 | 0.3.21 90 | 91 | Cargo.lock for packagers 92 | 93 | * [fix] don't output unparsed config 94 | * [fix] path enumeration reported by @noproto 95 | * man page tidy 96 | * list error should show "your" 97 | 98 | 0.3.20 99 | 100 | * Add current working directory to the syslo 101 | * Fix editor execution if it has arguments 102 | 103 | 0.3.19 104 | 105 | * [fix] group list in pleaseedit 106 | 107 | 0.3.18 108 | 109 | * New syslog bool 110 | * exitcmd placeholders 111 | 112 | 0.3.17 113 | 114 | * Man improvements 115 | 116 | 0.3.16 117 | 118 | * Minor optimisations 119 | * documentation around repeating regex rules 120 | 121 | 0.3.15 122 | 123 | * Performance improvements 124 | 125 | 0.3.14 126 | 127 | * 'last' option to halt processing on match 128 | 129 | 0.3.13 130 | 131 | * documentation fix for datematch 132 | 133 | 0.3.12 134 | 135 | * setgroup error capture 136 | 137 | 0.3.11 138 | 139 | * crate dependency change to align with debian 140 | 141 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Thank you for your interest in this project. 4 | 5 | # Licence 6 | 7 | Currently this project is licensed as GPL-3.0-or-later. At a later date there may be a significant reason to re-licence under new terms. If this is something that you may not wish, please state in a commit that you wish the contribution to be GPL-3.0-or-later. 8 | 9 | There may be a situation where, for project adoption GPL-3.0-or-later might not be compatible, but as yet I have no reason to think so. 10 | 11 | # Submissions 12 | 13 | I prefer commits to be submitted via pull requests at https://gitlab.com/edneville/please. This has the benefit of keeping track of who submitted something (this is important). 14 | 15 | Please squash/rebase your commits if you know how. 16 | 17 | Your commit message should be well formatted. Lines should be less than 72 characters long, the first should be a brief description, the second line should be blank, then bullet-point style paragraphs should follow. 18 | 19 | All submissions must be reviewed. You, at a later date may be the person performing the review or building the changelog. 20 | 21 | # Security 22 | 23 | If you have a security concern that you do not wish to report publicly please mail that to ed-please@s5h.net. 24 | 25 | # Tests 26 | 27 | Ensure that if you add something new, or change behaviour, it must be testable. Please create new tests and ensure that existing tests are not broken. 28 | 29 | # Credits 30 | 31 | If you wish, please add yourself to the credits in `src/lib.rs`. 32 | 33 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "autocfg" 16 | version = "1.1.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 19 | 20 | [[package]] 21 | name = "bitflags" 22 | version = "2.4.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 25 | 26 | [[package]] 27 | name = "cfg-if" 28 | version = "1.0.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 31 | 32 | [[package]] 33 | name = "chrono" 34 | version = "0.4.19" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 37 | dependencies = [ 38 | "libc", 39 | "num-integer", 40 | "num-traits", 41 | "time 0.1.43", 42 | "winapi", 43 | ] 44 | 45 | [[package]] 46 | name = "error-chain" 47 | version = "0.12.4" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 50 | dependencies = [ 51 | "version_check", 52 | ] 53 | 54 | [[package]] 55 | name = "getopts" 56 | version = "0.2.21" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 59 | dependencies = [ 60 | "unicode-width", 61 | ] 62 | 63 | [[package]] 64 | name = "getrandom" 65 | version = "0.2.6" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" 68 | dependencies = [ 69 | "cfg-if", 70 | "libc", 71 | "wasi", 72 | ] 73 | 74 | [[package]] 75 | name = "hostname" 76 | version = "0.3.1" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" 79 | dependencies = [ 80 | "libc", 81 | "match_cfg", 82 | "winapi", 83 | ] 84 | 85 | [[package]] 86 | name = "itoa" 87 | version = "1.0.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" 90 | 91 | [[package]] 92 | name = "libc" 93 | version = "0.2.153" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 96 | 97 | [[package]] 98 | name = "log" 99 | version = "0.4.17" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 102 | dependencies = [ 103 | "cfg-if", 104 | ] 105 | 106 | [[package]] 107 | name = "match_cfg" 108 | version = "0.1.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" 111 | 112 | [[package]] 113 | name = "memchr" 114 | version = "2.5.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 117 | 118 | [[package]] 119 | name = "nix" 120 | version = "0.27.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" 123 | dependencies = [ 124 | "bitflags", 125 | "cfg-if", 126 | "libc", 127 | ] 128 | 129 | [[package]] 130 | name = "num-integer" 131 | version = "0.1.45" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 134 | dependencies = [ 135 | "autocfg", 136 | "num-traits", 137 | ] 138 | 139 | [[package]] 140 | name = "num-traits" 141 | version = "0.2.15" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 144 | dependencies = [ 145 | "autocfg", 146 | ] 147 | 148 | [[package]] 149 | name = "num_threads" 150 | version = "0.1.6" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" 153 | dependencies = [ 154 | "libc", 155 | ] 156 | 157 | [[package]] 158 | name = "pam" 159 | version = "0.7.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "fa2bdc959c201c047004a1420a92aaa1dd1a6b64d5ef333aa3a4ac764fb93097" 162 | dependencies = [ 163 | "libc", 164 | "pam-sys", 165 | "users", 166 | ] 167 | 168 | [[package]] 169 | name = "pam-sys" 170 | version = "0.5.6" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "cd4858311a097f01a0006ef7d0cd50bca81ec430c949d7bf95cbefd202282434" 173 | dependencies = [ 174 | "libc", 175 | ] 176 | 177 | [[package]] 178 | name = "pleaser" 179 | version = "0.5.6" 180 | dependencies = [ 181 | "chrono", 182 | "getopts", 183 | "libc", 184 | "nix", 185 | "pam", 186 | "rand", 187 | "regex", 188 | "rpassword", 189 | "syslog", 190 | "uzers", 191 | ] 192 | 193 | [[package]] 194 | name = "ppv-lite86" 195 | version = "0.2.16" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 198 | 199 | [[package]] 200 | name = "rand" 201 | version = "0.8.5" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 204 | dependencies = [ 205 | "libc", 206 | "rand_chacha", 207 | "rand_core", 208 | ] 209 | 210 | [[package]] 211 | name = "rand_chacha" 212 | version = "0.3.1" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 215 | dependencies = [ 216 | "ppv-lite86", 217 | "rand_core", 218 | ] 219 | 220 | [[package]] 221 | name = "rand_core" 222 | version = "0.6.3" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 225 | dependencies = [ 226 | "getrandom", 227 | ] 228 | 229 | [[package]] 230 | name = "regex" 231 | version = "1.7.1" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 234 | dependencies = [ 235 | "aho-corasick", 236 | "memchr", 237 | "regex-syntax", 238 | ] 239 | 240 | [[package]] 241 | name = "regex-syntax" 242 | version = "0.6.27" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 245 | 246 | [[package]] 247 | name = "rpassword" 248 | version = "7.3.1" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" 251 | dependencies = [ 252 | "libc", 253 | "rtoolbox", 254 | "windows-sys", 255 | ] 256 | 257 | [[package]] 258 | name = "rtoolbox" 259 | version = "0.0.2" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" 262 | dependencies = [ 263 | "libc", 264 | "windows-sys", 265 | ] 266 | 267 | [[package]] 268 | name = "syslog" 269 | version = "6.0.1" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "978044cc68150ad5e40083c9f6a725e6fd02d7ba1bcf691ec2ff0d66c0b41acc" 272 | dependencies = [ 273 | "error-chain", 274 | "hostname", 275 | "libc", 276 | "log", 277 | "time 0.3.9", 278 | ] 279 | 280 | [[package]] 281 | name = "time" 282 | version = "0.1.43" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 285 | dependencies = [ 286 | "libc", 287 | "winapi", 288 | ] 289 | 290 | [[package]] 291 | name = "time" 292 | version = "0.3.9" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" 295 | dependencies = [ 296 | "itoa", 297 | "libc", 298 | "num_threads", 299 | ] 300 | 301 | [[package]] 302 | name = "unicode-width" 303 | version = "0.1.9" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 306 | 307 | [[package]] 308 | name = "users" 309 | version = "0.8.1" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "7fed7d0912567d35f88010c23dbaf865e9da8b5227295e8dc0f2fdd109155ab7" 312 | dependencies = [ 313 | "libc", 314 | ] 315 | 316 | [[package]] 317 | name = "uzers" 318 | version = "0.11.3" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "76d283dc7e8c901e79e32d077866eaf599156cbf427fffa8289aecc52c5c3f63" 321 | dependencies = [ 322 | "libc", 323 | "log", 324 | ] 325 | 326 | [[package]] 327 | name = "version_check" 328 | version = "0.9.4" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 331 | 332 | [[package]] 333 | name = "wasi" 334 | version = "0.10.2+wasi-snapshot-preview1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 337 | 338 | [[package]] 339 | name = "winapi" 340 | version = "0.3.9" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 343 | dependencies = [ 344 | "winapi-i686-pc-windows-gnu", 345 | "winapi-x86_64-pc-windows-gnu", 346 | ] 347 | 348 | [[package]] 349 | name = "winapi-i686-pc-windows-gnu" 350 | version = "0.4.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 353 | 354 | [[package]] 355 | name = "winapi-x86_64-pc-windows-gnu" 356 | version = "0.4.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 359 | 360 | [[package]] 361 | name = "windows-sys" 362 | version = "0.48.0" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 365 | dependencies = [ 366 | "windows-targets", 367 | ] 368 | 369 | [[package]] 370 | name = "windows-targets" 371 | version = "0.48.5" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 374 | dependencies = [ 375 | "windows_aarch64_gnullvm", 376 | "windows_aarch64_msvc", 377 | "windows_i686_gnu", 378 | "windows_i686_msvc", 379 | "windows_x86_64_gnu", 380 | "windows_x86_64_gnullvm", 381 | "windows_x86_64_msvc", 382 | ] 383 | 384 | [[package]] 385 | name = "windows_aarch64_gnullvm" 386 | version = "0.48.5" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 389 | 390 | [[package]] 391 | name = "windows_aarch64_msvc" 392 | version = "0.48.5" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 395 | 396 | [[package]] 397 | name = "windows_i686_gnu" 398 | version = "0.48.5" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 401 | 402 | [[package]] 403 | name = "windows_i686_msvc" 404 | version = "0.48.5" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 407 | 408 | [[package]] 409 | name = "windows_x86_64_gnu" 410 | version = "0.48.5" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 413 | 414 | [[package]] 415 | name = "windows_x86_64_gnullvm" 416 | version = "0.48.5" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 419 | 420 | [[package]] 421 | name = "windows_x86_64_msvc" 422 | version = "0.48.5" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 425 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pleaser" 3 | version = "0.5.6" 4 | authors = ["ed neville "] 5 | edition = "2018" 6 | description = "please, a polite regex-first sudo alternative" 7 | license = "GPL-3.0-or-later" 8 | homepage = "https://www.usenix.org.uk/content/please.html" 9 | repository = "https://gitlab.com/edneville/please" 10 | readme = "README.md" 11 | categories = ["command-line-utilities"] 12 | documentation = "https://www.usenix.org.uk/content/please.html" 13 | keywords = ["please", "access", "elevation", "edit", "regex"] 14 | exclude = ["/bintest"] 15 | 16 | [dependencies] 17 | regex = "1.7" 18 | chrono = "0.4" 19 | getopts = "0.2" 20 | nix = { version = ">= 0.27", features = ["signal", "user", "fs", "term", "hostname"] } 21 | pam = "0.7" 22 | uzers = ">= 0.10" 23 | rpassword = "7" 24 | syslog= ">= 6.0" 25 | libc = "0.2" 26 | rand = "0.8" 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Please, a sudo alternative 2 | 3 | Delegate accurate least privilege access with ease. Express easily with a regex and expose only what is needed and nothing more. Or validate file edits with `pleaseedit`. 4 | 5 | Admin your box without giving users full root shells, most admins have experience of regex in one form or another, so lets configure access that way. 6 | 7 | I saw regex but don't like regex. No problem, you can still use please and pleaseedit without regex by using `exact_` counterparts, or treat each field/property as plain text, and escape control characters `?(){}[]+` etc. Most of the regex match statements have `exact` counterparts. 8 | 9 | Please is written with memory safe rust. Traditional C memory unsafety is avoided, logic problems may exist but this codebase is relatively small. 10 | 11 | # How do I install it 12 | 13 | It might already be in the repo that you're using: 14 | 15 | [![Packaging status](https://repology.org/badge/vertical-allrepos/pleaser.svg)](https://repology.org/project/pleaser/versions) 16 | 17 | If not, it is a simple install: 18 | 19 | ``` 20 | git clone https://gitlab.com/edneville/please.git 21 | cd please 22 | cargo test && cargo build --release \ 23 | && install -o 0 -g 0 -m4755 target/release/please target/release/pleaseedit /usr/local/bin 24 | ``` 25 | 26 | Arch: 27 | 28 | ``` 29 | pacman -Syu git fakeroot devtools binutils gcc rust 30 | git clone https://aur@aur.archlinux.org/pleaser.git 31 | cd pleaser && makepkg -isr 32 | ``` 33 | 34 | Debian/Ubuntu: 35 | 36 | ``` 37 | apt install pleaser 38 | ``` 39 | 40 | Fedora (35): 41 | 42 | ``` 43 | dnf install pleaser 44 | ``` 45 | 46 | NetBSD: 47 | 48 | ``` 49 | pkgin install pleaser 50 | ``` 51 | 52 | SUSE Tumbleweed: 53 | 54 | ``` 55 | zypper install pleaser 56 | ``` 57 | 58 | RHEL 7 (EPEL): 59 | ``` 60 | yum install cargo git pam-devel 61 | git clone 'https://gitlab.com/edneville/please.git' 62 | cd please/ 63 | cargo test && cargo build --release && install -oroot -groot -D -m4755 target/release/please target/release/pleaseedit /usr/local/bin 64 | ``` 65 | 66 | Optionally, set `sudo` as an alias of `please`: 67 | 68 | ``` 69 | alias sudo="please" 70 | alias sudoedit="pleaseedit" 71 | ``` 72 | 73 | Or, if you like, symlink in local: 74 | 75 | ``` 76 | cd /usr/local/bin && ln -s /usr/local/bin/please sudo && ln -s /usr/local/bin/pleaseedit sudoedit 77 | ``` 78 | 79 | # How do I set it up 80 | 81 | You may need to configure PAM if you didn't use a distro package in order for `require_pass` to authenticate. Debian-based needs something similar to this in `/etc/pam.d/please` and `/etc/pam.d/pleaseedit`: 82 | 83 | ``` 84 | #%PAM-1.0 85 | 86 | # Set up user limits from /etc/security/limits.conf. 87 | session required pam_limits.so 88 | 89 | @include common-auth 90 | @include common-account 91 | @include common-session-noninteractive 92 | ``` 93 | 94 | Red Hat based needs something similar to this in the same files: 95 | 96 | ``` 97 | #%PAM-1.0 98 | auth include system-auth 99 | account include system-auth 100 | password include system-auth 101 | session optional pam_keyinit.so revoke 102 | session required pam_limits.so 103 | session include system-auth 104 | ``` 105 | 106 | Next, configure your `/etc/please.ini`, replace user names with appropriate values. The `ini` is divided into section options, matches and actions. 107 | 108 | ## Section options 109 | 110 | | Part | Effect | 111 | |-----------------------------|--------------| 112 | | [section-name] | Section name, shown in list mode | 113 | | include=file | Include file as another ini source, other options will be skipped in this section. | 114 | | includedir=dir | Include dir of `.ini` files as other sources, in ascii sort order other options will be skipped in this section. Files not matching `.ini` will be ignored to allow for editor tmp files. | 115 | 116 | `include` and `includedir` will override mandatory arguments. 117 | 118 | ## Matches 119 | 120 | One of the simplest, that does not require password authentication can be defined as follows, assuming the user is `jim`: 121 | 122 | The options are as follows: 123 | 124 | | Part | Effect | 125 | |-----------------------------|--------------| 126 | | name=regex | Mandatory, apply configuration to this entity. | 127 | | target=regex | May become these users. | 128 | | rule=regex | This is the command regex for the section, default is ^$ | 129 | | notbefore=YYYYmmdd | The date, or YYYYmmddHHMMSS when this rule becomes effective. | 130 | | notafter=YYYYmmdd | The date, or YYYYmmddHHMMSS when this rule expires. | 131 | | datematch=[Day dd Mon HH:MM:SS UTC YYYY] | regex to match against a date string | 132 | | type=[edit/run/list] | Set the entry type, run = execution, edit = pleaseedit, list = show user rights | 133 | | group=[true/false] | True to signify that name= refers to a group rather than a user. | 134 | | hostname=regex | Hosts where this applies, defaults to 'localhost'. | 135 | | target_group=regex | When set a group must be provided that matches | 136 | | dir=regex | Permit switching to regex defined directory prior to execution. | 137 | | permit_env=regex | When combined with `-a`, permit matching environments keys | 138 | | search_path=string | Change search_path to `:` separated directory list | 139 | 140 | Exact counterparts, which must match exactly. When both regex and exact rules are present, the exact rule match will have precedence. 141 | 142 | | Part | Effect | 143 | |-----------------------------|--------------| 144 | | exact_name=string | Match this exact name | 145 | | exact_hostname=string | Match this exact hostname | 146 | | exact_target=string | Match this exact target user | 147 | | exact_target_group=string | Match this exact target group | 148 | | exact_rule=string | Match this exact rule | 149 | | exact_dir=string | Match this exact directory | 150 | 151 | ## Actions 152 | 153 | | Part | Effect | 154 | |-----------------------------|--------------| 155 | | permit=[true/false] | Defaults to true | 156 | | require_pass=[true/false] | Defaults to true | 157 | | last=[true/false] | When true, stop processing when matched, defaults to false | 158 | | reason=[true/false/regex] | When set, require a reason provided by `-r`, defaults to false | 159 | | timeout=[number] | How long to wait for password input, in whole seconds | 160 | | syslog=[true/false] | Log this activity to syslog, default = true | 161 | | token_timeout=[number] | How long the authentication token is valid for, in whole seconds | 162 | | env_assign.key=value | Force environment **key** to be assigned **value** | 163 | | exitcmd=[program] | (pleaseedit) Continue with file replacement if `program` exits 0 | 164 | | editmode=[octal mode/keep] | (pleaseedit) Set destination file mode to `octal mode`, or keep the mode of an existing file. If the file is not present, or mode is not declared, then mode falls back to 0600. If there is a file present, then the mode is read and used just prior to file rename | 165 | 166 | Using a greedy `.*` for the regex field will be as good as saying the rule should match any command. In previous releases there was no anchor (`^` and `$`) however, it seems more sensible to follow `find`'s approach and insist that there are anchors around the regex. This avoids `/bin/bash` matching `/home/user/bin/bash`. 167 | 168 | If a `include` directive is met, no other entries in the section will be processed. The same goes for `includedir`. 169 | 170 | The ordering of rules matters. The last match will win. Set `permit=false` if you wish to exclude something, but this should be very rare as the permit should be against a regex rather than using a positive and then a negative match. A rule of best practice is to avoid a fail open and then try and exclude most of the universe. 171 | 172 | For example, using the two entries below: 173 | 174 | ``` 175 | [jim_root_du] 176 | name = jim 177 | target = root 178 | permit = true 179 | rule = ^(/usr)?/bin/du (/home/[a-z0-9-]+\s?)+ 180 | require_pass=false 181 | ``` 182 | 183 | ``` 184 | [jim_postgres] 185 | name = jim 186 | target = postgres 187 | permit = true 188 | rule = /bin/bash 189 | require_pass = false 190 | ``` 191 | 192 | Would permit running `du`, as `/usr/bin/du` or `/bin/du` as `root`: 193 | 194 | ``` 195 | $ please du /home/* 196 | ``` 197 | 198 | And would also permit running a bash shell as `postgres`: 199 | 200 | ``` 201 | $ please -t postgres /bin/bash 202 | postgres$ 203 | ``` 204 | 205 | # Date ranges 206 | 207 | For large environments it is not unusual for a third party to require access during a short time frame for debugging. To accommodate this there are the `notbefore` and `notafter` time brackets. These can be either `YYYYMMDD` or `YYYYMMDDHHMMSS`. 208 | 209 | The whole day is considered when using the shorter date form of `YYYYMMDD`. 210 | 211 | Many enterprises may wish to permit access to a user for a limited time only, even if that individual is in the role permanently. 212 | 213 | # Date matches 214 | 215 | Another date type is the `datematch` item, this constrains sections to a regex match against the date string `Day dd Mon HH:MM:SS UTC Year`. 216 | 217 | You can permit some a group of users to perform some house keeping on a Monday: 218 | 219 | ``` 220 | [l2_housekeeping] 221 | name = l2users 222 | group = true 223 | target = root 224 | permit = true 225 | rule = /usr/local/housekeeping/tidy_(logs|images|mail) 226 | datematch = ^Mon.* 227 | ``` 228 | 229 | # Default sections 230 | 231 | When a matching section name begins with `default` the actions will remain set until overwritten by another matching section. It is important to note that **permit=true** will be set implicitly on matches, therefore, unless there is good reason, set **permit=false** in default sections and **permit=true** in subsequent matching sections. See **please.ini** for further details. 232 | 233 | # pleaseedit 234 | 235 | `pleaseedit` enables editing of files as another user. Enable editing rather than execution with `type=edit`. The first argument will be passed to `EDITOR`. 236 | 237 | By default file permission bits will mirror existing file permissions. 238 | 239 | This is performed as follows: 240 | 241 | 1. user runs edit as `pleaseedit -u root /etc/fstab` 242 | 2. `/etc/fstab` is copied to `/tmp/pleaseedit.$USER.r8cYph9h._etc_fstab` 243 | 3. user's `EDITOR` is executed against `/tmp/pleaseedit.$USER.r8cYph9h._etc_fstab` 244 | 4. if `EDITOR` exits 0, and `exitcmd` exits 0, then `/tmp/pleaseedit.$USER.r8cYph9h._etc_fstab` is copied to `/etc/fstab.llD3wRQB.pleaseedit.copy.$USER` 245 | 5. `/etc/fstab.llD3wRQB.pleaseedit.copy.$USER` is set as (target) root owned and `renamed` to `/etc/fstab` 246 | 247 | # exitcmd 248 | 249 | exitcmd can be used prior to the tmp edit file move to the source location. This can be used to test configuration files are valid prior to renaming in place. 250 | 251 | For something similar to apache, consider copying the config tree to a tmp directory before running the test to accommodate includes. 252 | 253 | # Other examples 254 | 255 | Members of the `audio` group may remove temporary users that an application may not have cleaned up in the form of `username_tmp.<10 random alphanumerics>` using `userdel`: 256 | 257 | ``` 258 | [user_remove_tmp_user] 259 | name = audio 260 | group = true 261 | permit = true 262 | require_pass = false 263 | rule = /usr/sbin/userdel -f -r %{USER}_tmp\.[a-zA-Z0-9]{10} 264 | ``` 265 | 266 | How about, for the purpose of housekeeping, some users may be permitted to destroy zfs snapshots that look roughly like they're date stamped: 267 | 268 | ``` 269 | [user_remove_snapshots] 270 | name = data 271 | group = true 272 | permit = true 273 | require_pass = false 274 | rule = /usr/sbin/zfs destroy storage/photos@\d{8}T\d{6} 275 | ``` 276 | 277 | To list what you may or may not do: 278 | 279 | ``` 280 | $ please -l 281 | You may run the following: 282 | file: /etc/please.ini 283 | ed_root_list:root: ^.*$ 284 | You may edit the following: 285 | file: /etc/please.ini 286 | ed_edit_ini:root: ^/etc/please.ini$ 287 | ``` 288 | 289 | The above output shows that I may run anything and may edit the `please.ini` configuration. 290 | 291 | Or, perhaps any user who's name starts `admin` may execute `useradd` and `userdel`: 292 | 293 | ``` 294 | [admin_users] 295 | name = admin_\S+ 296 | permit = true 297 | require_pass = false 298 | rule = /usr/sbin/user(add -m|del) \S+ 299 | ``` 300 | 301 | # Files 302 | 303 | /etc/please.ini 304 | 305 | # Big installs 306 | 307 | For big installs, consider the following: 308 | 309 | ## Consolidate 310 | 311 | Where you can use groups when all member least privilege matches the set. It is best here to consider that people often perform the same role, so try and organise the rules that way, so use either a group or list accounts in a single `name` regex match. 312 | 313 | ## Central configuration considerations 314 | 315 | To avoid single points of failure in a service, `ini` configuration should be generated in a single location and pushed to installs. `ini` files parse very quickly whilst accessing LDAP is not only slower but also error prone. 316 | 317 | It could be possible to use caching, but a form of positive (correct match) and negative (incorrect match) would be required. 10,000 computers with hundreds of active users performing lookups against an LDAP server could be problematic. 318 | 319 | For these reasons I prefer rsync distribution as the protocol is highly efficient and reduces network transfer overall. 320 | 321 | LDAP may at a later date be reconsidered. 322 | 323 | # Contributions 324 | 325 | Should you find anything that you feel is missing, regardless of initial design, please feel free to raise an issue with or without a pull request. 326 | 327 | Locating bugs and logging issues are very appreciated, and I thank you in advance. 328 | 329 | I welcome pull requests with open arms. 330 | 331 | # Locations 332 | 333 | The source code for this project is currently hosted on [gitlab](https://gitlab.com/edneville/please) and mirrored to [github](https://github.com/edneville/please). There is a [crate on crates.io](https://crates.io/crates/pleaser). It also has a [homepage](https://www.usenix.org.uk/content/please.html) where other project information is kept. 334 | 335 | # Why pleaser in some circles? 336 | 337 | This project is named "please". In some places that project name was used by others for other things. Some packages will be named pleaser, some will be named please. The only important thing is if you wish someone to make you a sandwich, just say "please" first. 338 | 339 | -------------------------------------------------------------------------------- /bintest/WARNING.md: -------------------------------------------------------------------------------- 1 | ## Warning! 2 | 3 | Tests here are not expected to be run in a live environment, they will destroy 4 | `/etc/please*`. 5 | 6 | You have been warned! 7 | 8 | -------------------------------------------------------------------------------- /bintest/make_user.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | useradd -m ed 4 | useradd -m tester 5 | useradd -m bob 6 | 7 | usermod -p '$6$ARcSVK.6dRB6I0c3$E0V2Murhd.bSPXftaqQlSpW1akg8tKc13t7aOiEBxc4Yz47RTMGNCu7re8UQiIkUDPhgyM9rshy223fcGSBM10' ed 8 | 9 | -------------------------------------------------------------------------------- /bintest/test_1/please.d/00_ed.ini: -------------------------------------------------------------------------------- 1 | [ed] 2 | name = ed 3 | syslog = false 4 | rule = .* 5 | permit = true 6 | reason = ree 7 | search_path = : 8 | require_pass = false 9 | -------------------------------------------------------------------------------- /bintest/test_1/please.d/00_root.ini: -------------------------------------------------------------------------------- 1 | [root] 2 | name = root 3 | syslog = false 4 | rule = .* 5 | permit = true 6 | reason = ree 7 | require_pass = false 8 | -------------------------------------------------------------------------------- /bintest/test_1/please.ini: -------------------------------------------------------------------------------- 1 | [ed] 2 | rule = ^.*$ 3 | name = ed 4 | permit = true 5 | syslog = false 6 | 7 | [inc] 8 | includedir = /etc/please.d 9 | -------------------------------------------------------------------------------- /bintest/test_1/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # test first that /bin/echo is found in the section with no search path 4 | 5 | set -e 6 | 7 | echo '/usr/bin/please -r ree /bin/echo worked' | su - ed | grep -Fx worked 8 | 9 | # test that we fall back into the section in please.ini that has a default 10 | # search path 11 | echo '/usr/bin/please -r ree echo worked | grep -Fx "Cannot read password without tty"' | su - ed 12 | 13 | -------------------------------------------------------------------------------- /bintest/test_10_file_exists/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_10_file_exists/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_10_file_exists/please.ini: -------------------------------------------------------------------------------- 1 | [ed_all] 2 | syslog = false 3 | name = ed 4 | regex = ^/tmp/file_[a-z0-9A-Z]+ 5 | type = edit 6 | require_pass = false 7 | editmode = keep 8 | 9 | [ed_all] 10 | syslog = false 11 | name = ed 12 | regex = ^/tmp/static_0644 13 | type = edit 14 | require_pass = false 15 | editmode = 0644 16 | 17 | [ed_all] 18 | syslog = false 19 | name = ed 20 | regex = ^/tmp/static_0600 21 | type = edit 22 | require_pass = false 23 | editmode = 0600 24 | 25 | [ed_all] 26 | syslog = false 27 | name = ed 28 | regex = ^/tmp/static_defaults 29 | type = edit 30 | require_pass = false 31 | -------------------------------------------------------------------------------- /bintest/test_10_file_exists/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "file exists mode" 6 | for mode in 0755 0644 0600 4755 0777 0666 0222; do 7 | touch "/tmp/file_${mode}" 8 | chmod "$mode" "/tmp/file_${mode}" 9 | 10 | cat < /etc/tmp/hostname.ini 8 | [ed_all] 9 | syslog = false 10 | exact_hostname = `hostname` 11 | exact_name = ed 12 | exact_rule = /bin/bash 13 | require_pass = false 14 | EOT 15 | 16 | echo "test exact permits actual hostname" 17 | cat </etc/please.ini 4 | [ed_all] 5 | include = /etc/please.d/00_please_missing.ini 6 | permit = false 7 | EOT 8 | 9 | echo "test includedir (broken)" 10 | cat < /etc/thing 6 | 7 | cat <<'EOT' | su -s /bin/bash ed 8 | export EDITOR=tee 9 | echo "foo bar baz alice bob eve" | pleaseedit /etc/thing 10 | grep 'foo' /etc/thing* && exit 1 11 | ls -al /tmp/*thing* && exit 1 12 | /bin/true 13 | EOT 14 | 15 | -------------------------------------------------------------------------------- /bintest/test_15_empty_please_ini/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_empty_please_ini/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_15_empty_please_ini/please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_empty_please_ini/please.ini -------------------------------------------------------------------------------- /bintest/test_15_empty_please_ini/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "test misc empty please.ini" 6 | cat <<'EOT' | su -s /bin/bash ed 7 | please -l | egrep "You may not view your command list" 8 | please -l -t ed | egrep "You may not view your command list" 9 | please -l -t root | egrep "You may not view root" 10 | EOT 11 | 12 | -------------------------------------------------------------------------------- /bintest/test_15_escaped_arguments/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_escaped_arguments/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_15_escaped_arguments/please.ini: -------------------------------------------------------------------------------- 1 | [ed_true] 2 | name = ed 3 | regex = ^/bin/echo hello\\ world 4 | permit = true 5 | syslog = false 6 | require_pass = false 7 | 8 | [ed_false] 9 | name = ed 10 | regex = ^/bin/echo goodbye\\ world 11 | permit = false 12 | syslog = false 13 | require_pass = false 14 | 15 | -------------------------------------------------------------------------------- /bintest/test_15_escaped_arguments/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "escaped arguments" 6 | 7 | cat <<'EOT' | su -s /bin/bash ed 8 | export RUST_BACKTRACE=1 9 | echo "doing hello" 10 | please /bin/echo "hello world" | egrep "^hello world" 11 | if test $? -ne 0; then 12 | exit 1 13 | fi 14 | echo "did true" 15 | please /bin/echo "goodbye world" | egrep "You may not.*" 16 | if test $? -ne 0; then 17 | exit 0 18 | fi 19 | EOT 20 | 21 | 22 | -------------------------------------------------------------------------------- /bintest/test_15_file_existence/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_file_existence/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_15_file_existence/please.ini: -------------------------------------------------------------------------------- 1 | [all_run] 2 | name = ^%{USER}$ 3 | type = run 4 | regex = ^.*$ 5 | require_pass = false 6 | reason = false 7 | syslog = false 8 | 9 | -------------------------------------------------------------------------------- /bintest/test_15_file_existence/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "Arbitrary File Existence Test" 6 | 7 | cat <<'EOT' | su -s /bin/bash ed 8 | set -e 9 | # please thing | grep -F '[please]: command not found' || exit 1 10 | please thing | grep 'command not found' 11 | echo "" | please bash 12 | EOT 13 | 14 | -------------------------------------------------------------------------------- /bintest/test_15_list_reason/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_list_reason/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_15_list_reason/please.ini: -------------------------------------------------------------------------------- 1 | [all_users_list] 2 | name = ^%{USER}$ 3 | type = list 4 | target = ^%{USER}$ 5 | require_pass = false 6 | reason = true 7 | syslog = false 8 | 9 | -------------------------------------------------------------------------------- /bintest/test_15_list_reason/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "test list reason" 6 | cat <<'EOT' | su -s /bin/bash ed 7 | set -e 8 | please -l | grep reason 9 | please -l -r zip | grep "You may" 10 | echo "list and reason passed" 11 | EOT 12 | 13 | -------------------------------------------------------------------------------- /bintest/test_15_misc_batch/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_misc_batch/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_15_misc_batch/please.ini: -------------------------------------------------------------------------------- 1 | [ed_all] 2 | name = ed 3 | regex = ^.*$ 4 | require_pass = false 5 | syslog = false 6 | 7 | [all_users_list] 8 | name = ^%{USER}$ 9 | type = list 10 | target = ^%{USER}$ 11 | require_pass = false 12 | syslog = false 13 | 14 | [ed_edit] 15 | name = ed 16 | permit=true 17 | type=edit 18 | regex=^/tmp/foo.ini$ 19 | exitcmd=/usr/bin/please -c %{NEW} 20 | editmode = 600 21 | syslog = false 22 | require_pass = false 23 | 24 | [ene020_ed_bash] 25 | name = ene020 26 | regex = ^(/usr)?/bin/(bash$|tmux( attach)?)$ 27 | target = ed 28 | syslog = false 29 | require_pass = false 30 | 31 | -------------------------------------------------------------------------------- /bintest/test_15_misc_batch/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "test misc batch" 6 | cat <<'EOT' | su -s /bin/bash ed 7 | set -e 8 | 9 | please -l 10 | please -l | grep 'You may run' 11 | 12 | echo "hostname" | please /bin/bash 13 | 14 | please -l -t root | egrep "You may not view root's command list" 15 | please -l | grep -Fx ' ed_all:root (pass=false,dirs=): ^.*$' 16 | please -l | grep -Fx ' ed_edit:root (pass=false,dirs=): ^/tmp/foo.ini$' 17 | please -l | grep -Fx ' all_users_list:list: ^%{USER}$' 18 | 19 | echo "Append with tee" 20 | export EDITOR="/usr/bin/tee -a" 21 | echo "###" | pleaseedit /tmp/foo.ini 22 | echo "grep '###' /tmp/foo.ini" | please /bin/bash 23 | 24 | echo "cat /etc/please.ini" | please /bin/bash 25 | (echo "###" | pleaseedit /etc/fstab ) | egrep 'You may not edit "/etc/fstab" on \S+ as root' 26 | echo "skipped edit" 27 | 28 | echo "overwrite with tee" 29 | export EDITOR="/usr/bin/tee" 30 | echo "###" | pleaseedit /tmp/foo.ini 31 | EOT 32 | 33 | echo "reveal file contents" 34 | printf "[section]\ntest = test" > /tmp/t 35 | cat <<'EOT' | su -s /bin/bash ed 36 | please -c /tmp/t | grep test 37 | 38 | if test $? -eq 0; then 39 | echo "revealed test" 40 | exit 1 41 | fi 42 | exit 0 43 | EOT 44 | 45 | -------------------------------------------------------------------------------- /bintest/test_15_network_dir_umask/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_network_dir_umask/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_15_network_dir_umask/please.ini: -------------------------------------------------------------------------------- 1 | [all_run] 2 | name = ^%{USER}$ 3 | type = run 4 | regex = ^.*$ 5 | require_pass = true 6 | syslog = false 7 | 8 | -------------------------------------------------------------------------------- /bintest/test_15_network_dir_umask/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | apt-get update 6 | apt-get -y install expect 7 | 8 | echo "directory umask control" 9 | rm -rf /var/run/please 10 | cat <<'EOT' | su -s /bin/bash ed 11 | set -e 12 | cat <<'EOF' | expect 13 | spawn please /bin/ls 14 | expect -re ".*password.*" 15 | send "password\r" 16 | expect eof 17 | EOF 18 | EOT 19 | 20 | find /var/run/please -ls | grep -- "drwx------" || exit 1 21 | find /var/run/please -ls | grep -- "-rw-------" || exit 1 22 | 23 | 24 | -------------------------------------------------------------------------------- /bintest/test_15_password_list_network/.please.ini.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_password_list_network/.please.ini.swp -------------------------------------------------------------------------------- /bintest/test_15_password_list_network/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_password_list_network/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_15_password_list_network/please.ini: -------------------------------------------------------------------------------- 1 | [all_users_list] 2 | name = ^%{USER}$ 3 | type = list 4 | target = ^%{USER}$ 5 | require_pass = true 6 | reason = true 7 | syslog = false 8 | 9 | -------------------------------------------------------------------------------- /bintest/test_15_password_list_network/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | apt-get update 6 | apt-get -y install expect 7 | 8 | echo "test list reason and password" 9 | cat <<'EOT' | su -s /bin/bash ed 10 | please -l | grep reason || exit 1 11 | 12 | echo 'spawn expect' 13 | cat <<'EOF' | expect | grep "You may" 14 | spawn please -l -r zippy 15 | expect -re ".*password.*" 16 | send "password\r" 17 | expect eof 18 | EOF 19 | if test $? -ne 0; then 20 | exit 1 21 | fi 22 | echo "list reason and password passed" 23 | EOT 24 | 25 | -------------------------------------------------------------------------------- /bintest/test_15_password_read_network/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_password_read_network/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_15_password_read_network/please.ini: -------------------------------------------------------------------------------- 1 | [all_users_list] 2 | name = ^%{USER}$ 3 | type = list 4 | target = ^%{USER}$ 5 | require_pass = true 6 | syslog = false 7 | 8 | -------------------------------------------------------------------------------- /bintest/test_15_password_read_network/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | apt-get update 6 | apt-get -y install expect 7 | 8 | cat <<'EOT' | su -s /bin/bash ed 9 | set -e 10 | echo "test list password" 11 | cat <<'EOF' | expect | grep "You may" 12 | spawn please -l 13 | expect -re ".*password.*" 14 | send "password\r" 15 | expect eof 16 | EOF 17 | echo "list passed" 18 | EOT 19 | 20 | cat <<'EOT' | su -s /bin/bash ed 21 | echo list 22 | cat <<'EOF' | expect | grep "Authentication" 23 | spawn please -l 24 | expect -re ".*password.*" 25 | send "zpassword\r" 26 | expect -re ".*password.*" 27 | send "zpassword\r" 28 | expect -re ".*password.*" 29 | send "zpassword\r" 30 | expect eof 31 | EOF 32 | EOT 33 | 34 | 35 | -------------------------------------------------------------------------------- /bintest/test_15_purge_token/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_purge_token/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_15_purge_token/please.ini: -------------------------------------------------------------------------------- 1 | [ed] 2 | name = ed 3 | permit=true 4 | type=run 5 | regex=^.* 6 | target=root 7 | syslog = false 8 | require_pass = false 9 | 10 | -------------------------------------------------------------------------------- /bintest/test_15_purge_token/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | echo "test purge token (manual create)" 5 | 6 | cat <<'EOT' | su -s /bin/bash ed 7 | cat <<'EOF' | script /tmp/tty_test 8 | export RUST_BACKTRACE=1 9 | TOKEN="/var/run/please/token/ed:`tty | sed -e 's,/,_,g'`:$$" 10 | please touch "$TOKEN" 11 | please ls -al "$TOKEN" 12 | please -p 13 | please ls -al "$TOKEN" | grep token 14 | if test $? -eq 0; then 15 | exit 1 16 | fi 17 | EOF 18 | EOT 19 | 20 | echo "test purge token (broken tty)" 21 | cat <<'EOT' | su -s /bin/bash ed 22 | export RUST_BACKTRACE=1 23 | TOKEN="/var/run/please/token/ed:`echo /dev/pts/0 | sed -e 's,/,_,g'`:$$" 24 | echo "$TOKEN" 25 | please touch "$TOKEN" 26 | please ls -al "$TOKEN" 27 | please -p 28 | please ls -al "$TOKEN" | grep token 29 | EOT 30 | 31 | echo "test warm token (broken tty)" 32 | cat <<'EOT' | su -s /bin/bash ed 33 | export RUST_BACKTRACE=1 34 | TOKEN="/var/run/please/token/ed:`echo /dev/pts/0 | sed -e 's,/,_,g'`:$$" 35 | please -w 36 | please ls -al "$TOKEN" | grep token 37 | if test $? -eq 0; then 38 | exit 1 39 | fi 40 | EOT 41 | 42 | echo "test help output" 43 | cat <<'EOT' | su -s /bin/bash ed 44 | set -e 45 | export RUST_BACKTRACE=1 46 | please | grep -i 'no command' 47 | please | grep -i 'usage' 48 | please -v | egrep -i 'please.*version' 49 | please --version | egrep -i 'please.*version' 50 | please -h | grep -i 'version' 51 | please --help | grep -i 'version' 52 | 53 | pleaseedit | grep -i 'file' 54 | pleaseedit | grep -i 'usage' 55 | pleaseedit -v | egrep -i 'please.*version' 56 | pleaseedit --version | egrep -i 'please.*version' 57 | pleaseedit -h | grep -i 'version' 58 | pleaseedit --help | grep -i 'version' 59 | EOT 60 | 61 | -------------------------------------------------------------------------------- /bintest/test_15_require_password/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_require_password/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_15_require_password/please.ini: -------------------------------------------------------------------------------- 1 | [ed_all] 2 | name = ed 3 | regex = ^.*$ 4 | require_pass = false 5 | syslog = false 6 | 7 | [ed_some] 8 | name = ed 9 | regex = ^/usr/bin/(id|who) 10 | require_pass = true 11 | syslog = false 12 | 13 | [test_some] 14 | name = tester 15 | regex = ^.* 16 | require_pass = false 17 | syslog = false 18 | permit = false 19 | -------------------------------------------------------------------------------- /bintest/test_15_require_password/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "test password require" 6 | echo "please -n hostname" 7 | cat <<'EOT' | su -s /bin/bash ed 8 | please -n /bin/hostname 9 | EOT 10 | 11 | echo "please -n id" 12 | cat <<'EOT' | su -s /bin/bash ed 13 | please -n /usr/bin/id 14 | if test $? -ne 0; then 15 | exit 0; 16 | fi 17 | exit 1 18 | EOT 19 | 20 | echo "please -n who" 21 | cat <<'EOT' | su -s /bin/bash ed 22 | please -n /usr/bin/who 23 | if test $? -ne 0; then 24 | exit 0; 25 | fi 26 | exit 1 27 | EOT 28 | 29 | echo "(tester) please -l" 30 | cat <<'EOT' | su -s /bin/bash tester 31 | please -l | grep "You may not view your" 32 | EOT 33 | 34 | echo "(tester USER=ed) please bash" 35 | cat <<'EOT' | su -s /bin/bash tester 36 | export USER=ed 37 | please /bin/bash | egrep "You may not execute .* as root" 38 | EOT 39 | 40 | -------------------------------------------------------------------------------- /bintest/test_15_secure_path_test/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_secure_path_test/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_15_secure_path_test/please.ini: -------------------------------------------------------------------------------- 1 | [ed_all] 2 | permit = false 3 | name = ed 4 | regex = ^.*$ 5 | require_pass = false 6 | syslog = false 7 | 8 | -------------------------------------------------------------------------------- /bintest/test_15_secure_path_test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "secure path test" 6 | cat <<'EOT' | su -s /bin/bash ed 7 | export PLEASE=`which please`; 8 | export PATH=/root:/usr/bin:/bin 9 | $PLEASE .bashrc | grep 'command not found' 10 | EOT 11 | 12 | -------------------------------------------------------------------------------- /bintest/test_15_target_edit_as_bob/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_15_target_edit_as_bob/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_15_target_edit_as_bob/please.ini: -------------------------------------------------------------------------------- 1 | [ed_edit] 2 | name = ed 3 | permit=true 4 | type=edit 5 | regex=^/tmp/bobs_edit 6 | exitcmd=/usr/bin/touch /tmp/edited 7 | target=bob 8 | editmode = 600 9 | syslog = false 10 | require_pass = false 11 | 12 | [ed_grep] 13 | name = ed 14 | regex = ^.*$ 15 | syslog = false 16 | require_pass = false 17 | 18 | -------------------------------------------------------------------------------- /bintest/test_15_target_edit_as_bob/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "test target edit as bob" 6 | cat <<'EOT' | su -s /bin/bash ed 7 | export EDITOR=/usr/bin/tee 8 | export RUST_BACKTRACE=1 9 | echo "BOB WOZ ERE" | pleaseedit -t bob /tmp/bobs_edit 10 | echo "... exited $?" 11 | echo "... edited 1" 12 | echo "grep 'BOB WOZ ERE' /tmp/bobs_edit" | please bash 13 | ls -al /tmp/edited /tmp/bobs_edit | grep -- "-rw-------. 1 bob bob" 14 | stat /tmp/bobs_edit | egrep '^Access: \(0600' 15 | EOT 16 | 17 | echo "test -t -u conflicts" 18 | cat <<'EOT' | su -s /bin/bash ed 19 | export EDITOR=/usr/bin/tee 20 | export RUST_BACKTRACE=1 21 | please -u ed -t bob bash | grep "Cannot use -t and -u with conflicting values" 22 | please -u bob -t bob bash | grep "You may not" 23 | EOT 24 | 25 | 26 | -------------------------------------------------------------------------------- /bintest/test_16_command_not_found/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_16_command_not_found/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_16_command_not_found/please.ini: -------------------------------------------------------------------------------- 1 | [ed_all] 2 | syslog = false 3 | name = ed 4 | regex = ^.* 5 | require_pass = false 6 | 7 | -------------------------------------------------------------------------------- /bintest/test_16_command_not_found/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "test command not found" 6 | cat <<'EOT' | su -s /bin/bash ed 7 | set -e 8 | 9 | please command_not_found | grep 'command not found' 10 | EOT 11 | 12 | -------------------------------------------------------------------------------- /bintest/test_16_secure_path/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_16_secure_path/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_16_secure_path/please.ini: -------------------------------------------------------------------------------- 1 | [ed_all] 2 | syslog = false 3 | name = ed 4 | regex = ^/home/ed/foo$ 5 | require_pass = false 6 | search_path = /home/ed 7 | 8 | -------------------------------------------------------------------------------- /bintest/test_16_secure_path/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "test search path" 6 | cat <<'EOT' | su -s /bin/bash ed 7 | set -e 8 | 9 | printf '#!/bin/sh\n/bin/echo wibble\n' > /home/ed/foo 10 | chmod 755 /home/ed/foo 11 | 12 | export PATH=/home/ed 13 | 14 | /usr/bin/please foo | /bin/grep wibble 15 | EOT 16 | 17 | -------------------------------------------------------------------------------- /bintest/test_17_token_timeout_network/please.d/00_please.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_17_token_timeout_network/please.d/00_please.ini -------------------------------------------------------------------------------- /bintest/test_17_token_timeout_network/please.ini: -------------------------------------------------------------------------------- 1 | [ed_all] 2 | syslog = false 3 | name = ed 4 | rule = (/usr)?/bin/id 5 | require_pass = true 6 | token_timeout = 2 7 | 8 | [ed_list] 9 | syslog = false 10 | type=list 11 | name = ed 12 | target = .* 13 | require_pass = false 14 | 15 | -------------------------------------------------------------------------------- /bintest/test_17_token_timeout_network/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | apt-get update 6 | apt-get -y install expect 7 | 8 | echo "warm password token" 9 | cat <<'EOT' | su -s /bin/bash ed 10 | 11 | echo 'spawn expect' 12 | cat <<'EOF' | expect 13 | spawn bash 14 | send "/usr/bin/please -w\r" 15 | expect -re ".*password.*" 16 | send "password\r" 17 | expect "$" 18 | puts "warmed password" 19 | 20 | send "sleep 1\r" 21 | expect "$" 22 | send "please id\r" 23 | expect "root" 24 | 25 | send "sleep 3\r" 26 | expect "$" 27 | 28 | send "please id\r" 29 | expect { 30 | -re ".*password.*" { 31 | puts "found password prompt" 32 | exit 0 33 | } 34 | } 35 | puts "token did not timeout" 36 | exit 1 37 | EOF 38 | EOT 39 | 40 | -------------------------------------------------------------------------------- /bintest/test_2/please.d/00_ed.ini: -------------------------------------------------------------------------------- 1 | [tester] 2 | name = tester 3 | rule = .* 4 | permit = true 5 | reason = ree 6 | search_path = : 7 | require_pass = false 8 | -------------------------------------------------------------------------------- /bintest/test_2/please.d/00_root.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edneville/please/3ca6938f73ab0885dc442139d7bdf06f13a94105/bintest/test_2/please.d/00_root.ini -------------------------------------------------------------------------------- /bintest/test_2/please.ini: -------------------------------------------------------------------------------- 1 | [default:any] 2 | name = .* 3 | rule = .* 4 | syslog = false 5 | permit = false 6 | 7 | [inc] 8 | includedir = /etc/please.d 9 | -------------------------------------------------------------------------------- /bintest/test_2/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "test that when given a reason and search_path='' then echo not found" 6 | 7 | # since we can't find an echo then the rules traverse downwards 8 | # consider changing this to ensure that tester has a search_path 9 | # that's different, there's no definition for tester in the config, so the 10 | # message should be just 'echo' 11 | 12 | echo '/usr/bin/please -r ree echo worked' | su - tester \ 13 | | grep -E 'You may not execute "echo worked"' 14 | 15 | 16 | -------------------------------------------------------------------------------- /bintest/test_3_group_rules/please.d/00_please.ini: -------------------------------------------------------------------------------- 1 | [ed_group] 2 | syslog = false 3 | exact_name = ed 4 | rule = /bin/bash 5 | require_pass = false 6 | target_group = root 7 | 8 | [ed_edit_tmp] 9 | syslog = false 10 | exact_name = ed 11 | rule = /tmp/file 12 | require_pass = false 13 | target_group = nogroup 14 | type = edit 15 | 16 | [ed_edit_tmp] 17 | syslog = false 18 | exact_name = ed 19 | rule = /tmp/normal_file 20 | require_pass = false 21 | type = edit 22 | 23 | [ed_edit_tmp] 24 | syslog = false 25 | exact_name = ed 26 | rule = /tmp/bob_normal_file 27 | exact_target = bob 28 | require_pass = false 29 | type = edit 30 | 31 | [ed_edit_tmp] 32 | syslog = false 33 | exact_name = ed 34 | rule = /tmp/bob_nogroup_file 35 | exact_target = bob 36 | exact_target_group = nogroup 37 | require_pass = false 38 | type = edit 39 | 40 | # run 41 | [ed_edit_tmp] 42 | syslog = false 43 | exact_name = ed 44 | rule = /bin/bash 45 | exact_target = bob 46 | require_pass = false 47 | 48 | [ed_edit_tmp] 49 | syslog = false 50 | exact_name = ed 51 | rule = /bin/bash 52 | exact_target = bob 53 | exact_target_group = nogroup 54 | require_pass = false 55 | 56 | -------------------------------------------------------------------------------- /bintest/test_3_group_rules/please.ini: -------------------------------------------------------------------------------- 1 | [ed_all] 2 | includedir = /etc/please.d 3 | permit = false 4 | -------------------------------------------------------------------------------- /bintest/test_3_group_rules/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "test nogroup" 6 | cat < /tmp/perms 17 | chmod 600 /tmp/perms 18 | please -c /tmp/perms || exit 1 19 | 20 | echo "test config against mode 640" 21 | echo '#' > /tmp/perms 22 | chmod 640 /tmp/perms 23 | please -c /tmp/perms || exit 1 24 | 25 | echo "test config against mode 660" 26 | echo '#' > /tmp/perms 27 | chmod 660 /tmp/perms 28 | please -c /tmp/perms || true 29 | 30 | echo "test config against mode 646" 31 | echo '#' > /tmp/perms 32 | chmod 660 /tmp/perms 33 | please -c /tmp/perms || true 34 | 35 | echo "test config against mode 446" 36 | echo '#' > /tmp/perms 37 | chmod 660 /tmp/perms 38 | please -c /tmp/perms || true 39 | EOT 40 | 41 | -------------------------------------------------------------------------------- /completions/bash/please: -------------------------------------------------------------------------------- 1 | _please() 2 | { 3 | local cur prev words cword split 4 | _init_completion -s || return 5 | local i mode=normal 6 | 7 | [[ $1 == *pleaseedit ]] && mode=edit 8 | 9 | [[ $mode == normal ]] && 10 | for ((i = 1; i <= cword; i++)); do 11 | if [[ ${words[i]} != -* ]]; then 12 | local PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 13 | local root_command=${words[i]} 14 | _command_offset $i 15 | return 16 | fi 17 | [[ ${words[i]} == -[utgrad] || ${words[i]} == --@(user|target|group|reason|allowenv|dir) ]] 18 | ((i++)) 19 | done 20 | 21 | case "$prev" in 22 | --dir | -!(-*)[d]) 23 | _filedir 24 | return 25 | ;; 26 | --user | --target | -!(-*)[ut]) 27 | COMPREPLY=($(compgen -u -- "$cur")) 28 | return 29 | ;; 30 | --group | -!(-*)g) 31 | COMPREPLY=($(compgen -g -- "$cur")) 32 | return 33 | ;; 34 | esac 35 | 36 | $split && return 37 | 38 | if [[ $cur == -* ]]; then 39 | local opts=$(_parse_help "$1") 40 | COMPREPLY=($(compgen -W '${opts:-$(_parse_usage "$1")}' -- "$cur")) 41 | [[ ${COMPREPLY-} == *= ]] && compopt -o nospace 42 | return 43 | fi 44 | if [[ $mode == edit ]]; then 45 | _filedir 46 | fi 47 | } && 48 | complete -F _please please pleaseedit 49 | 50 | -------------------------------------------------------------------------------- /completions/zsh/_please: -------------------------------------------------------------------------------- 1 | #compdef please pleaseedit 2 | 3 | setopt localoptions extended_glob 4 | 5 | local environ e cmd cpp 6 | local -a args _comp_priv_prefix 7 | local -A opt_args 8 | 9 | zstyle -a ":completion:${curcontext}:" environ environ 10 | 11 | for e in "${environ[@]}" 12 | do local -x "$e" 13 | done 14 | 15 | args=( 16 | '(-u --user)'{-u+,--user=}'[run command (or edit file) as specified user]:user:_users' 17 | '(-t --target)'{-t+,--target=}'[run command (or edit file) as specified user]:user:_users' 18 | '(-g --group)'{-g+,--group=}'[run command as the specified group name or ID]:group:_groups' 19 | '(-n --noprompt)'{-n,--noprompt}'[do nothing if a password is required]' 20 | '(-w --warm)'{-w,--warm}"[update user's timestamp without running a command]" 21 | '(-p --purge)'{-p,--purge}'[purge timestamp file]' 22 | '(-r --reason)'{-r,--reason}'[provide reason for execution/edit]' 23 | '(-)'{-h,--help}'[display help message and exit]' 24 | '(-)'{-v,--version}'[display version information and exit]' 25 | ) 26 | 27 | if [[ $service = pleaseedit ]]; then 28 | args=( -A "-*" $args '*:file:_files' ) 29 | else 30 | cmd="$words[1]" 31 | cpp='_comp_priv_prefix=( 32 | $cmd -n 33 | ${(kv)opt_args[(I)(-[utgda]|--(user|target|group|dir|allowenv))]} 34 | )' 35 | args+=( 36 | '(-d --dir)'{-d+,--dir=}'[change the working directory before running command]:directory:_directories' 37 | '(-a --allowenv)'{-a+,--allowenv=}'[preserve comma separated user environment variables]::environment variable:_sequence _parameters -g "*export*"' 38 | '(-c --check)'{-c+,--check=}'[check config file]:file:_files' 39 | '(-l --list)'{-l,--list}"[list user's privileges or check a specific command]" 40 | "(-)1: :{ $cpp; _command_names -e }" 41 | "*:: :{ $cpp; _normal }" 42 | ) 43 | fi 44 | 45 | _arguments -s -S $args 46 | -------------------------------------------------------------------------------- /examples/pam/debian: -------------------------------------------------------------------------------- 1 | #%PAM-1.0 2 | 3 | # Set up user limits from /etc/security/limits.conf. 4 | session required pam_limits.so 5 | 6 | @include common-auth 7 | @include common-account 8 | @include common-session-noninteractive 9 | -------------------------------------------------------------------------------- /examples/pam/fedora: -------------------------------------------------------------------------------- 1 | #%PAM-1.0 2 | auth include system-auth 3 | account include system-auth 4 | password include system-auth 5 | session optional pam_keyinit.so revoke 6 | session required pam_limits.so 7 | session include system-auth 8 | -------------------------------------------------------------------------------- /examples/pam/macos: -------------------------------------------------------------------------------- 1 | auth required pam_opendirectory.so 2 | account required pam_permit.so 3 | password required pam_deny.so 4 | session required pam_permit.so 5 | -------------------------------------------------------------------------------- /examples/pam/suse: -------------------------------------------------------------------------------- 1 | #%PAM-1.0 2 | auth include system-auth 3 | account include system-auth 4 | password include system-auth 5 | session optional pam_keyinit.so revoke 6 | session required pam_limits.so 7 | session include system-auth 8 | -------------------------------------------------------------------------------- /examples/please.ini: -------------------------------------------------------------------------------- 1 | # 2 | ## Example rules and edit checkers, other suggestions welcomed 3 | ## Consider putting local *.ini configuration files in /etc/please.d 4 | # 5 | ## include *.ini files from the /etc/please.d directory (create it first) 6 | 7 | [include_local] 8 | includedir = /etc/please.d 9 | 10 | ## permit user 'jim' to run anything 11 | # 12 | #[jim_become_root] 13 | #name = jim 14 | #target = root 15 | #rule = .* 16 | #require_pass = false 17 | # 18 | ## permit user jim to modify the hosts file 19 | # 20 | #[jim_hosts] 21 | #name = jim 22 | #type = edit 23 | #target = root 24 | #rule = /etc/hosts 25 | #editmode = 644 26 | #require_pass = false 27 | # 28 | ## permit user jim to modify the /etc/please.ini and run a check on exit 29 | # 30 | #[jim_please] 31 | #name = jim 32 | #type = edit 33 | #target = root 34 | #rule = ^/etc/please(\.d/[\w.-]+)?\.ini$ 35 | #editmode = 600 36 | #require_pass = false 37 | #exitcmd = /usr/bin/please -c %{NEW} 38 | # 39 | ## permit all users to view their own ACL 40 | # 41 | #[list_own] 42 | #name=^%{USER}$ 43 | #permit=true 44 | #type=list 45 | #target=^%{USER}$ 46 | # 47 | ## config checkers 48 | # 49 | ## check fstab 50 | # 51 | #[fstab] 52 | #name=jim 53 | #type=edit 54 | #exitcmd=/bin/findmnt --verify --tab-file %{NEW} 55 | #target=root 56 | #rule=/etc/fstab 57 | #editmode=644 58 | # 59 | ## check openntpd config 60 | # 61 | #[edit_ntpd] 62 | #name=jim 63 | #type=edit 64 | #rule=/etc/openntpd/ntpd.conf 65 | #editmode=644 66 | #exitcmd=/usr/sbin/ntpd -f %{NEW} -n 67 | # 68 | ## check squid config 69 | # 70 | #[squid_check] 71 | #name=jim 72 | #type=edit 73 | #rule=/etc/squid/squid.conf 74 | #exitcmd=/usr/sbin/squid -k check -f %{NEW} 75 | #editmode=644 76 | # 77 | ## sshd 78 | # 79 | #[sshd] 80 | #name=jim 81 | #type=edit 82 | #exitcmd=/usr/sbin/sshd -t -f %{NEW} 83 | #editmode=644 84 | #rule=/etc/ssh/sshd_config 85 | # 86 | ## bind named.conf 87 | # 88 | #[named_conf] 89 | #name=jim 90 | #type=edit 91 | #exitcmd=/usr/sbin/named-checkconf %{NEW} 92 | #editmode=644 93 | #rule=/etc/bind/named.conf 94 | # 95 | ## bind zone 96 | ## setup /usr/local/bin/my-named-checkzone, like this: 97 | ## 98 | ## #!/bin/sh 99 | ## DOMAIN=`echo "$PLEASE_SOURCE_FILE" | sed -e 's%/etc/bind/db\.%%g'` 100 | ## /usr/sbin/named-checkzone "$DOMAIN" "$1" 101 | # 102 | #[named_zone] 103 | #name=jim 104 | #type=edit 105 | #exitcmd=/usr/local/bin/my-named-checkzone %{NEW} 106 | #editmode=644 107 | #rule=/etc/bind/db\.[\w.-]+ 108 | # 109 | ## nginx config 110 | # 111 | #[nginx_config] 112 | #name=jim 113 | #type=edit 114 | #exitcmd=/usr/sbin/nginx -t -c %{NEW} 115 | #editmode=644 116 | #rule=/etc/nginx/nginx.conf 117 | # 118 | #[varnish] 119 | #name=jim 120 | #type=edit 121 | #rule=/etc/varnish/[^/]+ 122 | #last=true 123 | #exitcmd=/usr/sbin/varnishd -j unix,user=vcache -C -f %{NEW} 124 | 125 | -------------------------------------------------------------------------------- /man/please.1: -------------------------------------------------------------------------------- 1 | .\" Automatically generated by Pandoc 2.17.1.1 2 | .\" 3 | .\" Define V font for inline verbatim, using C font in formats 4 | .\" that render this, and otherwise B font. 5 | .ie "\f[CB]x\f[]"x" \{\ 6 | . ftr V B 7 | . ftr VI BI 8 | . ftr VB B 9 | . ftr VBI BI 10 | .\} 11 | .el \{\ 12 | . ftr V CR 13 | . ftr VI CI 14 | . ftr VB CB 15 | . ftr VBI CBI 16 | .\} 17 | .TH "please" "1" "06 September 2024" "please 0.5.6" "User Manual" 18 | .hy 19 | .SH NAME 20 | .PP 21 | please - a tool for access elevation. 22 | .SH SYNOPSIS 23 | .PP 24 | \f[B]please /bin/bash\f[R] 25 | .PP 26 | \f[B]pleaseedit /etc/fstab\f[R] 27 | .PP 28 | \f[B]pleaseedit [-r/--reason \[dq]new fs\[dq]] /etc/fstab\f[R] 29 | .PP 30 | \f[B]pleaseedit [-g/--group groupname] filename\f[R] 31 | .PP 32 | \f[B]pleaseedit [-t/--target username] filename\f[R] 33 | .PP 34 | \f[B]pleaseedit [--resume] filename\f[R] 35 | .PP 36 | \f[B]please [-a/--allowenv list]\f[R] 37 | .PP 38 | \f[B]please [-c/--check] /etc/please.ini\f[R] 39 | .PP 40 | \f[B]please [-d/--dir directory] command\f[R] 41 | .PP 42 | \f[B]please [-e/--env environment] command\f[R] 43 | .PP 44 | \f[B]please [-g/--group groupname] command\f[R] 45 | .PP 46 | \f[B]please [-h/--help]\f[R] 47 | .PP 48 | \f[B]please [-t/--target username] backup tar -cvf - /home/data | 49 | \&...\f[R] 50 | .PP 51 | \f[B]please [-u/--user username] backup tar -cvf - /home/data | 52 | \&...\f[R] 53 | .PP 54 | \f[B]please [-l/--list]\f[R] 55 | .PP 56 | \f[B]please [-l/--list] [-t/--target username]\f[R] 57 | .PP 58 | \f[B]please [-l/--list] [-u/--user username]\f[R] 59 | .PP 60 | \f[B]please [-n/--noprompt] command\f[R] 61 | .PP 62 | \f[B]please [-r/--reason \[dq]sshd reconfigured, ticket 24365\[dq]] 63 | /etc/init.d/ssh restart\f[R] 64 | .PP 65 | \f[B]please [-p/--purge]\f[R] 66 | .PP 67 | \f[B]please [-w/--warm]\f[R] 68 | .SH DESCRIPTION 69 | .PP 70 | \f[B]please\f[R] and \f[B]pleaseedit\f[R] are sudo alternatives that 71 | have regex support and a simple approach to ACL. 72 | .PP 73 | The aim is to allow admins to delegate accurate principle of least 74 | privilege access with ease. 75 | \f[B]please.ini\f[R] allows for very specific and flexible regex defined 76 | permissions. 77 | .PP 78 | \f[B]pleaseedit\f[R] adds a layer of safety to editing files. 79 | The file is copied to /tmp, where it can be updated. 80 | When \f[B]EDITOR\f[R] exits cleanly the file is copied alongside the 81 | target, the file will then be renamed over the original, but if a 82 | \f[B]exitcmd\f[R] is configured it must exit cleanly first. 83 | \f[B]resume\f[R] will continue editing when \f[B]exitcmd\f[R] fails. 84 | .TP 85 | \f[B]-a\f[R]/\f[B]--allowenv list\f[R] 86 | allow environments separated by \f[B],\f[R] to be passed through 87 | .TP 88 | \f[B]-c\f[R]/\f[B]--check file\f[R] 89 | will check the syntax of a \f[B]please.ini\f[R] config file. 90 | Exits non-zero on error 91 | .TP 92 | \f[B]-d\f[R]/\f[B]--dir\f[R] 93 | will change directory to \f[B]dir\f[R] prior to executing the command 94 | .TP 95 | \f[B]-g\f[R]/\f[B]--group groupname\f[R] 96 | run or edit as groupname 97 | .TP 98 | \f[B]-h\f[R]/\f[B]--help\f[R] 99 | print help and exit 100 | .TP 101 | \f[B]-l\f[R]/\f[B]--list\f[R] 102 | to list rules 103 | .TP 104 | \f[B]-n\f[R]/\f[B]--noprompt\f[R] 105 | will not prompt for authentication and exits with a status of 1 106 | .TP 107 | \f[B]-p\f[R]/\f[B]--purge\f[R] 108 | will purge your current authentication token for the running user 109 | .TP 110 | \f[B]-r\f[R]/\f[B]--reason\f[R] \f[B][reason]\f[R] 111 | will add \f[B]reason\f[R] to the system log 112 | .TP 113 | \f[B]-t\f[R]/\f[B]--target\f[R] \f[B][username]\f[R] 114 | to execute command, or edit as target \f[B]username\f[R] 115 | .TP 116 | \f[B]-u\f[R]/\f[B]--user\f[R] \f[B][username]\f[R] 117 | to execute command, or edit as target \f[B]username\f[R] 118 | .TP 119 | \f[B]-v\f[R]/\f[B]--version\f[R] 120 | print version and exit 121 | .TP 122 | \f[B]-w\f[R]/\f[B]--warm\f[R] 123 | will warm an authentication token and exit 124 | .SH EXAMPLE USAGE 125 | .TP 126 | \f[B]please -t httpd /bin/bash\f[R] 127 | run a shell as the httpd user 128 | .TP 129 | \f[B]please -l\f[R] 130 | to list what you may run 131 | .TP 132 | \f[B]please -t \[dq]username\[dq] -l\f[R] 133 | to show what username may run. 134 | \f[B]username\f[R] must match the target regex in a \f[B]type=list\f[R] 135 | rule 136 | .TP 137 | \f[B]please -r \[aq]reloading apache2, change #123\[aq] systemctl reload apache2\f[R] 138 | to reload apache2 with a reason 139 | .TP 140 | \f[B]pleaseedit -r \[aq]adding new storage, ticket #24365\[aq] /etc/fstab\f[R] 141 | to use pleaseedit to modify \f[B]fstab\f[R] 142 | .PP 143 | Please see \f[B]please.ini\f[R] for configuration examples. 144 | .SH FILES 145 | .PP 146 | /etc/please.ini 147 | .SH CONTRIBUTIONS 148 | .PP 149 | I welcome pull requests with open arms. 150 | New features always considered. 151 | .SH BUGS 152 | .PP 153 | Found a bug? 154 | Please either open a ticket or send a pull request/patch. 155 | .SH SEE ALSO 156 | .PP 157 | \f[B]please.ini\f[R](5) 158 | .SH AUTHORS 159 | Ed Neville (ed-please\[at]s5h.net). 160 | -------------------------------------------------------------------------------- /man/please.ini.5: -------------------------------------------------------------------------------- 1 | .\" Automatically generated by Pandoc 2.17.1.1 2 | .\" 3 | .\" Define V font for inline verbatim, using C font in formats 4 | .\" that render this, and otherwise B font. 5 | .ie "\f[CB]x\f[]"x" \{\ 6 | . ftr V B 7 | . ftr VI BI 8 | . ftr VB B 9 | . ftr VBI BI 10 | .\} 11 | .el \{\ 12 | . ftr V CR 13 | . ftr VI CI 14 | . ftr VB CB 15 | . ftr VBI CBI 16 | .\} 17 | .TH "please.ini" "5" "06 September 2024" "please 0.5.6" "User Manual" 18 | .hy 19 | .SH NAME 20 | .PP 21 | please.ini - configuration file for access 22 | .SH DESCRIPTION 23 | .PP 24 | The \f[B]please.ini\f[R] file contains one or more \f[B][sections]\f[R] 25 | that hold ACL for users of the \f[B]please\f[R] and \f[B]pleaseedit\f[R] 26 | programs. 27 | .PP 28 | \f[V]please.ini\f[R] is an ini file, sections can be named with a short 29 | description of what the section provides. 30 | You may then find this helpful when listing rights with \f[B]please 31 | -l\f[R]. 32 | .PP 33 | Rules are read and applied in the order they are presented in the 34 | configuration file. 35 | For example, if the user matches a permit rule to run a command in an 36 | early section, but in a later section matches criteria for a deny and no 37 | further matches, then the user will not be permitted to run that 38 | command. 39 | The last match wins. 40 | .PP 41 | The properties permitted are described below and should appear at most 42 | once per section. 43 | If a property is used more than once in a section, the last one will be 44 | used. 45 | .SH SECTION OPTIONS 46 | .TP 47 | \f[B][section-name]\f[R] 48 | section name, shown in list mode 49 | .TP 50 | \f[B]include=[file]\f[R] 51 | read ini file, and continue to next section 52 | .TP 53 | \f[B]includedir=[directory]\f[R] 54 | read .ini files in directory, and continue to next section, if the 55 | directory does not exist config parse will fail 56 | .PP 57 | Sections with a name starting \f[B]default\f[R] will retain match 58 | actions including implicit \f[B]permit\f[R], therefore setting 59 | \f[B]permit=false\f[R] in the default block and \f[B]permit=true\f[R] 60 | elsewhere is advised. 61 | .SH MATCHES 62 | .TP 63 | \f[B]name=[regex]\f[R] 64 | mandatory, the user or \f[B]group\f[R] (see below) to match against 65 | .TP 66 | \f[B]target=[regex]\f[R] 67 | user to execute or list as, defaults to \f[B]root\f[R] 68 | .TP 69 | \f[B]target_group=[regex]\f[R] 70 | requires that the user runs with \f[B]--group\f[R] to run or edit with 71 | the match 72 | .TP 73 | \f[B]rule=[regex]\f[R] 74 | the regular expression that the command or edit path matches against, 75 | defaults to \[ha]$ 76 | .TP 77 | \f[B]notbefore=[YYYYmmdd|YYYYmmddHHMMSS]\f[R] 78 | will add HHMMSS as 00:00:00 to the date if not given, defaults to never 79 | .TP 80 | \f[B]notafter=[YYYYmmdd|YYYYmmddHHMMSS]\f[R] 81 | will add 23:59:59 to the date if not given, defaults to never 82 | .TP 83 | \f[B]datematch=[Day dd Mon HH:MM:SS UTC YYYY]\f[R] 84 | regex to match a date string with 85 | .TP 86 | \f[B]type=[edit/run/list]\f[R] 87 | this section\[cq]s mode behaviour, defaults to \f[B]run\f[R], edit = 88 | \f[B]pleaseedit\f[R] entry, list = user access rights listing 89 | .TP 90 | \f[B]group=[true|false]\f[R] 91 | defaults to false, when true, the \f[B]name\f[R] (above) refers to a 92 | group rather than a user 93 | .TP 94 | \f[B]hostname=[regex]\f[R] 95 | permitted hostnames where this may apply. 96 | A hostname defined as \f[B]any\f[R] or \f[B]localhost\f[R] will always 97 | match. 98 | Defaults to localhost 99 | .TP 100 | \f[B]dir=[regex]\f[R] 101 | permitted directories to run within 102 | .TP 103 | \f[B]permit_env=[regex]\f[R] 104 | allow environments that match \f[B]regex\f[R] to optionally pass through 105 | .TP 106 | \f[B]search_path=[string]\f[R] 107 | configure a \f[B]:\f[R] separated directory list to locate the binary to 108 | execute, does not configure a \f[B]PATH\f[R] environment and is searched 109 | as the user running \f[B]please\f[R], not as the \f[B]target\f[R] user 110 | (no plans to change that at present) 111 | .PP 112 | \f[B]regex\f[R] is a regular expression, \f[B]%{USER}\f[R] will expand 113 | to the user who is currently running \f[V]please\f[R], 114 | \f[B]%{HOSTNAME}\f[R] expands to the hostname. 115 | See below for examples. 116 | Other \f[B]%{}\f[R] expansions may be added at a later date. 117 | .PP 118 | Spaces within arguments will be substituted as \f[B]`\[rs]\ '\f[R] 119 | (backslash space). 120 | Use \f[B]\[ha]/bin/echo hello\[rs]\[rs] world$\f[R] to match 121 | \f[B]/bin/echo \[lq]hello world\[rq]\f[R], note that \f[B]\[rs]\f[R] is 122 | a regex escape character so it must be escaped, therefore matching a 123 | space becomes \f[B]`\[rs]\[rs]\ '\f[R] (backslash backslash space). 124 | .PP 125 | To match a \f[B]\[rs]\f[R] (backslash), the hex code \f[B]\[rs]x5c\f[R] 126 | can be used. 127 | .PP 128 | To match the string \f[B]%{USER}\f[R], the sequence 129 | \f[B]\[rs]x25\[rs]{USER\[rs]}\f[R] can be used. 130 | .PP 131 | Rules starting \f[B]exact\f[R] are string matches and not 132 | \f[B]regex\f[R] processed and take precedence over \f[B]regex\f[R] 133 | matches. 134 | .TP 135 | \f[B]exact_name=[string]\f[R] 136 | only permit a user/group name that matches exactly 137 | .TP 138 | \f[B]exact_hostname=[string]\f[R] 139 | only permit a hostname that matches exactly 140 | .TP 141 | \f[B]exact_target=[string]\f[R] 142 | only permit a target that matches exactly 143 | .TP 144 | \f[B]exact_target_group=[groupname]\f[R] 145 | requires that the user runs with \f[B]--group\f[R] to run or edit as 146 | \f[B]groupname\f[R] 147 | .TP 148 | \f[B]exact_rule=[string]\f[R] 149 | only permit a command rule that matches exactly 150 | .TP 151 | \f[B]exact_dir=[string]\f[R] 152 | only permit a dir that matches exactly 153 | .SH ACTIONS 154 | .TP 155 | \f[B]permit=[true|false]\f[R] 156 | permit or disallow the entry, defaults to true 157 | .TP 158 | \f[B]require_pass=[true|false]\f[R] 159 | if entry matches, require a password, defaults to true 160 | .TP 161 | \f[B]timeout=[number]\f[R] 162 | length of timeout in whole seconds to wait for password input 163 | .TP 164 | \f[B]last=[true|false]\f[R] 165 | if true, stop processing when entry is matched, defaults to false 166 | .TP 167 | \f[B]reason=[true|false|regex]\f[R] 168 | require a reason for execution/edit. 169 | If reason is \f[B]true\f[R] then any reason will satisfy. 170 | Any string other than \f[B]true\f[R] or \f[B]false\f[R] will be treated 171 | as a regex match. 172 | Defaults to false 173 | .TP 174 | \f[B]token_timeout=[number]\f[R] 175 | length of timeout for token authentication in whole seconds (default 176 | 600) 177 | .TP 178 | \f[B]syslog=[true|false]\f[R] 179 | log this activity to syslog, defaults to true 180 | .TP 181 | \f[B]env_assign.[key]=[value]\f[R] 182 | assign \f[B]value\f[R] to environment \f[B]key\f[R] 183 | .TP 184 | \f[B]editmode=[octal mode|keep]\f[R] 185 | (\f[B]type=edit\f[R]) set the file mode bits on replacement file to 186 | octal mode. 187 | When set to \f[B]keep\f[R] use the existing file mode. 188 | If the file is not present, or mode is not declared, then mode falls 189 | back to 0600. 190 | If there is a file present, then the mode is read and used just prior to 191 | file rename 192 | .TP 193 | \f[B]exitcmd=[program]\f[R] 194 | (\f[B]type=edit\f[R]) run program after editor exits as the target user, 195 | if exit is zero, continue with file replacement. 196 | \f[B]%{NEW}\f[R] and \f[B]%{OLD}\f[R] placeholders expand to new and old 197 | edit files 198 | .SH EXAMPLES 199 | .PP 200 | To allow all commands, you can use a greedy match (\f[B]\[ha].*$\f[R]). 201 | You should reduce this to the set of acceptable commands though. 202 | .IP 203 | .nf 204 | \f[C] 205 | [user_jim_root] 206 | name = jim 207 | target = root 208 | rule = \[ha].*$ 209 | \f[R] 210 | .fi 211 | .PP 212 | If you wish to permit a user to view another\[cq]s command set, then you 213 | may do this using \f[B]type=list\f[R] (\f[B]run\f[R] by default). 214 | To list another user, they must match the \f[B]target\f[R] regex. 215 | .IP 216 | .nf 217 | \f[C] 218 | [user_jim_list_root] 219 | name = jim 220 | type = list 221 | target = root 222 | \f[R] 223 | .fi 224 | .PP 225 | \f[B]type\f[R] may also be \f[B]edit\f[R] if you wish to permit a file 226 | edit with \f[B]pleaseedit\f[R]. 227 | .IP 228 | .nf 229 | \f[C] 230 | [user_jim_edit_hosts] 231 | name = jim 232 | type = edit 233 | target = root 234 | rule = \[ha]/etc/hosts$ 235 | editmode = 644 236 | \f[R] 237 | .fi 238 | .PP 239 | Naming sections should help later when listing permissions. 240 | .PP 241 | Below, user \f[B]mandy\f[R] may run \f[B]du\f[R] without needing a 242 | password, but must enter her password for a \f[B]bash\f[R] running as 243 | root: 244 | .IP 245 | .nf 246 | \f[C] 247 | [mandy_du] 248 | name = mandy 249 | rule = \[ha](/usr)?/bin/du .*$ 250 | require_pass = false 251 | [mandy_some] 252 | name = mandy 253 | rule = \[ha](/usr)?/bin/bash$ 254 | require_pass = true 255 | \f[R] 256 | .fi 257 | .PP 258 | The rule \f[B]regex\f[R] can include repetitions. 259 | To permit running \f[B]wc\f[R] to count the lines in the log files (we 260 | don\[cq]t know how many there are) in \f[B]/var/log\f[R]. 261 | This sort of regex will allow multiple instances of a \f[B]()\f[R] group 262 | with \f[B]+\f[R], which is used to define the character class 263 | \f[B][a-zA-Z0-9-]+\f[R], the numeric class \f[B]\f[R] and the group near 264 | the end of the line. 265 | In other words, multiple instances of files in \f[B]/var/log\f[R] that 266 | may end in common log rotate forms \f[B]-YYYYMMDD\f[R] or \f[B].N\f[R]. 267 | .PP 268 | This will permit commands such as the following, note how for efficiency 269 | find will combine arguments with \f[B]+\f[R] into fewer invocations. 270 | \f[B]xargs\f[R] could have been used in place of \f[B]find\f[R]. 271 | .IP 272 | .nf 273 | \f[C] 274 | $ find /var/log -type f -exec please /usr/bin/wc {} \[rs]+ 275 | \f[R] 276 | .fi 277 | .PP 278 | Here is a sample for the above scenario: 279 | .IP 280 | .nf 281 | \f[C] 282 | [user_jim_root_wc] 283 | name = jim 284 | target = root 285 | permit = true 286 | rule = \[ha]/usr/bin/wc (/var/log/[a-zA-Z0-9-]+(\[rs].\[rs]d+)?(\[rs]s)?)+$ 287 | \f[R] 288 | .fi 289 | .PP 290 | User jim may only start or stop a docker container: 291 | .IP 292 | .nf 293 | \f[C] 294 | [user_jim_root_docker] 295 | name = jim 296 | target = root 297 | permit = true 298 | rule = \[ha]/usr/bin/docker (start|stop) \[rs]S+ 299 | \f[R] 300 | .fi 301 | .PP 302 | User ben may only edit \f[B]/etc/fstab\f[R], and afterwards check the 303 | fstab file: 304 | .IP 305 | .nf 306 | \f[C] 307 | [ben_fstab] 308 | name = ben 309 | target = root 310 | permit = true 311 | type = edit 312 | editmode = 644 313 | rule = \[ha]/etc/fstab$ 314 | exitcmd = /bin/findmnt --verify --tab-file %{NEW} 315 | \f[R] 316 | .fi 317 | .PP 318 | User ben may list only users \f[B]eng\f[R], \f[B]net\f[R] and 319 | \f[B]dba\f[R]: 320 | .IP 321 | .nf 322 | \f[C] 323 | [ben_ops] 324 | name = ben 325 | permit = true 326 | type = list 327 | target = \[ha](eng|net|dba)ops$ 328 | \f[R] 329 | .fi 330 | .PP 331 | All users may list their own permissions. 332 | You may or may not wish to do this if you consider permitting a view of 333 | the rules to be a security risk. 334 | .IP 335 | .nf 336 | \f[C] 337 | [list_own] 338 | name = \[ha]%{USER}$ 339 | permit = true 340 | type = list 341 | target = \[ha]%{USER}$ 342 | \f[R] 343 | .fi 344 | .SH DEFAULT SECTION 345 | .PP 346 | Sections that are named starting with \f[B]default\f[R] retain their 347 | actions, which can be useful for turning off \f[B]syslog\f[R] or setting 348 | a \f[B]token_timeout\f[R] globally, for example, but they will retain 349 | \f[B]permit\f[R] which implicitly is \f[B]true\f[R], it is therefore 350 | sensible to negate this (setting \f[B]permit=false\f[R]) and set 351 | \f[B]permit=true\f[R] in subsequent sections as needed. 352 | .IP 353 | .nf 354 | \f[C] 355 | [default:nosyslog] 356 | name = .* 357 | rule = .* 358 | require_pass = false 359 | syslog = false 360 | permit = false 361 | token_timeout = 1800 362 | [mailusers] 363 | name = mailadm 364 | group = true 365 | rule = \[ha]/usr/sbin/postcat$ 366 | require_pass = true 367 | permit = true 368 | \f[R] 369 | .fi 370 | .SH EXITCMD 371 | .PP 372 | When the user completes their edit, and the editor exits cleanly, if 373 | \f[B]exitcmd\f[R] is included then this program will run as the target 374 | user. 375 | If the program also exits cleanly then the temporary edit will be copied 376 | to the destination. 377 | .PP 378 | \f[B]%{OLD}\f[R] and \f[B]%{NEW}\f[R] will expand to the old (existing 379 | source) file and edit candidate, respectively. 380 | To verify a file edit, \f[B]ben\f[R]\[cq]s entry to check 381 | \f[B]/etc/hosts\f[R] after clean exit could look like this: 382 | .IP 383 | .nf 384 | \f[C] 385 | [ben_ops] 386 | name = ben 387 | permit = true 388 | type = edit 389 | editmode = 644 390 | rule = \[ha]/etc/hosts$ 391 | exitcmd = /usr/local/bin/check_hosts %{OLD} %{NEW} 392 | \f[R] 393 | .fi 394 | .PP 395 | \f[B]/usr/local/bin/check_hosts\f[R] takes two arguments, the original 396 | file as the first argument and the modify candidate as the second 397 | argument. 398 | If \f[B]check_hosts\f[R] terminates zero, then the edit is considered 399 | clean and the original file is replaced with the candidate. 400 | Otherwise the edit file is not copied and is left, \f[B]pleaseedit\f[R] 401 | will exit with the return value from \f[B]check_hosts\f[R]. 402 | .PP 403 | A common \f[B]exitcmd\f[R] is to check the validity of 404 | \f[B]please.ini\f[R], shown below. 405 | This permits members of the \f[B]admin\f[R] group to edit 406 | \f[B]/etc/please.ini\f[R] if they provide a reason (\f[B]-r\f[R]). 407 | Upon clean exit from the editor the tmp file will be syntax checked. 408 | .IP 409 | .nf 410 | \f[C] 411 | [please_ini] 412 | name = admins 413 | group = true 414 | reason = true 415 | rule = /etc/please.ini 416 | type = edit 417 | editmode = 600 418 | exitcmd = /usr/bin/please -c %{NEW} 419 | \f[R] 420 | .fi 421 | .SH DATED RANGES 422 | .PP 423 | For large environments it is not unusual for a third party to require 424 | access during a short time frame for debugging. 425 | To accommodate this there are the \f[B]notbefore\f[R] and 426 | \f[B]notafter\f[R] time brackets. 427 | These can be either \f[B]YYYYmmdd\f[R] or \f[B]YYYYmmddHHMMSS\f[R]. 428 | .PP 429 | The whole day is considered when using the shorter date form of 430 | \f[B]YYYYmmdd\f[R]. 431 | .PP 432 | Many enterprises may wish to permit periods of access to a user for a 433 | limited time only, even if that individual is considered to have a 434 | permanent role. 435 | .PP 436 | User joker can do what they want as root on 1st April 2021: 437 | .IP 438 | .nf 439 | \f[C] 440 | [joker_april_first] 441 | name = joker 442 | target = root 443 | permit = true 444 | notbefore = 20210401 445 | notafter = 20210401 446 | rule = \[ha]/bin/bash 447 | \f[R] 448 | .fi 449 | .SH DATEMATCHES 450 | .PP 451 | \f[B]datematch\f[R] matches against the date string \f[B]Day dd mon 452 | HH:MM:SS UTC Year\f[R]. 453 | This enables calendar style date matches. 454 | .PP 455 | Note that the day of the month (\f[B]dd\f[R]) will be padded with spaces 456 | if less than two characters wide. 457 | .PP 458 | You can permit a group of users to run 459 | \f[B]/usr/local/housekeeping/\f[R] scripts every Monday: 460 | .IP 461 | .nf 462 | \f[C] 463 | [l2_housekeeping] 464 | name = l2users 465 | group = true 466 | target = root 467 | permit = true 468 | rule = /usr/local/housekeeping/tidy_(logs|images|mail) 469 | datematch = \[ha]Mon\[rs]s+.* 470 | \f[R] 471 | .fi 472 | .SH REASONS 473 | .PP 474 | When \f[B]reason=true\f[R], a user must pass a reason with the 475 | \f[B]-r\f[R] option to \f[B]please\f[R] and \f[B]pleaseedit\f[R]. 476 | Some organisations may prefer a reason to be logged when a command is 477 | executed. 478 | This can be helpful for some situations where something such as 479 | \f[B]mkfs\f[R] or \f[B]useradd\f[R] might be preferable to be logged 480 | against a ticket. 481 | .IP 482 | .nf 483 | \f[C] 484 | [l2_user_admin] 485 | name = l2users 486 | group = true 487 | target = root 488 | permit = true 489 | reason = true 490 | rule = \[ha]/usr/sbin/useradd -m \[rs]w+$ 491 | \f[R] 492 | .fi 493 | .PP 494 | Or, if tickets have a known prefix: 495 | .IP 496 | .nf 497 | \f[C] 498 | reason = .*(bug|incident|ticket|change)\[rs]d+.* 499 | \f[R] 500 | .fi 501 | .PP 502 | Perhaps you want to add a mini molly-guard where the hostname must 503 | appear in the reason: 504 | .IP 505 | .nf 506 | \f[C] 507 | [user_poweroff] 508 | name = l2users 509 | group = true 510 | rule = (/usr)?/s?bin/(shutdown( -h now)?|poweroff|reboot) 511 | require_pass = true 512 | reason = .*%{HOSTNAME}.* 513 | \f[R] 514 | .fi 515 | .SH DIR 516 | .PP 517 | In some situations you may only want a command to run within a set of 518 | directories. 519 | The directory is specified with the \f[B]-d\f[R] argument to 520 | \f[B]please\f[R]. 521 | For example, a program may output to the current working directory, 522 | which may only be desirable in certain locations. 523 | .IP 524 | .nf 525 | \f[C] 526 | [eng_build_aliases] 527 | name = l2users 528 | group = true 529 | dir = \[ha]/etc/mail$ 530 | rule = \[ha]/usr/local/bin/build_aliases$ 531 | \f[R] 532 | .fi 533 | .SH LAST 534 | .PP 535 | \f[B]last = true\f[R] stops processing at a match: 536 | .IP 537 | .nf 538 | \f[C] 539 | [mkfs] 540 | name = l2users 541 | group = true 542 | target = root 543 | permit = true 544 | reason = true 545 | rule = \[ha]/sbin/mkfs.(ext[234]|xfs) /dev/sd[bcdefg]\[rs]d?$ 546 | last = true 547 | \f[R] 548 | .fi 549 | .PP 550 | For simplicity, there is no need to process other configured rules if 551 | certain that the \f[B]l2users\f[R] group are safe to execute this. 552 | \f[B]last\f[R] should only be used in situations where there will never 553 | be something that could contradict the match in an undesired way later. 554 | .SH SYSLOG 555 | .PP 556 | By default entries are logged to syslog. 557 | If you do not wish an entry to be logged then specify 558 | \f[B]syslog=false\f[R]. 559 | In this case \f[B]jim\f[R] can run anything in \f[B]/usr/bin/\f[R] as 560 | root and it will not be logged. 561 | .IP 562 | .nf 563 | \f[C] 564 | [maverick] 565 | syslog = false 566 | name = jim 567 | rule = /usr/bin/.* 568 | reason = false 569 | \f[R] 570 | .fi 571 | .SH FILES 572 | .PP 573 | /etc/please.ini 574 | .SH NOTES 575 | .PP 576 | At a later date repeated properties within the same section may be 577 | treated as a match list. 578 | .SH CONTRIBUTIONS 579 | .PP 580 | I welcome pull requests with open arms. 581 | New features always considered. 582 | .SH BUGS 583 | .PP 584 | Found a bug? 585 | Please either open a ticket or send a pull request/patch. 586 | .SH SEE ALSO 587 | .PP 588 | \f[B]please\f[R](1) 589 | .SH AUTHORS 590 | Ed Neville (ed-please\[at]s5h.net). 591 | -------------------------------------------------------------------------------- /please.ini.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: please.ini 3 | section: 5 4 | header: User Manual 5 | footer: please 0.5.6 6 | author: Ed Neville (ed-please@s5h.net) 7 | date: 06 September 2024 8 | --- 9 | 10 | # NAME 11 | 12 | please.ini - configuration file for access 13 | 14 | # DESCRIPTION 15 | 16 | The **please.ini** file contains one or more **[sections]** that hold ACL for users of the **please** and **pleaseedit** programs. 17 | 18 | `please.ini` is an ini file, sections can be named with a short description of what the section provides. You may then find this helpful when listing rights with **please -l**. 19 | 20 | Rules are read and applied in the order they are presented in the configuration file. For example, if the user matches a permit rule to run a command in an early section, but in a later section matches criteria for a deny and no further matches, then the user will not be permitted to run that command. The last match wins. 21 | 22 | The properties permitted are described below and should appear at most once per section. If a property is used more than once in a section, the last one will be used. 23 | 24 | # SECTION OPTIONS 25 | 26 | **[section-name]** 27 | : section name, shown in list mode 28 | 29 | **include=[file]** 30 | : read ini file, and continue to next section 31 | 32 | **includedir=[directory]** 33 | : read .ini files in directory, and continue to next section, if the directory does not exist config parse will fail 34 | 35 | Sections with a name starting **default** will retain match actions including implicit **permit**, therefore setting **permit=false** in the default block and **permit=true** elsewhere is advised. 36 | 37 | # MATCHES 38 | 39 | **name=[regex]** 40 | : mandatory, the user or **group** (see below) to match against 41 | 42 | **target=[regex]** 43 | : user to execute or list as, defaults to **root** 44 | 45 | **target_group=[regex]** 46 | : requires that the user runs with **\-\-group** to run or edit with the match 47 | 48 | **rule=[regex]** 49 | : the regular expression that the command or edit path matches against, defaults to ^$ 50 | 51 | **notbefore=[YYYYmmdd|YYYYmmddHHMMSS]** 52 | : will add HHMMSS as 00:00:00 to the date if not given, defaults to never 53 | 54 | **notafter=[YYYYmmdd|YYYYmmddHHMMSS]** 55 | : will add 23:59:59 to the date if not given, defaults to never 56 | 57 | **datematch=[Day dd Mon HH:MM:SS UTC YYYY]** 58 | : regex to match a date string with 59 | 60 | **type=[edit/run/list]** 61 | : this section's mode behaviour, defaults to **run**, edit = **pleaseedit** entry, list = user access rights listing 62 | 63 | **group=[true|false]** 64 | : defaults to false, when true, the **name** (above) refers to a group rather than a user 65 | 66 | **hostname=[regex]** 67 | : permitted hostnames where this may apply. A hostname defined as **any** or **localhost** will always match. Defaults to localhost 68 | 69 | **dir=[regex]** 70 | : permitted directories to run within 71 | 72 | **permit_env=[regex]** 73 | : allow environments that match **regex** to optionally pass through 74 | 75 | **search_path=[string]** 76 | : configure a **:** separated directory list to locate the binary to execute, does not configure a **PATH** environment and is searched as the user running **please**, not as the **target** user (no plans to change that at present) 77 | 78 | **regex** is a regular expression, **%{USER}** will expand to the user who is currently running `please`, **%{HOSTNAME}** expands to the hostname. See below for examples. Other **%{}** expansions may be added at a later date. 79 | 80 | Spaces within arguments will be substituted as **'\\\ '** (backslash space). Use **^/bin/echo hello\\\\ world$** to match **/bin/echo "hello world"**, note that **\\** is a regex escape character so it must be escaped, therefore matching a space becomes **'\\\\\ '** (backslash backslash space). 81 | 82 | To match a **\\** (backslash), the hex code **\\x5c** can be used. 83 | 84 | To match the string **%{USER}**, the sequence **\\x25\\{USER\\}** can be used. 85 | 86 | Rules starting **exact** are string matches and not **regex** processed and take precedence over **regex** matches. 87 | 88 | **exact_name=[string]** 89 | : only permit a user/group name that matches exactly 90 | 91 | **exact_hostname=[string]** 92 | : only permit a hostname that matches exactly 93 | 94 | **exact_target=[string]** 95 | : only permit a target that matches exactly 96 | 97 | **exact_target_group=[groupname]** 98 | : requires that the user runs with **\-\-group** to run or edit as **groupname** 99 | 100 | **exact_rule=[string]** 101 | : only permit a command rule that matches exactly 102 | 103 | **exact_dir=[string]** 104 | : only permit a dir that matches exactly 105 | 106 | # ACTIONS 107 | 108 | **permit=[true|false]** 109 | : permit or disallow the entry, defaults to true 110 | 111 | **require_pass=[true|false]** 112 | : if entry matches, require a password, defaults to true 113 | 114 | **timeout=[number]** 115 | : length of timeout in whole seconds to wait for password input 116 | 117 | **last=[true|false]** 118 | : if true, stop processing when entry is matched, defaults to false 119 | 120 | **reason=[true|false|regex]** 121 | : require a reason for execution/edit. If reason is **true** then any reason will satisfy. Any string other than **true** or **false** will be treated as a regex match. Defaults to false 122 | 123 | **token_timeout=[number]** 124 | : length of timeout for token authentication in whole seconds (default 600) 125 | 126 | **syslog=[true|false]** 127 | : log this activity to syslog, defaults to true 128 | 129 | **env_assign.[key]=[value]** 130 | : assign **value** to environment **key** 131 | 132 | **editmode=[octal mode|keep]** 133 | : (**type=edit**) set the file mode bits on replacement file to octal mode. When set to **keep** use the existing file mode. If the file is not present, or mode is not declared, then mode falls back to 0600. If there is a file present, then the mode is read and used just prior to file rename 134 | 135 | **exitcmd=[program]** 136 | : (**type=edit**) run program after editor exits as the target user, if exit is zero, continue with file replacement. **%{NEW}** and **%{OLD}** placeholders expand to new and old edit files 137 | 138 | # EXAMPLES 139 | 140 | To allow all commands, you can use a greedy match (**^.\*$**). You should reduce this to the set of acceptable commands though. 141 | 142 | ``` 143 | [user_jim_root] 144 | name = jim 145 | target = root 146 | rule = ^.*$ 147 | ``` 148 | 149 | If you wish to permit a user to view another's command set, then you may do this using **type=list** (**run** by default). To list another user, they must match the **target** regex. 150 | 151 | ``` 152 | [user_jim_list_root] 153 | name = jim 154 | type = list 155 | target = root 156 | ``` 157 | 158 | **type** may also be **edit** if you wish to permit a file edit with **pleaseedit**. 159 | 160 | ``` 161 | [user_jim_edit_hosts] 162 | name = jim 163 | type = edit 164 | target = root 165 | rule = ^/etc/hosts$ 166 | editmode = 644 167 | ``` 168 | 169 | Naming sections should help later when listing permissions. 170 | 171 | Below, user **mandy** may run **du** without needing a password, but must enter her password for a **bash** running as root: 172 | 173 | ``` 174 | [mandy_du] 175 | name = mandy 176 | rule = ^(/usr)?/bin/du .*$ 177 | require_pass = false 178 | [mandy_some] 179 | name = mandy 180 | rule = ^(/usr)?/bin/bash$ 181 | require_pass = true 182 | ``` 183 | 184 | The rule **regex** can include repetitions. To permit running **wc** to count the lines in the log files (we don't know how many there are) in **/var/log**. This sort of regex will allow multiple instances of a **()** group with **+**, which is used to define the character class **[a-zA-Z0-9-]+**, the numeric class **\d+** and the group near the end of the line. In other words, multiple instances of files in **/var/log** that may end in common log rotate forms **-YYYYMMDD** or **.N**. 185 | 186 | This will permit commands such as the following, note how for efficiency find will combine arguments with **\+** into fewer invocations. **xargs** could have been used in place of **find**. 187 | 188 | ``` 189 | $ find /var/log -type f -exec please /usr/bin/wc {} \+ 190 | ``` 191 | 192 | Here is a sample for the above scenario: 193 | 194 | ``` 195 | [user_jim_root_wc] 196 | name = jim 197 | target = root 198 | permit = true 199 | rule = ^/usr/bin/wc (/var/log/[a-zA-Z0-9-]+(\.\d+)?(\s)?)+$ 200 | ``` 201 | 202 | User jim may only start or stop a docker container: 203 | 204 | ``` 205 | [user_jim_root_docker] 206 | name = jim 207 | target = root 208 | permit = true 209 | rule = ^/usr/bin/docker (start|stop) \S+ 210 | ``` 211 | 212 | User ben may only edit **/etc/fstab**, and afterwards check the fstab file: 213 | 214 | ``` 215 | [ben_fstab] 216 | name = ben 217 | target = root 218 | permit = true 219 | type = edit 220 | editmode = 644 221 | rule = ^/etc/fstab$ 222 | exitcmd = /bin/findmnt --verify --tab-file %{NEW} 223 | ``` 224 | 225 | User ben may list only users **eng**, **net** and **dba**: 226 | 227 | ``` 228 | [ben_ops] 229 | name = ben 230 | permit = true 231 | type = list 232 | target = ^(eng|net|dba)ops$ 233 | ``` 234 | 235 | All users may list their own permissions. You may or may not wish to do this if you consider permitting a view of the rules to be a security risk. 236 | 237 | ``` 238 | [list_own] 239 | name = ^%{USER}$ 240 | permit = true 241 | type = list 242 | target = ^%{USER}$ 243 | ``` 244 | 245 | # DEFAULT SECTION 246 | 247 | Sections that are named starting with **default** retain their actions, which can be useful for turning off **syslog** or setting a **token_timeout** globally, for example, but they will retain **permit** which implicitly is **true**, it is therefore sensible to negate this (setting **permit=false**) and set **permit=true** in subsequent sections as needed. 248 | 249 | ``` 250 | [default:nosyslog] 251 | name = .* 252 | rule = .* 253 | require_pass = false 254 | syslog = false 255 | permit = false 256 | token_timeout = 1800 257 | [mailusers] 258 | name = mailadm 259 | group = true 260 | rule = ^/usr/sbin/postcat$ 261 | require_pass = true 262 | permit = true 263 | ``` 264 | 265 | # EXITCMD 266 | 267 | When the user completes their edit, and the editor exits cleanly, if **exitcmd** is included then this program will run as the target user. If the program also exits cleanly then the temporary edit will be copied to the destination. 268 | 269 | **%{OLD}** and **%{NEW}** will expand to the old (existing source) file and edit candidate, respectively. To verify a file edit, **ben**'s entry to check **/etc/hosts** after clean exit could look like this: 270 | 271 | ``` 272 | [ben_ops] 273 | name = ben 274 | permit = true 275 | type = edit 276 | editmode = 644 277 | rule = ^/etc/hosts$ 278 | exitcmd = /usr/local/bin/check_hosts %{OLD} %{NEW} 279 | ``` 280 | 281 | **/usr/local/bin/check_hosts** takes two arguments, the original file as the first argument and the modify candidate as the second argument. If **check_hosts** terminates zero, then the edit is considered clean and the original file is replaced with the candidate. Otherwise the edit file is not copied and is left, **pleaseedit** will exit with the return value from **check_hosts**. 282 | 283 | A common **exitcmd** is to check the validity of **please.ini**, shown below. This permits members of the **admin** group to edit **/etc/please.ini** if they provide a reason (**-r**). Upon clean exit from the editor the tmp file will be syntax checked. 284 | 285 | ``` 286 | [please_ini] 287 | name = admins 288 | group = true 289 | reason = true 290 | rule = /etc/please.ini 291 | type = edit 292 | editmode = 600 293 | exitcmd = /usr/bin/please -c %{NEW} 294 | ``` 295 | 296 | # DATED RANGES 297 | 298 | For large environments it is not unusual for a third party to require access during a short time frame for debugging. To accommodate this there are the **notbefore** and **notafter** time brackets. These can be either **YYYYmmdd** or **YYYYmmddHHMMSS**. 299 | 300 | The whole day is considered when using the shorter date form of **YYYYmmdd**. 301 | 302 | Many enterprises may wish to permit periods of access to a user for a limited time only, even if that individual is considered to have a permanent role. 303 | 304 | User joker can do what they want as root on 1st April 2021: 305 | 306 | ``` 307 | [joker_april_first] 308 | name = joker 309 | target = root 310 | permit = true 311 | notbefore = 20210401 312 | notafter = 20210401 313 | rule = ^/bin/bash 314 | ``` 315 | 316 | # DATEMATCHES 317 | 318 | **datematch** matches against the date string **Day dd mon HH:MM:SS UTC Year**. This enables calendar style date matches. 319 | 320 | Note that the day of the month (**dd**) will be padded with spaces if less than two characters wide. 321 | 322 | You can permit a group of users to run **/usr/local/housekeeping/** scripts every Monday: 323 | 324 | ``` 325 | [l2_housekeeping] 326 | name = l2users 327 | group = true 328 | target = root 329 | permit = true 330 | rule = /usr/local/housekeeping/tidy_(logs|images|mail) 331 | datematch = ^Mon\s+.* 332 | ``` 333 | 334 | # REASONS 335 | 336 | When **reason=true**, a user must pass a reason with the **-r** option to **please** and **pleaseedit**. Some organisations may prefer a reason to be logged when a command is executed. This can be helpful for some situations where something such as **mkfs** or **useradd** might be preferable to be logged against a ticket. 337 | 338 | ``` 339 | [l2_user_admin] 340 | name = l2users 341 | group = true 342 | target = root 343 | permit = true 344 | reason = true 345 | rule = ^/usr/sbin/useradd -m \w+$ 346 | ``` 347 | 348 | Or, if tickets have a known prefix: 349 | 350 | ``` 351 | reason = .*(bug|incident|ticket|change)\d+.* 352 | ``` 353 | 354 | Perhaps you want to add a mini molly-guard where the hostname must appear in the reason: 355 | 356 | ``` 357 | [user_poweroff] 358 | name = l2users 359 | group = true 360 | rule = (/usr)?/s?bin/(shutdown( -h now)?|poweroff|reboot) 361 | require_pass = true 362 | reason = .*%{HOSTNAME}.* 363 | ``` 364 | 365 | # DIR 366 | 367 | In some situations you may only want a command to run within a set of directories. The directory is specified with the **-d** argument to **please**. For example, a program may output to the current working directory, which may only be desirable in certain locations. 368 | 369 | ``` 370 | [eng_build_aliases] 371 | name = l2users 372 | group = true 373 | dir = ^/etc/mail$ 374 | rule = ^/usr/local/bin/build_aliases$ 375 | ``` 376 | 377 | # LAST 378 | 379 | **last = true** stops processing at a match: 380 | 381 | ``` 382 | [mkfs] 383 | name = l2users 384 | group = true 385 | target = root 386 | permit = true 387 | reason = true 388 | rule = ^/sbin/mkfs.(ext[234]|xfs) /dev/sd[bcdefg]\d?$ 389 | last = true 390 | ``` 391 | 392 | For simplicity, there is no need to process other configured rules if certain that the **l2users** group are safe to execute this. **last** should only be used in situations where there will never be something that could contradict the match in an undesired way later. 393 | 394 | # SYSLOG 395 | 396 | By default entries are logged to syslog. If you do not wish an entry to be logged then specify **syslog=false**. In this case **jim** can run anything in **/usr/bin/** as root and it will not be logged. 397 | 398 | ``` 399 | [maverick] 400 | syslog = false 401 | name = jim 402 | rule = /usr/bin/.* 403 | reason = false 404 | ``` 405 | 406 | # FILES 407 | 408 | /etc/please.ini 409 | 410 | # NOTES 411 | 412 | At a later date repeated properties within the same section may be treated as a match list. 413 | 414 | # CONTRIBUTIONS 415 | 416 | I welcome pull requests with open arms. New features always considered. 417 | 418 | # BUGS 419 | 420 | Found a bug? Please either open a ticket or send a pull request/patch. 421 | 422 | # SEE ALSO 423 | 424 | **please**(1) 425 | -------------------------------------------------------------------------------- /please.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: please 3 | section: 1 4 | header: User Manual 5 | footer: please 0.5.6 6 | author: Ed Neville (ed-please@s5h.net) 7 | date: 06 September 2024 8 | --- 9 | 10 | # NAME 11 | 12 | please - a tool for access elevation. 13 | 14 | # SYNOPSIS 15 | 16 | **please /bin/bash** 17 | 18 | **pleaseedit /etc/fstab** 19 | 20 | **pleaseedit [-r/\--reason \"new fs\"] /etc/fstab** 21 | 22 | **pleaseedit [-g/\--group groupname] filename** 23 | 24 | **pleaseedit [-t/\--target username] filename** 25 | 26 | **pleaseedit [\--resume] filename** 27 | 28 | **please [-a/\--allowenv list]** 29 | 30 | **please [-c/\--check] /etc/please.ini** 31 | 32 | **please [-d/\--dir directory] command** 33 | 34 | **please [-e/\--env environment] command** 35 | 36 | **please [-g/\--group groupname] command** 37 | 38 | **please [-h/\--help]** 39 | 40 | **please [-t/\--target username] backup tar -cvf - /home/data | ...** 41 | 42 | **please [-u/\--user username] backup tar -cvf - /home/data | ...** 43 | 44 | **please [-l/\--list]** 45 | 46 | **please [-l/\--list] [-t/\--target username]** 47 | 48 | **please [-l/\--list] [-u/\--user username]** 49 | 50 | **please [-n/\--noprompt] command** 51 | 52 | **please [-r/\--reason \"sshd reconfigured, ticket 24365\"] /etc/init.d/ssh restart** 53 | 54 | **please [-p/\--purge]** 55 | 56 | **please [-w/\--warm]** 57 | 58 | # DESCRIPTION 59 | 60 | **please** and **pleaseedit** are sudo alternatives that have regex support and a simple approach to ACL. 61 | 62 | The aim is to allow admins to delegate accurate principle of least privilege access with ease. **please.ini** allows for very specific and flexible regex defined permissions. 63 | 64 | **pleaseedit** adds a layer of safety to editing files. The file is copied to /tmp, where it can be updated. When **EDITOR** exits cleanly the file is copied alongside the target, the file will then be renamed over the original, but if a **exitcmd** is configured it must exit cleanly first. **resume** will continue editing when **exitcmd** fails. 65 | 66 | **-a**/**\--allowenv list** 67 | : allow environments separated by **,** to be passed through 68 | 69 | **-c**/**\--check file** 70 | : will check the syntax of a **please.ini** config file. Exits non-zero on error 71 | 72 | **-d**/**\--dir** 73 | : will change directory to **dir** prior to executing the command 74 | 75 | **-g**/**\--group groupname** 76 | : run or edit as groupname 77 | 78 | **-h**/**\--help** 79 | : print help and exit 80 | 81 | **-l**/**\--list** 82 | : to list rules 83 | 84 | **-n**/**\--noprompt** 85 | : will not prompt for authentication and exits with a status of 1 86 | 87 | **-p**/**\--purge** 88 | : will purge your current authentication token for the running user 89 | 90 | **-r**/**\--reason** **[reason]** 91 | : will add **reason** to the system log 92 | 93 | **-t**/**\--target** **[username]** 94 | : to execute command, or edit as target **username** 95 | 96 | **-u**/**\--user** **[username]** 97 | : to execute command, or edit as target **username** 98 | 99 | **-v**/**\--version** 100 | : print version and exit 101 | 102 | **-w**/**\--warm** 103 | : will warm an authentication token and exit 104 | 105 | # EXAMPLE USAGE 106 | 107 | **please -t httpd /bin/bash** 108 | : run a shell as the httpd user 109 | 110 | **please -l** 111 | : to list what you may run 112 | 113 | **please -t \"username\" -l** 114 | : to show what username may run. **username** must match the target regex in a **type=list** rule 115 | 116 | **please -r \'reloading apache2, change #123\' systemctl reload apache2** 117 | : to reload apache2 with a reason 118 | 119 | **pleaseedit -r \'adding new storage, ticket #24365\' /etc/fstab** 120 | : to use pleaseedit to modify **fstab** 121 | 122 | Please see **please.ini** for configuration examples. 123 | 124 | # FILES 125 | 126 | /etc/please.ini 127 | 128 | # CONTRIBUTIONS 129 | 130 | I welcome pull requests with open arms. New features always considered. 131 | 132 | # BUGS 133 | 134 | Found a bug? Please either open a ticket or send a pull request/patch. 135 | 136 | # SEE ALSO 137 | 138 | **please.ini**(5) 139 | 140 | -------------------------------------------------------------------------------- /src/bin/please.rs: -------------------------------------------------------------------------------- 1 | // please 2 | // Copyright (C) 2020-2021 ed neville 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | //! please.rs a sudo-like clone that implements regex all over the place 18 | 19 | use pleaser::*; 20 | 21 | use std::os::unix::process::CommandExt; 22 | use std::process::Command; 23 | 24 | use std::collections::HashMap; 25 | 26 | use getopts::Options; 27 | 28 | use uzers::*; 29 | 30 | /// walk through user ACL 31 | fn do_list(ro: &mut RunOptions, vec_eo: &[EnvOptions], service: &str) { 32 | let name = if ro.target == ro.name || ro.target.is_empty() { 33 | "You".to_string() 34 | } else { 35 | ro.target.clone() 36 | }; 37 | 38 | let can_do = can(vec_eo, ro); 39 | ro.env_options = Some(can_do.clone()); 40 | if can_do.syslog.is_some() { 41 | ro.syslog = can_do.syslog.unwrap(); 42 | } 43 | 44 | if !can_do.permit() { 45 | let dest = format!("{}'s", &ro.target); 46 | log_action(service, "deny", ro, &ro.command); 47 | println!( 48 | "You may not view {} command list", 49 | if ro.target.is_empty() || ro.target == ro.name { 50 | "your" 51 | } else { 52 | &dest 53 | } 54 | ); 55 | std::process::exit(1); 56 | } 57 | 58 | // check if a reason was given 59 | if !reason_ok(&can_do, ro) { 60 | log_action(service, "reason_fail", ro, &ro.original_command.join(" ")); 61 | std::process::exit(1); 62 | } 63 | 64 | // check if a password is required 65 | if !challenge_password(ro, &can_do, service) { 66 | log_action(service, "deny", ro, &ro.original_command.join(" ")); 67 | std::process::exit(1); 68 | } 69 | 70 | log_action(service, "permit", ro, &ro.command); 71 | println!("{} may run the following:", name); 72 | ro.acl_type = Acltype::Run; 73 | list(vec_eo, ro); 74 | println!("{} may edit the following:", name); 75 | ro.acl_type = Acltype::Edit; 76 | list(vec_eo, ro); 77 | println!("{} may list the following:", name); 78 | ro.acl_type = Acltype::List; 79 | list(vec_eo, ro); 80 | } 81 | 82 | /// navigate to directory or exit 1 83 | fn do_dir_changes(ro: &RunOptions, service: &str) { 84 | if ro.directory.is_some() { 85 | if let Err(x) = std::env::set_current_dir(ro.directory.as_ref().unwrap()) { 86 | println!( 87 | "[{}] cannot cd into {}: {}", 88 | &service, 89 | &ro.directory.as_ref().unwrap(), 90 | x 91 | ); 92 | std::process::exit(1); 93 | } 94 | } 95 | } 96 | 97 | /// setup getopts for argument parsing and help output 98 | fn general_options( 99 | ro: &mut RunOptions, 100 | args: Vec, 101 | service: &str, 102 | vec_eo: &mut Vec, 103 | ) { 104 | let mut opts = Options::new(); 105 | opts.parsing_style(getopts::ParsingStyle::StopAtFirstFree); 106 | opts.optopt( 107 | "a", 108 | "allowenv", 109 | "allow permitted comma separated envs", 110 | "LIST", 111 | ); 112 | opts.optopt("c", "check", "check config file", "FILE"); 113 | opts.optopt("d", "dir", "change to directory prior to execution", "DIR"); 114 | opts.optopt("g", "group", "become target group", "GROUP"); 115 | opts.optflag("h", "help", "print usage help"); 116 | opts.optflag("l", "list", "list effective rules, can combine with -t/-u"); 117 | opts.optflag("n", "noprompt", "do nothing if a password is required"); 118 | opts.optflag("p", "purge", "purge access token"); 119 | opts.optopt("r", "reason", "provide reason for execution", "REASON"); 120 | opts.optopt("t", "target", "become target user", "USER"); 121 | opts.optopt("u", "user", "become target user", "USER"); 122 | opts.optflag("v", "version", "print version and exit"); 123 | opts.optflag("w", "warm", "warm access token and exit"); 124 | 125 | let matches = match opts.parse(&args[1..]) { 126 | Ok(m) => m, 127 | Err(f) => { 128 | println!("{}", f); 129 | std::process::exit(1); 130 | } 131 | }; 132 | 133 | if matches.opt_present("c") { 134 | let mut bytes = 0; 135 | let mut ini_list: HashMap = HashMap::new(); 136 | std::process::exit(read_ini_config_file( 137 | &matches.opt_str("c").unwrap(), 138 | vec_eo, 139 | ro, 140 | true, 141 | &mut bytes, 142 | &mut ini_list, 143 | ) as i32); 144 | } 145 | 146 | let root_uid = nix::unistd::Uid::from_raw(0); 147 | let root_gid = nix::unistd::Gid::from_raw(0); 148 | if nix::unistd::getuid() != root_uid { 149 | if !set_privs("root", root_uid, root_gid) { 150 | std::process::exit(1); 151 | } 152 | 153 | if !drop_privs(ro) { 154 | std::process::exit(1); 155 | } 156 | } 157 | 158 | if matches.opt_present("a") { 159 | let mut vec = vec![]; 160 | 161 | for s in matches.opt_str("a").unwrap().split(',') { 162 | if s.trim() == "" { 163 | continue; 164 | } 165 | vec.push(s.to_string()); 166 | } 167 | ro.allow_env_list = Some(vec); 168 | } 169 | 170 | if matches.opt_present("d") { 171 | ro.directory = Some(matches.opt_str("d").unwrap()); 172 | } 173 | if matches.opt_present("l") { 174 | ro.acl_type = Acltype::List; 175 | } 176 | 177 | let header = format!("{} [arguments] ", &service); 178 | common_opt_arguments(&matches, &opts, ro, service, &header); 179 | 180 | if ro.new_args.is_empty() && !ro.warm_token && !ro.purge_token && ro.acl_type != Acltype::List { 181 | println!("No command given"); 182 | print_usage(&opts, &header); 183 | print_version(service); 184 | std::process::exit(0); 185 | } 186 | } 187 | 188 | fn exit_if_command_not_found(ro: &RunOptions, service: &str) { 189 | if let Some(k) = ro.located_bin.get(&ro.new_args[0]) { 190 | if k.is_none() { 191 | println!("[{service}] command not found"); 192 | std::process::exit(1); 193 | } 194 | } 195 | } 196 | 197 | fn is_command_cd(ro: &RunOptions, service: &str) { 198 | if ro.cloned_args.is_none() && &ro.new_args[0] == "cd" { 199 | println!("[{service}] {} is a shell feature.", &ro.new_args[0]); 200 | if ro.new_args.len() > 1 { 201 | println!( 202 | "Try either changing to {} first or using {} -d {} instead.", 203 | &ro.new_args[1], service, &ro.new_args[1] 204 | ); 205 | } 206 | std::process::exit(1); 207 | } 208 | } 209 | 210 | /// main entry point 211 | fn main() { 212 | let args: Vec = std::env::args().collect(); 213 | let service = String::from("please"); 214 | let mut ro = RunOptions::new(); 215 | let original_uid = get_current_uid(); 216 | let original_user = get_user_by_uid(original_uid).unwrap(); 217 | ro.name = original_user.name().to_string_lossy().to_string(); 218 | ro.syslog = true; 219 | ro.original_command.clone_from(&args); 220 | let mut vec_eo: Vec = vec![]; 221 | 222 | let root_uid = nix::unistd::Uid::from_raw(0); 223 | let root_gid = nix::unistd::Gid::from_raw(0); 224 | if nix::unistd::getuid() == root_uid { 225 | if !set_privs("root", root_uid, root_gid) { 226 | std::process::exit(1); 227 | } 228 | 229 | if !drop_privs(&ro) { 230 | std::process::exit(1); 231 | } 232 | } 233 | 234 | general_options(&mut ro, args, &service, &mut vec_eo); 235 | 236 | clean_environment(&mut ro); 237 | 238 | ro.groups = group_hash(original_user.groups().unwrap()); 239 | if !esc_privs() { 240 | std::process::exit(1); 241 | } 242 | 243 | let mut bytes = 0; 244 | let mut ini_list: HashMap = HashMap::new(); 245 | if read_ini_config_file( 246 | "/etc/please.ini", 247 | &mut vec_eo, 248 | &ro, 249 | true, 250 | &mut bytes, 251 | &mut ini_list, 252 | ) { 253 | println!("Exiting due to error, cannot fully process /etc/please.ini"); 254 | std::process::exit(1); 255 | } 256 | 257 | if !drop_privs(&ro) { 258 | std::process::exit(1); 259 | } 260 | 261 | ro.command = replace_new_args(ro.new_args.clone()); 262 | 263 | if ro.acl_type == Acltype::List { 264 | if ro.target.is_empty() { 265 | ro.target = ro.name.to_string(); 266 | } 267 | do_list(&mut ro, &vec_eo, &service); 268 | return; 269 | } 270 | 271 | if ro.target.is_empty() { 272 | ro.target = "root".to_string(); 273 | } 274 | 275 | let entry = can(&vec_eo, &mut ro); 276 | ro.env_options = Some(entry.clone()); 277 | 278 | if entry.syslog.is_some() { 279 | ro.syslog = entry.syslog.unwrap(); 280 | } 281 | 282 | if !entry.permit() { 283 | log_action(&service, "deny", &ro, &ro.original_command.join(" ")); 284 | 285 | is_command_cd(&ro, &service); 286 | 287 | exit_if_command_not_found(&ro, &service); 288 | 289 | print_may_not(&ro); 290 | std::process::exit(1); 291 | } 292 | 293 | // check if a reason was given 294 | if !reason_ok(&entry, &ro) { 295 | log_action(&service, "reason_fail", &ro, &ro.original_command.join(" ")); 296 | std::process::exit(1); 297 | } 298 | 299 | // password required? 300 | if !challenge_password(&ro, &entry, &service) { 301 | log_action(&service, "deny", &ro, &ro.original_command.join(" ")); 302 | std::process::exit(1); 303 | } 304 | 305 | if !drop_privs(&ro) { 306 | std::process::exit(1); 307 | } 308 | 309 | // target user 310 | let lookup_name = get_user_by_name(&ro.target); 311 | if lookup_name.is_none() { 312 | println!("Could not lookup {}", &ro.target); 313 | std::process::exit(1); 314 | } 315 | let lookup_name = lookup_name.unwrap(); 316 | let target_uid = nix::unistd::Uid::from_raw(lookup_name.uid()); 317 | let target_gid = runopt_target_gid(&ro, &lookup_name); 318 | 319 | if !esc_privs() { 320 | std::process::exit(1); 321 | } 322 | if !set_eprivs(target_uid, target_gid) { 323 | std::process::exit(1); 324 | } 325 | 326 | // change to target dir 327 | do_dir_changes(&ro, &service); 328 | 329 | if !drop_privs(&ro) { 330 | std::process::exit(1); 331 | } 332 | 333 | log_action(&service, "permit", &ro, &ro.original_command.join(" ")); 334 | 335 | set_environment(&ro, &entry, &original_user, original_uid, &lookup_name); 336 | 337 | if !esc_privs() { 338 | std::process::exit(1); 339 | } 340 | 341 | if !set_privs(&ro.target, target_uid, target_gid) { 342 | std::process::exit(1); 343 | } 344 | 345 | nix::sys::stat::umask(ro.old_umask.unwrap()); 346 | 347 | if ro.cloned_args.as_ref().unwrap().len() > 1 { 348 | Command::new(&ro.cloned_args.as_ref().unwrap()[0]) 349 | .args(ro.cloned_args.as_ref().unwrap().clone().split_off(1)) 350 | .exec(); 351 | } else { 352 | Command::new(&ro.cloned_args.as_ref().unwrap()[0]).exec(); 353 | } 354 | println!("Error executing"); 355 | std::process::exit(1); 356 | } 357 | -------------------------------------------------------------------------------- /tests/basic_ro.rs: -------------------------------------------------------------------------------- 1 | use chrono::NaiveDate; 2 | use pleaser::*; 3 | 4 | pub fn basic_ro(name: &str, target: &str) -> RunOptions { 5 | let mut ro = RunOptions::new(); 6 | ro.date = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0); 7 | ro.name = name.to_string(); 8 | ro.target = target.to_string(); 9 | ro.acl_type = Acltype::Run; 10 | ro.hostname = "localhost".to_string(); 11 | 12 | ro 13 | } 14 | 15 | pub fn basic_cmd(ro: &mut RunOptions, cmd: &str) { 16 | ro.new_args = cmd.split_whitespace().map(|s| s.to_string()).collect(); 17 | if ro.new_args.len() == 0 { 18 | ro.new_args = vec!["".to_string()]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/defaults.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | mod basic_ro; 3 | 4 | #[cfg(test)] 5 | mod test { 6 | use super::*; 7 | use basic_ro::*; 8 | use pleaser::*; 9 | 10 | #[test] 11 | fn test_basic_default_non_match() { 12 | let config = "[default:syslog_off] 13 | name = ben 14 | rule = .* 15 | syslog = false 16 | 17 | [ed] 18 | name = ed 19 | rule = .* 20 | " 21 | .to_string(); 22 | 23 | let mut bytes = 0; 24 | let mut ini_list: HashMap = HashMap::new(); 25 | let mut vec_eo: Vec = vec![]; 26 | let mut ro = basic_ro("ed", "root"); 27 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 28 | 29 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 30 | 31 | let can = can(&vec_eo, &mut ro); 32 | assert_eq!(can.syslog.is_some(), false); 33 | } 34 | 35 | #[test] 36 | fn test_basic_default_syslog() { 37 | let config = "[default:syslog_off] 38 | name = .* 39 | rule = .* 40 | syslog = false 41 | 42 | [ed] 43 | name = ed 44 | rule = .* 45 | " 46 | .to_string(); 47 | 48 | let mut bytes = 0; 49 | let mut ini_list: HashMap = HashMap::new(); 50 | let mut vec_eo: Vec = vec![]; 51 | let mut ro = basic_ro("ed", "root"); 52 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 53 | 54 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 55 | 56 | let can = can(&vec_eo, &mut ro); 57 | assert_eq!(can.syslog.is_some(), true); 58 | assert_eq!(can.syslog.unwrap(), false); 59 | } 60 | 61 | #[test] 62 | fn test_basic_default_timeout() { 63 | let config = "[default] 64 | name = .* 65 | rule = .* 66 | 67 | [ed] 68 | name = ed 69 | rule = .* 70 | " 71 | .to_string(); 72 | 73 | let mut bytes = 0; 74 | let mut ini_list: HashMap = HashMap::new(); 75 | let mut vec_eo: Vec = vec![]; 76 | let mut ro = basic_ro("ed", "root"); 77 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 78 | 79 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 80 | 81 | let can = can(&vec_eo, &mut ro); 82 | assert_eq!(can.permit(), true); 83 | assert_eq!(can.timeout.is_none(), true); 84 | } 85 | 86 | #[test] 87 | fn test_layered_match() { 88 | let config = "[default:syslog_off] 89 | name = ed 90 | rule = .* 91 | syslog = false 92 | 93 | [default:require_pass] 94 | name = ed 95 | rule = .* 96 | require_pass = true 97 | 98 | [default:timeout] 99 | name = ed 100 | rule = .* 101 | timeout = 30 102 | 103 | [ed] 104 | name = ed 105 | rule = .* 106 | " 107 | .to_string(); 108 | 109 | let mut bytes = 0; 110 | let mut ini_list: HashMap = HashMap::new(); 111 | let mut vec_eo: Vec = vec![]; 112 | let mut ro = basic_ro("ed", "root"); 113 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 114 | 115 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 116 | 117 | let can = can(&vec_eo, &mut ro); 118 | assert_eq!(can.syslog.is_some(), true); 119 | assert_eq!(can.syslog.unwrap(), false); 120 | assert_eq!(can.require_pass.is_some(), true); 121 | assert_eq!(can.require_pass.unwrap(), true); 122 | assert_eq!(can.timeout.is_some(), true); 123 | assert_eq!(can.timeout.unwrap(), 30); 124 | } 125 | 126 | #[test] 127 | fn test_layered_fail_match() { 128 | let config = "[default:syslog_off] 129 | name = ed 130 | rule = .* 131 | syslog = false 132 | 133 | [default:require_pass] 134 | name = ed 135 | rule = /nopes 136 | require_pass = true 137 | 138 | [default:timeout] 139 | name = ed 140 | rule = .* 141 | notafter = 20191231 142 | timeout = 30 143 | 144 | [ed] 145 | name = ed 146 | rule = .* 147 | " 148 | .to_string(); 149 | 150 | let mut bytes = 0; 151 | let mut ini_list: HashMap = HashMap::new(); 152 | let mut vec_eo: Vec = vec![]; 153 | let mut ro = basic_ro("ed", "root"); 154 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 155 | 156 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 157 | 158 | let can = can(&vec_eo, &mut ro); 159 | assert_eq!(can.syslog.is_some(), true); 160 | assert_eq!(can.syslog.unwrap(), false); 161 | assert_eq!(can.require_pass.is_none(), true); 162 | assert_eq!(can.timeout.is_none(), true); 163 | } 164 | 165 | #[test] 166 | fn test_generic_match() { 167 | let config = "[default:syslog_off] 168 | rule = .* 169 | name = .* 170 | syslog = false 171 | permit = false 172 | 173 | [ed] 174 | name = ed 175 | rule = .* 176 | " 177 | .to_string(); 178 | 179 | let mut bytes = 0; 180 | let mut ini_list: HashMap = HashMap::new(); 181 | let mut vec_eo: Vec = vec![]; 182 | let mut ro = basic_ro("ed", "root"); 183 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 184 | 185 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 186 | 187 | let can = can(&vec_eo, &mut ro); 188 | assert_eq!(can.syslog.is_some(), true); 189 | assert_eq!(can.syslog.unwrap(), false); 190 | assert_eq!(can.permit(), false); 191 | } 192 | 193 | #[test] 194 | fn test_generic_true_match() { 195 | let config = "[default:syslog_off] 196 | rule = .* 197 | name = .* 198 | syslog = false 199 | 200 | [ed] 201 | name = ed 202 | rule = .* 203 | " 204 | .to_string(); 205 | 206 | let mut bytes = 0; 207 | let mut ini_list: HashMap = HashMap::new(); 208 | let mut vec_eo: Vec = vec![]; 209 | let mut ro = basic_ro("ed", "root"); 210 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 211 | 212 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 213 | 214 | let can = can(&vec_eo, &mut ro); 215 | assert_eq!(can.syslog.is_some(), true); 216 | assert_eq!(can.syslog.unwrap(), false); 217 | assert_eq!(can.permit(), true); 218 | } 219 | 220 | #[test] 221 | fn test_multilpe_non_match() { 222 | let config = "[default:syslog_off] 223 | rule = /bin/bash 224 | exact_name = ben 225 | syslog = false 226 | 227 | [default:timeout_off] 228 | rule = /bin/sh 229 | name = .* 230 | timeout = 100 231 | 232 | [default:require_pass] 233 | hostname = webby 234 | name = .* 235 | permit = false 236 | 237 | [ed] 238 | name = ed 239 | rule = .* 240 | " 241 | .to_string(); 242 | 243 | let mut bytes = 0; 244 | let mut ini_list: HashMap = HashMap::new(); 245 | let mut vec_eo: Vec = vec![]; 246 | let mut ro = basic_ro("ed", "root"); 247 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 248 | 249 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 250 | 251 | let can = can(&vec_eo, &mut ro); 252 | assert_eq!(can.syslog.is_none(), true); 253 | assert_eq!(can.timeout.is_none(), true); 254 | assert_eq!(can.permit(), true); 255 | } 256 | 257 | #[test] 258 | fn test_edit_mode_keep() { 259 | let config = " 260 | [default:edit_mode] 261 | name = .* 262 | rule = .* 263 | editmode = keep 264 | type = edit 265 | permit = false 266 | 267 | [ed] 268 | name = ed 269 | rule = .* 270 | type = edit 271 | " 272 | .to_string(); 273 | 274 | let mut bytes = 0; 275 | let mut ini_list: HashMap = HashMap::new(); 276 | let mut vec_eo: Vec = vec![]; 277 | let mut ro = basic_ro("ed", "root"); 278 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 279 | ro.acl_type = Acltype::Edit; 280 | 281 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 282 | 283 | let can = can(&vec_eo, &mut ro); 284 | assert_eq!(can.edit_mode, Some(EditMode::Keep(true))); 285 | assert_eq!(can.permit(), false); 286 | } 287 | 288 | #[test] 289 | fn test_edit_mode_keep_none() { 290 | let config = " 291 | [default:edit_mode] 292 | name = thing 293 | rule = .* 294 | editmode = keep 295 | type = edit 296 | permit = false 297 | 298 | [ed] 299 | name = ed 300 | rule = .* 301 | type = edit 302 | " 303 | .to_string(); 304 | 305 | let mut bytes = 0; 306 | let mut ini_list: HashMap = HashMap::new(); 307 | let mut vec_eo: Vec = vec![]; 308 | let mut ro = basic_ro("ed", "root"); 309 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 310 | ro.acl_type = Acltype::Edit; 311 | 312 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 313 | 314 | let can = can(&vec_eo, &mut ro); 315 | assert_eq!(can.edit_mode, None); 316 | assert_eq!(can.permit(), true); 317 | } 318 | 319 | #[test] 320 | fn test_edit_mode_non_keep() { 321 | let config = " 322 | [default:edit_mode] 323 | name = ed 324 | rule = .* 325 | type = edit 326 | editmode = 111 327 | exitcmd = /bin/false 328 | reason = blah 329 | 330 | [ed] 331 | name = ed 332 | rule = .* 333 | type = edit 334 | " 335 | .to_string(); 336 | 337 | let mut bytes = 0; 338 | let mut ini_list: HashMap = HashMap::new(); 339 | let mut vec_eo: Vec = vec![]; 340 | let mut ro = basic_ro("ed", "root"); 341 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 342 | ro.acl_type = Acltype::Edit; 343 | 344 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 345 | 346 | let can = can(&vec_eo, &mut ro); 347 | assert_eq!(can.edit_mode, Some(EditMode::Mode(0o111))); 348 | assert_eq!(can.permit(), true); 349 | assert_eq!(can.exitcmd, Some("/bin/false".to_string())); 350 | assert_eq!(can.reason, Some(ReasonType::Text("blah".to_string()))); 351 | } 352 | 353 | #[test] 354 | fn test_edit_default_other_user() { 355 | let config = " 356 | [default:edit_mode] 357 | name = ed 358 | rule = .* 359 | type = edit 360 | editmode = 111 361 | 362 | [ed] 363 | name = ed 364 | rule = .* 365 | type = edit 366 | 367 | [noted] 368 | name = noted 369 | rule = .* 370 | type = edit 371 | " 372 | .to_string(); 373 | 374 | let mut bytes = 0; 375 | let mut ini_list: HashMap = HashMap::new(); 376 | let mut vec_eo: Vec = vec![]; 377 | let mut ro = basic_ro("ed", "root"); 378 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 379 | ro.name = "noted".to_string(); 380 | ro.acl_type = Acltype::Edit; 381 | 382 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 383 | 384 | let can = can(&vec_eo, &mut ro); 385 | assert_eq!(can.edit_mode, None); 386 | assert_eq!(can.permit(), true); 387 | assert_eq!(can.exitcmd, None); 388 | assert_eq!(can.reason, None); 389 | } 390 | 391 | #[test] 392 | fn test_default_require_pass() { 393 | let config = " 394 | 395 | [ed] 396 | name = ed 397 | rule = .* 398 | " 399 | .to_string(); 400 | 401 | let mut bytes = 0; 402 | let mut ini_list: HashMap = HashMap::new(); 403 | let mut vec_eo: Vec = vec![]; 404 | let mut ro = basic_ro("ed", "root"); 405 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 406 | ro.name = "ed".to_string(); 407 | ro.acl_type = Acltype::Run; 408 | 409 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 410 | 411 | let can = can(&vec_eo, &mut ro); 412 | assert_eq!(can.edit_mode, None); 413 | assert_eq!(can.permit(), true); 414 | assert_eq!(can.exitcmd, None); 415 | assert_eq!(can.reason, None); 416 | assert_eq!(can.require_pass(), true); 417 | } 418 | 419 | #[test] 420 | fn test_default_require_pass_inherit() { 421 | let config = " 422 | [default_all] 423 | name = .* 424 | rule = .* 425 | require_pass = false 426 | " 427 | .to_string(); 428 | 429 | let mut bytes = 0; 430 | let mut ini_list: HashMap = HashMap::new(); 431 | let mut vec_eo: Vec = vec![]; 432 | let mut ro = basic_ro("ed", "root"); 433 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 434 | ro.name = "ed".to_string(); 435 | ro.acl_type = Acltype::Run; 436 | 437 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 438 | 439 | let can = can(&vec_eo, &mut ro); 440 | assert_eq!(can.edit_mode, None); 441 | assert_eq!(can.permit(), true); 442 | assert_eq!(can.exitcmd, None); 443 | assert_eq!(can.reason, None); 444 | assert_eq!(can.require_pass(), false); 445 | 446 | let config = " 447 | [default_all] 448 | name = .* 449 | rule = .* 450 | require_pass = false 451 | last = true 452 | 453 | [ed] 454 | name = ed 455 | rule = /bin/bash 456 | require_pass = true 457 | " 458 | .to_string(); 459 | 460 | let mut bytes = 0; 461 | let mut ini_list: HashMap = HashMap::new(); 462 | let mut vec_eo: Vec = vec![]; 463 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 464 | 465 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 466 | 467 | let can = pleaser::can(&vec_eo, &mut ro); 468 | assert_eq!(can.permit(), true); 469 | assert_eq!(can.require_pass(), false); 470 | assert_eq!(can.last, Some(true)); 471 | assert_eq!(can.reason, None); 472 | assert_eq!(can.syslog, None); 473 | assert_eq!(can.exitcmd, None); 474 | assert_eq!(can.edit_mode, None); 475 | assert_eq!(can.section, "default_all".to_string()); 476 | } 477 | 478 | #[test] 479 | fn test_empty_token_timeout() { 480 | let config = r#" 481 | [ed] 482 | name = ed 483 | rule = .* 484 | "# 485 | .to_string(); 486 | 487 | let mut bytes = 0; 488 | let mut ini_list: HashMap = HashMap::new(); 489 | let mut vec_eo: Vec = vec![]; 490 | let mut ro = basic_ro("ed", "root"); 491 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 492 | ro.name = "ed".to_string(); 493 | ro.acl_type = Acltype::Run; 494 | 495 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 496 | 497 | let can = can(&vec_eo, &mut ro); 498 | assert_eq!(can.token_timeout, None); 499 | } 500 | 501 | #[test] 502 | fn test_default_empty_token_timeout() { 503 | let config = r#" 504 | [default_ed] 505 | name = ed 506 | rule = .* 507 | 508 | [ed] 509 | name = ed 510 | rule = .* 511 | "# 512 | .to_string(); 513 | 514 | let mut bytes = 0; 515 | let mut ini_list: HashMap = HashMap::new(); 516 | let mut vec_eo: Vec = vec![]; 517 | let mut ro = basic_ro("ed", "root"); 518 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 519 | ro.name = "ed".to_string(); 520 | ro.acl_type = Acltype::Run; 521 | 522 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 523 | 524 | let can = can(&vec_eo, &mut ro); 525 | assert_eq!(can.token_timeout, None); 526 | } 527 | 528 | #[test] 529 | fn test_token_timeout_default_set() { 530 | let config = r#" 531 | [default_ed] 532 | name = ed 533 | rule = .* 534 | token_timeout = 60 535 | 536 | [ed] 537 | name = ed 538 | rule = .* 539 | "# 540 | .to_string(); 541 | 542 | let mut bytes = 0; 543 | let mut ini_list: HashMap = HashMap::new(); 544 | let mut vec_eo: Vec = vec![]; 545 | let mut ro = basic_ro("ed", "root"); 546 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 547 | ro.name = "ed".to_string(); 548 | ro.acl_type = Acltype::Run; 549 | 550 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 551 | 552 | let can = can(&vec_eo, &mut ro); 553 | assert_eq!(can.token_timeout, Some(60)); 554 | } 555 | 556 | #[test] 557 | fn test_token_timeout_match_set() { 558 | let config = r#" 559 | [default_ed] 560 | name = ed 561 | rule = .* 562 | 563 | [ed] 564 | name = ed 565 | rule = .* 566 | token_timeout = 60 567 | "# 568 | .to_string(); 569 | 570 | let mut bytes = 0; 571 | let mut ini_list: HashMap = HashMap::new(); 572 | let mut vec_eo: Vec = vec![]; 573 | let mut ro = basic_ro("ed", "root"); 574 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 575 | ro.name = "ed".to_string(); 576 | ro.acl_type = Acltype::Run; 577 | 578 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 579 | 580 | let can = can(&vec_eo, &mut ro); 581 | assert_eq!(can.token_timeout, Some(60)); 582 | } 583 | } 584 | -------------------------------------------------------------------------------- /tests/exact.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | mod basic_ro; 3 | 4 | #[cfg(test)] 5 | mod test { 6 | use super::*; 7 | use basic_ro::*; 8 | use pleaser::*; 9 | 10 | #[test] 11 | fn test_exact_rule() { 12 | let config = "[ed] 13 | exact_name=ed 14 | exact_target=root 15 | exact_rule = /bin/bash 16 | " 17 | .to_string(); 18 | 19 | let mut bytes = 0; 20 | let mut ini_list: HashMap = HashMap::new(); 21 | let mut vec_eo: Vec = vec![]; 22 | let mut ro = basic_ro("ed", "root"); 23 | 24 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 25 | 26 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 27 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 28 | 29 | ro = RunOptions::new(); 30 | basic_cmd(&mut ro, &"/bin/bashz".to_string()); 31 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 32 | 33 | ro = RunOptions::new(); 34 | basic_cmd(&mut ro, &"/".to_string()); 35 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 36 | } 37 | 38 | #[test] 39 | fn test_exact_rule_parameters() { 40 | let config = "[ed] 41 | exact_name=ed 42 | exact_target=root 43 | exact_rule = /bin/bash 44 | " 45 | .to_string(); 46 | 47 | let mut bytes = 0; 48 | let mut ini_list: HashMap = HashMap::new(); 49 | let mut vec_eo: Vec = vec![]; 50 | let mut ro = basic_ro("ed", "root"); 51 | 52 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 53 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 54 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 55 | 56 | basic_cmd(&mut ro, &"/bin/bash file".to_string()); 57 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 58 | 59 | let config = "[ed] 60 | exact_name=ed 61 | exact_target=root 62 | exact_rule = /bin/bash file 63 | " 64 | .to_string(); 65 | let mut vec_eo: Vec = vec![]; 66 | let mut ini_list: HashMap = HashMap::new(); 67 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 68 | 69 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 70 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 71 | 72 | basic_cmd(&mut ro, &"/bin/bash file".to_string()); 73 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 74 | 75 | let config = r#"[ed] 76 | exact_name=ed 77 | exact_target=root 78 | exact_rule = /bin/bash echo\ hello\ world 79 | "# 80 | .to_string(); 81 | let mut vec_eo: Vec = vec![]; 82 | let mut ini_list: HashMap = HashMap::new(); 83 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 84 | 85 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 86 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 87 | 88 | //ro.command = "/bin/bash echo\\ hello\\ world".to_string(); 89 | ro.new_args = vec!["/bin/bash".to_string(), r#"echo hello world"#.to_string()]; 90 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 91 | } 92 | 93 | #[test] 94 | fn test_exact_name() { 95 | let config = "[ed] 96 | exact_name=ed 97 | exact_target=root 98 | exact_rule = /bin/bash 99 | " 100 | .to_string(); 101 | 102 | let mut bytes = 0; 103 | let mut ini_list: HashMap = HashMap::new(); 104 | let mut vec_eo: Vec = vec![]; 105 | let mut ro = basic_ro("ed", "root"); 106 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 107 | 108 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 109 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 110 | 111 | ro.name = "jim".to_string(); 112 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 113 | 114 | ro.command = "edd".to_string(); 115 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 116 | } 117 | 118 | #[test] 119 | fn test_exact_target() { 120 | let config = "[ed] 121 | exact_name=ed 122 | exact_target=root 123 | exact_rule = /bin/bash 124 | " 125 | .to_string(); 126 | 127 | let mut bytes = 0; 128 | let mut ini_list: HashMap = HashMap::new(); 129 | let mut vec_eo: Vec = vec![]; 130 | let mut ro = basic_ro("ed", "root"); 131 | 132 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 133 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 134 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 135 | 136 | ro.target = "jim".to_string(); 137 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 138 | 139 | ro.target = "edd".to_string(); 140 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 141 | } 142 | 143 | #[test] 144 | fn test_exact_hostname() { 145 | let config = "[ed] 146 | exact_name=ed 147 | exact_target=root 148 | exact_hostname=thing 149 | exact_rule = /bin/bash 150 | " 151 | .to_string(); 152 | 153 | let mut bytes = 0; 154 | let mut ini_list: HashMap = HashMap::new(); 155 | let mut vec_eo: Vec = vec![]; 156 | let mut ro = basic_ro("ed", "root"); 157 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 158 | ro.hostname = "thing".to_string(); 159 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 160 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 161 | 162 | ro.hostname = "".to_string(); 163 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 164 | 165 | ro.hostname = "web".to_string(); 166 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 167 | 168 | ro.hostname = "localhost".to_string(); 169 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 170 | 171 | let config = "[ed] 172 | exact_name=ed 173 | exact_target=root 174 | exact_hostname=localhost 175 | exact_rule = /bin/bash 176 | " 177 | .to_string(); 178 | 179 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 180 | 181 | ro.hostname = "thing".to_string(); 182 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 183 | } 184 | 185 | #[test] 186 | fn test_exact_dir() { 187 | let config = "[ed] 188 | exact_name=ed 189 | exact_target=root 190 | exact_rule = /bin/sh 191 | exact_dir = /root 192 | " 193 | .to_string(); 194 | 195 | let mut bytes = 0; 196 | let mut ini_list: HashMap = HashMap::new(); 197 | let mut vec_eo: Vec = vec![]; 198 | let mut ro = basic_ro("ed", "root"); 199 | basic_cmd(&mut ro, &"/bin/sh".to_string()); 200 | ro.directory = Some("/root".to_string()); 201 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 202 | 203 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 204 | 205 | ro.directory = Some("/home".to_string()); 206 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 207 | 208 | ro.directory = Some("/".to_string()); 209 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 210 | } 211 | 212 | #[test] 213 | fn test_exact_name_precedence() { 214 | let config = "[ed] 215 | exact_name=ed 216 | name = zz 217 | exact_target=root 218 | exact_rule = /bin/bash 219 | " 220 | .to_string(); 221 | 222 | let mut bytes = 0; 223 | let mut ini_list: HashMap = HashMap::new(); 224 | let mut vec_eo: Vec = vec![]; 225 | let mut ro = basic_ro("ed", "root"); 226 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 227 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 228 | 229 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 230 | 231 | ro.name = "zz".to_string(); 232 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 233 | 234 | let config = "[ed] 235 | exact_name= 236 | name=zz 237 | exact_target=root 238 | exact_rule = /bin/bash 239 | " 240 | .to_string(); 241 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 242 | 243 | ro.name = "zz".to_string(); 244 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 245 | } 246 | 247 | #[test] 248 | fn test_exact_rule_precedence() { 249 | let config = "[ed] 250 | exact_name=ed 251 | exact_target=root 252 | exact_rule = /bin/sh 253 | rule = /bin/bash 254 | " 255 | .to_string(); 256 | 257 | let mut bytes = 0; 258 | let mut ini_list: HashMap = HashMap::new(); 259 | let mut vec_eo: Vec = vec![]; 260 | let mut ro = basic_ro("ed", "root"); 261 | basic_cmd(&mut ro, &"/bin/sh".to_string()); 262 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 263 | 264 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 265 | 266 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 267 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 268 | } 269 | 270 | #[test] 271 | fn test_exact_target_precedence() { 272 | let config = "[ed] 273 | exact_name=ed 274 | exact_target=root 275 | target=bob 276 | exact_rule = /bin/sh 277 | " 278 | .to_string(); 279 | 280 | let mut bytes = 0; 281 | let mut ini_list: HashMap = HashMap::new(); 282 | let mut vec_eo: Vec = vec![]; 283 | let mut ro = basic_ro("ed", "root"); 284 | basic_cmd(&mut ro, &"/bin/sh".to_string()); 285 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 286 | 287 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 288 | 289 | ro.target = "bob".to_string(); 290 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 291 | } 292 | 293 | #[test] 294 | fn test_exact_dir_precedence() { 295 | let config = "[ed] 296 | exact_name=ed 297 | exact_target=root 298 | exact_rule = /bin/sh 299 | exact_dir = /root 300 | dir = .* 301 | " 302 | .to_string(); 303 | 304 | let mut bytes = 0; 305 | let mut ini_list: HashMap = HashMap::new(); 306 | let mut vec_eo: Vec = vec![]; 307 | let mut ro = basic_ro("ed", "root"); 308 | basic_cmd(&mut ro, &"/bin/sh".to_string()); 309 | ro.directory = Some("/root".to_string()); 310 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 311 | 312 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 313 | 314 | ro.directory = Some("/home".to_string()); 315 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 316 | } 317 | 318 | #[test] 319 | fn test_exact_target_group() { 320 | let config = "[ed] 321 | group = true 322 | exact_target=root 323 | exact_rule = /bin/sh 324 | exact_name = audio 325 | " 326 | .to_string(); 327 | 328 | let mut bytes = 0; 329 | let mut ini_list: HashMap = HashMap::new(); 330 | let mut vec_eo: Vec = vec![]; 331 | let mut ro = basic_ro("ed", "root"); 332 | basic_cmd(&mut ro, &"/bin/sh".to_string()); 333 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 334 | 335 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 336 | 337 | ro.groups.insert(String::from("audio"), 1); 338 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 339 | } 340 | 341 | #[test] 342 | fn test_exact_rule_internal_backslash() { 343 | let config = r#"[ed] 344 | exact_name=ed 345 | exact_target=root 346 | exact_rule = /bin/bash echo\ hello\ \\\\\ world 347 | "# 348 | .to_string(); 349 | 350 | let mut bytes = 0; 351 | let mut ini_list: HashMap = HashMap::new(); 352 | let mut vec_eo: Vec = vec![]; 353 | let mut ro = basic_ro("ed", "root"); 354 | 355 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 356 | 357 | ro.new_args = vec![ 358 | "/bin/bash".to_string(), 359 | r#"echo hello \\ world"#.to_string(), 360 | ]; 361 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /tests/reason.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | mod basic_ro; 3 | 4 | #[cfg(test)] 5 | mod test { 6 | use super::*; 7 | use basic_ro::*; 8 | use pleaser::*; 9 | 10 | #[test] 11 | fn test_reason_abscence() { 12 | let config = "[ed] 13 | exact_name=ed 14 | exact_target=root 15 | exact_rule = /bin/bash 16 | reason = true 17 | " 18 | .to_string(); 19 | 20 | let mut bytes = 0; 21 | let mut ini_list: HashMap = HashMap::new(); 22 | let mut vec_eo: Vec = vec![]; 23 | let mut ro = basic_ro("ed", "root"); 24 | 25 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 26 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 27 | let can_do = can(&vec_eo, &mut ro); 28 | assert_eq!((can_do).permit(), true); 29 | assert_eq!(reason_ok(&can_do, &ro), false); 30 | } 31 | 32 | #[test] 33 | fn test_reason_present() { 34 | let config = "[ed] 35 | exact_name=ed 36 | exact_target=root 37 | exact_rule = /bin/bash 38 | reason = true 39 | " 40 | .to_string(); 41 | 42 | let mut bytes = 0; 43 | let mut ini_list: HashMap = HashMap::new(); 44 | let mut vec_eo: Vec = vec![]; 45 | let mut ro = basic_ro("ed", "root"); 46 | ro.reason = Some("simple reason".to_string()); 47 | 48 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 49 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 50 | let can_do = can(&vec_eo, &mut ro); 51 | assert_eq!((can_do).permit(), true); 52 | assert_eq!(reason_ok(&can_do, &ro), true); 53 | } 54 | 55 | #[test] 56 | fn test_reason_present_bad_match() { 57 | let config = "[ed] 58 | exact_name=ed 59 | exact_target=root 60 | exact_rule = /bin/bash 61 | reason = bigdb 62 | " 63 | .to_string(); 64 | 65 | let mut bytes = 0; 66 | let mut ini_list: HashMap = HashMap::new(); 67 | let mut vec_eo: Vec = vec![]; 68 | let mut ro = basic_ro("ed", "root"); 69 | ro.reason = Some("simple reason".to_string()); 70 | 71 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 72 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 73 | let can_do = can(&vec_eo, &mut ro); 74 | assert_eq!((can_do).permit(), true); 75 | assert_eq!(reason_ok(&can_do, &ro), false); 76 | } 77 | 78 | #[test] 79 | fn test_reason_present_good_match() { 80 | let config = "[ed] 81 | exact_name=ed 82 | exact_target=root 83 | exact_rule = /bin/bash 84 | reason = bigdb 85 | " 86 | .to_string(); 87 | 88 | let mut bytes = 0; 89 | let mut ini_list: HashMap = HashMap::new(); 90 | let mut vec_eo: Vec = vec![]; 91 | let mut ro = basic_ro("ed", "root"); 92 | ro.reason = Some("bigdb".to_string()); 93 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 94 | 95 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 96 | let can_do = can(&vec_eo, &mut ro); 97 | assert_eq!((can_do).permit(), true); 98 | assert_eq!(reason_ok(&can_do, &ro), true); 99 | } 100 | 101 | #[test] 102 | fn test_reason_present_good_host_match() { 103 | let config = "[ed] 104 | exact_name=ed 105 | exact_target=root 106 | exact_rule = /bin/bash 107 | reason = .*%{HOSTNAME}.* 108 | " 109 | .to_string(); 110 | 111 | let mut bytes = 0; 112 | let mut ini_list: HashMap = HashMap::new(); 113 | let mut vec_eo: Vec = vec![]; 114 | let mut ro = basic_ro("ed", "root"); 115 | ro.reason = Some("bash on localhost".to_string()); 116 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 117 | 118 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 119 | let can_do = can(&vec_eo, &mut ro); 120 | assert_eq!((can_do).permit(), true); 121 | assert_eq!(reason_ok(&can_do, &ro), true); 122 | 123 | ro.reason = Some("power off".to_string()); 124 | assert_eq!(reason_ok(&can_do, &ro), false); 125 | 126 | ro.reason = Some("localhost".to_string()); 127 | assert_eq!(reason_ok(&can_do, &ro), true); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /tests/search_path.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | mod basic_ro; 3 | 4 | #[cfg(test)] 5 | mod test { 6 | use super::*; 7 | use basic_ro::*; 8 | use pleaser::*; 9 | 10 | #[test] 11 | fn test_search_path() { 12 | let config = "[ed] 13 | exact_name=ed 14 | exact_target=root 15 | exact_rule = /bin/bash 16 | search_path = : 17 | " 18 | .to_string(); 19 | 20 | let mut bytes = 0; 21 | let mut ini_list: HashMap = HashMap::new(); 22 | let mut vec_eo: Vec = vec![]; 23 | let mut ro = basic_ro("ed", "root"); 24 | 25 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 26 | 27 | basic_cmd(&mut ro, &"bash".to_string()); 28 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 29 | } 30 | 31 | #[test] 32 | fn test_search_empty_path() { 33 | let config = "[ed] 34 | exact_name=ed 35 | exact_target=root 36 | exact_rule = /bin/bash 37 | search_path = 38 | " 39 | .to_string(); 40 | 41 | let mut bytes = 0; 42 | let mut ini_list: HashMap = HashMap::new(); 43 | let mut vec_eo: Vec = vec![]; 44 | let mut ro = basic_ro("ed", "root"); 45 | 46 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 47 | 48 | basic_cmd(&mut ro, &"bash".to_string()); 49 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 50 | } 51 | 52 | #[test] 53 | fn test_search_unlikely_to_exist_dir() { 54 | let config = "[ed] 55 | exact_name=ed 56 | exact_target=root 57 | rule = .*/bash 58 | search_path = /nonexistent 59 | " 60 | .to_string(); 61 | 62 | let mut bytes = 0; 63 | let mut ini_list: HashMap = HashMap::new(); 64 | let mut vec_eo: Vec = vec![]; 65 | let mut ro = basic_ro("ed", "root"); 66 | 67 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 68 | 69 | basic_cmd(&mut ro, &"bash".to_string()); 70 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 71 | } 72 | 73 | #[test] 74 | fn test_search_bin() { 75 | let config = "[ed] 76 | exact_name=ed 77 | exact_target=root 78 | rule = .*/bash 79 | search_path = /bin 80 | " 81 | .to_string(); 82 | 83 | let mut bytes = 0; 84 | let mut ini_list: HashMap = HashMap::new(); 85 | let mut vec_eo: Vec = vec![]; 86 | let mut ro = basic_ro("ed", "root"); 87 | 88 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 89 | 90 | basic_cmd(&mut ro, &"bash".to_string()); 91 | let c = can(&vec_eo, &mut ro); 92 | assert_eq!(c.permit(), true); 93 | assert_eq!(ro.command, "/bin/bash"); 94 | } 95 | 96 | #[test] 97 | fn test_search_bin_default() { 98 | let config = " 99 | [default_all] 100 | name = .* 101 | target = root 102 | rule = .* 103 | search_path = /bin 104 | 105 | [ed] 106 | exact_name=ed 107 | exact_target=root 108 | rule = .*/bash 109 | " 110 | .to_string(); 111 | 112 | let mut bytes = 0; 113 | let mut ini_list: HashMap = HashMap::new(); 114 | let mut vec_eo: Vec = vec![]; 115 | let mut ro = basic_ro("ed", "root"); 116 | 117 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 118 | 119 | basic_cmd(&mut ro, &"bash".to_string()); 120 | let c = can(&vec_eo, &mut ro); 121 | assert_eq!(ro.command, "/bin/bash"); 122 | assert_eq!(c.permit(), true); 123 | } 124 | 125 | #[test] 126 | fn test_search_bin_default_sbin() { 127 | let config = " 128 | [default:all] 129 | name = .* 130 | target = .* 131 | rule = .* 132 | search_path = /sbin 133 | 134 | [ed] 135 | name=ed 136 | target=root 137 | rule = .*/e2fsck 138 | " 139 | .to_string(); 140 | 141 | let mut bytes = 0; 142 | let mut ini_list: HashMap = HashMap::new(); 143 | let mut vec_eo: Vec = vec![]; 144 | let mut ro = basic_ro("ed", "root"); 145 | 146 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 147 | 148 | basic_cmd(&mut ro, &"e2fsck".to_string()); 149 | let c = can(&vec_eo, &mut ro); 150 | dbg!(&c); 151 | assert_eq!(c.permit(), true); 152 | assert_eq!(ro.command, "/sbin/e2fsck"); 153 | assert_eq!(c.search_path, Some("/sbin".to_string())); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /tests/target_group.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | mod basic_ro; 3 | 4 | #[cfg(test)] 5 | mod test { 6 | use super::*; 7 | use basic_ro::*; 8 | use pleaser::*; 9 | 10 | #[test] 11 | fn test_target_group_rule() { 12 | let config = "[ed] 13 | exact_name=ed 14 | exact_target=root 15 | exact_rule = /bin/bash 16 | target_group = potato 17 | " 18 | .to_string(); 19 | 20 | let mut bytes = 0; 21 | let mut ini_list: HashMap = HashMap::new(); 22 | let mut vec_eo: Vec = vec![]; 23 | let mut ro = basic_ro("ed", "root"); 24 | ro.target_group = Some("potato".to_string()); 25 | 26 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 27 | 28 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 29 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 30 | 31 | ro.target_group = Some("potatoes".to_string()); 32 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 33 | 34 | ro.target_group = None; 35 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 36 | } 37 | 38 | #[test] 39 | fn test_exact_target_group_rule() { 40 | let config = "[ed] 41 | exact_name=ed 42 | exact_target=root 43 | exact_rule = /bin/bash 44 | exact_target_group = potato 45 | " 46 | .to_string(); 47 | 48 | let mut bytes = 0; 49 | let mut ini_list: HashMap = HashMap::new(); 50 | let mut vec_eo: Vec = vec![]; 51 | let mut ro = basic_ro("ed", "root"); 52 | ro.target_group = Some("potato".to_string()); 53 | 54 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 55 | 56 | basic_cmd(&mut ro, &"/bin/bash".to_string()); 57 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 58 | 59 | ro.target_group = Some("potatoes".to_string()); 60 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 61 | 62 | ro.target_group = None; 63 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 64 | } 65 | 66 | #[test] 67 | fn test_target_group_edit() { 68 | let config = " 69 | [please_ini] 70 | name = ed 71 | group = false 72 | regex = /etc/please.ini 73 | type = edit 74 | target_group = oracle 75 | " 76 | .to_string(); 77 | let mut vec_eo: Vec = vec![]; 78 | let mut bytes = 0; 79 | let mut ini_list: HashMap = HashMap::new(); 80 | let mut ro = basic_ro("ed", "root"); 81 | basic_cmd(&mut ro, &"/etc/please.ini".to_string()); 82 | ro.target_group = Some("oracle".to_string()); 83 | ro.acl_type = Acltype::Edit; 84 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 85 | 86 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 87 | 88 | basic_cmd(&mut ro, &"".to_string()); 89 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 90 | } 91 | 92 | #[test] 93 | fn test_target_group_run() { 94 | let config = " 95 | [please_ini] 96 | name = ed 97 | group = false 98 | regex = /etc/missing 99 | type = run 100 | target_group = oracle 101 | " 102 | .to_string(); 103 | let mut vec_eo: Vec = vec![]; 104 | let mut bytes = 0; 105 | let mut ini_list: HashMap = HashMap::new(); 106 | let mut ro = basic_ro("ed", "root"); 107 | basic_cmd(&mut ro, &"/etc/missing".to_string()); 108 | ro.target_group = Some("oracle".to_string()); 109 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 110 | 111 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 112 | 113 | ro.command = "".to_string(); 114 | basic_cmd(&mut ro, &"".to_string()); 115 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 116 | } 117 | 118 | // group has no effect in list context 119 | #[test] 120 | fn test_target_group_list() { 121 | let config = " 122 | [please_ini] 123 | name = ed 124 | group = false 125 | regex = /etc/please.ini 126 | type = list 127 | target_group = oracle 128 | " 129 | .to_string(); 130 | let mut vec_eo: Vec = vec![]; 131 | let mut bytes = 0; 132 | let mut ini_list: HashMap = HashMap::new(); 133 | let mut ro = basic_ro("ed", "root"); 134 | basic_cmd(&mut ro, &"/etc/please.ini".to_string()); 135 | ro.target_group = Some("oracle".to_string()); 136 | ro.acl_type = Acltype::List; 137 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 138 | 139 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 140 | 141 | basic_cmd(&mut ro, &"".to_string()); 142 | assert_eq!(can(&vec_eo, &mut ro).permit(), true); 143 | } 144 | 145 | #[test] 146 | fn test_target_group_run_given_but_not_configured() { 147 | let config = " 148 | [please_ini] 149 | name = ed 150 | group = false 151 | regex = /etc/please.ini 152 | type = edit 153 | " 154 | .to_string(); 155 | let mut vec_eo: Vec = vec![]; 156 | let mut bytes = 0; 157 | let mut ini_list: HashMap = HashMap::new(); 158 | let mut ro = basic_ro("ed", "root"); 159 | basic_cmd(&mut ro, &"/etc/please.ini".to_string()); 160 | ro.target_group = Some("oracle".to_string()); 161 | read_ini_config_str(&config, &mut vec_eo, &ro, false, &mut bytes, &mut ini_list); 162 | 163 | assert_eq!(can(&vec_eo, &mut ro).permit(), false); 164 | } 165 | } 166 | --------------------------------------------------------------------------------