Version 0.01

This commit is contained in:
2026-05-02 02:16:46 +04:00
commit 3265107079
9 changed files with 876 additions and 0 deletions

252
README.md Normal file
View File

@@ -0,0 +1,252 @@
# Cascade
Cascade is a lightweight, terminal-based interactive menu that lets administrators expose a curated set of shell commands to users without giving them a full shell. It is written in Go with no external dependencies.
---
## Table of Contents
- [How It Works](#how-it-works)
- [Building and Installing](#building-and-installing)
- [Configuration Files](#configuration-files)
- [Application Config (`config.ini`)](#application-config-configini)
- [Menu Config (`menu.ini`)](#menu-config-menuini)
- [Configuration Resolution Order](#configuration-resolution-order)
- [Menu File Format](#menu-file-format)
- [Comments and Section Headers](#comments-and-section-headers)
- [Dot-Separated Keys](#dot-separated-keys)
- [Mixed Nodes (Branch + Leaf)](#mixed-nodes-branch--leaf)
- [Environment Variables](#environment-variables)
- [Use Case: SSH User Isolation](#use-case-ssh-user-isolation)
- [Security Notes](#security-notes)
---
## How It Works
When Cascade starts it:
1. Reads the **application config** to know which shell to use and where the menu file lives.
2. Reads the **menu file** and builds an in-memory tree from dot-separated keys.
3. Switches stdin to **raw mode** and presents the current menu level.
4. The user navigates and selects an item using **arrow keys** or by **typing a number** — both methods work simultaneously.
5. `0` / selecting the Back/Exit item goes one level up (or exits at the root).
6. The selected command is executed by spawning `<shell> -c "<command>"` with stdin/stdout/stderr inherited from the current terminal, so fully interactive programs (shells, `less`, `psql`, etc.) work correctly.
7. After the command exits, the menu is redrawn. The menu file is **reloaded on every redraw**, so changes take effect without restarting Cascade.
---
## Building and Installing
```bash
# Build binary into ./build/cascade
make build
# Build and run locally
make run
# Install to /opt/cascade/ (copies binary + config files)
sudo make install
# Remove /opt/cascade/
sudo make delete
# Remove ./build/
make clean
```
`make install` backs up existing `/opt/cascade/config.ini` and `/opt/cascade/menu.ini` before overwriting them (timestamped `.old.*` copies).
---
## Configuration Files
### Application Config (`config.ini`)
A simple `key=value` file. Blank lines and lines starting with `#` are ignored.
| Key | Default | Description |
|---------|-------------|-------------|
| `shell` | `/bin/sh` | The shell used to execute menu commands (`<shell> -c "<cmd>"`). |
| `menu` | *(none)* | Explicit path to the menu file. Overridden by `CASCADE_MENU`. |
**Example (`config.ini.example`):**
```ini
shell=/bin/bash
menu=/etc/cascade/menu.ini
```
### Menu Config (`menu.ini`)
Defines the tree of commands shown to the user. See [Menu File Format](#menu-file-format) below.
---
## Configuration Resolution Order
### Application config
Cascade tries each candidate in order and stops at the first one it can read:
1. Path in `$CASCADE_CONF` environment variable.
2. `$HOME/.cascade/config.ini`
3. `/opt/cascade/config.ini`
4. Built-in defaults (`shell=/bin/sh`, no menu path).
### Menu file
1. Path in `$CASCADE_MENU` environment variable.
2. `menu=` value from the application config.
3. `$HOME/.cascade/menu.ini` (only if the file exists).
4. `/opt/cascade/menu.ini` (only if the file exists).
5. Fatal error — Cascade exits with a descriptive message.
---
## Menu File Format
### Comments and Section Headers
Lines starting with `#` are comments and are ignored entirely.
```ini
# This is a top-level comment
## This is a sub-section comment
Key=command
```
### Dot-Separated Keys
Each non-comment, non-blank line must be `Key=command`.
The **key** uses dots (`.`) as level separators to express a hierarchy:
```
Level1.Level2.Level3=shell command here
```
This creates a three-level menu: entering `Level1` shows `Level2`; entering `Level2` shows `Level3`; selecting `Level3` runs the command.
**Example:**
```ini
Nginx.Restart=docker restart portal-nginx
Nginx.Logs.Access=docker exec -it portal-nginx less /var/log/nginx/access.log
Nginx.Logs.Error=docker exec -it portal-nginx less /var/log/nginx/error.log
```
Results in this navigation tree:
```
[ Cascade v0.01 ]
Select Option:
1) Nginx <-
0) Exit
Enter number:
```
After pressing Enter (or ↓ then Enter):
```
[ Nginx ]
Select Option:
1) Logs
2) Restart <-
0) Back
Enter number:
```
```
[ Nginx > Logs ]
Select Option:
1) Access
2) Error
0) Back <-
Enter number:
```
Keys at the same level are displayed **sorted alphabetically**.
### Navigation Controls
| Input | Action |
|-------|--------|
| ↑ / ↓ | Move the cursor one item up or down. Wraps around at both ends. Moving clears any typed number. |
| Digit keys (`0``9`) | Type a number directly. The cursor jumps to the matching item as you type. |
| Backspace | Delete the last typed digit. |
| Enter | Confirm the selection — uses the typed number if one is being entered, otherwise uses the cursor position. |
Both input methods (arrows and number typing) can be mixed freely at any time.
### Mixed Nodes (Branch + Leaf)
A node can have both a direct command **and** child items. When this happens, an extra option `(run directly)` appears at the top of the submenu, allowing the user to execute the node's own command without navigating further.
```ini
Hrm.Shell=docker exec -it portal-hrm-php sh
Hrm.Service.PhpFpm.Restart=docker restart portal-hrm-php
```
`Hrm` has its own command (`Shell`) and also has children (`Service`). Selecting `Hrm` opens a submenu that includes `(run directly)` as option 1.
---
## Environment Variables
| Variable | Description |
|-----------------|-------------|
| `CASCADE_CONF` | Override the path to the application config file. |
| `CASCADE_MENU` | Override the path to the menu config file. |
These variables take highest priority in their respective resolution chains.
---
## Use Case: SSH User Isolation
Cascade is designed to be used as a restricted `ForceCommand` in OpenSSH, so that certain SSH users see only the allowed menu instead of a real shell.
**`/etc/ssh/sshd_config` (or a drop-in file):**
```
Match User dev
SetEnv CASCADE_MENU=/opt/cascade/dev.ini
SetEnv CASCADE_CONF=/opt/cascade/config.ini
ForceCommand /usr/bin/cascade
```
What this does:
- `ForceCommand /usr/bin/cascade` — every time the user `dev` connects, Cascade is launched instead of their login shell, regardless of what command they pass to `ssh`.
- `SetEnv CASCADE_CONF` — points to the application config (defines the shell used to run commands).
- `SetEnv CASCADE_MENU` — points to a menu file specific to this user, so different users can have different sets of allowed commands.
Different users (or `Match` blocks) can each have their own `CASCADE_MENU` pointing to different `.ini` files, giving fine-grained control over which commands each user can run.
**Deployment checklist:**
1. Copy the `cascade` binary to `/usr/bin/cascade` (or `/opt/cascade/cascade`).
2. Create `/opt/cascade/config.ini` with at minimum `shell=/bin/bash`.
3. Create per-user (or shared) menu files, e.g. `/opt/cascade/dev.ini`.
4. Add the `Match` block(s) to `sshd_config` and reload sshd (`systemctl reload sshd`).
---
## Security Notes
- **Raw terminal mode** — stdin is switched to raw mode only while waiting for a key press and is restored before running any command, so executed programs see a normal terminal.
- **Input validation** — typed number input is limited to 16 characters and must be all digits. Only arrow keys, digits, Backspace, and Enter are acted upon; all other key sequences are ignored.
- **Line length** — config lines longer than 4096 bytes are rejected by the scanner.
- **No shell access** — commands are defined entirely by the administrator in the menu file. The user can only pick a number; they cannot inject arbitrary commands.
- **ForceCommand** — when used with SSH `ForceCommand`, the user cannot bypass Cascade by passing a command to `ssh` directly.
- **Menu hot-reload** — the menu file is re-read on every screen redraw. Ensure the file is writable only by root/admin to prevent privilege escalation.