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.

501 lines
15 KiB

  1. // Copyright (c) 2015-2021 MinIO, Inc.
  2. //
  3. // This file is part of MinIO Object Storage stack
  4. //
  5. // This program is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU Affero General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU Affero General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. package cmd
  18. import (
  19. "bytes"
  20. "context"
  21. "io"
  22. "net/http"
  23. "net/url"
  24. "os"
  25. "testing"
  26. "time"
  27. "github.com/minio/minio/internal/auth"
  28. "github.com/minio/pkg/v3/policy"
  29. )
  30. type nullReader struct{}
  31. func (r *nullReader) Read(b []byte) (int, error) {
  32. return len(b), nil
  33. }
  34. // Test get request auth type.
  35. func TestGetRequestAuthType(t *testing.T) {
  36. type testCase struct {
  37. req *http.Request
  38. authT authType
  39. }
  40. nopCloser := io.NopCloser(io.LimitReader(&nullReader{}, 1024))
  41. testCases := []testCase{
  42. // Test case - 1
  43. // Check for generic signature v4 header.
  44. {
  45. req: &http.Request{
  46. URL: &url.URL{
  47. Host: "127.0.0.1:9000",
  48. Scheme: httpScheme,
  49. Path: SlashSeparator,
  50. },
  51. Header: http.Header{
  52. "Authorization": []string{"AWS4-HMAC-SHA256 <cred_string>"},
  53. "X-Amz-Content-Sha256": []string{streamingContentSHA256},
  54. "Content-Encoding": []string{streamingContentEncoding},
  55. },
  56. Method: http.MethodPut,
  57. Body: nopCloser,
  58. },
  59. authT: authTypeStreamingSigned,
  60. },
  61. // Test case - 2
  62. // Check for JWT header.
  63. {
  64. req: &http.Request{
  65. URL: &url.URL{
  66. Host: "127.0.0.1:9000",
  67. Scheme: httpScheme,
  68. Path: SlashSeparator,
  69. },
  70. Header: http.Header{
  71. "Authorization": []string{"Bearer 12313123"},
  72. },
  73. },
  74. authT: authTypeJWT,
  75. },
  76. // Test case - 3
  77. // Empty authorization header.
  78. {
  79. req: &http.Request{
  80. URL: &url.URL{
  81. Host: "127.0.0.1:9000",
  82. Scheme: httpScheme,
  83. Path: SlashSeparator,
  84. },
  85. Header: http.Header{
  86. "Authorization": []string{""},
  87. },
  88. },
  89. authT: authTypeUnknown,
  90. },
  91. // Test case - 4
  92. // Check for presigned.
  93. {
  94. req: &http.Request{
  95. URL: &url.URL{
  96. Host: "127.0.0.1:9000",
  97. Scheme: httpScheme,
  98. Path: SlashSeparator,
  99. RawQuery: "X-Amz-Credential=EXAMPLEINVALIDEXAMPL%2Fs3%2F20160314%2Fus-east-1",
  100. },
  101. },
  102. authT: authTypePresigned,
  103. },
  104. // Test case - 5
  105. // Check for post policy.
  106. {
  107. req: &http.Request{
  108. URL: &url.URL{
  109. Host: "127.0.0.1:9000",
  110. Scheme: httpScheme,
  111. Path: SlashSeparator,
  112. },
  113. Header: http.Header{
  114. "Content-Type": []string{"multipart/form-data"},
  115. },
  116. Method: http.MethodPost,
  117. Body: nopCloser,
  118. },
  119. authT: authTypePostPolicy,
  120. },
  121. }
  122. // .. Tests all request auth type.
  123. for i, testc := range testCases {
  124. authT := getRequestAuthType(testc.req)
  125. if authT != testc.authT {
  126. t.Errorf("Test %d: Expected %d, got %d", i+1, testc.authT, authT)
  127. }
  128. }
  129. }
  130. // Test all s3 supported auth types.
  131. func TestS3SupportedAuthType(t *testing.T) {
  132. type testCase struct {
  133. authT authType
  134. pass bool
  135. }
  136. // List of all valid and invalid test cases.
  137. testCases := []testCase{
  138. // Test 1 - supported s3 type anonymous.
  139. {
  140. authT: authTypeAnonymous,
  141. pass: true,
  142. },
  143. // Test 2 - supported s3 type presigned.
  144. {
  145. authT: authTypePresigned,
  146. pass: true,
  147. },
  148. // Test 3 - supported s3 type signed.
  149. {
  150. authT: authTypeSigned,
  151. pass: true,
  152. },
  153. // Test 4 - supported s3 type with post policy.
  154. {
  155. authT: authTypePostPolicy,
  156. pass: true,
  157. },
  158. // Test 5 - supported s3 type with streaming signed.
  159. {
  160. authT: authTypeStreamingSigned,
  161. pass: true,
  162. },
  163. // Test 6 - supported s3 type with signature v2.
  164. {
  165. authT: authTypeSignedV2,
  166. pass: true,
  167. },
  168. // Test 7 - supported s3 type with presign v2.
  169. {
  170. authT: authTypePresignedV2,
  171. pass: true,
  172. },
  173. // Test 8 - JWT is not supported s3 type.
  174. {
  175. authT: authTypeJWT,
  176. pass: false,
  177. },
  178. // Test 9 - unknown auth header is not supported s3 type.
  179. {
  180. authT: authTypeUnknown,
  181. pass: false,
  182. },
  183. // Test 10 - some new auth type is not supported s3 type.
  184. {
  185. authT: authType(9),
  186. pass: false,
  187. },
  188. }
  189. // Validate all the test cases.
  190. for i, tt := range testCases {
  191. ok := isSupportedS3AuthType(tt.authT)
  192. if ok != tt.pass {
  193. t.Errorf("Test %d:, Expected %t, got %t", i+1, tt.pass, ok)
  194. }
  195. }
  196. }
  197. func TestIsRequestPresignedSignatureV2(t *testing.T) {
  198. testCases := []struct {
  199. inputQueryKey string
  200. inputQueryValue string
  201. expectedResult bool
  202. }{
  203. // Test case - 1.
  204. // Test case with query key "AWSAccessKeyId" set.
  205. {"", "", false},
  206. // Test case - 2.
  207. {"AWSAccessKeyId", "", true},
  208. // Test case - 3.
  209. {"X-Amz-Content-Sha256", "", false},
  210. }
  211. for i, testCase := range testCases {
  212. // creating an input HTTP request.
  213. // Only the query parameters are relevant for this particular test.
  214. inputReq, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
  215. if err != nil {
  216. t.Fatalf("Error initializing input HTTP request: %v", err)
  217. }
  218. q := inputReq.URL.Query()
  219. q.Add(testCase.inputQueryKey, testCase.inputQueryValue)
  220. inputReq.URL.RawQuery = q.Encode()
  221. inputReq.ParseForm()
  222. actualResult := isRequestPresignedSignatureV2(inputReq)
  223. if testCase.expectedResult != actualResult {
  224. t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult)
  225. }
  226. }
  227. }
  228. // TestIsRequestPresignedSignatureV4 - Test validates the logic for presign signature version v4 detection.
  229. func TestIsRequestPresignedSignatureV4(t *testing.T) {
  230. testCases := []struct {
  231. inputQueryKey string
  232. inputQueryValue string
  233. expectedResult bool
  234. }{
  235. // Test case - 1.
  236. // Test case with query key ""X-Amz-Credential" set.
  237. {"", "", false},
  238. // Test case - 2.
  239. {"X-Amz-Credential", "", true},
  240. // Test case - 3.
  241. {"X-Amz-Content-Sha256", "", false},
  242. }
  243. for i, testCase := range testCases {
  244. // creating an input HTTP request.
  245. // Only the query parameters are relevant for this particular test.
  246. inputReq, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
  247. if err != nil {
  248. t.Fatalf("Error initializing input HTTP request: %v", err)
  249. }
  250. q := inputReq.URL.Query()
  251. q.Add(testCase.inputQueryKey, testCase.inputQueryValue)
  252. inputReq.URL.RawQuery = q.Encode()
  253. inputReq.ParseForm()
  254. actualResult := isRequestPresignedSignatureV4(inputReq)
  255. if testCase.expectedResult != actualResult {
  256. t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult)
  257. }
  258. }
  259. }
  260. // Provides a fully populated http request instance, fails otherwise.
  261. func mustNewRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {
  262. req, err := newTestRequest(method, urlStr, contentLength, body)
  263. if err != nil {
  264. t.Fatalf("Unable to initialize new http request %s", err)
  265. }
  266. return req
  267. }
  268. // This is similar to mustNewRequest but additionally the request
  269. // is signed with AWS Signature V4, fails if not able to do so.
  270. func mustNewSignedRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {
  271. req := mustNewRequest(method, urlStr, contentLength, body, t)
  272. cred := globalActiveCred
  273. if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil {
  274. t.Fatalf("Unable to initialized new signed http request %s", err)
  275. }
  276. return req
  277. }
  278. // This is similar to mustNewRequest but additionally the request
  279. // is signed with AWS Signature V2, fails if not able to do so.
  280. func mustNewSignedV2Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {
  281. req := mustNewRequest(method, urlStr, contentLength, body, t)
  282. cred := globalActiveCred
  283. if err := signRequestV2(req, cred.AccessKey, cred.SecretKey); err != nil {
  284. t.Fatalf("Unable to initialized new signed http request %s", err)
  285. }
  286. return req
  287. }
  288. // This is similar to mustNewRequest but additionally the request
  289. // is presigned with AWS Signature V2, fails if not able to do so.
  290. func mustNewPresignedV2Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {
  291. req := mustNewRequest(method, urlStr, contentLength, body, t)
  292. cred := globalActiveCred
  293. if err := preSignV2(req, cred.AccessKey, cred.SecretKey, time.Now().Add(10*time.Minute).Unix()); err != nil {
  294. t.Fatalf("Unable to initialized new signed http request %s", err)
  295. }
  296. return req
  297. }
  298. // This is similar to mustNewRequest but additionally the request
  299. // is presigned with AWS Signature V4, fails if not able to do so.
  300. func mustNewPresignedRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {
  301. req := mustNewRequest(method, urlStr, contentLength, body, t)
  302. cred := globalActiveCred
  303. if err := preSignV4(req, cred.AccessKey, cred.SecretKey, time.Now().Add(10*time.Minute).Unix()); err != nil {
  304. t.Fatalf("Unable to initialized new signed http request %s", err)
  305. }
  306. return req
  307. }
  308. func mustNewSignedShortMD5Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {
  309. req := mustNewRequest(method, urlStr, contentLength, body, t)
  310. req.Header.Set("Content-Md5", "invalid-digest")
  311. cred := globalActiveCred
  312. if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil {
  313. t.Fatalf("Unable to initialized new signed http request %s", err)
  314. }
  315. return req
  316. }
  317. func mustNewSignedEmptyMD5Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {
  318. req := mustNewRequest(method, urlStr, contentLength, body, t)
  319. req.Header.Set("Content-Md5", "")
  320. cred := globalActiveCred
  321. if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil {
  322. t.Fatalf("Unable to initialized new signed http request %s", err)
  323. }
  324. return req
  325. }
  326. func mustNewSignedBadMD5Request(method string, urlStr string, contentLength int64,
  327. body io.ReadSeeker, t *testing.T,
  328. ) *http.Request {
  329. req := mustNewRequest(method, urlStr, contentLength, body, t)
  330. req.Header.Set("Content-Md5", "YWFhYWFhYWFhYWFhYWFhCg==")
  331. cred := globalActiveCred
  332. if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil {
  333. t.Fatalf("Unable to initialized new signed http request %s", err)
  334. }
  335. return req
  336. }
  337. // Tests is requested authenticated function, tests replies for s3 errors.
  338. func TestIsReqAuthenticated(t *testing.T) {
  339. ctx, cancel := context.WithCancel(GlobalContext)
  340. defer cancel()
  341. objLayer, fsDir, err := prepareFS(ctx)
  342. if err != nil {
  343. t.Fatal(err)
  344. }
  345. defer os.RemoveAll(fsDir)
  346. if err = newTestConfig(globalMinioDefaultRegion, objLayer); err != nil {
  347. t.Fatalf("unable initialize config file, %s", err)
  348. }
  349. initAllSubsystems(ctx)
  350. initConfigSubsystem(ctx, objLayer)
  351. creds, err := auth.CreateCredentials("myuser", "mypassword")
  352. if err != nil {
  353. t.Fatalf("unable create credential, %s", err)
  354. }
  355. globalActiveCred = creds
  356. globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
  357. // List of test cases for validating http request authentication.
  358. testCases := []struct {
  359. req *http.Request
  360. s3Error APIErrorCode
  361. }{
  362. // When request is unsigned, access denied is returned.
  363. {mustNewRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrAccessDenied},
  364. // Empty Content-Md5 header.
  365. {mustNewSignedEmptyMD5Request(http.MethodPut, "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrInvalidDigest},
  366. // Short Content-Md5 header.
  367. {mustNewSignedShortMD5Request(http.MethodPut, "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrInvalidDigest},
  368. // When request is properly signed, but has bad Content-MD5 header.
  369. {mustNewSignedBadMD5Request(http.MethodPut, "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrBadDigest},
  370. // When request is properly signed, error is none.
  371. {mustNewSignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrNone},
  372. }
  373. // Validates all testcases.
  374. for i, testCase := range testCases {
  375. s3Error := isReqAuthenticated(ctx, testCase.req, globalSite.Region(), serviceS3)
  376. if s3Error != testCase.s3Error {
  377. if _, err := io.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {
  378. t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code)
  379. }
  380. }
  381. }
  382. }
  383. func TestCheckAdminRequestAuthType(t *testing.T) {
  384. ctx, cancel := context.WithCancel(t.Context())
  385. defer cancel()
  386. objLayer, fsDir, err := prepareFS(ctx)
  387. if err != nil {
  388. t.Fatal(err)
  389. }
  390. defer os.RemoveAll(fsDir)
  391. if err = newTestConfig(globalMinioDefaultRegion, objLayer); err != nil {
  392. t.Fatalf("unable initialize config file, %s", err)
  393. }
  394. creds, err := auth.CreateCredentials("myuser", "mypassword")
  395. if err != nil {
  396. t.Fatalf("unable create credential, %s", err)
  397. }
  398. globalActiveCred = creds
  399. testCases := []struct {
  400. Request *http.Request
  401. ErrCode APIErrorCode
  402. }{
  403. {Request: mustNewRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
  404. {Request: mustNewSignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrNone},
  405. {Request: mustNewSignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
  406. {Request: mustNewPresignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
  407. {Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
  408. }
  409. for i, testCase := range testCases {
  410. if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, policy.AllAdminActions, globalSite.Region()); s3Error != testCase.ErrCode {
  411. t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
  412. }
  413. }
  414. }
  415. func TestValidateAdminSignature(t *testing.T) {
  416. ctx, cancel := context.WithCancel(t.Context())
  417. defer cancel()
  418. objLayer, fsDir, err := prepareFS(ctx)
  419. if err != nil {
  420. t.Fatal(err)
  421. }
  422. defer os.RemoveAll(fsDir)
  423. if err = newTestConfig(globalMinioDefaultRegion, objLayer); err != nil {
  424. t.Fatalf("unable initialize config file, %s", err)
  425. }
  426. initAllSubsystems(ctx)
  427. initConfigSubsystem(ctx, objLayer)
  428. creds, err := auth.CreateCredentials("admin", "mypassword")
  429. if err != nil {
  430. t.Fatalf("unable create credential, %s", err)
  431. }
  432. globalActiveCred = creds
  433. globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
  434. testCases := []struct {
  435. AccessKey string
  436. SecretKey string
  437. ErrCode APIErrorCode
  438. }{
  439. {"", "", ErrInvalidAccessKeyID},
  440. {"admin", "", ErrSignatureDoesNotMatch},
  441. {"admin", "wrongpassword", ErrSignatureDoesNotMatch},
  442. {"wronguser", "mypassword", ErrInvalidAccessKeyID},
  443. {"", "mypassword", ErrInvalidAccessKeyID},
  444. {"admin", "mypassword", ErrNone},
  445. }
  446. for i, testCase := range testCases {
  447. req := mustNewRequest(http.MethodGet, "http://localhost:9000/", 0, nil, t)
  448. if err := signRequestV4(req, testCase.AccessKey, testCase.SecretKey); err != nil {
  449. t.Fatalf("Unable to initialized new signed http request %s", err)
  450. }
  451. _, _, s3Error := validateAdminSignature(ctx, req, globalMinioDefaultRegion)
  452. if s3Error != testCase.ErrCode {
  453. t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i+1, testCase.ErrCode, s3Error)
  454. }
  455. }
  456. }