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