Skip to content

Commit

Permalink
Implement auto mode for automated TLS testing with HTTP and RabbitMQ
Browse files Browse the repository at this point in the history
- Added a new command `auto` to the CLI for automatically testing both HTTP and RabbitMQ TLS connections.
- Updated README.md to include details about the new auto mode and its usage.
- Enhanced server functionality to support automatic testing, including starting and stopping HTTP and RabbitMQ servers.
- Improved error handling and cleanup processes for server shutdowns.

This commit significantly enhances the usability of the TLS Checker application by providing an automated testing mode, streamlining the process for users.
  • Loading branch information
hskiba committed Jan 15, 2025
1 parent 4c59aa2 commit ff8730c
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 52 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
*.key
node_modules
tls-checker
.idea/
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ A command-line tool for testing TLS connections with both HTTP and RabbitMQ serv
- HTTP client for testing TLS connections
- AMQP client for testing RabbitMQ TLS connections
- Configurable via command line flags or config file
- Auto mode for automated testing of both server types

## Installation

Expand All @@ -25,6 +26,21 @@ go install https://github.com/frgrisk/tls-checker@latest

## Usage

### Auto Mode (Recommended)

Automatically test both HTTP and RabbitMQ TLS connections:

```bash
tls-checker auto --cert cert.pem --key key.pem --ca rootCA.pem --host localhost
```

This will:
1. Start an HTTPS server on a random port and test the connection
2. Start a RabbitMQ server with TLS on random ports and test the connection
3. Clean up all servers after testing

### Manual Server Modes

### Running HTTP Server

```bash
Expand Down
136 changes: 136 additions & 0 deletions cmd/auto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package cmd

import (
"crypto/tls"
"crypto/x509"
"fmt"
"log"
"net"
"net/http"
"os"
"time"

amqp "github.com/rabbitmq/amqp091-go"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var autoCmd = &cobra.Command{
Use: "auto",
Short: "Automatically test both HTTP and RabbitMQ TLS connections",
Run: runAuto,
}

func init() {
rootCmd.AddCommand(autoCmd)
autoCmd.Flags().String("host", "localhost", "Host to use for connections")
}

// getRandomPort returns a random available port
func getRandomPort() (int, error) {
listener, err := net.Listen("tcp", ":0")
if err != nil {
return 0, err
}
defer listener.Close()
return listener.Addr().(*net.TCPAddr).Port, nil
}

func runAuto(cmd *cobra.Command, args []string) {
certFile := viper.GetString("cert")
keyFile := viper.GetString("key")
rootCAFile := viper.GetString("ca")
host, _ := cmd.Flags().GetString("host")

// Load root CA for client connections
rootCA, err := os.ReadFile(rootCAFile)
if err != nil {
log.Fatalf("Failed to read root CA certificate: %v", err)
}

rootCAPool := x509.NewCertPool()
if ok := rootCAPool.AppendCertsFromPEM(rootCA); !ok {
log.Fatalf("Failed to append root CA certificate to pool")
}

// Test HTTP Server
httpPort, err := getRandomPort()
if err != nil {
log.Fatalf("Failed to get random port for HTTP: %v", err)
}

httpAddr := fmt.Sprintf("%s:%d", host, httpPort)
log.Printf("Starting HTTP server on %s", httpAddr)

server, err := startHTTPServer(certFile, keyFile, httpAddr)
if err != nil {
log.Fatalf("Failed to start HTTP server: %v", err)
}

// Give the server a moment to start
time.Sleep(time.Second)

// Test HTTP connection
clientTLSConfig := &tls.Config{
RootCAs: rootCAPool,
ServerName: host,
}

client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: clientTLSConfig,
},
}

url := fmt.Sprintf("https://%s", httpAddr)
resp, err := client.Get(url)
if err != nil {
log.Printf("❌ HTTP connection test failed: %v", err)
} else {
log.Printf("✅ HTTP connection test successful")
resp.Body.Close()
}

// Clean up HTTP server
server.Close()

// Test RabbitMQ
amqpPort, err := getRandomPort()
if err != nil {
log.Fatalf("Failed to get random port for AMQP: %v", err)
}

mgmtPortTLS, err := getRandomPort()
if err != nil {
log.Fatalf("Failed to get random port for RabbitMQ management TLS: %v", err)
}

log.Printf("Starting RabbitMQ server (AMQPS: %d, Management: HTTPS=%d)", amqpPort, mgmtPortTLS)

amqpAddr := fmt.Sprintf("%s:%d", host, amqpPort)
containerID, err := startRabbitMQServer(certFile, keyFile, amqpPort, mgmtPortTLS)
if err != nil {
log.Fatalf("Failed to start RabbitMQ: %v", err)
}

// Give RabbitMQ time to start
log.Printf("Waiting for RabbitMQ to start...")
time.Sleep(10 * time.Second)

// Test RabbitMQ connection
amqpTLSConfig := &tls.Config{
RootCAs: rootCAPool,
ServerName: host,
}

conn, err := amqp.DialTLS(fmt.Sprintf("amqps://guest:guest@%s", amqpAddr), amqpTLSConfig)
if err != nil {
log.Printf("❌ RabbitMQ connection test failed: %v", err)
} else {
log.Printf("✅ RabbitMQ connection test successful")
conn.Close()
}

// Clean up RabbitMQ container
cleanupRabbitMQ(containerID)
}
121 changes: 69 additions & 52 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,39 @@ func runServer(cmd *cobra.Command, args []string) {

useRabbitMQ, _ := cmd.Flags().GetBool("rabbitmq")
if useRabbitMQ {
runRabbitMQ(certFile, keyFile, addr)
containerID, err := startRabbitMQServer(certFile, keyFile, 5671, 15671)
if err != nil {
log.Fatalf("Failed to start RabbitMQ: %v", err)
}

// Wait for interrupt
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan

cleanupRabbitMQ(containerID)
} else {
runHTTPServer(certFile, keyFile, addr)
server, err := startHTTPServer(certFile, keyFile, addr)
if err != nil {
log.Fatalf("Failed to start HTTP server: %v", err)
}

// Wait for interrupt
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan

if err := server.Close(); err != nil {
log.Printf("Error during shutdown: %v", err)
}
}
}

func runHTTPServer(certFile, keyFile, addr string) {
// Load server certificate and key
// startHTTPServer starts an HTTPS server and returns the server instance
func startHTTPServer(certFile, keyFile, addr string) (*http.Server, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
log.Fatalf("failed to load server certificate and key: %v", err)
return nil, fmt.Errorf("failed to load server certificate and key: %v", err)
}

tlsConfig := &tls.Config{
Expand All @@ -61,65 +83,56 @@ func runHTTPServer(certFile, keyFile, addr string) {
}),
}

// Handle shutdown gracefully
go func() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
log.Println("Shutting down HTTP server...")
if err := server.Close(); err != nil {
log.Printf("Error during shutdown: %v", err)
if err := server.ListenAndServeTLS("", ""); err != http.ErrServerClosed {
log.Printf("HTTP server error: %v", err)
}
}()

log.Printf("Starting TLS server on %s...", addr)
if err := server.ListenAndServeTLS("", ""); err != http.ErrServerClosed {
log.Fatalf("server failed: %v", err)
}
return server, nil
}

func runRabbitMQ(certFile, keyFile, addr string) {
// startRabbitMQServer starts a RabbitMQ server in a Docker container and returns the container ID
func startRabbitMQServer(certFile, keyFile string, amqpPort, mgmtPortTLS int) (string, error) {
ctx := context.Background()

// Create Docker client
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
log.Fatalf("failed to create Docker client: %v", err)
return "", fmt.Errorf("failed to create Docker client: %v", err)
}
defer cli.Close()

// Get absolute paths for mounting certificates
certPath, err := filepath.Abs(certFile)
if err != nil {
log.Fatalf("failed to get absolute path for cert: %v", err)
return "", fmt.Errorf("failed to get absolute path for cert: %v", err)
}
keyPath, err := filepath.Abs(keyFile)
if err != nil {
log.Fatalf("failed to get absolute path for key: %v", err)
return "", fmt.Errorf("failed to get absolute path for key: %v", err)
}

// Create temporary rabbitmq.conf file
// Create a temporary rabbitmq.conf file
configContent := fmt.Sprintf(`
listeners.ssl.default = 5671
listeners.ssl.default = %d
ssl_options.certfile = /etc/rabbitmq/certs/cert.pem
ssl_options.keyfile = /etc/rabbitmq/certs/key.pem
ssl_options.verify = verify_none
ssl_options.fail_if_no_peer_cert = false
management.ssl.port = 15671
management.ssl.port = %d
management.ssl.certfile = /etc/rabbitmq/certs/cert.pem
management.ssl.keyfile = /etc/rabbitmq/certs/key.pem
`)
`, amqpPort, mgmtPortTLS)

tmpConfigFile, err := os.CreateTemp("", "rabbitmq.*.conf")
if err != nil {
log.Fatalf("failed to create temp config: %v", err)
return "", fmt.Errorf("failed to create temp config: %v", err)
}
defer os.Remove(tmpConfigFile.Name())

if _, err := tmpConfigFile.WriteString(configContent); err != nil {
log.Fatalf("failed to write config: %v", err)
return "", fmt.Errorf("failed to write config: %v", err)
}
tmpConfigFile.Close()

Expand All @@ -128,16 +141,18 @@ management.ssl.keyfile = /etc/rabbitmq/certs/key.pem
&container.Config{
Image: "rabbitmq:3-management",
ExposedPorts: nat.PortSet{
"5671/tcp": {}, // AMQPS
"15671/tcp": {}, // Management HTTPS
"15672/tcp": {}, // Management HTTP
nat.Port(fmt.Sprintf("%d/tcp", amqpPort)): {},
nat.Port(fmt.Sprintf("%d/tcp", mgmtPortTLS)): {},
},
},
&container.HostConfig{
PortBindings: nat.PortMap{
"5671/tcp": []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: "5671"}},
"15671/tcp": []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: "15671"}},
"15672/tcp": []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: "15672"}},
nat.Port(fmt.Sprintf("%d/tcp", amqpPort)): []nat.PortBinding{
{HostIP: "0.0.0.0", HostPort: fmt.Sprintf("%d", amqpPort)},
},
nat.Port(fmt.Sprintf("%d/tcp", mgmtPortTLS)): []nat.PortBinding{
{HostIP: "0.0.0.0", HostPort: fmt.Sprintf("%d", mgmtPortTLS)},
},
},
Binds: []string{
fmt.Sprintf("%s:/etc/rabbitmq/certs/cert.pem:ro", certPath),
Expand All @@ -150,34 +165,36 @@ management.ssl.keyfile = /etc/rabbitmq/certs/key.pem
"rabbitmq-tls",
)
if err != nil {
log.Fatalf("failed to create container: %v", err)
return "", fmt.Errorf("failed to create container: %v", err)
}

// Start container
if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
log.Fatalf("failed to start container: %v", err)
return "", fmt.Errorf("failed to start container: %v", err)
}

log.Printf("Started RabbitMQ container with ID: %s", resp.ID[:12])
log.Printf("Management UI available at:\n http://localhost:15672\n https://localhost:15671")
log.Printf("AMQPS available at: amqps://localhost:5671")

// Handle shutdown gracefully
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
log.Printf("Management UI available at: https://localhost:%d", mgmtPortTLS)
log.Printf("AMQPS available at: amqps://localhost:%d", amqpPort)

// Wait for shutdown signal
<-sigChan
log.Println("Shutting down...")
return resp.ID, nil
}

// Stop and remove container
timeout := 10 // seconds
if err := cli.ContainerStop(ctx, resp.ID, container.StopOptions{Timeout: &timeout}); err != nil {
log.Printf("failed to stop container: %v", err)
func cleanupRabbitMQ(containerID string) {
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
log.Printf("Failed to create Docker client for cleanup: %v", err)
return
}
if err := cli.ContainerRemove(ctx, resp.ID, container.RemoveOptions{}); err != nil {
log.Printf("failed to remove container: %v", err)
defer cli.Close()

timeout := 10
if err := cli.ContainerStop(ctx, containerID, container.StopOptions{Timeout: &timeout}); err != nil {
log.Printf("Failed to stop container: %v", err)
}

log.Println("Cleanup complete")
if err := cli.ContainerRemove(ctx, containerID, container.RemoveOptions{}); err != nil {
log.Printf("Failed to remove container: %v", err)
}
}

0 comments on commit ff8730c

Please sign in to comment.