1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package certs
16
17 import (
18 "context"
19 "encoding/json"
20 "fmt"
21 "io/ioutil"
22 "net/http"
23 "net/http/httptest"
24 "testing"
25 "time"
26
27 "google.golang.org/api/option"
28 sqladmin "google.golang.org/api/sqladmin/v1beta4"
29 )
30
31 const fakeCert = `-----BEGIN CERTIFICATE-----
32 MIICgTCCAWmgAwIBAgIBADANBgkqhkiG9w0BAQsFADAAMCIYDzAwMDEwMTAxMDAw
33 MDAwWhgPMDAwMTAxMDEwMDAwMDBaMAAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
34 ggEKAoIBAQCvN0H6/ecloIfNyRu8KKtVSIK0JaW1lB1C1/ZI9iZmihqiUrxeyKTb
35 9hWuMPJ3u9NfSn1Vlwuj0bw7/T8e3Ol5BImcGxYxWMefkqFtqnjCafo2wnIea/eQ
36 JFLt4wXYkeveHReUseGtaBzpCo4wYOiqgxyIrGiQ/rq4Xjr2hXuqTg4TTgxv+0Iv
37 nrJwn61pitGvLPjsl9quzSQ6CdM3tWfb6cwozF5uJatbxRCZDsp1qUBXX9/zYqmx
38 8regdRG95btNgXLCfNS0iX0jopl00vGwYRGGKjfPZ5AkpuxX9M4Ys3X7pOspaQMC
39 Zf4VjXdwOljqZxIOGhOBbrXQacSywTLjAgMBAAGjAjAAMA0GCSqGSIb3DQEBCwUA
40 A4IBAQAXj/0iiU2AQGztlFstLVwQ9yz+7/pfqAr26DYu9hpI/QvrZsJWjwNUNlX+
41 7gwhrwiJs7xsLZqnEr2qvj6at/MtxIEVgQd43sOsWW9de8R5WNQNzsCb+5npWcx7
42 vtcKXD9jFFLDDCIYjAf9+6m/QrMJtIf++zBmjguShccjZzY+GQih78oWqNTYqRQs
43 //wOP15vFQ/gB4DcJ0UyO9icVgbJha66yzG7XABDEepha5uhpLhwFaONU8jMxW7A
44 fOx52xqIUu3m4M3Ci0ZIp22TeGVuJ/Dy1CPbDOshcb0dXTE+mU5T91SHKRF4jz77
45 +9TQIXHGk7lJyVVhbed8xm/p727f
46 -----END CERTIFICATE-----`
47
48 func TestLocalCertSupportsStaleReads(t *testing.T) {
49 var (
50 gotReadTimes []string
51 ok bool
52 )
53 handleEphemeralCert := func(w http.ResponseWriter, r *http.Request) {
54 var actual sqladmin.GenerateEphemeralCertRequest
55 data, err := ioutil.ReadAll(r.Body)
56 if err != nil {
57 t.Fatalf("failed to read request body: %v", err)
58 }
59 defer r.Body.Close()
60 if err = json.Unmarshal(data, &actual); err != nil {
61 t.Fatalf("failed to unmarshal request body: %v", err)
62 }
63 gotReadTimes = append(gotReadTimes, actual.ReadTime)
64 if !ok {
65 w.WriteHeader(http.StatusServiceUnavailable)
66 fmt.Fprintln(w, `{"message":"the first request fails"}`)
67 ok = true
68 return
69 }
70
71 fmt.Fprintln(w, fmt.Sprintf(`{"ephemeralCert":{"cert": %q}}`, fakeCert))
72 }
73 ts := httptest.NewServer(http.HandlerFunc(handleEphemeralCert))
74 defer ts.Close()
75
76 cs := NewCertSourceOpts(ts.Client(), RemoteOpts{})
77
78 var err error
79 cs.serv, err = sqladmin.NewService(context.Background(),
80 option.WithEndpoint(ts.URL), option.WithHTTPClient(ts.Client()))
81 if err != nil {
82 t.Fatalf("failed to replace SQL Admin client: %v", err)
83 }
84
85
86 _, err = cs.Local("my-proj:reg:my-inst")
87 if err != nil {
88 t.Fatal(err)
89 }
90
91
92
93 if len(gotReadTimes) != 2 {
94 t.Fatalf("expected two results, got = %v", len(gotReadTimes))
95 }
96 if gotReadTimes[0] != "" {
97 t.Fatalf("expected empty ReadTime for first request, got = %v", gotReadTimes[0])
98 }
99 wantStaleness := 30 * time.Second
100 if !staleTimestamp(gotReadTimes[1], wantStaleness) {
101 t.Fatalf("expected timestamp at least %v old, got = %v (now = %v)",
102 wantStaleness, gotReadTimes[1], time.Now().UTC().Format(time.RFC3339))
103 }
104 }
105
106 func staleTimestamp(ts string, staleness time.Duration) bool {
107 t, err := time.Parse(time.RFC3339, ts)
108 if err != nil {
109
110 return false
111 }
112 return t.Before(time.Now().Add(-staleness))
113 }
114
115 func TestRemoteCertSupportsStaleReads(t *testing.T) {
116 var (
117 gotReadTimes []string
118 ok bool
119 )
120 handleConnectSettings := func(w http.ResponseWriter, r *http.Request) {
121 rt := r.URL.Query()["readTime"]
122
123
124 if rt != nil {
125 gotReadTimes = append(gotReadTimes, rt[0])
126 } else {
127 gotReadTimes = append(gotReadTimes, "")
128 }
129 if !ok {
130 w.WriteHeader(http.StatusServiceUnavailable)
131 fmt.Fprintln(w, `{"message":"the first request fails"}`)
132 ok = true
133 return
134 }
135 fmt.Fprintln(w, fmt.Sprintf(`{
136 "region":"us-central1",
137 "ipAddresses": [
138 {"type":"PRIMARY", "ipAddress":"127.0.0.1"}
139 ],
140 "serverCaCert": {"cert": %q}
141 }`, fakeCert))
142 }
143 ts := httptest.NewServer(http.HandlerFunc(handleConnectSettings))
144 defer ts.Close()
145
146 cs := NewCertSourceOpts(ts.Client(), RemoteOpts{})
147 var err error
148
149 cs.serv, err = sqladmin.NewService(context.Background(),
150 option.WithEndpoint(ts.URL), option.WithHTTPClient(ts.Client()))
151 if err != nil {
152 t.Fatalf("failed to replace SQL Admin client: %v", err)
153 }
154
155
156 _, _, _, _, err = cs.Remote("my-proj:us-central1:my-inst")
157 if err != nil {
158 t.Fatal(err)
159 }
160
161
162
163 if len(gotReadTimes) != 2 {
164 t.Fatalf("expected two results, got = %v", len(gotReadTimes))
165 }
166 if gotReadTimes[0] != "" {
167 t.Fatalf("expected empty ReadTime for first request, got = %v", gotReadTimes[0])
168 }
169 wantStaleness := 30 * time.Second
170 if !staleTimestamp(gotReadTimes[1], wantStaleness) {
171 t.Fatalf("expected timestamp at least %v old, got = %v (now = %v)",
172 wantStaleness, gotReadTimes[1], time.Now().UTC().Format(time.RFC3339))
173 }
174 }
175
View as plain text