mirror of https://github.com/go-gost/gost.git
test(e2e): add TLS listener rejectUnknownSNI test suite
Covers rejectUnknownSNI with and without a serverNames allow list, verifying allowed/unknown/empty SNI handshakes via openssl s_client.master
parent
a8bd4a5f16
commit
fa815bf4a0
|
|
@ -1,4 +1,4 @@
|
|||
FROM alpine:3.22
|
||||
|
||||
# add tools needed by e2e containers and health checks
|
||||
RUN apk add --no-cache iptables curl netcat-openbsd python3
|
||||
RUN apk add --no-cache iptables curl netcat-openbsd python3 openssl
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
services:
|
||||
- name: tls-reject
|
||||
addr: :8443
|
||||
handler:
|
||||
type: http
|
||||
listener:
|
||||
type: tls
|
||||
tls:
|
||||
rejectUnknownSNI: true
|
||||
serverNames:
|
||||
- example.com
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
services:
|
||||
- name: tls-reject
|
||||
addr: :8443
|
||||
handler:
|
||||
type: http
|
||||
listener:
|
||||
type: tls
|
||||
tls:
|
||||
rejectUnknownSNI: true
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
)
|
||||
|
||||
// TLSSuite covers TLS listener behavior, in particular the rejectUnknownSNI
|
||||
// option which drops TLS handshakes with an unknown or empty SNI before any
|
||||
// certificate is sent.
|
||||
type TLSSuite struct {
|
||||
suite.Suite
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (s *TLSSuite) SetupSuite() {
|
||||
s.ctx = context.Background()
|
||||
}
|
||||
|
||||
// startGost starts a gost container with the given TLS listener config. The
|
||||
// listener always binds :8443; checks run openssl inside the container.
|
||||
func (s *TLSSuite) startGost(yamlPath string) testcontainers.Container {
|
||||
s.T().Helper()
|
||||
rendered, err := RenderConfig(yamlPath, ConfigData{})
|
||||
s.Require().NoError(err)
|
||||
s.T().Cleanup(func() { os.Remove(rendered) })
|
||||
|
||||
c, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, rendered, "8443/tcp")
|
||||
s.Require().NoError(err)
|
||||
return c
|
||||
}
|
||||
|
||||
// sniHandshake runs openssl s_client against the TLS listener and returns its
|
||||
// exit code: 0 means the handshake completed (a certificate was exchanged),
|
||||
// non-zero means the handshake was rejected. An empty sni sends no SNI
|
||||
// extension.
|
||||
func (s *TLSSuite) sniHandshake(c testcontainers.Container, sni string) int {
|
||||
s.T().Helper()
|
||||
args := []string{"openssl", "s_client", "-brief", "-connect", "127.0.0.1:8443"}
|
||||
if sni == "" {
|
||||
args = append(args, "-noservername")
|
||||
} else {
|
||||
args = append(args, "-servername", sni)
|
||||
}
|
||||
code, out, err := c.Exec(s.ctx, args)
|
||||
if err != nil {
|
||||
s.T().Logf("openssl exec error: %v", err)
|
||||
return -1
|
||||
}
|
||||
if b, e := io.ReadAll(out); e == nil {
|
||||
s.T().Logf("openssl output (sni=%q):\n%s", sni, string(b))
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// TestRejectWithAllowList covers rejectUnknownSNI with a populated serverNames
|
||||
// list: the allowed SNI completes the handshake, while a wrong or missing SNI
|
||||
// is rejected.
|
||||
func (s *TLSSuite) TestRejectWithAllowList() {
|
||||
c := s.startGost("testdata/tls/reject_sni.yaml")
|
||||
defer c.Terminate(s.ctx)
|
||||
|
||||
s.Require().Zero(s.sniHandshake(c, "example.com"),
|
||||
"handshake with allowed SNI should succeed")
|
||||
|
||||
s.Require().NotZero(s.sniHandshake(c, "wrong.com"),
|
||||
"handshake with disallowed SNI should fail")
|
||||
|
||||
s.Require().NotZero(s.sniHandshake(c, ""),
|
||||
"handshake without SNI should fail")
|
||||
}
|
||||
|
||||
// TestRejectEmptyOnly covers rejectUnknownSNI with an empty serverNames list:
|
||||
// only a missing SNI is rejected, any named SNI is allowed.
|
||||
func (s *TLSSuite) TestRejectEmptyOnly() {
|
||||
c := s.startGost("testdata/tls/reject_sni_empty.yaml")
|
||||
defer c.Terminate(s.ctx)
|
||||
|
||||
s.Require().NotZero(s.sniHandshake(c, ""),
|
||||
"handshake without SNI should fail")
|
||||
|
||||
s.Require().Zero(s.sniHandshake(c, "anything.test"),
|
||||
"named SNI should be allowed when allow list is empty")
|
||||
}
|
||||
|
||||
func TestTLSSuite(t *testing.T) {
|
||||
suite.Run(t, new(TLSSuite))
|
||||
}
|
||||
Loading…
Reference in New Issue