diff --git a/Cargo.lock b/Cargo.lock index ebc7c01..b0eb8d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "atty" version = "0.2.14" @@ -25,6 +40,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bumpalo" version = "3.7.1" @@ -57,7 +78,7 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", - "num-traits", + "num-traits 0.2.14", "time", "winapi", ] @@ -95,6 +116,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "config" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" +dependencies = [ + "lazy_static", + "nom", + "rust-ini", + "serde 1.0.130", + "serde-hjson", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "ellipse" version = "0.2.0" @@ -370,12 +407,31 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + [[package]] name = "log" version = "0.4.14" @@ -425,6 +481,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -441,7 +508,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", - "num-traits", + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", ] [[package]] @@ -471,14 +547,16 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "pam-send-slack-message" -version = "0.1.2" +version = "0.2.0" dependencies = [ "chrono", "chrono-tz", + "config", "ellipse", "gethostname", "log", "reqwest", + "serde 1.0.130", "simple_logger", ] @@ -630,6 +708,8 @@ version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] @@ -662,7 +742,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls", - "serde", + "serde 1.0.130", "serde_json", "serde_urlencoded", "tokio", @@ -690,6 +770,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" + [[package]] name = "rustls" version = "0.19.1" @@ -719,11 +805,43 @@ dependencies = [ "untrusted", ] +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + [[package]] name = "serde" version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-hjson" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +dependencies = [ + "lazy_static", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "serde_json" @@ -733,7 +851,7 @@ checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" dependencies = [ "itoa", "ryu", - "serde", + "serde 1.0.130", ] [[package]] @@ -745,7 +863,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde", + "serde 1.0.130", ] [[package]] @@ -789,6 +907,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.80" @@ -866,6 +990,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde 1.0.130", +] + [[package]] name = "tower-service" version = "0.3.1" @@ -1099,3 +1232,12 @@ checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ "winapi", ] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index 5c2931b..ac476ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pam-send-slack-message" -version = "0.1.2" +version = "0.2.0" edition = "2018" authors = ["Iuri Diniz "] license = "MIT" @@ -13,6 +13,13 @@ description = "pam helper that publishes messages on Slack based on ssh access" extended-description = """\ pam-send-slack-message is a program that publishes messages on slack when the linux server is accessed through ssh.""" recommends = "openssh-server" +assets = [ + ["target/release/pam-send-slack-message", "/usr/bin/pam-send-slack-message", "755"], + ["src/settings.default.toml", "etc/pam-send-slack-message.toml", "600"], + ["README.md", "usr/share/doc/pam-send-slack-message/README", "644" ], + ["LICENSE", "usr/share/doc/pam-send-slack-message/LICENSE", "644" ], +] +conf-files = ["etc/pam-send-slack-message.toml"] [dependencies] log = "0.4.14" @@ -23,6 +30,8 @@ ellipse = "0.2.0" chrono = "0.4.19" chrono-tz = "0.6.0" gethostname = "0.2.1" +config = "0.11.0" +serde = { version = "1.0.130", features = ["derive"] } [profile.release] lto = true diff --git a/Makefile b/Makefile index d7369ed..10747df 100644 --- a/Makefile +++ b/Makefile @@ -13,14 +13,14 @@ CROSS_BINARIES=$(BINARY).x86_64.musl.upx $(BINARY).aarch64.musl.upx $(BINARY).i6 all: $(BINARY) cross: $(CROSS_BINARIES) -$(BINARY): $(wildcard src/*.rs) Cargo.toml +$(BINARY): $(wildcard src/*) Cargo.toml cargo build cp target/debug/$(BINARY) $(BINARY) strip $(BINARY) du -hs target/debug/$(BINARY) $(BINARY) # X86_64 musl -$(BINARY).x86_64.musl: $(wildcard src/*.rs) Cargo.toml +$(BINARY).x86_64.musl: $(wildcard src/*) Cargo.toml make _cross TARGET_BINARY=$@ TARGET_TRIPLE=x86_64-unknown-linux-musl x86_64-linux-gnu-strip $@ du -hs $@ @@ -29,7 +29,7 @@ $(BINARY).x86_64.musl.upx: $(BINARY).x86_64.musl make _upx TARGET_BINARY=$@ SOURCE_BINARY=$< # i686 musl -$(BINARY).i686.musl: $(wildcard src/*.rs) Cargo.toml +$(BINARY).i686.musl: $(wildcard src/*) Cargo.toml make _cross TARGET_BINARY=$@ TARGET_TRIPLE=i686-unknown-linux-musl i686-linux-gnu-strip $@ du -hs $@ @@ -38,7 +38,7 @@ $(BINARY).i686.musl.upx: $(BINARY).i686.musl make _upx TARGET_BINARY=$@ SOURCE_BINARY=$< # AARCH64 musl -$(BINARY).aarch64.musl: $(wildcard src/*.rs) Cargo.toml +$(BINARY).aarch64.musl: $(wildcard src/*) Cargo.toml make _cross TARGET_BINARY=$@ TARGET_TRIPLE=aarch64-unknown-linux-musl aarch64-linux-gnu-strip $@ du -hs $@ @@ -73,7 +73,7 @@ deb-i686: $(BINARY).i686.musl cargo deb -o ./ --target i686-unknown-linux-musl --no-build # requires nightly, rust-src, rust-std -# $(BINARY).musl-optz: $(wildcard src/*.rs) Cargo.toml +# $(BINARY).musl-optz: $(wildcard src/*) Cargo.toml # RUSTFLAGS="$(RUSTFLAGS) -L/usr/lib/x86_64-linux-musl/ -Copt-level=z -Cpanic=abort" cargo +nightly build -v -Z unstable-options -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --release --target x86_64-unknown-linux-musl # cp target/x86_64-unknown-linux-musl/release/$(BINARY) $(BINARY).musl-optz # strip $(BINARY).musl-optz @@ -111,8 +111,6 @@ fake-close-session: $(BINARY) release: cross sha1sum.txt - - sha1sum.txt: $(CROSS_BINARIES) rm -f sha1sum.txt sha1sum pam-send-slack-message.* | tee sha1sum.txt diff --git a/README.md b/README.md index ac68bb4..8f1e46c 100644 --- a/README.md +++ b/README.md @@ -4,37 +4,67 @@ pam-send-slack-message is a program that publishes messages on slack when the li ## Installation -Go to [releases page](https://github.com/iuridiniz/pam-send-slack-message/releases) and download last release. +Go to [releases page](https://github.com/iuridiniz/pam-send-slack-message/releases) and download last release. There are static binaries for Linux (ARM64, x86_64, x86) and a debian package for debian based systems (Ubuntu). + +Here a example of how to install it using upx (compressed) binary: ```bash -wget https://github.com/iuridiniz/pam-send-slack-message/releases/download/v0.1.2/pam-send-slack-message.$(uname -m).musl.upx +wget https://github.com/iuridiniz/pam-send-slack-message/releases/download/v0.2.0/pam-send-slack-message.$(uname -m).musl.upx sudo mkdir -p /usr/local/bin/ sudo cp pam-send-slack-message.$(uname -m).musl.upx /usr/local/bin/pam-send-slack-message chmod +x /usr/local/bin/pam-send-slack-message ``` +### Configuration In order to work, you need a `SLACK-TOKEN` with `channel.write` permission and a `SLACK-CHANNEL-ID`. Follow instructions [here](https://api.slack.com/messaging/sending), if you are lost. -``` + +```bash # configure pam/sshd -echo "session optional pam_exec.so /usr/local/bin/pam-send-slack-message SLACK-CHANNEL-ID SLACK-TOKEN" | sudo tee -a /etc/pam.d/sshd +echo "session optional pam_exec.so /usr/local/bin/pam-send-slack-message | sudo tee -a /etc/pam.d/sshd +``` + +create a file `/etc/pam.d/pam-send-slack-message.conf` with the following content: -# assure token cannot be viewed by any ordinary user -sudo chmod o-r /etc/pam.d/sshd +```ini +slack_token = "" +slack_channel_id = "" +# see https://api.slack.com/reference/surfaces/formatting +open_session_message = """🕵️ ▶️▶️▶️ IP `{addr}` logged in `{hostname}` as `{user}` using `{auth_info}` at `{when}`""" +close_session_message = """🕵️ 🛑🛑🛑 IP `{addr}` logout from `{hostname}` (is was `{user}` using `{auth_info}`) at `{when}`""" +# could be "America/Sao_Paulo" or "America/Los_Angeles" or "Europe/Oslo" +timezone = "UTC" ``` +replace `` and `` with your own. ## Usage -After configuration, just log via ssh. +After machine configuration, just log in the machine through ssh. ### pam/sshd configuration -This program need to be called by pam at session phase, you must change `/etc/pam.d/sshd` to have this line: +This program need to be called by pam at session phase, you must edit `/etc/pam.d/sshd` to have this line: ``` -session optional pam_exec.so /path/to/pam-send-slack-message SLACK-CHANNEL-ID SLACK-TOKEN +session optional pam_exec.so /path/to/pam-send-slack-message ``` +You can learn about pam configuration [here](http://www.linux-pam.org/Linux-PAM-html/sag-configuration-file.html). + +### pam-send-slack-message configuration + +A file located at `/etc/pam.d/pam-send-slack-message.conf` is used to configure this software. + +The valid keys are: + +* `slack_token`: your slack token (required) +* `slack_channel_id`: your slack channel id (required) +* `open_session_message`: the message to send when a user logs in (if not specified, the default message will be used) +* `close_session_message`: the message to send when a user logs out (if not specified, the default message will be used). +* `timezone`: the timezone to use (defaults to UTC) + +You can view default values in [src/settings.default.toml](https://github.com/iuridiniz/pam-send-slack-message/blob/master/src/settings.default.toml) + ## Hacking ### Manual compilation diff --git a/src/main.rs b/src/main.rs index 25d7f2d..1627cb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,14 @@ use ellipse::Ellipse; -use log::{error, info}; +use log::{error, info, warn}; use simple_logger::SimpleLogger; -use chrono_tz::America::Fortaleza; +mod settings; +use settings::Settings; + +enum PamType { + OpenSession, + CloseSession, +} #[derive(Default, Debug)] struct SshAuthInfo { @@ -82,44 +88,39 @@ fn send_slack_msg( .send() } -fn open_session(channel_id: String, token: String) { +fn handle_pam(settings: Settings, pam_type: PamType) { + let channel_id = settings.slack_channel_id.clone(); + let token = settings.slack_token.clone(); + let tz: Result = settings.timezone.parse(); + let when; + if tz.is_ok() { + when = chrono::Utc::now().with_timezone(&tz.unwrap()).to_rfc2822(); + } else { + warn!("invalid timezone `{}`, using utc", settings.timezone); + when = chrono::Utc::now().to_rfc2822(); + } + let user = get_user(); let addr = get_remote_ip(); - let host = get_hostname(); + let hostname = get_hostname(); let auth_info = get_ssh_auth_info(); - let when = chrono::Local::now().with_timezone(&Fortaleza).to_rfc2822(); - // https://api.slack.com/reference/surfaces/formatting - // TODO: use a external template - let msg = format!( - "🕵️ ▶️▶️▶️ IP `{}` logged in `{}` as `{}` using `{}` at `{}`", - addr, host, user, auth_info, when - ); - - info!("{}", msg); - let res = send_slack_msg(channel_id, token, msg); - if res.is_err() { - error!("Cannot send slack message"); - return; + let mut msg; + match pam_type { + PamType::OpenSession => { + msg = settings.open_session_message.clone(); + } + PamType::CloseSession => { + msg = settings.close_session_message.clone(); + } } - let body = res.unwrap().text().unwrap(); - info!("API response:\n==mark==\n{}\n==mark==", body); -} + // Replace placeholders + msg = msg.replace("{addr}", &addr); + msg = msg.replace("{hostname}", &hostname); + msg = msg.replace("{user}", &user); + msg = msg.replace("{auth_info}", auth_info.to_string().as_str()); + msg = msg.replace("{when}", &when); -fn close_session(channel_id: String, token: String) { - let user = get_user(); - let addr = get_remote_ip(); - let host = get_hostname(); - let auth_info = get_ssh_auth_info(); - let when = chrono::Local::now().with_timezone(&Fortaleza).to_rfc2822(); - - // https://api.slack.com/reference/surfaces/formatting - // TODO: use a external template - let msg = format!( - "🕵️ 🛑🛑🛑 IP `{}` logout from `{}` (is was `{}` using `{}`) at `{}`", - addr, host, user, auth_info, when - ); - info!("{}", msg); let res = send_slack_msg(channel_id, token, msg); if res.is_err() { error!("Cannot send slack message"); @@ -132,21 +133,21 @@ fn close_session(channel_id: String, token: String) { fn main() { SimpleLogger::new().init().unwrap(); - let args: Vec = std::env::args().collect(); - if args.len() < 3 { - eprintln!("Usage: {} ", args[0]); - return; - } + let config_file = format!("/etc/{}", env!("CARGO_PKG_NAME")); + let settings = Settings::new(config_file.as_str()); + // dbg!(&settings); - let channel_id = args[1].clone(); - let token = args[2].clone(); + if settings.is_err() { + eprintln!("Cannot read settings: {:?}", settings.err()); + std::process::exit(1); + } let pam_type = get_pam_type(); if pam_type == "open_session" { - open_session(channel_id, token); + handle_pam(settings.unwrap(), PamType::OpenSession); } else if pam_type == "close_session" { - close_session(channel_id, token); + handle_pam(settings.unwrap(), PamType::CloseSession); } else { eprintln!("Unknown environment `PAM_TYPE`={:?}", pam_type); }