You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

435 lines
9.5 KiB

9 years ago
10 years ago
FEATURE: add JWT to HTTP endpoints of Filer and use them in S3 Client - one JWT for reading and one for writing, analogous to how the JWT between Master and Volume Server works - I did not implement IP `whiteList` parameter on the filer Additionally, because http_util.DownloadFile now sets the JWT, the `download` command should now work when `jwt.signing.read` is configured. By looking at the code, I think this case did not work before. ## Docs to be adjusted after a release Page `Amazon-S3-API`: ``` # Authentication with Filer You can use mTLS for the gRPC connection between S3-API-Proxy and the filer, as explained in [Security-Configuration](Security-Configuration) - controlled by the `grpc.*` configuration in `security.toml`. Starting with version XX, it is also possible to authenticate the HTTP operations between the S3-API-Proxy and the Filer (especially uploading new files). This is configured by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. With both configurations (gRPC and JWT), it is possible to have Filer and S3 communicate in fully authenticated fashion; so Filer will reject any unauthenticated communication. ``` Page `Security Overview`: ``` The following items are not covered, yet: - master server http REST services Starting with version XX, the Filer HTTP REST services can be secured with a JWT, by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. ... Before version XX: "weed filer -disableHttp", disable http operations, only gRPC operations are allowed. This works with "weed mount" by FUSE. It does **not work** with the [S3 Gateway](Amazon S3 API), as this does HTTP calls to the Filer. Starting with version XX: secured by JWT, by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. **This now works with the [S3 Gateway](Amazon S3 API).** ... # Securing Filer HTTP with JWT To enable JWT-based access control for the Filer, 1. generate `security.toml` file by `weed scaffold -config=security` 2. set `filer_jwt.signing.key` to a secret string - and optionally filer_jwt.signing.read.key` as well to a secret string 3. copy the same `security.toml` file to the filers and all S3 proxies. If `filer_jwt.signing.key` is configured: When sending upload/update/delete HTTP operations to a filer server, the request header `Authorization` should be the JWT string (`Authorization: Bearer [JwtToken]`). The operation is authorized after the filer validates the JWT with `filer_jwt.signing.key`. If `filer_jwt.signing.read.key` is configured: When sending GET or HEAD requests to a filer server, the request header `Authorization` should be the JWT string (`Authorization: Bearer [JwtToken]`). The operation is authorized after the filer validates the JWT with `filer_jwt.signing.read.key`. The S3 API Gateway reads the above JWT keys and sends authenticated HTTP requests to the filer. ``` Page `Security Configuration`: ``` (update scaffold file) ... [filer_jwt.signing] key = "blahblahblahblah" [filer_jwt.signing.read] key = "blahblahblahblah" ``` Resolves: #158
3 years ago
FEATURE: add JWT to HTTP endpoints of Filer and use them in S3 Client - one JWT for reading and one for writing, analogous to how the JWT between Master and Volume Server works - I did not implement IP `whiteList` parameter on the filer Additionally, because http_util.DownloadFile now sets the JWT, the `download` command should now work when `jwt.signing.read` is configured. By looking at the code, I think this case did not work before. ## Docs to be adjusted after a release Page `Amazon-S3-API`: ``` # Authentication with Filer You can use mTLS for the gRPC connection between S3-API-Proxy and the filer, as explained in [Security-Configuration](Security-Configuration) - controlled by the `grpc.*` configuration in `security.toml`. Starting with version XX, it is also possible to authenticate the HTTP operations between the S3-API-Proxy and the Filer (especially uploading new files). This is configured by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. With both configurations (gRPC and JWT), it is possible to have Filer and S3 communicate in fully authenticated fashion; so Filer will reject any unauthenticated communication. ``` Page `Security Overview`: ``` The following items are not covered, yet: - master server http REST services Starting with version XX, the Filer HTTP REST services can be secured with a JWT, by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. ... Before version XX: "weed filer -disableHttp", disable http operations, only gRPC operations are allowed. This works with "weed mount" by FUSE. It does **not work** with the [S3 Gateway](Amazon S3 API), as this does HTTP calls to the Filer. Starting with version XX: secured by JWT, by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. **This now works with the [S3 Gateway](Amazon S3 API).** ... # Securing Filer HTTP with JWT To enable JWT-based access control for the Filer, 1. generate `security.toml` file by `weed scaffold -config=security` 2. set `filer_jwt.signing.key` to a secret string - and optionally filer_jwt.signing.read.key` as well to a secret string 3. copy the same `security.toml` file to the filers and all S3 proxies. If `filer_jwt.signing.key` is configured: When sending upload/update/delete HTTP operations to a filer server, the request header `Authorization` should be the JWT string (`Authorization: Bearer [JwtToken]`). The operation is authorized after the filer validates the JWT with `filer_jwt.signing.key`. If `filer_jwt.signing.read.key` is configured: When sending GET or HEAD requests to a filer server, the request header `Authorization` should be the JWT string (`Authorization: Bearer [JwtToken]`). The operation is authorized after the filer validates the JWT with `filer_jwt.signing.read.key`. The S3 API Gateway reads the above JWT keys and sends authenticated HTTP requests to the filer. ``` Page `Security Configuration`: ``` (update scaffold file) ... [filer_jwt.signing] key = "blahblahblahblah" [filer_jwt.signing.read] key = "blahblahblahblah" ``` Resolves: #158
3 years ago
5 years ago
3 years ago
5 years ago
4 years ago
FEATURE: add JWT to HTTP endpoints of Filer and use them in S3 Client - one JWT for reading and one for writing, analogous to how the JWT between Master and Volume Server works - I did not implement IP `whiteList` parameter on the filer Additionally, because http_util.DownloadFile now sets the JWT, the `download` command should now work when `jwt.signing.read` is configured. By looking at the code, I think this case did not work before. ## Docs to be adjusted after a release Page `Amazon-S3-API`: ``` # Authentication with Filer You can use mTLS for the gRPC connection between S3-API-Proxy and the filer, as explained in [Security-Configuration](Security-Configuration) - controlled by the `grpc.*` configuration in `security.toml`. Starting with version XX, it is also possible to authenticate the HTTP operations between the S3-API-Proxy and the Filer (especially uploading new files). This is configured by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. With both configurations (gRPC and JWT), it is possible to have Filer and S3 communicate in fully authenticated fashion; so Filer will reject any unauthenticated communication. ``` Page `Security Overview`: ``` The following items are not covered, yet: - master server http REST services Starting with version XX, the Filer HTTP REST services can be secured with a JWT, by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. ... Before version XX: "weed filer -disableHttp", disable http operations, only gRPC operations are allowed. This works with "weed mount" by FUSE. It does **not work** with the [S3 Gateway](Amazon S3 API), as this does HTTP calls to the Filer. Starting with version XX: secured by JWT, by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. **This now works with the [S3 Gateway](Amazon S3 API).** ... # Securing Filer HTTP with JWT To enable JWT-based access control for the Filer, 1. generate `security.toml` file by `weed scaffold -config=security` 2. set `filer_jwt.signing.key` to a secret string - and optionally filer_jwt.signing.read.key` as well to a secret string 3. copy the same `security.toml` file to the filers and all S3 proxies. If `filer_jwt.signing.key` is configured: When sending upload/update/delete HTTP operations to a filer server, the request header `Authorization` should be the JWT string (`Authorization: Bearer [JwtToken]`). The operation is authorized after the filer validates the JWT with `filer_jwt.signing.key`. If `filer_jwt.signing.read.key` is configured: When sending GET or HEAD requests to a filer server, the request header `Authorization` should be the JWT string (`Authorization: Bearer [JwtToken]`). The operation is authorized after the filer validates the JWT with `filer_jwt.signing.read.key`. The S3 API Gateway reads the above JWT keys and sends authenticated HTTP requests to the filer. ``` Page `Security Configuration`: ``` (update scaffold file) ... [filer_jwt.signing] key = "blahblahblahblah" [filer_jwt.signing.read] key = "blahblahblahblah" ``` Resolves: #158
3 years ago
FEATURE: add JWT to HTTP endpoints of Filer and use them in S3 Client - one JWT for reading and one for writing, analogous to how the JWT between Master and Volume Server works - I did not implement IP `whiteList` parameter on the filer Additionally, because http_util.DownloadFile now sets the JWT, the `download` command should now work when `jwt.signing.read` is configured. By looking at the code, I think this case did not work before. ## Docs to be adjusted after a release Page `Amazon-S3-API`: ``` # Authentication with Filer You can use mTLS for the gRPC connection between S3-API-Proxy and the filer, as explained in [Security-Configuration](Security-Configuration) - controlled by the `grpc.*` configuration in `security.toml`. Starting with version XX, it is also possible to authenticate the HTTP operations between the S3-API-Proxy and the Filer (especially uploading new files). This is configured by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. With both configurations (gRPC and JWT), it is possible to have Filer and S3 communicate in fully authenticated fashion; so Filer will reject any unauthenticated communication. ``` Page `Security Overview`: ``` The following items are not covered, yet: - master server http REST services Starting with version XX, the Filer HTTP REST services can be secured with a JWT, by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. ... Before version XX: "weed filer -disableHttp", disable http operations, only gRPC operations are allowed. This works with "weed mount" by FUSE. It does **not work** with the [S3 Gateway](Amazon S3 API), as this does HTTP calls to the Filer. Starting with version XX: secured by JWT, by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. **This now works with the [S3 Gateway](Amazon S3 API).** ... # Securing Filer HTTP with JWT To enable JWT-based access control for the Filer, 1. generate `security.toml` file by `weed scaffold -config=security` 2. set `filer_jwt.signing.key` to a secret string - and optionally filer_jwt.signing.read.key` as well to a secret string 3. copy the same `security.toml` file to the filers and all S3 proxies. If `filer_jwt.signing.key` is configured: When sending upload/update/delete HTTP operations to a filer server, the request header `Authorization` should be the JWT string (`Authorization: Bearer [JwtToken]`). The operation is authorized after the filer validates the JWT with `filer_jwt.signing.key`. If `filer_jwt.signing.read.key` is configured: When sending GET or HEAD requests to a filer server, the request header `Authorization` should be the JWT string (`Authorization: Bearer [JwtToken]`). The operation is authorized after the filer validates the JWT with `filer_jwt.signing.read.key`. The S3 API Gateway reads the above JWT keys and sends authenticated HTTP requests to the filer. ``` Page `Security Configuration`: ``` (update scaffold file) ... [filer_jwt.signing] key = "blahblahblahblah" [filer_jwt.signing.read] key = "blahblahblahblah" ``` Resolves: #158
3 years ago
FEATURE: add JWT to HTTP endpoints of Filer and use them in S3 Client - one JWT for reading and one for writing, analogous to how the JWT between Master and Volume Server works - I did not implement IP `whiteList` parameter on the filer Additionally, because http_util.DownloadFile now sets the JWT, the `download` command should now work when `jwt.signing.read` is configured. By looking at the code, I think this case did not work before. ## Docs to be adjusted after a release Page `Amazon-S3-API`: ``` # Authentication with Filer You can use mTLS for the gRPC connection between S3-API-Proxy and the filer, as explained in [Security-Configuration](Security-Configuration) - controlled by the `grpc.*` configuration in `security.toml`. Starting with version XX, it is also possible to authenticate the HTTP operations between the S3-API-Proxy and the Filer (especially uploading new files). This is configured by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. With both configurations (gRPC and JWT), it is possible to have Filer and S3 communicate in fully authenticated fashion; so Filer will reject any unauthenticated communication. ``` Page `Security Overview`: ``` The following items are not covered, yet: - master server http REST services Starting with version XX, the Filer HTTP REST services can be secured with a JWT, by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. ... Before version XX: "weed filer -disableHttp", disable http operations, only gRPC operations are allowed. This works with "weed mount" by FUSE. It does **not work** with the [S3 Gateway](Amazon S3 API), as this does HTTP calls to the Filer. Starting with version XX: secured by JWT, by setting `filer_jwt.signing.key` and `filer_jwt.signing.read.key` in `security.toml`. **This now works with the [S3 Gateway](Amazon S3 API).** ... # Securing Filer HTTP with JWT To enable JWT-based access control for the Filer, 1. generate `security.toml` file by `weed scaffold -config=security` 2. set `filer_jwt.signing.key` to a secret string - and optionally filer_jwt.signing.read.key` as well to a secret string 3. copy the same `security.toml` file to the filers and all S3 proxies. If `filer_jwt.signing.key` is configured: When sending upload/update/delete HTTP operations to a filer server, the request header `Authorization` should be the JWT string (`Authorization: Bearer [JwtToken]`). The operation is authorized after the filer validates the JWT with `filer_jwt.signing.key`. If `filer_jwt.signing.read.key` is configured: When sending GET or HEAD requests to a filer server, the request header `Authorization` should be the JWT string (`Authorization: Bearer [JwtToken]`). The operation is authorized after the filer validates the JWT with `filer_jwt.signing.read.key`. The S3 API Gateway reads the above JWT keys and sends authenticated HTTP requests to the filer. ``` Page `Security Configuration`: ``` (update scaffold file) ... [filer_jwt.signing] key = "blahblahblahblah" [filer_jwt.signing.read] key = "blahblahblahblah" ``` Resolves: #158
3 years ago
  1. package util
  2. import (
  3. "compress/gzip"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "github.com/seaweedfs/seaweedfs/weed/util/mem"
  8. "io"
  9. "net/http"
  10. "net/url"
  11. "strings"
  12. "github.com/seaweedfs/seaweedfs/weed/glog"
  13. )
  14. var (
  15. client *http.Client
  16. Transport *http.Transport
  17. )
  18. func init() {
  19. Transport = &http.Transport{
  20. MaxIdleConns: 1024,
  21. MaxIdleConnsPerHost: 1024,
  22. }
  23. client = &http.Client{
  24. Transport: Transport,
  25. }
  26. }
  27. func Post(url string, values url.Values) ([]byte, error) {
  28. r, err := client.PostForm(url, values)
  29. if err != nil {
  30. return nil, err
  31. }
  32. defer r.Body.Close()
  33. b, err := io.ReadAll(r.Body)
  34. if r.StatusCode >= 400 {
  35. if err != nil {
  36. return nil, fmt.Errorf("%s: %d - %s", url, r.StatusCode, string(b))
  37. } else {
  38. return nil, fmt.Errorf("%s: %s", url, r.Status)
  39. }
  40. }
  41. if err != nil {
  42. return nil, err
  43. }
  44. return b, nil
  45. }
  46. // github.com/seaweedfs/seaweedfs/unmaintained/repeated_vacuum/repeated_vacuum.go
  47. // may need increasing http.Client.Timeout
  48. func Get(url string) ([]byte, bool, error) {
  49. request, err := http.NewRequest("GET", url, nil)
  50. request.Header.Add("Accept-Encoding", "gzip")
  51. response, err := client.Do(request)
  52. if err != nil {
  53. return nil, true, err
  54. }
  55. defer response.Body.Close()
  56. var reader io.ReadCloser
  57. switch response.Header.Get("Content-Encoding") {
  58. case "gzip":
  59. reader, err = gzip.NewReader(response.Body)
  60. defer reader.Close()
  61. default:
  62. reader = response.Body
  63. }
  64. b, err := io.ReadAll(reader)
  65. if response.StatusCode >= 400 {
  66. retryable := response.StatusCode >= 500
  67. return nil, retryable, fmt.Errorf("%s: %s", url, response.Status)
  68. }
  69. if err != nil {
  70. return nil, false, err
  71. }
  72. return b, false, nil
  73. }
  74. func Head(url string) (http.Header, error) {
  75. r, err := client.Head(url)
  76. if err != nil {
  77. return nil, err
  78. }
  79. defer CloseResponse(r)
  80. if r.StatusCode >= 400 {
  81. return nil, fmt.Errorf("%s: %s", url, r.Status)
  82. }
  83. return r.Header, nil
  84. }
  85. func Delete(url string, jwt string) error {
  86. req, err := http.NewRequest("DELETE", url, nil)
  87. if jwt != "" {
  88. req.Header.Set("Authorization", "BEARER "+string(jwt))
  89. }
  90. if err != nil {
  91. return err
  92. }
  93. resp, e := client.Do(req)
  94. if e != nil {
  95. return e
  96. }
  97. defer resp.Body.Close()
  98. body, err := io.ReadAll(resp.Body)
  99. if err != nil {
  100. return err
  101. }
  102. switch resp.StatusCode {
  103. case http.StatusNotFound, http.StatusAccepted, http.StatusOK:
  104. return nil
  105. }
  106. m := make(map[string]interface{})
  107. if e := json.Unmarshal(body, &m); e == nil {
  108. if s, ok := m["error"].(string); ok {
  109. return errors.New(s)
  110. }
  111. }
  112. return errors.New(string(body))
  113. }
  114. func DeleteProxied(url string, jwt string) (body []byte, httpStatus int, err error) {
  115. req, err := http.NewRequest("DELETE", url, nil)
  116. if jwt != "" {
  117. req.Header.Set("Authorization", "BEARER "+string(jwt))
  118. }
  119. if err != nil {
  120. return
  121. }
  122. resp, err := client.Do(req)
  123. if err != nil {
  124. return
  125. }
  126. defer resp.Body.Close()
  127. body, err = io.ReadAll(resp.Body)
  128. if err != nil {
  129. return
  130. }
  131. httpStatus = resp.StatusCode
  132. return
  133. }
  134. func GetBufferStream(url string, values url.Values, allocatedBytes []byte, eachBuffer func([]byte)) error {
  135. r, err := client.PostForm(url, values)
  136. if err != nil {
  137. return err
  138. }
  139. defer CloseResponse(r)
  140. if r.StatusCode != 200 {
  141. return fmt.Errorf("%s: %s", url, r.Status)
  142. }
  143. for {
  144. n, err := r.Body.Read(allocatedBytes)
  145. if n > 0 {
  146. eachBuffer(allocatedBytes[:n])
  147. }
  148. if err != nil {
  149. if err == io.EOF {
  150. return nil
  151. }
  152. return err
  153. }
  154. }
  155. }
  156. func GetUrlStream(url string, values url.Values, readFn func(io.Reader) error) error {
  157. r, err := client.PostForm(url, values)
  158. if err != nil {
  159. return err
  160. }
  161. defer CloseResponse(r)
  162. if r.StatusCode != 200 {
  163. return fmt.Errorf("%s: %s", url, r.Status)
  164. }
  165. return readFn(r.Body)
  166. }
  167. func DownloadFile(fileUrl string, jwt string) (filename string, header http.Header, resp *http.Response, e error) {
  168. req, err := http.NewRequest("GET", fileUrl, nil)
  169. if err != nil {
  170. return "", nil, nil, err
  171. }
  172. if len(jwt) > 0 {
  173. req.Header.Set("Authorization", "BEARER "+jwt)
  174. }
  175. response, err := client.Do(req)
  176. if err != nil {
  177. return "", nil, nil, err
  178. }
  179. header = response.Header
  180. contentDisposition := response.Header["Content-Disposition"]
  181. if len(contentDisposition) > 0 {
  182. idx := strings.Index(contentDisposition[0], "filename=")
  183. if idx != -1 {
  184. filename = contentDisposition[0][idx+len("filename="):]
  185. filename = strings.Trim(filename, "\"")
  186. }
  187. }
  188. resp = response
  189. return
  190. }
  191. func Do(req *http.Request) (resp *http.Response, err error) {
  192. return client.Do(req)
  193. }
  194. func NormalizeUrl(url string) string {
  195. if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
  196. return url
  197. }
  198. return "http://" + url
  199. }
  200. func ReadUrl(fileUrl string, cipherKey []byte, isContentCompressed bool, isFullChunk bool, offset int64, size int, buf []byte) (int64, error) {
  201. if cipherKey != nil {
  202. var n int
  203. _, err := readEncryptedUrl(fileUrl, cipherKey, isContentCompressed, isFullChunk, offset, size, func(data []byte) {
  204. n = copy(buf, data)
  205. })
  206. return int64(n), err
  207. }
  208. req, err := http.NewRequest("GET", fileUrl, nil)
  209. if err != nil {
  210. return 0, err
  211. }
  212. if !isFullChunk {
  213. req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+int64(size)-1))
  214. } else {
  215. req.Header.Set("Accept-Encoding", "gzip")
  216. }
  217. r, err := client.Do(req)
  218. if err != nil {
  219. return 0, err
  220. }
  221. defer r.Body.Close()
  222. if r.StatusCode >= 400 {
  223. return 0, fmt.Errorf("%s: %s", fileUrl, r.Status)
  224. }
  225. var reader io.ReadCloser
  226. contentEncoding := r.Header.Get("Content-Encoding")
  227. switch contentEncoding {
  228. case "gzip":
  229. reader, err = gzip.NewReader(r.Body)
  230. defer reader.Close()
  231. default:
  232. reader = r.Body
  233. }
  234. var (
  235. i, m int
  236. n int64
  237. )
  238. // refers to https://github.com/golang/go/blob/master/src/bytes/buffer.go#L199
  239. // commit id c170b14c2c1cfb2fd853a37add92a82fd6eb4318
  240. for {
  241. m, err = reader.Read(buf[i:])
  242. i += m
  243. n += int64(m)
  244. if err == io.EOF {
  245. return n, nil
  246. }
  247. if err != nil {
  248. return n, err
  249. }
  250. if n == int64(len(buf)) {
  251. break
  252. }
  253. }
  254. // drains the response body to avoid memory leak
  255. data, _ := io.ReadAll(reader)
  256. if len(data) != 0 {
  257. glog.V(1).Infof("%s reader has remaining %d bytes", contentEncoding, len(data))
  258. }
  259. return n, err
  260. }
  261. func ReadUrlAsStream(fileUrl string, cipherKey []byte, isContentGzipped bool, isFullChunk bool, offset int64, size int, fn func(data []byte)) (retryable bool, err error) {
  262. if cipherKey != nil {
  263. return readEncryptedUrl(fileUrl, cipherKey, isContentGzipped, isFullChunk, offset, size, fn)
  264. }
  265. req, err := http.NewRequest("GET", fileUrl, nil)
  266. if err != nil {
  267. return false, err
  268. }
  269. if isFullChunk {
  270. req.Header.Add("Accept-Encoding", "gzip")
  271. } else {
  272. req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+int64(size)-1))
  273. }
  274. r, err := client.Do(req)
  275. if err != nil {
  276. return true, err
  277. }
  278. defer CloseResponse(r)
  279. if r.StatusCode >= 400 {
  280. retryable = r.StatusCode >= 500
  281. return retryable, fmt.Errorf("%s: %s", fileUrl, r.Status)
  282. }
  283. var reader io.ReadCloser
  284. contentEncoding := r.Header.Get("Content-Encoding")
  285. switch contentEncoding {
  286. case "gzip":
  287. reader, err = gzip.NewReader(r.Body)
  288. defer reader.Close()
  289. default:
  290. reader = r.Body
  291. }
  292. var (
  293. m int
  294. )
  295. buf := mem.Allocate(64 * 1024)
  296. defer mem.Free(buf)
  297. for {
  298. m, err = reader.Read(buf)
  299. fn(buf[:m])
  300. if err == io.EOF {
  301. return false, nil
  302. }
  303. if err != nil {
  304. return true, err
  305. }
  306. }
  307. }
  308. func readEncryptedUrl(fileUrl string, cipherKey []byte, isContentCompressed bool, isFullChunk bool, offset int64, size int, fn func(data []byte)) (bool, error) {
  309. encryptedData, retryable, err := Get(fileUrl)
  310. if err != nil {
  311. return retryable, fmt.Errorf("fetch %s: %v", fileUrl, err)
  312. }
  313. decryptedData, err := Decrypt(encryptedData, CipherKey(cipherKey))
  314. if err != nil {
  315. return false, fmt.Errorf("decrypt %s: %v", fileUrl, err)
  316. }
  317. if isContentCompressed {
  318. decryptedData, err = DecompressData(decryptedData)
  319. if err != nil {
  320. glog.V(0).Infof("unzip decrypt %s: %v", fileUrl, err)
  321. }
  322. }
  323. if len(decryptedData) < int(offset)+size {
  324. return false, fmt.Errorf("read decrypted %s size %d [%d, %d)", fileUrl, len(decryptedData), offset, int(offset)+size)
  325. }
  326. if isFullChunk {
  327. fn(decryptedData)
  328. } else {
  329. fn(decryptedData[int(offset) : int(offset)+size])
  330. }
  331. return false, nil
  332. }
  333. func ReadUrlAsReaderCloser(fileUrl string, jwt string, rangeHeader string) (io.ReadCloser, error) {
  334. req, err := http.NewRequest("GET", fileUrl, nil)
  335. if err != nil {
  336. return nil, err
  337. }
  338. if rangeHeader != "" {
  339. req.Header.Add("Range", rangeHeader)
  340. } else {
  341. req.Header.Add("Accept-Encoding", "gzip")
  342. }
  343. if len(jwt) > 0 {
  344. req.Header.Set("Authorization", "BEARER "+jwt)
  345. }
  346. r, err := client.Do(req)
  347. if err != nil {
  348. return nil, err
  349. }
  350. if r.StatusCode >= 400 {
  351. return nil, fmt.Errorf("%s: %s", fileUrl, r.Status)
  352. }
  353. var reader io.ReadCloser
  354. contentEncoding := r.Header.Get("Content-Encoding")
  355. switch contentEncoding {
  356. case "gzip":
  357. reader, err = gzip.NewReader(r.Body)
  358. defer reader.Close()
  359. default:
  360. reader = r.Body
  361. }
  362. return reader, nil
  363. }
  364. func CloseResponse(resp *http.Response) {
  365. reader := &CountingReader{reader: resp.Body}
  366. io.Copy(io.Discard, reader)
  367. resp.Body.Close()
  368. if reader.BytesRead > 0 {
  369. glog.V(1).Infof("response leftover %d bytes", reader.BytesRead)
  370. }
  371. }
  372. func CloseRequest(req *http.Request) {
  373. reader := &CountingReader{reader: req.Body}
  374. io.Copy(io.Discard, reader)
  375. req.Body.Close()
  376. if reader.BytesRead > 0 {
  377. glog.V(1).Infof("request leftover %d bytes", reader.BytesRead)
  378. }
  379. }
  380. type CountingReader struct {
  381. reader io.Reader
  382. BytesRead int
  383. }
  384. func (r *CountingReader) Read(p []byte) (n int, err error) {
  385. n, err = r.reader.Read(p)
  386. r.BytesRead += n
  387. return n, err
  388. }