Skip to content

Commit

Permalink
Merge pull request #65 from udx/develop-andy
Browse files Browse the repository at this point in the history
fix: Improve SFTP reliability and documentation
  • Loading branch information
kavaribes authored Jan 10, 2025
2 parents e690b30 + 000e0e8 commit 403ba33
Show file tree
Hide file tree
Showing 10 changed files with 484 additions and 66 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:23.4-alpine
FROM node:23.5-alpine
ENV VERSION=v1.31.0
ENV NODE_ENV=production
ENV SERVICE_ENABLE_SSHD=true
Expand Down
48 changes: 29 additions & 19 deletions bin/controller.ssh.entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,35 @@ export USER_LOGIN=$(echo ${ENV_VARS} | cut -d ';' -f 2)

echo "[$(date)] Have a session for [${USER_LOGIN}] : ${USER}, ${SSH_ORIGINAL_COMMAND}, ${SSH_CLIENT}, ${SSH_CONNECTION} and [${CONNECTION_STRING}] command." >> /var/log/sshd.log

## SFTP.
if [[ ${SSH_ORIGINAL_COMMAND} == "internal-sftp" ]]; then

echo "[$(date)] Have SFTP connection [${CONNECTION_STRING}] for [${USER}]." >> /var/log/sshd.log

/usr/local/bin/kubectl exec -n ${CONNECTION_STRING} -i -- /usr/lib/sftp-server

exit;

fi

if [[ ${SSH_ORIGINAL_COMMAND} == "/usr/lib/ssh/sftp-server" ]]; then

echo "[$(date)] Have SFTP connection [${CONNECTION_STRING}] for [${USER}]." >> /var/log/sshd.log

/usr/local/bin/kubectl exec -n ${CONNECTION_STRING} -i -- /usr/lib/sftp-server

exit;

## SFTP handling with Alpine-specific paths
if [[ ${SSH_ORIGINAL_COMMAND} == "internal-sftp" ]] || [[ ${SSH_ORIGINAL_COMMAND} == "/usr/lib/ssh/sftp-server" ]]; then
echo "[$(date)] SFTP connection attempt from [${SSH_CLIENT}] for user [${USER}] to pod [${CONNECTION_STRING}]" >> /var/log/sshd.log

# Check container OS type for better error reporting
CONTAINER_OS="unknown"
if /usr/local/bin/kubectl exec -n ${CONNECTION_STRING} -- which apk >/dev/null 2>&1; then
CONTAINER_OS="Alpine"
elif /usr/local/bin/kubectl exec -n ${CONNECTION_STRING} -- which apt-get >/dev/null 2>&1; then
CONTAINER_OS="Debian/Ubuntu"
elif /usr/local/bin/kubectl exec -n ${CONNECTION_STRING} -- which yum >/dev/null 2>&1; then
CONTAINER_OS="RHEL/CentOS"
fi
echo "[$(date)] Container OS detected: ${CONTAINER_OS}" >> /var/log/sshd.log

# Try common SFTP server paths
for SFTP_PATH in "/usr/lib/ssh/sftp-server" "/usr/lib/sftp-server" "/usr/libexec/sftp-server"; do
echo "[$(date)] Checking for SFTP server at ${SFTP_PATH}" >> /var/log/sshd.log
if /usr/local/bin/kubectl exec -n ${CONNECTION_STRING} -- test -f ${SFTP_PATH} 2>/dev/null; then
echo "[$(date)] Found SFTP server at ${SFTP_PATH}, establishing connection" >> /var/log/sshd.log
exec /usr/local/bin/kubectl exec -n ${CONNECTION_STRING} -i -- ${SFTP_PATH}
exit 0
fi
done

# If we get here, we couldn't find the SFTP server
echo "[$(date)] Error: SFTP server not found in ${CONTAINER_OS} container [${CONNECTION_STRING}]. Client IP: ${SSH_CLIENT}" >> /var/log/sshd.log
echo "Error: SFTP access requires openssh-sftp-server to be installed in the container. Please contact your administrator." >&2
exit 1
fi

## Specific Command, pipe into container.
Expand Down
6 changes: 6 additions & 0 deletions changes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### 0.5.5
* OS updates
* Updated node modules. Updated Alpine to node:23.5-alpine
* Improved SSH/SFTP handling and documentation
* Improved SSH config and add logging documentation

### 0.5.4
* Updated openssh to 9.9p1

Expand Down
123 changes: 123 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Architecture Details

## Overview

The Docker SFTP/SSH Gateway is designed as a secure bridge between users and Kubernetes pods, using GitHub for authentication and authorization. The system consists of several interconnected components that work together to provide secure access.

## Core Components

### 1. SSH Gateway (SSHD)

- **Process Management**: Runs under PM2 for reliability
- **Authentication**: Uses GitHub SSH keys
- **Configuration**: Custom `sshd_config` with ForceCommand
- **SFTP Support**: Internal SFTP subsystem enabled

#### Key Files:
- `/etc/ssh/sshd_config`: SSH daemon configuration
- `/bin/controller.ssh.entrypoint.sh`: Connection handler
- `/etc/ssh/authorized_keys.d/`: Dynamic key storage

### 2. API Server

The API server provides internal services for pod management and authentication.

#### Endpoints:
- `/_cat/connection-string/:user`: Get pod connection details
- `/users`: List available users
- `/apps`: List available applications
- `/v1/pods`: Kubernetes pod management
- `/flushFirebaseContainers`: Maintenance endpoint

#### Key Files:
- `/bin/server.js`: Main API implementation
- `/lib/utility.js`: Helper functions

### 3. Key Management

Handles SSH key synchronization and access control.

#### Features:
- GitHub collaborator synchronization
- Role-based access control
- Key rotation and updates
- Slack notifications

#### Key Files:
- `/bin/controller.keys.js`: Key management logic
- `/static/templates/*.mustache`: Password file templates

### 4. State Management

Uses Firebase for maintaining container state and configuration.

#### Features:
- Real-time container tracking
- State persistence
- Automatic cleanup
- Event handling

## Security Model

### Authentication Flow

1. User connects with SSH key
2. System validates key against GitHub
3. Checks user's repository permissions
4. Grants appropriate access level

### Authorization Levels

1. **Production Access**
- Limited to admin roles
- Stricter security controls
- Additional validation

2. **Development Access**
- Available to write, maintain, admin roles
- Standard security controls

## Container Integration

### Pod Connection Process

1. SSH connection received
2. User authenticated via GitHub
3. Pod identified from connection string
4. kubectl exec establishes connection
5. Session handed over to user

### SFTP Handling

1. SFTP subsystem activated
2. Path resolution in container
3. File operations proxied to pod
4. Access controls enforced

## Monitoring and Maintenance

### Health Checks
- API server status
- Pod connectivity
- Firebase state
- SSH daemon health

### Notifications
- Key updates
- Access attempts
- System events
- Error conditions

## Deployment

### Container Setup
- Alpine Linux base
- Node.js runtime
- PM2 process manager
- OpenSSH server

### Kubernetes Integration
- Service account configuration
- RBAC policies
- Network policies
- Security contexts
57 changes: 57 additions & 0 deletions docs/client-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SSH Client Configuration

Add this to your `~/.ssh/config`:

```ssh-config
# Production environment
Host www-myapp-com
HostName ssh.rabbit.ci
User www-myapp-com-production-pod-a1b2c3
IdentityFile ~/.ssh/github_rsa
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
RequestTTY force
ConnectTimeout 10
ServerAliveInterval 15
ServerAliveCountMax 3
# Development environment
Host www-myapp-com-dev
HostName ssh.rabbit.ci
User www-myapp-com-development-pod-x1y2z3
IdentityFile ~/.ssh/github_rsa
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
RequestTTY force
ConnectTimeout 10
ServerAliveInterval 15
ServerAliveCountMax 3
```

## Usage

```bash
# Connect to production
ssh www-myapp-com

# Connect to development
ssh www-myapp-com-dev

# Use SFTP
sftp www-myapp-com-dev

# Copy files
scp local.txt www-myapp-com-dev:/remote/path/
```

## Configuration Explained

- `HostName`: The SSH server address
- `User`: Your pod-specific username
- `IdentityFile`: Your GitHub SSH key
- `StrictHostKeyChecking no`: Skip host verification
- `UserKnownHostsFile /dev/null`: Don't store host keys
- `RequestTTY force`: Ensure proper terminal allocation
- `ConnectTimeout 10`: Connection timeout in seconds
- `ServerAliveInterval 15`: Keep connection alive
- `ServerAliveCountMax 3`: Maximum keepalive retries
60 changes: 60 additions & 0 deletions docs/environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Environment Variables

## Kubernetes Configuration

| Variable | Description | Required |
|----------|-------------|----------|
| `KUBERNETES_CLUSTER_ENDPOINT` | Kubernetes API endpoint | Yes |
| `KUBERNETES_CLUSTER_NAME` | Name of the cluster | Yes |
| `KUBERNETES_CLUSTER_NAMESPACE` | Namespace for deployments | Yes |
| `KUBERNETES_CLUSTER_USER_TOKEN` | Authentication token | Yes |
| `KUBERNETES_CLUSTER_SERVICEACCOUNT` | Service account name | Yes |
| `KUBERNETES_CLUSTER_CERTIFICATE` | Cluster certificate | Yes |
| `KUBERNETES_CLUSTER_USER_SECRET` | User secret for auth | Yes |
| `KUBERNETES_CLUSTER_CONTEXT` | Kubernetes context | Yes |

## GitHub Configuration

| Variable | Description | Required |
|----------|-------------|----------|
| `ACCESS_TOKEN` | GitHub access token | Yes |
| `ALLOW_SSH_ACCESS_ROLES` | Allowed GitHub roles (e.g., "admin,maintain,write") | Yes |

## Server Configuration

| Variable | Description | Required |
|----------|-------------|----------|
| `NODE_PORT` | API server port (default: 8080) | No |

## Health Checks

The service includes built-in health monitoring:

```yaml
livenessProbe:
tcpSocket:
port: ssh
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 2

readinessProbe:
tcpSocket:
port: ssh
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 2
```
## Deployment Labels
Each deployment includes metadata labels:
```yaml
git.name: docker-sftp
git.owner: [organization]
git.branch: [branch-name]
```
These are used for service discovery and routing.
81 changes: 81 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Troubleshooting Guide

## Common Issues

### SSH Connection Issues

#### 1. "Permission denied (publickey)"
- Verify GitHub SSH key is properly added
- Check user has correct repository permissions
- Ensure key is synced to authorized_keys

#### 2. "Unknown operand" for SFTP
```
sh: internal-sftp: unknown operand
sh: /usr/lib/ssh/sftp-server: unknown operand
```
**Solution**:
- Container needs openssh-sftp-server package
- Check container OS type in logs
- Contact administrator to add SFTP support

#### 3. Pod Not Found
```
Error from server (NotFound): pods "www-myapp-com" not found
```
**Solution**:
- Verify pod exists in correct namespace
- Check connection string format
- Ensure proper Kubernetes permissions

## Logging

### Log Locations
- SSH/SFTP sessions: `/var/log/sshd.log`
```bash
# View connection attempts
tail -f /var/log/sshd.log

# Example log entry:
# [2025-01-07 19:42:27] SFTP connection attempt from [192.168.1.100] for user [www-myapp-com] to pod [production/www-myapp-com-a1b2c3]
```
- Process logs:
```bash
# View all logs
pm2 logs

# View specific service
pm2 logs sshd
```

### What's in the Logs
- Connection attempts (successful/failed)
- SFTP server availability
- Container OS detection
- Error messages and reasons
- Client IP addresses
- User and pod information

## Security Verification

### 1. Check Permissions
```bash
# Verify key permissions
ls -la /etc/ssh/ssh_host_*

# Check authorized_keys
ls -la /etc/ssh/authorized_keys.d/
```

### 2. Verify Configuration
```bash
# Check SSHD config
sshd -T | grep -E 'passwordauthentication|permitrootlogin|subsystem'
```

## Required Information for Support
1. Pod name and namespace
2. SSH client version
3. Contents of /var/log/sshd.log
4. Client IP address
5. GitHub username
Loading

0 comments on commit 403ba33

Please sign in to comment.