1 package sources_test
2
3 import (
4 "crypto/ecdsa"
5 "crypto/elliptic"
6 "crypto/rand"
7 "crypto/x509"
8 "crypto/x509/pkix"
9 "encoding/pem"
10 "math/big"
11 "os"
12 "path/filepath"
13 "testing"
14 "time"
15
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
18
19 "github.com/cybertec-postgresql/pgwatch/v6/internal/sources"
20 )
21
22
23
24 func writeTestCert(t *testing.T, dir string) (certFile, keyFile string) {
25 t.Helper()
26 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
27 require.NoError(t, err)
28 tmpl := &x509.Certificate{
29 SerialNumber: big.NewInt(1),
30 Subject: pkix.Name{CommonName: "pgwatch-test"},
31 NotBefore: time.Now().Add(-time.Hour),
32 NotAfter: time.Now().Add(time.Hour),
33 IsCA: true,
34 BasicConstraintsValid: true,
35 }
36 der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
37 require.NoError(t, err)
38 keyDer, err := x509.MarshalECPrivateKey(key)
39 require.NoError(t, err)
40
41 certFile = filepath.Join(dir, "cert.pem")
42 keyFile = filepath.Join(dir, "key.pem")
43 require.NoError(t, os.WriteFile(certFile, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0o600))
44 require.NoError(t, os.WriteFile(keyFile, pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDer}), 0o600))
45 return certFile, keyFile
46 }
47
48 func TestGetTransport(t *testing.T) {
49 dir := t.TempDir()
50 certFile, keyFile := writeTestCert(t, dir)
51 garbage := filepath.Join(dir, "garbage.pem")
52 require.NoError(t, os.WriteFile(garbage, []byte("not a certificate"), 0o600))
53
54 t.Run("no TLS params", func(t *testing.T) {
55 cfg, err := sources.GetTransport(sources.HostConfig{})
56 require.NoError(t, err)
57 assert.Nil(t, cfg)
58 })
59
60 t.Run("CA only", func(t *testing.T) {
61 cfg, err := sources.GetTransport(sources.HostConfig{CAFile: certFile})
62 require.NoError(t, err)
63 require.NotNil(t, cfg)
64 assert.NotNil(t, cfg.RootCAs)
65 assert.Empty(t, cfg.Certificates)
66 })
67
68 t.Run("client cert without CA", func(t *testing.T) {
69 cfg, err := sources.GetTransport(sources.HostConfig{CertFile: certFile, KeyFile: keyFile})
70 require.NoError(t, err)
71 require.NotNil(t, cfg)
72 assert.Nil(t, cfg.RootCAs, "system CA pool must be used")
73 assert.Len(t, cfg.Certificates, 1)
74 })
75
76 t.Run("CA and client cert", func(t *testing.T) {
77 cfg, err := sources.GetTransport(sources.HostConfig{CAFile: certFile, CertFile: certFile, KeyFile: keyFile})
78 require.NoError(t, err)
79 require.NotNil(t, cfg)
80 assert.NotNil(t, cfg.RootCAs)
81 assert.Len(t, cfg.Certificates, 1)
82 })
83
84 t.Run("missing CA file", func(t *testing.T) {
85 _, err := sources.GetTransport(sources.HostConfig{CAFile: filepath.Join(dir, "missing.pem")})
86 assert.Error(t, err)
87 })
88
89 t.Run("CA file without certificates", func(t *testing.T) {
90 _, err := sources.GetTransport(sources.HostConfig{CAFile: garbage})
91 assert.ErrorContains(t, err, "no PEM certificates")
92 })
93
94 t.Run("bad client key", func(t *testing.T) {
95 _, err := sources.GetTransport(sources.HostConfig{CertFile: certFile, KeyFile: garbage})
96 assert.Error(t, err)
97 })
98 }
99