Crates.io | pleaser |
lib.rs | pleaser |
version | 0.5.6 |
source | src |
created_at | 2020-08-23 18:15:45.901352 |
updated_at | 2024-09-06 19:24:23.828301 |
description | please, a polite regex-first sudo alternative |
homepage | https://www.usenix.org.uk/content/please.html |
repository | https://gitlab.com/edneville/please |
max_upload_size | |
id | 279900 |
size | 291,727 |
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
.
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.
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.
Please is written with memory safe rust. Traditional C memory unsafety is avoided, logic problems may exist but this codebase is relatively small.
It might already be in the repo that you're using:
If not, it is a simple install:
git clone https://gitlab.com/edneville/please.git
cd please
cargo test && cargo build --release \
&& install -o 0 -g 0 -m4755 target/release/please target/release/pleaseedit /usr/local/bin
Arch:
pacman -Syu git fakeroot devtools binutils gcc rust
git clone https://aur@aur.archlinux.org/pleaser.git
cd pleaser && makepkg -isr
Debian/Ubuntu:
apt install pleaser
Fedora (35):
dnf install pleaser
NetBSD:
pkgin install pleaser
SUSE Tumbleweed:
zypper install pleaser
RHEL 7 (EPEL):
yum install cargo git pam-devel
git clone 'https://gitlab.com/edneville/please.git'
cd please/
cargo test && cargo build --release && install -oroot -groot -D -m4755 target/release/please target/release/pleaseedit /usr/local/bin
Optionally, set sudo
as an alias of please
:
alias sudo="please"
alias sudoedit="pleaseedit"
Or, if you like, symlink in local:
cd /usr/local/bin && ln -s /usr/local/bin/please sudo && ln -s /usr/local/bin/pleaseedit sudoedit
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
:
#%PAM-1.0
# Set up user limits from /etc/security/limits.conf.
session required pam_limits.so
@include common-auth
@include common-account
@include common-session-noninteractive
Red Hat based needs something similar to this in the same files:
#%PAM-1.0
auth include system-auth
account include system-auth
password include system-auth
session optional pam_keyinit.so revoke
session required pam_limits.so
session include system-auth
Next, configure your /etc/please.ini
, replace user names with appropriate values. The ini
is divided into section options, matches and actions.
Part | Effect |
---|---|
[section-name] | Section name, shown in list mode |
include=file | Include file as another ini source, other options will be skipped in this section. |
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. |
include
and includedir
will override mandatory arguments.
One of the simplest, that does not require password authentication can be defined as follows, assuming the user is jim
:
The options are as follows:
Part | Effect |
---|---|
name=regex | Mandatory, apply configuration to this entity. |
target=regex | May become these users. |
rule=regex | This is the command regex for the section, default is ^$ |
notbefore=YYYYmmdd | The date, or YYYYmmddHHMMSS when this rule becomes effective. |
notafter=YYYYmmdd | The date, or YYYYmmddHHMMSS when this rule expires. |
datematch=[Day dd Mon HH:MM:SS UTC YYYY] | regex to match against a date string |
type=[edit/run/list] | Set the entry type, run = execution, edit = pleaseedit, list = show user rights |
group=[true/false] | True to signify that name= refers to a group rather than a user. |
hostname=regex | Hosts where this applies, defaults to 'localhost'. |
target_group=regex | When set a group must be provided that matches |
dir=regex | Permit switching to regex defined directory prior to execution. |
permit_env=regex | When combined with -a , permit matching environments keys |
search_path=string | Change search_path to : separated directory list |
Exact counterparts, which must match exactly. When both regex and exact rules are present, the exact rule match will have precedence.
Part | Effect |
---|---|
exact_name=string | Match this exact name |
exact_hostname=string | Match this exact hostname |
exact_target=string | Match this exact target user |
exact_target_group=string | Match this exact target group |
exact_rule=string | Match this exact rule |
exact_dir=string | Match this exact directory |
Part | Effect |
---|---|
permit=[true/false] | Defaults to true |
require_pass=[true/false] | Defaults to true |
last=[true/false] | When true, stop processing when matched, defaults to false |
reason=[true/false/regex] | When set, require a reason provided by -r , defaults to false |
timeout=[number] | How long to wait for password input, in whole seconds |
syslog=[true/false] | Log this activity to syslog, default = true |
token_timeout=[number] | How long the authentication token is valid for, in whole seconds |
env_assign.key=value | Force environment key to be assigned value |
exitcmd=[program] | (pleaseedit) Continue with file replacement if program exits 0 |
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 |
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
.
If a include
directive is met, no other entries in the section will be processed. The same goes for includedir
.
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.
For example, using the two entries below:
[jim_root_du]
name = jim
target = root
permit = true
rule = ^(/usr)?/bin/du (/home/[a-z0-9-]+\s?)+
require_pass=false
[jim_postgres]
name = jim
target = postgres
permit = true
rule = /bin/bash
require_pass = false
Would permit running du
, as /usr/bin/du
or /bin/du
as root
:
$ please du /home/*
And would also permit running a bash shell as postgres
:
$ please -t postgres /bin/bash
postgres$
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
.
The whole day is considered when using the shorter date form of YYYYMMDD
.
Many enterprises may wish to permit access to a user for a limited time only, even if that individual is in the role permanently.
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
.
You can permit some a group of users to perform some house keeping on a Monday:
[l2_housekeeping]
name = l2users
group = true
target = root
permit = true
rule = /usr/local/housekeeping/tidy_(logs|images|mail)
datematch = ^Mon.*
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.
pleaseedit
enables editing of files as another user. Enable editing rather than execution with type=edit
. The first argument will be passed to EDITOR
.
By default file permission bits will mirror existing file permissions.
This is performed as follows:
pleaseedit -u root /etc/fstab
/etc/fstab
is copied to /tmp/pleaseedit.$USER.r8cYph9h._etc_fstab
EDITOR
is executed against /tmp/pleaseedit.$USER.r8cYph9h._etc_fstab
EDITOR
exits 0, and exitcmd
exits 0, then /tmp/pleaseedit.$USER.r8cYph9h._etc_fstab
is copied to /etc/fstab.llD3wRQB.pleaseedit.copy.$USER
/etc/fstab.llD3wRQB.pleaseedit.copy.$USER
is set as (target) root owned and renamed
to /etc/fstab
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.
For something similar to apache, consider copying the config tree to a tmp directory before running the test to accommodate includes.
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
:
[user_remove_tmp_user]
name = audio
group = true
permit = true
require_pass = false
rule = /usr/sbin/userdel -f -r %{USER}_tmp\.[a-zA-Z0-9]{10}
How about, for the purpose of housekeeping, some users may be permitted to destroy zfs snapshots that look roughly like they're date stamped:
[user_remove_snapshots]
name = data
group = true
permit = true
require_pass = false
rule = /usr/sbin/zfs destroy storage/photos@\d{8}T\d{6}
To list what you may or may not do:
$ please -l
You may run the following:
file: /etc/please.ini
ed_root_list:root: ^.*$
You may edit the following:
file: /etc/please.ini
ed_edit_ini:root: ^/etc/please.ini$
The above output shows that I may run anything and may edit the please.ini
configuration.
Or, perhaps any user who's name starts admin
may execute useradd
and userdel
:
[admin_users]
name = admin_\S+
permit = true
require_pass = false
rule = /usr/sbin/user(add -m|del) \S+
/etc/please.ini
For big installs, consider the following:
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.
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.
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.
For these reasons I prefer rsync distribution as the protocol is highly efficient and reduces network transfer overall.
LDAP may at a later date be reconsidered.
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.
Locating bugs and logging issues are very appreciated, and I thank you in advance.
I welcome pull requests with open arms.
The source code for this project is currently hosted on gitlab and mirrored to github. There is a crate on crates.io. It also has a homepage where other project information is kept.
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.