1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package disk 18 19 import ( 20 "bytes" 21 "crypto/sha256" 22 "fmt" 23 "net/http" 24 "os" 25 "path/filepath" 26 27 "github.com/gregjones/httpcache" 28 "github.com/peterbourgon/diskv" 29 "k8s.io/klog/v2" 30 ) 31 32 type cacheRoundTripper struct { 33 rt *httpcache.Transport 34 } 35 36 // newCacheRoundTripper creates a roundtripper that reads the ETag on 37 // response headers and send the If-None-Match header on subsequent 38 // corresponding requests. 39 func newCacheRoundTripper(cacheDir string, rt http.RoundTripper) http.RoundTripper { 40 d := diskv.New(diskv.Options{ 41 PathPerm: os.FileMode(0750), 42 FilePerm: os.FileMode(0660), 43 BasePath: cacheDir, 44 TempDir: filepath.Join(cacheDir, ".diskv-temp"), 45 }) 46 t := httpcache.NewTransport(&sumDiskCache{disk: d}) 47 t.Transport = rt 48 49 return &cacheRoundTripper{rt: t} 50 } 51 52 func (rt *cacheRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 53 return rt.rt.RoundTrip(req) 54 } 55 56 func (rt *cacheRoundTripper) CancelRequest(req *http.Request) { 57 type canceler interface { 58 CancelRequest(*http.Request) 59 } 60 if cr, ok := rt.rt.Transport.(canceler); ok { 61 cr.CancelRequest(req) 62 } else { 63 klog.Errorf("CancelRequest not implemented by %T", rt.rt.Transport) 64 } 65 } 66 67 func (rt *cacheRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt.Transport } 68 69 // A sumDiskCache is a cache backend for github.com/gregjones/httpcache. It is 70 // similar to httpcache's diskcache package, but uses SHA256 sums to ensure 71 // cache integrity at read time rather than fsyncing each cache entry to 72 // increase the likelihood they will be persisted at write time. This avoids 73 // significant performance degradation on MacOS. 74 // 75 // See https://github.com/kubernetes/kubernetes/issues/110753 for more. 76 type sumDiskCache struct { 77 disk *diskv.Diskv 78 } 79 80 // Get the requested key from the cache on disk. If Get encounters an error, or 81 // the returned value is not a SHA256 sum followed by bytes with a matching 82 // checksum it will return false to indicate a cache miss. 83 func (c *sumDiskCache) Get(key string) ([]byte, bool) { 84 b, err := c.disk.Read(sanitize(key)) 85 if err != nil || len(b) < sha256.Size { 86 return []byte{}, false 87 } 88 89 response := b[sha256.Size:] 90 want := b[:sha256.Size] // The first 32 bytes of the file should be the SHA256 sum. 91 got := sha256.Sum256(response) 92 if !bytes.Equal(want, got[:]) { 93 return []byte{}, false 94 } 95 96 return response, true 97 } 98 99 // Set writes the response to a file on disk. The filename will be the SHA256 100 // sum of the key. The file will contain a SHA256 sum of the response bytes, 101 // followed by said response bytes. 102 func (c *sumDiskCache) Set(key string, response []byte) { 103 s := sha256.Sum256(response) 104 _ = c.disk.Write(sanitize(key), append(s[:], response...)) // Nothing we can do with this error. 105 } 106 107 func (c *sumDiskCache) Delete(key string) { 108 _ = c.disk.Erase(sanitize(key)) // Nothing we can do with this error. 109 } 110 111 // Sanitize an httpcache key such that it can be used as a diskv key, which must 112 // be a valid filename. The httpcache key will either be the requested URL (if 113 // the request method was GET) or "<method> <url>" for other methods, per the 114 // httpcache.cacheKey function. 115 func sanitize(key string) string { 116 // These keys are not sensitive. We use sha256 to avoid a (potentially 117 // malicious) collision causing the wrong cache data to be written or 118 // accessed. 119 return fmt.Sprintf("%x", sha256.Sum256([]byte(key))) 120 } 121