Setup a Static IP for Go (Golang) SFTP

Learn how to securely route Go (Golang) SFTP file transfers through a QuotaGuard Static IP. Includes full code examples.

Prerequisites

This example demonstrates connecting to an SFTP server via a QuotaGuard Static SOCKS5 proxy. It connects to the public Rebex SFTP server and reads the contents of readme.txt.

  • Golang
  • Docker (optional)

Instructions

Run Example (Without Docker)
  1. Place the code in a file named app.go.
  2. Download dependencies:
go mod tidy

 

3. Run the example by setting your proxy URL (format: socks5://username:password@host:port):

QUOTAGUARDSTATIC_URL="socks5://username:password@host:port" go run app.go

Test in Docker
docker build -t qg-static-golang-sftp-example .
docker run -e QUOTAGUARDSTATIC_URL=... qg-static-golang-sftp-example

Code Samples

App.go
package main

import (
	"fmt"
	"io"
	"log"
	"net/url"
	"os"
	"time"

	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
	"golang.org/x/net/proxy"
)

func main() {
	// Get the proxy URL from the environment variable.
	proxyURL := os.Getenv("QUOTAGUARDSTATIC_URL")
	if proxyURL == "" {
		log.Fatal("Environment variable QUOTAGUARDSTATIC_URL not set")
	}

	// Parse the proxy URL.
	parsedURL, err := url.Parse(proxyURL)
	if err != nil {
		log.Fatalf("Error parsing QUOTAGUARDSTATIC_URL: %v", err)
	}

	// Extract user info if provided.
	var auth *proxy.Auth = nil
	if parsedURL.User != nil {
		username := parsedURL.User.Username()
		password, _ := parsedURL.User.Password()
		auth = &proxy.Auth{
			User:     username,
			Password: password,
		}
	}

	// Get host and port for the proxy.
	proxyAddr := parsedURL.Host

	// Create a SOCKS5 dialer using the provided proxy details.
	dialer, err := proxy.SOCKS5("tcp", proxyAddr, auth, proxy.Direct)
	if err != nil {
		log.Fatalf("Failed to create SOCKS5 dialer: %v", err)
	}

	// Use the public Rebex SFTP server for testing.
	sshHost := "test.rebex.net:22"
	sshUser := "demo"
	sshPassword := "password"

	// Configure the SSH client.
	sshConfig := &ssh.ClientConfig{
		User: sshUser,
		Auth: []ssh.AuthMethod{
			ssh.Password(sshPassword),
		},
		// NOTE: For production, replace InsecureIgnoreHostKey() with proper host key validation.
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		Timeout:         10 * time.Second,
	}

	// Connect to the SSH server via the proxy.
	conn, err := dialer.Dial("tcp", sshHost)
	if err != nil {
		log.Fatalf("Failed to dial SSH server through proxy: %v", err)
	}

	// Upgrade the connection to an SSH client connection.
	sshConn, chans, reqs, err := ssh.NewClientConn(conn, sshHost, sshConfig)
	if err != nil {
		log.Fatalf("Failed to establish SSH connection: %v", err)
	}
	sshClient := ssh.NewClient(sshConn, chans, reqs)
	defer sshClient.Close()

	// Create an SFTP client over the SSH connection.
	sftpClient, err := sftp.NewClient(sshClient)
	if err != nil {
		log.Fatalf("Failed to create SFTP client: %v", err)
	}
	defer sftpClient.Close()

	// Open the "readme.txt" file in the SFTP server's root directory.
	remoteFilePath := "readme.txt"
	file, err := sftpClient.Open(remoteFilePath)
	if err != nil {
		log.Fatalf("Failed to open file %s: %v", remoteFilePath, err)
	}
	defer file.Close()

	// Read the contents of the file.
	contents, err := io.ReadAll(file)
	if err != nil {
		log.Fatalf("Failed to read file %s: %v", remoteFilePath, err)
	}

	// Output the file contents.
	fmt.Printf("Contents of %s:\n%s\n", remoteFilePath, contents)
}
Docker File
FROM golang:latest as builder
WORKDIR /app

# Copy source code into the container.
COPY . .

# Initialize module if needed and tidy dependencies.
RUN if [ ! -f go.mod ]; then \
      go mod init example.com/my-sftp-app; \
    fi && go mod tidy

# Build the application.
RUN go build -o app .

FROM alpine:latest
WORKDIR /app

# Copy the compiled binary from the builder stage.
COPY --from=builder /app/app .

# Run the application.
CMD ["./app"]