1 package akamai
2
3 import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/jmhodges/clock"
14 blog "github.com/letsencrypt/boulder/log"
15 "github.com/letsencrypt/boulder/metrics"
16 "github.com/letsencrypt/boulder/test"
17 )
18
19 func TestMakeAuthHeader(t *testing.T) {
20 log := blog.NewMock()
21 stats := metrics.NoopRegisterer
22 cpc, err := NewCachePurgeClient(
23 "https://akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net",
24 "akab-client-token-xxx-xxxxxxxxxxxxxxxx",
25 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=",
26 "akab-access-token-xxx-xxxxxxxxxxxxxxxx",
27 "production",
28 2,
29 time.Second,
30 log,
31 stats,
32 )
33 test.AssertNotError(t, err, "Failed to create cache purge client")
34 fc := clock.NewFake()
35 cpc.clk = fc
36 wantedTimestamp, err := time.Parse(timestampFormat, "20140321T19:34:21+0000")
37 test.AssertNotError(t, err, "Failed to parse timestamp")
38 fc.Set(wantedTimestamp)
39
40 expectedHeader := "EG1-HMAC-SHA256 client_token=akab-client-token-xxx-xxxxxxxxxxxxxxxx;access_token=akab-access-token-xxx-xxxxxxxxxxxxxxxx;timestamp=20140321T19:34:21+0000;nonce=nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;signature=hXm4iCxtpN22m4cbZb4lVLW5rhX8Ca82vCFqXzSTPe4="
41 authHeader := cpc.makeAuthHeader(
42 []byte("datadatadatadatadatadatadatadata"),
43 "/testapi/v1/t3",
44 "nonce-xx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
45 )
46 test.AssertEquals(t, authHeader, expectedHeader)
47 }
48
49 type akamaiServer struct {
50 responseCode int
51 *httptest.Server
52 }
53
54 func (as *akamaiServer) sendResponse(w http.ResponseWriter, resp purgeResponse) {
55 respBytes, err := json.Marshal(resp)
56 if err != nil {
57 fmt.Printf("Failed to marshal response body: %s\n", err)
58 w.WriteHeader(http.StatusInternalServerError)
59 return
60 }
61 w.WriteHeader(as.responseCode)
62 w.Write(respBytes)
63 }
64
65 func (as *akamaiServer) purgeHandler(w http.ResponseWriter, r *http.Request) {
66 var req struct {
67 Objects []string
68 }
69 body, err := io.ReadAll(r.Body)
70 if err != nil {
71 fmt.Printf("Failed to read request body: %s\n", err)
72 w.WriteHeader(http.StatusInternalServerError)
73 return
74 }
75
76 err = CheckSignature("secret", as.URL, r, body)
77 if err != nil {
78 fmt.Printf("Error checking signature: %s\n", err)
79 w.WriteHeader(http.StatusInternalServerError)
80 return
81 }
82
83 err = json.Unmarshal(body, &req)
84 if err != nil {
85 fmt.Printf("Failed to unmarshal request body: %s\n", err)
86 w.WriteHeader(http.StatusInternalServerError)
87 return
88 }
89
90 resp := purgeResponse{
91 HTTPStatus: as.responseCode,
92 Detail: "?",
93 EstimatedSeconds: 10,
94 PurgeID: "?",
95 }
96
97 fmt.Println(r.URL.Path, v3PurgePath)
98 if strings.HasPrefix(r.URL.Path, v3PurgePath) {
99 for _, testURL := range req.Objects {
100 if !strings.HasPrefix(testURL, "http://") {
101 resp.HTTPStatus = http.StatusForbidden
102 break
103 }
104 }
105 }
106 as.sendResponse(w, resp)
107 }
108 func newAkamaiServer(code int) *akamaiServer {
109 m := http.NewServeMux()
110 as := akamaiServer{
111 responseCode: code,
112 Server: httptest.NewServer(m),
113 }
114 m.HandleFunc(v3PurgePath, as.purgeHandler)
115 m.HandleFunc(v3PurgeTagPath, as.purgeHandler)
116 return &as
117 }
118
119
120 func TestV3Purge(t *testing.T) {
121 as := newAkamaiServer(http.StatusCreated)
122 defer as.Close()
123
124
125 client, err := NewCachePurgeClient(
126 as.URL,
127 "token",
128 "secret",
129 "accessToken",
130 "production",
131 3,
132 time.Second,
133 blog.NewMock(),
134 metrics.NoopRegisterer,
135 )
136 test.AssertNotError(t, err, "Failed to create CachePurgeClient")
137 client.clk = clock.NewFake()
138
139 err = client.Purge([]string{"http://test.com"})
140 test.AssertNotError(t, err, "Purge failed; expected 201 response")
141
142 started := client.clk.Now()
143 as.responseCode = http.StatusInternalServerError
144 err = client.Purge([]string{"http://test.com"})
145 test.AssertError(t, err, "Purge succeeded; expected 500 response")
146 t.Log(client.clk.Since(started))
147
148
149
150 test.Assert(t, client.clk.Since(started) > (time.Second*3), "Retries should've taken at least 3.192 seconds")
151
152 started = client.clk.Now()
153 as.responseCode = http.StatusCreated
154 err = client.Purge([]string{"http:/test.com"})
155 test.AssertError(t, err, "Purge succeeded; expected a 403 response from malformed URL")
156 test.Assert(t, client.clk.Since(started) < time.Second, "Purge should've failed out immediately")
157 }
158
159 func TestPurgeTags(t *testing.T) {
160 as := newAkamaiServer(http.StatusCreated)
161 defer as.Close()
162
163
164 client, err := NewCachePurgeClient(
165 as.URL,
166 "token",
167 "secret",
168 "accessToken",
169 "production",
170 3,
171 time.Second,
172 blog.NewMock(),
173 metrics.NoopRegisterer,
174 )
175 test.AssertNotError(t, err, "Failed to create CachePurgeClient")
176 fc := clock.NewFake()
177 client.clk = fc
178
179 err = client.PurgeTags([]string{"ff"})
180 test.AssertNotError(t, err, "Purge failed; expected response 201")
181
182 as.responseCode = http.StatusForbidden
183 err = client.PurgeTags([]string{"http://test.com"})
184 test.AssertError(t, err, "Purge succeeded; expected Forbidden response")
185 }
186
187 func TestNewCachePurgeClient(t *testing.T) {
188
189 _, err := NewCachePurgeClient(
190 "http://127.0.0.1:9000/",
191 "token",
192 "secret",
193 "accessToken",
194 "fake",
195 3,
196 time.Second,
197 blog.NewMock(),
198 metrics.NoopRegisterer,
199 )
200 test.AssertError(t, err, "NewCachePurgeClient with invalid network parameter didn't error")
201
202
203 _, err = NewCachePurgeClient(
204 "http://127.0.0.1:9000/",
205 "token",
206 "secret",
207 "accessToken",
208 "staging",
209 3,
210 time.Second,
211 blog.NewMock(),
212 metrics.NoopRegisterer,
213 )
214 test.AssertNotError(t, err, "NewCachePurgeClient with valid network parameter errored")
215
216
217 _, err = NewCachePurgeClient(
218 "h&ttp://whatever",
219 "token",
220 "secret",
221 "accessToken",
222 "staging",
223 3,
224 time.Second,
225 blog.NewMock(),
226 metrics.NoopRegisterer,
227 )
228 test.AssertError(t, err, "NewCachePurgeClient with invalid server url parameter didn't error")
229 }
230
231 func TestBigBatchPurge(t *testing.T) {
232 log := blog.NewMock()
233
234 as := newAkamaiServer(http.StatusCreated)
235
236 client, err := NewCachePurgeClient(
237 as.URL,
238 "token",
239 "secret",
240 "accessToken",
241 "production",
242 3,
243 time.Second,
244 log,
245 metrics.NoopRegisterer,
246 )
247 test.AssertNotError(t, err, "Failed to create CachePurgeClient")
248
249 var urls []string
250 for i := 0; i < 250; i++ {
251 urls = append(urls, fmt.Sprintf("http://test.com/%d", i))
252 }
253
254 err = client.Purge(urls)
255 test.AssertNotError(t, err, "Purge failed.")
256 }
257
258 func TestReverseBytes(t *testing.T) {
259 a := []byte{0, 1, 2, 3}
260 test.AssertDeepEquals(t, reverseBytes(a), []byte{3, 2, 1, 0})
261 }
262
263 func TestGenerateOCSPCacheKeys(t *testing.T) {
264 der := []byte{105, 239, 255}
265 test.AssertDeepEquals(
266 t,
267 makeOCSPCacheURLs(der, "ocsp.invalid/"),
268 []string{
269 "ocsp.invalid/?body-md5=d6101198a9d9f1f6",
270 "ocsp.invalid/ae/",
271 "ocsp.invalid/ae%2F%2F",
272 },
273 )
274 }
275
View as plain text