├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build compiled files 2 | target/ 3 | 4 | # Ignore backups created by rustfmt 5 | **/*.rs.bk 6 | 7 | # Ignore vim temp and swap files 8 | *~ 9 | *.sw? 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: nightly 9 | -------------------------------------------------------------------------------- /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 = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "1.1.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 21 | 22 | [[package]] 23 | name = "bitflags" 24 | version = "1.3.2" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 27 | 28 | [[package]] 29 | name = "clap" 30 | version = "3.2.14" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "54635806b078b7925d6e36810b1755f2a4b5b4d57560432c1ecf60bcbe10602b" 33 | dependencies = [ 34 | "atty", 35 | "bitflags", 36 | "clap_lex", 37 | "indexmap", 38 | "once_cell", 39 | "strsim", 40 | "termcolor", 41 | "textwrap", 42 | ] 43 | 44 | [[package]] 45 | name = "clap_lex" 46 | version = "0.2.4" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 49 | dependencies = [ 50 | "os_str_bytes", 51 | ] 52 | 53 | [[package]] 54 | name = "hashbrown" 55 | version = "0.12.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 58 | 59 | [[package]] 60 | name = "hermit-abi" 61 | version = "0.1.19" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 64 | dependencies = [ 65 | "libc", 66 | ] 67 | 68 | [[package]] 69 | name = "indexmap" 70 | version = "1.9.1" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 73 | dependencies = [ 74 | "autocfg", 75 | "hashbrown", 76 | ] 77 | 78 | [[package]] 79 | name = "libc" 80 | version = "0.2.126" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 83 | 84 | [[package]] 85 | name = "once_cell" 86 | version = "1.13.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 89 | 90 | [[package]] 91 | name = "os_str_bytes" 92 | version = "6.2.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" 95 | 96 | [[package]] 97 | name = "proc-macro2" 98 | version = "1.0.40" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" 101 | dependencies = [ 102 | "unicode-ident", 103 | ] 104 | 105 | [[package]] 106 | name = "quote" 107 | version = "1.0.20" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" 110 | dependencies = [ 111 | "proc-macro2", 112 | ] 113 | 114 | [[package]] 115 | name = "resh" 116 | version = "0.1.4" 117 | dependencies = [ 118 | "clap", 119 | "serde", 120 | "serde_derive", 121 | "toml", 122 | ] 123 | 124 | [[package]] 125 | name = "serde" 126 | version = "1.0.140" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" 129 | 130 | [[package]] 131 | name = "serde_derive" 132 | version = "1.0.140" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" 135 | dependencies = [ 136 | "proc-macro2", 137 | "quote", 138 | "syn", 139 | ] 140 | 141 | [[package]] 142 | name = "strsim" 143 | version = "0.10.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 146 | 147 | [[package]] 148 | name = "syn" 149 | version = "1.0.98" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 152 | dependencies = [ 153 | "proc-macro2", 154 | "quote", 155 | "unicode-ident", 156 | ] 157 | 158 | [[package]] 159 | name = "termcolor" 160 | version = "1.1.3" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 163 | dependencies = [ 164 | "winapi-util", 165 | ] 166 | 167 | [[package]] 168 | name = "textwrap" 169 | version = "0.15.0" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 172 | 173 | [[package]] 174 | name = "toml" 175 | version = "0.5.9" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" 178 | dependencies = [ 179 | "serde", 180 | ] 181 | 182 | [[package]] 183 | name = "unicode-ident" 184 | version = "1.0.2" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" 187 | 188 | [[package]] 189 | name = "winapi" 190 | version = "0.3.9" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 193 | dependencies = [ 194 | "winapi-i686-pc-windows-gnu", 195 | "winapi-x86_64-pc-windows-gnu", 196 | ] 197 | 198 | [[package]] 199 | name = "winapi-i686-pc-windows-gnu" 200 | version = "0.4.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 203 | 204 | [[package]] 205 | name = "winapi-util" 206 | version = "0.1.5" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 209 | dependencies = [ 210 | "winapi", 211 | ] 212 | 213 | [[package]] 214 | name = "winapi-x86_64-pc-windows-gnu" 215 | version = "0.4.0" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 218 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "resh" 3 | version = "0.1.5" 4 | authors = ["Koen Wilde "] 5 | description = "A restricted shell that only allows execution of previously defined aliases" 6 | edition = "2018" 7 | 8 | homepage = "https://github.com/koenw/resh" 9 | repository = "https://github.com/koenw/resh" 10 | 11 | readme = "README.md" 12 | license = "MIT" 13 | keywords = ["shell", "ssh", "restricted", "nrpe"] 14 | 15 | [dependencies] 16 | serde = "1.0" 17 | serde_derive = "1.0" 18 | toml = "0.5" 19 | clap = { version = "3", features = ["cargo"] } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # resh 2 | 3 | [![Build Status](https://api.travis-ci.org/koenw/resh.svg?branch=master)](https://travis-ci.org/koenw/resh) 4 | 5 | `resh` is a shell that only allows the execution of previously 6 | whitelisted commands. Use it to restrict automated ssh logins. 7 | 8 | ## Usage 9 | 10 | Define aliases for the commands you want to allow in the *commands* section of 11 | `/etc/resh.toml`: 12 | 13 | ```sh 14 | $ cat /etc/resh.toml 15 | [commands] 16 | foo = "echo hello" 17 | ``` 18 | 19 | Next set resh as the login shell for the user you want to restrict. The user 20 | will now only be able to execute the whitelisted commands: 21 | 22 | ```sh 23 | # su - example_user 24 | Usage: -resh 25 | # su - example_user bar 26 | Undefined command alias: bar 27 | # su - example_user foo 28 | hello 29 | ``` 30 | 31 | Or using ssh: 32 | ```sh 33 | $ ssh example_user@localhost foo 34 | hello 35 | ``` 36 | 37 | ### Alternative config file locations 38 | 39 | You can specify an alternative config file by setting the `RESH_CONFIG` 40 | environmental variable. For example, to specify a config file per ssh key: 41 | 42 | ```sh 43 | $ cat ~example_user/.ssh/authorized_keys 44 | environment="RESH_CONFIG=/home/test/resh.toml" AAAAB3NzaC1yc2EAAAADAQABAAACAQD7BsnSaa0gkPJDGZM7psAEkx+68ILJlKHS6MlUfVpQu7UoercvJXqctHczeIEf1eJToK7RmiKufoicLkHQplRpI9kP4IDAx2V0LO4BRncIOyF8wk6I7N6k6glAxePA4MgPaSsFp8SyXYW9wy+0491YHr9sWaqaKG78OQSCyf+/wwynRnwdn2u0dcRl064CGxrYleGe0AHHOSl9jj9J2Ve6M7pjZLuixRLqB2VBYyIAwy/zO7dvuxxvLIGr31TqKdLnnUvLKeInn5IU+UPMxuHG9DC9yLnif29OUzNRERTF4utkRI+ywByFTj/QePp+uTvmVv0PtkGwm77LKxeBP7jP3Hhe2uvf5clApcF+6EjFBNKWxVReH35NGPasY8DNL7Mt5CfBZcdi4nhQZyCQ7Z/XlXmJRMxmYsowhHQB8HkOM8MpHPqP9EBf9eTnxhMaA5qnrSy/z+1vdKHVXc4camSF8z7dRJKDmuoYl+aPcjS5MX6AEVz5gtFsizjhLq+mp2HkvskSZCPY87D0/hriPPtSMUlhh4XKyFJ2VzkfIr1uqQlaN1tIPdCAdUDjH5o5fnqSFHqkD8iah8OiNhmGLk2VPiYohnMLcDdLGtPMkOpX3ODgjNOTcaUfaMZW4IacVcHA2A11Zxe8r73qcjKjcX5mEppMa1Z2vosqJn2dGTasHQ== example_user@example 45 | ``` 46 | 47 | ### Use resh without changing the login shell 48 | 49 | If you don't want to change the user's login shell, you can force resh on a 50 | per-key basis by setting the `command` option for the ssh public key: 51 | 52 | ```sh 53 | $ cat ~example_user/.ssh/authorized_keys 54 | command="/usr/local/bin/resh" AAAAB3NzaC1yc2EAAAADAQABAAACAQD7BsnSaa0gkPJDGZM7psAEkx+68ILJlKHS6MlUfVpQu7UoercvJXqctHczeIEf1eJToK7RmiKufoicLkHQplRpI9kP4IDAx2V0LO4BRncIOyF8wk6I7N6k6glAxePA4MgPaSsFp8SyXYW9wy+0491YHr9sWaqaKG78OQSCyf+/wwynRnwdn2u0dcRl064CGxrYleGe0AHHOSl9jj9J2Ve6M7pjZLuixRLqB2VBYyIAwy/zO7dvuxxvLIGr31TqKdLnnUvLKeInn5IU+UPMxuHG9DC9yLnif29OUzNRERTF4utkRI+ywByFTj/QePp+uTvmVv0PtkGwm77LKxeBP7jP3Hhe2uvf5clApcF+6EjFBNKWxVReH35NGPasY8DNL7Mt5CfBZcdi4nhQZyCQ7Z/XlXmJRMxmYsowhHQB8HkOM8MpHPqP9EBf9eTnxhMaA5qnrSy/z+1vdKHVXc4camSF8z7dRJKDmuoYl+aPcjS5MX6AEVz5gtFsizjhLq+mp2HkvskSZCPY87D0/hriPPtSMUlhh4XKyFJ2VzkfIr1uqQlaN1tIPdCAdUDjH5o5fnqSFHqkD8iah8OiNhmGLk2VPiYohnMLcDdLGtPMkOpX3ODgjNOTcaUfaMZW4IacVcHA2A11Zxe8r73qcjKjcX5mEppMa1Z2vosqJn2dGTasHQ== example_user@example 55 | ``` 56 | 57 | ### Full ssh *`authorized_keys`* example 58 | 59 | ```sh 60 | $ cat ~example_user/.ssh/authorized_keys 61 | command="/usr/local/bin/resh",environment="RESH_CONFIG=/usr/local/etc/resh.toml",restrict AAAAB3NzaC1yc2EAAAADAQABAAACAQD7BsnSaa0gkPJDGZM7psAEkx+68ILJlKHS6MlUfVpQu7UoercvJXqctHczeIEf1eJToK7RmiKufoicLkHQplRpI9kP4IDAx2V0LO4BRncIOyF8wk6I7N6k6glAxePA4MgPaSsFp8SyXYW9wy+0491YHr9sWaqaKG78OQSCyf+/wwynRnwdn2u0dcRl064CGxrYleGe0AHHOSl9jj9J2Ve6M7pjZLuixRLqB2VBYyIAwy/zO7dvuxxvLIGr31TqKdLnnUvLKeInn5IU+UPMxuHG9DC9yLnif29OUzNRERTF4utkRI+ywByFTj/QePp+uTvmVv0PtkGwm77LKxeBP7jP3Hhe2uvf5clApcF+6EjFBNKWxVReH35NGPasY8DNL7Mt5CfBZcdi4nhQZyCQ7Z/XlXmJRMxmYsowhHQB8HkOM8MpHPqP9EBf9eTnxhMaA5qnrSy/z+1vdKHVXc4camSF8z7dRJKDmuoYl+aPcjS5MX6AEVz5gtFsizjhLq+mp2HkvskSZCPY87D0/hriPPtSMUlhh4XKyFJ2VzkfIr1uqQlaN1tIPdCAdUDjH5o5fnqSFHqkD8iah8OiNhmGLk2VPiYohnMLcDdLGtPMkOpX3ODgjNOTcaUfaMZW4IacVcHA2A11Zxe8r73qcjKjcX5mEppMa1Z2vosqJn2dGTasHQ== example_user@example 62 | ``` 63 | 64 | For more information on the options you can specify in the `authorized_keys` 65 | file, refer to the *`AUTHORIZED_KEYS FILE FORMAT`* section of `man 8 sshd`. You 66 | may especially be interested in the `restrict` option, which disables 67 | features like tcp port forwarding. 68 | 69 | ## Building 70 | 71 | To build resh, you will need rust and cargo installed, for which I'll refer to 72 | the [official documentation](https://www.rust-lang.org/tools/install), but 73 | don't forget to check your local package manager. Then, from the repo root 74 | directory: 75 | 76 | ```sh 77 | cargo build --release 78 | ``` 79 | 80 | The resulting binary will be written to `target/release/resh`. 81 | 82 | ## Installation 83 | 84 | `cargo install resh` will install resh to `~/.cargo/bin`, which should be in in 85 | your `$PATH` if cargo is installed correctly. 86 | 87 | ## Future ideas 88 | 89 | * Possibly support an *IncludeDir* option in the config file, for easier 90 | provisioning from e.g. puppet or ansible. 91 | * Provide pre-build binaries for OpenBSD, FreeBSD and linux. 92 | 93 | ## Feedback & Questions 94 | 95 | If you've got any feedback or questions, please don't hesitate :) 96 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1656065134, 6 | "narHash": "sha256-oc6E6ByIw3oJaIyc67maaFcnjYOz1mMcOtHxbEf9NwQ=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "bee6a7250dd1b01844a2de7e02e4df7d8a0a206c", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "nixpkgs": { 19 | "locked": { 20 | "lastModified": 1658380158, 21 | "narHash": "sha256-DBunkegKWlxPZiOcw3/SNIFg93amkdGIy2g0y/jDpHg=", 22 | "owner": "NixOS", 23 | "repo": "nixpkgs", 24 | "rev": "a65b5b3f5504b8b89c196aba733bdf2b0bd13c16", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "owner": "NixOS", 29 | "ref": "nixos-unstable", 30 | "repo": "nixpkgs", 31 | "type": "github" 32 | } 33 | }, 34 | "nixpkgs_2": { 35 | "locked": { 36 | "lastModified": 1656401090, 37 | "narHash": "sha256-bUS2nfQsvTQW2z8SK7oEFSElbmoBahOPtbXPm0AL3I4=", 38 | "owner": "NixOS", 39 | "repo": "nixpkgs", 40 | "rev": "16de63fcc54e88b9a106a603038dd5dd2feb21eb", 41 | "type": "github" 42 | }, 43 | "original": { 44 | "owner": "NixOS", 45 | "ref": "nixpkgs-unstable", 46 | "repo": "nixpkgs", 47 | "type": "github" 48 | } 49 | }, 50 | "root": { 51 | "inputs": { 52 | "nixpkgs": "nixpkgs", 53 | "rust-overlay": "rust-overlay" 54 | } 55 | }, 56 | "rust-overlay": { 57 | "inputs": { 58 | "flake-utils": "flake-utils", 59 | "nixpkgs": "nixpkgs_2" 60 | }, 61 | "locked": { 62 | "lastModified": 1658458343, 63 | "narHash": "sha256-kzVNJ3EMIlwzztBQQxHv+kGr+L+pYdjFX8LSf3G8LN4=", 64 | "owner": "oxalica", 65 | "repo": "rust-overlay", 66 | "rev": "a6c778067fb6682e4b14835a0957ac2d235f5593", 67 | "type": "github" 68 | }, 69 | "original": { 70 | "owner": "oxalica", 71 | "repo": "rust-overlay", 72 | "type": "github" 73 | } 74 | } 75 | }, 76 | "root": "root", 77 | "version": 7 78 | } 79 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "resh is a shell that only allows the execution of previously whitelisted commands."; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | rust-overlay.url = "github:oxalica/rust-overlay"; 7 | }; 8 | 9 | outputs = { self, nixpkgs, rust-overlay }: 10 | let 11 | supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"]; 12 | forAllSystems = nixpkgs.lib.genAttrs supportedSystems; 13 | overlays = [ (import rust-overlay) ]; 14 | nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system overlays; }); 15 | in 16 | { 17 | devShells = forAllSystems (system: 18 | let 19 | pkgs = nixpkgsFor.${system}; 20 | rustPlatform = pkgs.makeRustPlatform { 21 | cargo = pkgs.rust-bin.stable.latest.default; 22 | rustc = pkgs.rust-bin.stable.latest.default; 23 | }; 24 | in 25 | { 26 | default = pkgs.mkShell { 27 | buildInputs = with pkgs; [ 28 | gcc 29 | rust-bin.stable.latest.default 30 | ]; 31 | shellHook = '' 32 | echo "Welcome to the resh development shell." 33 | user_shell=$(getent passwd "$(whoami)" |cut -d: -f 7) 34 | exec "$user_shell" 35 | ''; 36 | }; 37 | } 38 | ); 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_derive; 3 | 4 | use std::collections::BTreeMap; 5 | use std::fs::File; 6 | use std::io::prelude::*; 7 | 8 | use clap::{App, Arg}; 9 | 10 | macro_rules! die( 11 | ($($arg:tt)*) => { { 12 | writeln!(std::io::stderr(), $($arg)*) 13 | .expect("Failed to print to stderr"); 14 | std::process::exit(1); 15 | } } 16 | ); 17 | 18 | #[derive(Deserialize)] 19 | struct Config { 20 | commands: BTreeMap, 21 | } 22 | 23 | fn read_config>(path: P) -> Result> { 24 | let mut contents = String::new(); 25 | 26 | File::open(path)?.read_to_string(&mut contents)?; 27 | 28 | let config: Config = toml::from_str(&contents)?; 29 | Ok(config) 30 | } 31 | 32 | fn run_command(command: &str) -> Result> { 33 | let mut child = std::process::Command::new("/bin/sh") 34 | .arg("-c") 35 | .arg(command) 36 | .spawn()?; 37 | 38 | child 39 | .wait()? 40 | .code() 41 | .ok_or_else(|| std::io::Error::last_os_error().into()) 42 | } 43 | 44 | fn main() { 45 | let matches = App::new(clap::crate_name!()) 46 | .version(clap::crate_version!()) 47 | .author(clap::crate_authors!()) 48 | .about("resh is a restricted (ssh) shell that only allows whitelisted commands") 49 | .arg( 50 | Arg::with_name("command") 51 | .short('c') 52 | .help("Alias of command to execute") 53 | .value_name("COMMAND"), 54 | ) 55 | .get_matches(); 56 | 57 | let command_alias = match matches.value_of("command") { 58 | Some(cmd) => String::from(cmd), 59 | None => match std::env::var("SSH_ORIGINAL_COMMAND") { 60 | Ok(cmd) => cmd, 61 | _ => die!("Usage: {} ", clap::crate_name!()), 62 | }, 63 | }; 64 | 65 | let config_file = std::env::var("RESH_CONFIG").unwrap_or_else(|_| "/etc/resh.toml".to_string()); 66 | 67 | let config: Config = read_config(&config_file).unwrap_or_else(|e| { 68 | die!("Failed to read {}: {}", config_file, e); 69 | }); 70 | 71 | let full_command = match config.commands.get(&command_alias) { 72 | Some(cmd) => cmd, 73 | None => die!("Undefined command alias: {}", command_alias), 74 | }; 75 | 76 | let exitcode = run_command(full_command).unwrap_or(1); 77 | 78 | std::process::exit(exitcode); 79 | } 80 | --------------------------------------------------------------------------------