ssh-timeout

This commit is contained in:
Colin 2024-04-30 20:30:18 -04:00
commit b5f0ab4908
15 changed files with 199 additions and 0 deletions

57
README.md Normal file
View File

@ -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.

57
build.sh Executable file
View File

@ -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."

View File

View File

View File

View File

View File

BIN
dist/ssh-timeout vendored Executable file

Binary file not shown.

BIN
dist/ssh-timeout_darwin_amd64 vendored Executable file

Binary file not shown.

BIN
dist/ssh-timeout_darwin_arm64 vendored Executable file

Binary file not shown.

BIN
dist/ssh-timeout_linux_arm vendored Executable file

Binary file not shown.

BIN
dist/ssh-timeout_linux_arm64 vendored Executable file

Binary file not shown.

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module yourmodule
go 1.21.1
require github.com/google/uuid v1.6.0

2
go.sum Normal file
View File

@ -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=

78
main.go Normal file
View File

@ -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)
}