feat: add psql shell and username-to-shell routing

Add a PostgreSQL psql interactive terminal shell with backslash
meta-commands, SQL statement handling with multi-line buffering, and
canned responses for common queries. Add username-based shell routing
via [shell.username_routes] config (second priority after credential-
specific shell, before random selection). Bump version to 0.13.0.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 19:58:34 +01:00
parent c4801e3309
commit 40fda3420c
12 changed files with 912 additions and 10 deletions

View File

@@ -28,10 +28,11 @@ type WebConfig struct {
}
type ShellConfig struct {
Hostname string `toml:"hostname"`
Banner string `toml:"banner"`
FakeUser string `toml:"fake_user"`
Shells map[string]map[string]any `toml:"-"` // per-shell config extracted manually
Hostname string `toml:"hostname"`
Banner string `toml:"banner"`
FakeUser string `toml:"fake_user"`
UsernameRoutes map[string]string `toml:"username_routes"`
Shells map[string]map[string]any `toml:"-"` // per-shell config extracted manually
}
type StorageConfig struct {
@@ -165,9 +166,10 @@ func applyDefaults(cfg *Config) {
// knownShellKeys are top-level keys in [shell] that are not per-shell sub-tables.
var knownShellKeys = map[string]bool{
"hostname": true,
"banner": true,
"fake_user": true,
"hostname": true,
"banner": true,
"fake_user": true,
"username_routes": true,
}
// extractShellTables pulls per-shell config sub-tables from the raw [shell] section.

View File

@@ -313,6 +313,42 @@ func TestLoadInvalidTOML(t *testing.T) {
}
}
func TestLoadUsernameRoutes(t *testing.T) {
content := `
[shell]
hostname = "myhost"
[shell.username_routes]
postgres = "psql"
admin = "bash"
[shell.bash]
custom_key = "value"
`
path := writeTemp(t, content)
cfg, err := Load(path)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if cfg.Shell.UsernameRoutes == nil {
t.Fatal("UsernameRoutes should not be nil")
}
if cfg.Shell.UsernameRoutes["postgres"] != "psql" {
t.Errorf("UsernameRoutes[\"postgres\"] = %q, want %q", cfg.Shell.UsernameRoutes["postgres"], "psql")
}
if cfg.Shell.UsernameRoutes["admin"] != "bash" {
t.Errorf("UsernameRoutes[\"admin\"] = %q, want %q", cfg.Shell.UsernameRoutes["admin"], "bash")
}
// username_routes should NOT appear in the Shells map.
if _, ok := cfg.Shell.Shells["username_routes"]; ok {
t.Error("username_routes should not appear in Shells map")
}
// bash should still appear in Shells map.
if _, ok := cfg.Shell.Shells["bash"]; !ok {
t.Error("Shells[\"bash\"] should still be present")
}
}
func writeTemp(t *testing.T, content string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "config.toml")