1package app_test
2
3import (
4 "context"
5 "crypto/tls"
6 "crypto/x509"
7 "net/http"
8 "testing"
9 "time"
10
11 pkiadapter "github.com/bnema/gordon/internal/adapters/out/pki"
12 "github.com/bnema/gordon/internal/boundaries/out"
13 pkiusecase "github.com/bnema/gordon/internal/usecase/pki"
14 "github.com/bnema/zerowrap"
15 "github.com/stretchr/testify/assert"
16 "github.com/stretchr/testify/require"
17)
18
19// stubAppRoutes is an ACTIVE-derived host source for PKI tests.
20type stubAppRoutes struct {
21 hosts []out.AppHost
22}
23
24func (s *stubAppRoutes) AppHosts() []out.AppHost { return s.hosts }
25
26func (s *stubAppRoutes) GetExternalRoutes() map[string]string { return nil }
27
28func newRoutesMock(_ *testing.T, domains ...string) *stubAppRoutes {
29 hosts := make([]out.AppHost, 0, len(domains))
30 for _, d := range domains {
31 hosts = append(hosts, out.AppHost{Host: d})
32 }
33 return &stubAppRoutes{hosts: hosts}
34}
35
36func TestTLSHandshake_OnDemandCert(t *testing.T) {
37 dir := t.TempDir()
38 log := zerowrap.Default()
39
40 ca, err := pkiadapter.NewCA(dir, log)
41 require.NoError(t, err)
42
43 routes := newRoutesMock(t, "test.local")
44 ctx, cancel := context.WithCancel(context.Background())
45 defer cancel()
46
47 svc := pkiusecase.NewService(ctx, ca, routes, nil, log)
48 defer svc.Stop()
49
50 // Start a TLS server using the PKI service
51 tlsCfg := &tls.Config{
52 MinVersion: tls.VersionTLS12,
53 GetCertificate: svc.GetCertificate,
54 }
55
56 srv := &http.Server{
57 Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("ok")) }),
58 TLSConfig: tlsCfg,
59 }
60
61 ln, err := tls.Listen("tcp", "127.0.0.1:0", tlsCfg)
62 require.NoError(t, err)
63 defer ln.Close()
64
65 go srv.Serve(ln)
66 defer srv.Close()
67
68 // Build a client that trusts Gordon's root CA
69 rootPool := x509.NewCertPool()
70 rootPool.AppendCertsFromPEM(ca.RootCertificate())
71
72 client := &http.Client{
73 Transport: &http.Transport{
74 TLSClientConfig: &tls.Config{
75 MinVersion: tls.VersionTLS12,
76 RootCAs: rootPool,
77 ServerName: "test.local",
78 },
79 },
80 Timeout: 5 * time.Second,
81 }
82
83 resp, err := client.Get("https://" + ln.Addr().String() + "/")
84 require.NoError(t, err)
85 defer resp.Body.Close()
86 assert.Equal(t, http.StatusOK, resp.StatusCode)
87}
88
89func TestTLSHandshake_UnknownDomain_Rejected(t *testing.T) {
90 dir := t.TempDir()
91 log := zerowrap.Default()
92
93 ca, err := pkiadapter.NewCA(dir, log)
94 require.NoError(t, err)
95
96 routes := newRoutesMock(t, "allowed.local")
97 ctx, cancel := context.WithCancel(context.Background())
98 defer cancel()
99
100 svc := pkiusecase.NewService(ctx, ca, routes, nil, log)
101 defer svc.Stop()
102
103 tlsCfg := &tls.Config{
104 MinVersion: tls.VersionTLS12,
105 GetCertificate: svc.GetCertificate,
106 }
107
108 ln, err := tls.Listen("tcp", "127.0.0.1:0", tlsCfg)
109 require.NoError(t, err)
110 defer ln.Close()
111
112 srv := &http.Server{Handler: http.NotFoundHandler(), TLSConfig: tlsCfg}
113 go srv.Serve(ln)
114 defer srv.Close()
115
116 rootPool := x509.NewCertPool()
117 rootPool.AppendCertsFromPEM(ca.RootCertificate())
118
119 client := &http.Client{
120 Transport: &http.Transport{
121 TLSClientConfig: &tls.Config{
122 MinVersion: tls.VersionTLS12,
123 RootCAs: rootPool,
124 ServerName: "unknown.local", // not in route table
125 },
126 },
127 Timeout: 5 * time.Second,
128 }
129
130 _, err = client.Get("https://" + ln.Addr().String() + "/")
131 assert.Error(t, err, "TLS handshake should fail for unknown domain")
132}