比較提交
8 次程式碼提交
1c03d47dac
...
master
作者 | SHA1 | 日期 | |
---|---|---|---|
30f3002bda | |||
ac315529b3 | |||
aaa6670eda | |||
9f2f0e66b2 | |||
db4d3cafbb | |||
659f7bd320 | |||
bb0b2886e9 | |||
dce3d063f7 |
33
README.md
33
README.md
@ -7,9 +7,16 @@ specified by the administrator.
|
|||||||
|
|
||||||
Originally written in C, it's now reimplemented in Rust.
|
Originally written in C, it's now reimplemented in Rust.
|
||||||
|
|
||||||
By default, raou looks in /etc/raou.d/ for config files. If you run
|
### When to use raou (over sudo)
|
||||||
"raou backup", it will look for /etc/raou.d/backup.
|
Generally, it's not a replacement for sudo. The primary use case of raou is a situation in which you would want to allow a user to run a privileged operation as root without entering passwords. You may not want to use sudo for that, particularly if you don't have it installed already. Some further arguments for raou:
|
||||||
|
|
||||||
|
- Simpler config
|
||||||
|
- Less complexity, less attack surface
|
||||||
|
- Writte in a memory-safe language
|
||||||
|
|
||||||
|
### Config
|
||||||
|
By default, raou looks in ```/etc/raou.d/``` for config files. If you run
|
||||||
|
"raou backup", it will look for ```/etc/raou.d/backup```.
|
||||||
Example config file:
|
Example config file:
|
||||||
```
|
```
|
||||||
user john
|
user john
|
||||||
@ -20,29 +27,29 @@ path /usr/local/bin/script.sh
|
|||||||
**user** is the name of the user who you want to give permissions to
|
**user** is the name of the user who you want to give permissions to
|
||||||
execute **path** as the **target_user**.
|
execute **path** as the **target_user**.
|
||||||
|
|
||||||
**path** must contain the absolute path.
|
**path** must contain the absolute path of the to be executed command.
|
||||||
|
|
||||||
Optional fields
|
#### Optional fields
|
||||||
---------------
|
|
||||||
**args**: If you want to leave out optional arguments (argv) to *path*,
|
**args** (string): If you want to leave out optional arguments (argv) to *path*,
|
||||||
simply don't include this. Otherwise, simply specify them
|
simply don't include this. Otherwise, specify them here.
|
||||||
```
|
```
|
||||||
...
|
...
|
||||||
args -v -ltr
|
args -v -ltr
|
||||||
```
|
```
|
||||||
**allow_args**: Allow arbitrary arguments, so:
|
**allow_args** (1 or 0, default 0): Allow arbitrary arguments, so:
|
||||||
```
|
```
|
||||||
raou backup /path
|
raou backup /path
|
||||||
```
|
```
|
||||||
|
|
||||||
Will launch "path" as specified in the file for the backup entry with "/path" as argv[1] instead of the argument specified with "args" in the config file.
|
Will execute the command specified in **path** of the ```backup``` entry with "/path" as argv[1] instead of the argument specified with "args" in the config file.
|
||||||
|
|
||||||
**no_new_privs**: Defaults to 1. Processes launched with this option active
|
**no_new_privs** (1 or 0, default 1): Processes launched with this option active
|
||||||
won't be able to gain more privileges, even when they call setuid programs.
|
won't be able to gain more privileges, even when they call setuid programs. This can break some programs.
|
||||||
|
|
||||||
**env_vars**: A comma-separated list of environment variables to inherit
|
**env_vars** (string): A comma-separated list of environment variables to inherit
|
||||||
from the current environment. Everything else will be wiped (but others
|
from the current environment. Everything else will be wiped (but others
|
||||||
like HOME, SHELL etc. will be appropriately set).
|
like HOME, SHELL etc. will be appropriately set).
|
||||||
|
|
||||||
**argv0**: Set this option if you want to provide your own value as "argv0"
|
**argv0** (string): Set this option if you want to provide your own value as "argv0"
|
||||||
The default is the name of the launched binary (not the whole path).
|
The default is the name of the launched binary (not the whole path).
|
||||||
|
77
src/main.rs
77
src/main.rs
@ -5,6 +5,7 @@ use std::fs::File;
|
|||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
use libc::passwd;
|
use libc::passwd;
|
||||||
@ -47,7 +48,9 @@ struct Passwd {
|
|||||||
|
|
||||||
fn initgroups(user: &str, group: libc::gid_t) -> std::io::Result<()> {
|
fn initgroups(user: &str, group: libc::gid_t) -> std::io::Result<()> {
|
||||||
let userarg = CString::new(user);
|
let userarg = CString::new(user);
|
||||||
return errnowrapper(unsafe { libc::initgroups(userarg.unwrap().as_ptr(), group) });
|
return errnowrapper(unsafe {
|
||||||
|
libc::initgroups(userarg.unwrap().as_ptr(), group)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn errnowrapper(ret: libc::c_int) -> std::io::Result<()> {
|
fn errnowrapper(ret: libc::c_int) -> std::io::Result<()> {
|
||||||
@ -79,7 +82,20 @@ fn getpwnam(username: &str) -> std::io::Result<Passwd> {
|
|||||||
let username_ptr = username_c.as_ptr();
|
let username_ptr = username_c.as_ptr();
|
||||||
let pwnamresult: *mut libc::passwd = unsafe { libc::getpwnam(username_ptr) };
|
let pwnamresult: *mut libc::passwd = unsafe { libc::getpwnam(username_ptr) };
|
||||||
if pwnamresult.is_null() {
|
if pwnamresult.is_null() {
|
||||||
return Err(Error::new(Error::last_os_error().kind(),"Lookup of user failed: ".to_owned() + &Error::last_os_error().to_string()));
|
if Error::last_os_error().raw_os_error().unwrap() == 0 {
|
||||||
|
return Err(Error::new(
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
format!("The username '{}' was not found", username),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return Err(Error::new(
|
||||||
|
Error::last_os_error().kind(),
|
||||||
|
format!(
|
||||||
|
"Lookup of user {} failed: {}",
|
||||||
|
username,
|
||||||
|
&Error::last_os_error().to_string()
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
Ok(Passwd {
|
Ok(Passwd {
|
||||||
@ -172,8 +188,7 @@ fn clearenv() -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
//TODO: AsRef for envs?
|
//TODO: AsRef for envs?
|
||||||
fn setup_environment(passwd: &Passwd, envs: &[String]) -> std::io::Result<()> {
|
fn setup_environment(passwd: &Passwd, envs: &[String]) -> std::io::Result<()> {
|
||||||
let saved_envs: Vec<String> = envs
|
let saved_envs: Vec<String> = envs.iter()
|
||||||
.iter()
|
|
||||||
.map(|s| std::env::var(s).expect("No such var"))
|
.map(|s| std::env::var(s).expect("No such var"))
|
||||||
.collect();
|
.collect();
|
||||||
clearenv()?;
|
clearenv()?;
|
||||||
@ -201,7 +216,9 @@ fn become_user(passwd: &Passwd) -> std::io::Result<()> {
|
|||||||
fn drop_privs(entry: &Entry) -> std::io::Result<()> {
|
fn drop_privs(entry: &Entry) -> std::io::Result<()> {
|
||||||
if entry.no_new_privs {
|
if entry.no_new_privs {
|
||||||
errnowrapper(unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0) })?;
|
errnowrapper(unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0) })?;
|
||||||
errnowrapper(unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) })?;
|
errnowrapper(unsafe {
|
||||||
|
libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -233,14 +250,35 @@ fn create_execv_args(entry: &Entry, cmdargs: &Vec<String>) -> Vec<*const libc::c
|
|||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
fn exec(entryname: &str, cmdargs: &Vec<String>) -> std::io::Result<()> {
|
fn exec(entryname: &str, cmdargs: &Vec<String>) -> std::io::Result<()> {
|
||||||
let mut filepath: String = String::from("/etc/raou.d/");
|
let basedir: String = String::from("/etc/raou.d/");
|
||||||
filepath = filepath + entryname;
|
let filepath: String = basedir.to_string() + entryname;
|
||||||
|
|
||||||
if !std::path::Path::new(&filepath).exists() {
|
let realpath = fs::canonicalize(&filepath);
|
||||||
|
match realpath {
|
||||||
|
Ok(p) => {
|
||||||
|
if !p.starts_with(basedir) {
|
||||||
|
return Err(std::io::Error::new(
|
||||||
|
ErrorKind::InvalidInput,
|
||||||
|
"Specified entry is outside base directory",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !p.is_file() {
|
||||||
|
return Err(std::io::Error::new(
|
||||||
|
ErrorKind::Other,
|
||||||
|
"Error: Entry not a file",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() == ErrorKind::NotFound {
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::new(
|
||||||
ErrorKind::NotFound,
|
ErrorKind::NotFound,
|
||||||
"The entry ".to_owned() + &filepath + " does not exist",
|
format!("The entry {} does not exist", entryname),
|
||||||
));
|
));
|
||||||
|
} else {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let entry: Entry = create_entry_from_file(&filepath)?;
|
let entry: Entry = create_entry_from_file(&filepath)?;
|
||||||
let destuserpasswd: Passwd = getpwnam(&entry.dest_user)?;
|
let destuserpasswd: Passwd = getpwnam(&entry.dest_user)?;
|
||||||
@ -252,28 +290,30 @@ fn exec(entryname: &str, cmdargs: &Vec<String>) -> std::io::Result<()> {
|
|||||||
become_user(&destuserpasswd).or_else(|e| {
|
become_user(&destuserpasswd).or_else(|e| {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::PermissionDenied,
|
ErrorKind::PermissionDenied,
|
||||||
"Failed to switch user: ".to_owned() + &e.to_string(),
|
format!("Failed to switch user: {}", &e.to_string()),
|
||||||
));
|
));
|
||||||
})?;
|
})?;
|
||||||
setup_environment(&destuserpasswd, &entry.inherit_envs).or_else(|e| {
|
setup_environment(&destuserpasswd, &entry.inherit_envs)
|
||||||
|
.or_else(|e| {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
"Environment setup failure: ".to_owned() + &e.to_string(),
|
format!("Environment setup failure: {}", &e.to_string()),
|
||||||
));
|
));
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
drop_privs(&entry).or_else(|e| {
|
drop_privs(&entry).or_else(|e| {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
"Failed to drop priviliges: ".to_owned() + &e.to_string(),
|
format!("Failed to drop priviliges: {}", &e.to_string()),
|
||||||
));
|
));
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
errnowrapper(libc::execv(to_cstring(entry.cmd), args.as_ptr())).or_else(|e| {
|
errnowrapper(libc::execv(to_cstring(entry.cmd), args.as_ptr()))
|
||||||
|
.or_else(|e| {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
"execv failed: ".to_owned() + &e.to_string(),
|
format!("execv failed: {}", &e.to_string()),
|
||||||
));
|
));
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
@ -284,7 +324,12 @@ fn main() -> Result<(), std::io::Error> {
|
|||||||
let cmdargs: Vec<String> = argv.collect();
|
let cmdargs: Vec<String> = argv.collect();
|
||||||
let entryname = cmdargs.get(1);
|
let entryname = cmdargs.get(1);
|
||||||
if entryname.is_some() {
|
if entryname.is_some() {
|
||||||
match exec(&entryname.unwrap(), &cmdargs) {
|
let entry = entryname.unwrap();
|
||||||
|
if !entry.chars().all(|c| c.is_alphanumeric() || c == '.') {
|
||||||
|
eprintln!("Entry names can only contain alphanumeric characters and dots");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
match exec(entry, &cmdargs) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("The following error ocurred:");
|
eprintln!("The following error ocurred:");
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
|
新增問題並參考
封鎖使用者