Compare commits

...

120 Commits

Author SHA1 Message Date
Bala FA 2a2ff96ee1 change ReadPerf into ReadThroughput in NetPerfInfo. (#8316)
Previously `ReadPerf` was in time.Duration is changed to `ReadThroughput` in uint64.
2019-09-27 00:01:18 +05:30
Harshavardhana fd53057654 Add InfoCannedPolicy API to fetch only necessary policy (#8307)
This PR adds
- InfoCannedPolicy() API for efficiency in fetching policies
- Send group memberships for LDAPUser if available
2019-09-26 23:53:13 +05:30
Harshavardhana 3094615e38 Update LDAP document to reflect work user/group sub-commands (#8313)
This PR also fixes markdown formatting
2019-09-26 23:48:26 +05:30
Klaus Post ff726969aa Switch to Snappy -> S2 compression (#8189) 2019-09-25 23:08:24 -07:00
Klaus Post be313f1758 S3 Select: Workaround java buffer size (#8312)
Updates #7475

The Java implementation has a 128KB buffer and a message must be emitted before that is used. #7475 therefore limits the message size to 128KB. But up to 256 bytes are written to the buffer in each call. This means we must emit a message before shorter than 128KB.

Therefore we change the limit to 128KB minus 256 bytes.
2019-09-26 04:56:20 +05:30
Aditya Manthramurthy 704be85987 Fix numbering in doc (#8311) 2019-09-26 04:48:59 +05:30
Aditya Manthramurthy c8da04ba5b Add section on user/group policy for AD/LDAP integration (#8310) 2019-09-26 04:45:09 +05:30
Minio Trusted 9ed423b13f Update yaml files to latest version RELEASE.2019-09-25T18-25-51Z 2019-09-25 18:31:05 +00:00
Harshavardhana c8fbc94329 Fix writing 'format.json' and make it atomic (#8296)
- Choose a unique uuid such that under situations of duplicate
  mounts we do not append to an existing json entry.
- Avoid AppendFile instead use WriteAll() to write the entire
  byte array atomically.
2019-09-24 18:47:26 -07:00
Harshavardhana be70ef59e7 Update minio-go to latest release (#8297)
- Change LDAP XML names to follow server changes
- Fix listObjects hang when listing special characters
- set encoding type to url in listobjects API
2019-09-24 01:01:54 -07:00
Anis Elleuch a790877c01 s3: Encode continuation & next continuation tokens when asked (#8292)
When url encoding is passed in v2 listing handler, continuationToken
and nextContinuationToken needs to be encoded. The reason is that
both represents an object name/prefix in Minio server and it could
contain a character unsupported by XML specification.
2019-09-24 05:30:53 +05:30
Harshavardhana 77dc2031a2 Fix LDAP responseXML to be named appropriately (#8285)
This PR additionally also adds support for missing

- Session policy support for AD/LDAP
- Add API request/response parameters detail
- Update example to take ldap username,
  password input from the command line
- Fixes session policy handling for
  ClientGrants and WebIdentity
2019-09-24 03:51:16 +05:30
Harshavardhana 975134e42b Add checks in DiskInfo() to protect against changing mounts (#8286) 2019-09-23 15:16:55 -07:00
Andreas Auernhammer cb7d23cb17 remove SSE-S3 key rotation in CopyObject (#8278)
This commit removes the SSE-S3 key rotation functionality
from CopyObject since there will be a dedicated Admin-API
for this purpose.

Also update the security documentation to link to mc and
the admin documentation.
2019-09-24 02:05:04 +05:30
poornas 2e02e1889b Cleanup ResponseWriter function for audit and trace (#8283) 2019-09-24 02:04:28 +05:30
Julio Pintos 3b05e175d7 fix link to let's encrypt doc (#8294) 2019-09-23 23:53:11 +05:30
Harshavardhana b512241300 Add metrics healthcheck test with JWT (#8287) 2019-09-22 23:30:38 -07:00
ebozduman dbf7b1e573 starts-with policy condition support issue (#7937) 2019-09-22 14:20:49 -07:00
Harshavardhana 26985ac632 Fix all failing tests with -race 2019-09-22 11:01:46 -07:00
Praveen raj Mani ad75683bde Authorize prometheus endpoint with bearer token (#7640) 2019-09-22 20:27:12 +05:30
poornas 4925bc3e80 log server startup messages to admin console api (#8264) 2019-09-22 13:54:32 +05:30
Andreas Auernhammer ffded5a930 make the crypto error type a native go type (#8267)
This commit makes the `crypto.Error` type a native go (string)
type. That allows us to define error values as constants instead
of variables.

For reference see:
 - https://twitter.com/_aead_/status/1118170258215514115?s=20
 - https://dave.cheney.net/2016/04/07/constant-errors
2019-09-22 01:12:51 -07:00
Krishnan Parthasarathi 1127293863 Add a basic .dockerignore file to reduce docker context in make docker (#8282)
- Add useful default tag to dev docker image build using `make docker`
2019-09-21 04:49:56 +05:30
Andreas Auernhammer 2b51fe9f26 make SSE request header check comprehensive (#8276)
This commit refactors the SSE header check
by moving it into the `crypto` package, adds
a unit test for it and makes the check comprehensive.
2019-09-21 03:26:12 +05:30
Harshavardhana 4780fa5a58 Remove setting net.Conn Deadlines as its not needed anymore (#8269)
This commit fixes a bug introduced in af6c6a2b35.

Setting deadlines in Go results in arbitrary hangs as reported here
https://github.com/golang/go/issues/34385

Fixes https://github.com/minio/minio/issues/7852
2019-09-20 23:37:24 +05:30
Klaus Post 520552ffa9 S3 select: flush when reaching limit (#8279)
Add missing flush when reaching select limit.
2019-09-20 11:00:17 -07:00
Andreas Auernhammer b823d6d7bd remove the unused code for decrypting io.Writer (#8277)
This commit removes unused code for decrypting
`io.Writer` since the actual implementation only
decrypts `io.Reader`
2019-09-20 14:51:07 +05:30
Andreas Auernhammer a9d724120f remove TLS 1.3 opt-in code (#8275)
This commit removes the TLS 1.3 opt-in code.
Since TLS 1.3 is opt-out for >= Go 1.13 this
code is not needed anymore.
2019-09-20 01:51:44 +05:30
Praveen raj Mani dc0dce9beb Check for connection errors during event transit in elasticsearch and webhook (#8273) 2019-09-19 08:23:43 -07:00
Yao Zongyou 0e1408844b fix get container id bug (#8196) 2019-09-19 10:19:51 +05:30
Andreas Auernhammer e34369c860 prepare SSE-S3 metadata parsing for K/V data key store (#8259)
This commit allows the MinIO server to parse the metadata if:
 - either the `X-Minio-Internal-Server-Side-Encryption-S3-Key-Id`
   and the `X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key`
   entries are present.
 - or *both* headers are not present.

This is in service to support a K/V data key store.
2019-09-19 04:08:09 +05:30
Minio Trusted 8dc897b5f5 Update yaml files to latest version RELEASE.2019-09-18T21-55-05Z 2019-09-18 22:02:41 +00:00
Praveen raj Mani 456ce4cc92 Add rootCAs support to Kafka & MQTT (#8236)
Fixes #8211
2019-09-18 23:43:04 +05:30
Aditya Manthramurthy 696f4ceee2 Add notes on configuring LDAP STS with Microsoft Active Directory (#8260) 2019-09-17 17:21:37 -07:00
Klaus Post dac1cf5a9a S3 Select: Parsing tweaks (#8261)
* Don't output empty lines.
* Trim whitespace from byte to int/float/bool conversions.
2019-09-17 17:21:23 -07:00
Harshavardhana cb01516a26 In HDFS gateway fix non-empty folder behavior (#8254)
To be compatible with our FS and Erasure coded
mode deployments, make sure that we do not send
200 OK for folders which have files inside.

Fixes #8143
2019-09-18 01:59:59 +05:30
Aditya Manthramurthy dfa1b417a8 Update comments in ldap example client code (#8257) 2019-09-18 01:59:31 +05:30
Krishnan Parthasarathi 31bee6b6ed Remove size query parameter from PerfInfo handler (#8258) 2019-09-18 01:59:12 +05:30
poornas 04b92124c5 fs/xl: Log warning if cache config specified (#8251)
in non-gateway mode.
2019-09-16 19:55:52 -07:00
Harshavardhana 5392eee250 Avoid recursion and use a simple loop to merge entries (#8239)
This avoids stack overflows when there are
lot of entries to be skipped, this PR also
optimizes the code to reuse the buffers.
2019-09-17 06:08:37 +05:30
Vivian Kong fa32c71a56 Build staticcheck as pre-built binaries are not available for s390x (#8220) 2019-09-17 05:59:15 +05:30
Klaus Post c9b8bd8de2 S3 Select: optimize output (#8238)
Queue output items and reuse them.
Remove the unneeded type system in sql and just use the Go type system.

In best case this is more than an order of magnitude speedup:

```
BenchmarkSelectAll_1M-12    	       1	1841049400 ns/op	274299728 B/op	 4198522 allocs/op
BenchmarkSelectAll_1M-12    	      14	  84833400 ns/op	169228346 B/op	 3146541 allocs/op
```
2019-09-17 05:56:27 +05:30
Klaus Post 017456df63 Wait clearing the close channel (#8250)
Close channel should not be nilled before goroutines have exited.

Fixes potential hang on closing.
2019-09-16 16:18:01 -07:00
Harshavardhana 14b137aa66 posix/readDir should populate name for DT_UKNOWN (#8240)
In commit a8296445ad we changed the code to handle
some corner cases on ARM and other platforms, this
PR just avoids the return for unknown filetypes
prematurely and let the name be populated appropriately.

This fixes bug for older XFS implementations such as
in Ubuntu 14.04
2019-09-17 03:04:01 +05:30
Andreas Auernhammer 3064da7b08 return error during part listing when no quorum (#8241)
This commit fixes a subtle bug that (probably)
caused an issue affecting encrypted multipart objects.

When a cluster has no quorum this bug causes `ListObjectParts`
to return nil as error instead of a quorum error.

Thanks to @harshavardhana for detecting this.
2019-09-17 02:57:34 +05:30
poornas 76df027264 Allow caching only in gateway mode. (#8232)
This PR changes cache on PUT behavior to background fill the cache
after PutObject completes. This will avoid concurrency issues as in #8219.

Added cleanup of partially filled cache to prevent cache corruption
- Fixes #8208
2019-09-17 02:54:04 +05:30
k-tipp 208efb843b Update Kafka notification example doc (#8212) 2019-09-16 19:24:55 +05:30
Harshavardhana 9ac12cf898 Remove unusued Set/GetConfigKeys API (#8235) 2019-09-13 16:34:34 -07:00
Klaus Post ddea0bdf11 Concurrent CSV parsing and reduce S3 select allocations (#8200)
```
CSV parsing, BEFORE:
BenchmarkReaderBasic-12         	    2842	    407533 ns/op	  397860 B/op	     957 allocs/op
BenchmarkReaderReplace-12       	    2718	    429914 ns/op	  397844 B/op	     957 allocs/op
BenchmarkReaderReplaceTwo-12    	    2718	    435556 ns/op	  397855 B/op	     957 allocs/op
BenchmarkAggregateCount_100K-12    	     171	   6798974 ns/op	16667102 B/op	  308077 allocs/op
BenchmarkAggregateCount_1M-12    	      19	  65657411 ns/op	168057743 B/op	 3146610 allocs/op
BenchmarkSelectAll_10M-12    	       1	20882119900 ns/op	2758799896 B/op	41978762 allocs/op

CSV parsing, AFTER:
BenchmarkReaderBasic-12         	    3721	    312549 ns/op	  101920 B/op	     338 allocs/op
BenchmarkReaderReplace-12       	    3776	    318810 ns/op	  101993 B/op	     340 allocs/op
BenchmarkReaderReplaceTwo-12    	    3610	    330967 ns/op	  102012 B/op	     341 allocs/op
BenchmarkAggregateCount_100K-12    	     295	   4149588 ns/op	 3553623 B/op	  103261 allocs/op
BenchmarkAggregateCount_1M-12    	      30	  37746503 ns/op	33827931 B/op	 1049435 allocs/op
BenchmarkSelectAll_10M-12    	       1	17608495800 ns/op	1416504040 B/op	21007082 allocs/op

~ benchcmp old.txt new.txt
benchmark                           old ns/op       new ns/op       delta
BenchmarkReaderBasic-12             407533          312549          -23.31%
BenchmarkReaderReplace-12           429914          318810          -25.84%
BenchmarkReaderReplaceTwo-12        435556          330967          -24.01%
BenchmarkAggregateCount_100K-12     6798974         4149588         -38.97%
BenchmarkAggregateCount_1M-12       65657411        37746503        -42.51%
BenchmarkSelectAll_10M-12           20882119900     17608495800     -15.68%

benchmark                           old allocs     new allocs     delta
BenchmarkReaderBasic-12             957            338            -64.68%
BenchmarkReaderReplace-12           957            340            -64.47%
BenchmarkReaderReplaceTwo-12        957            341            -64.37%
BenchmarkAggregateCount_100K-12     308077         103261         -66.48%
BenchmarkAggregateCount_1M-12       3146610        1049435        -66.65%
BenchmarkSelectAll_10M-12           41978762       21007082       -49.96%

benchmark                           old bytes      new bytes      delta
BenchmarkReaderBasic-12             397860         101920         -74.38%
BenchmarkReaderReplace-12           397844         101993         -74.36%
BenchmarkReaderReplaceTwo-12        397855         102012         -74.36%
BenchmarkAggregateCount_100K-12     16667102       3553623        -78.68%
BenchmarkAggregateCount_1M-12       168057743      33827931       -79.87%
BenchmarkSelectAll_10M-12           2758799896     1416504040     -48.66%
```

```
BenchmarkReaderHuge/97K-12         	    2200	    540840 ns/op	 184.32 MB/s	 1604450 B/op	     687 allocs/op
BenchmarkReaderHuge/194K-12        	    1522	    752257 ns/op	 265.04 MB/s	 2143135 B/op	    1335 allocs/op
BenchmarkReaderHuge/389K-12        	    1190	    947858 ns/op	 420.69 MB/s	 3221831 B/op	    2630 allocs/op
BenchmarkReaderHuge/778K-12        	     806	   1472486 ns/op	 541.61 MB/s	 5201856 B/op	    5187 allocs/op
BenchmarkReaderHuge/1557K-12       	     426	   2575269 ns/op	 619.36 MB/s	 9101330 B/op	   10233 allocs/op
BenchmarkReaderHuge/3115K-12       	     286	   4034656 ns/op	 790.66 MB/s	12397968 B/op	   16099 allocs/op
BenchmarkReaderHuge/6230K-12       	     172	   6830563 ns/op	 934.05 MB/s	16008416 B/op	   26844 allocs/op
BenchmarkReaderHuge/12461K-12      	     100	  11409467 ns/op	1118.39 MB/s	22655163 B/op	   48107 allocs/op
BenchmarkReaderHuge/24922K-12      	      66	  19780395 ns/op	1290.19 MB/s	35158559 B/op	   90216 allocs/op
BenchmarkReaderHuge/49844K-12      	      34	  37282559 ns/op	1369.03 MB/s	60528624 B/op	  174497 allocs/op
```
2019-09-13 14:18:35 -07:00
Harshavardhana e7f491a14b Use optimized sha256-simd whenever possible (#8227)
Avoid using `crypto/sha256` and use always
`github.com/minio/sha256-simd`
2019-09-14 00:39:39 +05:30
Praveen raj Mani 8700945cdf Handle connection failures on webhook/url pings (#8204)
Properly handle connection failures while replaying events

Fixes #8194
2019-09-12 16:44:51 -07:00
Harshavardhana ff6aabd9c0 Honor standard HTTP headers for sourceIP (#8233)
Behind load balancers we should be tracing sourceIP
preserved by load balancers.
2019-09-13 03:59:59 +05:30
Krishnan Parthasarathi 6ba323b009 Add ability to test drive speeds on a MinIO setup (#7664)
- Extends existing Admin API to measure disk performance
2019-09-13 03:22:30 +05:30
Anis Elleuch e7b3f39064 xl: Fix verifying non streaming highway algo with a dist setup (#8230)
VerifyFile in the distributed setup does not work with
the non streaming highway hash. The reason is that the
internode mux router did not expect `storageRESTBitrotHash`
parameter.
2019-09-12 13:08:02 -07:00
Harshavardhana 9fa727d154 Provide a friendlier error when an update fails (#8228)
Add upgrading documentation as well
2019-09-13 01:33:42 +05:30
Harshavardhana 73e4e99942 Hosts should be skipped, when calculating local info (#8191)
endpoint.IsLocal will not have .Host entries so
using them to skip double entries will never work.

change the code such that we look for endpoint.Host
outside of endpoint.IsLocal logic to skip double
hosts appropriately.

Move these functions to their appropriate file.
2019-09-12 23:36:12 +05:30
Alex Pardoe a87fc7d09b Use the B2 'list' endpoint to determine file ID (#8169)
- More effective deletion and checking for existence.
- Rever Dockerfile.
- Add a 'GOPROXY' to the Dockerfile to workaround Apache issues.
2019-09-12 22:48:47 +05:30
Harshavardhana 475df52a19 Fix etcd watch regression in IAM subsystem (#8224)
Fixes #8223
2019-09-12 07:24:25 +05:30
Harshavardhana 5512baab21 Fix go mod tidy errors and fix platform independent downloads (#8222)
Refer https://github.com/gin-gonic/gin/issues/1673
2019-09-12 04:57:59 +05:30
Harshavardhana 77963078a2 Remove maintainers.md use only contributing.md (#8215) 2019-09-12 04:57:11 +05:30
Anis Elleuch 3f258062d8 bitrot: Verify file size inside storage interface (#7932) 2019-09-12 02:19:53 +05:30
Minio Trusted 3d65dc8d94 Update yaml files to latest version RELEASE.2019-09-11T19-53-16Z 2019-09-11 19:59:05 +00:00
Harshavardhana 53e4887e02 Simplify and cleanup metadata r/w functions (#8146) 2019-09-11 22:52:12 +05:30
Harshavardhana a7be313230 Start using new errors package (#8207) 2019-09-11 22:51:43 +05:30
Harshavardhana e12f52e2c6 Enhancements to daily-sweeper routine to reduce CPU load (#8209)
- ListObjectsHeal should list only objects
  which need healing, not the entire namespace.
- DeleteObjects() to be used to delete 1000s of
  objects in bulk instead of serially.
2019-09-11 00:38:44 +05:30
Nitish Tiwari 432cb38dbd Add reference to MinIO operator in Kubernetes docs (#8098) 2019-09-10 11:37:31 -07:00
Yao Zongyou 18fedc67d5 friendly prompt for s3select MalformedXML error (#8171)
partly fix #7911
2019-09-09 21:33:27 -07:00
Aditya Manthramurthy a0456ce940 LDAP STS API (#8091)
Add LDAP based users-groups system

This change adds support to integrate an LDAP server for user
authentication. This works via a custom STS API for LDAP. Each user
accessing the MinIO who can be authenticated via LDAP receives
temporary credentials to access the MinIO server.

LDAP is enabled only over TLS.

User groups are also supported via LDAP. The administrator may
configure an LDAP search query to find the group attribute of a user -
this may correspond to any attribute in the LDAP tree (that the user
has access to view). One or more groups may be returned by such a
query.

A group is mapped to an IAM policy in the usual way, and the server
enforces a policy corresponding to all the groups and the user's own
mapped policy.

When LDAP is configured, the internal MinIO users system is disabled.
2019-09-10 04:42:29 +05:30
Harshavardhana 94e5cb7576 Migrate to go1.13 to avail all new features (#8203)
Read more https://blog.golang.org/go1.13
2019-09-08 16:44:15 -07:00
Klaus Post 33aec08e8c Clean up go. mod (#8197)
Golang proposes to clean up these and is fairly insisting about it ;)
2019-09-06 14:57:55 -07:00
Minio Trusted 5bde31d021 Update yaml files to latest version RELEASE.2019-09-05T23-24-38Z 2019-09-05 23:30:40 +00:00
Harshavardhana b52a3e523c Avoid using fastjson parser pool, move back to jsoniter (#8190)
It looks like from implementation point of view fastjson
parser pool doesn't behave the same way as expected
when dealing many `xl.json` from multiple disks.

The fastjson parser pool usage ends up returning incorrect
xl.json entries for checksums, with references pointing
to older entries. This led to the subtle bug where checksum
info is duplicated from a previous xl.json read of a different
file from different disk.
2019-09-06 04:21:27 +05:30
Praveen raj Mani 428836d4e1 Ignore "connection reset" errors while initializing the targets with queue store enabled (#8185)
Fixes #8178
2019-09-05 13:55:48 -07:00
poornas 259a5d825b cache - fix corruption when client prematurely terminates request (#8155) 2019-09-05 23:33:32 +05:30
poornas 29f64355ce Allow caching on single PutObject (#8100) 2019-09-05 19:50:16 +05:30
Nitish Tiwari 496fba3e9a Return 200 OK for liveness checks while distributed cluster starts (#8176)
With this PR, liveness check responds with 200 OK with "server-not-
initialized" header while objectLayer gets initialized. The header
is removed as objectLayer is initialized. This is to allow
MinIO distributed cluster to get started when running on an
orchestration platforms like Docker Swarm.

This PR also updates sample Swarm yaml files to use correct values
for healthcheck fields.

Fixes #8140
2019-09-05 14:50:56 +05:30
Andreas Auernhammer 810a44e951 KMS Admin-API: add route and handler for KMS key info (#7955)
This commit adds an admin API route and handler for
requesting status information about a KMS key.

Therefore, the client specifies the KMS key ID (when
empty / not set the server takes the currently configured
default key-ID) and the server tries to perform a dummy encryption,
re-wrap and decryption operation. If all three succeed we know that
the server can access the KMS and has permissions to generate, re-wrap
and decrypt data keys (policy is set correctly).
2019-09-05 01:49:44 +05:30
Praveen raj Mani 341d61e3d8 Fix for web-uploads in federated mode (#8175)
Fixes #8173
2019-09-04 23:14:02 +05:30
poornas 8a71b0ec5a Add admin API to send console log messages (#7784)
Utilized by mc admin console command.
2019-09-03 23:40:48 +05:30
Harshavardhana 0772438125 Update orchestration docs to use ellipses syntax (#8153) 2019-09-01 10:02:15 -07:00
Anis Elleuch b3c19e2d4b storage: Expect empty param in REST requests (#8167)
Empty parameter was forgotten to be added to restQueries() function,
scanning with deep parameter wasn't working properly for distributed
setup.
2019-08-31 13:51:25 +05:30
Yao Zongyou ec9bfd3aef speed up the performance of s3select on csv (#7945) 2019-08-31 00:07:40 -07:00
Bala FA fa3546bb03 Add NetPerfInfo() API in madmin (#8112) 2019-08-31 08:27:53 +05:30
Harshavardhana 42e716a094 formatsToDrivesInfo should return drives with correct order (#8157)
This is a defensive change to avoid any future issues,
from this part of the code. New change also ensures
to populate UUID if present for the right disk.
2019-08-30 14:11:18 -07:00
Andreas Auernhammer 6b2ed0fc47 fix DownloadZIP for encrypted objects (#8159)
This commit fixes the web ZIP download handler for
encrypted objects. The decryption logic has moved into
`getObjectNInfo`. So trying to decrypt the (already decrypted)
content again in the ZIP handler obviously causes an error.

This commit fixes this by removing the decryption logic from the
the handler.

Fixes #7965
2019-08-30 10:46:09 -07:00
Harshavardhana 0cd0f6c255 Avoid error modification during IAM migration (#8156)
The underlying errors are important, for IAM
requirements and should wait appropriately at
the caller level, this allows for distributed
setups to run properly and not fail prematurely
during startup.

Also additionally fix the onlineDisk counting
2019-08-30 10:41:02 -07:00
poornas 4553db3872 mint: turn off progress for awscli sync test (#8158)
Bumping up to awscli 1.11.177 which supports --no-progress flag
2019-08-30 10:26:48 -07:00
Aditya Manthramurthy 847a3ea0a2 Add unit tests and refactor to improve coverage (#7617) 2019-08-29 13:53:27 -07:00
Aditya Manthramurthy 1f3d270de8 Fix delete policy routing (#8145) 2019-08-29 07:07:43 +05:30
Minio Trusted daa57e8e5a Update yaml files to latest version RELEASE.2019-08-29T00-25-01Z 2019-08-29 00:30:50 +00:00
Aditya Manthramurthy eb18c82976 Remove policy query param from being rejected for objects (#8144) 2019-08-28 16:58:40 -07:00
Krishna Srinivas 2ab0681c0c Do not ignore Lock()'s return value (#8142) 2019-08-28 16:12:57 -07:00
Harshavardhana 83d4c5763c Decouple ServiceUpdate to ServerUpdate to be more native (#8138)
The change now is to ensure that we take custom URL as
well for updating the deployment, this is required for
hotfix deliveries for certain deployments - other than
the community release.

This commit changes the previous work d65a2c6725
with newer set of requirements.

Also deprecates PeerUptime()
2019-08-28 15:04:43 -07:00
Ashish Kumar Sinha c6f86d35d3 Rectify Examples and README.MD (#8141) 2019-08-28 10:56:02 -07:00
Harshavardhana d65a2c6725 Implement cluster-wide in-place updates (#8070)
This PR is a breaking change and also deprecates
`minio update` command, from this release onwards
all users are advised to just use `mc admin update`
2019-08-27 11:37:47 -07:00
Harshavardhana 70136fb55b Look for network errors appropriately for RemoteStorageAPI (#8128)
net.Error is very unreliable in providing better error
handling, we need to ensure that we always have a fallback
option in case of network failures.

This fixes an important issue in our distributed server
setups when one of the servers is down, all deployments
out there are recommended to upgrade after this fix is
merged to ensure that availability is not lost.

Fixes #8127
Fixes #8016
Fixes #7964
2019-08-25 13:32:49 -07:00
Harshavardhana d6dd98e597 Avoid data-race in getDisksInfo call (#8126) 2019-08-23 17:03:15 -07:00
Krishna Srinivas c38ada1a26 write() to disk in 4MB blocks for better performance (#7888) 2019-08-23 15:36:46 -07:00
poornas 48bc3f1d53 Allow cached content to be encrypted (#8001)
If MINIO_CACHE_ENCRYPTION_MASTER_KEY is set,
automatically encrypt all cached content on disk.
2019-08-23 10:13:22 -07:00
Praveen raj Mani e211f6f52e Parallelize the DiskInfo calls in xl.StorageInfo() (#8115) 2019-08-22 20:02:40 -07:00
Harshavardhana f13f421e84 Allow CopyObject in pathStyle across federated instances (#8064)
Fixes #7976
2019-08-21 22:02:39 -10:00
Aditya Manthramurthy cd03bfb3cf Fix ignoring claims in list buckets call (#8118) 2019-08-21 19:20:11 -10:00
Harshavardhana add1b6cb6b Remove go mod cache due to buggy travis caching (#8113)
There are sporadic failures due to go-mod build
caches on travis, deprecate it since we moved to
using GOPROXY.
2019-08-21 13:38:52 -10:00
Dee Koder 4f3c9d7677 Update doc with Object Tagging not supported (#8116) 2019-08-21 16:10:59 -07:00
poornas 2e19619e79 browser: Avoid logging BucketNotEmpty error (#8110) 2019-08-21 10:01:46 -10:00
Minio Trusted de736f2864 Update yaml files to latest version RELEASE.2019-08-21T19-40-07Z 2019-08-21 19:46:01 +00:00
Harshavardhana 2fa98b1d6a Convert errAuthentication as AccessDenied appropriately (#8105)
Fixes #8062
2019-08-21 09:13:15 +05:30
kannappanr 99a4298938 Use a non-strict invalid bucket name check in Get and Delete object (#8073) 2019-08-20 17:40:52 -10:00
Harshavardhana 069badc7e9 Allow CopyObjectPart to work in federated setups (#8066)
Fixes #8065
2019-08-20 07:19:22 -10:00
Harshavardhana c601cb2f1e Add listBucketObjectsVersions implementation (#8093)
This API implementation simply behaves like listObjects()
but returns back single version for each object, this
implementation should be considered dummy it is only
meant for some applications which rely on this.
2019-08-19 11:02:54 -10:00
Harshavardhana 9ca7470ccc Avoid using jsoniter, move to fastjson (#8063)
This is to avoid using unsafe.Pointer type
code dependency for MinIO, this causes
crashes on ARM64 platforms

Refer #8005 collection of runtime crashes due
to unsafe.Pointer usage incorrectly. We have
seen issues like this before when using
jsoniter library in the past.

This PR hopes to fix this using fastjson
2019-08-19 08:35:52 -10:00
Harshavardhana b3ca304c01 Avoid excessive listing attempts in the daily sweep (#8081)
Add better dynamic timeouts for locks, also
add jitters before launching daily sweep to ensure
that not all the servers in distributed setup
are not trying to hold locks to begin the sweep
round.

Also, add enough delay for incoming requests based
on totalSetCount*totalDriveCount.

A possible fix for #8071
2019-08-19 08:22:32 -10:00
Bala FA 60f52f461f add network read performance collection support. (#8038)
ReST API on /minio/admin/v1/performance?perfType=net[?size=N] 
returns

```
{
  "PEER-1": [
             {
	       "addr": ADDR,
	       "readPerf": DURATION,
	       "error": ERROR,
	     },
	     ...
	   ],
  ...
  ...
  "PEER-N": [
             {
	       "addr": ADDR,
	       "readPerf": DURATION,
	       "error": ERROR,
	     },
	     ...
	   ]
}
```
2019-08-19 08:26:32 +05:30
Nitish Tiwari e5fb6294a7 Remove healthcheck script for Docker image (#8095)
There are multiple possibilities for running MinIO within
a container e.g. configurable address, non-root user etc.
This makes it difficult to identify actual IP / Port to
use to check healthcheck status from within a container.

It is simpler to use external healthcheck mechanisms
like healthcheck command in docker-compose to check
for MinIO health status. This is similar to how checks
work in Kubernetes as well.

This PR removes the healthcheck script used inside
Docker container and ad documentation on how to
use docker-compose based healthcheck mechanism.
2019-08-17 12:44:04 -07:00
Harshavardhana a15bb19d37 Allow audit logging to work while tracing (#8077)
It is observed that when `mc admin trace` is being
used due to ResponseWriter wrapper, we loose information
about statusCode,statusText for audit logging.

This PR fixes this behavior
2019-08-15 16:17:46 -07:00
poornas 7bf1caa0fe Fix broken link to go install docs (#8090) 2019-08-15 16:00:50 -07:00
Harshavardhana 6e7962bf35 Return if paths are empty in DeleteFileBulk (#8085)
This avoids a network call, also fixes an issue
when empty paths are passed the underlying call
fails with "405 Method Not Allowed".

This is reproducible when you are deleting a
non-existent object.

Fixes #8083
2019-08-15 13:15:49 -07:00
Aditya Manthramurthy 825e29f301 Check if user or group is disabled when evaluating policy (#8078) 2019-08-14 16:59:16 -07:00
Harshavardhana cebeca3075 Fix multi-user doc to reflect the latest change 2019-08-14 14:12:23 -07:00
Minio Trusted 26640033b0 Update yaml files to latest version RELEASE.2019-08-14T20-37-41Z 2019-08-14 20:42:16 +00:00
260 changed files with 7825 additions and 4663 deletions
+2
View File
@@ -0,0 +1,2 @@
.git
.github
-1
View File
@@ -22,4 +22,3 @@ prime/
stage/ stage/
.sia_temp/ .sia_temp/
config.json config.json
dockerscripts/healthcheck
+2 -15
View File
@@ -26,16 +26,7 @@ matrix:
- ARCH=x86_64 - ARCH=x86_64
- CGO_ENABLED=0 - CGO_ENABLED=0
- GO111MODULE=on - GO111MODULE=on
- GOPROXY=https://proxy.golang.org go: 1.13.x
# Enable build cache
# https://restic.net/blog/2018-09-02/travis-build-cache
cache:
directories:
- $HOME/.cache/go-build
- $HOME/gopath/pkg/mod
- $HOME/go/pkg/mod
go: 1.12.5
script: script:
- make - make
- diff -au <(gofmt -s -d cmd) <(printf "") - diff -au <(gofmt -s -d cmd) <(printf "")
@@ -53,8 +44,7 @@ matrix:
- ARCH=x86_64 - ARCH=x86_64
- CGO_ENABLED=0 - CGO_ENABLED=0
- GO111MODULE=on - GO111MODULE=on
- GOPROXY=https://proxy.golang.org go: 1.13.x
go: 1.12.5
script: script:
- go build --ldflags="$(go run buildscripts/gen-ldflags.go)" -o %GOPATH%\bin\minio.exe - go build --ldflags="$(go run buildscripts/gen-ldflags.go)" -o %GOPATH%\bin\minio.exe
- bash buildscripts/go-coverage.sh - bash buildscripts/go-coverage.sh
@@ -66,6 +56,3 @@ before_script:
before_install: before_install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then nvm install 11.10.1 ; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then nvm install 11.10.1 ; fi
after_success:
- bash <(curl -s https://codecov.io/bash)
+3 -6
View File
@@ -1,16 +1,16 @@
FROM golang:1.12-alpine FROM golang:1.13-alpine
LABEL maintainer="MinIO Inc <dev@min.io>" LABEL maintainer="MinIO Inc <dev@min.io>"
ENV GOPATH /go ENV GOPATH /go
ENV CGO_ENABLED 0 ENV CGO_ENABLED 0
ENV GO111MODULE on ENV GO111MODULE on
ENV GOPROXY https://proxy.golang.org
RUN \ RUN \
apk add --no-cache git && \ apk add --no-cache git && \
git clone https://github.com/minio/minio && cd minio && \ git clone https://github.com/minio/minio && cd minio && \
go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" && \ go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
cd dockerscripts; go build -tags kqueue -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go
FROM alpine:3.9 FROM alpine:3.9
@@ -22,7 +22,6 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
EXPOSE 9000 EXPOSE 9000
COPY --from=0 /go/bin/minio /usr/bin/minio COPY --from=0 /go/bin/minio /usr/bin/minio
COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
COPY dockerscripts/docker-entrypoint.sh /usr/bin/ COPY dockerscripts/docker-entrypoint.sh /usr/bin/
RUN \ RUN \
@@ -33,6 +32,4 @@ ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
VOLUME ["/data"] VOLUME ["/data"]
HEALTHCHECK --interval=1m CMD healthcheck
CMD ["minio"] CMD ["minio"]
+2 -5
View File
@@ -2,7 +2,7 @@ FROM alpine:3.9
LABEL maintainer="MinIO Inc <dev@min.io>" LABEL maintainer="MinIO Inc <dev@min.io>"
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck /usr/bin/ COPY dockerscripts/docker-entrypoint.sh /usr/bin/
COPY minio /usr/bin/ COPY minio /usr/bin/
ENV MINIO_UPDATE off ENV MINIO_UPDATE off
@@ -14,8 +14,7 @@ RUN \
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \ apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
chmod +x /usr/bin/minio && \ chmod +x /usr/bin/minio && \
chmod +x /usr/bin/docker-entrypoint.sh && \ chmod +x /usr/bin/docker-entrypoint.sh
chmod +x /usr/bin/healthcheck
EXPOSE 9000 EXPOSE 9000
@@ -23,6 +22,4 @@ ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
VOLUME ["/data"] VOLUME ["/data"]
HEALTHCHECK --interval=1m CMD healthcheck
CMD ["minio"] CMD ["minio"]
+3 -8
View File
@@ -1,4 +1,4 @@
FROM golang:1.12-alpine FROM golang:1.13-alpine
ENV GOPATH /go ENV GOPATH /go
ENV CGO_ENABLED 0 ENV CGO_ENABLED 0
@@ -6,14 +6,12 @@ ENV GO111MODULE on
RUN \ RUN \
apk add --no-cache git && \ apk add --no-cache git && \
git clone https://github.com/minio/minio && cd minio/dockerscripts && \ git clone https://github.com/minio/minio
go build -tags kqueue -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go
FROM alpine:3.9 FROM alpine:3.9
LABEL maintainer="MinIO Inc <dev@min.io>" LABEL maintainer="MinIO Inc <dev@min.io>"
COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
COPY dockerscripts/docker-entrypoint.sh /usr/bin/ COPY dockerscripts/docker-entrypoint.sh /usr/bin/
ENV MINIO_UPDATE off ENV MINIO_UPDATE off
@@ -26,8 +24,7 @@ RUN \
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
curl https://dl.min.io/server/minio/release/linux-amd64/minio > /usr/bin/minio && \ curl https://dl.min.io/server/minio/release/linux-amd64/minio > /usr/bin/minio && \
chmod +x /usr/bin/minio && \ chmod +x /usr/bin/minio && \
chmod +x /usr/bin/docker-entrypoint.sh && \ chmod +x /usr/bin/docker-entrypoint.sh
chmod +x /usr/bin/healthcheck
EXPOSE 9000 EXPOSE 9000
@@ -35,6 +32,4 @@ ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
VOLUME ["/data"] VOLUME ["/data"]
HEALTHCHECK --interval=1m CMD healthcheck
CMD ["minio"] CMD ["minio"]
+1 -2
View File
@@ -1,14 +1,13 @@
#------------------------------------------------------------- #-------------------------------------------------------------
# Stage 1: Build and Unit tests # Stage 1: Build and Unit tests
#------------------------------------------------------------- #-------------------------------------------------------------
FROM golang:1.12 FROM golang:1.13
COPY . /go/src/github.com/minio/minio COPY . /go/src/github.com/minio/minio
WORKDIR /go/src/github.com/minio/minio WORKDIR /go/src/github.com/minio/minio
RUN apt-get update && apt-get install -y jq RUN apt-get update && apt-get install -y jq
ENV GO111MODULE=on ENV GO111MODULE=on
ENV GOPROXY=https://proxy.golang.org
RUN git config --global http.cookiefile /gitcookie/.gitcookie RUN git config --global http.cookiefile /gitcookie/.gitcookie
-37
View File
@@ -1,37 +0,0 @@
# For maintainers only
### Setup your minio GitHub Repository
Fork [minio upstream](https://github.com/minio/minio/fork) source repository to your own personal repository.
```bash
$ mkdir -p $GOPATH/src/github.com/minio
$ cd $GOPATH/src/github.com/minio
$ git clone https://github.com/$USER_ID/minio
$
```
``minio`` uses [govendor](https://github.com/kardianos/govendor) for its dependency management.
### To manage dependencies
#### Add new dependencies
- Run `go get foo/bar`
- Edit your code to import foo/bar
- Run `govendor add foo/bar` from top-level directory
#### Remove dependencies
- Run `govendor remove foo/bar`
#### Update dependencies
- Run `govendor remove +vendor`
- Run to update the dependent package `go get -u foo/bar`
- Run `govendor add +external`
### Making new releases
`minio` doesn't follow semantic versioning style, `minio` instead uses the release date and time as the release versions.
`make release` will generate new binary into `release` directory.
+30 -27
View File
@@ -2,13 +2,12 @@ PWD := $(shell pwd)
GOPATH := $(shell go env GOPATH) GOPATH := $(shell go env GOPATH)
LDFLAGS := $(shell go run buildscripts/gen-ldflags.go) LDFLAGS := $(shell go run buildscripts/gen-ldflags.go)
GOARCH := $(shell go env GOARCH)
GOOS := $(shell go env GOOS) GOOS := $(shell go env GOOS)
GOOSALT ?= 'linux'
ifeq ($(GOOS),'darwin')
GOOSALT = 'mac'
endif
TAG ?= $(USER) VERSION ?= $(shell git describe --tags)
TAG ?= "minio/minio:$(VERSION)"
BUILD_LDFLAGS := '$(LDFLAGS)' BUILD_LDFLAGS := '$(LDFLAGS)'
all: build all: build
@@ -19,9 +18,13 @@ checks:
getdeps: getdeps:
@mkdir -p ${GOPATH}/bin @mkdir -p ${GOPATH}/bin
@which golint 1>/dev/null || (echo "Installing golint" && go get -u golang.org/x/lint/golint) @which golint 1>/dev/null || (echo "Installing golint" && GO111MODULE=off go get -u golang.org/x/lint/golint)
@which staticcheck 1>/dev/null || (echo "Installing staticcheck" && wget --quiet -O ${GOPATH}/bin/staticcheck https://github.com/dominikh/go-tools/releases/download/2019.1/staticcheck_${GOOS}_amd64 && chmod +x ${GOPATH}/bin/staticcheck) ifeq ($(GOARCH),s390x)
@which misspell 1>/dev/null || (echo "Installing misspell" && wget --quiet https://github.com/client9/misspell/releases/download/v0.3.4/misspell_0.3.4_${GOOSALT}_64bit.tar.gz && tar xf misspell_0.3.4_${GOOSALT}_64bit.tar.gz && mv misspell ${GOPATH}/bin/misspell && chmod +x ${GOPATH}/bin/misspell && rm -f misspell_0.3.4_${GOOSALT}_64bit.tar.gz) @which staticcheck 1>/dev/null || (echo "Installing staticcheck" && GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck)
else
@which staticcheck 1>/dev/null || (echo "Installing staticcheck" && wget --quiet https://github.com/dominikh/go-tools/releases/download/2019.2.3/staticcheck_${GOOS}_${GOARCH}.tar.gz && tar xf staticcheck_${GOOS}_${GOARCH}.tar.gz && mv staticcheck/staticcheck ${GOPATH}/bin/staticcheck && chmod +x ${GOPATH}/bin/staticcheck && rm -f staticcheck_${GOOS}_${GOARCH}.tar.gz && rm -rf staticcheck)
endif
@which misspell 1>/dev/null || (echo "Installing misspell" && GO111MODULE=off go get -u github.com/client9/misspell/cmd/misspell)
crosscompile: crosscompile:
@(env bash $(PWD)/buildscripts/cross-compile.sh) @(env bash $(PWD)/buildscripts/cross-compile.sh)
@@ -29,36 +32,37 @@ crosscompile:
verifiers: getdeps vet fmt lint staticcheck spelling verifiers: getdeps vet fmt lint staticcheck spelling
vet: vet:
@echo "Running $@" @echo "Running $@ check"
@GOPROXY=https://proxy.golang.org GO111MODULE=on go vet github.com/minio/minio/... @GO111MODULE=on go vet github.com/minio/minio/...
fmt: fmt:
@echo "Running $@" @echo "Running $@ check"
@GOPROXY=https://proxy.golang.org GO111MODULE=on gofmt -d cmd/ @GO111MODULE=on gofmt -d cmd/
@GOPROXY=https://proxy.golang.org GO111MODULE=on gofmt -d pkg/ @GO111MODULE=on gofmt -d pkg/
lint: lint:
@echo "Running $@" @echo "Running $@ check"
@GOPROXY=https://proxy.golang.org GO111MODULE=on ${GOPATH}/bin/golint -set_exit_status github.com/minio/minio/cmd/... @GO111MODULE=on ${GOPATH}/bin/golint -set_exit_status github.com/minio/minio/cmd/...
@GOPROXY=https://proxy.golang.org GO111MODULE=on ${GOPATH}/bin/golint -set_exit_status github.com/minio/minio/pkg/... @GO111MODULE=on ${GOPATH}/bin/golint -set_exit_status github.com/minio/minio/pkg/...
staticcheck: staticcheck:
@echo "Running $@" @echo "Running $@ check"
@GOPROXY=https://proxy.golang.org GO111MODULE=on ${GOPATH}/bin/staticcheck github.com/minio/minio/cmd/... @GO111MODULE=on ${GOPATH}/bin/staticcheck github.com/minio/minio/cmd/...
@GOPROXY=https://proxy.golang.org GO111MODULE=on ${GOPATH}/bin/staticcheck github.com/minio/minio/pkg/... @GO111MODULE=on ${GOPATH}/bin/staticcheck github.com/minio/minio/pkg/...
spelling: spelling:
@GOPROXY=https://proxy.golang.org GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find cmd/` @echo "Running $@ check"
@GOPROXY=https://proxy.golang.org GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find pkg/` @GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find cmd/`
@GOPROXY=https://proxy.golang.org GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find docs/` @GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find pkg/`
@GOPROXY=https://proxy.golang.org GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find buildscripts/` @GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find docs/`
@GOPROXY=https://proxy.golang.org GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find dockerscripts/` @GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find buildscripts/`
@GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find dockerscripts/`
# Builds minio, runs the verifiers then runs the tests. # Builds minio, runs the verifiers then runs the tests.
check: test check: test
test: verifiers build test: verifiers build
@echo "Running unit tests" @echo "Running unit tests"
@GOPROXY=https://proxy.golang.org GO111MODULE=on CGO_ENABLED=0 go test -tags kqueue ./... 1>/dev/null @GO111MODULE=on CGO_ENABLED=0 go test -tags kqueue ./... 1>/dev/null
verify: build verify: build
@echo "Verifying build" @echo "Verifying build"
@@ -71,8 +75,7 @@ coverage: build
# Builds minio locally. # Builds minio locally.
build: checks build: checks
@echo "Building minio binary to './minio'" @echo "Building minio binary to './minio'"
@GOPROXY=https://proxy.golang.org GO111MODULE=on GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/minio 1>/dev/null @GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/minio 1>/dev/null
@GOPROXY=https://proxy.golang.org GO111MODULE=on GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/dockerscripts/healthcheck $(PWD)/dockerscripts/healthcheck.go 1>/dev/null
docker: build docker: build
@docker build -t $(TAG) . -f Dockerfile.dev @docker build -t $(TAG) . -f Dockerfile.dev
+18 -2
View File
@@ -5,7 +5,7 @@ MinIO is an object storage server released under Apache License v2.0. It is comp
MinIO server is light enough to be bundled with the application stack, similar to NodeJS, Redis and MySQL. MinIO server is light enough to be bundled with the application stack, similar to NodeJS, Redis and MySQL.
[1]: MinIO in its default mode is faster and does not calculate MD5Sum unless passed by client. This may lead to incompatibility with few S3 clients like s3ql that heavily depend on MD5Sum. For full compatibility with Amazon S3 API, start MinIO with `--compat` option. [1] MinIO in its default mode is faster and does not calculate MD5Sum unless passed by client. This may lead to incompatibility with some S3 clients like s3ql that heavily depend on MD5Sum. For such applications start MinIO with `--compat` option.
```sh ```sh
minio --compat server /data minio --compat server /data
``` ```
@@ -91,7 +91,7 @@ service minio start
Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.12](https://golang.org/dl/#stable) Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.12](https://golang.org/dl/#stable)
```sh ```sh
GOPROXY=https://proxy.golang.org GO111MODULE=on go get github.com/minio/minio GO111MODULE=on go get github.com/minio/minio
``` ```
## Allow port access for Firewalls ## Allow port access for Firewalls
@@ -162,6 +162,22 @@ When deployed on a single drive, MinIO server lets clients access any pre-existi
The above statement is also valid for all gateway backends. The above statement is also valid for all gateway backends.
## Upgrading MinIO
MinIO server supports rolling upgrades, i.e. you can update one MinIO instance at a time in a distributed cluster. This allows upgrades with no downtime. Upgrades can be done manually by replacing the binary with the latest release and restarting all servers in a rolling fashion. However, we recommend all our users to use [`mc admin update`](https://docs.min.io/docs/minio-admin-complete-guide.html#update) from the client. This will update all the nodes in the cluster and restart them, as shown in the following command from the MinIO client (mc):
```
mc admin update <minio alias, e.g., myminio>
```
**Important things to remember during upgrades**:
- `mc admin update` will only work if the user running MinIO has write access to the parent directory where the binary is located, for example if the current binary is at `/usr/local/bin/minio`, you would need write access to `/usr/local/bin`.
- In the case of federated setups `mc admin update` should be run against each cluster individually. Avoid updating `mc` until all clusters have been updated.
- If you are updating the server it is always recommended (unless explicitly mentioned in MinIO server release notes), to update `mc` once all the servers have been upgraded using `mc update`.
- `mc admin update` is disabled in docker/container environments, container environments provide their own mechanisms for updating running containers.
- If you are using Vault as KMS with MinIO, ensure you have followed the Vault upgrade procedure outlined here: https://www.vaultproject.io/docs/upgrading/index.html
- If you are using etcd with MinIO for the federation, ensure you have followed the etcd upgrade procedure outlined here: https://github.com/etcd-io/etcd/blob/master/Documentation/upgrades/upgrading-etcd.md
## Explore Further ## Explore Further
- [MinIO Erasure Code QuickStart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide) - [MinIO Erasure Code QuickStart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
- [Use `mc` with MinIO Server](https://docs.min.io/docs/minio-client-quickstart-guide) - [Use `mc` with MinIO Server](https://docs.min.io/docs/minio-client-quickstart-guide)
+1 -1
View File
@@ -75,7 +75,7 @@ service minio start
## 使用源码安装 ## 使用源码安装
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://docs.min.io/docs/how-to-install-golang). 采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://golang.org/doc/install).
```sh ```sh
go get -u github.com/minio/minio go get -u github.com/minio/minio
+1 -1
View File
@@ -13,7 +13,7 @@ nvm install stable
### Install `go-bindata` and `go-bindata-assetfs` ### Install `go-bindata` and `go-bindata-assetfs`
If you do not have a working Golang environment, please follow [Install Golang](https://docs.min.io/docs/how-to-install-golang) If you do not have a working Golang environment, please follow [Install Golang](https://golang.org/doc/install)
```sh ```sh
go get github.com/go-bindata/go-bindata/go-bindata go get github.com/go-bindata/go-bindata/go-bindata
+2 -2
View File
@@ -21,7 +21,7 @@ _init() {
## Minimum required versions for build dependencies ## Minimum required versions for build dependencies
GIT_VERSION="1.0" GIT_VERSION="1.0"
GO_VERSION="1.12" GO_VERSION="1.13"
OSX_VERSION="10.8" OSX_VERSION="10.8"
KNAME=$(uname -s) KNAME=$(uname -s)
ARCH=$(uname -m) ARCH=$(uname -m)
@@ -119,7 +119,7 @@ assert_is_supported_os() {
assert_check_golang_env() { assert_check_golang_env() {
if ! which go >/dev/null 2>&1; then if ! which go >/dev/null 2>&1; then
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document at https://docs.min.io/docs/how-to-install-golang" echo "Cannot find go binary in your PATH configuration, please refer to Go installation document at https://golang.org/doc/install"
exit 1 exit 1
fi fi
-1
View File
@@ -23,7 +23,6 @@ function _build() {
export GOOS=$os export GOOS=$os
export GOARCH=$arch export GOARCH=$arch
export GO111MODULE=on export GO111MODULE=on
export GOPROXY=https://proxy.golang.org
go build -tags kqueue -o /dev/null go build -tags kqueue -o /dev/null
} }
+1 -1
View File
@@ -2,4 +2,4 @@
set -e set -e
GOPROXY=https://proxy.golang.org GO111MODULE=on CGO_ENABLED=0 go test -v -coverprofile=coverage.txt -covermode=atomic ./... GO111MODULE=on CGO_ENABLED=0 go test -v -coverprofile=coverage.txt -covermode=atomic ./...
-1
View File
@@ -33,7 +33,6 @@ export ACCESS_KEY="minio"
export SECRET_KEY="minio123" export SECRET_KEY="minio123"
export ENABLE_HTTPS=0 export ENABLE_HTTPS=0
export GO111MODULE=on export GO111MODULE=on
export GOPROXY=https://proxy.golang.org
MINIO_CONFIG_DIR="$WORK_DIR/.minio" MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" ) MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" )
+401 -296
View File
@@ -1,5 +1,5 @@
/* /*
* MinIO Cloud Storage, (C) 2016, 2017, 2018, 2019 MinIO, Inc. * MinIO Cloud Storage, (C) 2016-2019 MinIO, Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,28 +17,29 @@
package cmd package cmd
import ( import (
"bytes"
"context" "context"
"encoding/base64" "crypto/subtle"
"encoding/json" "encoding/json"
"errors" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url"
"os" "os"
"path"
"runtime"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
humanize "github.com/dustin/go-humanize"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/cpu" "github.com/minio/minio/pkg/cpu"
"github.com/minio/minio/pkg/disk"
"github.com/minio/minio/pkg/handlers" "github.com/minio/minio/pkg/handlers"
iampolicy "github.com/minio/minio/pkg/iam/policy" iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
@@ -50,6 +51,7 @@ import (
const ( const (
maxEConfigJSONSize = 262272 maxEConfigJSONSize = 262272
defaultNetPerfSize = 100 * humanize.MiByte
) )
// Type-safe query params. // Type-safe query params.
@@ -64,107 +66,139 @@ const (
mgmtForceStop = "forceStop" mgmtForceStop = "forceStop"
) )
var ( func updateServer(updateURL, sha256Hex string, latestReleaseTime time.Time) (us madmin.ServerUpdateStatus, err error) {
// This struct literal represents the Admin API version that minioMode := getMinioMode()
// the server uses. // No inputs provided we should try to update using the default URL.
adminAPIVersionInfo = madmin.AdminAPIVersionInfo{ if updateURL == "" && sha256Hex == "" && latestReleaseTime.IsZero() {
Version: "1", var updateMsg string
updateMsg, sha256Hex, _, latestReleaseTime, err = getUpdateInfo(updateTimeout, minioMode)
if err != nil {
return us, err
}
if updateMsg == "" {
us.CurrentVersion = Version
us.UpdatedVersion = Version
return us, nil
}
if runtime.GOOS == "windows" {
updateURL = minioReleaseURL + "minio.exe"
} else {
updateURL = minioReleaseURL + "minio"
}
}
if err = doUpdate(updateURL, sha256Hex, minioMode); err != nil {
return us, err
}
us.CurrentVersion = Version
us.UpdatedVersion = latestReleaseTime.Format(minioReleaseTagTimeLayout)
return us, nil
} }
)
// VersionHandler - GET /minio/admin/version // ServerUpdateHandler - POST /minio/admin/v1/update?updateURL={updateURL}
// ----------- // ----------
// Returns Administration API version // updates all minio servers and restarts them gracefully.
func (a adminAPIHandlers) VersionHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "Version") ctx := newContext(r, w, "ServerUpdate")
objectAPI := validateAdminReq(ctx, w, r) objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil { if objectAPI == nil {
return return
} }
jsonBytes, err := json.Marshal(adminAPIVersionInfo) if globalInplaceUpdateDisabled {
// if MINIO_UPDATE=off - inplace update is disabled, mostly
// in containers.
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
vars := mux.Vars(r)
updateURL := vars[peerRESTUpdateURL]
mode := getMinioMode()
var sha256Hex string
var latestReleaseTime time.Time
if updateURL != "" {
u, err := url.Parse(updateURL)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
writeSuccessResponseJSON(w, jsonBytes) content, err := downloadReleaseURL(updateURL, updateTimeout, mode)
} if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
// ServiceStatusHandler - GET /minio/admin/v1/service
// ----------
// Returns server version and uptime.
func (a adminAPIHandlers) ServiceStatusHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ServiceStatus")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return return
} }
// Fetch server version sha256Hex, latestReleaseTime, err = parseReleaseData(content)
serverVersion := madmin.ServerVersion{ if err != nil {
Version: Version, writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
CommitID: CommitID, return
} }
// Fetch uptimes from all peers and pick the latest. if runtime.GOOS == "windows" {
uptime := getPeerUptimes(globalNotificationSys.ServerInfo(ctx)) u.Path = path.Dir(u.Path) + "minio.exe"
} else {
u.Path = path.Dir(u.Path) + "minio"
}
// Create API response updateURL = u.String()
serverStatus := madmin.ServiceStatus{ }
ServerVersion: serverVersion,
Uptime: uptime, for _, nerr := range globalNotificationSys.ServerUpdate(updateURL, sha256Hex, latestReleaseTime) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
updateStatus, err := updateServer(updateURL, sha256Hex, latestReleaseTime)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
} }
// Marshal API response // Marshal API response
jsonBytes, err := json.Marshal(serverStatus) jsonBytes, err := json.Marshal(updateStatus)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
// Reply with storage information (across nodes in a
// distributed setup) as json.
writeSuccessResponseJSON(w, jsonBytes) writeSuccessResponseJSON(w, jsonBytes)
if updateStatus.CurrentVersion != updateStatus.UpdatedVersion {
// We did upgrade - restart all services.
globalServiceSignalCh <- serviceRestart
}
} }
// ServiceStopNRestartHandler - POST /minio/admin/v1/service // ServiceActionHandler - POST /minio/admin/v1/service?action={action}
// Body: {"action": <restart-action>}
// ---------- // ----------
// Restarts/Stops minio server gracefully. In a distributed setup, // restarts/stops minio server gracefully. In a distributed setup,
// restarts all the servers in the cluster. func (a adminAPIHandlers) ServiceActionHandler(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) ServiceStopNRestartHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "ServiceAction")
ctx := newContext(r, w, "ServiceStopNRestart")
vars := mux.Vars(r)
action := vars["action"]
objectAPI := validateAdminReq(ctx, w, r) objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil { if objectAPI == nil {
return return
} }
var sa madmin.ServiceAction
err := json.NewDecoder(r.Body).Decode(&sa)
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrRequestBodyParse), r.URL)
return
}
var serviceSig serviceSignal var serviceSig serviceSignal
switch sa.Action { switch madmin.ServiceAction(action) {
case madmin.ServiceActionValueRestart: case madmin.ServiceActionRestart:
serviceSig = serviceRestart serviceSig = serviceRestart
case madmin.ServiceActionValueStop: case madmin.ServiceActionStop:
serviceSig = serviceStop serviceSig = serviceStop
default: default:
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action))
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
logger.LogIf(ctx, errors.New("Invalid service action received"))
return return
} }
// Reply to the client before restarting minio server.
writeSuccessResponseHeadersOnly(w)
// Notify all other MinIO peers signal service. // Notify all other MinIO peers signal service.
for _, nerr := range globalNotificationSys.SignalService(serviceSig) { for _, nerr := range globalNotificationSys.SignalService(serviceSig) {
if nerr.Err != nil { if nerr.Err != nil {
@@ -173,6 +207,9 @@ func (a adminAPIHandlers) ServiceStopNRestartHandler(w http.ResponseWriter, r *h
} }
} }
// Reply to the client before restarting, stopping MinIO server.
writeSuccessResponseHeadersOnly(w)
globalServiceSignalCh <- serviceSig globalServiceSignalCh <- serviceSig
} }
@@ -275,15 +312,6 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
writeSuccessResponseJSON(w, jsonBytes) writeSuccessResponseJSON(w, jsonBytes)
} }
// ServerDrivesPerfInfo holds information about address, performance
// of all drives on one server. It also reports any errors if encountered
// while trying to reach this server.
type ServerDrivesPerfInfo struct {
Addr string `json:"addr"`
Error string `json:"error,omitempty"`
Perf []disk.Performance `json:"perf"`
}
// ServerCPULoadInfo holds informantion about cpu utilization // ServerCPULoadInfo holds informantion about cpu utilization
// of one minio node. It also reports any errors if encountered // of one minio node. It also reports any errors if encountered
// while trying to reach this server. // while trying to reach this server.
@@ -304,6 +332,13 @@ type ServerMemUsageInfo struct {
HistoricUsage []mem.Usage `json:"historicUsage"` HistoricUsage []mem.Usage `json:"historicUsage"`
} }
// ServerNetReadPerfInfo network read performance information.
type ServerNetReadPerfInfo struct {
Addr string `json:"addr"`
ReadThroughput uint64 `json:"readThroughput"`
Error string `json:"error,omitempty"`
}
// PerfInfoHandler - GET /minio/admin/v1/performance?perfType={perfType} // PerfInfoHandler - GET /minio/admin/v1/performance?perfType={perfType}
// ---------- // ----------
// Get all performance information based on input type // Get all performance information based on input type
@@ -318,17 +353,63 @@ func (a adminAPIHandlers) PerfInfoHandler(w http.ResponseWriter, r *http.Request
vars := mux.Vars(r) vars := mux.Vars(r)
switch perfType := vars["perfType"]; perfType { switch perfType := vars["perfType"]; perfType {
case "drive": case "net":
info := objectAPI.StorageInfo(ctx) var size int64 = defaultNetPerfSize
if !(info.Backend.Type == BackendFS || info.Backend.Type == BackendErasure) { if sizeStr, found := vars["size"]; found {
var err error
if size, err = strconv.ParseInt(sizeStr, 10, 64); err != nil || size < 0 {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
return
}
}
if !globalIsDistXL {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return return
} }
addr := r.Host
if globalIsDistXL {
addr = GetLocalPeer(globalEndpoints)
}
infos := map[string][]ServerNetReadPerfInfo{}
infos[addr] = globalNotificationSys.NetReadPerfInfo(size)
for peer, info := range globalNotificationSys.CollectNetPerfInfo(size) {
infos[peer] = info
}
// Marshal API response
jsonBytes, err := json.Marshal(infos)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Reply with performance information (across nodes in a
// distributed setup) as json.
writeSuccessResponseJSON(w, jsonBytes)
case "drive":
// Drive Perf is only implemented for Erasure coded backends
if !globalIsXL {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
var size int64 = madmin.DefaultDrivePerfSize
if sizeStr, found := vars["size"]; found {
var err error
if size, err = strconv.ParseInt(sizeStr, 10, 64); err != nil || size <= 0 {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
return
}
}
// Get drive performance details from local server's drive(s) // Get drive performance details from local server's drive(s)
dp := localEndpointsDrivePerf(globalEndpoints, r) dp := getLocalDrivesPerf(globalEndpoints, size, r)
// Notify all other MinIO peers to report drive performance numbers // Notify all other MinIO peers to report drive performance numbers
dps := globalNotificationSys.DrivePerfInfo() dps := globalNotificationSys.DrivePerfInfo(size)
dps = append(dps, dp) dps = append(dps, dp)
// Marshal API response // Marshal API response
@@ -343,7 +424,7 @@ func (a adminAPIHandlers) PerfInfoHandler(w http.ResponseWriter, r *http.Request
writeSuccessResponseJSON(w, jsonBytes) writeSuccessResponseJSON(w, jsonBytes)
case "cpu": case "cpu":
// Get CPU load details from local server's cpu(s) // Get CPU load details from local server's cpu(s)
cpu := localEndpointsCPULoad(globalEndpoints, r) cpu := getLocalCPULoad(globalEndpoints, r)
// Notify all other MinIO peers to report cpu load numbers // Notify all other MinIO peers to report cpu load numbers
cpus := globalNotificationSys.CPULoadInfo() cpus := globalNotificationSys.CPULoadInfo()
cpus = append(cpus, cpu) cpus = append(cpus, cpu)
@@ -360,7 +441,7 @@ func (a adminAPIHandlers) PerfInfoHandler(w http.ResponseWriter, r *http.Request
writeSuccessResponseJSON(w, jsonBytes) writeSuccessResponseJSON(w, jsonBytes)
case "mem": case "mem":
// Get mem usage details from local server(s) // Get mem usage details from local server(s)
m := localEndpointsMemUsage(globalEndpoints, r) m := getLocalMemUsage(globalEndpoints, r)
// Notify all other MinIO peers to report mem usage numbers // Notify all other MinIO peers to report mem usage numbers
mems := globalNotificationSys.MemUsageInfo() mems := globalNotificationSys.MemUsageInfo()
mems = append(mems, m) mems = append(mems, m)
@@ -391,7 +472,6 @@ func newLockEntry(l lockRequesterInfo, resource, server string) *madmin.LockEntr
} }
func topLockEntries(peerLocks []*PeerLocks) madmin.LockEntries { func topLockEntries(peerLocks []*PeerLocks) madmin.LockEntries {
const listCount int = 10
entryMap := make(map[string]*madmin.LockEntry) entryMap := make(map[string]*madmin.LockEntry)
for _, peerLock := range peerLocks { for _, peerLock := range peerLocks {
if peerLock == nil { if peerLock == nil {
@@ -412,6 +492,7 @@ func topLockEntries(peerLocks []*PeerLocks) madmin.LockEntries {
lockEntries = append(lockEntries, *v) lockEntries = append(lockEntries, *v)
} }
sort.Sort(lockEntries) sort.Sort(lockEntries)
const listCount int = 10
if len(lockEntries) > listCount { if len(lockEntries) > listCount {
lockEntries = lockEntries[:listCount] lockEntries = lockEntries[:listCount]
} }
@@ -569,45 +650,60 @@ func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *htt
} }
} }
type healInitParams struct {
bucket, objPrefix string
hs madmin.HealOpts
clientToken string
forceStart, forceStop bool
}
// extractHealInitParams - Validates params for heal init API. // extractHealInitParams - Validates params for heal init API.
func extractHealInitParams(r *http.Request) (bucket, objPrefix string, func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reader) (hip healInitParams, err APIErrorCode) {
hs madmin.HealOpts, clientToken string, forceStart bool, forceStop bool, hip.bucket = vars[string(mgmtBucket)]
err APIErrorCode) { hip.objPrefix = vars[string(mgmtPrefix)]
vars := mux.Vars(r) if hip.bucket == "" {
bucket = vars[string(mgmtBucket)] if hip.objPrefix != "" {
objPrefix = vars[string(mgmtPrefix)]
if bucket == "" {
if objPrefix != "" {
// Bucket is required if object-prefix is given // Bucket is required if object-prefix is given
err = ErrHealMissingBucket err = ErrHealMissingBucket
return return
} }
} else if isReservedOrInvalidBucket(bucket, false) { } else if isReservedOrInvalidBucket(hip.bucket, false) {
err = ErrInvalidBucketName err = ErrInvalidBucketName
return return
} }
// empty prefix is valid. // empty prefix is valid.
if !IsValidObjectPrefix(objPrefix) { if !IsValidObjectPrefix(hip.objPrefix) {
err = ErrInvalidObjectName err = ErrInvalidObjectName
return return
} }
qParms := r.URL.Query()
if len(qParms[string(mgmtClientToken)]) > 0 { if len(qParms[string(mgmtClientToken)]) > 0 {
clientToken = qParms[string(mgmtClientToken)][0] hip.clientToken = qParms[string(mgmtClientToken)][0]
} }
if _, ok := qParms[string(mgmtForceStart)]; ok { if _, ok := qParms[string(mgmtForceStart)]; ok {
forceStart = true hip.forceStart = true
} }
if _, ok := qParms[string(mgmtForceStop)]; ok { if _, ok := qParms[string(mgmtForceStop)]; ok {
forceStop = true hip.forceStop = true
} }
// Invalid request conditions:
//
// Cannot have both forceStart and forceStop in the same
// request; If clientToken is provided, request can only be
// to continue receiving logs, so it cannot be start or
// stop;
if (hip.forceStart && hip.forceStop) ||
(hip.clientToken != "" && (hip.forceStart || hip.forceStop)) {
err = ErrInvalidRequest
return
}
// ignore body if clientToken is provided // ignore body if clientToken is provided
if clientToken == "" { if hip.clientToken == "" {
jerr := json.NewDecoder(r.Body).Decode(&hs) jerr := json.NewDecoder(r).Decode(&hip.hs)
if jerr != nil { if jerr != nil {
logger.LogIf(context.Background(), jerr) logger.LogIf(context.Background(), jerr)
err = ErrRequestBodyParse err = ErrRequestBodyParse
@@ -646,7 +742,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
bucket, objPrefix, hs, clientToken, forceStart, forceStop, errCode := extractHealInitParams(r) hip, errCode := extractHealInitParams(mux.Vars(r), r.URL.Query(), r.Body)
if errCode != ErrNone { if errCode != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(errCode), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(errCode), r.URL)
return return
@@ -720,8 +816,8 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
info := objectAPI.StorageInfo(ctx) info := objectAPI.StorageInfo(ctx)
numDisks := info.Backend.OfflineDisks + info.Backend.OnlineDisks numDisks := info.Backend.OfflineDisks + info.Backend.OnlineDisks
healPath := pathJoin(bucket, objPrefix) healPath := pathJoin(hip.bucket, hip.objPrefix)
if clientToken == "" && !forceStart && !forceStop { if hip.clientToken == "" && !hip.forceStart && !hip.forceStop {
nh, exists := globalAllHealState.getHealSequence(healPath) nh, exists := globalAllHealState.getHealSequence(healPath)
if exists && !nh.hasEnded() && len(nh.currentStatus.Items) > 0 { if exists && !nh.hasEnded() && len(nh.currentStatus.Items) > 0 {
b, err := json.Marshal(madmin.HealStartSuccess{ b, err := json.Marshal(madmin.HealStartSuccess{
@@ -740,11 +836,11 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
if clientToken != "" && !forceStart && !forceStop { if hip.clientToken != "" && !hip.forceStart && !hip.forceStop {
// Since clientToken is given, fetch heal status from running // Since clientToken is given, fetch heal status from running
// heal sequence. // heal sequence.
respBytes, errCode := globalAllHealState.PopHealStatusJSON( respBytes, errCode := globalAllHealState.PopHealStatusJSON(
healPath, clientToken) healPath, hip.clientToken)
if errCode != ErrNone { if errCode != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(errCode), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(errCode), r.URL)
} else { } else {
@@ -755,14 +851,14 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
respCh := make(chan healResp) respCh := make(chan healResp)
switch { switch {
case forceStop: case hip.forceStop:
go func() { go func() {
respBytes, apiErr := globalAllHealState.stopHealSequence(healPath) respBytes, apiErr := globalAllHealState.stopHealSequence(healPath)
hr := healResp{respBytes: respBytes, apiErr: apiErr} hr := healResp{respBytes: respBytes, apiErr: apiErr}
respCh <- hr respCh <- hr
}() }()
case clientToken == "": case hip.clientToken == "":
nh := newHealSequence(bucket, objPrefix, handlers.GetSourceIP(r), numDisks, hs, forceStart) nh := newHealSequence(hip.bucket, hip.objPrefix, handlers.GetSourceIP(r), numDisks, hip.hs, hip.forceStart)
go func() { go func() {
respBytes, apiErr, errMsg := globalAllHealState.LaunchNewHealSequence(nh) respBytes, apiErr, errMsg := globalAllHealState.LaunchNewHealSequence(nh)
hr := healResp{respBytes, apiErr, errMsg} hr := healResp{respBytes, apiErr, errMsg}
@@ -851,25 +947,6 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
writeSuccessResponseJSON(w, econfigData) writeSuccessResponseJSON(w, econfigData)
} }
// Disable tidwall json array notation in JSON key path so
// users can set json with a key as a number.
// In tidwall json, notify.webhook.0 = val means { "notify" : { "webhook" : [val] }}
// In MinIO, notify.webhook.0 = val means { "notify" : { "webhook" : {"0" : val}}}
func normalizeJSONKey(input string) (key string) {
subKeys := strings.Split(input, ".")
for i, k := range subKeys {
if i > 0 {
key += "."
}
if _, err := strconv.Atoi(k); err == nil {
key += ":" + k
} else {
key += k
}
}
return
}
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request) ObjectLayer { func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request) ObjectLayer {
// Get current object layer instance. // Get current object layer instance.
objectAPI := newObjectLayerFn() objectAPI := newObjectLayerFn()
@@ -888,59 +965,23 @@ func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Reques
return objectAPI return objectAPI
} }
// GetConfigHandler - GET /minio/admin/v1/config-keys // AdminError - is a generic error for all admin APIs.
// Get some keys in config.json of this minio setup. type AdminError struct {
func (a adminAPIHandlers) GetConfigKeysHandler(w http.ResponseWriter, r *http.Request) { Code string
ctx := newContext(r, w, "GetConfigKeysHandler") Message string
StatusCode int
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
} }
var keys []string func (ae AdminError) Error() string {
queries := r.URL.Query() return ae.Message
for k := range queries {
keys = append(keys, k)
} }
config, err := readServerConfig(ctx, objectAPI) // Admin API errors
if err != nil { const (
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) AdminUpdateUnexpectedFailure = "XMinioAdminUpdateUnexpectedFailure"
return AdminUpdateURLNotReachable = "XMinioAdminUpdateURLNotReachable"
} AdminUpdateApplyFailure = "XMinioAdminUpdateApplyFailure"
)
configData, err := json.Marshal(config)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
configStr := string(configData)
newConfigStr := `{}`
for _, key := range keys {
// sjson.Set does not return an error if key is empty
// we should check by ourselves here
if key == "" {
continue
}
val := gjson.Get(configStr, key)
if j, ierr := sjson.Set(newConfigStr, normalizeJSONKey(key), val.Value()); ierr == nil {
newConfigStr = j
}
}
password := config.GetCredential().SecretKey
econfigData, err := madmin.EncryptData(password, []byte(newConfigStr))
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, []byte(econfigData))
}
// toAdminAPIErrCode - converts errXLWriteQuorum error to admin API // toAdminAPIErrCode - converts errXLWriteQuorum error to admin API
// specific error. // specific error.
@@ -954,7 +995,21 @@ func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
} }
func toAdminAPIErr(ctx context.Context, err error) APIError { func toAdminAPIErr(ctx context.Context, err error) APIError {
return errorCodes.ToAPIErr(toAdminAPIErrCode(ctx, err)) if err == nil {
return noError
}
apiErr := errorCodes.ToAPIErr(toAdminAPIErrCode(ctx, err))
if apiErr.Code == "InternalError" {
switch e := err.(type) {
case AdminError:
apiErr = APIError{
Code: e.Code,
Description: e.Message,
HTTPStatusCode: e.StatusCode,
}
}
}
return apiErr
} }
// RemoveUser - DELETE /minio/admin/v1/remove-user?accessKey=<access_key> // RemoveUser - DELETE /minio/admin/v1/remove-user?accessKey=<access_key>
@@ -1126,7 +1181,12 @@ func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
return return
} }
groups := globalIAMSys.ListGroups() groups, err := globalIAMSys.ListGroups()
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
body, err := json.Marshal(groups) body, err := json.Marshal(groups)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
@@ -1269,6 +1329,25 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
} }
} }
// InfoCannedPolicy - GET /minio/admin/v1/info-canned-policy?name={policyName}
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "InfoCannedPolicy")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
data, err := globalIAMSys.InfoPolicy(mux.Vars(r)["name"])
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
w.Write(data)
w.(http.Flusher).Flush()
}
// ListCannedPolicies - GET /minio/admin/v1/list-canned-policies // ListCannedPolicies - GET /minio/admin/v1/list-canned-policies
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListCannedPolicies") ctx := newContext(r, w, "ListCannedPolicies")
@@ -1486,127 +1565,6 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
} }
func convertValueType(elem []byte, jsonType gjson.Type) (interface{}, error) {
str := string(elem)
switch jsonType {
case gjson.False, gjson.True:
return strconv.ParseBool(str)
case gjson.JSON:
return gjson.Parse(str).Value(), nil
case gjson.String:
return str, nil
case gjson.Number:
return strconv.ParseFloat(str, 64)
default:
return nil, nil
}
}
// SetConfigKeysHandler - PUT /minio/admin/v1/config-keys
func (a adminAPIHandlers) SetConfigKeysHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SetConfigKeysHandler")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
// Load config
configStruct, err := readServerConfig(ctx, objectAPI)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Convert config to json bytes
configBytes, err := json.Marshal(configStruct)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
configStr := string(configBytes)
queries := r.URL.Query()
password := globalServerConfig.GetCredential().SecretKey
// Set key values in the JSON config
for k := range queries {
// Decode encrypted data associated to the current key
encryptedElem, dErr := base64.StdEncoding.DecodeString(queries.Get(k))
if dErr != nil {
reqInfo := (&logger.ReqInfo{}).AppendTags("key", k)
ctx = logger.SetReqInfo(ctx, reqInfo)
logger.LogIf(ctx, dErr)
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), dErr.Error(), r.URL)
return
}
elem, dErr := madmin.DecryptData(password, bytes.NewBuffer([]byte(encryptedElem)))
if dErr != nil {
logger.LogIf(ctx, dErr)
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), dErr.Error(), r.URL)
return
}
// Calculate the type of the current key from the
// original config json
jsonFieldType := gjson.Get(configStr, k).Type
// Convert passed value to json filed type
val, cErr := convertValueType(elem, jsonFieldType)
if cErr != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), cErr.Error(), r.URL)
return
}
// Set the key/value in the new json document
if s, sErr := sjson.Set(configStr, normalizeJSONKey(k), val); sErr == nil {
configStr = s
}
}
configBytes = []byte(configStr)
// Validate config
var config serverConfig
if err = json.Unmarshal(configBytes, &config); err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
return
}
if err = config.Validate(); err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
return
}
if err = config.TestNotificationTargets(); err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
return
}
// If credentials for the server are provided via environment,
// then credentials in the provided configuration must match.
if globalIsEnvCreds {
if !globalServerConfig.GetCredential().Equal(config.Credential) {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminCredentialsMismatch), r.URL)
return
}
}
if err = saveServerConfig(ctx, objectAPI, &config); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Send success response
writeSuccessResponseHeadersOnly(w)
}
// Returns true if the trace.Info should be traced, // Returns true if the trace.Info should be traced,
// false if certain conditions are not met. // false if certain conditions are not met.
// - input entry is not of the type *trace.Info* // - input entry is not of the type *trace.Info*
@@ -1682,3 +1640,150 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
} }
// The handler sends console logs to the connected HTTP client.
func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ConsoleLog")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
node := r.URL.Query().Get("node")
// limit buffered console entries if client requested it.
limitStr := r.URL.Query().Get("limit")
limitLines, err := strconv.Atoi(limitStr)
if err != nil {
limitLines = 10
}
// Avoid reusing tcp connection if read timeout is hit
// This is needed to make r.Context().Done() work as
// expected in case of read timeout
w.Header().Add("Connection", "close")
w.Header().Set(xhttp.ContentType, "text/event-stream")
doneCh := make(chan struct{})
defer close(doneCh)
logCh := make(chan interface{}, 4000)
remoteHosts := getRemoteHosts(globalEndpoints)
peers, err := getRestClients(remoteHosts)
if err != nil {
return
}
globalConsoleSys.Subscribe(logCh, doneCh, node, limitLines, nil)
for _, peer := range peers {
if node == "" || strings.EqualFold(peer.host.Name, node) {
peer.ConsoleLog(logCh, doneCh)
}
}
enc := json.NewEncoder(w)
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
defer keepAliveTicker.Stop()
for {
select {
case entry := <-logCh:
log := entry.(madmin.LogInfo)
if log.SendLog(node) {
if err := enc.Encode(log); err != nil {
return
}
w.(http.Flusher).Flush()
}
case <-keepAliveTicker.C:
if _, err := w.Write([]byte(" ")); err != nil {
return
}
w.(http.Flusher).Flush()
case <-GlobalServiceDoneCh:
return
}
}
}
// KMSKeyStatusHandler - GET /minio/admin/v1/kms/key/status?key-id=<master-key-id>
func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "KMSKeyStatusHandler")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
if GlobalKMS == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
return
}
keyID := r.URL.Query().Get("key-id")
if keyID == "" {
keyID = globalKMSKeyID
}
var response = madmin.KMSKeyStatus{
KeyID: keyID,
}
kmsContext := crypto.Context{"MinIO admin API": "KMSKeyStatusHandler"} // Context for a test key operation
// 1. Generate a new key using the KMS.
key, sealedKey, err := GlobalKMS.GenerateKey(keyID, kmsContext)
if err != nil {
response.EncryptionErr = err.Error()
resp, err := json.Marshal(response)
if err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
return
}
writeSuccessResponseJSON(w, resp)
return
}
// 2. Check whether we can update / re-wrap the sealed key.
sealedKey, err = GlobalKMS.UpdateKey(keyID, sealedKey, kmsContext)
if err != nil {
response.UpdateErr = err.Error()
resp, err := json.Marshal(response)
if err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
return
}
writeSuccessResponseJSON(w, resp)
return
}
// 3. Verify that we can indeed decrypt the (encrypted) key
decryptedKey, err := GlobalKMS.UnsealKey(keyID, sealedKey, kmsContext)
if err != nil {
response.DecryptionErr = err.Error()
resp, err := json.Marshal(response)
if err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
return
}
writeSuccessResponseJSON(w, resp)
return
}
// 4. Compare generated key with decrypted key
if subtle.ConstantTimeCompare(key[:], decryptedKey[:]) != 1 {
response.DecryptionErr = "The generated and the decrypted data key do not match"
resp, err := json.Marshal(response)
if err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
return
}
writeSuccessResponseJSON(w, resp)
return
}
resp, err := json.Marshal(response)
if err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
return
}
writeSuccessResponseJSON(w, resp)
}
+111 -123
View File
@@ -1,5 +1,5 @@
/* /*
* MinIO Cloud Storage, (C) 2016, 2017, 2018 MinIO, Inc. * MinIO Cloud Storage, (C) 2016-2019 MinIO, Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import (
"strings" "strings"
"sync" "sync"
"testing" "testing"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
@@ -344,117 +345,35 @@ func initTestXLObjLayer() (ObjectLayer, []string, error) {
return objLayer, xlDirs, nil return objLayer, xlDirs, nil
} }
func TestAdminVersionHandler(t *testing.T) {
adminTestBed, err := prepareAdminXLTestBed()
if err != nil {
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
}
defer adminTestBed.TearDown()
req, err := newTestRequest("GET", "/minio/admin/version", 0, nil)
if err != nil {
t.Fatalf("Failed to construct request - %v", err)
}
cred := globalServerConfig.GetCredential()
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
if err != nil {
t.Fatalf("Failed to sign request - %v", err)
}
rec := httptest.NewRecorder()
adminTestBed.router.ServeHTTP(rec, req)
if http.StatusOK != rec.Code {
t.Errorf("Unexpected status code - got %d but expected %d",
rec.Code, http.StatusOK)
}
var result madmin.AdminAPIVersionInfo
err = json.NewDecoder(rec.Body).Decode(&result)
if err != nil {
t.Errorf("json parse err: %v", err)
}
if result != adminAPIVersionInfo {
t.Errorf("unexpected version: %v", result)
}
}
// cmdType - Represents different service subcomands like status, stop // cmdType - Represents different service subcomands like status, stop
// and restart. // and restart.
type cmdType int type cmdType int
const ( const (
statusCmd cmdType = iota restartCmd cmdType = iota
restartCmd
stopCmd stopCmd
setCreds
) )
// String - String representation for cmdType
func (c cmdType) String() string {
switch c {
case statusCmd:
return "status"
case restartCmd:
return "restart"
case stopCmd:
return "stop"
case setCreds:
return "set-credentials"
}
return ""
}
// apiMethod - Returns the HTTP method corresponding to the admin REST
// API for a given cmdType value.
func (c cmdType) apiMethod() string {
switch c {
case statusCmd:
return "GET"
case restartCmd:
return "POST"
case stopCmd:
return "POST"
case setCreds:
return "PUT"
}
return "GET"
}
// apiEndpoint - Return endpoint for each admin REST API mapped to a
// command here.
func (c cmdType) apiEndpoint() string {
switch c {
case statusCmd, restartCmd, stopCmd:
return "/minio/admin/v1/service"
case setCreds:
return "/minio/admin/v1/config/credential"
}
return ""
}
// toServiceSignal - Helper function that translates a given cmdType // toServiceSignal - Helper function that translates a given cmdType
// value to its corresponding serviceSignal value. // value to its corresponding serviceSignal value.
func (c cmdType) toServiceSignal() serviceSignal { func (c cmdType) toServiceSignal() serviceSignal {
switch c { switch c {
case statusCmd:
return serviceStatus
case restartCmd: case restartCmd:
return serviceRestart return serviceRestart
case stopCmd: case stopCmd:
return serviceStop return serviceStop
} }
return serviceStatus return serviceRestart
} }
func (c cmdType) toServiceActionValue() madmin.ServiceActionValue { func (c cmdType) toServiceAction() madmin.ServiceAction {
switch c { switch c {
case restartCmd: case restartCmd:
return madmin.ServiceActionValueRestart return madmin.ServiceActionRestart
case stopCmd: case stopCmd:
return madmin.ServiceActionValueStop return madmin.ServiceActionStop
} }
return madmin.ServiceActionValueStop return madmin.ServiceActionRestart
} }
// testServiceSignalReceiver - Helper function that simulates a // testServiceSignalReceiver - Helper function that simulates a
@@ -469,19 +388,15 @@ func testServiceSignalReceiver(cmd cmdType, t *testing.T) {
// getServiceCmdRequest - Constructs a management REST API request for service // getServiceCmdRequest - Constructs a management REST API request for service
// subcommands for a given cmdType value. // subcommands for a given cmdType value.
func getServiceCmdRequest(cmd cmdType, cred auth.Credentials, body []byte) (*http.Request, error) { func getServiceCmdRequest(cmd cmdType, cred auth.Credentials) (*http.Request, error) {
req, err := newTestRequest(cmd.apiMethod(), cmd.apiEndpoint(), 0, nil) queryVal := url.Values{}
queryVal.Set("action", string(cmd.toServiceAction()))
resource := "/minio/admin/v1/service?" + queryVal.Encode()
req, err := newTestRequest(http.MethodPost, resource, 0, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Set body
req.Body = ioutil.NopCloser(bytes.NewReader(body))
req.ContentLength = int64(len(body))
// Set sha-sum header
req.Header.Set("X-Amz-Content-Sha256", getSHA256Hash(body))
// management REST API uses signature V4 for authentication. // management REST API uses signature V4 for authentication.
err = signRequestV4(req, cred.AccessKey, cred.SecretKey) err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
if err != nil { if err != nil {
@@ -517,13 +432,7 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
} }
credentials := globalServerConfig.GetCredential() credentials := globalServerConfig.GetCredential()
body, err := json.Marshal(madmin.ServiceAction{ req, err := getServiceCmdRequest(cmd, credentials)
Action: cmd.toServiceActionValue()})
if err != nil {
t.Fatalf("JSONify error: %v", err)
}
req, err := getServiceCmdRequest(cmd, credentials, body)
if err != nil { if err != nil {
t.Fatalf("Failed to build service status request %v", err) t.Fatalf("Failed to build service status request %v", err)
} }
@@ -531,19 +440,6 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
adminTestBed.router.ServeHTTP(rec, req) adminTestBed.router.ServeHTTP(rec, req)
if cmd == statusCmd {
expectedInfo := madmin.ServiceStatus{
ServerVersion: madmin.ServerVersion{Version: Version, CommitID: CommitID},
}
receivedInfo := madmin.ServiceStatus{}
if jsonErr := json.Unmarshal(rec.Body.Bytes(), &receivedInfo); jsonErr != nil {
t.Errorf("Failed to unmarshal StorageInfo - %v", jsonErr)
}
if expectedInfo.ServerVersion != receivedInfo.ServerVersion {
t.Errorf("Expected storage info and received storage info differ, %v %v", expectedInfo, receivedInfo)
}
}
if rec.Code != http.StatusOK { if rec.Code != http.StatusOK {
resp, _ := ioutil.ReadAll(rec.Body) resp, _ := ioutil.ReadAll(rec.Body)
t.Errorf("Expected to receive %d status code but received %d. Body (%s)", t.Errorf("Expected to receive %d status code but received %d. Body (%s)",
@@ -554,11 +450,6 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
wg.Wait() wg.Wait()
} }
// Test for service status management REST API.
func TestServiceStatusHandler(t *testing.T) {
testServicesCmdHandler(statusCmd, t)
}
// Test for service restart management REST API. // Test for service restart management REST API.
func TestServiceRestartHandler(t *testing.T) { func TestServiceRestartHandler(t *testing.T) {
testServicesCmdHandler(restartCmd, t) testServicesCmdHandler(restartCmd, t)
@@ -760,3 +651,100 @@ func TestToAdminAPIErrCode(t *testing.T) {
} }
} }
} }
func TestTopLockEntries(t *testing.T) {
t1 := UTCNow()
t2 := UTCNow().Add(10 * time.Second)
peerLocks := []*PeerLocks{
{
Addr: "1",
Locks: map[string][]lockRequesterInfo{
"1": {
{false, "node2", "ep2", "2", t2, t2, ""},
{true, "node1", "ep1", "1", t1, t1, ""},
},
"2": {
{false, "node2", "ep2", "2", t2, t2, ""},
{true, "node1", "ep1", "1", t1, t1, ""},
},
},
},
{
Addr: "2",
Locks: map[string][]lockRequesterInfo{
"1": {
{false, "node2", "ep2", "2", t2, t2, ""},
{true, "node1", "ep1", "1", t1, t1, ""},
},
"2": {
{false, "node2", "ep2", "2", t2, t2, ""},
{true, "node1", "ep1", "1", t1, t1, ""},
},
},
},
}
les := topLockEntries(peerLocks)
if len(les) != 2 {
t.Fatalf("Did not get 2 results")
}
if les[0].Timestamp.After(les[1].Timestamp) {
t.Fatalf("Got wrong sorted value")
}
}
func TestExtractHealInitParams(t *testing.T) {
mkParams := func(clientToken string, forceStart, forceStop bool) url.Values {
v := url.Values{}
if clientToken != "" {
v.Add(string(mgmtClientToken), clientToken)
}
if forceStart {
v.Add(string(mgmtForceStart), "")
}
if forceStop {
v.Add(string(mgmtForceStop), "")
}
return v
}
qParmsArr := []url.Values{
// Invalid cases
mkParams("", true, true),
mkParams("111", true, true),
mkParams("111", true, false),
mkParams("111", false, true),
// Valid cases follow
mkParams("", true, false),
mkParams("", false, true),
mkParams("", false, false),
mkParams("111", false, false),
}
varsArr := []map[string]string{
// Invalid cases
{string(mgmtPrefix): "objprefix"},
// Valid cases
{},
{string(mgmtBucket): "bucket"},
{string(mgmtBucket): "bucket", string(mgmtPrefix): "objprefix"},
}
// Body is always valid - we do not test JSON decoding.
body := `{"recursive": false, "dryRun": true, "remove": false, "scanMode": 0}`
// Test all combinations!
for pIdx, parms := range qParmsArr {
for vIdx, vars := range varsArr {
_, err := extractHealInitParams(vars, parms, bytes.NewBuffer([]byte(body)))
isErrCase := false
if pIdx < 4 || vIdx < 1 {
isErrCase = true
}
if err != ErrNone && !isErrCase {
t.Errorf("Got unexpected error: %v %v %v", pIdx, vIdx, err)
} else if err == ErrNone && isErrCase {
t.Errorf("Got no error but expected one: %v %v", pIdx, vIdx)
}
}
}
}
+15 -12
View File
@@ -38,17 +38,14 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
adminRouter := router.PathPrefix(adminAPIPathPrefix).Subrouter() adminRouter := router.PathPrefix(adminAPIPathPrefix).Subrouter()
// Version handler // Version handler
adminRouter.Methods(http.MethodGet).Path("/version").HandlerFunc(httpTraceAll(adminAPI.VersionHandler))
adminV1Router := adminRouter.PathPrefix("/v1").Subrouter() adminV1Router := adminRouter.PathPrefix("/v1").Subrouter()
/// Service operations /// Service operations
// Service status // Restart and stop MinIO service.
adminV1Router.Methods(http.MethodGet).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceStatusHandler)) adminV1Router.Methods(http.MethodPost).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceActionHandler)).Queries("action", "{action:.*}")
// Update MinIO servers.
// Service restart and stop - TODO adminV1Router.Methods(http.MethodPost).Path("/update").HandlerFunc(httpTraceAll(adminAPI.ServerUpdateHandler)).Queries("updateURL", "{updateURL:.*}")
adminV1Router.Methods(http.MethodPost).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceStopNRestartHandler))
// Info operations // Info operations
adminV1Router.Methods(http.MethodGet).Path("/info").HandlerFunc(httpTraceAll(adminAPI.ServerInfoHandler)) adminV1Router.Methods(http.MethodGet).Path("/info").HandlerFunc(httpTraceAll(adminAPI.ServerInfoHandler))
@@ -80,11 +77,6 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
adminV1Router.Methods(http.MethodGet).Path("/config").HandlerFunc(httpTraceHdrs(adminAPI.GetConfigHandler)) adminV1Router.Methods(http.MethodGet).Path("/config").HandlerFunc(httpTraceHdrs(adminAPI.GetConfigHandler))
// Set config // Set config
adminV1Router.Methods(http.MethodPut).Path("/config").HandlerFunc(httpTraceHdrs(adminAPI.SetConfigHandler)) adminV1Router.Methods(http.MethodPut).Path("/config").HandlerFunc(httpTraceHdrs(adminAPI.SetConfigHandler))
// Get config keys/values
adminV1Router.Methods(http.MethodGet).Path("/config-keys").HandlerFunc(httpTraceHdrs(adminAPI.GetConfigKeysHandler))
// Set config keys/values
adminV1Router.Methods(http.MethodPut).Path("/config-keys").HandlerFunc(httpTraceHdrs(adminAPI.SetConfigKeysHandler))
} }
if enableIAMOps { if enableIAMOps {
@@ -99,6 +91,9 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
adminV1Router.Methods(http.MethodPut).Path("/set-user-status").HandlerFunc(httpTraceHdrs(adminAPI.SetUserStatus)). adminV1Router.Methods(http.MethodPut).Path("/set-user-status").HandlerFunc(httpTraceHdrs(adminAPI.SetUserStatus)).
Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}") Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}")
// Info policy IAM
adminV1Router.Methods(http.MethodGet).Path("/info-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.InfoCannedPolicy)).Queries("name", "{name:.*}")
// Remove policy IAM // Remove policy IAM
adminV1Router.Methods(http.MethodDelete).Path("/remove-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.RemoveCannedPolicy)).Queries("name", "{name:.*}") adminV1Router.Methods(http.MethodDelete).Path("/remove-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.RemoveCannedPolicy)).Queries("name", "{name:.*}")
@@ -138,6 +133,14 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
// HTTP Trace // HTTP Trace
adminV1Router.Methods(http.MethodGet).Path("/trace").HandlerFunc(adminAPI.TraceHandler) adminV1Router.Methods(http.MethodGet).Path("/trace").HandlerFunc(adminAPI.TraceHandler)
// Console Logs
adminV1Router.Methods(http.MethodGet).Path("/log").HandlerFunc(httpTraceAll(adminAPI.ConsoleLogHandler))
// -- KMS APIs --
//
adminV1Router.Methods(http.MethodGet).Path("/kms/key/status").HandlerFunc(httpTraceAll(adminAPI.KMSKeyStatusHandler))
// If none of the routes match, return error. // If none of the routes match, return error.
adminV1Router.NotFoundHandler = http.HandlerFunc(httpTraceHdrs(notFoundHandlerJSON)) adminV1Router.NotFoundHandler = http.HandlerFunc(httpTraceHdrs(notFoundHandlerJSON))
} }
+114
View File
@@ -0,0 +1,114 @@
/*
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"net/http"
"os"
"github.com/minio/minio-go/pkg/set"
"github.com/minio/minio/pkg/cpu"
"github.com/minio/minio/pkg/disk"
"github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/mem"
)
// getLocalMemUsage - returns ServerMemUsageInfo for only the
// local endpoints from given list of endpoints
func getLocalMemUsage(endpoints EndpointList, r *http.Request) ServerMemUsageInfo {
var memUsages []mem.Usage
var historicUsages []mem.Usage
seenHosts := set.NewStringSet()
for _, endpoint := range endpoints {
if seenHosts.Contains(endpoint.Host) {
continue
}
seenHosts.Add(endpoint.Host)
// Only proceed for local endpoints
if endpoint.IsLocal {
memUsages = append(memUsages, mem.GetUsage())
historicUsages = append(historicUsages, mem.GetHistoricUsage())
}
}
addr := r.Host
if globalIsDistXL {
addr = GetLocalPeer(endpoints)
}
return ServerMemUsageInfo{
Addr: addr,
Usage: memUsages,
HistoricUsage: historicUsages,
}
}
// getLocalCPULoad - returns ServerCPULoadInfo for only the
// local endpoints from given list of endpoints
func getLocalCPULoad(endpoints EndpointList, r *http.Request) ServerCPULoadInfo {
var cpuLoads []cpu.Load
var historicLoads []cpu.Load
seenHosts := set.NewStringSet()
for _, endpoint := range endpoints {
if seenHosts.Contains(endpoint.Host) {
continue
}
seenHosts.Add(endpoint.Host)
// Only proceed for local endpoints
if endpoint.IsLocal {
cpuLoads = append(cpuLoads, cpu.GetLoad())
historicLoads = append(historicLoads, cpu.GetHistoricLoad())
}
}
addr := r.Host
if globalIsDistXL {
addr = GetLocalPeer(endpoints)
}
return ServerCPULoadInfo{
Addr: addr,
Load: cpuLoads,
HistoricLoad: historicLoads,
}
}
// getLocalDrivesPerf - returns ServerDrivesPerfInfo for only the
// local endpoints from given list of endpoints
func getLocalDrivesPerf(endpoints EndpointList, size int64, r *http.Request) madmin.ServerDrivesPerfInfo {
var dps []disk.Performance
for _, endpoint := range endpoints {
// Only proceed for local endpoints
if endpoint.IsLocal {
if _, err := os.Stat(endpoint.Path); err != nil {
// Since this drive is not available, add relevant details and proceed
dps = append(dps, disk.Performance{Path: endpoint.Path, Error: err.Error()})
continue
}
dp := disk.GetPerformance(pathJoin(endpoint.Path, minioMetaTmpBucket, mustGetUUID()), size)
dp.Path = endpoint.Path
dps = append(dps, dp)
}
}
addr := r.Host
if globalIsDistXL {
addr = GetLocalPeer(endpoints)
}
return madmin.ServerDrivesPerfInfo{
Addr: addr,
Perf: dps,
Size: size,
}
}
+14
View File
@@ -315,6 +315,7 @@ const (
ErrAdminProfilerNotEnabled ErrAdminProfilerNotEnabled
ErrInvalidDecompressedSize ErrInvalidDecompressedSize
ErrAddUserInvalidArgument ErrAddUserInvalidArgument
ErrPostPolicyConditionInvalidFormat
) )
type errorCodeMap map[APIErrorCode]APIError type errorCodeMap map[APIErrorCode]APIError
@@ -1496,6 +1497,11 @@ var errorCodes = errorCodeMap{
Description: "User is not allowed to be same as admin access key", Description: "User is not allowed to be same as admin access key",
HTTPStatusCode: http.StatusConflict, HTTPStatusCode: http.StatusConflict,
}, },
ErrPostPolicyConditionInvalidFormat: {
Code: "PostPolicyInvalidKeyName",
Description: "Invalid according to Policy: Policy Condition failed",
HTTPStatusCode: http.StatusForbidden,
},
// Add your error structure here. // Add your error structure here.
} }
@@ -1526,6 +1532,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrEntityTooLarge apiErr = ErrEntityTooLarge
case errDataTooSmall: case errDataTooSmall:
apiErr = ErrEntityTooSmall apiErr = ErrEntityTooSmall
case errAuthentication:
apiErr = ErrAccessDenied
case auth.ErrInvalidAccessKeyLength: case auth.ErrInvalidAccessKeyLength:
apiErr = ErrAdminInvalidAccessKey apiErr = ErrAdminInvalidAccessKey
case auth.ErrInvalidSecretKeyLength: case auth.ErrInvalidSecretKeyLength:
@@ -1721,6 +1729,12 @@ func toAPIError(ctx context.Context, err error) APIError {
// their internal error types. This code is only // their internal error types. This code is only
// useful with gateway implementations. // useful with gateway implementations.
switch e := err.(type) { switch e := err.(type) {
case crypto.Error:
apiErr = APIError{
Code: "XKMSInternalError",
Description: e.Error(),
HTTPStatusCode: http.StatusBadRequest,
}
case minio.ErrorResponse: case minio.ErrorResponse:
apiErr = APIError{ apiErr = APIError{
Code: e.Code, Code: e.Code,
+21
View File
@@ -42,6 +42,27 @@ func getListObjectsV1Args(values url.Values) (prefix, marker, delimiter string,
return return
} }
func getListBucketObjectVersionsArgs(values url.Values) (prefix, marker, delimiter string, maxkeys int, encodingType, versionIDMarker string, errCode APIErrorCode) {
errCode = ErrNone
if values.Get("max-keys") != "" {
var err error
if maxkeys, err = strconv.Atoi(values.Get("max-keys")); err != nil {
errCode = ErrInvalidMaxKeys
return
}
} else {
maxkeys = maxObjectList
}
prefix = values.Get("prefix")
marker = values.Get("key-marker")
delimiter = values.Get("delimiter")
encodingType = values.Get("encoding-type")
versionIDMarker = values.Get("version-id-marker")
return
}
// Parse bucket url queries for ListObjects V2. // Parse bucket url queries for ListObjects V2.
func getListObjectsV2Args(values url.Values) (prefix, token, startAfter, delimiter string, fetchOwner bool, maxkeys int, encodingType string, errCode APIErrorCode) { func getListObjectsV2Args(values url.Values) (prefix, token, startAfter, delimiter string, fetchOwner bool, maxkeys int, encodingType string, errCode APIErrorCode) {
errCode = ErrNone errCode = ErrNone
+95 -2
View File
@@ -44,6 +44,45 @@ type LocationResponse struct {
Location string `xml:",chardata"` Location string `xml:",chardata"`
} }
// ListVersionsResponse - format for list bucket versions response.
type ListVersionsResponse struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListVersionsResult" json:"-"`
Name string
Prefix string
KeyMarker string
// When response is truncated (the IsTruncated element value in the response
// is true), you can use the key name in this field as marker in the subsequent
// request to get next set of objects. Server lists objects in alphabetical
// order Note: This element is returned only if you have delimiter request parameter
// specified. If response does not include the NextMaker and it is truncated,
// you can use the value of the last Key in the response as the marker in the
// subsequent request to get the next set of object keys.
NextKeyMarker string `xml:"NextKeyMarker,omitempty"`
// When the number of responses exceeds the value of MaxKeys,
// NextVersionIdMarker specifies the first object version not
// returned that satisfies the search criteria. Use this value
// for the version-id-marker request parameter in a subsequent request.
NextVersionIDMarker string `xml:"NextVersionIdMarker"`
// Marks the last version of the Key returned in a truncated response.
VersionIDMarker string `xml:"VersionIdMarker"`
MaxKeys int
Delimiter string
// A flag that indicates whether or not ListObjects returned all of the results
// that satisfied the search criteria.
IsTruncated bool
CommonPrefixes []CommonPrefix
Versions []ObjectVersion
// Encoding type used to encode object keys in the response.
EncodingType string `xml:"EncodingType,omitempty"`
}
// ListObjectsResponse - format for list objects response. // ListObjectsResponse - format for list objects response.
type ListObjectsResponse struct { type ListObjectsResponse struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"` XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"`
@@ -191,6 +230,14 @@ type Bucket struct {
CreationDate string // time string of format "2006-01-02T15:04:05.000Z" CreationDate string // time string of format "2006-01-02T15:04:05.000Z"
} }
// ObjectVersion container for object version metadata
type ObjectVersion struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Version" json:"-"`
Object
VersionID string `xml:"VersionId"`
IsLatest bool
}
// Object container for object metadata // Object container for object metadata
type Object struct { type Object struct {
Key string Key string
@@ -328,6 +375,52 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
return data return data
} }
// generates an ListBucketVersions response for the said bucket with other enumerated options.
func generateListVersionsResponse(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListVersionsResponse {
var versions []ObjectVersion
var prefixes []CommonPrefix
var owner = Owner{}
var data = ListVersionsResponse{}
owner.ID = globalMinioDefaultOwnerID
for _, object := range resp.Objects {
var content = ObjectVersion{}
if object.Name == "" {
continue
}
content.Key = s3EncodeName(object.Name, encodingType)
content.LastModified = object.ModTime.UTC().Format(timeFormatAMZLong)
if object.ETag != "" {
content.ETag = "\"" + object.ETag + "\""
}
content.Size = object.Size
content.StorageClass = object.StorageClass
content.Owner = owner
content.VersionID = "null"
content.IsLatest = true
versions = append(versions, content)
}
data.Name = bucket
data.Versions = versions
data.EncodingType = encodingType
data.Prefix = s3EncodeName(prefix, encodingType)
data.KeyMarker = s3EncodeName(marker, encodingType)
data.Delimiter = s3EncodeName(delimiter, encodingType)
data.MaxKeys = maxKeys
data.NextKeyMarker = s3EncodeName(resp.NextMarker, encodingType)
data.IsTruncated = resp.IsTruncated
for _, prefix := range resp.Prefixes {
var prefixItem = CommonPrefix{}
prefixItem.Prefix = s3EncodeName(prefix, encodingType)
prefixes = append(prefixes, prefixItem)
}
data.CommonPrefixes = prefixes
return data
}
// generates an ListObjectsV1 response for the said bucket with other enumerated options. // generates an ListObjectsV1 response for the said bucket with other enumerated options.
func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse { func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
var contents []Object var contents []Object
@@ -405,8 +498,8 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
data.Delimiter = s3EncodeName(delimiter, encodingType) data.Delimiter = s3EncodeName(delimiter, encodingType)
data.Prefix = s3EncodeName(prefix, encodingType) data.Prefix = s3EncodeName(prefix, encodingType)
data.MaxKeys = maxKeys data.MaxKeys = maxKeys
data.ContinuationToken = token data.ContinuationToken = s3EncodeName(token, encodingType)
data.NextContinuationToken = nextToken data.NextContinuationToken = s3EncodeName(nextToken, encodingType)
data.IsTruncated = isTruncated data.IsTruncated = isTruncated
for _, prefix := range prefixes { for _, prefix := range prefixes {
var prefixItem = CommonPrefix{} var prefixItem = CommonPrefix{}
+3 -1
View File
@@ -64,7 +64,7 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(httpTraceAll(api.CopyObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}") bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(httpTraceAll(api.CopyObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
// PutObjectPart // PutObjectPart
bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.PutObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}") bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.PutObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
// ListObjectPxarts // ListObjectParts
bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(httpTraceAll(api.ListObjectPartsHandler)).Queries("uploadId", "{uploadId:.*}") bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(httpTraceAll(api.ListObjectPartsHandler)).Queries("uploadId", "{uploadId:.*}")
// CompleteMultipartUpload // CompleteMultipartUpload
bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(httpTraceAll(api.CompleteMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}") bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(httpTraceAll(api.CompleteMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}")
@@ -129,6 +129,8 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.ListMultipartUploadsHandler)).Queries("uploads", "") bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.ListMultipartUploadsHandler)).Queries("uploads", "")
// ListObjectsV2 // ListObjectsV2
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.ListObjectsV2Handler)).Queries("list-type", "2") bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.ListObjectsV2Handler)).Queries("list-type", "2")
// ListBucketVersions
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.ListBucketObjectVersionsHandler)).Queries("versions", "")
// ListObjectsV1 (Legacy) // ListObjectsV1 (Legacy)
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListObjectsV1Handler)) bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListObjectsV1Handler))
// PutBucketLifecycle // PutBucketLifecycle
+6
View File
@@ -203,6 +203,12 @@ func getClaimsFromToken(r *http.Request) (map[string]interface{}, error) {
} }
if globalPolicyOPA == nil { if globalPolicyOPA == nil {
// If OPA is not set and if ldap claim key is set,
// allow the claim.
if _, ok := claims[ldapUser]; ok {
return claims, nil
}
// If OPA is not set, session token should // If OPA is not set, session token should
// have a policy and its mandatory, reject // have a policy and its mandatory, reject
// requests without policy claim. // requests without policy claim.
+4 -4
View File
@@ -62,11 +62,11 @@ func (h *healRoutine) run() {
break break
} }
if globalHTTPServer != nil { if globalHTTPServer != nil {
// Wait at max 1 minute for an inprogress request // Wait at max 10 minute for an inprogress request before proceeding to heal
// before proceeding to heal waitCount := 600
waitCount := 60
// Any requests in progress, delay the heal. // Any requests in progress, delay the heal.
for globalHTTPServer.GetRequestCount() > 2 && waitCount > 0 { for (globalHTTPServer.GetRequestCount() >= int32(globalXLSetCount*globalXLSetDriveCount)) &&
waitCount > 0 {
waitCount-- waitCount--
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
+8
View File
@@ -155,3 +155,11 @@ func bitrotWriterSum(w io.Writer) []byte {
} }
return nil return nil
} }
// Returns the size of the file with bitrot protection
func bitrotShardFileSize(size int64, shardSize int64, algo BitrotAlgorithm) int64 {
if algo != HighwayHash256S {
return size
}
return ceilFrac(size, shardSize)*int64(algo.New().Size()) + size
}
+77
View File
@@ -49,6 +49,83 @@ func validateListObjectsArgs(prefix, marker, delimiter, encodingType string, max
return ErrNone return ErrNone
} }
// ListBucketObjectVersions - GET Bucket Object versions
// You can use the versions subresource to list metadata about all
// of the versions of objects in a bucket.
func (api objectAPIHandlers) ListBucketObjectVersionsHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListBucketObjectVersions")
defer logger.AuditLog(w, r, "ListBucketObjectVersions", mustGetClaimsFromToken(r))
vars := mux.Vars(r)
bucket := vars["bucket"]
objectAPI := api.ObjectAPI()
if objectAPI == nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return
}
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return
}
urlValues := r.URL.Query()
// Extract all the listBucketVersions query params to their native values.
// versionIDMarker is ignored here.
prefix, marker, delimiter, maxkeys, encodingType, _, errCode := getListBucketObjectVersionsArgs(urlValues)
if errCode != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r))
return
}
// Validate the query params before beginning to serve the request.
if s3Error := validateListObjectsArgs(prefix, marker, delimiter, encodingType, maxkeys); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return
}
listObjects := objectAPI.ListObjects
// Inititate a list objects operation based on the input params.
// On success would return back ListObjectsInfo object to be
// marshaled into S3 compatible XML header.
listObjectsInfo, err := listObjects(ctx, bucket, prefix, marker, delimiter, maxkeys)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
for i := range listObjectsInfo.Objects {
var actualSize int64
if listObjectsInfo.Objects[i].IsCompressed() {
// Read the decompressed size from the meta.json.
actualSize = listObjectsInfo.Objects[i].GetActualSize()
if actualSize < 0 {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidDecompressedSize),
r.URL, guessIsBrowserReq(r))
return
}
// Set the info.Size to the actualSize.
listObjectsInfo.Objects[i].Size = actualSize
} else if crypto.IsEncrypted(listObjectsInfo.Objects[i].UserDefined) {
listObjectsInfo.Objects[i].ETag = getDecryptedETag(r.Header, listObjectsInfo.Objects[i], false)
listObjectsInfo.Objects[i].Size, err = listObjectsInfo.Objects[i].DecryptedSize()
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
}
}
response := generateListVersionsResponse(bucket, prefix, marker, delimiter, encodingType, maxkeys, listObjectsInfo)
// Write success response.
writeSuccessResponseXML(w, encodeResponse(response))
}
// ListObjectsV2Handler - GET Bucket (List Objects) Version 2. // ListObjectsV2Handler - GET Bucket (List Objects) Version 2.
// -------------------------- // --------------------------
// This implementation of the GET operation returns some or all (up to 1000) // This implementation of the GET operation returns some or all (up to 1000)
+12 -4
View File
@@ -288,6 +288,9 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
// Set delimiter value for "s3:delimiter" policy conditionals. // Set delimiter value for "s3:delimiter" policy conditionals.
r.Header.Set("delimiter", SlashSeparator) r.Header.Set("delimiter", SlashSeparator)
// err will be nil here as we already called this function
// earlier in this request.
claims, _ := getClaimsFromToken(r)
var newBucketsInfo []BucketInfo var newBucketsInfo []BucketInfo
for _, bucketInfo := range bucketsInfo { for _, bucketInfo := range bucketsInfo {
if globalIAMSys.IsAllowed(iampolicy.Args{ if globalIAMSys.IsAllowed(iampolicy.Args{
@@ -297,6 +300,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
ConditionValues: getConditionValues(r, "", accessKey), ConditionValues: getConditionValues(r, "", accessKey),
IsOwner: owner, IsOwner: owner,
ObjectName: "", ObjectName: "",
Claims: claims,
}) { }) {
newBucketsInfo = append(newBucketsInfo, bucketInfo) newBucketsInfo = append(newBucketsInfo, bucketInfo)
} }
@@ -553,8 +557,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
if !api.EncryptionEnabled() && crypto.IsRequested(r.Header) {
if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -653,9 +656,10 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
// Handle policy if it is set. // Handle policy if it is set.
if len(policyBytes) > 0 { if len(policyBytes) > 0 {
postPolicyForm, err := parsePostPolicyForm(string(policyBytes)) postPolicyForm, err := parsePostPolicyForm(string(policyBytes))
if err != nil { if err != nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPostPolicyConditionInvalidFormat), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -711,7 +715,11 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
return return
} }
if objectAPI.IsEncryptionSupported() { if objectAPI.IsEncryptionSupported() {
if hasServerSideEncryptionHeader(formValues) && !hasSuffix(object, SlashSeparator) { // handle SSE-C and SSE-S3 requests if crypto.IsRequested(formValues) && !hasSuffix(object, SlashSeparator) { // handle SSE requests
if crypto.SSECopy.IsRequested(r.Header) {
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r))
return
}
var reader io.Reader var reader io.Reader
var key []byte var key []byte
if crypto.SSEC.IsRequested(formValues) { if crypto.SSEC.IsRequested(formValues) {
+18 -4
View File
@@ -31,7 +31,6 @@ import (
"github.com/minio/cli" "github.com/minio/cli"
"github.com/minio/minio-go/v6/pkg/set" "github.com/minio/minio-go/v6/pkg/set"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/cmd/logger/target/console"
"github.com/minio/minio/cmd/logger/target/http" "github.com/minio/minio/cmd/logger/target/http"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/dns" "github.com/minio/minio/pkg/dns"
@@ -65,9 +64,9 @@ func checkUpdate(mode string) {
return return
} }
if globalInplaceUpdateDisabled { if globalInplaceUpdateDisabled {
logger.StartupMessage(updateMsg) logStartupMessage(updateMsg)
} else { } else {
logger.StartupMessage(prepareUpdateMessage("Run `minio update`", latestReleaseTime.Sub(currentReleaseTime))) logStartupMessage(prepareUpdateMessage("Run `mc admin update`", latestReleaseTime.Sub(currentReleaseTime)))
} }
} }
} }
@@ -97,7 +96,7 @@ func loadLoggers() {
if globalServerConfig.Logger.Console.Enabled { if globalServerConfig.Logger.Console.Enabled {
// Enable console logging // Enable console logging
logger.AddTarget(console.New()) logger.AddTarget(globalConsoleSys.Console())
} }
} }
@@ -359,6 +358,14 @@ func handleCommonEnvVars() {
globalCacheMaxUse = maxUse globalCacheMaxUse = maxUse
} }
} }
var err error
if cacheEncKey := os.Getenv("MINIO_CACHE_ENCRYPTION_MASTER_KEY"); cacheEncKey != "" {
globalCacheKMSKeyID, globalCacheKMS, err = parseKMSMasterKey(cacheEncKey)
if err != nil {
logger.Fatal(uiErrInvalidCacheEncryptionKey(err), "Invalid cache encryption master key")
}
}
// In place update is true by default if the MINIO_UPDATE is not set // In place update is true by default if the MINIO_UPDATE is not set
// or is not set to 'off', if MINIO_UPDATE is set to 'off' then // or is not set to 'off', if MINIO_UPDATE is set to 'off' then
// in-place update is off. // in-place update is off.
@@ -431,3 +438,10 @@ func handleCommonEnvVars() {
} }
} }
} }
func logStartupMessage(msg string, data ...interface{}) {
if globalConsoleSys != nil {
globalConsoleSys.Send(msg)
}
logger.StartupMessage(msg, data...)
}
+14 -1
View File
@@ -1,5 +1,5 @@
/* /*
* MinIO Cloud Storage, (C) 2016, 2017, 2018 MinIO, Inc. * MinIO Cloud Storage, (C) 2016-2019 MinIO, Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -303,6 +303,12 @@ func (s *serverConfig) loadFromEnvs() {
s.Policy.OPA.URL = opaArgs.URL s.Policy.OPA.URL = opaArgs.URL
s.Policy.OPA.AuthToken = opaArgs.AuthToken s.Policy.OPA.AuthToken = opaArgs.AuthToken
} }
var err error
s.LDAPServerConfig, err = newLDAPConfigFromEnv()
if err != nil {
logger.FatalIf(err, "Unable to parse LDAP configuration from env")
}
} }
// TestNotificationTargets tries to establish connections to all notification // TestNotificationTargets tries to establish connections to all notification
@@ -335,6 +341,9 @@ func (s *serverConfig) TestNotificationTargets() error {
if !v.Enable { if !v.Enable {
continue continue
} }
if v.TLS.Enable {
v.TLS.RootCAs = globalRootCAs
}
t, err := target.NewKafkaTarget(k, v, GlobalServiceDoneCh) t, err := target.NewKafkaTarget(k, v, GlobalServiceDoneCh)
if err != nil { if err != nil {
return fmt.Errorf("kafka(%s): %s", k, err.Error()) return fmt.Errorf("kafka(%s): %s", k, err.Error())
@@ -346,6 +355,7 @@ func (s *serverConfig) TestNotificationTargets() error {
if !v.Enable { if !v.Enable {
continue continue
} }
v.RootCAs = globalRootCAs
t, err := target.NewMQTTTarget(k, v, GlobalServiceDoneCh) t, err := target.NewMQTTTarget(k, v, GlobalServiceDoneCh)
if err != nil { if err != nil {
return fmt.Errorf("mqtt(%s): %s", k, err.Error()) return fmt.Errorf("mqtt(%s): %s", k, err.Error())
@@ -684,6 +694,9 @@ func getNotificationTargets(config *serverConfig) *event.TargetList {
for id, args := range config.Notify.Kafka { for id, args := range config.Notify.Kafka {
if args.Enable { if args.Enable {
if args.TLS.Enable {
args.TLS.RootCAs = globalRootCAs
}
newTarget, err := target.NewKafkaTarget(id, args, GlobalServiceDoneCh) newTarget, err := target.NewKafkaTarget(id, args, GlobalServiceDoneCh)
if err != nil { if err != nil {
logger.LogIf(context.Background(), err) logger.LogIf(context.Background(), err)
+2
View File
@@ -926,4 +926,6 @@ type serverConfigV33 struct {
// Add new external policy enforcements here. // Add new external policy enforcements here.
} `json:"policy"` } `json:"policy"`
LDAPServerConfig ldapServerConfig `json:"ldapserverconfig"`
} }
+9 -3
View File
@@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"path" "path"
"runtime" "runtime"
"strings" "strings"
@@ -136,7 +137,10 @@ func (sys *ConfigSys) Init(objAPI ObjectLayer) error {
// of the object layer. // of the object layer.
// - Write quorum not met when upgrading configuration // - Write quorum not met when upgrading configuration
// version is needed. // version is needed.
for range newRetryTimerSimple(doneCh) { retryTimerCh := newRetryTimerSimple(doneCh)
for {
select {
case <-retryTimerCh:
if err := initConfig(objAPI); err != nil { if err := initConfig(objAPI); err != nil {
if strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) || if strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) { strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
@@ -145,9 +149,11 @@ func (sys *ConfigSys) Init(objAPI ObjectLayer) error {
} }
return err return err
} }
break
}
return nil return nil
case <-globalOSSignalCh:
return fmt.Errorf("Initializing config sub-system gracefully stopped")
}
}
} }
// NewConfigSys - creates new config system object. // NewConfigSys - creates new config system object.
+133
View File
@@ -0,0 +1,133 @@
/*
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
ring "container/ring"
"context"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/cmd/logger/message/log"
"github.com/minio/minio/cmd/logger/target/console"
"github.com/minio/minio/pkg/madmin"
xnet "github.com/minio/minio/pkg/net"
"github.com/minio/minio/pkg/pubsub"
)
// number of log messages to buffer
const defaultLogBufferCount = 10000
//HTTPConsoleLoggerSys holds global console logger state
type HTTPConsoleLoggerSys struct {
pubsub *pubsub.PubSub
console *console.Target
nodeName string
logBuf *ring.Ring
}
// NewConsoleLogger - creates new HTTPConsoleLoggerSys with all nodes subscribed to
// the console logging pub sub system
func NewConsoleLogger(ctx context.Context, endpoints EndpointList) *HTTPConsoleLoggerSys {
host, err := xnet.ParseHost(GetLocalPeer(globalEndpoints))
if err != nil {
logger.FatalIf(err, "Unable to start console logging subsystem")
}
var nodeName string
if globalIsDistXL {
nodeName = host.Name
}
ps := pubsub.New()
return &HTTPConsoleLoggerSys{
ps, nil, nodeName, ring.New(defaultLogBufferCount),
}
}
// HasLogListeners returns true if console log listeners are registered
// for this node or peers
func (sys *HTTPConsoleLoggerSys) HasLogListeners() bool {
return sys != nil && sys.pubsub.HasSubscribers()
}
// Subscribe starts console logging for this node.
func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan interface{}, doneCh chan struct{}, node string, last int, filter func(entry interface{}) bool) {
// Enable console logging for remote client even if local console logging is disabled in the config.
if !globalServerConfig.Logger.Console.Enabled && !sys.pubsub.HasSubscribers() {
logger.AddTarget(globalConsoleSys.Console())
}
cnt := 0
// by default send all console logs in the ring buffer unless node or limit query parameters
// are set.
var lastN []madmin.LogInfo
if last > defaultLogBufferCount || last <= 0 {
last = defaultLogBufferCount
}
lastN = make([]madmin.LogInfo, last)
r := sys.logBuf
r.Do(func(p interface{}) {
if p != nil && (p.(madmin.LogInfo)).SendLog(node) {
lastN[cnt%last] = p.(madmin.LogInfo)
cnt++
}
})
// send last n console log messages in order filtered by node
if cnt > 0 {
for i := 0; i < last; i++ {
entry := lastN[(cnt+i)%last]
if (entry == madmin.LogInfo{}) {
continue
}
select {
case subCh <- entry:
case <-doneCh:
return
}
}
}
sys.pubsub.Subscribe(subCh, doneCh, filter)
}
// Console returns a console target
func (sys *HTTPConsoleLoggerSys) Console() *HTTPConsoleLoggerSys {
if sys.console == nil {
sys.console = console.New()
}
return sys
}
// Send log message 'e' to console and publish to console
// log pubsub system
func (sys *HTTPConsoleLoggerSys) Send(e interface{}) error {
var lg madmin.LogInfo
switch e := e.(type) {
case log.Entry:
lg = madmin.LogInfo{Entry: e, NodeName: sys.nodeName}
case string:
lg = madmin.LogInfo{ConsoleMsg: e, NodeName: sys.nodeName}
}
sys.pubsub.Publish(lg)
// add log to ring buffer
sys.logBuf.Value = lg
sys.logBuf = sys.logBuf.Next()
if globalServerConfig.Logger.Console.Enabled {
return sys.console.Send(e)
}
return nil
}
+8 -8
View File
@@ -19,9 +19,9 @@ import "errors"
// Error is the generic type for any error happening during decrypting // Error is the generic type for any error happening during decrypting
// an object. It indicates that the object itself or its metadata was // an object. It indicates that the object itself or its metadata was
// modified accidentally or maliciously. // modified accidentally or maliciously.
type Error struct{ msg string } type Error string
func (e Error) Error() string { return e.msg } func (e Error) Error() string { return string(e) }
var ( var (
// ErrInvalidEncryptionMethod indicates that the specified SSE encryption method // ErrInvalidEncryptionMethod indicates that the specified SSE encryption method
@@ -56,14 +56,14 @@ var (
ErrIncompatibleEncryptionMethod = errors.New("Server side encryption specified with both SSE-C and SSE-S3 headers") ErrIncompatibleEncryptionMethod = errors.New("Server side encryption specified with both SSE-C and SSE-S3 headers")
) )
var ( const (
errMissingInternalIV = Error{"The object metadata is missing the internal encryption IV"} errMissingInternalIV Error = "The object metadata is missing the internal encryption IV"
errMissingInternalSealAlgorithm = Error{"The object metadata is missing the internal seal algorithm"} errMissingInternalSealAlgorithm Error = "The object metadata is missing the internal seal algorithm"
errInvalidInternalIV = Error{"The internal encryption IV is malformed"} errInvalidInternalIV Error = "The internal encryption IV is malformed"
errInvalidInternalSealAlgorithm = Error{"The internal seal algorithm is invalid and not supported"} errInvalidInternalSealAlgorithm Error = "The internal seal algorithm is invalid and not supported"
errMissingUpdatedKey = Error{"The key update returned no error but also no sealed key"} errMissingUpdatedKey Error = "The key update returned no error but also no sealed key"
) )
var ( var (
+7
View File
@@ -83,6 +83,13 @@ func RemoveSensitiveHeaders(h http.Header) {
h.Del(SSECopyKey) h.Del(SSECopyKey)
} }
// IsRequested returns true if the HTTP headers indicates
// that any form server-side encryption (SSE-C, SSE-S3 or SSE-KMS)
// is requested.
func IsRequested(h http.Header) bool {
return S3.IsRequested(h) || SSEC.IsRequested(h) || SSECopy.IsRequested(h) || S3KMS.IsRequested(h)
}
// S3 represents AWS SSE-S3. It provides functionality to handle // S3 represents AWS SSE-S3. It provides functionality to handle
// SSE-S3 requests. // SSE-S3 requests.
var S3 = s3{} var S3 = s3{}
+23
View File
@@ -20,6 +20,29 @@ import (
"testing" "testing"
) )
func TestIsRequested(t *testing.T) {
for i, test := range kmsIsRequestedTests {
if got := IsRequested(test.Header) && S3KMS.IsRequested(test.Header); got != test.Expected {
t.Errorf("SSE-KMS: Test %d: Wanted %v but got %v", i, test.Expected, got)
}
}
for i, test := range s3IsRequestedTests {
if got := IsRequested(test.Header) && S3.IsRequested(test.Header); got != test.Expected {
t.Errorf("SSE-S3: Test %d: Wanted %v but got %v", i, test.Expected, got)
}
}
for i, test := range ssecIsRequestedTests {
if got := IsRequested(test.Header) && SSEC.IsRequested(test.Header); got != test.Expected {
t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got)
}
}
for i, test := range ssecCopyIsRequestedTests {
if got := IsRequested(test.Header) && SSECopy.IsRequested(test.Header); got != test.Expected {
t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got)
}
}
}
var kmsIsRequestedTests = []struct { var kmsIsRequestedTests = []struct {
Header http.Header Header http.Header
Expected bool Expected bool
+1 -1
View File
@@ -108,7 +108,7 @@ func (key *ObjectKey) Unseal(extKey [32]byte, sealedKey SealedKey, domain, bucke
) )
switch sealedKey.Algorithm { switch sealedKey.Algorithm {
default: default:
return Error{fmt.Sprintf("The sealing algorithm '%s' is not supported", sealedKey.Algorithm)} return Error(fmt.Sprintf("The sealing algorithm '%s' is not supported", sealedKey.Algorithm))
case SealAlgorithm: case SealAlgorithm:
mac := hmac.New(sha256.New, extKey[:]) mac := hmac.New(sha256.New, extKey[:])
mac.Write(sealedKey.IV[:]) mac.Write(sealedKey.IV[:])
+43 -17
View File
@@ -17,6 +17,7 @@ package crypto
import ( import (
"context" "context"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
@@ -119,28 +120,46 @@ func CreateMultipartMetadata(metadata map[string]string) map[string]string {
return metadata return metadata
} }
// CreateMetadata encodes the keyID, the sealed kms data key and the sealed key // CreateMetadata encodes the sealed object key into the metadata and returns
// into the metadata and returns the modified metadata. It allocates a new // the modified metadata. If the keyID and the kmsKey is not empty it encodes
// metadata map if metadata is nil. // both into the metadata as well. It allocates a new metadata map if metadata
// is nil.
func (s3) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string { func (s3) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string {
if sealedKey.Algorithm != SealAlgorithm { if sealedKey.Algorithm != SealAlgorithm {
logger.CriticalIf(context.Background(), fmt.Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm)) logger.CriticalIf(context.Background(), fmt.Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm))
} }
// There are two possibilites:
// - We use a KMS -> There must be non-empty key ID and a KMS data key.
// - We use a K/V -> There must be no key ID and no KMS data key.
// Otherwise, the caller has passed an invalid argument combination.
if keyID == "" && len(kmsKey) != 0 {
logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present"))
}
if keyID != "" && len(kmsKey) == 0 {
logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present"))
}
if metadata == nil { if metadata == nil {
metadata = map[string]string{} metadata = map[string]string{}
} }
metadata[S3KMSKeyID] = keyID
metadata[SSESealAlgorithm] = sealedKey.Algorithm metadata[SSESealAlgorithm] = sealedKey.Algorithm
metadata[SSEIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:]) metadata[SSEIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
metadata[S3SealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Key[:]) metadata[S3SealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key.
metadata[S3KMSKeyID] = keyID
metadata[S3KMSSealedKey] = base64.StdEncoding.EncodeToString(kmsKey) metadata[S3KMSSealedKey] = base64.StdEncoding.EncodeToString(kmsKey)
}
return metadata return metadata
} }
// ParseMetadata extracts all SSE-S3 related values from the object metadata // ParseMetadata extracts all SSE-S3 related values from the object metadata
// and checks whether they are well-formed. It returns the KMS key-ID, the // and checks whether they are well-formed. It returns the sealed object key
// sealed KMS key and the sealed object key on success. // on success. If the metadata contains both, a KMS master key ID and a sealed
// KMS data key it returns both. If the metadata does not contain neither a
// KMS master key ID nor a sealed KMS data key it returns an empty keyID and
// KMS data key. Otherwise, it returns an error.
func (s3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) { func (s3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) {
// Extract all required values from object metadata // Extract all required values from object metadata
b64IV, ok := metadata[SSEIV] b64IV, ok := metadata[SSEIV]
@@ -153,15 +172,20 @@ func (s3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte
} }
b64SealedKey, ok := metadata[S3SealedKey] b64SealedKey, ok := metadata[S3SealedKey]
if !ok { if !ok {
return keyID, kmsKey, sealedKey, Error{"The object metadata is missing the internal sealed key for SSE-S3"} return keyID, kmsKey, sealedKey, Error("The object metadata is missing the internal sealed key for SSE-S3")
} }
keyID, ok = metadata[S3KMSKeyID]
if !ok { // There are two possibilites:
return keyID, kmsKey, sealedKey, Error{"The object metadata is missing the internal KMS key-ID for SSE-S3"} // - We use a KMS -> There must be a key ID and a KMS data key.
// - We use a K/V -> There must be no key ID and no KMS data key.
// Otherwise, the metadata is corrupted.
keyID, idPresent := metadata[S3KMSKeyID]
b64KMSSealedKey, kmsKeyPresent := metadata[S3KMSSealedKey]
if !idPresent && kmsKeyPresent {
return keyID, kmsKey, sealedKey, Error("The object metadata is missing the internal KMS key-ID for SSE-S3")
} }
b64KMSSealedKey, ok := metadata[S3KMSSealedKey] if idPresent && !kmsKeyPresent {
if !ok { return keyID, kmsKey, sealedKey, Error("The object metadata is missing the internal sealed KMS data key for SSE-S3")
return keyID, kmsKey, sealedKey, Error{"The object metadata is missing the internal sealed KMS data key for SSE-S3"}
} }
// Check whether all extracted values are well-formed // Check whether all extracted values are well-formed
@@ -174,11 +198,13 @@ func (s3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte
} }
encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey) encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
if err != nil || len(encryptedKey) != 64 { if err != nil || len(encryptedKey) != 64 {
return keyID, kmsKey, sealedKey, Error{"The internal sealed key for SSE-S3 is invalid"} return keyID, kmsKey, sealedKey, Error("The internal sealed key for SSE-S3 is invalid")
} }
if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key.
kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey) kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey)
if err != nil { if err != nil {
return keyID, kmsKey, sealedKey, Error{"The internal sealed KMS data key for SSE-S3 is invalid"} return keyID, kmsKey, sealedKey, Error("The internal sealed KMS data key for SSE-S3 is invalid")
}
} }
sealedKey.Algorithm = algorithm sealedKey.Algorithm = algorithm
@@ -218,7 +244,7 @@ func (ssec) ParseMetadata(metadata map[string]string) (sealedKey SealedKey, err
} }
b64SealedKey, ok := metadata[SSECSealedKey] b64SealedKey, ok := metadata[SSECSealedKey]
if !ok { if !ok {
return sealedKey, Error{"The object metadata is missing the internal sealed key for SSE-C"} return sealedKey, Error("The object metadata is missing the internal sealed key for SSE-C")
} }
// Check whether all extracted values are well-formed // Check whether all extracted values are well-formed
@@ -231,7 +257,7 @@ func (ssec) ParseMetadata(metadata map[string]string) (sealedKey SealedKey, err
} }
encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey) encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
if err != nil || len(encryptedKey) != 64 { if err != nil || len(encryptedKey) != 64 {
return sealedKey, Error{"The internal sealed key for SSE-C is invalid"} return sealedKey, Error("The internal sealed key for SSE-C is invalid")
} }
sealedKey.Algorithm = algorithm sealedKey.Algorithm = algorithm
+11 -9
View File
@@ -125,15 +125,15 @@ var s3ParseMetadataTests = []struct {
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 1 }, // 1
{ {
ExpectedErr: Error{"The object metadata is missing the internal sealed key for SSE-S3"}, ExpectedErr: Error("The object metadata is missing the internal sealed key for SSE-S3"),
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 2 }, // 2
{ {
ExpectedErr: Error{"The object metadata is missing the internal KMS key-ID for SSE-S3"}, ExpectedErr: Error("The object metadata is missing the internal KMS key-ID for SSE-S3"),
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSSealedKey: "IAAF0b=="}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 3 }, // 3
{ {
ExpectedErr: Error{"The object metadata is missing the internal sealed KMS data key for SSE-S3"}, ExpectedErr: Error("The object metadata is missing the internal sealed KMS data key for SSE-S3"),
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSKeyID: ""}, Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSKeyID: ""},
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 4 }, // 4
@@ -150,7 +150,7 @@ var s3ParseMetadataTests = []struct {
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 6 }, // 6
{ {
ExpectedErr: Error{"The internal sealed key for SSE-S3 is invalid"}, ExpectedErr: Error("The internal sealed key for SSE-S3 is invalid"),
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, S3SealedKey: "", SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, S3SealedKey: "",
S3KMSKeyID: "", S3KMSSealedKey: "", S3KMSKeyID: "", S3KMSSealedKey: "",
@@ -158,7 +158,7 @@ var s3ParseMetadataTests = []struct {
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 7 }, // 7
{ {
ExpectedErr: Error{"The internal sealed KMS data key for SSE-S3 is invalid"}, ExpectedErr: Error("The internal sealed KMS data key for SSE-S3 is invalid"),
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm,
S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), S3KMSKeyID: "key-1", S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), S3KMSKeyID: "key-1",
@@ -218,7 +218,7 @@ var ssecParseMetadataTests = []struct {
{ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, SealedKey: SealedKey{}}, // 0 {ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, SealedKey: SealedKey{}}, // 0
{ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{SSEIV: ""}, SealedKey: SealedKey{}}, // 1 {ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{SSEIV: ""}, SealedKey: SealedKey{}}, // 1
{ {
ExpectedErr: Error{"The object metadata is missing the internal sealed key for SSE-C"}, ExpectedErr: Error("The object metadata is missing the internal sealed key for SSE-C"),
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: ""}, SealedKey: SealedKey{}, Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: ""}, SealedKey: SealedKey{},
}, // 2 }, // 2
{ {
@@ -233,7 +233,7 @@ var ssecParseMetadataTests = []struct {
SealedKey: SealedKey{}, SealedKey: SealedKey{},
}, // 4 }, // 4
{ {
ExpectedErr: Error{"The internal sealed key for SSE-C is invalid"}, ExpectedErr: Error("The internal sealed key for SSE-C is invalid"),
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, SSECSealedKey: "", SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, SSECSealedKey: "",
}, },
@@ -287,7 +287,9 @@ var s3CreateMetadataTests = []struct {
SealedDataKey []byte SealedDataKey []byte
SealedKey SealedKey SealedKey SealedKey
}{ }{
{KeyID: "", SealedDataKey: make([]byte, 48), SealedKey: SealedKey{Algorithm: SealAlgorithm}},
{KeyID: "", SealedDataKey: nil, SealedKey: SealedKey{Algorithm: SealAlgorithm}},
{KeyID: "my-minio-key", SealedDataKey: make([]byte, 48), SealedKey: SealedKey{Algorithm: SealAlgorithm}},
{KeyID: "cafebabe", SealedDataKey: make([]byte, 48), SealedKey: SealedKey{Algorithm: SealAlgorithm}}, {KeyID: "cafebabe", SealedDataKey: make([]byte, 48), SealedKey: SealedKey{Algorithm: SealAlgorithm}},
{KeyID: "deadbeef", SealedDataKey: make([]byte, 32), SealedKey: SealedKey{IV: [32]byte{0xf7}, Key: [64]byte{0xea}, Algorithm: SealAlgorithm}}, {KeyID: "deadbeef", SealedDataKey: make([]byte, 32), SealedKey: SealedKey{IV: [32]byte{0xf7}, Key: [64]byte{0xea}, Algorithm: SealAlgorithm}},
} }
+10 -10
View File
@@ -103,14 +103,12 @@ func startDailyLifecycle() {
} }
} }
var lifecycleTimeout = newDynamicTimeout(60*time.Second, time.Second)
func lifecycleRound(ctx context.Context, objAPI ObjectLayer) error { func lifecycleRound(ctx context.Context, objAPI ObjectLayer) error {
zeroDuration := time.Millisecond
zeroDynamicTimeout := newDynamicTimeout(zeroDuration, zeroDuration)
// Lock to avoid concurrent lifecycle ops from other nodes // Lock to avoid concurrent lifecycle ops from other nodes
sweepLock := globalNSMutex.NewNSLock(ctx, "system", "daily-lifecycle-ops") sweepLock := globalNSMutex.NewNSLock(ctx, "system", "daily-lifecycle-ops")
if err := sweepLock.GetLock(zeroDynamicTimeout); err != nil { if err := sweepLock.GetLock(lifecycleTimeout); err != nil {
return err return err
} }
defer sweepLock.Unlock() defer sweepLock.Unlock()
@@ -141,22 +139,24 @@ func lifecycleRound(ctx context.Context, objAPI ObjectLayer) error {
if err != nil { if err != nil {
continue continue
} }
var objects []string
for _, obj := range res.Objects { for _, obj := range res.Objects {
// Find the action that need to be executed // Find the action that need to be executed
action := l.ComputeAction(obj.Name, obj.ModTime) action := l.ComputeAction(obj.Name, obj.ModTime)
switch action { switch action {
case lifecycle.DeleteAction: case lifecycle.DeleteAction:
objAPI.DeleteObject(ctx, bucket.Name, obj.Name) objects = append(objects, obj.Name)
default: default:
// Nothing // Do nothing, for now.
} }
} }
// Deletes a list of objects.
objAPI.DeleteObjects(ctx, bucket.Name, objects)
if !res.IsTruncated { if !res.IsTruncated {
// We are done here, proceed to next bucket.
break break
} else {
marker = res.NextMarker
} }
marker = res.NextMarker
} }
} }
+21 -8
View File
@@ -18,6 +18,7 @@ package cmd
import ( import (
"context" "context"
"math/rand"
"sync" "sync"
"time" "time"
@@ -48,15 +49,14 @@ func copyDailySweepListeners() []chan string {
return listenersCopy return listenersCopy
} }
var sweepTimeout = newDynamicTimeout(60*time.Second, time.Second)
// sweepRound will list all objects, having read quorum or not and // sweepRound will list all objects, having read quorum or not and
// feeds to all listeners, such as the background healing // feeds to all listeners, such as the background healing
func sweepRound(ctx context.Context, objAPI ObjectLayer) error { func sweepRound(ctx context.Context, objAPI ObjectLayer) error {
zeroDuration := time.Millisecond
zeroDynamicTimeout := newDynamicTimeout(zeroDuration, zeroDuration)
// General lock so we avoid parallel daily sweep by different instances. // General lock so we avoid parallel daily sweep by different instances.
sweepLock := globalNSMutex.NewNSLock(ctx, "system", "daily-sweep") sweepLock := globalNSMutex.NewNSLock(ctx, "system", "daily-sweep")
if err := sweepLock.GetLock(zeroDynamicTimeout); err != nil { if err := sweepLock.GetLock(sweepTimeout); err != nil {
return err return err
} }
defer sweepLock.Unlock() defer sweepLock.Unlock()
@@ -76,10 +76,22 @@ func sweepRound(ctx context.Context, objAPI ObjectLayer) error {
marker := "" marker := ""
for { for {
if globalHTTPServer != nil {
// Wait at max 10 minute for an inprogress request before proceeding to heal
waitCount := 600
// Any requests in progress, delay the heal.
for (globalHTTPServer.GetRequestCount() >= int32(globalXLSetCount*globalXLSetDriveCount)) &&
waitCount > 0 {
waitCount--
time.Sleep(1 * time.Second)
}
}
res, err := objAPI.ListObjectsHeal(ctx, bucket.Name, "", marker, "", 1000) res, err := objAPI.ListObjectsHeal(ctx, bucket.Name, "", marker, "", 1000)
if err != nil { if err != nil {
continue continue
} }
for _, obj := range res.Objects { for _, obj := range res.Objects {
for _, l := range copyDailySweepListeners() { for _, l := range copyDailySweepListeners() {
l <- pathJoin(bucket.Name, obj.Name) l <- pathJoin(bucket.Name, obj.Name)
@@ -119,7 +131,9 @@ func dailySweeper() {
break break
} }
// Perform a sweep round each month // Start with random sleep time, so as to avoid "synchronous checks" between servers
time.Sleep(time.Duration(rand.Float64() * float64(time.Hour)))
for { for {
if time.Since(lastSweepTime) < 30*24*time.Hour { if time.Since(lastSweepTime) < 30*24*time.Hour {
time.Sleep(time.Hour) time.Sleep(time.Hour)
@@ -133,13 +147,12 @@ func dailySweeper() {
// instance doing the sweep round // instance doing the sweep round
case OperationTimedOut: case OperationTimedOut:
lastSweepTime = time.Now() lastSweepTime = time.Now()
default: continue
}
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
time.Sleep(time.Minute) time.Sleep(time.Minute)
continue continue
} }
} else {
lastSweepTime = time.Now() lastSweepTime = time.Now()
} }
} }
}
+99 -15
View File
@@ -19,6 +19,7 @@ package cmd
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/rand"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
@@ -33,8 +34,10 @@ import (
"time" "time"
"github.com/djherbis/atime" "github.com/djherbis/atime"
"github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/disk" "github.com/minio/minio/pkg/disk"
"github.com/minio/sio"
"github.com/ncw/directio" "github.com/ncw/directio"
) )
@@ -45,6 +48,10 @@ const (
cacheMetaVersion = "1.0.0" cacheMetaVersion = "1.0.0"
cacheEnvDelimiter = ";" cacheEnvDelimiter = ";"
// SSECacheEncrypted is the metadata key indicating that the object
// is a cache entry encrypted with cache KMS master key in globalCacheKMS.
SSECacheEncrypted = "X-Minio-Internal-Encrypted-Cache"
) )
// CacheChecksumInfoV1 - carries checksums of individual blocks on disk. // CacheChecksumInfoV1 - carries checksums of individual blocks on disk.
@@ -223,6 +230,7 @@ func (c *diskCache) purge() {
continue continue
} }
cc := cacheControlOpts(objInfo) cc := cacheControlOpts(objInfo)
if atime.Get(fi).Before(expiry) || if atime.Get(fi).Before(expiry) ||
cc.isStale(objInfo.ModTime) { cc.isStale(objInfo.ModTime) {
if err = removeAll(pathJoin(c.dir, obj.Name())); err != nil { if err = removeAll(pathJoin(c.dir, obj.Name())); err != nil {
@@ -273,6 +281,10 @@ func (c *diskCache) Stat(ctx context.Context, bucket, object string) (oi ObjectI
} }
oi.Bucket = bucket oi.Bucket = bucket
oi.Name = object oi.Name = object
if err = decryptCacheObjectETag(&oi); err != nil {
return oi, err
}
return return
} }
@@ -323,7 +335,24 @@ func (c *diskCache) saveMetadata(ctx context.Context, bucket, object string, met
// Backend metadata could have changed through server side copy - reset cache metadata if that is the case // Backend metadata could have changed through server side copy - reset cache metadata if that is the case
func (c *diskCache) updateMetadataIfChanged(ctx context.Context, bucket, object string, bkObjectInfo, cacheObjInfo ObjectInfo) error { func (c *diskCache) updateMetadataIfChanged(ctx context.Context, bucket, object string, bkObjectInfo, cacheObjInfo ObjectInfo) error {
if !reflect.DeepEqual(bkObjectInfo.UserDefined, cacheObjInfo.UserDefined) ||
bkMeta := make(map[string]string)
cacheMeta := make(map[string]string)
for k, v := range bkObjectInfo.UserDefined {
if hasPrefix(k, ReservedMetadataPrefix) {
// Do not need to send any internal metadata
continue
}
bkMeta[http.CanonicalHeaderKey(k)] = v
}
for k, v := range cacheObjInfo.UserDefined {
if hasPrefix(k, ReservedMetadataPrefix) {
// Do not need to send any internal metadata
continue
}
cacheMeta[http.CanonicalHeaderKey(k)] = v
}
if !reflect.DeepEqual(bkMeta, cacheMeta) ||
bkObjectInfo.ETag != cacheObjInfo.ETag || bkObjectInfo.ETag != cacheObjInfo.ETag ||
bkObjectInfo.ContentType != cacheObjInfo.ContentType || bkObjectInfo.ContentType != cacheObjInfo.ContentType ||
bkObjectInfo.Expires != cacheObjInfo.Expires { bkObjectInfo.Expires != cacheObjInfo.Expires {
@@ -337,11 +366,11 @@ func getCacheSHADir(dir, bucket, object string) string {
} }
// Cache data to disk with bitrot checksum added for each block of 1MB // Cache data to disk with bitrot checksum added for each block of 1MB
func (c *diskCache) bitrotWriteToCache(ctx context.Context, cachePath string, reader io.Reader, size int64) (int64, error) { func (c *diskCache) bitrotWriteToCache(ctx context.Context, cachePath string, reader io.Reader, size uint64) (int64, error) {
if err := os.MkdirAll(cachePath, 0777); err != nil { if err := os.MkdirAll(cachePath, 0777); err != nil {
return 0, err return 0, err
} }
bufSize := int64(readSizeV1) bufSize := uint64(readSizeV1)
if size > 0 && bufSize > size { if size > 0 && bufSize > size {
bufSize = size bufSize = size
} }
@@ -367,28 +396,29 @@ func (c *diskCache) bitrotWriteToCache(ctx context.Context, cachePath string, re
bufp := c.pool.Get().(*[]byte) bufp := c.pool.Get().(*[]byte)
defer c.pool.Put(bufp) defer c.pool.Put(bufp)
var n, n2 int
for { for {
n, err := io.ReadFull(reader, *bufp) n, err = io.ReadFull(reader, *bufp)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF && err != io.ErrClosedPipe { if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
return 0, err return 0, err
} }
eof := err == io.EOF || err == io.ErrUnexpectedEOF || err == io.ErrClosedPipe eof := err == io.EOF || err == io.ErrUnexpectedEOF
if n == 0 && size != 0 { if n == 0 && size != 0 {
// Reached EOF, nothing more to be done. // Reached EOF, nothing more to be done.
break break
} }
h.Reset() h.Reset()
if _, err := h.Write((*bufp)[:n]); err != nil { if _, err = h.Write((*bufp)[:n]); err != nil {
return 0, err return 0, err
} }
hashBytes := h.Sum(nil) hashBytes := h.Sum(nil)
if _, err = f.Write(hashBytes); err != nil { if _, err = f.Write(hashBytes); err != nil {
return 0, err return 0, err
} }
if _, err = f.Write((*bufp)[:n]); err != nil { if n2, err = f.Write((*bufp)[:n]); err != nil {
return 0, err return 0, err
} }
bytesWritten += int64(n) bytesWritten += int64(n2)
if eof { if eof {
break break
} }
@@ -396,6 +426,39 @@ func (c *diskCache) bitrotWriteToCache(ctx context.Context, cachePath string, re
return bytesWritten, nil return bytesWritten, nil
} }
func newCacheEncryptReader(content io.Reader, bucket, object string, metadata map[string]string) (r io.Reader, err error) {
objectEncryptionKey, err := newCacheEncryptMetadata(bucket, object, metadata)
if err != nil {
return nil, err
}
reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey[:], MinVersion: sio.Version20})
if err != nil {
return nil, crypto.ErrInvalidCustomerKey
}
return reader, nil
}
func newCacheEncryptMetadata(bucket, object string, metadata map[string]string) ([]byte, error) {
var sealedKey crypto.SealedKey
if globalCacheKMS == nil {
return nil, errKMSNotConfigured
}
key, encKey, err := globalCacheKMS.GenerateKey(globalCacheKMSKeyID, crypto.Context{bucket: path.Join(bucket, object)})
if err != nil {
return nil, err
}
objectKey := crypto.GenerateKey(key, rand.Reader)
sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, object)
crypto.S3.CreateMetadata(metadata, globalCacheKMSKeyID, encKey, sealedKey)
if etag, ok := metadata["etag"]; ok {
metadata["etag"] = hex.EncodeToString(objectKey.SealETag([]byte(etag)))
}
metadata[SSECacheEncrypted] = ""
return objectKey[:], nil
}
// Caches the object to disk // Caches the object to disk
func (c *diskCache) Put(ctx context.Context, bucket, object string, data io.Reader, size int64, opts ObjectOptions) error { func (c *diskCache) Put(ctx context.Context, bucket, object string, data io.Reader, size int64, opts ObjectOptions) error {
if c.diskUsageHigh() { if c.diskUsageHigh() {
@@ -417,14 +480,33 @@ func (c *diskCache) Put(ctx context.Context, bucket, object string, data io.Read
bufSize = size bufSize = size
} }
n, err := c.bitrotWriteToCache(ctx, cachePath, data, size) var metadata = make(map[string]string)
for k, v := range opts.UserDefined {
metadata[k] = v
}
var reader = data
var actualSize = uint64(size)
var err error
if globalCacheKMS != nil {
reader, err = newCacheEncryptReader(data, bucket, object, metadata)
if err != nil {
return err
}
actualSize, _ = sio.EncryptedSize(uint64(size))
}
n, err := c.bitrotWriteToCache(ctx, cachePath, reader, actualSize)
if IsErr(err, baseErrs...) { if IsErr(err, baseErrs...) {
c.setOnline(false) c.setOnline(false)
} }
if err != nil { if err != nil {
removeAll(cachePath)
return err return err
} }
return c.saveMetadata(ctx, bucket, object, opts.UserDefined, n) if actualSize != uint64(n) {
removeAll(cachePath)
return IncompleteBody{}
}
return c.saveMetadata(ctx, bucket, object, metadata, n)
} }
// checks streaming bitrot checksum of cached object before returning data // checks streaming bitrot checksum of cached object before returning data
@@ -484,7 +566,6 @@ func (c *diskCache) bitrotReadFromCache(ctx context.Context, filePath string, of
if _, err := io.ReadFull(rc, checksumHash); err != nil { if _, err := io.ReadFull(rc, checksumHash); err != nil {
return err return err
} }
h.Reset() h.Reset()
n, err := io.ReadFull(rc, *bufp) n, err := io.ReadFull(rc, *bufp)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
@@ -527,7 +608,7 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
var objInfo ObjectInfo var objInfo ObjectInfo
cacheObjPath := getCacheSHADir(c.dir, bucket, object) cacheObjPath := getCacheSHADir(c.dir, bucket, object)
if objInfo, err = c.statCache(ctx, cacheObjPath); err != nil { if objInfo, err = c.Stat(ctx, bucket, object); err != nil {
return nil, toObjectErr(err, bucket, object) return nil, toObjectErr(err, bucket, object)
} }
@@ -547,12 +628,15 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
filePath := path.Join(cacheObjPath, cacheDataFile) filePath := path.Join(cacheObjPath, cacheDataFile)
pr, pw := io.Pipe() pr, pw := io.Pipe()
go func() { go func() {
pw.CloseWithError(c.bitrotReadFromCache(ctx, filePath, off, length, pw)) err := c.bitrotReadFromCache(ctx, filePath, off, length, pw)
if err != nil {
removeAll(cacheObjPath)
}
pw.CloseWithError(err)
}() }()
// Cleanup function to cause the go routine above to exit, in // Cleanup function to cause the go routine above to exit, in
// case of incomplete read. // case of incomplete read.
pipeCloser := func() { pr.Close() } pipeCloser := func() { pr.Close() }
return fn(pr, h, opts.CheckCopyPrecondFn, pipeCloser) return fn(pr, h, opts.CheckCopyPrecondFn, pipeCloser)
} }
+51 -1
View File
@@ -17,8 +17,10 @@
package cmd package cmd
import ( import (
"encoding/hex"
"io" "io"
"os" "os"
"path"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -124,7 +126,7 @@ func backendDownError(err error) bool {
// IsCacheable returns if the object should be saved in the cache. // IsCacheable returns if the object should be saved in the cache.
func (o ObjectInfo) IsCacheable() bool { func (o ObjectInfo) IsCacheable() bool {
return !crypto.IsEncrypted(o.UserDefined) return !crypto.IsEncrypted(o.UserDefined) || globalCacheKMS != nil
} }
// reads file cached on disk from offset upto length // reads file cached on disk from offset upto length
@@ -147,6 +149,10 @@ func readCacheFileStream(filePath string, offset, length int64) (io.ReadCloser,
return nil, err return nil, err
} }
if err = os.Chtimes(filePath, time.Now(), st.ModTime()); err != nil {
return nil, err
}
// Verify if its not a regular file, since subsequent Seek is undefined. // Verify if its not a regular file, since subsequent Seek is undefined.
if !st.Mode().IsRegular() { if !st.Mode().IsRegular() {
return nil, errIsNotRegular return nil, errIsNotRegular
@@ -168,3 +174,47 @@ func readCacheFileStream(filePath string, offset, length int64) (io.ReadCloser,
io.Closer io.Closer
}{Reader: io.LimitReader(fr, length), Closer: fr}, nil }{Reader: io.LimitReader(fr, length), Closer: fr}, nil
} }
func isCacheEncrypted(meta map[string]string) bool {
_, ok := meta[SSECacheEncrypted]
return ok
}
// decryptCacheObjectETag tries to decrypt the ETag saved in encrypted format using the cache KMS
func decryptCacheObjectETag(info *ObjectInfo) error {
// Directories are never encrypted.
if info.IsDir {
return nil
}
encrypted := crypto.S3.IsEncrypted(info.UserDefined) && isCacheEncrypted(info.UserDefined)
switch {
case encrypted:
if globalCacheKMS == nil {
return errKMSNotConfigured
}
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(info.UserDefined)
if err != nil {
return err
}
extKey, err := globalCacheKMS.UnsealKey(keyID, kmsKey, crypto.Context{info.Bucket: path.Join(info.Bucket, info.Name)})
if err != nil {
return err
}
var objectKey crypto.ObjectKey
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), info.Bucket, info.Name); err != nil {
return err
}
etagStr := tryDecryptETag(objectKey[:], info.ETag, false)
// backend ETag was hex encoded before encrypting, so hex decode to get actual ETag
etag, err := hex.DecodeString(etagStr)
if err != nil {
return err
}
info.ETag = string(etag)
return nil
}
return nil
}
+60 -6
View File
@@ -35,6 +35,7 @@ type CacheObjectLayer interface {
GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
DeleteObject(ctx context.Context, bucket, object string) error DeleteObject(ctx context.Context, bucket, object string) error
DeleteObjects(ctx context.Context, bucket string, objects []string) ([]error, error) DeleteObjects(ctx context.Context, bucket string, objects []string) ([]error, error)
PutObject(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
// Storage operations. // Storage operations.
StorageInfo(ctx context.Context) CacheStorageInfo StorageInfo(ctx context.Context) CacheStorageInfo
} }
@@ -58,6 +59,7 @@ type cacheObjects struct {
GetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) GetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
DeleteObjectFn func(ctx context.Context, bucket, object string) error DeleteObjectFn func(ctx context.Context, bucket, object string) error
DeleteObjectsFn func(ctx context.Context, bucket string, objects []string) ([]error, error) DeleteObjectsFn func(ctx context.Context, bucket string, objects []string) ([]error, error)
PutObjectFn func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
} }
func (c *cacheObjects) delete(ctx context.Context, dcache *diskCache, bucket, object string) (err error) { func (c *cacheObjects) delete(ctx context.Context, dcache *diskCache, bucket, object string) (err error) {
@@ -231,13 +233,13 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
pipeReader, pipeWriter := io.Pipe() pipeReader, pipeWriter := io.Pipe()
teeReader := io.TeeReader(bkReader, pipeWriter) teeReader := io.TeeReader(bkReader, pipeWriter)
go func() { go func() {
putErr := dcache.Put(ctx, bucket, object, io.LimitReader(pipeReader, bkReader.ObjInfo.Size), bkReader.ObjInfo.Size, ObjectOptions{UserDefined: getMetadata(bkReader.ObjInfo)}) putErr := c.put(ctx, dcache, bucket, object, io.LimitReader(pipeReader, bkReader.ObjInfo.Size), bkReader.ObjInfo.Size, ObjectOptions{UserDefined: getMetadata(bkReader.ObjInfo)})
// close the write end of the pipe, so the error gets // close the write end of the pipe, so the error gets
// propagated to getObjReader // propagated to getObjReader
pipeWriter.CloseWithError(putErr) pipeWriter.CloseWithError(putErr)
}() }()
cleanupBackend := func() { bkReader.Close() } cleanupBackend := func() { bkReader.Close() }
cleanupPipe := func() { pipeReader.Close() } cleanupPipe := func() { pipeWriter.Close() }
return NewGetObjectReaderFromReader(teeReader, bkReader.ObjInfo, opts.CheckCopyPrecondFn, cleanupBackend, cleanupPipe) return NewGetObjectReaderFromReader(teeReader, bkReader.ObjInfo, opts.CheckCopyPrecondFn, cleanupBackend, cleanupPipe)
} }
@@ -263,7 +265,6 @@ func (c *cacheObjects) GetObjectInfo(ctx context.Context, bucket, object string,
return cachedObjInfo, nil return cachedObjInfo, nil
} }
} }
objInfo, err := getObjectInfoFn(ctx, bucket, object, opts) objInfo, err := getObjectInfoFn(ctx, bucket, object, opts)
if err != nil { if err != nil {
if _, ok := err.(ObjectNotFound); ok { if _, ok := err.(ObjectNotFound); ok {
@@ -445,8 +446,9 @@ func checkAtimeSupport(dir string) (err error) {
return return
} }
func (c *cacheObjects) migrateCacheFromV1toV2(ctx context.Context) { func (c *cacheObjects) migrateCacheFromV1toV2(ctx context.Context) {
logger.StartupMessage(colorBlue("Cache migration initiated ....")) logStartupMessage(colorBlue("Cache migration initiated ...."))
var wg = &sync.WaitGroup{}
var wg sync.WaitGroup
errs := make([]error, len(c.cache)) errs := make([]error, len(c.cache))
for i, dc := range c.cache { for i, dc := range c.cache {
if dc == nil { if dc == nil {
@@ -480,7 +482,56 @@ func (c *cacheObjects) migrateCacheFromV1toV2(ctx context.Context) {
c.migMutex.Lock() c.migMutex.Lock()
defer c.migMutex.Unlock() defer c.migMutex.Unlock()
c.migrating = false c.migrating = false
logger.StartupMessage(colorBlue("Cache migration completed successfully.")) logStartupMessage(colorBlue("Cache migration completed successfully."))
}
// PutObject - caches the uploaded object for single Put operations
func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
putObjectFn := c.PutObjectFn
dcache, err := c.getCacheToLoc(ctx, bucket, object)
if err != nil {
// disk cache could not be located,execute backend call.
return putObjectFn(ctx, bucket, object, r, opts)
}
size := r.Size()
if c.skipCache() {
return putObjectFn(ctx, bucket, object, r, opts)
}
// fetch from backend if there is no space on cache drive
if !dcache.diskAvailable(size) {
return putObjectFn(ctx, bucket, object, r, opts)
}
if opts.ServerSideEncryption != nil {
return putObjectFn(ctx, bucket, object, r, opts)
}
// fetch from backend if cache exclude pattern or cache-control
// directive set to exclude
if c.isCacheExclude(bucket, object) {
dcache.Delete(ctx, bucket, object)
return putObjectFn(ctx, bucket, object, r, opts)
}
objInfo, err = putObjectFn(ctx, bucket, object, r, opts)
if err == nil {
go func() {
// fill cache in the background
bReader, bErr := c.GetObjectNInfoFn(ctx, bucket, object, nil, http.Header{}, readLock, ObjectOptions{})
if bErr != nil {
return
}
defer bReader.Close()
oi, err := c.stat(ctx, dcache, bucket, object)
// avoid cache overwrite if another background routine filled cache
if err != nil || oi.ETag != bReader.ObjInfo.ETag {
c.put(ctx, dcache, bucket, object, bReader, bReader.ObjInfo.Size, ObjectOptions{UserDefined: getMetadata(bReader.ObjInfo)})
}
}()
}
return objInfo, err
} }
// Returns cacheObjects for use by Server. // Returns cacheObjects for use by Server.
@@ -513,6 +564,9 @@ func newServerCacheObjects(ctx context.Context, config CacheConfig) (CacheObject
} }
return errs, nil return errs, nil
}, },
PutObjectFn: func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
return newObjectLayerFn().PutObject(ctx, bucket, object, data, opts)
},
} }
if migrateSw { if migrateSw {
go c.migrateCacheFromV1toV2(ctx) go c.migrateCacheFromV1toV2(ctx)
+17 -248
View File
@@ -32,7 +32,6 @@ import (
"github.com/minio/minio-go/v6/pkg/encrypt" "github.com/minio/minio-go/v6/pkg/encrypt"
"github.com/minio/minio/cmd/crypto" "github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/ioutil"
sha256 "github.com/minio/sha256-simd" sha256 "github.com/minio/sha256-simd"
"github.com/minio/sio" "github.com/minio/sio"
) )
@@ -64,23 +63,6 @@ const (
) )
const (
// SSESealAlgorithmDareSha256 specifies DARE as authenticated en/decryption scheme and SHA256 as cryptographic
// hash function. The key derivation of DARE-SHA256 is not optimal and does not include the object path.
// It is considered legacy and should not be used anymore.
SSESealAlgorithmDareSha256 = "DARE-SHA256"
// SSESealAlgorithmDareV2HmacSha256 specifies DAREv2 as authenticated en/decryption scheme and SHA256 as cryptographic
// hash function for the HMAC PRF.
SSESealAlgorithmDareV2HmacSha256 = "DAREv2-HMAC-SHA256"
)
// hasServerSideEncryptionHeader returns true if the given HTTP header
// contains server-side-encryption.
func hasServerSideEncryptionHeader(header http.Header) bool {
return crypto.S3.IsRequested(header) || crypto.SSEC.IsRequested(header)
}
// isEncryptedMultipart returns true if the current object is // isEncryptedMultipart returns true if the current object is
// uploaded by the user using multipart mechanism: // uploaded by the user using multipart mechanism:
// initiate new multipart, upload part, complete upload // initiate new multipart, upload part, complete upload
@@ -279,6 +261,23 @@ func decryptObjectInfo(key []byte, bucket, object string, metadata map[string]st
switch { switch {
default: default:
return nil, errObjectTampered return nil, errObjectTampered
case crypto.S3.IsEncrypted(metadata) && isCacheEncrypted(metadata):
if globalCacheKMS == nil {
return nil, errKMSNotConfigured
}
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(metadata)
if err != nil {
return nil, err
}
extKey, err := globalCacheKMS.UnsealKey(keyID, kmsKey, crypto.Context{bucket: path.Join(bucket, object)})
if err != nil {
return nil, err
}
var objectKey crypto.ObjectKey
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, object); err != nil {
return nil, err
}
return objectKey[:], nil
case crypto.S3.IsEncrypted(metadata): case crypto.S3.IsEncrypted(metadata):
if GlobalKMS == nil { if GlobalKMS == nil {
return nil, errKMSNotConfigured return nil, errKMSNotConfigured
@@ -580,236 +579,6 @@ func (d *DecryptBlocksReader) Read(p []byte) (int, error) {
return len(p), nil return len(p), nil
} }
// DecryptBlocksWriter - decrypts multipart parts, while implementing
// a io.Writer compatible interface.
type DecryptBlocksWriter struct {
// Original writer where the plain data will be written
writer io.Writer
// Current decrypter for the current encrypted data block
decrypter io.WriteCloser
// Start sequence number
startSeqNum uint32
// Current part index
partIndex int
// Parts information
parts []ObjectPartInfo
req *http.Request
bucket, object string
metadata map[string]string
partEncRelOffset int64
copySource bool
// Customer Key
customerKeyHeader string
}
func (w *DecryptBlocksWriter) buildDecrypter(partID int) error {
m := make(map[string]string)
for k, v := range w.metadata {
m[k] = v
}
// Initialize the first decrypter, new decrypters will be initialized in Write() operation as needed.
var key []byte
var err error
if w.copySource {
if crypto.SSEC.IsEncrypted(w.metadata) {
w.req.Header.Set(crypto.SSECopyKey, w.customerKeyHeader)
key, err = ParseSSECopyCustomerRequest(w.req.Header, w.metadata)
}
} else {
if crypto.SSEC.IsEncrypted(w.metadata) {
w.req.Header.Set(crypto.SSECKey, w.customerKeyHeader)
key, err = ParseSSECustomerRequest(w.req)
}
}
if err != nil {
return err
}
objectEncryptionKey, err := decryptObjectInfo(key, w.bucket, w.object, m)
if err != nil {
return err
}
var partIDbin [4]byte
binary.LittleEndian.PutUint32(partIDbin[:], uint32(partID)) // marshal part ID
mac := hmac.New(sha256.New, objectEncryptionKey) // derive part encryption key from part ID and object key
mac.Write(partIDbin[:])
partEncryptionKey := mac.Sum(nil)
// make sure to provide a NopCloser such that a Close
// on sio.decryptWriter doesn't close the underlying writer's
// close which perhaps can close the stream prematurely.
decrypter, err := newDecryptWriterWithObjectKey(ioutil.NopCloser(w.writer), partEncryptionKey, w.startSeqNum, m)
if err != nil {
return err
}
if w.decrypter != nil {
// Pro-actively close the writer such that any pending buffers
// are flushed already before we allocate a new decrypter.
err = w.decrypter.Close()
if err != nil {
return err
}
}
w.decrypter = decrypter
return nil
}
func (w *DecryptBlocksWriter) Write(p []byte) (int, error) {
var err error
var n1 int
if int64(len(p)) < w.parts[w.partIndex].Size-w.partEncRelOffset {
n1, err = w.decrypter.Write(p)
if err != nil {
return 0, err
}
w.partEncRelOffset += int64(n1)
} else {
n1, err = w.decrypter.Write(p[:w.parts[w.partIndex].Size-w.partEncRelOffset])
if err != nil {
return 0, err
}
// We should now proceed to next part, reset all values appropriately.
w.partEncRelOffset = 0
w.startSeqNum = 0
w.partIndex++
err = w.buildDecrypter(w.partIndex + 1)
if err != nil {
return 0, err
}
n1, err = w.decrypter.Write(p[n1:])
if err != nil {
return 0, err
}
w.partEncRelOffset += int64(n1)
}
return len(p), nil
}
// Close closes the LimitWriter. It behaves like io.Closer.
func (w *DecryptBlocksWriter) Close() error {
if w.decrypter != nil {
err := w.decrypter.Close()
if err != nil {
return err
}
}
if closer, ok := w.writer.(io.Closer); ok {
return closer.Close()
}
return nil
}
// DecryptAllBlocksCopyRequest - setup a struct which can decrypt many concatenated encrypted data
// parts information helps to know the boundaries of each encrypted data block, this function decrypts
// all parts starting from part-1.
func DecryptAllBlocksCopyRequest(client io.Writer, r *http.Request, bucket, object string, objInfo ObjectInfo) (io.WriteCloser, int64, error) {
w, _, size, err := DecryptBlocksRequest(client, r, bucket, object, 0, objInfo.Size, objInfo, true)
return w, size, err
}
// DecryptBlocksRequest - setup a struct which can decrypt many concatenated encrypted data
// parts information helps to know the boundaries of each encrypted data block.
func DecryptBlocksRequest(client io.Writer, r *http.Request, bucket, object string, startOffset, length int64, objInfo ObjectInfo, copySource bool) (io.WriteCloser, int64, int64, error) {
var seqNumber uint32
var encStartOffset, encLength int64
if !isEncryptedMultipart(objInfo) {
seqNumber, encStartOffset, encLength = getEncryptedSinglePartOffsetLength(startOffset, length, objInfo)
var writer io.WriteCloser
var err error
if copySource {
writer, err = DecryptCopyRequest(client, r, bucket, object, objInfo.UserDefined)
} else {
writer, err = DecryptRequestWithSequenceNumber(client, r, bucket, object, seqNumber, objInfo.UserDefined)
}
if err != nil {
return nil, 0, 0, err
}
return writer, encStartOffset, encLength, nil
}
_, encStartOffset, encLength = getEncryptedMultipartsOffsetLength(startOffset, length, objInfo)
var partStartIndex int
var partStartOffset = startOffset
// Skip parts until final offset maps to a particular part offset.
for i, part := range objInfo.Parts {
decryptedSize, err := sio.DecryptedSize(uint64(part.Size))
if err != nil {
return nil, -1, -1, errObjectTampered
}
partStartIndex = i
// Offset is smaller than size we have reached the
// proper part offset, break out we start from
// this part index.
if partStartOffset < int64(decryptedSize) {
break
}
// Continue to look for next part.
partStartOffset -= int64(decryptedSize)
}
startSeqNum := partStartOffset / SSEDAREPackageBlockSize
partEncRelOffset := int64(startSeqNum) * (SSEDAREPackageBlockSize + SSEDAREPackageMetaSize)
w := &DecryptBlocksWriter{
writer: client,
startSeqNum: uint32(startSeqNum),
partEncRelOffset: partEncRelOffset,
parts: objInfo.Parts,
partIndex: partStartIndex,
req: r,
bucket: bucket,
object: object,
customerKeyHeader: r.Header.Get(crypto.SSECKey),
copySource: copySource,
}
w.metadata = map[string]string{}
// Copy encryption metadata for internal use.
for k, v := range objInfo.UserDefined {
w.metadata[k] = v
}
// Purge all the encryption headers.
delete(objInfo.UserDefined, crypto.SSEIV)
delete(objInfo.UserDefined, crypto.SSESealAlgorithm)
delete(objInfo.UserDefined, crypto.SSECSealedKey)
delete(objInfo.UserDefined, crypto.SSEMultipart)
if crypto.S3.IsEncrypted(objInfo.UserDefined) {
delete(objInfo.UserDefined, crypto.S3SealedKey)
delete(objInfo.UserDefined, crypto.S3KMSKeyID)
delete(objInfo.UserDefined, crypto.S3KMSSealedKey)
}
if w.copySource {
w.customerKeyHeader = r.Header.Get(crypto.SSECopyKey)
}
if err := w.buildDecrypter(w.parts[w.partIndex].Number); err != nil {
return nil, 0, 0, err
}
return w, encStartOffset, encLength, nil
}
// getEncryptedMultipartsOffsetLength - fetch sequence number, encrypted start offset and encrypted length. // getEncryptedMultipartsOffsetLength - fetch sequence number, encrypted start offset and encrypted length.
func getEncryptedMultipartsOffsetLength(offset, length int64, obj ObjectInfo) (uint32, int64, int64) { func getEncryptedMultipartsOffsetLength(offset, length int64, obj ObjectInfo) (uint32, int64, int64) {
// Calculate encrypted offset of a multipart object // Calculate encrypted offset of a multipart object
+9 -89
View File
@@ -28,86 +28,6 @@ import (
"github.com/minio/sio" "github.com/minio/sio"
) )
var hasServerSideEncryptionHeaderTests = []struct {
headers map[string]string
sseRequest bool
}{
{headers: map[string]string{crypto.SSECAlgorithm: "AES256", crypto.SSECKey: "key", crypto.SSECKeyMD5: "md5"}, sseRequest: true}, // 0
{headers: map[string]string{crypto.SSECAlgorithm: "AES256"}, sseRequest: true}, // 1
{headers: map[string]string{crypto.SSECKey: "key"}, sseRequest: true}, // 2
{headers: map[string]string{crypto.SSECKeyMD5: "md5"}, sseRequest: true}, // 3
{headers: map[string]string{}, sseRequest: false}, // 4
{headers: map[string]string{crypto.SSECopyAlgorithm + " ": "AES256", " " + crypto.SSECopyKey: "key", crypto.SSECopyKeyMD5 + " ": "md5"}, sseRequest: false}, // 5
{headers: map[string]string{crypto.SSECopyAlgorithm: "", crypto.SSECopyKey: "", crypto.SSECopyKeyMD5: ""}, sseRequest: false}, // 6
{headers: map[string]string{crypto.SSEHeader: ""}, sseRequest: true}, // 7
}
func TestHasServerSideEncryptionHeader(t *testing.T) {
for i, test := range hasServerSideEncryptionHeaderTests {
headers := http.Header{}
for k, v := range test.headers {
headers.Set(k, v)
}
if hasServerSideEncryptionHeader(headers) != test.sseRequest {
t.Errorf("Test %d: Expected hasServerSideEncryptionHeader to return %v", i, test.sseRequest)
}
}
}
var hasSSECopyCustomerHeaderTests = []struct {
headers map[string]string
sseRequest bool
}{
{headers: map[string]string{crypto.SSECopyAlgorithm: "AES256", crypto.SSECopyKey: "key", crypto.SSECopyKeyMD5: "md5"}, sseRequest: true}, // 0
{headers: map[string]string{crypto.SSECopyAlgorithm: "AES256"}, sseRequest: true}, // 1
{headers: map[string]string{crypto.SSECopyKey: "key"}, sseRequest: true}, // 2
{headers: map[string]string{crypto.SSECopyKeyMD5: "md5"}, sseRequest: true}, // 3
{headers: map[string]string{}, sseRequest: false}, // 4
{headers: map[string]string{crypto.SSECopyAlgorithm + " ": "AES256", " " + crypto.SSECopyKey: "key", crypto.SSECopyKeyMD5 + " ": "md5"}, sseRequest: false}, // 5
{headers: map[string]string{crypto.SSECopyAlgorithm: "", crypto.SSECopyKey: "", crypto.SSECopyKeyMD5: ""}, sseRequest: true}, // 6
{headers: map[string]string{crypto.SSEHeader: ""}, sseRequest: false}, // 7
}
func TestIsSSECopyCustomerRequest(t *testing.T) {
for i, test := range hasSSECopyCustomerHeaderTests {
headers := http.Header{}
for k, v := range test.headers {
headers.Set(k, v)
}
if crypto.SSECopy.IsRequested(headers) != test.sseRequest {
t.Errorf("Test %d: Expected crypto.SSECopy.IsRequested to return %v", i, test.sseRequest)
}
}
}
var hasSSECustomerHeaderTests = []struct {
headers map[string]string
sseRequest bool
}{
{headers: map[string]string{crypto.SSECAlgorithm: "AES256", crypto.SSECKey: "key", crypto.SSECKeyMD5: "md5"}, sseRequest: true}, // 0
{headers: map[string]string{crypto.SSECAlgorithm: "AES256"}, sseRequest: true}, // 1
{headers: map[string]string{crypto.SSECKey: "key"}, sseRequest: true}, // 2
{headers: map[string]string{crypto.SSECKeyMD5: "md5"}, sseRequest: true}, // 3
{headers: map[string]string{}, sseRequest: false}, // 4
{headers: map[string]string{crypto.SSECAlgorithm + " ": "AES256", " " + crypto.SSECKey: "key", crypto.SSECKeyMD5 + " ": "md5"}, sseRequest: false}, // 5
{headers: map[string]string{crypto.SSECAlgorithm: "", crypto.SSECKey: "", crypto.SSECKeyMD5: ""}, sseRequest: true}, // 6
{headers: map[string]string{crypto.SSEHeader: ""}, sseRequest: false}, // 7
}
func TestHasSSECustomerHeader(t *testing.T) {
for i, test := range hasSSECustomerHeaderTests {
headers := http.Header{}
for k, v := range test.headers {
headers.Set(k, v)
}
if crypto.SSEC.IsRequested(headers) != test.sseRequest {
t.Errorf("Test %d: Expected hasSSECustomerHeader to return %v", i, test.sseRequest)
}
}
}
var encryptRequestTests = []struct { var encryptRequestTests = []struct {
header map[string]string header map[string]string
metadata map[string]string metadata map[string]string
@@ -173,7 +93,7 @@ var decryptRequestTests = []struct {
crypto.SSECKeyMD5: "7PpPLAK26ONlVUGOWlusfg==", crypto.SSECKeyMD5: "7PpPLAK26ONlVUGOWlusfg==",
}, },
metadata: map[string]string{ metadata: map[string]string{
crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256, crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm,
crypto.SSEIV: "7nQqotA8xgrPx6QK7Ap3GCfjKitqJSrGP7xzgErSJlw=", crypto.SSEIV: "7nQqotA8xgrPx6QK7Ap3GCfjKitqJSrGP7xzgErSJlw=",
crypto.SSECSealedKey: "EAAfAAAAAAD7v1hQq3PFRUHsItalxmrJqrOq6FwnbXNarxOOpb8jTWONPPKyM3Gfjkjyj6NCf+aB/VpHCLCTBA==", crypto.SSECSealedKey: "EAAfAAAAAAD7v1hQq3PFRUHsItalxmrJqrOq6FwnbXNarxOOpb8jTWONPPKyM3Gfjkjyj6NCf+aB/VpHCLCTBA==",
}, },
@@ -188,7 +108,7 @@ var decryptRequestTests = []struct {
crypto.SSECKeyMD5: "7PpPLAK26ONlVUGOWlusfg==", crypto.SSECKeyMD5: "7PpPLAK26ONlVUGOWlusfg==",
}, },
metadata: map[string]string{ metadata: map[string]string{
crypto.SSESealAlgorithm: SSESealAlgorithmDareV2HmacSha256, crypto.SSESealAlgorithm: crypto.SealAlgorithm,
crypto.SSEIV: "qEqmsONcorqlcZXJxaw32H04eyXyXwUgjHzlhkaIYrU=", crypto.SSEIV: "qEqmsONcorqlcZXJxaw32H04eyXyXwUgjHzlhkaIYrU=",
crypto.SSECSealedKey: "IAAfAIM14ugTGcM/dIrn4iQMrkl1sjKyeBQ8FBEvRebYj8vWvxG+0cJRpC6NXRU1wJN50JaUOATjO7kz0wZ2mA==", crypto.SSECSealedKey: "IAAfAIM14ugTGcM/dIrn4iQMrkl1sjKyeBQ8FBEvRebYj8vWvxG+0cJRpC6NXRU1wJN50JaUOATjO7kz0wZ2mA==",
}, },
@@ -218,7 +138,7 @@ var decryptRequestTests = []struct {
crypto.SSECKeyMD5: "bY4wkxQejw9mUJfo72k53A==", crypto.SSECKeyMD5: "bY4wkxQejw9mUJfo72k53A==",
}, },
metadata: map[string]string{ metadata: map[string]string{
crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256, crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm,
crypto.SSEIV: "RrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", crypto.SSEIV: "RrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
crypto.SSECSealedKey: "SY5E9AvI2tI7/nUrUAssIGE32Hcs4rR9z/CUuPqu5N4=", crypto.SSECSealedKey: "SY5E9AvI2tI7/nUrUAssIGE32Hcs4rR9z/CUuPqu5N4=",
}, },
@@ -233,7 +153,7 @@ var decryptRequestTests = []struct {
crypto.SSECKeyMD5: "bY4wkxQejw9mUJfo72k53A==", crypto.SSECKeyMD5: "bY4wkxQejw9mUJfo72k53A==",
}, },
metadata: map[string]string{ metadata: map[string]string{
crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256, crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm,
crypto.SSEIV: "XAm0dRrJsEsyPb1UuFNezv1bl9ehxuYsgUVC/MUctE2k=", crypto.SSEIV: "XAm0dRrJsEsyPb1UuFNezv1bl9ehxuYsgUVC/MUctE2k=",
crypto.SSECSealedKey: "SY5E9AvI2tI7/nUrUAssIGE32Hds4rR9z/CUuPqu5N4=", crypto.SSECSealedKey: "SY5E9AvI2tI7/nUrUAssIGE32Hds4rR9z/CUuPqu5N4=",
}, },
@@ -248,7 +168,7 @@ var decryptRequestTests = []struct {
crypto.SSECKeyMD5: "7PpPLAK26ONlVUGOWlusfg==", crypto.SSECKeyMD5: "7PpPLAK26ONlVUGOWlusfg==",
}, },
metadata: map[string]string{ metadata: map[string]string{
crypto.SSESealAlgorithm: SSESealAlgorithmDareV2HmacSha256, crypto.SSESealAlgorithm: crypto.SealAlgorithm,
crypto.SSEIV: "qEqmsONcorqlcZXJxaw32H04eyXyXwUgjHzlhkaIYrU=", crypto.SSEIV: "qEqmsONcorqlcZXJxaw32H04eyXyXwUgjHzlhkaIYrU=",
crypto.SSECSealedKey: "IAAfAIM14ugTGcM/dIrn4iQMrkl1sjKyeBQ8FBEvRebYj8vWvxG+0cJRpC6NXRU1wJN50JaUOATjO7kz0wZ2mA==", crypto.SSECSealedKey: "IAAfAIM14ugTGcM/dIrn4iQMrkl1sjKyeBQ8FBEvRebYj8vWvxG+0cJRpC6NXRU1wJN50JaUOATjO7kz0wZ2mA==",
}, },
@@ -298,12 +218,12 @@ var decryptObjectInfoTests = []struct {
expErr: nil, expErr: nil,
}, },
{ {
info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256}}, info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}},
headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}, headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}},
expErr: nil, expErr: nil,
}, },
{ {
info: ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256}}, info: ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}},
headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}, headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}},
expErr: nil, expErr: nil,
}, },
@@ -318,7 +238,7 @@ var decryptObjectInfoTests = []struct {
expErr: errInvalidEncryptionParameters, expErr: errInvalidEncryptionParameters,
}, },
{ {
info: ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256}}, info: ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}},
headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}, headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}},
expErr: errObjectTampered, expErr: errObjectTampered,
}, },
@@ -408,7 +328,7 @@ func TestGetDecryptedRange(t *testing.T) {
} }
udMap = func(isMulti bool) map[string]string { udMap = func(isMulti bool) map[string]string {
m := map[string]string{ m := map[string]string{
crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256, crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm,
crypto.SSEMultipart: "1", crypto.SSEMultipart: "1",
} }
if !isMulti { if !isMulti {
+1 -88
View File
@@ -1,5 +1,5 @@
/* /*
* MinIO Cloud Storage, (C) 2017 MinIO, Inc. * MinIO Cloud Storage, (C) 2017-2019 MinIO, Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"net/http"
"net/url" "net/url"
"os" "os"
"path" "path"
@@ -34,9 +33,6 @@ import (
"github.com/minio/cli" "github.com/minio/cli"
"github.com/minio/minio-go/v6/pkg/set" "github.com/minio/minio-go/v6/pkg/set"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/cpu"
"github.com/minio/minio/pkg/disk"
"github.com/minio/minio/pkg/mem"
"github.com/minio/minio/pkg/mountinfo" "github.com/minio/minio/pkg/mountinfo"
) )
@@ -287,89 +283,6 @@ func (endpoints EndpointList) UpdateIsLocal() error {
} }
// localEndpointsMemUsage - returns ServerMemUsageInfo for only the
// local endpoints from given list of endpoints
func localEndpointsMemUsage(endpoints EndpointList, r *http.Request) ServerMemUsageInfo {
var memUsages []mem.Usage
var historicUsages []mem.Usage
scratchSpace := map[string]bool{}
for _, endpoint := range endpoints {
// Only proceed for local endpoints
if endpoint.IsLocal {
if _, ok := scratchSpace[endpoint.Host]; ok {
continue
}
memUsages = append(memUsages, mem.GetUsage())
historicUsages = append(historicUsages, mem.GetHistoricUsage())
scratchSpace[endpoint.Host] = true
}
}
addr := r.Host
if globalIsDistXL {
addr = GetLocalPeer(endpoints)
}
return ServerMemUsageInfo{
Addr: addr,
Usage: memUsages,
HistoricUsage: historicUsages,
}
}
// localEndpointsCPULoad - returns ServerCPULoadInfo for only the
// local endpoints from given list of endpoints
func localEndpointsCPULoad(endpoints EndpointList, r *http.Request) ServerCPULoadInfo {
var cpuLoads []cpu.Load
var historicLoads []cpu.Load
scratchSpace := map[string]bool{}
for _, endpoint := range endpoints {
// Only proceed for local endpoints
if endpoint.IsLocal {
if _, ok := scratchSpace[endpoint.Host]; ok {
continue
}
cpuLoads = append(cpuLoads, cpu.GetLoad())
historicLoads = append(historicLoads, cpu.GetHistoricLoad())
scratchSpace[endpoint.Host] = true
}
}
addr := r.Host
if globalIsDistXL {
addr = GetLocalPeer(endpoints)
}
return ServerCPULoadInfo{
Addr: addr,
Load: cpuLoads,
HistoricLoad: historicLoads,
}
}
// localEndpointsDrivePerf - returns ServerDrivesPerfInfo for only the
// local endpoints from given list of endpoints
func localEndpointsDrivePerf(endpoints EndpointList, r *http.Request) ServerDrivesPerfInfo {
var dps []disk.Performance
for _, endpoint := range endpoints {
// Only proceed for local endpoints
if endpoint.IsLocal {
if _, err := os.Stat(endpoint.Path); err != nil {
// Since this drive is not available, add relevant details and proceed
dps = append(dps, disk.Performance{Path: endpoint.Path, Error: err.Error()})
continue
}
dp := disk.GetPerformance(pathJoin(endpoint.Path, minioMetaTmpBucket, mustGetUUID()))
dp.Path = endpoint.Path
dps = append(dps, dp)
}
}
addr := r.Host
if globalIsDistXL {
addr = GetLocalPeer(endpoints)
}
return ServerDrivesPerfInfo{
Addr: addr,
Perf: dps,
}
}
// NewEndpointList - returns new endpoint list based on input args. // NewEndpointList - returns new endpoint list based on input args.
func NewEndpointList(args ...string) (endpoints EndpointList, err error) { func NewEndpointList(args ...string) (endpoints EndpointList, err error) {
var endpointType EndpointType var endpointType EndpointType
+29 -15
View File
@@ -30,6 +30,7 @@ import (
"strings" "strings"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/sio"
) )
const ( const (
@@ -362,7 +363,7 @@ func loadAndValidateCacheFormat(ctx context.Context, drives []string) (formats [
// reads cached object on disk and writes it back after adding bitrot // reads cached object on disk and writes it back after adding bitrot
// hashsum per block as per the new disk cache format. // hashsum per block as per the new disk cache format.
func migrateData(ctx context.Context, c *diskCache, oldfile, destDir string) error { func migrateCacheData(ctx context.Context, c *diskCache, bucket, object, oldfile, destDir string, metadata map[string]string) error {
st, err := os.Stat(oldfile) st, err := os.Stat(oldfile)
if err != nil { if err != nil {
err = osErrToFSFileErr(err) err = osErrToFSFileErr(err)
@@ -372,7 +373,18 @@ func migrateData(ctx context.Context, c *diskCache, oldfile, destDir string) err
if err != nil { if err != nil {
return err return err
} }
_, err = c.bitrotWriteToCache(ctx, destDir, readCloser, st.Size()) var reader io.Reader = readCloser
actualSize := uint64(st.Size())
if globalCacheKMS != nil {
reader, err = newCacheEncryptReader(readCloser, bucket, object, metadata)
if err != nil {
return err
}
actualSize, _ = sio.EncryptedSize(uint64(st.Size()))
}
_, err = c.bitrotWriteToCache(ctx, destDir, reader, uint64(actualSize))
return err return err
} }
@@ -401,6 +413,7 @@ func migrateOldCache(ctx context.Context, c *diskCache) error {
} }
for _, bucket := range buckets { for _, bucket := range buckets {
bucket = strings.TrimSuffix(bucket, SlashSeparator)
var objMetaPaths []string var objMetaPaths []string
root := path.Join(oldCacheBucketsPath, bucket) root := path.Join(oldCacheBucketsPath, bucket)
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
@@ -422,10 +435,23 @@ func migrateOldCache(ctx context.Context, c *diskCache) error {
return err return err
} }
prevCachedPath := path.Join(c.dir, bucket, object) prevCachedPath := path.Join(c.dir, bucket, object)
// get old cached metadata
oldMetaPath := pathJoin(oldCacheBucketsPath, bucket, object, cacheMetaJSONFile)
metaPath := pathJoin(destdir, cacheMetaJSONFile)
metaBytes, err := ioutil.ReadFile(oldMetaPath)
if err != nil {
return err
}
// marshal cache metadata after adding version and stat info
meta := &cacheMeta{}
if err = json.Unmarshal(metaBytes, &meta); err != nil {
return err
}
// move cached object to new cache directory path // move cached object to new cache directory path
// migrate cache data and add bit-rot protection hash sum // migrate cache data and add bit-rot protection hash sum
// at the start of each block // at the start of each block
if err := migrateData(ctx, c, prevCachedPath, destdir); err != nil { if err := migrateCacheData(ctx, c, bucket, object, prevCachedPath, destdir, meta.Meta); err != nil {
continue continue
} }
stat, err := os.Stat(prevCachedPath) stat, err := os.Stat(prevCachedPath)
@@ -440,19 +466,7 @@ func migrateOldCache(ctx context.Context, c *diskCache) error {
if err := os.Remove(prevCachedPath); err != nil { if err := os.Remove(prevCachedPath); err != nil {
return err return err
} }
// move cached metadata after changing cache metadata version // move cached metadata after changing cache metadata version
oldMetaPath := pathJoin(oldCacheBucketsPath, bucket, object, cacheMetaJSONFile)
metaPath := pathJoin(destdir, cacheMetaJSONFile)
metaBytes, err := ioutil.ReadFile(oldMetaPath)
if err != nil {
return err
}
// marshal cache metadata after adding version and stat info
meta := &cacheMeta{}
if err = json.Unmarshal(metaBytes, &meta); err != nil {
return err
}
meta.Checksum = CacheChecksumInfoV1{Algorithm: HighwayHash256S.String(), Blocksize: cacheBlkSize} meta.Checksum = CacheChecksumInfoV1{Algorithm: HighwayHash256S.String(), Blocksize: cacheBlkSize}
meta.Version = cacheMetaVersion meta.Version = cacheMetaVersion
meta.Stat.Size = stat.Size() meta.Stat.Size = stat.Size()
-3
View File
@@ -20,9 +20,6 @@ package cmd
const ( const (
// Format config file carries backend format specific details. // Format config file carries backend format specific details.
formatConfigFile = "format.json" formatConfigFile = "format.json"
// Format config tmp file carries backend format.
formatConfigFileTmp = "format.json.tmp"
) )
const ( const (
+10 -17
View File
@@ -17,6 +17,7 @@
package cmd package cmd
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
@@ -291,16 +292,6 @@ func formatXLMigrateV2ToV3(export string) error {
return ioutil.WriteFile(formatPath, b, 0644) return ioutil.WriteFile(formatPath, b, 0644)
} }
// Returns true, if one of the errors is non-nil and is Unformatted disk.
func hasAnyErrorsUnformatted(errs []error) bool {
for _, err := range errs {
if err != nil && err == errUnformattedDisk {
return true
}
}
return false
}
// countErrs - count a specific error. // countErrs - count a specific error.
func countErrs(errs []error, err error) int { func countErrs(errs []error, err error) int {
var i = 0 var i = 0
@@ -325,7 +316,7 @@ func quorumUnformattedDisks(errs []error) bool {
// loadFormatXLAll - load all format config from all input disks in parallel. // loadFormatXLAll - load all format config from all input disks in parallel.
func loadFormatXLAll(storageDisks []StorageAPI) ([]*formatXLV3, []error) { func loadFormatXLAll(storageDisks []StorageAPI) ([]*formatXLV3, []error) {
// Initialize sync waitgroup. // Initialize sync waitgroup.
var wg = &sync.WaitGroup{} var wg sync.WaitGroup
// Initialize list of errors. // Initialize list of errors.
var sErrs = make([]error, len(storageDisks)) var sErrs = make([]error, len(storageDisks))
@@ -367,16 +358,18 @@ func saveFormatXL(disk StorageAPI, format interface{}) error {
return err return err
} }
tmpFormatJSON := mustGetUUID() + ".json"
// Purge any existing temporary file, okay to ignore errors here. // Purge any existing temporary file, okay to ignore errors here.
defer disk.DeleteFile(minioMetaBucket, formatConfigFileTmp) defer disk.DeleteFile(minioMetaBucket, tmpFormatJSON)
// Append file `format.json.tmp`. // Append file `format.json.tmp`.
if err = disk.AppendFile(minioMetaBucket, formatConfigFileTmp, formatBytes); err != nil { if err = disk.WriteAll(minioMetaBucket, tmpFormatJSON, bytes.NewReader(formatBytes)); err != nil {
return err return err
} }
// Rename file `format.json.tmp` --> `format.json`. // Rename file `uuid.json` --> `format.json`.
return disk.RenameFile(minioMetaBucket, formatConfigFileTmp, minioMetaBucket, formatConfigFile) return disk.RenameFile(minioMetaBucket, tmpFormatJSON, minioMetaBucket, formatConfigFile)
} }
var ignoredHiddenDirectories = []string{ var ignoredHiddenDirectories = []string{
@@ -652,7 +645,7 @@ func formatXLV3Check(reference *formatXLV3, format *formatXLV3) error {
func saveFormatXLAll(ctx context.Context, storageDisks []StorageAPI, formats []*formatXLV3) error { func saveFormatXLAll(ctx context.Context, storageDisks []StorageAPI, formats []*formatXLV3) error {
var errs = make([]error, len(storageDisks)) var errs = make([]error, len(storageDisks))
var wg = &sync.WaitGroup{} var wg sync.WaitGroup
// Write `format.json` to all disks. // Write `format.json` to all disks.
for index, disk := range storageDisks { for index, disk := range storageDisks {
@@ -812,7 +805,7 @@ func initFormatXLMetaVolume(storageDisks []StorageAPI, formats []*formatXLV3) er
// This happens for the first time, but keep this here since this // This happens for the first time, but keep this here since this
// is the only place where it can be made expensive optimizing all // is the only place where it can be made expensive optimizing all
// other calls. Create minio meta volume, if it doesn't exist yet. // other calls. Create minio meta volume, if it doesn't exist yet.
var wg = &sync.WaitGroup{} var wg sync.WaitGroup
// Initialize errs to collect errors inside go-routine. // Initialize errs to collect errors inside go-routine.
var errs = make([]error, len(storageDisks)) var errs = make([]error, len(storageDisks))
+5 -47
View File
@@ -27,10 +27,10 @@ import (
pathutil "path" pathutil "path"
"time" "time"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/lock" "github.com/minio/minio/pkg/lock"
"github.com/minio/minio/pkg/mimedb" "github.com/minio/minio/pkg/mimedb"
"github.com/tidwall/gjson"
) )
// FS format, and object metadata. // FS format, and object metadata.
@@ -203,44 +203,6 @@ func (m *fsMetaV1) WriteTo(lk *lock.LockedFile) (n int64, err error) {
return fi.Size(), nil return fi.Size(), nil
} }
func parseFSVersion(fsMetaBuf []byte) string {
return gjson.GetBytes(fsMetaBuf, "version").String()
}
func parseFSMetaMap(fsMetaBuf []byte) map[string]string {
// Get xlMetaV1.Meta map.
metaMapResult := gjson.GetBytes(fsMetaBuf, "meta").Map()
metaMap := make(map[string]string)
for key, valResult := range metaMapResult {
metaMap[key] = valResult.String()
}
return metaMap
}
func parseFSPartsArray(fsMetaBuf []byte) []ObjectPartInfo {
// Get xlMetaV1.Parts array
var partsArray []ObjectPartInfo
partsArrayResult := gjson.GetBytes(fsMetaBuf, "parts")
partsArrayResult.ForEach(func(key, part gjson.Result) bool {
partJSON := part.String()
number := gjson.Get(partJSON, "number").Int()
name := gjson.Get(partJSON, "name").String()
etag := gjson.Get(partJSON, "etag").String()
size := gjson.Get(partJSON, "size").Int()
actualSize := gjson.Get(partJSON, "actualSize").Int()
partsArray = append(partsArray, ObjectPartInfo{
Number: int(number),
Name: name,
ETag: etag,
Size: size,
ActualSize: int64(actualSize),
})
return true
})
return partsArray
}
func (m *fsMetaV1) ReadFrom(ctx context.Context, lk *lock.LockedFile) (n int64, err error) { func (m *fsMetaV1) ReadFrom(ctx context.Context, lk *lock.LockedFile) (n int64, err error) {
var fsMetaBuf []byte var fsMetaBuf []byte
fi, err := lk.Stat() fi, err := lk.Stat()
@@ -260,8 +222,10 @@ func (m *fsMetaV1) ReadFrom(ctx context.Context, lk *lock.LockedFile) (n int64,
return 0, io.EOF return 0, io.EOF
} }
// obtain version. var json = jsoniter.ConfigCompatibleWithStandardLibrary
m.Version = parseFSVersion(fsMetaBuf) if err = json.Unmarshal(fsMetaBuf, m); err != nil {
return 0, err
}
// Verify if the format is valid, return corrupted format // Verify if the format is valid, return corrupted format
// for unrecognized formats. // for unrecognized formats.
@@ -271,12 +235,6 @@ func (m *fsMetaV1) ReadFrom(ctx context.Context, lk *lock.LockedFile) (n int64,
return 0, errCorruptedFormat return 0, errCorruptedFormat
} }
// obtain parts information
m.Parts = parseFSPartsArray(fsMetaBuf)
// obtain metadata.
m.Meta = parseFSMetaMap(fsMetaBuf)
// Success. // Success.
return int64(len(fsMetaBuf)), nil return int64(len(fsMetaBuf)), nil
} }
+10 -2
View File
@@ -28,6 +28,7 @@ import (
"strings" "strings"
"time" "time"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
mioutil "github.com/minio/minio/pkg/ioutil" mioutil "github.com/minio/minio/pkg/ioutil"
) )
@@ -456,7 +457,8 @@ func (fs *FSObjects) ListObjectParts(ctx context.Context, bucket, object, upload
} }
for i, part := range result.Parts { for i, part := range result.Parts {
var stat os.FileInfo var stat os.FileInfo
stat, err = fsStatFile(ctx, pathJoin(uploadIDDir, fs.encodePartFile(part.PartNumber, part.ETag, part.ActualSize))) stat, err = fsStatFile(ctx, pathJoin(uploadIDDir,
fs.encodePartFile(part.PartNumber, part.ETag, part.ActualSize)))
if err != nil { if err != nil {
return result, toObjectErr(err) return result, toObjectErr(err)
} }
@@ -470,7 +472,13 @@ func (fs *FSObjects) ListObjectParts(ctx context.Context, bucket, object, upload
return result, err return result, err
} }
result.UserDefined = parseFSMetaMap(fsMetaBytes) var fsMeta fsMetaV1
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(fsMetaBytes, &fsMeta); err != nil {
return result, err
}
result.UserDefined = fsMeta.Meta
return result, nil return result, nil
} }
+22 -21
View File
@@ -95,7 +95,6 @@ func TestPutObjectPartFaultyDisk(t *testing.T) {
defer os.RemoveAll(disk) defer os.RemoveAll(disk)
obj := initFSObjects(disk, t) obj := initFSObjects(disk, t)
fs := obj.(*FSObjects)
bucketName := "bucket" bucketName := "bucket"
objectName := "object" objectName := "object"
data := []byte("12345") data := []byte("12345")
@@ -105,7 +104,7 @@ func TestPutObjectPartFaultyDisk(t *testing.T) {
t.Fatal("Cannot create bucket, err: ", err) t.Fatal("Cannot create bucket, err: ", err)
} }
uploadID, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}}) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}})
if err != nil { if err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
@@ -113,12 +112,15 @@ func TestPutObjectPartFaultyDisk(t *testing.T) {
md5Hex := getMD5Hash(data) md5Hex := getMD5Hash(data)
sha256sum := "" sha256sum := ""
fs.fsPath = filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) newDisk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
_, err = fs.PutObjectPart(context.Background(), bucketName, objectName, uploadID, 1, mustGetPutObjReader(t, bytes.NewReader(data), dataLen, md5Hex, sha256sum), ObjectOptions{}) defer os.RemoveAll(newDisk)
obj = initFSObjects(newDisk, t)
if _, err = obj.PutObjectPart(context.Background(), bucketName, objectName, uploadID, 1, mustGetPutObjReader(t, bytes.NewReader(data), dataLen, md5Hex, sha256sum), ObjectOptions{}); err != nil {
if !isSameType(err, BucketNotFound{}) { if !isSameType(err, BucketNotFound{}) {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
} }
}
// TestCompleteMultipartUploadFaultyDisk - test CompleteMultipartUpload with faulty disks // TestCompleteMultipartUploadFaultyDisk - test CompleteMultipartUpload with faulty disks
func TestCompleteMultipartUploadFaultyDisk(t *testing.T) { func TestCompleteMultipartUploadFaultyDisk(t *testing.T) {
@@ -127,7 +129,6 @@ func TestCompleteMultipartUploadFaultyDisk(t *testing.T) {
defer os.RemoveAll(disk) defer os.RemoveAll(disk)
obj := initFSObjects(disk, t) obj := initFSObjects(disk, t)
fs := obj.(*FSObjects)
bucketName := "bucket" bucketName := "bucket"
objectName := "object" objectName := "object"
data := []byte("12345") data := []byte("12345")
@@ -136,7 +137,7 @@ func TestCompleteMultipartUploadFaultyDisk(t *testing.T) {
t.Fatal("Cannot create bucket, err: ", err) t.Fatal("Cannot create bucket, err: ", err)
} }
uploadID, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}}) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}})
if err != nil { if err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
@@ -144,8 +145,10 @@ func TestCompleteMultipartUploadFaultyDisk(t *testing.T) {
md5Hex := getMD5Hash(data) md5Hex := getMD5Hash(data)
parts := []CompletePart{{PartNumber: 1, ETag: md5Hex}} parts := []CompletePart{{PartNumber: 1, ETag: md5Hex}}
fs.fsPath = filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) newDisk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
if _, err := fs.CompleteMultipartUpload(context.Background(), bucketName, objectName, uploadID, parts, ObjectOptions{}); err != nil { defer os.RemoveAll(newDisk)
obj = initFSObjects(newDisk, t)
if _, err := obj.CompleteMultipartUpload(context.Background(), bucketName, objectName, uploadID, parts, ObjectOptions{}); err != nil {
if !isSameType(err, BucketNotFound{}) { if !isSameType(err, BucketNotFound{}) {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
@@ -159,7 +162,6 @@ func TestCompleteMultipartUpload(t *testing.T) {
defer os.RemoveAll(disk) defer os.RemoveAll(disk)
obj := initFSObjects(disk, t) obj := initFSObjects(disk, t)
fs := obj.(*FSObjects)
bucketName := "bucket" bucketName := "bucket"
objectName := "object" objectName := "object"
data := []byte("12345") data := []byte("12345")
@@ -168,20 +170,19 @@ func TestCompleteMultipartUpload(t *testing.T) {
t.Fatal("Cannot create bucket, err: ", err) t.Fatal("Cannot create bucket, err: ", err)
} }
uploadID, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}}) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}})
if err != nil { if err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
md5Hex := getMD5Hash(data) md5Hex := getMD5Hash(data)
if _, err := fs.PutObjectPart(context.Background(), bucketName, objectName, uploadID, 1, mustGetPutObjReader(t, bytes.NewReader(data), 5, md5Hex, ""), ObjectOptions{}); err != nil { if _, err := obj.PutObjectPart(context.Background(), bucketName, objectName, uploadID, 1, mustGetPutObjReader(t, bytes.NewReader(data), 5, md5Hex, ""), ObjectOptions{}); err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
parts := []CompletePart{{PartNumber: 1, ETag: md5Hex}} parts := []CompletePart{{PartNumber: 1, ETag: md5Hex}}
if _, err := obj.CompleteMultipartUpload(context.Background(), bucketName, objectName, uploadID, parts, ObjectOptions{}); err != nil {
if _, err := fs.CompleteMultipartUpload(context.Background(), bucketName, objectName, uploadID, parts, ObjectOptions{}); err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
} }
@@ -193,7 +194,6 @@ func TestAbortMultipartUpload(t *testing.T) {
defer os.RemoveAll(disk) defer os.RemoveAll(disk)
obj := initFSObjects(disk, t) obj := initFSObjects(disk, t)
fs := obj.(*FSObjects)
bucketName := "bucket" bucketName := "bucket"
objectName := "object" objectName := "object"
data := []byte("12345") data := []byte("12345")
@@ -202,18 +202,18 @@ func TestAbortMultipartUpload(t *testing.T) {
t.Fatal("Cannot create bucket, err: ", err) t.Fatal("Cannot create bucket, err: ", err)
} }
uploadID, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}}) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}})
if err != nil { if err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
md5Hex := getMD5Hash(data) md5Hex := getMD5Hash(data)
if _, err := fs.PutObjectPart(context.Background(), bucketName, objectName, uploadID, 1, mustGetPutObjReader(t, bytes.NewReader(data), 5, md5Hex, ""), ObjectOptions{}); err != nil { if _, err := obj.PutObjectPart(context.Background(), bucketName, objectName, uploadID, 1, mustGetPutObjReader(t, bytes.NewReader(data), 5, md5Hex, ""), ObjectOptions{}); err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
time.Sleep(time.Second) // Without Sleep on windows, the fs.AbortMultipartUpload() fails with "The process cannot access the file because it is being used by another process." time.Sleep(time.Second) // Without Sleep on windows, the fs.AbortMultipartUpload() fails with "The process cannot access the file because it is being used by another process."
if err := fs.AbortMultipartUpload(context.Background(), bucketName, objectName, uploadID); err != nil { if err := obj.AbortMultipartUpload(context.Background(), bucketName, objectName, uploadID); err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
} }
@@ -226,7 +226,6 @@ func TestListMultipartUploadsFaultyDisk(t *testing.T) {
obj := initFSObjects(disk, t) obj := initFSObjects(disk, t)
fs := obj.(*FSObjects)
bucketName := "bucket" bucketName := "bucket"
objectName := "object" objectName := "object"
@@ -234,13 +233,15 @@ func TestListMultipartUploadsFaultyDisk(t *testing.T) {
t.Fatal("Cannot create bucket, err: ", err) t.Fatal("Cannot create bucket, err: ", err)
} }
_, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}}) _, err := obj.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}})
if err != nil { if err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
fs.fsPath = filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) newDisk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
if _, err := fs.ListMultipartUploads(context.Background(), bucketName, objectName, "", "", "", 1000); err != nil { defer os.RemoveAll(newDisk)
obj = initFSObjects(newDisk, t)
if _, err := obj.ListMultipartUploads(context.Background(), bucketName, objectName, "", "", "", 1000); err != nil {
if !isSameType(err, BucketNotFound{}) { if !isSameType(err, BucketNotFound{}) {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
+9 -2
View File
@@ -30,6 +30,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio-go/v6/pkg/s3utils" "github.com/minio/minio-go/v6/pkg/s3utils"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/lifecycle" "github.com/minio/minio/pkg/lifecycle"
@@ -1092,13 +1093,19 @@ func (fs *FSObjects) getObjectETag(ctx context.Context, bucket, entry string, lo
return "", toObjectErr(err, bucket, entry) return "", toObjectErr(err, bucket, entry)
} }
var fsMeta fsMetaV1
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(fsMetaBuf, &fsMeta); err != nil {
return "", err
}
// Check if FS metadata is valid, if not return error. // Check if FS metadata is valid, if not return error.
if !isFSMetaValid(parseFSVersion(fsMetaBuf)) { if !isFSMetaValid(fsMeta.Version) {
logger.LogIf(ctx, errCorruptedFormat) logger.LogIf(ctx, errCorruptedFormat)
return "", toObjectErr(errCorruptedFormat, bucket, entry) return "", toObjectErr(errCorruptedFormat, bucket, entry)
} }
return extractETag(parseFSMetaMap(fsMetaBuf)), nil return extractETag(fsMeta.Meta), nil
} }
// ListObjects - list all objects at prefix upto maxKeys., optionally delimited by '/'. Maintains the list pool // ListObjects - list all objects at prefix upto maxKeys., optionally delimited by '/'. Maintains the list pool
+2 -1
View File
@@ -24,6 +24,7 @@ import (
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/hash" "github.com/minio/minio/pkg/hash"
xnet "github.com/minio/minio/pkg/net"
minio "github.com/minio/minio-go/v6" minio "github.com/minio/minio-go/v6"
) )
@@ -299,7 +300,7 @@ func ErrorRespToObjectError(err error, params ...string) error {
object = params[1] object = params[1]
} }
if isNetworkOrHostDown(err) { if xnet.IsNetworkOrHostDown(err) {
return BackendDown{} return BackendDown{}
} }
+4 -1
View File
@@ -159,6 +159,9 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
registerSTSRouter(router) registerSTSRouter(router)
} }
// initialize globalConsoleSys system
globalConsoleSys = NewConsoleLogger(context.Background(), globalEndpoints)
enableConfigOps := gatewayName == "nas" enableConfigOps := gatewayName == "nas"
enableIAMOps := globalEtcdClient != nil enableIAMOps := globalEtcdClient != nil
@@ -301,7 +304,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
// Print a warning message if gateway is not ready for production before the startup banner. // Print a warning message if gateway is not ready for production before the startup banner.
if !gw.Production() { if !gw.Production() {
logger.StartupMessage(colorYellow(" *** Warning: Not Ready for Production ***")) logStartupMessage(colorYellow(" *** Warning: Not Ready for Production ***"))
} }
// Print gateway startup message. // Print gateway startup message.
+5 -7
View File
@@ -20,8 +20,6 @@ import (
"context" "context"
"fmt" "fmt"
"strings" "strings"
"github.com/minio/minio/cmd/logger"
) )
// Prints the formatted startup message. // Prints the formatted startup message.
@@ -57,15 +55,15 @@ func printGatewayCommonMsg(apiEndpoints []string) {
apiEndpointStr := strings.Join(apiEndpoints, " ") apiEndpointStr := strings.Join(apiEndpoints, " ")
// Colorize the message and print. // Colorize the message and print.
logger.StartupMessage(colorBlue("Endpoint: ") + colorBold(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 1), apiEndpointStr))) logStartupMessage(colorBlue("Endpoint: ") + colorBold(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 1), apiEndpointStr)))
if isTerminal() && !globalCLIContext.Anonymous { if isTerminal() && !globalCLIContext.Anonymous {
logger.StartupMessage(colorBlue("AccessKey: ") + colorBold(fmt.Sprintf("%s ", cred.AccessKey))) logStartupMessage(colorBlue("AccessKey: ") + colorBold(fmt.Sprintf("%s ", cred.AccessKey)))
logger.StartupMessage(colorBlue("SecretKey: ") + colorBold(fmt.Sprintf("%s ", cred.SecretKey))) logStartupMessage(colorBlue("SecretKey: ") + colorBold(fmt.Sprintf("%s ", cred.SecretKey)))
} }
printEventNotifiers() printEventNotifiers()
if globalIsBrowserEnabled { if globalIsBrowserEnabled {
logger.StartupMessage(colorBlue("\nBrowser Access:")) logStartupMessage(colorBlue("\nBrowser Access:"))
logger.StartupMessage(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 3), apiEndpointStr)) logStartupMessage(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 3), apiEndpointStr))
} }
} }
+18 -13
View File
@@ -22,7 +22,6 @@ import (
"fmt" "fmt"
"hash" "hash"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"strings" "strings"
@@ -460,13 +459,23 @@ func (l *b2Objects) GetObjectInfo(ctx context.Context, bucket string, object str
if err != nil { if err != nil {
return objInfo, err return objInfo, err
} }
f, err := bkt.DownloadFileByName(l.ctx, object, 0, 1)
f, _, err := bkt.ListFileNames(l.ctx, 1, object, "", "")
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return objInfo, b2ToObjectError(err, bucket, object) return objInfo, b2ToObjectError(err, bucket, object)
} }
f.Close()
fi, err := bkt.File(f.ID, object).GetFileInfo(l.ctx) // B2's list will return the next item in the bucket if the object doesn't
// exist so we need to perform a name check too
if len(f) != 1 || (len(f) == 1 && f[0].Name != object) {
return objInfo, minio.ObjectNotFound{
Bucket: bucket,
Object: object,
}
}
fi, err := bkt.File(f[0].ID, object).GetFileInfo(l.ctx)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return objInfo, b2ToObjectError(err, bucket, object) return objInfo, b2ToObjectError(err, bucket, object)
@@ -474,7 +483,7 @@ func (l *b2Objects) GetObjectInfo(ctx context.Context, bucket string, object str
return minio.ObjectInfo{ return minio.ObjectInfo{
Bucket: bucket, Bucket: bucket,
Name: object, Name: object,
ETag: minio.ToS3ETag(f.ID), ETag: minio.ToS3ETag(f[0].ID),
Size: fi.Size, Size: fi.Size,
ModTime: fi.Timestamp, ModTime: fi.Timestamp,
ContentType: fi.ContentType, ContentType: fi.ContentType,
@@ -595,14 +604,10 @@ func (l *b2Objects) DeleteObject(ctx context.Context, bucket string, object stri
if err != nil { if err != nil {
return err return err
} }
reader, err := bkt.DownloadFileByName(l.ctx, object, 0, 1)
if err != nil { // If we hide the file we'll conform to B2's versioning policy, it also
logger.LogIf(ctx, err) // saves an additional call to check if the file exists first
return b2ToObjectError(err, bucket, object) _, err = bkt.HideFile(l.ctx, object)
}
io.Copy(ioutil.Discard, reader)
reader.Close()
err = bkt.File(reader.ID, object).DeleteFileVersion(l.ctx)
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return b2ToObjectError(err, bucket, object) return b2ToObjectError(err, bucket, object)
} }
+27 -21
View File
@@ -18,6 +18,7 @@ package hdfs
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
@@ -280,7 +281,7 @@ func hdfsToObjectErr(ctx context.Context, err error, params ...string) error {
return minio.PrefixAccessDenied{Bucket: bucket, Object: object} return minio.PrefixAccessDenied{Bucket: bucket, Object: object}
} }
return minio.BucketAlreadyOwnedByYou{Bucket: bucket} return minio.BucketAlreadyOwnedByYou{Bucket: bucket}
case isSysErrNotEmpty(err): case errors.Is(err, syscall.ENOTEMPTY):
if object != "" { if object != "" {
return minio.PrefixAccessDenied{Bucket: bucket, Object: object} return minio.PrefixAccessDenied{Bucket: bucket, Object: object}
} }
@@ -387,20 +388,6 @@ func (n *hdfsObjects) ListObjects(ctx context.Context, bucket, prefix, marker, d
return minio.ListObjects(ctx, n, bucket, prefix, marker, delimiter, maxKeys, n.listPool, n.listDirFactory(), getObjectInfo, getObjectInfo) return minio.ListObjects(ctx, n, bucket, prefix, marker, delimiter, maxKeys, n.listPool, n.listDirFactory(), getObjectInfo, getObjectInfo)
} }
// Check if the given error corresponds to ENOTEMPTY for unix
// and ERROR_DIR_NOT_EMPTY for windows (directory not empty).
func isSysErrNotEmpty(err error) bool {
if err == syscall.ENOTEMPTY {
return true
}
if pathErr, ok := err.(*os.PathError); ok {
if pathErr.Err == syscall.ENOTEMPTY {
return true
}
}
return false
}
// deleteObject deletes a file path if its empty. If it's successfully deleted, // deleteObject deletes a file path if its empty. If it's successfully deleted,
// it will recursively move up the tree, deleting empty parent directories // it will recursively move up the tree, deleting empty parent directories
// until it finds one with files in it. Returns nil for a non-empty directory. // until it finds one with files in it. Returns nil for a non-empty directory.
@@ -411,16 +398,13 @@ func (n *hdfsObjects) deleteObject(basePath, deletePath string) error {
// Attempt to remove path. // Attempt to remove path.
if err := n.clnt.Remove(deletePath); err != nil { if err := n.clnt.Remove(deletePath); err != nil {
switch { if errors.Is(err, syscall.ENOTEMPTY) {
case err == syscall.ENOTEMPTY:
case isSysErrNotEmpty(err):
// Ignore errors if the directory is not empty. The server relies on // Ignore errors if the directory is not empty. The server relies on
// this functionality, and sometimes uses recursion that should not // this functionality, and sometimes uses recursion that should not
// error on parent directories. // error on parent directories.
return nil return nil
default:
return err
} }
return err
} }
// Trailing slash is removed when found to ensure // Trailing slash is removed when found to ensure
@@ -523,12 +507,34 @@ func (n *hdfsObjects) GetObject(ctx context.Context, bucket, key string, startOf
return hdfsToObjectErr(ctx, err, bucket, key) return hdfsToObjectErr(ctx, err, bucket, key)
} }
func (n *hdfsObjects) isObjectDir(ctx context.Context, bucket, object string) bool {
f, err := n.clnt.Open(minio.PathJoin(hdfsSeparator, bucket, object))
if err != nil {
if os.IsNotExist(err) {
return false
}
logger.LogIf(ctx, err)
return false
}
defer f.Close()
fis, err := f.Readdir(1)
if err != nil {
logger.LogIf(ctx, err)
return false
}
return len(fis) == 0
}
// GetObjectInfo reads object info and replies back ObjectInfo. // GetObjectInfo reads object info and replies back ObjectInfo.
func (n *hdfsObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) { func (n *hdfsObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
_, err = n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket)) _, err = n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket))
if err != nil { if err != nil {
return objInfo, hdfsToObjectErr(ctx, err, bucket) return objInfo, hdfsToObjectErr(ctx, err, bucket)
} }
if strings.HasSuffix(object, hdfsSeparator) && !n.isObjectDir(ctx, bucket, object) {
return objInfo, hdfsToObjectErr(ctx, os.ErrNotExist, bucket, object)
}
fi, err := n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket, object)) fi, err := n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket, object))
if err != nil { if err != nil {
return objInfo, hdfsToObjectErr(ctx, err, bucket, object) return objInfo, hdfsToObjectErr(ctx, err, bucket, object)
@@ -552,7 +558,7 @@ func (n *hdfsObjects) PutObject(ctx context.Context, bucket string, object strin
name := minio.PathJoin(hdfsSeparator, bucket, object) name := minio.PathJoin(hdfsSeparator, bucket, object)
// If its a directory create a prefix { // If its a directory create a prefix {
if strings.HasSuffix(object, hdfsSeparator) { if strings.HasSuffix(object, hdfsSeparator) && r.Size() == 0 {
if err = n.clnt.MkdirAll(name, os.FileMode(0755)); err != nil { if err = n.clnt.MkdirAll(name, os.FileMode(0755)); err != nil {
n.deleteObject(minio.PathJoin(hdfsSeparator, bucket), name) n.deleteObject(minio.PathJoin(hdfsSeparator, bucket), name)
return objInfo, hdfsToObjectErr(ctx, err, bucket, object) return objInfo, hdfsToObjectErr(ctx, err, bucket, object)
+5 -78
View File
@@ -24,10 +24,10 @@ import (
"net/http" "net/http"
"time" "time"
jsoniter "github.com/json-iterator/go"
minio "github.com/minio/minio/cmd" minio "github.com/minio/minio/cmd"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/hash" "github.com/minio/minio/pkg/hash"
"github.com/tidwall/gjson"
) )
var ( var (
@@ -137,85 +137,12 @@ func (m gwMetaV1) ObjectToPartOffset(ctx context.Context, offset int64) (partInd
return 0, 0, minio.InvalidRange{} return 0, 0, minio.InvalidRange{}
} }
// parses gateway metadata stat info from metadata json // Constructs GWMetaV1 using `jsoniter` lib to retrieve each field.
func parseGWStat(gwMetaBuf []byte) (si minio.StatInfo, e error) { func gwMetaUnmarshalJSON(ctx context.Context, gwMetaBuf []byte) (gwMeta gwMetaV1, err error) {
// obtain stat info. var json = jsoniter.ConfigCompatibleWithStandardLibrary
stat := minio.StatInfo{} err = json.Unmarshal(gwMetaBuf, &gwMeta)
// fetching modTime.
modTime, err := time.Parse(time.RFC3339, gjson.GetBytes(gwMetaBuf, "stat.modTime").String())
if err != nil {
return si, err
}
stat.ModTime = modTime
// obtain Stat.Size .
stat.Size = gjson.GetBytes(gwMetaBuf, "stat.size").Int()
return stat, nil
}
// parses gateway metadata version from metadata json
func parseGWVersion(gwMetaBuf []byte) string {
return gjson.GetBytes(gwMetaBuf, "version").String()
}
// parses gateway ETag from metadata json
func parseGWETag(gwMetaBuf []byte) string {
return gjson.GetBytes(gwMetaBuf, "etag").String()
}
// parses gateway metadata format from metadata json
func parseGWFormat(gwMetaBuf []byte) string {
return gjson.GetBytes(gwMetaBuf, "format").String()
}
// parses gateway metadata json to get list of ObjectPartInfo
func parseGWParts(gwMetaBuf []byte) []minio.ObjectPartInfo {
// Parse the GW Parts.
partsResult := gjson.GetBytes(gwMetaBuf, "parts").Array()
partInfo := make([]minio.ObjectPartInfo, len(partsResult))
for i, p := range partsResult {
info := minio.ObjectPartInfo{}
info.Number = int(p.Get("number").Int())
info.Name = p.Get("name").String()
info.ETag = p.Get("etag").String()
info.Size = p.Get("size").Int()
partInfo[i] = info
}
return partInfo
}
// parses gateway metadata json to get the metadata map
func parseGWMetaMap(gwMetaBuf []byte) map[string]string {
// Get gwMetaV1.Meta map.
metaMapResult := gjson.GetBytes(gwMetaBuf, "meta").Map()
metaMap := make(map[string]string)
for key, valResult := range metaMapResult {
metaMap[key] = valResult.String()
}
return metaMap
}
// Constructs GWMetaV1 using `gjson` lib to retrieve each field.
func gwMetaUnmarshalJSON(ctx context.Context, gwMetaBuf []byte) (gwMeta gwMetaV1, e error) {
// obtain version.
gwMeta.Version = parseGWVersion(gwMetaBuf)
// obtain format.
gwMeta.Format = parseGWFormat(gwMetaBuf)
// Parse gwMetaV1.Stat .
stat, err := parseGWStat(gwMetaBuf)
if err != nil {
logger.LogIf(ctx, err)
return gwMeta, err return gwMeta, err
} }
gwMeta.ETag = parseGWETag(gwMetaBuf)
gwMeta.Stat = stat
// Parse the GW Parts.
gwMeta.Parts = parseGWParts(gwMetaBuf)
// parse gwMetaV1.
gwMeta.Meta = parseGWMetaMap(gwMetaBuf)
return gwMeta, nil
}
// readGWMeta reads `dare.meta` and returns back GW metadata structure. // readGWMeta reads `dare.meta` and returns back GW metadata structure.
func readGWMetadata(ctx context.Context, buf bytes.Buffer) (gwMeta gwMetaV1, err error) { func readGWMetadata(ctx context.Context, buf bytes.Buffer) (gwMeta gwMetaV1, err error) {
+8 -8
View File
@@ -53,25 +53,25 @@ func TestReadGWMetadata(t *testing.T) {
metaStr string metaStr string
pass bool pass bool
}{ }{
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", {"stat": {"size": "132", "modTime": "2018-08-31T22:25:39.23626461Z" }}}`, true}, {`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", "stat": {"size": 132, "modTime": "2018-08-31T22:25:39.23626461Z" }}`, true},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", {"stat": {"size": "132", "modTime": "0000-00-00T00:00:00.00000000Z" }}}`, false}, {`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", "stat": {"size": 132, "modTime": "0000-00-00T00:00:00.00000000Z" }}`, false},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", {"stat": {"size": "5242880", "modTime": "2018-08-31T22:25:39.23626461Z" }},"meta":{"content-type":"application/octet-stream","etag":"57c743902b2fc8eea6ba3bb4fc58c8e8"},"parts":[{"number":1,"name":"part.1","etag":"","size":5242880}]}}`, true}, {`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", "stat": {"size": 5242880, "modTime": "2018-08-31T22:25:39.23626461Z" },"meta":{"content-type":"application/octet-stream","etag":"57c743902b2fc8eea6ba3bb4fc58c8e8"},"parts":[{"number":1,"name":"part.1","etag":"","size":5242880}]}`, true},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", {"stat": {"size": "68190720", "modTime": "2018-08-31T22:25:39.23626461Z" }},"meta":{"X-Minio-Internal-Encrypted-Multipart":"","X-Minio-Internal-Server-Side-Encryption-Iv":"kdbOcKdXD3Sew8tOiHe5eI9xkX1oQ2W9JURz0oslCZA=","X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm":"DAREv2-HMAC-SHA256","X-Minio-Internal-Server-Side-Encryption-Sealed-Key":"IAAfAMfqKrxMXC9LuiI7ENP+p0xArepzAiIeB/MftFp7Xmq2OzDkKlmNbj5RKI89RrjiAbOVLSSEMvqQsrIrTQ==","content-type":"text/plain; charset=utf-8","etag":"2b137fa4ab80126af54623b010c98de6-2"},"parts":[{"number":1,"name":"part.1","etag":"c5cac075eefdab801a5198812f51b36e","size":67141632},{"number":2,"name":"part.2","etag":"ccdf4b774bc3be8eef9a8987309e8171","size":1049088}]}`, true}, {`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", "stat": {"size": 68190720, "modTime": "2018-08-31T22:25:39.23626461Z" },"meta":{"X-Minio-Internal-Encrypted-Multipart":"","X-Minio-Internal-Server-Side-Encryption-Iv":"kdbOcKdXD3Sew8tOiHe5eI9xkX1oQ2W9JURz0oslCZA=","X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm":"DAREv2-HMAC-SHA256","X-Minio-Internal-Server-Side-Encryption-Sealed-Key":"IAAfAMfqKrxMXC9LuiI7ENP+p0xArepzAiIeB/MftFp7Xmq2OzDkKlmNbj5RKI89RrjiAbOVLSSEMvqQsrIrTQ==","content-type":"text/plain; charset=utf-8","etag":"2b137fa4ab80126af54623b010c98de6-2"},"parts":[{"number":1,"name":"part.1","etag":"c5cac075eefdab801a5198812f51b36e","size":67141632},{"number":2,"name":"part.2","etag":"ccdf4b774bc3be8eef9a8987309e8171","size":1049088}]}`, true},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", {"stat": {"size": "68190720", "modTime": "2018-08-31T22:25:39.23626461Z" }},"meta":{"X-Minio-Internal-Encrypted-Multipart":"","X-Minio-Internal-Server-Side-Encryption-Iv":"kdbOcKdXD3Sew8tOiHe5eI9xkX1oQ2W9JURz0oslCZA=","X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm":"DAREv2-HMAC-SHA256","X-Minio-Internal-Server-Side-Encryption-Sealed-Key":"IAAfAMfqKrxMXC9LuiI7ENP+p0xArepzAiIeB/MftFp7Xmq2OzDkKlmNbj5RKI89RrjiAbOVLSSEMvqQsrIrTQ==","content-type":"text/plain; charset=utf-8","etag":"2b137fa4ab80126af54623b010c98de6-2"},"parts":"123"}`, true}, {`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", "stat": {"size": "68190720", "modTime": "2018-08-31T22:25:39.23626461Z" },"meta":{"X-Minio-Internal-Encrypted-Multipart":"","X-Minio-Internal-Server-Side-Encryption-Iv":"kdbOcKdXD3Sew8tOiHe5eI9xkX1oQ2W9JURz0oslCZA=","X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm":"DAREv2-HMAC-SHA256","X-Minio-Internal-Server-Side-Encryption-Sealed-Key":"IAAfAMfqKrxMXC9LuiI7ENP+p0xArepzAiIeB/MftFp7Xmq2OzDkKlmNbj5RKI89RrjiAbOVLSSEMvqQsrIrTQ==","content-type":"text/plain; charset=utf-8","etag":"2b137fa4ab80126af54623b010c98de6-2"},"parts":"123"}`, false},
} }
for i, tt := range tests { for i, tt := range tests {
buf := bytes.NewBufferString(tt.metaStr) buf := bytes.NewBufferString(tt.metaStr)
m, err := readGWMetadata(context.Background(), *buf) m, err := readGWMetadata(context.Background(), *buf)
if err != nil && tt.pass { if err != nil && tt.pass {
t.Errorf("Test %d: Expected parse gw metadata to succeed, but failed", i) t.Errorf("Test %d: Expected parse gw metadata to succeed, but failed, %s", i+1, err)
} }
if err == nil && !tt.pass { if err == nil && !tt.pass {
t.Errorf("Test %d: Expected parse gw metadata to succeed, but failed", i) t.Errorf("Test %d: Expected parse gw metadata to succeed, but failed", i+1)
} }
if err == nil { if err == nil {
if m.Version != gwMetaVersion { if m.Version != gwMetaVersion {
t.Errorf("Test %d: Expected version %s, but failed with %s", i, gwMetaVersion, m.Version) t.Errorf("Test %d: Expected version %s, but failed with %s", i+1, gwMetaVersion, m.Version)
} }
} }
} }
+13 -8
View File
@@ -221,7 +221,7 @@ func guessIsMetricsReq(req *http.Request) bool {
return false return false
} }
aType := getRequestAuthType(req) aType := getRequestAuthType(req)
return aType == authTypeAnonymous && return (aType == authTypeAnonymous || aType == authTypeJWT) &&
req.URL.Path == minioReservedBucketPath+prometheusMetricsPath req.URL.Path == minioReservedBucketPath+prometheusMetricsPath
} }
@@ -481,14 +481,12 @@ var notimplementedBucketResourceNames = map[string]bool{
"requestPayment": true, "requestPayment": true,
"tagging": true, "tagging": true,
"versioning": true, "versioning": true,
"versions": true,
"website": true, "website": true,
} }
// List of not implemented object queries // List of not implemented object queries
var notimplementedObjectResourceNames = map[string]bool{ var notimplementedObjectResourceNames = map[string]bool{
"acl": true, "acl": true,
"policy": true,
"restore": true, "restore": true,
"tagging": true, "tagging": true,
"torrent": true, "torrent": true,
@@ -674,13 +672,18 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
bucket, _ = urlPath2BucketObjectName(r.URL.Path) bucket, _ = urlPath2BucketObjectName(r.URL.Path)
} }
} }
if bucket != "" { if bucket == "" {
f.handler.ServeHTTP(w, r)
return
}
sr, err := globalDNSConfig.Get(bucket) sr, err := globalDNSConfig.Get(bucket)
if err != nil { if err != nil {
if err == dns.ErrNoEntriesFound { if err == dns.ErrNoEntriesFound {
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrNoSuchBucket), r.URL, guessIsBrowserReq(r)) writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrNoSuchBucket),
r.URL, guessIsBrowserReq(r))
} else { } else {
writeErrorResponse(context.Background(), w, toAPIError(context.Background(), err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(context.Background(), w, toAPIError(context.Background(), err),
r.URL, guessIsBrowserReq(r))
} }
return return
} }
@@ -693,7 +696,6 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
f.fwd.ServeHTTP(w, r) f.fwd.ServeHTTP(w, r)
return return
} }
}
f.handler.ServeHTTP(w, r) f.handler.ServeHTTP(w, r)
return return
} }
@@ -716,10 +718,13 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
// CopyObject requests should be handled at current endpoint as path style // CopyObject requests should be handled at current endpoint as path style
// requests have target bucket and object in URI and source details are in // requests have target bucket and object in URI and source details are in
// header fields // header fields
if r.Method == http.MethodPut && r.Header.Get("X-Amz-Copy-Source") != "" { if r.Method == http.MethodPut && r.Header.Get(xhttp.AmzCopySource) != "" {
bucket, object = urlPath2BucketObjectName(r.Header.Get(xhttp.AmzCopySource))
if bucket == "" || object == "" {
f.handler.ServeHTTP(w, r) f.handler.ServeHTTP(w, r)
return return
} }
}
sr, err := globalDNSConfig.Get(bucket) sr, err := globalDNSConfig.Get(bucket)
if err != nil { if err != nil {
if err == dns.ErrNoEntriesFound { if err == dns.ErrNoEntriesFound {
+1 -1
View File
@@ -185,7 +185,7 @@ var containsReservedMetadataTests = []struct {
shouldFail: true, shouldFail: true,
}, },
{ {
header: http.Header{crypto.SSESealAlgorithm: []string{SSESealAlgorithmDareSha256}}, header: http.Header{crypto.SSESealAlgorithm: []string{crypto.InsecureSealAlgorithm}},
shouldFail: true, shouldFail: true,
}, },
{ {
+13 -18
View File
@@ -174,6 +174,10 @@ var (
// registered listeners // registered listeners
globalHTTPTrace = pubsub.New() globalHTTPTrace = pubsub.New()
// global console system to send console logs to
// registered listeners
globalConsoleSys *HTTPConsoleLoggerSys
globalEndpoints EndpointList globalEndpoints EndpointList
// Global server's network statistics // Global server's network statistics
@@ -221,7 +225,10 @@ var (
globalCacheExpiry = 90 globalCacheExpiry = 90
// Max allowed disk cache percentage // Max allowed disk cache percentage
globalCacheMaxUse = 80 globalCacheMaxUse = 80
// Disk cache KMS Key
globalCacheKMSKeyID string
// Initialized KMS configuration for disk cache
globalCacheKMS crypto.KMS
// Allocated etcd endpoint for config and bucket DNS. // Allocated etcd endpoint for config and bucket DNS.
globalEtcdClient *etcd.Client globalEtcdClient *etcd.Client
@@ -244,18 +251,18 @@ var (
// configuration must be present. // configuration must be present.
globalAutoEncryption bool globalAutoEncryption bool
// Is compression include extensions/content-types set. // Is compression include extensions/content-types set?
globalIsEnvCompression bool globalIsEnvCompression bool
// Is compression enabeld. // Is compression enabled?
globalIsCompressionEnabled = false globalIsCompressionEnabled = false
// Include-list for compression. // Include-list for compression.
globalCompressExtensions = []string{".txt", ".log", ".csv", ".json"} globalCompressExtensions = []string{".txt", ".log", ".csv", ".json", ".tar", ".xml", ".bin"}
globalCompressMimeTypes = []string{"text/csv", "text/plain", "application/json"} globalCompressMimeTypes = []string{"text/*", "application/json", "application/xml"}
// Some standard object extensions which we strictly dis-allow for compression. // Some standard object extensions which we strictly dis-allow for compression.
standardExcludeCompressExtensions = []string{".gz", ".bz2", ".rar", ".zip", ".7z"} standardExcludeCompressExtensions = []string{".gz", ".bz2", ".rar", ".zip", ".7z", ".xz", ".mp4", ".mkv", ".mov"}
// Some standard content-types which we strictly dis-allow for compression. // Some standard content-types which we strictly dis-allow for compression.
standardExcludeCompressContentTypes = []string{"video/*", "audio/*", "application/zip", "application/x-gzip", "application/x-zip-compressed", " application/x-compress", "application/x-spoon"} standardExcludeCompressContentTypes = []string{"video/*", "audio/*", "application/zip", "application/x-gzip", "application/x-zip-compressed", " application/x-compress", "application/x-spoon"}
@@ -336,18 +343,6 @@ var (
} }
return fmt.Sprintf return fmt.Sprintf
}() }()
colorGreenBold = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgGreen, color.Bold).SprintfFunc()
}
return fmt.Sprintf
}()
colorRedBold = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgRed, color.Bold).SprintfFunc()
}
return fmt.Sprintf
}()
) )
// Returns minio global information, as a key value map. // Returns minio global information, as a key value map.
+7 -1
View File
@@ -22,6 +22,7 @@ import (
"os" "os"
"runtime" "runtime"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
) )
@@ -54,7 +55,12 @@ func LivenessCheckHandler(w http.ResponseWriter, r *http.Request) {
objLayer := newObjectLayerFn() objLayer := newObjectLayerFn()
// Service not initialized yet // Service not initialized yet
if objLayer == nil { if objLayer == nil {
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone) // Respond with 200 OK while server initializes to ensure a distributed cluster
// is able to start on orchestration platforms like Docker Swarm.
// Refer https://github.com/minio/minio/issues/8140 for more details.
// Make sure to add server not initialized status in header
w.Header().Set(xhttp.MinIOServerStatus, "Server-not-initialized")
writeSuccessResponseHeadersOnly(w)
return return
} }
+17 -88
View File
@@ -18,7 +18,6 @@ package cmd
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
@@ -29,11 +28,11 @@ import (
"strings" "strings"
"time" "time"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/handlers"
trace "github.com/minio/minio/pkg/trace" trace "github.com/minio/minio/pkg/trace"
) )
var traceBodyPlaceHolder = []byte("<BODY>")
// recordRequest - records the first recLen bytes // recordRequest - records the first recLen bytes
// of a given io.Reader // of a given io.Reader
type recordRequest struct { type recordRequest struct {
@@ -76,81 +75,7 @@ func (r *recordRequest) Data() []byte {
return r.buf.Bytes() return r.buf.Bytes()
} }
// ... otherwise we return <BODY> placeholder // ... otherwise we return <BODY> placeholder
return traceBodyPlaceHolder return logger.BodyPlaceHolder
}
// recordResponseWriter - records the first recLen bytes
// of a given http.ResponseWriter
type recordResponseWriter struct {
// Data source to record
http.ResponseWriter
// Response body should be logged
logBody bool
// Internal recording buffer
headers bytes.Buffer
body bytes.Buffer
// The status code of the current HTTP request
statusCode int
// Indicate if headers are written in the log
headersLogged bool
// number of bytes written
bytesWritten int
}
// Write the headers into the given buffer
func (r *recordResponseWriter) writeHeaders(w io.Writer, statusCode int, headers http.Header) {
n, _ := fmt.Fprintf(w, "%d %s\n", statusCode, http.StatusText(statusCode))
r.bytesWritten += n
for k, v := range headers {
n, _ := fmt.Fprintf(w, "%s: %s\n", k, v[0])
r.bytesWritten += n
}
}
// Record the headers.
func (r *recordResponseWriter) WriteHeader(i int) {
r.statusCode = i
if !r.headersLogged {
r.writeHeaders(&r.headers, i, r.ResponseWriter.Header())
r.headersLogged = true
}
r.ResponseWriter.WriteHeader(i)
}
func (r *recordResponseWriter) Write(p []byte) (n int, err error) {
n, err = r.ResponseWriter.Write(p)
r.bytesWritten += n
if !r.headersLogged {
// We assume the response code to be '200 OK' when WriteHeader() is not called,
// that way following Golang HTTP response behavior.
r.writeHeaders(&r.headers, http.StatusOK, r.ResponseWriter.Header())
r.headersLogged = true
}
if (r.statusCode != http.StatusOK && r.statusCode != http.StatusPartialContent && r.statusCode != 0) || r.logBody {
// Always logging error responses.
r.body.Write(p)
}
return n, err
}
func (r *recordResponseWriter) Size() int {
return r.bytesWritten
}
// Calls the underlying Flush.
func (r *recordResponseWriter) Flush() {
r.ResponseWriter.(http.Flusher).Flush()
}
// Return response body.
func (r *recordResponseWriter) Body() []byte {
// If there was an error response or body logging is enabled
// then we return the body contents
if r.statusCode >= 400 || r.logBody {
return r.body.Bytes()
}
// ... otherwise we return the <BODY> place holder
return traceBodyPlaceHolder
} }
// getOpName sanitizes the operation name for mc // getOpName sanitizes the operation name for mc
@@ -174,7 +99,7 @@ func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Requ
name := getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()) name := getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name())
// Setup a http request body recorder // Setup a http request body recorder
reqHeaders := cloneHeader(r.Header) reqHeaders := r.Header.Clone()
reqHeaders.Set("Content-Length", strconv.Itoa(int(r.ContentLength))) reqHeaders.Set("Content-Length", strconv.Itoa(int(r.ContentLength)))
reqHeaders.Set("Host", r.Host) reqHeaders.Set("Host", r.Host)
for _, enc := range r.TransferEncoding { for _, enc := range r.TransferEncoding {
@@ -199,20 +124,19 @@ func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Requ
Method: r.Method, Method: r.Method,
Path: r.URL.Path, Path: r.URL.Path,
RawQuery: r.URL.RawQuery, RawQuery: r.URL.RawQuery,
Client: r.RemoteAddr, Client: handlers.GetSourceIP(r),
Headers: reqHeaders, Headers: reqHeaders,
Body: reqBodyRecorder.Data(), Body: reqBodyRecorder.Data(),
} }
rw, _ := w.(*logger.ResponseWriter)
// Setup a http response body recorder rw.LogBody = logBody
respBodyRecorder := &recordResponseWriter{ResponseWriter: w, logBody: logBody} f(rw, r)
f(respBodyRecorder, r)
rs := trace.ResponseInfo{ rs := trace.ResponseInfo{
Time: time.Now().UTC(), Time: time.Now().UTC(),
Headers: cloneHeader(respBodyRecorder.Header()), Headers: rw.Header().Clone(),
StatusCode: respBodyRecorder.statusCode, StatusCode: rw.StatusCode,
Body: respBodyRecorder.Body(), Body: rw.Body(),
} }
if rs.StatusCode == 0 { if rs.StatusCode == 0 {
@@ -222,6 +146,11 @@ func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Requ
t.ReqInfo = rq t.ReqInfo = rq
t.RespInfo = rs t.RespInfo = rs
t.CallStats = trace.CallStats{Latency: rs.Time.Sub(rq.Time), InputBytes: reqBodyRecorder.Size(), OutputBytes: respBodyRecorder.Size()} t.CallStats = trace.CallStats{
Latency: rs.Time.Sub(rw.StartTime),
InputBytes: reqBodyRecorder.Size(),
OutputBytes: rw.Size(),
TimeToFirstByte: rw.TimeToFirstByte,
}
return t return t
} }
+57
View File
@@ -0,0 +1,57 @@
/*
* MinIO Cloud Storage, (C) 2017-2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package http
import (
"net"
)
// AccountingConn - is a generic stream-oriented network connection supporting buffered reader and read/write timeout.
type AccountingConn struct {
net.Conn
updateBytesReadFunc func(int) // function to be called to update bytes read.
updateBytesWrittenFunc func(int) // function to be called to update bytes written.
}
// Read - reads data from the connection using wrapped buffered reader.
func (c *AccountingConn) Read(b []byte) (n int, err error) {
n, err = c.Conn.Read(b)
if err == nil && c.updateBytesReadFunc != nil {
c.updateBytesReadFunc(n)
}
return n, err
}
// Write - writes data to the connection.
func (c *AccountingConn) Write(b []byte) (n int, err error) {
n, err = c.Conn.Write(b)
if err == nil && c.updateBytesWrittenFunc != nil {
c.updateBytesWrittenFunc(n)
}
return n, err
}
// newAccountingConn - creates a new connection object wrapping net.Conn with deadlines.
func newAccountingConn(c net.Conn, updateBytesReadFunc, updateBytesWrittenFunc func(int)) *AccountingConn {
return &AccountingConn{
Conn: c,
updateBytesReadFunc: updateBytesReadFunc,
updateBytesWrittenFunc: updateBytesWrittenFunc,
}
}
-77
View File
@@ -1,77 +0,0 @@
/*
* MinIO Cloud Storage, (C) 2017 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package http
import (
"net"
"time"
)
// DeadlineConn - is a generic stream-oriented network connection supporting buffered reader and read/write timeout.
type DeadlineConn struct {
net.Conn
readTimeout time.Duration // sets the read timeout in the connection.
writeTimeout time.Duration // sets the write timeout in the connection.
updateBytesReadFunc func(int) // function to be called to update bytes read.
updateBytesWrittenFunc func(int) // function to be called to update bytes written.
}
// Sets read timeout
func (c *DeadlineConn) setReadTimeout() {
if c.readTimeout != 0 {
c.SetReadDeadline(time.Now().UTC().Add(c.readTimeout))
}
}
func (c *DeadlineConn) setWriteTimeout() {
if c.writeTimeout != 0 {
c.SetWriteDeadline(time.Now().UTC().Add(c.writeTimeout))
}
}
// Read - reads data from the connection using wrapped buffered reader.
func (c *DeadlineConn) Read(b []byte) (n int, err error) {
c.setReadTimeout()
n, err = c.Conn.Read(b)
if err == nil && c.updateBytesReadFunc != nil {
c.updateBytesReadFunc(n)
}
return n, err
}
// Write - writes data to the connection.
func (c *DeadlineConn) Write(b []byte) (n int, err error) {
c.setWriteTimeout()
n, err = c.Conn.Write(b)
if err == nil && c.updateBytesWrittenFunc != nil {
c.updateBytesWrittenFunc(n)
}
return n, err
}
// newDeadlineConn - creates a new connection object wrapping net.Conn with deadlines.
func newDeadlineConn(c net.Conn, readTimeout, writeTimeout time.Duration, updateBytesReadFunc, updateBytesWrittenFunc func(int)) *DeadlineConn {
return &DeadlineConn{
Conn: c,
readTimeout: readTimeout,
writeTimeout: writeTimeout,
updateBytesReadFunc: updateBytesReadFunc,
updateBytesWrittenFunc: updateBytesWrittenFunc,
}
}
-114
View File
@@ -1,114 +0,0 @@
/*
* MinIO Cloud Storage, (C) 2017 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package http
import (
"bufio"
"io"
"net"
"sync"
"testing"
"time"
)
// Test deadlineconn handles read timeout properly by reading two messages beyond deadline.
func TestBuffConnReadTimeout(t *testing.T) {
l, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("unable to create listener. %v", err)
}
defer l.Close()
serverAddr := l.Addr().String()
tcpListener, ok := l.(*net.TCPListener)
if !ok {
t.Fatalf("failed to assert to net.TCPListener")
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
tcpConn, terr := tcpListener.AcceptTCP()
if terr != nil {
t.Errorf("failed to accept new connection. %v", terr)
return
}
deadlineconn := newDeadlineConn(tcpConn, 1*time.Second, 1*time.Second, nil, nil)
defer deadlineconn.Close()
// Read a line
var b = make([]byte, 12)
_, terr = deadlineconn.Read(b)
if terr != nil {
t.Errorf("failed to read from client. %v", terr)
return
}
received := string(b)
if received != "message one\n" {
t.Errorf(`server: expected: "message one\n", got: %v`, received)
return
}
// Wait for more than read timeout to simulate processing.
time.Sleep(3 * time.Second)
_, terr = deadlineconn.Read(b)
if terr != nil {
t.Errorf("failed to read from client. %v", terr)
return
}
received = string(b)
if received != "message two\n" {
t.Errorf(`server: expected: "message two\n", got: %v`, received)
return
}
// Send a response.
_, terr = io.WriteString(deadlineconn, "messages received\n")
if terr != nil {
t.Errorf("failed to write to client. %v", terr)
return
}
}()
c, err := net.Dial("tcp", serverAddr)
if err != nil {
t.Fatalf("unable to connect to server. %v", err)
}
defer c.Close()
_, err = io.WriteString(c, "message one\n")
if err != nil {
t.Fatalf("failed to write to server. %v", err)
}
_, err = io.WriteString(c, "message two\n")
if err != nil {
t.Fatalf("failed to write to server. %v", err)
}
received, err := bufio.NewReader(c).ReadString('\n')
if err != nil {
t.Fatalf("failed to read from server. %v", err)
}
if received != "messages received\n" {
t.Fatalf(`client: expected: "messages received\n", got: %v`, received)
}
wg.Wait()
}
+3
View File
@@ -78,4 +78,7 @@ const (
// Deployment id. // Deployment id.
MinioDeploymentID = "x-minio-deployment-id" MinioDeploymentID = "x-minio-deployment-id"
// Server-Status
MinIOServerStatus = "x-minio-server-status"
) )
+2 -9
View File
@@ -38,8 +38,6 @@ type httpListener struct {
acceptCh chan acceptResult // channel where all TCP listeners write accepted connection. acceptCh chan acceptResult // channel where all TCP listeners write accepted connection.
doneCh chan struct{} // done channel for TCP listener goroutines. doneCh chan struct{} // done channel for TCP listener goroutines.
tcpKeepAliveTimeout time.Duration tcpKeepAliveTimeout time.Duration
readTimeout time.Duration
writeTimeout time.Duration
updateBytesReadFunc func(int) // function to be called to update bytes read in Deadlineconn. updateBytesReadFunc func(int) // function to be called to update bytes read in Deadlineconn.
updateBytesWrittenFunc func(int) // function to be called to update bytes written in Deadlineconn. updateBytesWrittenFunc func(int) // function to be called to update bytes written in Deadlineconn.
} }
@@ -91,10 +89,9 @@ func (listener *httpListener) start() {
tcpConn.SetKeepAlive(true) tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(listener.tcpKeepAliveTimeout) tcpConn.SetKeepAlivePeriod(listener.tcpKeepAliveTimeout)
deadlineConn := newDeadlineConn(tcpConn, listener.readTimeout, acctConn := newAccountingConn(tcpConn, listener.updateBytesReadFunc, listener.updateBytesWrittenFunc)
listener.writeTimeout, listener.updateBytesReadFunc, listener.updateBytesWrittenFunc)
send(acceptResult{deadlineConn, nil}, doneCh) send(acceptResult{acctConn, nil}, doneCh)
} }
// Closure to handle TCPListener until done channel is closed. // Closure to handle TCPListener until done channel is closed.
@@ -176,8 +173,6 @@ func (listener *httpListener) Addrs() (addrs []net.Addr) {
// * controls incoming connections only doing HTTP protocol // * controls incoming connections only doing HTTP protocol
func newHTTPListener(serverAddrs []string, func newHTTPListener(serverAddrs []string,
tcpKeepAliveTimeout time.Duration, tcpKeepAliveTimeout time.Duration,
readTimeout time.Duration,
writeTimeout time.Duration,
updateBytesReadFunc func(int), updateBytesReadFunc func(int),
updateBytesWrittenFunc func(int)) (listener *httpListener, err error) { updateBytesWrittenFunc func(int)) (listener *httpListener, err error) {
@@ -214,8 +209,6 @@ func newHTTPListener(serverAddrs []string,
listener = &httpListener{ listener = &httpListener{
tcpListeners: tcpListeners, tcpListeners: tcpListeners,
tcpKeepAliveTimeout: tcpKeepAliveTimeout, tcpKeepAliveTimeout: tcpKeepAliveTimeout,
readTimeout: readTimeout,
writeTimeout: writeTimeout,
updateBytesReadFunc: updateBytesReadFunc, updateBytesReadFunc: updateBytesReadFunc,
updateBytesWrittenFunc: updateBytesWrittenFunc, updateBytesWrittenFunc: updateBytesWrittenFunc,
} }
-8
View File
@@ -154,8 +154,6 @@ func TestNewHTTPListener(t *testing.T) {
listener, err := newHTTPListener( listener, err := newHTTPListener(
testCase.serverAddrs, testCase.serverAddrs,
testCase.tcpKeepAliveTimeout, testCase.tcpKeepAliveTimeout,
testCase.readTimeout,
testCase.writeTimeout,
testCase.updateBytesReadFunc, testCase.updateBytesReadFunc,
testCase.updateBytesWrittenFunc, testCase.updateBytesWrittenFunc,
) )
@@ -192,8 +190,6 @@ func TestHTTPListenerStartClose(t *testing.T) {
listener, err := newHTTPListener( listener, err := newHTTPListener(
testCase.serverAddrs, testCase.serverAddrs,
time.Duration(0), time.Duration(0),
time.Duration(0),
time.Duration(0),
nil, nil, nil, nil,
) )
if err != nil { if err != nil {
@@ -235,8 +231,6 @@ func TestHTTPListenerAddr(t *testing.T) {
listener, err := newHTTPListener( listener, err := newHTTPListener(
testCase.serverAddrs, testCase.serverAddrs,
time.Duration(0), time.Duration(0),
time.Duration(0),
time.Duration(0),
nil, nil, nil, nil,
) )
if err != nil { if err != nil {
@@ -275,8 +269,6 @@ func TestHTTPListenerAddrs(t *testing.T) {
listener, err := newHTTPListener( listener, err := newHTTPListener(
testCase.serverAddrs, testCase.serverAddrs,
time.Duration(0), time.Duration(0),
time.Duration(0),
time.Duration(0),
nil, nil, nil, nil,
) )
if err != nil { if err != nil {
-22
View File
@@ -20,7 +20,6 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"net/http" "net/http"
"os"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -31,13 +30,6 @@ import (
"github.com/minio/minio/pkg/certs" "github.com/minio/minio/pkg/certs"
) )
func init() {
// Opt-in to TLS 1.3. See: https://golang.org/pkg/crypto/tls
// In future Go versions TLS 1.3 probably gets enabled by default.
// So, we can remove this line as soon as this is the case.
os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1")
}
const ( const (
serverShutdownPoll = 500 * time.Millisecond serverShutdownPoll = 500 * time.Millisecond
@@ -47,12 +39,6 @@ const (
// DefaultTCPKeepAliveTimeout - default TCP keep alive timeout for accepted connection. // DefaultTCPKeepAliveTimeout - default TCP keep alive timeout for accepted connection.
DefaultTCPKeepAliveTimeout = 30 * time.Second DefaultTCPKeepAliveTimeout = 30 * time.Second
// DefaultReadTimeout - default timout to read data from accepted connection.
DefaultReadTimeout = 5 * time.Minute
// DefaultWriteTimeout - default timout to write data to accepted connection.
DefaultWriteTimeout = 5 * time.Minute
// DefaultMaxHeaderBytes - default maximum HTTP header size in bytes. // DefaultMaxHeaderBytes - default maximum HTTP header size in bytes.
DefaultMaxHeaderBytes = 1 * humanize.MiByte DefaultMaxHeaderBytes = 1 * humanize.MiByte
) )
@@ -61,8 +47,6 @@ const (
type Server struct { type Server struct {
http.Server http.Server
Addrs []string // addresses on which the server listens for new connection. Addrs []string // addresses on which the server listens for new connection.
ReadTimeout time.Duration // timeout used for net.Conn.Read() deadlines.
WriteTimeout time.Duration // timeout used for net.Conn.Write() deadlines.
ShutdownTimeout time.Duration // timeout used for graceful server shutdown. ShutdownTimeout time.Duration // timeout used for graceful server shutdown.
TCPKeepAliveTimeout time.Duration // timeout used for underneath TCP connection. TCPKeepAliveTimeout time.Duration // timeout used for underneath TCP connection.
UpdateBytesReadFunc func(int) // function to be called to update bytes read in bufConn. UpdateBytesReadFunc func(int) // function to be called to update bytes read in bufConn.
@@ -85,8 +69,6 @@ func (srv *Server) Start() (err error) {
if srv.TLSConfig != nil { if srv.TLSConfig != nil {
tlsConfig = srv.TLSConfig.Clone() tlsConfig = srv.TLSConfig.Clone()
} }
readTimeout := srv.ReadTimeout
writeTimeout := srv.WriteTimeout
handler := srv.Handler // if srv.Handler holds non-synced state -> possible data race handler := srv.Handler // if srv.Handler holds non-synced state -> possible data race
addrs := set.CreateStringSet(srv.Addrs...).ToSlice() // copy and remove duplicates addrs := set.CreateStringSet(srv.Addrs...).ToSlice() // copy and remove duplicates
@@ -97,8 +79,6 @@ func (srv *Server) Start() (err error) {
listener, err = newHTTPListener( listener, err = newHTTPListener(
addrs, addrs,
tcpKeepAliveTimeout, tcpKeepAliveTimeout,
readTimeout,
writeTimeout,
srv.UpdateBytesReadFunc, srv.UpdateBytesReadFunc,
srv.UpdateBytesWrittenFunc, srv.UpdateBytesWrittenFunc,
) )
@@ -221,8 +201,6 @@ func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificat
} }
httpServer.Handler = handler httpServer.Handler = handler
httpServer.TLSConfig = tlsConfig httpServer.TLSConfig = tlsConfig
httpServer.ReadTimeout = DefaultReadTimeout
httpServer.WriteTimeout = DefaultWriteTimeout
httpServer.MaxHeaderBytes = DefaultMaxHeaderBytes httpServer.MaxHeaderBytes = DefaultMaxHeaderBytes
return httpServer return httpServer
-8
View File
@@ -77,14 +77,6 @@ func TestNewServer(t *testing.T) {
t.Fatalf("Case %v: server.TCPKeepAliveTimeout: expected: %v, got: %v", (i + 1), DefaultTCPKeepAliveTimeout, server.TCPKeepAliveTimeout) t.Fatalf("Case %v: server.TCPKeepAliveTimeout: expected: %v, got: %v", (i + 1), DefaultTCPKeepAliveTimeout, server.TCPKeepAliveTimeout)
} }
if server.ReadTimeout != DefaultReadTimeout {
t.Fatalf("Case %v: server.ReadTimeout: expected: %v, got: %v", (i + 1), DefaultReadTimeout, server.ReadTimeout)
}
if server.WriteTimeout != DefaultWriteTimeout {
t.Fatalf("Case %v: server.WriteTimeout: expected: %v, got: %v", (i + 1), DefaultWriteTimeout, server.WriteTimeout)
}
if server.MaxHeaderBytes != DefaultMaxHeaderBytes { if server.MaxHeaderBytes != DefaultMaxHeaderBytes {
t.Fatalf("Case %v: server.MaxHeaderBytes: expected: %v, got: %v", (i + 1), DefaultMaxHeaderBytes, server.MaxHeaderBytes) t.Fatalf("Case %v: server.MaxHeaderBytes: expected: %v, got: %v", (i + 1), DefaultMaxHeaderBytes, server.MaxHeaderBytes)
} }
+27 -8
View File
@@ -213,7 +213,7 @@ func (ies *IAMEtcdStore) migrateToV1() error {
case errConfigNotFound: case errConfigNotFound:
// Need to migrate to V1. // Need to migrate to V1.
default: default:
return errors.New("corrupt IAM format file") return err
} }
} else { } else {
if iamFmt.Version >= iamFormatVersion1 { if iamFmt.Version >= iamFormatVersion1 {
@@ -415,23 +415,35 @@ func (ies *IAMEtcdStore) loadAll(sys *IAMSys, objectAPI ObjectLayer) error {
iamUserPolicyMap := make(map[string]MappedPolicy) iamUserPolicyMap := make(map[string]MappedPolicy)
iamGroupPolicyMap := make(map[string]MappedPolicy) iamGroupPolicyMap := make(map[string]MappedPolicy)
isMinIOUsersSys := false
sys.RLock()
if sys.usersSysType == MinIOUsersSysType {
isMinIOUsersSys = true
}
sys.RUnlock()
if err := ies.loadPolicyDocs(iamPolicyDocsMap); err != nil { if err := ies.loadPolicyDocs(iamPolicyDocsMap); err != nil {
return err return err
} }
if err := ies.loadUsers(false, iamUsersMap); err != nil {
// load STS temp users
if err := ies.loadUsers(true, iamUsersMap); err != nil {
return err return err
} }
// load STS temp users into the same map
if err := ies.loadUsers(true, iamUsersMap); err != nil { if isMinIOUsersSys {
// load long term users
if err := ies.loadUsers(false, iamUsersMap); err != nil {
return err return err
} }
if err := ies.loadGroups(iamGroupsMap); err != nil { if err := ies.loadGroups(iamGroupsMap); err != nil {
return err return err
} }
if err := ies.loadMappedPolicies(false, false, iamUserPolicyMap); err != nil { if err := ies.loadMappedPolicies(false, false, iamUserPolicyMap); err != nil {
return err return err
} }
}
// load STS policy mappings into the same map // load STS policy mappings into the same map
if err := ies.loadMappedPolicies(true, false, iamUserPolicyMap); err != nil { if err := ies.loadMappedPolicies(true, false, iamUserPolicyMap); err != nil {
return err return err
@@ -491,23 +503,29 @@ func (ies *IAMEtcdStore) deleteGroupInfo(name string) error {
func (ies *IAMEtcdStore) watch(sys *IAMSys) { func (ies *IAMEtcdStore) watch(sys *IAMSys) {
watchEtcd := func() { watchEtcd := func() {
// Refresh IAMSys with etcd watch.
for { for {
outerLoop:
// Refresh IAMSys with etcd watch.
watchCh := ies.client.Watch(context.Background(), watchCh := ies.client.Watch(context.Background(),
iamConfigPrefix, etcd.WithPrefix(), etcd.WithKeysOnly()) iamConfigPrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
for {
select { select {
case <-GlobalServiceDoneCh: case <-GlobalServiceDoneCh:
return return
case watchResp, ok := <-watchCh: case watchResp, ok := <-watchCh:
if !ok { if !ok {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
continue // Upon an error on watch channel
// re-init the watch channel.
goto outerLoop
} }
if err := watchResp.Err(); err != nil { if err := watchResp.Err(); err != nil {
logger.LogIf(context.Background(), err) logger.LogIf(context.Background(), err)
// log and retry. // log and retry.
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
continue // Upon an error on watch channel
// re-init the watch channel.
goto outerLoop
} }
for _, event := range watchResp.Events { for _, event := range watchResp.Events {
sys.Lock() sys.Lock()
@@ -517,6 +535,7 @@ func (ies *IAMEtcdStore) watch(sys *IAMSys) {
} }
} }
} }
}
go watchEtcd() go watchEtcd()
} }
+16 -7
View File
@@ -127,8 +127,8 @@ func (iamOS *IAMObjectStore) migrateUsersConfigToV1(isSTS bool) error {
identityPath := pathJoin(basePrefix, user, iamIdentityFile) identityPath := pathJoin(basePrefix, user, iamIdentityFile)
var cred auth.Credentials var cred auth.Credentials
if err := iamOS.loadIAMConfig(&cred, identityPath); err != nil { if err := iamOS.loadIAMConfig(&cred, identityPath); err != nil {
switch err.(type) { switch err {
case ObjectNotFound: case errConfigNotFound:
// This should not happen. // This should not happen.
default: default:
// File may be corrupt or network error // File may be corrupt or network error
@@ -169,7 +169,7 @@ func (iamOS *IAMObjectStore) migrateToV1() error {
case errConfigNotFound: case errConfigNotFound:
// Need to migrate to V1. // Need to migrate to V1.
default: default:
return errors.New("corrupt IAM format file") return err
} }
} else { } else {
if iamFmt.Version >= iamFormatVersion1 { if iamFmt.Version >= iamFormatVersion1 {
@@ -429,14 +429,22 @@ func (iamOS *IAMObjectStore) loadAll(sys *IAMSys, objectAPI ObjectLayer) error {
iamUserPolicyMap := make(map[string]MappedPolicy) iamUserPolicyMap := make(map[string]MappedPolicy)
iamGroupPolicyMap := make(map[string]MappedPolicy) iamGroupPolicyMap := make(map[string]MappedPolicy)
isMinIOUsersSys := false
sys.RLock()
if sys.usersSysType == MinIOUsersSysType {
isMinIOUsersSys = true
}
sys.RUnlock()
if err := iamOS.loadPolicyDocs(iamPolicyDocsMap); err != nil { if err := iamOS.loadPolicyDocs(iamPolicyDocsMap); err != nil {
return err return err
} }
if err := iamOS.loadUsers(false, iamUsersMap); err != nil { // load STS temp users
if err := iamOS.loadUsers(true, iamUsersMap); err != nil {
return err return err
} }
// load STS temp users into the same map if isMinIOUsersSys {
if err := iamOS.loadUsers(true, iamUsersMap); err != nil { if err := iamOS.loadUsers(false, iamUsersMap); err != nil {
return err return err
} }
if err := iamOS.loadGroups(iamGroupsMap); err != nil { if err := iamOS.loadGroups(iamGroupsMap); err != nil {
@@ -446,7 +454,8 @@ func (iamOS *IAMObjectStore) loadAll(sys *IAMSys, objectAPI ObjectLayer) error {
if err := iamOS.loadMappedPolicies(false, false, iamUserPolicyMap); err != nil { if err := iamOS.loadMappedPolicies(false, false, iamUserPolicyMap); err != nil {
return err return err
} }
// load STS policy mappings into the same map }
// load STS policy mappings
if err := iamOS.loadMappedPolicies(true, false, iamUserPolicyMap); err != nil { if err := iamOS.loadMappedPolicies(true, false, iamUserPolicyMap); err != nil {
return err return err
} }
+177 -7
View File
@@ -30,6 +30,20 @@ import (
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
) )
// UsersSysType - defines the type of users and groups system that is
// active on the server.
type UsersSysType string
// Types of users configured in the server.
const (
// This mode uses the internal users system in MinIO.
MinIOUsersSysType UsersSysType = "MinIOUsersSys"
// This mode uses users and groups from a configured LDAP
// server.
LDAPUsersSysType UsersSysType = "LDAPUsersSys"
)
const ( const (
// IAM configuration directory. // IAM configuration directory.
iamConfigPrefix = minioConfigPrefix + "/iam" iamConfigPrefix = minioConfigPrefix + "/iam"
@@ -145,6 +159,9 @@ func newMappedPolicy(policy string) MappedPolicy {
// IAMSys - config system. // IAMSys - config system.
type IAMSys struct { type IAMSys struct {
sync.RWMutex sync.RWMutex
usersSysType UsersSysType
// map of policy names to policy definitions // map of policy names to policy definitions
iamPolicyDocsMap map[string]iampolicy.Policy iamPolicyDocsMap map[string]iampolicy.Policy
// map of usernames to credentials // map of usernames to credentials
@@ -412,6 +429,23 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
return err return err
} }
// InfoPolicy - expands the canned policy into its JSON structure.
func (sys *IAMSys) InfoPolicy(policyName string) ([]byte, error) {
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return nil, errServerNotInitialized
}
sys.RLock()
defer sys.RUnlock()
v, ok := sys.iamPolicyDocsMap[policyName]
if !ok {
return nil, errNoSuchPolicy
}
return json.Marshal(v)
}
// ListPolicies - lists all canned policies. // ListPolicies - lists all canned policies.
func (sys *IAMSys) ListPolicies() (map[string][]byte, error) { func (sys *IAMSys) ListPolicies() (map[string][]byte, error) {
objectAPI := newObjectLayerFn() objectAPI := newObjectLayerFn()
@@ -463,6 +497,13 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
return errServerNotInitialized return errServerNotInitialized
} }
sys.Lock()
defer sys.Unlock()
if sys.usersSysType != MinIOUsersSysType {
return errIAMActionNotAllowed
}
// It is ok to ignore deletion error on the mapped policy // It is ok to ignore deletion error on the mapped policy
sys.store.deleteMappedPolicy(accessKey, false, false) sys.store.deleteMappedPolicy(accessKey, false, false)
err := sys.store.deleteUserIdentity(accessKey, false) err := sys.store.deleteUserIdentity(accessKey, false)
@@ -472,9 +513,6 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
err = nil err = nil
} }
sys.Lock()
defer sys.Unlock()
delete(sys.iamUsersMap, accessKey) delete(sys.iamUsersMap, accessKey)
delete(sys.iamUserPolicyMap, accessKey) delete(sys.iamUserPolicyMap, accessKey)
@@ -533,6 +571,10 @@ func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) {
sys.RLock() sys.RLock()
defer sys.RUnlock() defer sys.RUnlock()
if sys.usersSysType != MinIOUsersSysType {
return nil, errIAMActionNotAllowed
}
for k, v := range sys.iamUsersMap { for k, v := range sys.iamUsersMap {
users[k] = madmin.UserInfo{ users[k] = madmin.UserInfo{
PolicyName: sys.iamUserPolicyMap[k].Policy, PolicyName: sys.iamUserPolicyMap[k].Policy,
@@ -553,6 +595,13 @@ func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) {
sys.RLock() sys.RLock()
defer sys.RUnlock() defer sys.RUnlock()
if sys.usersSysType != MinIOUsersSysType {
return madmin.UserInfo{
PolicyName: sys.iamUserPolicyMap[name].Policy,
MemberOf: sys.iamUserGroupMemberships[name].ToSlice(),
}, nil
}
creds, found := sys.iamUsersMap[name] creds, found := sys.iamUsersMap[name]
if !found { if !found {
return u, errNoSuchUser return u, errNoSuchUser
@@ -580,6 +629,10 @@ func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus)
sys.Lock() sys.Lock()
defer sys.Unlock() defer sys.Unlock()
if sys.usersSysType != MinIOUsersSysType {
return errIAMActionNotAllowed
}
cred, ok := sys.iamUsersMap[accessKey] cred, ok := sys.iamUsersMap[accessKey]
if !ok { if !ok {
return errNoSuchUser return errNoSuchUser
@@ -614,6 +667,10 @@ func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
sys.Lock() sys.Lock()
defer sys.Unlock() defer sys.Unlock()
if sys.usersSysType != MinIOUsersSysType {
return errIAMActionNotAllowed
}
if err := sys.store.saveUserIdentity(accessKey, false, u); err != nil { if err := sys.store.saveUserIdentity(accessKey, false, u); err != nil {
return err return err
} }
@@ -636,6 +693,10 @@ func (sys *IAMSys) SetUserSecretKey(accessKey string, secretKey string) error {
sys.Lock() sys.Lock()
defer sys.Unlock() defer sys.Unlock()
if sys.usersSysType != MinIOUsersSysType {
return errIAMActionNotAllowed
}
cred, ok := sys.iamUsersMap[accessKey] cred, ok := sys.iamUsersMap[accessKey]
if !ok { if !ok {
return errNoSuchUser return errNoSuchUser
@@ -675,6 +736,10 @@ func (sys *IAMSys) AddUsersToGroup(group string, members []string) error {
sys.Lock() sys.Lock()
defer sys.Unlock() defer sys.Unlock()
if sys.usersSysType != MinIOUsersSysType {
return errIAMActionNotAllowed
}
// Validate that all members exist. // Validate that all members exist.
for _, member := range members { for _, member := range members {
_, ok := sys.iamUsersMap[member] _, ok := sys.iamUsersMap[member]
@@ -729,6 +794,10 @@ func (sys *IAMSys) RemoveUsersFromGroup(group string, members []string) error {
sys.Lock() sys.Lock()
defer sys.Unlock() defer sys.Unlock()
if sys.usersSysType != MinIOUsersSysType {
return errIAMActionNotAllowed
}
// Validate that all members exist. // Validate that all members exist.
for _, member := range members { for _, member := range members {
_, ok := sys.iamUsersMap[member] _, ok := sys.iamUsersMap[member]
@@ -802,6 +871,10 @@ func (sys *IAMSys) SetGroupStatus(group string, enabled bool) error {
sys.Lock() sys.Lock()
defer sys.Unlock() defer sys.Unlock()
if sys.usersSysType != MinIOUsersSysType {
return errIAMActionNotAllowed
}
if group == "" { if group == "" {
return errInvalidArgument return errInvalidArgument
} }
@@ -830,12 +903,20 @@ func (sys *IAMSys) GetGroupDescription(group string) (gd madmin.GroupDesc, err e
if err != nil { if err != nil {
return gd, err return gd, err
} }
// A group may be mapped to at most one policy. // A group may be mapped to at most one policy.
policy := "" policy := ""
if len(ps) > 0 { if len(ps) > 0 {
policy = ps[0] policy = ps[0]
} }
if sys.usersSysType != MinIOUsersSysType {
return madmin.GroupDesc{
Name: group,
Policy: policy,
}, nil
}
sys.RLock() sys.RLock()
defer sys.RUnlock() defer sys.RUnlock()
@@ -852,15 +933,19 @@ func (sys *IAMSys) GetGroupDescription(group string) (gd madmin.GroupDesc, err e
}, nil }, nil
} }
// ListGroups - lists groups // ListGroups - lists groups.
func (sys *IAMSys) ListGroups() (r []string) { func (sys *IAMSys) ListGroups() (r []string, err error) {
sys.RLock() sys.RLock()
defer sys.RUnlock() defer sys.RUnlock()
if sys.usersSysType != MinIOUsersSysType {
return nil, errIAMActionNotAllowed
}
for k := range sys.iamGroupsMap { for k := range sys.iamGroupsMap {
r = append(r, k) r = append(r, k)
} }
return r return r, nil
} }
// PolicyDBSet - sets a policy for a user or group in the // PolicyDBSet - sets a policy for a user or group in the
@@ -889,6 +974,8 @@ func (sys *IAMSys) policyDBSet(objectAPI ObjectLayer, name, policy string, isSTS
if _, ok := sys.iamPolicyDocsMap[policy]; !ok { if _, ok := sys.iamPolicyDocsMap[policy]; !ok {
return errNoSuchPolicy return errNoSuchPolicy
} }
if sys.usersSysType == MinIOUsersSysType {
if !isGroup { if !isGroup {
if _, ok := sys.iamUsersMap[name]; !ok { if _, ok := sys.iamUsersMap[name]; !ok {
return errNoSuchUser return errNoSuchUser
@@ -898,6 +985,7 @@ func (sys *IAMSys) policyDBSet(objectAPI ObjectLayer, name, policy string, isSTS
return errNoSuchGroup return errNoSuchGroup
} }
} }
}
mp := newMappedPolicy(policy) mp := newMappedPolicy(policy)
if err := sys.store.saveMappedPolicy(name, isSTS, isGroup, mp); err != nil { if err := sys.store.saveMappedPolicy(name, isSTS, isGroup, mp); err != nil {
@@ -945,8 +1033,14 @@ func (sys *IAMSys) policyDBGet(name string, isGroup bool) ([]string, error) {
return []string{policy.Policy}, nil return []string{policy.Policy}, nil
} }
if _, ok := sys.iamUsersMap[name]; !ok { // When looking for a user's policies, we also check if the
// user and the groups they are member of are enabled.
if u, ok := sys.iamUsersMap[name]; !ok {
return nil, errNoSuchUser return nil, errNoSuchUser
} else if u.Status == statusDisabled {
// User is disabled, so we return no policy - this
// ensures the request is denied.
return nil, nil
} }
result := []string{} result := []string{}
@@ -956,6 +1050,12 @@ func (sys *IAMSys) policyDBGet(name string, isGroup bool) ([]string, error) {
result = append(result, policy.Policy) result = append(result, policy.Policy)
} }
for _, group := range sys.iamUserGroupMemberships[name].ToSlice() { for _, group := range sys.iamUserGroupMemberships[name].ToSlice() {
// Skip missing or disabled groups
gi, ok := sys.iamGroupsMap[group]
if !ok || gi.Status == statusDisabled {
continue
}
p, ok := sys.iamGroupPolicyMap[group] p, ok := sys.iamGroupPolicyMap[group]
if ok && p.Policy != "" { if ok && p.Policy != "" {
result = append(result, p.Policy) result = append(result, p.Policy)
@@ -968,6 +1068,63 @@ func (sys *IAMSys) policyDBGet(name string, isGroup bool) ([]string, error) {
// which implements claims validation and verification other than // which implements claims validation and verification other than
// applying policies. // applying policies.
func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool { func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool {
// If it is an LDAP request, check that user and group
// policies allow the request.
if userIface, ok := args.Claims[ldapUser]; ok {
var user string
if u, ok := userIface.(string); ok {
user = u
} else {
return false
}
var groups []string
groupsVal := args.Claims[ldapGroups]
if g, ok := groupsVal.([]interface{}); ok {
for _, eachG := range g {
if eachGStr, ok := eachG.(string); ok {
groups = append(groups, eachGStr)
}
}
} else {
return false
}
sys.RLock()
defer sys.RUnlock()
// We look up the policy mapping directly to bypass
// users exists, group exists validations that do not
// apply here.
var policies []iampolicy.Policy
if policy, ok := sys.iamUserPolicyMap[user]; ok {
p, found := sys.iamPolicyDocsMap[policy.Policy]
if found {
policies = append(policies, p)
}
}
for _, group := range groups {
policy, ok := sys.iamGroupPolicyMap[group]
if !ok {
continue
}
p, found := sys.iamPolicyDocsMap[policy.Policy]
if found {
policies = append(policies, p)
}
}
if len(policies) == 0 {
return false
}
combinedPolicy := policies[0]
for i := 1; i < len(policies); i++ {
combinedPolicy.Statements =
append(combinedPolicy.Statements,
policies[i].Statements...)
}
return combinedPolicy.IsAllowed(args)
}
pname, ok := args.Claims[iampolicy.PolicyName] pname, ok := args.Claims[iampolicy.PolicyName]
if !ok { if !ok {
// When claims are set, it should have a "policy" field. // When claims are set, it should have a "policy" field.
@@ -1143,7 +1300,20 @@ func (sys *IAMSys) removeGroupFromMembershipsMap(group string) {
// NewIAMSys - creates new config system object. // NewIAMSys - creates new config system object.
func NewIAMSys() *IAMSys { func NewIAMSys() *IAMSys {
// Check global server configuration to determine the type of
// users system configured.
// The default users system
var utype UsersSysType
switch {
case globalServerConfig.LDAPServerConfig.ServerAddr != "":
utype = LDAPUsersSysType
default:
utype = MinIOUsersSysType
}
return &IAMSys{ return &IAMSys{
usersSysType: utype,
iamUsersMap: make(map[string]auth.Credentials), iamUsersMap: make(map[string]auth.Credentials),
iamPolicyDocsMap: make(map[string]iampolicy.Policy), iamPolicyDocsMap: make(map[string]iampolicy.Policy),
iamUserPolicyMap: make(map[string]MappedPolicy), iamUserPolicyMap: make(map[string]MappedPolicy),
+178
View File
@@ -0,0 +1,178 @@
/*
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"crypto/tls"
"errors"
"fmt"
"log"
"os"
"regexp"
"time"
ldap "gopkg.in/ldap.v3"
)
const (
defaultLDAPExpiry = time.Hour * 1
)
// ldapServerConfig contains server connectivity information.
type ldapServerConfig struct {
IsEnabled bool `json:"enabled"`
// E.g. "ldap.minio.io:636"
ServerAddr string `json:"serverAddr"`
// STS credentials expiry duration
STSExpiryDuration string `json:"stsExpiryDuration"`
stsExpiryDuration time.Duration // contains converted value
// Skips TLS verification (for testing, not
// recommended in production).
SkipTLSVerify bool `json:"skipTLSverify"`
// Format string for usernames
UsernameFormat string `json:"usernameFormat"`
GroupSearchBaseDN string `json:"groupSearchBaseDN"`
GroupSearchFilter string `json:"groupSearchFilter"`
GroupNameAttribute string `json:"groupNameAttribute"`
}
func (l *ldapServerConfig) Connect() (ldapConn *ldap.Conn, err error) {
if l == nil {
// Happens when LDAP is not configured.
return
}
if l.SkipTLSVerify {
ldapConn, err = ldap.DialTLS("tcp", l.ServerAddr, &tls.Config{InsecureSkipVerify: true})
} else {
ldapConn, err = ldap.DialTLS("tcp", l.ServerAddr, &tls.Config{})
}
return
}
// newLDAPConfigFromEnv loads configuration from the environment
func newLDAPConfigFromEnv() (l ldapServerConfig, err error) {
if ldapServer, ok := os.LookupEnv("MINIO_IDENTITY_LDAP_SERVER_ADDR"); ok {
l.IsEnabled = true
l.ServerAddr = ldapServer
if v := os.Getenv("MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY"); v == "true" {
l.SkipTLSVerify = true
}
if v := os.Getenv("MINIO_IDENTITY_LDAP_STS_EXPIRY"); v != "" {
expDur, err := time.ParseDuration(v)
if err != nil {
return l, errors.New("LDAP expiry time err:" + err.Error())
}
if expDur <= 0 {
return l, errors.New("LDAP expiry time has to be positive")
}
l.STSExpiryDuration = v
l.stsExpiryDuration = expDur
} else {
l.stsExpiryDuration = defaultLDAPExpiry
}
if v := os.Getenv("MINIO_IDENTITY_LDAP_USERNAME_FORMAT"); v != "" {
subs := newSubstituter("username", "test")
if _, err := subs.substitute(v); err != nil {
return l, errors.New("Only username may be substituted in the username format")
}
l.UsernameFormat = v
}
grpSearchFilter := os.Getenv("MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER")
grpSearchNameAttr := os.Getenv("MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE")
grpSearchBaseDN := os.Getenv("MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN")
// Either all group params must be set or none must be set.
allNotSet := grpSearchFilter == "" && grpSearchNameAttr == "" && grpSearchBaseDN == ""
allSet := grpSearchFilter != "" && grpSearchNameAttr != "" && grpSearchBaseDN != ""
if !allNotSet && !allSet {
return l, errors.New("All group related parameters must be set")
}
if allSet {
subs := newSubstituter("username", "test", "usernamedn", "test2")
if _, err := subs.substitute(grpSearchFilter); err != nil {
return l, errors.New("Only username and usernamedn may be substituted in the group search filter string")
}
l.GroupSearchFilter = grpSearchFilter
l.GroupNameAttribute = grpSearchNameAttr
subs = newSubstituter("username", "test", "usernamedn", "test2")
if _, err := subs.substitute(grpSearchBaseDN); err != nil {
return l, errors.New("Only username and usernamedn may be substituted in the base DN string")
}
l.GroupSearchBaseDN = grpSearchBaseDN
}
}
return
}
// substituter - This type is to allow restricted runtime
// substitutions of variables in LDAP configuration items during
// runtime.
type substituter struct {
vals map[string]string
}
// newSubstituter - sets up the substituter for usage, for e.g.:
//
// subber := newSubstituter("username", "john")
func newSubstituter(v ...string) substituter {
if len(v)%2 != 0 {
log.Fatal("Need an even number of arguments")
}
vals := make(map[string]string)
for i := 0; i < len(v); i += 2 {
vals[v[i]] = v[i+1]
}
return substituter{vals: vals}
}
// substitute - performs substitution on the given string `t`. Returns
// an error if there are any variables in the input that do not have
// values in the substituter. E.g.:
//
// subber.substitute("uid=${username},cn=users,dc=example,dc=com")
//
// returns "uid=john,cn=users,dc=example,dc=com"
//
// whereas:
//
// subber.substitute("uid=${usernamedn}")
//
// returns an error.
func (s *substituter) substitute(t string) (string, error) {
for k, v := range s.vals {
re := regexp.MustCompile(fmt.Sprintf(`\$\{%s\}`, k))
t = re.ReplaceAllLiteralString(t, v)
}
// Check if all requested substitutions have been made.
re := regexp.MustCompile(`\$\{.*\}`)
if re.MatchString(t) {
return "", errors.New("unsupported substitution requested")
}
return t, nil
}
+9 -3
View File
@@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/xml" "encoding/xml"
"fmt"
"path" "path"
"strings" "strings"
"sync" "sync"
@@ -134,7 +135,10 @@ func (sys *LifecycleSys) Init(objAPI ObjectLayer) error {
// the following reasons: // the following reasons:
// - Read quorum is lost just after the initialization // - Read quorum is lost just after the initialization
// of the object layer. // of the object layer.
for range newRetryTimerSimple(doneCh) { retryTimerCh := newRetryTimerSimple(doneCh)
for {
select {
case <-retryTimerCh:
// Load LifecycleSys once during boot. // Load LifecycleSys once during boot.
if err := sys.refresh(objAPI); err != nil { if err := sys.refresh(objAPI); err != nil {
if err == errDiskNotFound || if err == errDiskNotFound ||
@@ -145,9 +149,11 @@ func (sys *LifecycleSys) Init(objAPI ObjectLayer) error {
} }
return err return err
} }
break
}
return nil return nil
case <-globalOSSignalCh:
return fmt.Errorf("Initializing Lifecycle sub-system gracefully stopped")
}
}
} }
// Refresh LifecycleSys. // Refresh LifecycleSys.
+23 -4
View File
@@ -43,6 +43,20 @@ type lockRESTClient struct {
timer *time.Timer timer *time.Timer
} }
func toLockError(err error) error {
if err == nil {
return nil
}
switch err.Error() {
case errLockConflict.Error():
return errLockConflict
case errLockNotExpired.Error():
return errLockNotExpired
}
return err
}
// ServerAddr - dsync.NetLocker interface compatible method. // ServerAddr - dsync.NetLocker interface compatible method.
func (client *lockRESTClient) ServerAddr() string { func (client *lockRESTClient) ServerAddr() string {
return client.serverURL.Host return client.serverURL.Host
@@ -88,7 +102,6 @@ func (client *lockRESTClient) markHostDown() {
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints() // permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
// after verifying format.json // after verifying format.json
func (client *lockRESTClient) call(method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) { func (client *lockRESTClient) call(method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
if !client.isHostUp() { if !client.isHostUp() {
return nil, errors.New("Lock rest server node is down") return nil, errors.New("Lock rest server node is down")
} }
@@ -98,7 +111,6 @@ func (client *lockRESTClient) call(method string, values url.Values, body io.Rea
} }
respBody, err = client.restClient.Call(method, values, body, length) respBody, err = client.restClient.Call(method, values, body, length)
if err == nil { if err == nil {
return respBody, nil return respBody, nil
} }
@@ -107,7 +119,7 @@ func (client *lockRESTClient) call(method string, values url.Values, body io.Rea
client.markHostDown() client.markHostDown()
} }
return nil, err return nil, toLockError(err)
} }
// Stringer provides a canonicalized representation of node. // Stringer provides a canonicalized representation of node.
@@ -138,7 +150,14 @@ func (client *lockRESTClient) restCall(call string, args dsync.LockArgs) (reply
respBody, err := client.call(call, values, nil, -1) respBody, err := client.call(call, values, nil, -1)
defer http.DrainBody(respBody) defer http.DrainBody(respBody)
return err == nil, err switch err {
case nil:
return true, nil
case errLockConflict, errLockNotExpired:
return false, nil
default:
return false, err
}
} }
// RLock calls read lock REST API. // RLock calls read lock REST API.
+4
View File
@@ -17,6 +17,7 @@
package cmd package cmd
import ( import (
"errors"
"path" "path"
"time" "time"
) )
@@ -53,6 +54,9 @@ type nameLockRequesterInfoPair struct {
lri lockRequesterInfo lri lockRequesterInfo
} }
var errLockConflict = errors.New("lock conflict")
var errLockNotExpired = errors.New("lock not expired")
// Similar to removeEntry but only removes an entry only if the lock entry exists in map. // Similar to removeEntry but only removes an entry only if the lock entry exists in map.
func (l *localLocker) removeEntryIfExists(nlrip nameLockRequesterInfoPair) { func (l *localLocker) removeEntryIfExists(nlrip nameLockRequesterInfoPair) {
// Check if entry is still in map (could have been removed altogether by 'concurrent' (R)Unlock of last entry) // Check if entry is still in map (could have been removed altogether by 'concurrent' (R)Unlock of last entry)
+1 -1
View File
@@ -35,7 +35,7 @@ func createLockTestServer(t *testing.T) (string, *lockRESTServer, string) {
} }
locker := &lockRESTServer{ locker := &lockRESTServer{
ll: localLocker{ ll: &localLocker{
mutex: sync.Mutex{}, mutex: sync.Mutex{},
serviceEndpoint: "rpc-path", serviceEndpoint: "rpc-path",
lockMap: make(map[string][]lockRequesterInfo), lockMap: make(map[string][]lockRequesterInfo),
+30 -14
View File
@@ -42,7 +42,7 @@ const (
// To abstract a node over network. // To abstract a node over network.
type lockRESTServer struct { type lockRESTServer struct {
ll localLocker ll *localLocker
} }
func (l *lockRESTServer) writeErrorResponse(w http.ResponseWriter, err error) { func (l *lockRESTServer) writeErrorResponse(w http.ResponseWriter, err error) {
@@ -62,6 +62,7 @@ func (l *lockRESTServer) IsValid(w http.ResponseWriter, r *http.Request) bool {
func getLockArgs(r *http.Request) dsync.LockArgs { func getLockArgs(r *http.Request) dsync.LockArgs {
return dsync.LockArgs{ return dsync.LockArgs{
UID: r.URL.Query().Get(lockRESTUID), UID: r.URL.Query().Get(lockRESTUID),
Source: r.URL.Query().Get(lockRESTSource),
Resource: r.URL.Query().Get(lockRESTResource), Resource: r.URL.Query().Get(lockRESTResource),
ServerAddr: r.URL.Query().Get(lockRESTServerAddr), ServerAddr: r.URL.Query().Get(lockRESTServerAddr),
ServiceEndpoint: r.URL.Query().Get(lockRESTServerEndpoint), ServiceEndpoint: r.URL.Query().Get(lockRESTServerEndpoint),
@@ -75,7 +76,11 @@ func (l *lockRESTServer) LockHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if _, err := l.ll.Lock(getLockArgs(r)); err != nil { success, err := l.ll.Lock(getLockArgs(r))
if err == nil && !success {
err = errLockConflict
}
if err != nil {
l.writeErrorResponse(w, err) l.writeErrorResponse(w, err)
return return
} }
@@ -88,7 +93,10 @@ func (l *lockRESTServer) UnlockHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if _, err := l.ll.Unlock(getLockArgs(r)); err != nil { _, err := l.ll.Unlock(getLockArgs(r))
// Ignore the Unlock() "reply" return value because if err == nil, "reply" is always true
// Consequently, if err != nil, reply is always false
if err != nil {
l.writeErrorResponse(w, err) l.writeErrorResponse(w, err)
return return
} }
@@ -101,7 +109,11 @@ func (l *lockRESTServer) RLockHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if _, err := l.ll.RLock(getLockArgs(r)); err != nil { success, err := l.ll.RLock(getLockArgs(r))
if err == nil && !success {
err = errLockConflict
}
if err != nil {
l.writeErrorResponse(w, err) l.writeErrorResponse(w, err)
return return
} }
@@ -114,7 +126,10 @@ func (l *lockRESTServer) RUnlockHandler(w http.ResponseWriter, r *http.Request)
return return
} }
if _, err := l.ll.RUnlock(getLockArgs(r)); err != nil { // Ignore the RUnlock() "reply" return value because if err == nil, "reply" is always true.
// Consequently, if err != nil, reply is always false
_, err := l.ll.RUnlock(getLockArgs(r))
if err != nil {
l.writeErrorResponse(w, err) l.writeErrorResponse(w, err)
return return
} }
@@ -127,6 +142,8 @@ func (l *lockRESTServer) ForceUnlockHandler(w http.ResponseWriter, r *http.Reque
return return
} }
// Ignore the ForceUnlock() "reply" return value because if err == nil, "reply" is always true
// Consequently, if err != nil, reply is always false
if _, err := l.ll.ForceUnlock(getLockArgs(r)); err != nil { if _, err := l.ll.ForceUnlock(getLockArgs(r)); err != nil {
l.writeErrorResponse(w, err) l.writeErrorResponse(w, err)
return return
@@ -142,7 +159,6 @@ func (l *lockRESTServer) ExpiredHandler(w http.ResponseWriter, r *http.Request)
lockArgs := getLockArgs(r) lockArgs := getLockArgs(r)
success := true
l.ll.mutex.Lock() l.ll.mutex.Lock()
defer l.ll.mutex.Unlock() defer l.ll.mutex.Unlock()
// Lock found, proceed to verify if belongs to given uid. // Lock found, proceed to verify if belongs to given uid.
@@ -150,16 +166,12 @@ func (l *lockRESTServer) ExpiredHandler(w http.ResponseWriter, r *http.Request)
// Check whether uid is still active // Check whether uid is still active
for _, entry := range lri { for _, entry := range lri {
if entry.UID == lockArgs.UID { if entry.UID == lockArgs.UID {
success = false // When uid found, lock is still active so return not expired. l.writeErrorResponse(w, errLockNotExpired)
break
}
}
}
if !success {
l.writeErrorResponse(w, errors.New("lock already expired"))
return return
} }
} }
}
}
// lockMaintenance loops over locks that have been active for some time and checks back // lockMaintenance loops over locks that have been active for some time and checks back
// with the original server whether it is still alive or not // with the original server whether it is still alive or not
@@ -189,11 +201,15 @@ func (l *lockRESTServer) lockMaintenance(interval time.Duration) {
} }
// Call back to original server verify whether the lock is still active (based on name & uid) // Call back to original server verify whether the lock is still active (based on name & uid)
expired, _ := c.Expired(dsync.LockArgs{ expired, err := c.Expired(dsync.LockArgs{
UID: nlrip.lri.UID, UID: nlrip.lri.UID,
Resource: nlrip.name, Resource: nlrip.name,
}) })
if err != nil {
continue
}
// For successful response, verify if lock was indeed active or stale. // For successful response, verify if lock was indeed active or stale.
if expired { if expired {
// The lock is no longer active at server that originated // The lock is no longer active at server that originated
+69 -13
View File
@@ -1,5 +1,5 @@
/* /*
* MinIO Cloud Storage, (C) 2018 MinIO, Inc. * MinIO Cloud Storage, (C) 2018, 2019 MinIO, Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,6 +17,9 @@
package logger package logger
import ( import (
"bytes"
"fmt"
"io"
"net/http" "net/http"
"time" "time"
@@ -27,9 +30,18 @@ import (
// ResponseWriter - is a wrapper to trap the http response status code. // ResponseWriter - is a wrapper to trap the http response status code.
type ResponseWriter struct { type ResponseWriter struct {
http.ResponseWriter http.ResponseWriter
statusCode int StatusCode int
startTime time.Time // Response body should be logged
timeToFirstByte time.Duration LogBody bool
TimeToFirstByte time.Duration
StartTime time.Time
// number of bytes written
bytesWritten int
// Internal recording buffer
headers bytes.Buffer
body bytes.Buffer
// Indicate if headers are written in the log
headersLogged bool
} }
// NewResponseWriter - returns a wrapped response writer to trap // NewResponseWriter - returns a wrapped response writer to trap
@@ -37,25 +49,64 @@ type ResponseWriter struct {
func NewResponseWriter(w http.ResponseWriter) *ResponseWriter { func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
return &ResponseWriter{ return &ResponseWriter{
ResponseWriter: w, ResponseWriter: w,
statusCode: http.StatusOK, StatusCode: http.StatusOK,
startTime: time.Now().UTC(), StartTime: time.Now().UTC(),
} }
} }
func (lrw *ResponseWriter) Write(p []byte) (int, error) { func (lrw *ResponseWriter) Write(p []byte) (int, error) {
n, err := lrw.ResponseWriter.Write(p) n, err := lrw.ResponseWriter.Write(p)
lrw.bytesWritten += n
if lrw.TimeToFirstByte == 0 {
lrw.TimeToFirstByte = time.Now().UTC().Sub(lrw.StartTime)
}
if !lrw.headersLogged {
// We assume the response code to be '200 OK' when WriteHeader() is not called,
// that way following Golang HTTP response behavior.
lrw.writeHeaders(&lrw.headers, http.StatusOK, lrw.Header())
lrw.headersLogged = true
}
if lrw.StatusCode >= http.StatusBadRequest || lrw.LogBody {
// Always logging error responses.
lrw.body.Write(p)
}
if err != nil { if err != nil {
return n, err return n, err
} }
if lrw.timeToFirstByte == 0 {
lrw.timeToFirstByte = time.Now().UTC().Sub(lrw.startTime)
}
return n, err return n, err
} }
// Write the headers into the given buffer
func (lrw *ResponseWriter) writeHeaders(w io.Writer, statusCode int, headers http.Header) {
n, _ := fmt.Fprintf(w, "%d %s\n", statusCode, http.StatusText(statusCode))
lrw.bytesWritten += n
for k, v := range headers {
n, _ := fmt.Fprintf(w, "%s: %s\n", k, v[0])
lrw.bytesWritten += n
}
}
// BodyPlaceHolder returns a dummy body placeholder
var BodyPlaceHolder = []byte("<BODY>")
// Body - Return response body.
func (lrw *ResponseWriter) Body() []byte {
// If there was an error response or body logging is enabled
// then we return the body contents
if lrw.StatusCode >= http.StatusBadRequest || lrw.LogBody {
return lrw.body.Bytes()
}
// ... otherwise we return the <BODY> place holder
return BodyPlaceHolder
}
// WriteHeader - writes http status code // WriteHeader - writes http status code
func (lrw *ResponseWriter) WriteHeader(code int) { func (lrw *ResponseWriter) WriteHeader(code int) {
lrw.statusCode = code lrw.StatusCode = code
if !lrw.headersLogged {
lrw.writeHeaders(&lrw.headers, code, lrw.ResponseWriter.Header())
lrw.headersLogged = true
}
lrw.ResponseWriter.WriteHeader(code) lrw.ResponseWriter.WriteHeader(code)
} }
@@ -64,6 +115,11 @@ func (lrw *ResponseWriter) Flush() {
lrw.ResponseWriter.(http.Flusher).Flush() lrw.ResponseWriter.(http.Flusher).Flush()
} }
// Size - reutrns the number of bytes written
func (lrw *ResponseWriter) Size() int {
return lrw.bytesWritten
}
// AuditTargets is the list of enabled audit loggers // AuditTargets is the list of enabled audit loggers
var AuditTargets = []Target{} var AuditTargets = []Target{}
@@ -80,9 +136,9 @@ func AuditLog(w http.ResponseWriter, r *http.Request, api string, reqClaims map[
var timeToFirstByte time.Duration var timeToFirstByte time.Duration
lrw, ok := w.(*ResponseWriter) lrw, ok := w.(*ResponseWriter)
if ok { if ok {
statusCode = lrw.statusCode statusCode = lrw.StatusCode
timeToResponse = time.Now().UTC().Sub(lrw.startTime) timeToResponse = time.Now().UTC().Sub(lrw.StartTime)
timeToFirstByte = lrw.timeToFirstByte timeToFirstByte = lrw.TimeToFirstByte
} }
vars := mux.Vars(r) vars := mux.Vars(r)
-1
View File
@@ -116,7 +116,6 @@ func newApp(name string) *cli.App {
// Register all commands. // Register all commands.
registerCommand(serverCmd) registerCommand(serverCmd)
registerCommand(gatewayCmd) registerCommand(gatewayCmd)
registerCommand(updateCmd)
registerCommand(versionCmd) registerCommand(versionCmd)
// Set up app. // Set up app.
+1 -1
View File
@@ -28,5 +28,5 @@ const (
func registerMetricsRouter(router *mux.Router) { func registerMetricsRouter(router *mux.Router) {
// metrics router // metrics router
metricsRouter := router.NewRoute().PathPrefix(minioReservedBucketPath).Subrouter() metricsRouter := router.NewRoute().PathPrefix(minioReservedBucketPath).Subrouter()
metricsRouter.Handle(prometheusMetricsPath, metricsHandler()) metricsRouter.Handle(prometheusMetricsPath, AuthMiddleware(metricsHandler()))
} }
+14
View File
@@ -199,6 +199,7 @@ func (c *minioCollector) Collect(ch chan<- prometheus.Metric) {
} }
func metricsHandler() http.Handler { func metricsHandler() http.Handler {
registry := prometheus.NewRegistry() registry := prometheus.NewRegistry()
err := registry.Register(minioVersionInfo) err := registry.Register(minioVersionInfo)
@@ -222,4 +223,17 @@ func metricsHandler() http.Handler {
ErrorHandling: promhttp.ContinueOnError, ErrorHandling: promhttp.ContinueOnError,
}), }),
) )
}
// AuthMiddleware checks if the bearer token is valid and authorized.
func AuthMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims, _, authErr := webRequestAuthenticate(r)
if authErr != nil || !claims.VerifyIssuer("prometheus", true) {
w.WriteHeader(http.StatusForbidden)
return
}
h.ServeHTTP(w, r)
})
} }
+2 -2
View File
@@ -67,7 +67,7 @@ func newDsyncNodes(endpoints EndpointList) (clnts []dsync.NetLocker, myNode int)
myNode = len(clnts) myNode = len(clnts)
receiver := &lockRESTServer{ receiver := &lockRESTServer{
ll: localLocker{ ll: &localLocker{
serverAddr: endpoint.Host, serverAddr: endpoint.Host,
serviceEndpoint: lockServicePath, serviceEndpoint: lockServicePath,
lockMap: make(map[string][]lockRequesterInfo), lockMap: make(map[string][]lockRequesterInfo),
@@ -75,7 +75,7 @@ func newDsyncNodes(endpoints EndpointList) (clnts []dsync.NetLocker, myNode int)
} }
globalLockServer = receiver globalLockServer = receiver
locker = &(receiver.ll) locker = receiver.ll
} else { } else {
host, err := xnet.ParseHost(endpoint.Host) host, err := xnet.ParseHost(endpoint.Host)
logger.FatalIf(err, "Unable to parse Lock RPC Host") logger.FatalIf(err, "Unable to parse Lock RPC Host")
+2 -2
View File
@@ -196,9 +196,9 @@ func (d *naughtyDisk) ReadAll(volume string, path string) (buf []byte, err error
return d.disk.ReadAll(volume, path) return d.disk.ReadAll(volume, path)
} }
func (d *naughtyDisk) VerifyFile(volume, path string, empty bool, algo BitrotAlgorithm, sum []byte, shardSize int64) error { func (d *naughtyDisk) VerifyFile(volume, path string, size int64, algo BitrotAlgorithm, sum []byte, shardSize int64) error {
if err := d.calcError(); err != nil { if err := d.calcError(); err != nil {
return err return err
} }
return d.disk.VerifyFile(volume, path, empty, algo, sum, shardSize) return d.disk.VerifyFile(volume, path, size, algo, sum, shardSize)
} }
+76 -7
View File
@@ -17,7 +17,6 @@
package cmd package cmd
import ( import (
"archive/zip"
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
@@ -31,6 +30,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/klauspost/compress/zip"
"github.com/minio/minio/cmd/crypto" "github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/event" "github.com/minio/minio/pkg/event"
@@ -405,6 +405,21 @@ func (sys *NotificationSys) DownloadProfilingData(ctx context.Context, writer io
return profilingDataFound return profilingDataFound
} }
// ServerUpdate - updates remote peers.
func (sys *NotificationSys) ServerUpdate(updateURL, sha256Hex string, latestReleaseTime time.Time) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
for idx, client := range sys.peerClients {
if client == nil {
continue
}
client := client
ng.Go(context.Background(), func() error {
return client.ServerUpdate(updateURL, sha256Hex, latestReleaseTime)
}, idx, *client.host)
}
return ng.Wait()
}
// SignalService - calls signal service RPC call on all peers. // SignalService - calls signal service RPC call on all peers.
func (sys *NotificationSys) SignalService(sig serviceSignal) []NotificationPeerErr { func (sys *NotificationSys) SignalService(sig serviceSignal) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients)) ng := WithNPeers(len(sys.peerClients))
@@ -793,7 +808,10 @@ func (sys *NotificationSys) Init(objAPI ObjectLayer) error {
// the following reasons: // the following reasons:
// - Read quorum is lost just after the initialization // - Read quorum is lost just after the initialization
// of the object layer. // of the object layer.
for range newRetryTimerSimple(doneCh) { retryTimerCh := newRetryTimerSimple(doneCh)
for {
select {
case <-retryTimerCh:
if err := sys.refresh(objAPI); err != nil { if err := sys.refresh(objAPI); err != nil {
if err == errDiskNotFound || if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) || strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
@@ -803,9 +821,11 @@ func (sys *NotificationSys) Init(objAPI ObjectLayer) error {
} }
return err return err
} }
break
}
return nil return nil
case <-globalOSSignalCh:
return fmt.Errorf("Initializing Notification sub-system gracefully stopped")
}
}
} }
// AddRulesMap - adds rules map for bucket name. // AddRulesMap - adds rules map for bucket name.
@@ -907,9 +927,58 @@ func (sys *NotificationSys) Send(args eventArgs) []event.TargetIDErr {
return sys.send(args.BucketName, args.ToEvent(), targetIDs...) return sys.send(args.BucketName, args.ToEvent(), targetIDs...)
} }
// NetReadPerfInfo - Network read performance information.
func (sys *NotificationSys) NetReadPerfInfo(size int64) []ServerNetReadPerfInfo {
reply := make([]ServerNetReadPerfInfo, len(sys.peerClients))
// Execution is done serially.
for i, client := range sys.peerClients {
if client == nil {
continue
}
info, err := client.NetReadPerfInfo(size)
if err != nil {
reqInfo := (&logger.ReqInfo{}).AppendTags("remotePeer", client.host.String())
ctx := logger.SetReqInfo(context.Background(), reqInfo)
logger.LogIf(ctx, err)
info.Addr = client.host.String()
info.Error = err.Error()
}
reply[i] = info
}
return reply
}
// CollectNetPerfInfo - Collect network performance information of all peers.
func (sys *NotificationSys) CollectNetPerfInfo(size int64) map[string][]ServerNetReadPerfInfo {
reply := map[string][]ServerNetReadPerfInfo{}
// Execution is done serially.
for _, client := range sys.peerClients {
if client == nil {
continue
}
info, err := client.CollectNetPerfInfo(size)
if err != nil {
reqInfo := (&logger.ReqInfo{}).AppendTags("remotePeer", client.host.String())
ctx := logger.SetReqInfo(context.Background(), reqInfo)
logger.LogIf(ctx, err)
}
reply[client.host.String()] = info
}
return reply
}
// DrivePerfInfo - Drive speed (read and write) information // DrivePerfInfo - Drive speed (read and write) information
func (sys *NotificationSys) DrivePerfInfo() []ServerDrivesPerfInfo { func (sys *NotificationSys) DrivePerfInfo(size int64) []madmin.ServerDrivesPerfInfo {
reply := make([]ServerDrivesPerfInfo, len(sys.peerClients)) reply := make([]madmin.ServerDrivesPerfInfo, len(sys.peerClients))
var wg sync.WaitGroup var wg sync.WaitGroup
for i, client := range sys.peerClients { for i, client := range sys.peerClients {
if client == nil { if client == nil {
@@ -918,7 +987,7 @@ func (sys *NotificationSys) DrivePerfInfo() []ServerDrivesPerfInfo {
wg.Add(1) wg.Add(1)
go func(client *peerRESTClient, idx int) { go func(client *peerRESTClient, idx int) {
defer wg.Done() defer wg.Done()
di, err := client.DrivePerfInfo() di, err := client.DrivePerfInfo(size)
if err != nil { if err != nil {
reqInfo := (&logger.ReqInfo{}).AppendTags("remotePeer", client.host.String()) reqInfo := (&logger.ReqInfo{}).AppendTags("remotePeer", client.host.String())
ctx := logger.SetReqInfo(context.Background(), reqInfo) ctx := logger.SetReqInfo(context.Background(), reqInfo)
-1
View File
@@ -64,7 +64,6 @@ func testGetObjectInfo(obj ObjectLayer, instanceType string, t TestErrHandler) {
}{ }{
// Test cases with invalid bucket names ( Test number 1-4 ). // Test cases with invalid bucket names ( Test number 1-4 ).
{".test", "", ObjectInfo{}, BucketNameInvalid{Bucket: ".test"}, false}, {".test", "", ObjectInfo{}, BucketNameInvalid{Bucket: ".test"}, false},
{"Test", "", ObjectInfo{}, BucketNameInvalid{Bucket: "Test"}, false},
{"---", "", ObjectInfo{}, BucketNameInvalid{Bucket: "---"}, false}, {"---", "", ObjectInfo{}, BucketNameInvalid{Bucket: "---"}, false},
{"ad", "", ObjectInfo{}, BucketNameInvalid{Bucket: "ad"}, false}, {"ad", "", ObjectInfo{}, BucketNameInvalid{Bucket: "ad"}, false},
// Test cases with valid but non-existing bucket names (Test number 5-6). // Test cases with valid but non-existing bucket names (Test number 5-6).
+2 -1
View File
@@ -19,6 +19,7 @@ package cmd
import ( import (
"context" "context"
"github.com/minio/minio-go/v6/pkg/s3utils"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/skyrings/skyring-common/tools/uuid" "github.com/skyrings/skyring-common/tools/uuid"
) )
@@ -36,7 +37,7 @@ func checkDelObjArgs(ctx context.Context, bucket, object string) error {
// Checks bucket and object name validity, returns nil if both are valid. // Checks bucket and object name validity, returns nil if both are valid.
func checkBucketAndObjectNames(ctx context.Context, bucket, object string) error { func checkBucketAndObjectNames(ctx context.Context, bucket, object string) error {
// Verify if bucket is valid. // Verify if bucket is valid.
if !IsValidBucketName(bucket) { if !isMinioMetaBucketName(bucket) && s3utils.CheckValidBucketName(bucket) != nil {
logger.LogIf(ctx, BucketNameInvalid{Bucket: bucket}) logger.LogIf(ctx, BucketNameInvalid{Bucket: bucket})
return BucketNameInvalid{Bucket: bucket} return BucketNameInvalid{Bucket: bucket}
} }
+78 -60
View File
@@ -33,7 +33,8 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
snappy "github.com/golang/snappy" "github.com/klauspost/compress/s2"
"github.com/klauspost/readahead"
"github.com/minio/minio-go/v6/pkg/s3utils" "github.com/minio/minio-go/v6/pkg/s3utils"
"github.com/minio/minio/cmd/crypto" "github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
@@ -56,6 +57,12 @@ const (
minioMetaTmpBucket = minioMetaBucket + "/tmp" minioMetaTmpBucket = minioMetaBucket + "/tmp"
// DNS separator (period), used for bucket name validation. // DNS separator (period), used for bucket name validation.
dnsDelimiter = "." dnsDelimiter = "."
// On compressed files bigger than this;
compReadAheadSize = 100 << 20
// Read this many buffers ahead.
compReadAheadBuffers = 5
// Size of each buffer.
compReadAheadBufSize = 1 << 20
) )
// isMinioBucket returns true if given bucket is a MinIO internal // isMinioBucket returns true if given bucket is a MinIO internal
@@ -337,6 +344,22 @@ func (o ObjectInfo) IsCompressed() bool {
return ok return ok
} }
// IsCompressedOK returns whether the object is compressed and can be decompressed.
func (o ObjectInfo) IsCompressedOK() (bool, error) {
scheme, ok := o.UserDefined[ReservedMetadataPrefix+"compression"]
if !ok {
return false, nil
}
if crypto.IsEncrypted(o.UserDefined) {
return true, fmt.Errorf("compression %q and encryption enabled on same object", scheme)
}
switch scheme {
case compressionAlgorithmV1, compressionAlgorithmV2:
return true, nil
}
return true, fmt.Errorf("unknown compression scheme: %s", scheme)
}
// GetActualSize - read the decompressed size from the meta json. // GetActualSize - read the decompressed size from the meta json.
func (o ObjectInfo) GetActualSize() int64 { func (o ObjectInfo) GetActualSize() int64 {
metadata := o.UserDefined metadata := o.UserDefined
@@ -354,7 +377,7 @@ func (o ObjectInfo) GetActualSize() int64 {
// Using compression and encryption together enables room for side channel attacks. // Using compression and encryption together enables room for side channel attacks.
// Eliminate non-compressible objects by extensions/content-types. // Eliminate non-compressible objects by extensions/content-types.
func isCompressible(header http.Header, object string) bool { func isCompressible(header http.Header, object string) bool {
if hasServerSideEncryptionHeader(header) || excludeForCompression(header, object) { if crypto.IsRequested(header) || excludeForCompression(header, object) {
return false return false
} }
return true return true
@@ -364,13 +387,20 @@ func isCompressible(header http.Header, object string) bool {
func excludeForCompression(header http.Header, object string) bool { func excludeForCompression(header http.Header, object string) bool {
objStr := object objStr := object
contentType := header.Get(xhttp.ContentType) contentType := header.Get(xhttp.ContentType)
if globalIsCompressionEnabled { if !globalIsCompressionEnabled {
return true
}
// We strictly disable compression for standard extensions/content-types (`compressed`). // We strictly disable compression for standard extensions/content-types (`compressed`).
if hasStringSuffixInSlice(objStr, standardExcludeCompressExtensions) || hasPattern(standardExcludeCompressContentTypes, contentType) { if hasStringSuffixInSlice(objStr, standardExcludeCompressExtensions) || hasPattern(standardExcludeCompressContentTypes, contentType) {
return true return true
} }
// Filter compression includes. // Filter compression includes.
if len(globalCompressExtensions) > 0 || len(globalCompressMimeTypes) > 0 { if len(globalCompressExtensions) == 0 || len(globalCompressMimeTypes) == 0 {
return false
}
extensions := globalCompressExtensions extensions := globalCompressExtensions
mimeTypes := globalCompressMimeTypes mimeTypes := globalCompressMimeTypes
if hasStringSuffixInSlice(objStr, extensions) || hasPattern(mimeTypes, contentType) { if hasStringSuffixInSlice(objStr, extensions) || hasPattern(mimeTypes, contentType) {
@@ -378,15 +408,13 @@ func excludeForCompression(header http.Header, object string) bool {
} }
return true return true
} }
return false
}
return true
}
// Utility which returns if a string is present in the list. // Utility which returns if a string is present in the list.
// Comparison is case insensitive.
func hasStringSuffixInSlice(str string, list []string) bool { func hasStringSuffixInSlice(str string, list []string) bool {
str = strings.ToLower(str)
for _, v := range list { for _, v := range list {
if strings.HasSuffix(str, v) { if strings.HasSuffix(str, strings.ToLower(v)) {
return true return true
} }
} }
@@ -413,7 +441,7 @@ func getPartFile(entries []string, partNumber int, etag string) string {
return "" return ""
} }
// Returs the compressed offset which should be skipped. // Returns the compressed offset which should be skipped.
func getCompressedOffsets(objectInfo ObjectInfo, offset int64) (int64, int64) { func getCompressedOffsets(objectInfo ObjectInfo, offset int64) (int64, int64) {
var compressedOffset int64 var compressedOffset int64
var skipLength int64 var skipLength int64
@@ -494,7 +522,10 @@ func NewGetObjectReader(rs *HTTPRangeSpec, oi ObjectInfo, pcfn CheckCopyPrecondi
}() }()
isEncrypted := crypto.IsEncrypted(oi.UserDefined) isEncrypted := crypto.IsEncrypted(oi.UserDefined)
isCompressed := oi.IsCompressed() isCompressed, err := oi.IsCompressedOK()
if err != nil {
return nil, 0, 0, err
}
var skipLen int64 var skipLen int64
// Calculate range to read (different for // Calculate range to read (different for
// e.g. encrypted/compressed objects) // e.g. encrypted/compressed objects)
@@ -602,10 +633,23 @@ func NewGetObjectReader(rs *HTTPRangeSpec, oi ObjectInfo, pcfn CheckCopyPrecondi
} }
} }
// Decompression reader. // Decompression reader.
snappyReader := snappy.NewReader(inputReader) s2Reader := s2.NewReader(inputReader)
// Apply the skipLen and limit on the // Apply the skipLen and limit on the decompressed stream.
// decompressed stream err = s2Reader.Skip(decOff)
decReader := io.LimitReader(ioutil.NewSkipReader(snappyReader, decOff), decLength) if err != nil {
return nil, err
}
decReader := io.LimitReader(s2Reader, decLength)
if decLength > compReadAheadSize {
rah, err := readahead.NewReaderSize(decReader, compReadAheadBuffers, compReadAheadBufSize)
if err == nil {
decReader = rah
cFns = append(cFns, func() {
rah.Close()
})
}
}
oi.Size = decLength oi.Size = decLength
// Assemble the GetObjectReader // Assemble the GetObjectReader
@@ -760,55 +804,29 @@ func CleanMinioInternalMetadataKeys(metadata map[string]string) map[string]strin
return newMeta return newMeta
} }
// snappyCompressReader compresses data as it reads // newS2CompressReader will read data from r, compress it and return the compressed data as a Reader.
// from the underlying io.Reader. // Use Close to ensure resources are released on incomplete streams.
type snappyCompressReader struct { func newS2CompressReader(r io.Reader) io.ReadCloser {
r io.Reader pr, pw := io.Pipe()
w *snappy.Writer comp := s2.NewWriter(pw)
closed bool // Copy input to compressor
buf bytes.Buffer go func() {
} _, err := io.Copy(comp, r)
func newSnappyCompressReader(r io.Reader) *snappyCompressReader {
cr := &snappyCompressReader{r: r}
cr.w = snappy.NewBufferedWriter(&cr.buf)
return cr
}
func (cr *snappyCompressReader) Read(p []byte) (int, error) {
if cr.closed {
// if snappy writer is closed r has been completely read,
// return any remaining data in buf.
return cr.buf.Read(p)
}
// read from original using p as buffer
nr, readErr := cr.r.Read(p)
// write read bytes to snappy writer
nw, err := cr.w.Write(p[:nr])
if err != nil { if err != nil {
return 0, err comp.Close()
pw.CloseWithError(err)
return
} }
if nw != nr { // Close the stream.
return 0, io.ErrShortWrite err = comp.Close()
}
// if last of data from reader, close snappy writer to flush
if readErr == io.EOF {
err := cr.w.Close()
cr.closed = true
if err != nil { if err != nil {
return 0, err pw.CloseWithError(err)
return
} }
} // Everything ok, do regular close.
pw.Close()
// read compressed bytes out of buf }()
n, err := cr.buf.Read(p) return pr
if readErr != io.EOF && (err == nil || err == io.EOF) {
err = readErr
}
return n, err
} }
// Returns error if the cancelCh has been closed (indicating that S3 client has disconnected) // Returns error if the cancelCh has been closed (indicating that S3 client has disconnected)
+62 -13
View File
@@ -1,5 +1,5 @@
/* /*
* MinIO Cloud Storage, (C) 2016 MinIO, Inc. * MinIO Cloud Storage, (C) 2016-2019 MinIO, Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,9 +21,11 @@ import (
"io" "io"
"net/http" "net/http"
"reflect" "reflect"
"strconv"
"testing" "testing"
"github.com/golang/snappy" "github.com/klauspost/compress/s2"
"github.com/minio/minio/cmd/crypto"
) )
// Tests validate bucket name. // Tests validate bucket name.
@@ -298,10 +300,11 @@ func TestIsCompressed(t *testing.T) {
testCases := []struct { testCases := []struct {
objInfo ObjectInfo objInfo ObjectInfo
result bool result bool
err bool
}{ }{
{ {
objInfo: ObjectInfo{ objInfo: ObjectInfo{
UserDefined: map[string]string{"X-Minio-Internal-compression": "golang/snappy/LZ77", UserDefined: map[string]string{"X-Minio-Internal-compression": compressionAlgorithmV1,
"content-type": "application/octet-stream", "content-type": "application/octet-stream",
"etag": "b3ff3ef3789147152fbfbc50efba4bfd-2"}, "etag": "b3ff3ef3789147152fbfbc50efba4bfd-2"},
}, },
@@ -309,7 +312,35 @@ func TestIsCompressed(t *testing.T) {
}, },
{ {
objInfo: ObjectInfo{ objInfo: ObjectInfo{
UserDefined: map[string]string{"X-Minio-Internal-XYZ": "golang/snappy/LZ77", UserDefined: map[string]string{"X-Minio-Internal-compression": compressionAlgorithmV2,
"content-type": "application/octet-stream",
"etag": "b3ff3ef3789147152fbfbc50efba4bfd-2"},
},
result: true,
},
{
objInfo: ObjectInfo{
UserDefined: map[string]string{"X-Minio-Internal-compression": "unknown/compression/type",
"content-type": "application/octet-stream",
"etag": "b3ff3ef3789147152fbfbc50efba4bfd-2"},
},
result: true,
err: true,
},
{
objInfo: ObjectInfo{
UserDefined: map[string]string{"X-Minio-Internal-compression": compressionAlgorithmV2,
"content-type": "application/octet-stream",
"etag": "b3ff3ef3789147152fbfbc50efba4bfd-2",
crypto.SSEIV: "yes",
},
},
result: true,
err: true,
},
{
objInfo: ObjectInfo{
UserDefined: map[string]string{"X-Minio-Internal-XYZ": "klauspost/compress/s2",
"content-type": "application/octet-stream", "content-type": "application/octet-stream",
"etag": "b3ff3ef3789147152fbfbc50efba4bfd-2"}, "etag": "b3ff3ef3789147152fbfbc50efba4bfd-2"},
}, },
@@ -324,11 +355,21 @@ func TestIsCompressed(t *testing.T) {
}, },
} }
for i, test := range testCases { for i, test := range testCases {
t.Run(strconv.Itoa(i), func(t *testing.T) {
got := test.objInfo.IsCompressed() got := test.objInfo.IsCompressed()
if got != test.result { if got != test.result {
t.Errorf("Test %d - expected %v but received %v", t.Errorf("IsCompressed: Expected %v but received %v",
i+1, test.result, got) test.result, got)
} }
got, gErr := test.objInfo.IsCompressedOK()
if got != test.result {
t.Errorf("IsCompressedOK: Expected %v but received %v",
test.result, got)
}
if gErr != nil != test.err {
t.Errorf("IsCompressedOK: want error: %t, got error: %v", test.err, gErr)
}
})
} }
} }
@@ -367,6 +408,13 @@ func TestExcludeForCompression(t *testing.T) {
}, },
result: false, result: false,
}, },
{
object: "object",
header: http.Header{
"Content-Type": []string{"text/something"},
},
result: false,
},
} }
for i, test := range testCases { for i, test := range testCases {
globalIsCompressionEnabled = true globalIsCompressionEnabled = true
@@ -422,7 +470,7 @@ func TestGetActualSize(t *testing.T) {
}{ }{
{ {
objInfo: ObjectInfo{ objInfo: ObjectInfo{
UserDefined: map[string]string{"X-Minio-Internal-compression": "golang/snappy/LZ77", UserDefined: map[string]string{"X-Minio-Internal-compression": "klauspost/compress/s2",
"X-Minio-Internal-actual-size": "100000001", "X-Minio-Internal-actual-size": "100000001",
"content-type": "application/octet-stream", "content-type": "application/octet-stream",
"etag": "b3ff3ef3789147152fbfbc50efba4bfd-2"}, "etag": "b3ff3ef3789147152fbfbc50efba4bfd-2"},
@@ -441,7 +489,7 @@ func TestGetActualSize(t *testing.T) {
}, },
{ {
objInfo: ObjectInfo{ objInfo: ObjectInfo{
UserDefined: map[string]string{"X-Minio-Internal-compression": "golang/snappy/LZ77", UserDefined: map[string]string{"X-Minio-Internal-compression": "klauspost/compress/s2",
"X-Minio-Internal-actual-size": "841", "X-Minio-Internal-actual-size": "841",
"content-type": "application/octet-stream", "content-type": "application/octet-stream",
"etag": "b3ff3ef3789147152fbfbc50efba4bfd-2"}, "etag": "b3ff3ef3789147152fbfbc50efba4bfd-2"},
@@ -451,7 +499,7 @@ func TestGetActualSize(t *testing.T) {
}, },
{ {
objInfo: ObjectInfo{ objInfo: ObjectInfo{
UserDefined: map[string]string{"X-Minio-Internal-compression": "golang/snappy/LZ77", UserDefined: map[string]string{"X-Minio-Internal-compression": "klauspost/compress/s2",
"content-type": "application/octet-stream", "content-type": "application/octet-stream",
"etag": "b3ff3ef3789147152fbfbc50efba4bfd-2"}, "etag": "b3ff3ef3789147152fbfbc50efba4bfd-2"},
Parts: []ObjectPartInfo{}, Parts: []ObjectPartInfo{},
@@ -540,7 +588,7 @@ func TestGetCompressedOffsets(t *testing.T) {
} }
} }
func TestSnappyCompressReader(t *testing.T) { func TestS2CompressReader(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
data []byte data []byte
@@ -554,7 +602,8 @@ func TestSnappyCompressReader(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
buf := make([]byte, 100) // make small buffer to ensure multiple reads are required for large case buf := make([]byte, 100) // make small buffer to ensure multiple reads are required for large case
r := newSnappyCompressReader(bytes.NewReader(tt.data)) r := newS2CompressReader(bytes.NewReader(tt.data))
defer r.Close()
var rdrBuf bytes.Buffer var rdrBuf bytes.Buffer
_, err := io.CopyBuffer(&rdrBuf, r, buf) _, err := io.CopyBuffer(&rdrBuf, r, buf)
@@ -563,7 +612,7 @@ func TestSnappyCompressReader(t *testing.T) {
} }
var stdBuf bytes.Buffer var stdBuf bytes.Buffer
w := snappy.NewBufferedWriter(&stdBuf) w := s2.NewWriter(&stdBuf)
_, err = io.CopyBuffer(w, bytes.NewReader(tt.data), buf) _, err = io.CopyBuffer(w, bytes.NewReader(tt.data), buf)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -582,7 +631,7 @@ func TestSnappyCompressReader(t *testing.T) {
} }
var decBuf bytes.Buffer var decBuf bytes.Buffer
decRdr := snappy.NewReader(&rdrBuf) decRdr := s2.NewReader(&rdrBuf)
_, err = io.Copy(&decBuf, decRdr) _, err = io.Copy(&decBuf, decRdr)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
+87 -52
View File
@@ -61,6 +61,7 @@ var supportedHeadGetReqParams = map[string]string{
const ( const (
compressionAlgorithmV1 = "golang/snappy/LZ77" compressionAlgorithmV1 = "golang/snappy/LZ77"
compressionAlgorithmV2 = "klauspost/compress/s2"
) )
// setHeadGetRespHeaders - set any requested parameters as response headers. // setHeadGetRespHeaders - set any requested parameters as response headers.
@@ -92,7 +93,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { if !api.EncryptionEnabled() && crypto.IsRequested(r.Header) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -251,7 +252,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r))
return return
} }
if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { if !api.EncryptionEnabled() && crypto.IsRequested(r.Header) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -422,7 +423,7 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrBadRequest)) writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrBadRequest))
return return
} }
if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { if !api.EncryptionEnabled() && crypto.IsRequested(r.Header) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -601,8 +602,21 @@ var getRemoteInstanceClient = func(r *http.Request, host string) (*miniogo.Core,
return core, nil return core, nil
} }
// Check if the destination bucket is on a remote site, this code only gets executed
// when federation is enabled, ie when globalDNSConfig is non 'nil'.
//
// This function is similar to isRemoteCallRequired but specifically for COPY object API
// if destination and source are same we do not need to check for destnation bucket
// to exist locally.
func isRemoteCopyRequired(ctx context.Context, srcBucket, dstBucket string, objAPI ObjectLayer) bool {
if srcBucket == dstBucket {
return false
}
return isRemoteCallRequired(ctx, dstBucket, objAPI)
}
// Check if the bucket is on a remote site, this code only gets executed when federation is enabled. // Check if the bucket is on a remote site, this code only gets executed when federation is enabled.
var isRemoteCallRequired = func(ctx context.Context, bucket string, objAPI ObjectLayer) bool { func isRemoteCallRequired(ctx context.Context, bucket string, objAPI ObjectLayer) bool {
if globalDNSConfig == nil { if globalDNSConfig == nil {
return false return false
} }
@@ -633,7 +647,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
return return
} }
if !api.EncryptionEnabled() && (hasServerSideEncryptionHeader(r.Header) || crypto.SSECopy.IsRequested(r.Header)) { if !api.EncryptionEnabled() && crypto.IsRequested(r.Header) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -694,6 +708,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidMetadataDirective), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidMetadataDirective), r.URL, guessIsBrowserReq(r))
return return
} }
// This request header needs to be set prior to setting ObjectOptions // This request header needs to be set prior to setting ObjectOptions
if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) { if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) {
r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256) r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
@@ -720,11 +735,8 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
} }
cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject))
if globalWORMEnabled { // Deny if WORM is enabled.
// Deny if WORM is enabled. If operation is key rotation of SSE-S3 encrypted object if _, err := objectAPI.GetObjectInfo(ctx, dstBucket, dstObject, dstOpts); err == nil {
// allow the operation
if globalWORMEnabled && !(cpSrcDstSame && crypto.S3.IsRequested(r.Header)) {
if _, err = objectAPI.GetObjectInfo(ctx, dstBucket, dstObject, dstOpts); err == nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -762,11 +774,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
return return
} }
// Deny if WORM is enabled, and it is not a SSE-S3 -> SSE-S3 key rotation or if metadata replacement is requested.
if globalWORMEnabled && cpSrcDstSame && (!crypto.S3.IsEncrypted(srcInfo.UserDefined) || isMetadataReplace(r.Header)) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
return
}
// We have to copy metadata only if source and destination are same. // We have to copy metadata only if source and destination are same.
// this changes for encryption which can be observed below. // this changes for encryption which can be observed below.
if cpSrcDstSame { if cpSrcDstSame {
@@ -787,23 +794,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
length = actualSize length = actualSize
} }
// Check if the destination bucket is on a remote site, this code only gets executed
// when federation is enabled, ie when globalDNSConfig is non 'nil'.
//
// This function is similar to isRemoteCallRequired but specifically for COPY object API
// if destination and source are same we do not need to check for destnation bucket
// to exist locally.
var isRemoteCopyRequired = func(ctx context.Context, srcBucket, dstBucket string, objAPI ObjectLayer) bool {
if globalDNSConfig == nil {
return false
}
if srcBucket == dstBucket {
return false
}
_, err := objAPI.GetBucketInfo(ctx, dstBucket)
return err == toObjectErr(errVolumeNotFound, dstBucket)
}
var compressMetadata map[string]string var compressMetadata map[string]string
// No need to compress for remote etcd calls // No need to compress for remote etcd calls
// Pass the decompressed stream to such calls. // Pass the decompressed stream to such calls.
@@ -811,13 +801,15 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
if isCompressed { if isCompressed {
compressMetadata = make(map[string]string, 2) compressMetadata = make(map[string]string, 2)
// Preserving the compression metadata. // Preserving the compression metadata.
compressMetadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV1 compressMetadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2
compressMetadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(actualSize, 10) compressMetadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(actualSize, 10)
// Remove all source encrypted related metadata to // Remove all source encrypted related metadata to
// avoid copying them in target object. // avoid copying them in target object.
crypto.RemoveInternalEntries(srcInfo.UserDefined) crypto.RemoveInternalEntries(srcInfo.UserDefined)
reader = newSnappyCompressReader(gr) s2c := newS2CompressReader(gr)
defer s2c.Close()
reader = s2c
length = -1 length = -1
} else { } else {
// Remove the metadata for remote calls. // Remove the metadata for remote calls.
@@ -870,14 +862,12 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
// - the object is encrypted using SSE-S3 and the SSE-S3 header is present // - the object is encrypted using SSE-S3 and the SSE-S3 header is present
// than execute a key rotation. // than execute a key rotation.
var keyRotation bool var keyRotation bool
if cpSrcDstSame && ((sseCopyC && sseC) || (sseS3 && sseCopyS3)) { if cpSrcDstSame && (sseCopyC && sseC) {
if sseCopyC && sseC {
oldKey, err = ParseSSECopyCustomerRequest(r.Header, srcInfo.UserDefined) oldKey, err = ParseSSECopyCustomerRequest(r.Header, srcInfo.UserDefined)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
}
for k, v := range srcInfo.UserDefined { for k, v := range srcInfo.UserDefined {
if hasPrefix(k, ReservedMetadataPrefix) { if hasPrefix(k, ReservedMetadataPrefix) {
@@ -1055,7 +1045,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
return return
} }
if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { if !api.EncryptionEnabled() && crypto.IsRequested(r.Header) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -1067,7 +1057,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
r.Body = &detectDisconnect{r.Body, r.Context().Done()} r.Body = &detectDisconnect{r.Body, r.Context().Done()}
// X-Amz-Copy-Source shouldn't be set for this call. // X-Amz-Copy-Source shouldn't be set for this call.
if _, ok := r.Header["X-Amz-Copy-Source"]; ok { if _, ok := r.Header[xhttp.AmzCopySource]; ok {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopySource), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopySource), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -1090,7 +1080,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
size := r.ContentLength size := r.ContentLength
rAuthType := getRequestAuthType(r) rAuthType := getRequestAuthType(r)
if rAuthType == authTypeStreamingSigned { if rAuthType == authTypeStreamingSigned {
if sizeStr, ok := r.Header["X-Amz-Decoded-Content-Length"]; ok { if sizeStr, ok := r.Header[xhttp.AmzDecodedContentLength]; ok {
if sizeStr[0] == "" { if sizeStr[0] == "" {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
return return
@@ -1188,7 +1178,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > 0 { if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > 0 {
// Storing the compression metadata. // Storing the compression metadata.
metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV1 metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2
metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(size, 10) metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(size, 10)
actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat) actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat)
@@ -1198,7 +1188,9 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
} }
// Set compression metrics. // Set compression metrics.
reader = newSnappyCompressReader(actualReader) s2c := newS2CompressReader(actualReader)
defer s2c.Close()
reader = s2c
size = -1 // Since compressed size is un-predictable. size = -1 // Since compressed size is un-predictable.
md5hex = "" // Do not try to verify the content. md5hex = "" // Do not try to verify the content.
sha256hex = "" sha256hex = ""
@@ -1209,6 +1201,11 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
getObjectInfo := objectAPI.GetObjectInfo
if api.CacheAPI() != nil {
getObjectInfo = api.CacheAPI().GetObjectInfo
putObject = api.CacheAPI().PutObject
}
rawReader := hashReader rawReader := hashReader
pReader := NewPutObjReader(rawReader, nil, nil) pReader := NewPutObjReader(rawReader, nil, nil)
@@ -1223,7 +1220,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
// Deny if WORM is enabled // Deny if WORM is enabled
if globalWORMEnabled { if globalWORMEnabled {
if _, err = objectAPI.GetObjectInfo(ctx, bucket, object, opts); err == nil { if _, err = getObjectInfo(ctx, bucket, object, opts); err == nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -1231,7 +1228,11 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
var objectEncryptionKey []byte var objectEncryptionKey []byte
if objectAPI.IsEncryptionSupported() { if objectAPI.IsEncryptionSupported() {
if hasServerSideEncryptionHeader(r.Header) && !hasSuffix(object, SlashSeparator) { // handle SSE requests if crypto.IsRequested(r.Header) && !hasSuffix(object, SlashSeparator) { // handle SSE requests
if crypto.SSECopy.IsRequested(r.Header) {
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r))
return
}
reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata) reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
@@ -1263,7 +1264,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
if !strings.HasSuffix(objInfo.ETag, "-1") { if !strings.HasSuffix(objInfo.ETag, "-1") {
etag = objInfo.ETag + "-1" etag = objInfo.ETag + "-1"
} }
} else if hasServerSideEncryptionHeader(r.Header) { } else if crypto.IsRequested(r.Header) {
etag = getDecryptedETag(r.Header, objInfo, false) etag = getDecryptedETag(r.Header, objInfo, false)
} }
w.Header()[xhttp.ETag] = []string{"\"" + etag + "\""} w.Header()[xhttp.ETag] = []string{"\"" + etag + "\""}
@@ -1317,7 +1318,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
return return
} }
if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { if !api.EncryptionEnabled() && crypto.IsRequested(r.Header) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -1364,7 +1365,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
var encMetadata = map[string]string{} var encMetadata = map[string]string{}
if objectAPI.IsEncryptionSupported() { if objectAPI.IsEncryptionSupported() {
if hasServerSideEncryptionHeader(r.Header) { if crypto.IsRequested(r.Header) {
if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil { if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
@@ -1393,7 +1394,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) { if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) {
// Storing the compression metadata. // Storing the compression metadata.
metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV1 metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2
} }
opts, err = putOpts(ctx, r, bucket, object, metadata) opts, err = putOpts(ctx, r, bucket, object, metadata)
@@ -1431,7 +1432,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
return return
} }
if !api.EncryptionEnabled() && (hasServerSideEncryptionHeader(r.Header) || crypto.SSECopy.IsRequested(r.Header)) { if !api.EncryptionEnabled() && crypto.IsRequested(r.Header) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -1591,6 +1592,36 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
return return
} }
if isRemoteCopyRequired(ctx, srcBucket, dstBucket, objectAPI) {
var dstRecords []dns.SrvRecord
dstRecords, err = globalDNSConfig.Get(dstBucket)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
// Send PutObject request to appropriate instance (in federated deployment)
client, rerr := getRemoteInstanceClient(r, getHostFromSrv(dstRecords))
if rerr != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, rerr), r.URL, guessIsBrowserReq(r))
return
}
partInfo, err := client.PutObjectPart(dstBucket, dstObject, uploadID, partID,
srcInfo.Reader, srcInfo.Size, "", "", dstOpts.ServerSideEncryption)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
response := generateCopyObjectPartResponse(partInfo.ETag, partInfo.LastModified)
encodedSuccessResponse := encodeResponse(response)
// Write success response.
writeSuccessResponseXML(w, encodedSuccessResponse)
return
}
actualPartSize = length actualPartSize = length
var reader io.Reader var reader io.Reader
@@ -1606,7 +1637,9 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
isCompressed := compressPart isCompressed := compressPart
// Compress only if the compression is enabled during initial multipart. // Compress only if the compression is enabled during initial multipart.
if isCompressed { if isCompressed {
reader = newSnappyCompressReader(gr) s2c := newS2CompressReader(gr)
defer s2c.Close()
reader = s2c
length = -1 length = -1
} else { } else {
reader = gr reader = gr
@@ -1716,7 +1749,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
return return
} }
if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { if !api.EncryptionEnabled() && crypto.IsRequested(r.Header) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -1846,7 +1879,9 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
} }
// Set compression metrics. // Set compression metrics.
reader = newSnappyCompressReader(actualReader) s2c := newS2CompressReader(actualReader)
defer s2c.Close()
reader = s2c
size = -1 // Since compressed size is un-predictable. size = -1 // Since compressed size is un-predictable.
md5hex = "" // Do not try to verify the content. md5hex = "" // Do not try to verify the content.
sha256hex = "" sha256hex = ""
+97 -3
View File
@@ -22,6 +22,7 @@ import (
"crypto/tls" "crypto/tls"
"encoding/gob" "encoding/gob"
"io" "io"
"math/rand"
"net/url" "net/url"
"strconv" "strconv"
"time" "time"
@@ -107,6 +108,37 @@ func (client *peerRESTClient) Close() error {
// GetLocksResp stores various info from the client for each lock that is requested. // GetLocksResp stores various info from the client for each lock that is requested.
type GetLocksResp map[string][]lockRequesterInfo type GetLocksResp map[string][]lockRequesterInfo
// NetReadPerfInfo - fetch network read performance information for a remote node.
func (client *peerRESTClient) NetReadPerfInfo(size int64) (info ServerNetReadPerfInfo, err error) {
params := make(url.Values)
params.Set(peerRESTNetPerfSize, strconv.FormatInt(size, 10))
respBody, err := client.call(
peerRESTMethodNetReadPerfInfo,
params,
rand.New(rand.NewSource(time.Now().UnixNano())),
size,
)
if err != nil {
return
}
defer http.DrainBody(respBody)
err = gob.NewDecoder(respBody).Decode(&info)
return info, err
}
// CollectNetPerfInfo - collect network performance information of other peers.
func (client *peerRESTClient) CollectNetPerfInfo(size int64) (info []ServerNetReadPerfInfo, err error) {
params := make(url.Values)
params.Set(peerRESTNetPerfSize, strconv.FormatInt(size, 10))
respBody, err := client.call(peerRESTMethodCollectNetPerfInfo, params, nil, -1)
if err != nil {
return
}
defer http.DrainBody(respBody)
err = gob.NewDecoder(respBody).Decode(&info)
return info, err
}
// GetLocks - fetch older locks for a remote node. // GetLocks - fetch older locks for a remote node.
func (client *peerRESTClient) GetLocks() (locks GetLocksResp, err error) { func (client *peerRESTClient) GetLocks() (locks GetLocksResp, err error) {
respBody, err := client.call(peerRESTMethodGetLocks, nil, nil, -1) respBody, err := client.call(peerRESTMethodGetLocks, nil, nil, -1)
@@ -141,8 +173,10 @@ func (client *peerRESTClient) CPULoadInfo() (info ServerCPULoadInfo, err error)
} }
// DrivePerfInfo - fetch Drive performance information for a remote node. // DrivePerfInfo - fetch Drive performance information for a remote node.
func (client *peerRESTClient) DrivePerfInfo() (info ServerDrivesPerfInfo, err error) { func (client *peerRESTClient) DrivePerfInfo(size int64) (info madmin.ServerDrivesPerfInfo, err error) {
respBody, err := client.call(peerRESTMethodDrivePerfInfo, nil, nil, -1) params := make(url.Values)
params.Set(peerRESTDrivePerfSize, strconv.FormatInt(size, 10))
respBody, err := client.call(peerRESTMethodDrivePerfInfo, params, nil, -1)
if err != nil { if err != nil {
return return
} }
@@ -471,10 +505,28 @@ func (client *peerRESTClient) LoadGroup(group string) error {
return nil return nil
} }
// ServerUpdate - sends server update message to remote peers.
func (client *peerRESTClient) ServerUpdate(updateURL, sha256Hex string, latestReleaseTime time.Time) error {
values := make(url.Values)
values.Set(peerRESTUpdateURL, updateURL)
values.Set(peerRESTSha256Hex, sha256Hex)
if !latestReleaseTime.IsZero() {
values.Set(peerRESTLatestRelease, latestReleaseTime.Format(time.RFC3339))
} else {
values.Set(peerRESTLatestRelease, "")
}
respBody, err := client.call(peerRESTMethodServerUpdate, values, nil, -1)
if err != nil {
return err
}
defer http.DrainBody(respBody)
return nil
}
// SignalService - sends signal to peer nodes. // SignalService - sends signal to peer nodes.
func (client *peerRESTClient) SignalService(sig serviceSignal) error { func (client *peerRESTClient) SignalService(sig serviceSignal) error {
values := make(url.Values) values := make(url.Values)
values.Set(peerRESTSignal, string(sig)) values.Set(peerRESTSignal, strconv.Itoa(int(sig)))
respBody, err := client.call(peerRESTMethodSignalService, values, nil, -1) respBody, err := client.call(peerRESTMethodSignalService, values, nil, -1)
if err != nil { if err != nil {
return err return err
@@ -579,6 +631,48 @@ func (client *peerRESTClient) Trace(traceCh chan interface{}, doneCh chan struct
}() }()
} }
// ConsoleLog - sends request to peer nodes to get console logs
func (client *peerRESTClient) ConsoleLog(logCh chan interface{}, doneCh chan struct{}) {
go func() {
for {
// get cancellation context to properly unsubscribe peers
ctx, cancel := context.WithCancel(context.Background())
respBody, err := client.callWithContext(ctx, peerRESTMethodLog, nil, nil, -1)
if err != nil {
// Retry the failed request.
time.Sleep(5 * time.Second)
} else {
dec := gob.NewDecoder(respBody)
go func() {
<-doneCh
cancel()
}()
for {
var log madmin.LogInfo
if err = dec.Decode(&log); err != nil {
break
}
select {
case logCh <- log:
default:
}
}
}
select {
case <-doneCh:
cancel()
http.DrainBody(respBody)
return
default:
// There was error in the REST request, retry.
}
}
}()
}
func getRemoteHosts(endpoints EndpointList) []*xnet.Host { func getRemoteHosts(endpoints EndpointList) []*xnet.Host {
var remoteHosts []*xnet.Host var remoteHosts []*xnet.Host
for _, hostStr := range GetRemotePeers(endpoints) { for _, hostStr := range GetRemotePeers(endpoints) {
+9
View File
@@ -20,11 +20,14 @@ const peerRESTVersion = "v4"
const peerRESTPath = minioReservedBucketPath + "/peer/" + peerRESTVersion const peerRESTPath = minioReservedBucketPath + "/peer/" + peerRESTVersion
const ( const (
peerRESTMethodNetReadPerfInfo = "netreadperfinfo"
peerRESTMethodCollectNetPerfInfo = "collectnetperfinfo"
peerRESTMethodServerInfo = "serverinfo" peerRESTMethodServerInfo = "serverinfo"
peerRESTMethodCPULoadInfo = "cpuloadinfo" peerRESTMethodCPULoadInfo = "cpuloadinfo"
peerRESTMethodMemUsageInfo = "memusageinfo" peerRESTMethodMemUsageInfo = "memusageinfo"
peerRESTMethodDrivePerfInfo = "driveperfinfo" peerRESTMethodDrivePerfInfo = "driveperfinfo"
peerRESTMethodDeleteBucket = "deletebucket" peerRESTMethodDeleteBucket = "deletebucket"
peerRESTMethodServerUpdate = "serverupdate"
peerRESTMethodSignalService = "signalservice" peerRESTMethodSignalService = "signalservice"
peerRESTMethodBackgroundHealStatus = "backgroundhealstatus" peerRESTMethodBackgroundHealStatus = "backgroundhealstatus"
peerRESTMethodBackgroundOpsStatus = "backgroundopsstatus" peerRESTMethodBackgroundOpsStatus = "backgroundopsstatus"
@@ -48,9 +51,12 @@ const (
peerRESTMethodTrace = "trace" peerRESTMethodTrace = "trace"
peerRESTMethodBucketLifecycleSet = "setbucketlifecycle" peerRESTMethodBucketLifecycleSet = "setbucketlifecycle"
peerRESTMethodBucketLifecycleRemove = "removebucketlifecycle" peerRESTMethodBucketLifecycleRemove = "removebucketlifecycle"
peerRESTMethodLog = "log"
) )
const ( const (
peerRESTNetPerfSize = "netperfsize"
peerRESTDrivePerfSize = "driveperfsize"
peerRESTBucket = "bucket" peerRESTBucket = "bucket"
peerRESTUser = "user" peerRESTUser = "user"
peerRESTGroup = "group" peerRESTGroup = "group"
@@ -58,6 +64,9 @@ const (
peerRESTPolicy = "policy" peerRESTPolicy = "policy"
peerRESTUserOrGroup = "user-or-group" peerRESTUserOrGroup = "user-or-group"
peerRESTIsGroup = "is-group" peerRESTIsGroup = "is-group"
peerRESTUpdateURL = "updateURL"
peerRESTSha256Hex = "sha256Hex"
peerRESTLatestRelease = "latestReleaseTime"
peerRESTSignal = "signal" peerRESTSignal = "signal"
peerRESTProfiler = "profiler" peerRESTProfiler = "profiler"
peerRESTDryRun = "dry-run" peerRESTDryRun = "dry-run"
+160 -35
View File
@@ -21,8 +21,9 @@ import (
"encoding/gob" "encoding/gob"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil"
"net/http" "net/http"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -65,44 +66,82 @@ func getServerInfo() (*ServerInfoData, error) {
}, nil }, nil
} }
// uptimes - used to sort uptimes in chronological order. // NetReadPerfInfoHandler - returns network read performance information.
type uptimes []time.Duration func (s *peerRESTServer) NetReadPerfInfoHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
func (ts uptimes) Len() int { s.writeErrorResponse(w, errors.New("Invalid request"))
return len(ts) return
} }
func (ts uptimes) Less(i, j int) bool { params := mux.Vars(r)
return ts[i] < ts[j]
sizeStr, found := params[peerRESTNetPerfSize]
if !found {
s.writeErrorResponse(w, errors.New("size is missing"))
return
} }
func (ts uptimes) Swap(i, j int) { size, err := strconv.ParseInt(sizeStr, 10, 64)
ts[i], ts[j] = ts[j], ts[i] if err != nil || size < 0 {
s.writeErrorResponse(w, errInvalidArgument)
return
} }
// getPeerUptimes - returns the uptime. start := time.Now()
func getPeerUptimes(serverInfo []ServerInfo) time.Duration { n, err := io.CopyN(ioutil.Discard, r.Body, size)
// In a single node Erasure or FS backend setup the uptime of end := time.Now()
// the setup is the uptime of the single minio server
// instance. if err != nil {
if !globalIsDistXL { s.writeErrorResponse(w, err)
return UTCNow().Sub(globalBootTime) return
} }
var times []time.Duration if n != size {
s.writeErrorResponse(w, fmt.Errorf("short read; expected: %v, got: %v", size, n))
for _, info := range serverInfo { return
if info.Error != "" {
continue
}
times = append(times, info.Data.Properties.Uptime)
} }
// Sort uptimes in chronological order. addr := r.Host
sort.Sort(uptimes(times)) if globalIsDistXL {
addr = GetLocalPeer(globalEndpoints)
}
// Return the latest time as the uptime. d := end.Sub(start)
return times[0] info := ServerNetReadPerfInfo{
Addr: addr,
ReadThroughput: uint64(int64(time.Second) * size / int64(d)),
}
ctx := newContext(r, w, "NetReadPerfInfo")
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
w.(http.Flusher).Flush()
}
// CollectNetPerfInfoHandler - returns network performance information collected from other peers.
func (s *peerRESTServer) CollectNetPerfInfoHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
params := mux.Vars(r)
sizeStr, found := params[peerRESTNetPerfSize]
if !found {
s.writeErrorResponse(w, errors.New("size is missing"))
return
}
size, err := strconv.ParseInt(sizeStr, 10, 64)
if err != nil || size < 0 {
s.writeErrorResponse(w, errInvalidArgument)
return
}
info := globalNotificationSys.NetReadPerfInfo(size)
ctx := newContext(r, w, "CollectNetPerfInfo")
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
w.(http.Flusher).Flush()
} }
// GetLocksHandler - returns list of older lock from the server. // GetLocksHandler - returns list of older lock from the server.
@@ -379,7 +418,7 @@ func (s *peerRESTServer) CPULoadInfoHandler(w http.ResponseWriter, r *http.Reque
} }
ctx := newContext(r, w, "CPULoadInfo") ctx := newContext(r, w, "CPULoadInfo")
info := localEndpointsCPULoad(globalEndpoints, r) info := getLocalCPULoad(globalEndpoints, r)
defer w.(http.Flusher).Flush() defer w.(http.Flusher).Flush()
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info)) logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
@@ -392,8 +431,23 @@ func (s *peerRESTServer) DrivePerfInfoHandler(w http.ResponseWriter, r *http.Req
return return
} }
params := mux.Vars(r)
sizeStr, found := params[peerRESTDrivePerfSize]
if !found {
s.writeErrorResponse(w, errors.New("size is missing"))
return
}
size, err := strconv.ParseInt(sizeStr, 10, 64)
if err != nil || size < 0 {
s.writeErrorResponse(w, errInvalidArgument)
return
}
ctx := newContext(r, w, "DrivePerfInfo") ctx := newContext(r, w, "DrivePerfInfo")
info := localEndpointsDrivePerf(globalEndpoints, r)
info := getLocalDrivesPerf(globalEndpoints, size, r)
defer w.(http.Flusher).Flush() defer w.(http.Flusher).Flush()
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info)) logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
@@ -406,7 +460,7 @@ func (s *peerRESTServer) MemUsageInfoHandler(w http.ResponseWriter, r *http.Requ
return return
} }
ctx := newContext(r, w, "MemUsageInfo") ctx := newContext(r, w, "MemUsageInfo")
info := localEndpointsMemUsage(globalEndpoints, r) info := getLocalMemUsage(globalEndpoints, r)
defer w.(http.Flusher).Flush() defer w.(http.Flusher).Flush()
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info)) logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
@@ -730,6 +784,35 @@ func (s *peerRESTServer) ListenBucketNotificationHandler(w http.ResponseWriter,
w.(http.Flusher).Flush() w.(http.Flusher).Flush()
} }
// ServerUpdateHandler - updates the current server.
func (s *peerRESTServer) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
vars := mux.Vars(r)
updateURL := vars[peerRESTUpdateURL]
sha256Hex := vars[peerRESTSha256Hex]
var latestReleaseTime time.Time
var err error
if latestRelease := vars[peerRESTLatestRelease]; latestRelease != "" {
latestReleaseTime, err = time.Parse(latestRelease, time.RFC3339)
if err != nil {
s.writeErrorResponse(w, err)
return
}
}
us, err := updateServer(updateURL, sha256Hex, latestReleaseTime)
if err != nil {
s.writeErrorResponse(w, err)
return
}
if us.CurrentVersion != us.UpdatedVersion {
globalServiceSignalCh <- serviceRestart
}
}
var errUnsupportedSignal = fmt.Errorf("unsupported signal: only restart and stop signals are supported") var errUnsupportedSignal = fmt.Errorf("unsupported signal: only restart and stop signals are supported")
// SignalServiceHandler - signal service handler. // SignalServiceHandler - signal service handler.
@@ -745,10 +828,17 @@ func (s *peerRESTServer) SignalServiceHandler(w http.ResponseWriter, r *http.Req
s.writeErrorResponse(w, errors.New("signal name is missing")) s.writeErrorResponse(w, errors.New("signal name is missing"))
return return
} }
signal := serviceSignal(signalString) si, err := strconv.Atoi(signalString)
if err != nil {
s.writeErrorResponse(w, err)
return
}
signal := serviceSignal(si)
defer w.(http.Flusher).Flush() defer w.(http.Flusher).Flush()
switch signal { switch signal {
case serviceRestart, serviceStop: case serviceRestart:
globalServiceSignalCh <- signal
case serviceStop:
globalServiceSignalCh <- signal globalServiceSignalCh <- signal
default: default:
s.writeErrorResponse(w, errUnsupportedSignal) s.writeErrorResponse(w, errUnsupportedSignal)
@@ -829,6 +919,37 @@ func (s *peerRESTServer) BackgroundOpsStatusHandler(w http.ResponseWriter, r *ht
logger.LogIf(ctx, gob.NewEncoder(w).Encode(state)) logger.LogIf(ctx, gob.NewEncoder(w).Encode(state))
} }
// ConsoleLogHandler sends console logs of this node back to peer rest client
func (s *peerRESTServer) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
w.Header().Set("Connection", "close")
w.WriteHeader(http.StatusOK)
w.(http.Flusher).Flush()
doneCh := make(chan struct{})
defer close(doneCh)
ch := make(chan interface{}, 2000)
globalConsoleSys.Subscribe(ch, doneCh, "", 0, nil)
enc := gob.NewEncoder(w)
for {
select {
case entry := <-ch:
if err := enc.Encode(entry); err != nil {
return
}
w.(http.Flusher).Flush()
case <-r.Context().Done():
return
}
}
}
func (s *peerRESTServer) writeErrorResponse(w http.ResponseWriter, err error) { func (s *peerRESTServer) writeErrorResponse(w http.ResponseWriter, err error) {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
w.Write([]byte(err.Error())) w.Write([]byte(err.Error()))
@@ -847,18 +968,21 @@ func (s *peerRESTServer) IsValid(w http.ResponseWriter, r *http.Request) bool {
func registerPeerRESTHandlers(router *mux.Router) { func registerPeerRESTHandlers(router *mux.Router) {
server := &peerRESTServer{} server := &peerRESTServer{}
subrouter := router.PathPrefix(peerRESTPath).Subrouter() subrouter := router.PathPrefix(peerRESTPath).Subrouter()
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodNetReadPerfInfo).HandlerFunc(httpTraceHdrs(server.NetReadPerfInfoHandler)).Queries(restQueries(peerRESTNetPerfSize)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodCollectNetPerfInfo).HandlerFunc(httpTraceHdrs(server.CollectNetPerfInfoHandler)).Queries(restQueries(peerRESTNetPerfSize)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodGetLocks).HandlerFunc(httpTraceHdrs(server.GetLocksHandler)) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodGetLocks).HandlerFunc(httpTraceHdrs(server.GetLocksHandler))
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodServerInfo).HandlerFunc(httpTraceHdrs(server.ServerInfoHandler)) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodServerInfo).HandlerFunc(httpTraceHdrs(server.ServerInfoHandler))
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodCPULoadInfo).HandlerFunc(httpTraceHdrs(server.CPULoadInfoHandler)) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodCPULoadInfo).HandlerFunc(httpTraceHdrs(server.CPULoadInfoHandler))
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodMemUsageInfo).HandlerFunc(httpTraceHdrs(server.MemUsageInfoHandler)) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodMemUsageInfo).HandlerFunc(httpTraceHdrs(server.MemUsageInfoHandler))
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDrivePerfInfo).HandlerFunc(httpTraceHdrs(server.DrivePerfInfoHandler)) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDrivePerfInfo).HandlerFunc(httpTraceHdrs(server.DrivePerfInfoHandler)).Queries(restQueries(peerRESTDrivePerfSize)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDeleteBucket).HandlerFunc(httpTraceHdrs(server.DeleteBucketHandler)).Queries(restQueries(peerRESTBucket)...) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDeleteBucket).HandlerFunc(httpTraceHdrs(server.DeleteBucketHandler)).Queries(restQueries(peerRESTBucket)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodSignalService).HandlerFunc(httpTraceHdrs(server.SignalServiceHandler)).Queries(restQueries(peerRESTSignal)...) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodSignalService).HandlerFunc(httpTraceHdrs(server.SignalServiceHandler)).Queries(restQueries(peerRESTSignal)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodServerUpdate).HandlerFunc(httpTraceHdrs(server.ServerUpdateHandler)).Queries(restQueries(peerRESTUpdateURL, peerRESTSha256Hex, peerRESTLatestRelease)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodBucketPolicyRemove).HandlerFunc(httpTraceAll(server.RemoveBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodBucketPolicyRemove).HandlerFunc(httpTraceAll(server.RemoveBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodBucketPolicySet).HandlerFunc(httpTraceHdrs(server.SetBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodBucketPolicySet).HandlerFunc(httpTraceHdrs(server.SetBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDeletePolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDeletePolicy).HandlerFunc(httpTraceAll(server.DeletePolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodLoadPolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodLoadPolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodLoadPolicyMapping).HandlerFunc(httpTraceAll(server.LoadPolicyMappingHandler)).Queries(restQueries(peerRESTUserOrGroup)...) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodLoadPolicyMapping).HandlerFunc(httpTraceAll(server.LoadPolicyMappingHandler)).Queries(restQueries(peerRESTUserOrGroup)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDeleteUser).HandlerFunc(httpTraceAll(server.LoadUserHandler)).Queries(restQueries(peerRESTUser)...) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDeleteUser).HandlerFunc(httpTraceAll(server.LoadUserHandler)).Queries(restQueries(peerRESTUser)...)
@@ -881,6 +1005,7 @@ func registerPeerRESTHandlers(router *mux.Router) {
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodTrace).HandlerFunc(server.TraceHandler) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodTrace).HandlerFunc(server.TraceHandler)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodBackgroundHealStatus).HandlerFunc(server.BackgroundHealStatusHandler) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodBackgroundHealStatus).HandlerFunc(server.BackgroundHealStatusHandler)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodLog).HandlerFunc(server.ConsoleLogHandler)
router.NotFoundHandler = http.HandlerFunc(httpTraceAll(notFoundHandler)) router.NotFoundHandler = http.HandlerFunc(httpTraceAll(notFoundHandler))
} }
+8 -3
View File
@@ -163,7 +163,10 @@ func (sys *PolicySys) Init(objAPI ObjectLayer) error {
// the following reasons: // the following reasons:
// - Read quorum is lost just after the initialization // - Read quorum is lost just after the initialization
// of the object layer. // of the object layer.
for range newRetryTimerSimple(doneCh) { retryTimerCh := newRetryTimerSimple(doneCh)
for {
select {
case <-retryTimerCh:
// Load PolicySys once during boot. // Load PolicySys once during boot.
if err := sys.refresh(objAPI); err != nil { if err := sys.refresh(objAPI); err != nil {
if err == errDiskNotFound || if err == errDiskNotFound ||
@@ -174,9 +177,11 @@ func (sys *PolicySys) Init(objAPI ObjectLayer) error {
} }
return err return err
} }
break
}
return nil return nil
case <-globalOSSignalCh:
return fmt.Errorf("Initializing Policy sub-system gracefully stopped")
}
}
} }
// NewPolicySys - creates new policy system. // NewPolicySys - creates new policy system.

Some files were not shown because too many files have changed in this diff Show More