commit b5f0ab49089cc8ff06b1bbada5b0ad4cb55e009c Author: Colin Date: Tue Apr 30 20:30:18 2024 -0400 ssh-timeout diff --git a/README.md b/README.md new file mode 100644 index 0000000..42cdae4 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# SSH Timeout Proxy + +## Description +SSH Timeout Proxy is a simple TCP reverse proxy designed to handle SSH connections, forwarding them to a specified backend SSH server and enforcing a maximum connection duration. It utilizes environment variables to configure the backend SSH server's address and the maximum allowed connection duration, making it flexible for various deployment environments. + +## Requirements +- **Go** (at least Go 1.13) - To run the program. +- **SSH Server** - The backend server where SSH connections should be forwarded. Typically, this is `localhost:22` for testing purposes. + +## Installation +1. **Clone the repository:** + ```bash + git clone https://example.com/ssh-timeout-proxy.git + cd ssh-timeout-proxy + ``` + +2. **Build the project:** + ```bash + go build -o ssh-timeout + ``` + +## Configuration +The application uses two environment variables: +- `SSH_BACKEND`: Specifies the IP address and port of the backend SSH server (e.g., "localhost:22"). +- `SSH_MAX_DURATION`: Specifies the maximum duration (in seconds) that a connection should be allowed to persist. + +## Usage +To run the application, use the following command, setting the environment variables as needed: + +```bash +SSH_BACKEND="your_backend_ip:port" SSH_MAX_DURATION="duration_in_seconds" ./ssh-timeout +``` + +For example, to set the backend SSH server to `localhost` on port `22` and limit connection duration to 600 seconds (10 minutes), you would use: + +```bash +SSH_BACKEND="localhost:22" SSH_MAX_DURATION="600" ./ssh-timeout +``` + +## Testing +To test the SSH Timeout Proxy, perform the following steps: + +1. **Start the Proxy:** + Run the proxy with the desired backend and maximum duration configuration as shown in the Usage section. + +2. **SSH Through the Proxy:** + In a separate terminal window, use SSH to connect through the proxy: + ```bash + ssh -p 2222 your-username@localhost + ``` + Replace `your-username` with your actual username, and ensure you connect to the port where the proxy listens (default `2222`). + +3. **Observe:** + Monitor the terminal running the proxy to see the connection and disconnection logs. Ensure the connection is terminated after the specified duration. + +## Contributing +Contributions to the SSH Timeout Proxy are welcome. Please feel free to fork the repository, make changes, and submit pull requests. diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..293a43a --- /dev/null +++ b/build.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Default architecture +DEFAULT_ARCH="linux/amd64" + +# Supported architectures (adjust as needed) +ARCHITECTURES=("linux/amd64" "linux/arm64" "linux/arm/v7" "darwin/amd64" "darwin/arm64") + +# Ensure all necessary directories exist and go modules are ready +prepare_build() { + # Create necessary directories if they don't exist + mkdir -p dist + mkdir -p build_logs + + # Initialize go modules if go.mod does not exist + if [ ! -f go.mod ]; then + echo "Initializing Go modules" + go mod init yourmodule # Replace 'yourmodule' with your actual module name or path + fi + + # Fetch and ensure all dependencies are up to date + echo "Checking dependencies..." + go mod tidy +} + +# Build function +build_binary() { + os=$1 + arch=$2 + output_name="ssh-timeout" + + if [[ "$os/$arch" != "$DEFAULT_ARCH" ]]; then + output_name="${output_name}_${os}_${arch}" + fi + + output_name="dist/${output_name}" + + echo "Building for ${os}/${arch} -> ${output_name}" + GOOS=${os} GOARCH=${arch} go build -o ${output_name} main.go 2>build_logs/${os}_${arch}_build.log + if [ $? -eq 0 ]; then + echo "Successfully built ${output_name}" + else + echo "Failed to build ${output_name}. Check build_logs/${os}_${arch}_build.log for errors." + fi +} + +# Main Build Process +prepare_build +for arch in "${ARCHITECTURES[@]}"; do + IFS='/' read -r -a parts <<< "$arch" # Split architecture string + os=${parts[0]} + arch=${parts[1]} + build_binary $os $arch +done + +echo "Build process completed." + diff --git a/build_logs/darwin_amd64_build.log b/build_logs/darwin_amd64_build.log new file mode 100644 index 0000000..e69de29 diff --git a/build_logs/darwin_arm64_build.log b/build_logs/darwin_arm64_build.log new file mode 100644 index 0000000..e69de29 diff --git a/build_logs/linux_amd64_build.log b/build_logs/linux_amd64_build.log new file mode 100644 index 0000000..e69de29 diff --git a/build_logs/linux_arm64_build.log b/build_logs/linux_arm64_build.log new file mode 100644 index 0000000..e69de29 diff --git a/build_logs/linux_arm_build.log b/build_logs/linux_arm_build.log new file mode 100644 index 0000000..e69de29 diff --git a/dist/ssh-timeout b/dist/ssh-timeout new file mode 100755 index 0000000..acdb95b Binary files /dev/null and b/dist/ssh-timeout differ diff --git a/dist/ssh-timeout_darwin_amd64 b/dist/ssh-timeout_darwin_amd64 new file mode 100755 index 0000000..5718021 Binary files /dev/null and b/dist/ssh-timeout_darwin_amd64 differ diff --git a/dist/ssh-timeout_darwin_arm64 b/dist/ssh-timeout_darwin_arm64 new file mode 100755 index 0000000..19674ff Binary files /dev/null and b/dist/ssh-timeout_darwin_arm64 differ diff --git a/dist/ssh-timeout_linux_arm b/dist/ssh-timeout_linux_arm new file mode 100755 index 0000000..e93a00d Binary files /dev/null and b/dist/ssh-timeout_linux_arm differ diff --git a/dist/ssh-timeout_linux_arm64 b/dist/ssh-timeout_linux_arm64 new file mode 100755 index 0000000..144e361 Binary files /dev/null and b/dist/ssh-timeout_linux_arm64 differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f9030c8 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module yourmodule + +go 1.21.1 + +require github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7790d7c --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/main.go b/main.go new file mode 100644 index 0000000..371671c --- /dev/null +++ b/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "io" + "log" + "net" + "os" + "strconv" + "time" + + "github.com/google/uuid" +) + +func main() { + listenAddr := ":2222" // The local port on which to listen for incoming SSH connections. + backendAddr := os.Getenv("SSH_BACKEND") // Backend SSH server address from environment variable. + maxDuration := os.Getenv("SSH_MAX_DURATION") // Max connection duration from environment variable. + + duration, err := strconv.Atoi(maxDuration) + if err != nil { + log.Fatalf("Invalid SSH_MAX_DURATION value: %s", err) + } + + listener, err := net.Listen("tcp", listenAddr) + if err != nil { + log.Fatalf("Failed to open listener: %s", err) + } + defer listener.Close() + log.Println("Listening on", listenAddr) + + for { + clientConn, err := listener.Accept() + if err != nil { + log.Println("Failed to accept connection:", err) + continue + } + + go handleConnection(clientConn, backendAddr, time.Duration(duration)*time.Second) + } +} + +func handleConnection(clientConn net.Conn, backendAddr string, maxDuration time.Duration) { + connID := uuid.New().String() + log.Printf("New connection [%s] started from %s", connID, clientConn.RemoteAddr()) + + defer clientConn.Close() + + backendConn, err := net.Dial("tcp", backendAddr) + if err != nil { + log.Printf("Failed to connect to backend [%s]: %s", connID, err) + return + } + defer backendConn.Close() + + // Set up a timer to close both connections when the maxDuration is exceeded + timer := time.AfterFunc(maxDuration, func() { + log.Printf("Connection [%s] exceeded max duration, terminating", connID) + clientConn.Close() + backendConn.Close() + }) + defer timer.Stop() + + // Forward traffic between the client and the backend + go func() { + _, err := io.Copy(backendConn, clientConn) + if err != nil { + log.Printf("Error forwarding from client to backend [%s]: %s", connID, err) + } + }() + + _, err = io.Copy(clientConn, backendConn) + if err != nil { + log.Printf("Error forwarding from backend to client [%s]: %s", connID, err) + } + + log.Printf("Connection [%s] terminated", connID) +} +