Skip to content

Commit

Permalink
Merge pull request #33 from Razz4780/michals/mbe-733-add-e2e-tests-fo…
Browse files Browse the repository at this point in the history
…r-https-stealing

Added a Go HTTP/HTTPS server for TLS stealing E2E tests
  • Loading branch information
Razz4780 authored Feb 24, 2025
2 parents f2c6d0c + 8bf2998 commit be78b8c
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 1 deletion.
10 changes: 9 additions & 1 deletion .github/workflows/action-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,12 @@ jobs:
push: true
tags: |
ghcr.io/metalbear-co/mirrord-go-statfs:latest
- name: go-server - build and push
uses: docker/build-push-action@v3
with:
context: go-server
file: go-server/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/metalbear-co/mirrord-go-server:latest
20 changes: 20 additions & 0 deletions go-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM golang:latest AS build

WORKDIR /app

COPY go.mod .
COPY go.sum .

RUN go mod download

COPY main.go .

RUN CGO_ENABLED=0 go build main.go

FROM alpine:latest AS run

COPY --from=build /app /app

WORKDIR /app

CMD ["/app/main"]
11 changes: 11 additions & 0 deletions go-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Simple HTTP/HTTPS server written in Go.

Always responds with 200 OK.

# Configuration:
* `SERVER_MESSAGE` - message to respond with. Optional, defaults to `Hello from remote!`.
* `SERVER_MODE` - either `HTTP` or `HTTPS`. Optional, defaults to `HTTP`.
* `SERVER_PORT` - port to listen on. Optional, defaults to `80` in the `HTTP` mode and `443` in the `HTTPS` mode.
* `TLS_SERVER_CERT` - path to a PEM file containing certificate chain to use for server authentication (required in the `HTTPS` mode).
* `TLS_SERVER_KEY` - path to a PEM file containing private key to use with the certificate chain from `TLS_SERVER_CERT` (required in the `HTTPS` mode).
* `TLS_CLIENT_ROOTS` - path to a PEM file containing certificates to use as trusted roots for client authentication. Optional, if mode is `HTTPS` and this variable is not provided, the server will not offer client authentication.
5 changes: 5 additions & 0 deletions go-server/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module go-server

go 1.23.4

require github.com/sethvargo/go-envconfig v1.1.1
2 changes: 2 additions & 0 deletions go-server/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/sethvargo/go-envconfig v1.1.1 h1:JDu8Q9baIzJf47NPkzhIB6aLYL0vQ+pPypoYrejS9QY=
github.com/sethvargo/go-envconfig v1.1.1/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
139 changes: 139 additions & 0 deletions go-server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package main

import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/sethvargo/go-envconfig"
"log"
"net/http"
"os"
)

type ServerConfig struct {
Message string `env:"SERVER_MESSAGE"`
Mode string `env:"SERVER_MODE"`
Port string `env:"SERVER_PORT"`
ServerCert string `env:"TLS_SERVER_CERT"`
ServerKey string `env:"TLS_SERVER_KEY"`
ClientRoots string `env:"TLS_CLIENT_ROOTS"`
}

func makeTlsConfig(serverConfig *ServerConfig) *tls.Config {
if serverConfig.Mode == "HTTP" {
return nil
}

cfg := &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
},
NextProtos: []string{"h2", "http/1.1", "http/1.0"},
}

if serverConfig.ClientRoots != "" {
cfg.ClientAuth = tls.RequireAndVerifyClientCert
cfg.ClientCAs = loadClientCerts(serverConfig.ClientRoots)
} else {
cfg.ClientAuth = tls.NoClientCert
}

return cfg
}

func loadClientCerts(path string) *x509.CertPool {
certPool := x509.NewCertPool()

rawPem, err := os.ReadFile(path)
if err != nil {
log.Fatalf("Error reading client allowed roots (%s): %v\n", path, err)
}

var added uint = 0
for {
var certDERBlock *pem.Block
certDERBlock, rawPem = pem.Decode(rawPem)
if certDERBlock == nil {
break
}
cert, err := x509.ParseCertificate(certDERBlock.Bytes)
if err != nil {
log.Fatalf("Error parsing X509 certificate (%s): %v\n", path, err)
}

certPool.AddCert(cert)
added += 1
}

log.Printf("Loaded client cert pool (%s): %v certificates\n", path, added)

return certPool
}

func main() {
ctx := context.Background()

var c ServerConfig
if err := envconfig.Process(ctx, &c); err != nil {
log.Fatalf("Failed to read configuration: %v\n", err)
}

if c.Mode == "" {
c.Mode = "HTTP"
} else if c.Mode != "HTTP" && c.Mode != "HTTPS" {
log.Fatalf("Invalid mode: %s, expected either HTTP or HTTPS\n", c.Mode)
}

if c.Message == "" {
c.Message = "Hello from remote!"
}

if c.Port == "" {
if c.Mode == "HTTP" {
c.Port = "80"
} else {
c.Port = "443"
}
}

log.Printf("Resolved server configuration: %+v\n", c)

mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
log.Printf("Got a %s %s request from %s\n", req.Proto, req.Method, req.RemoteAddr)

if c.Mode == "HTTPS" {
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
}

w.Header().Add("Content-Type", "text/plain")

_, _ = w.Write([]byte(c.Message))
})

srv := &http.Server{
Addr: fmt.Sprintf(":%s", c.Port),
Handler: mux,
TLSConfig: makeTlsConfig(&c),
}

var err error
if c.Mode == "HTTP" {
err = srv.ListenAndServe()
} else {
err = srv.ListenAndServeTLS(c.ServerCert, c.ServerKey)
}

if err != nil {
log.Fatalf("Server failed: %v\n", err)
}
}

0 comments on commit be78b8c

Please sign in to comment.