Compare commits

..

97 Commits

Author SHA1 Message Date
Anis Eleuch 22d2dbc4e6 decom: Fix infinite retry when the decom is canceled (#18143)
Also, use rand.Float64() since it is thread-safe; otherwise go race
will complain.
2023-09-30 00:02:29 -07:00
Shireesh Anjal 2b5d9428b1 Use latest madmin-go (v3.0.21) (#18138)
This ensures that drive model is included in the partition data inside
the health diagnostics report.
2023-09-29 11:25:34 -07:00
Harshavardhana d6446cb096 do not return an error in AbortMultipartUpload() (#18135)
returning an error is a bit undefined in AWS S3
as it may return an error or not depending on the
time from AbortMultipartUpload().
2023-09-29 10:28:19 -07:00
Harshavardhana c34bdc33fb make sure to set Versioned field to ensure rename2 is not called (#18141)
without this the rename2() can rename the previous dataDir
causing issues for different versions of the object, only
latest version is preserved due to this bug.

Added healing code to ensure recovery of such content.
2023-09-29 09:08:24 -07:00
ferhat elmas dd8547e51c chore: drop unnecessary linter (#18133) 2023-09-29 03:11:31 -07:00
Anis Eleuch aec023f537 Avoid showing buckets without quorum in each pool (#18125) 2023-09-29 00:58:54 -07:00
Poorna e101eeeda9 fix: tier addition validation (#18136) 2023-09-28 22:33:24 -07:00
Minio Trusted f29522269d Update yaml files to latest version RELEASE.2023-09-27T15-22-50Z 2023-09-28 17:49:57 +00:00
Harshavardhana 3c470a6b8b fix: the inspect script to use scheme per deployment (#18118) 2023-09-27 08:22:50 -07:00
Poorna 6bc7d711b3 delete of a missing versionId return 204 (#18117) 2023-09-26 14:02:56 -07:00
Shubhendu 10d5dd3a67 fix: a regression with audit log sending (#18112)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2023-09-26 12:23:02 -07:00
Harshavardhana d9f1df01eb return an error in CopyAligned upon premature EOF (#18110)
add a unit-test to capture this corner case
2023-09-26 11:20:06 -07:00
Harshavardhana cdeab19673 fix: always check error upon w.Close() in Write() (#18111)
not checking w.Close() can prematurely make us
think that the w.Write() actually succeeded, apparently
Write() may or may not return an error but sometimes
only during a Close() call to the fd we may see the
error from Write() propagate.

Fdatasync(w) on the FD would return an error requiring
Close() error handling is less of a concern, however it may
happen such that fdatasync() did not return an error, where
as Close() would.
2023-09-26 11:04:00 -07:00
Anis Eleuch 22ee678136 tier: Avoid doing versioned operations since not required anymore (#18108)
Currently, setting a new tiering target returns an error when a bucket
is versioned and the tiering credentials does not have authorization to
specify a version-id when reading or removing a specific version;

Since tiering does not require versioning anymore; avoid doing versioned
operations when performing checklist ops while adding a new tiering
configuration.
2023-09-26 00:14:56 -07:00
Poorna 50a8f13e85 site replication: allow setting bandwidth default for bucket (#18062)
This can still be overridden at the bucket level
2023-09-25 15:50:52 -07:00
jiuker 6dec60b6e6 fix: check post policy like AWS S3 (#18074) 2023-09-25 12:35:25 -07:00
Harshavardhana ac3a19138a fix: set scanning details locally to avoid cached values (#18092)
atomic variable results such as scanning must not use
cached values, instead rely on real-time information.
2023-09-25 08:26:29 -07:00
Klaus Post 21e8e071d7 Improve ListObject Compatibility (#18099)
Do not error out when a provided marker is before or after the prefix, but instead just ignore it if before and return an empty list when after.

Fixes #18093
2023-09-25 08:13:08 -07:00
Klaus Post 57f84a8b4c Add abandoned folder scanning to metrics (#18076)
Include object and versions heal scan times when checking non-empty abandoned folders.

Furthermore don't add delay between healing versions, instead do one per object wait.
2023-09-24 22:15:31 -07:00
Minio Trusted 8a672e70a7 Update yaml files to latest version RELEASE.2023-09-23T03-47-50Z 2023-09-25 01:55:31 +00:00
Aditya Manthramurthy 22041bbcc4 fix: Update policy mapping properly in notification (#18088)
This is fixing a regression from an earlier change where STS account
loading was made lazy.
2023-09-22 20:47:50 -07:00
Harshavardhana 5afb459113 upgrade all dependencies (#18085) 2023-09-22 14:45:19 -07:00
Harshavardhana 91ebac0a00 fix: move abandoned parts check after healing not in ILM path (#18087) 2023-09-22 12:07:52 -07:00
mundry 5fcb1cfd31 fix: broken bucket versioning support in community helm chart (#18003) 2023-09-22 11:32:24 -07:00
Harshavardhana 3a90fb108c only look for metadata if batch replication asks for metadata filters (#18082)
This PR changes the StatObject() to be must have for non-minio source
to being a conditional API call.

- Calls StatObject() when needed
- Calls GetObjectTagging() when needed

These calls if we do without these conditionals can cause a lot of
delays, so we avoid them if not needed in more common scenario.
2023-09-22 11:31:57 -07:00
Anis Eleuch 4eeb48f8e0 Return cached online/offline status for audit/http loggers (#18083)
To avoid having delays in prometheus scrape and in 'mc admin info' command.
2023-09-21 16:58:24 -07:00
Harshavardhana 373d48c8a3 allow admin actions to have proper condition map (#18080)
upgrade minio/pkg to v2.0.2

fixes #18078
2023-09-21 13:22:09 -07:00
Harshavardhana 1472875670 fix: failed messages counting in audit_http metrics (#18075)
all retries must not be counted as failed messages,
a failed message is a single counter not for all
retries, this PR fixes this.

Also we do not need to retry 10-times, instead we should
retry at max 3 times with some jitter to deliver the
messages.
2023-09-21 11:24:56 -07:00
Shubhendu 74cfb207c1 Added check for mandatory MINIO_KMS_KES_KEY_NAME env var (#18077)
If MinIO started with KMS enabled, MINIO_KMS_KES_KEY_NAME should
be set for server to start.

Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2023-09-21 10:37:37 -07:00
Minio Trusted 6a096e7dc7 Update yaml files to latest version RELEASE.2023-09-20T22-49-55Z 2023-09-21 00:42:16 +00:00
Harshavardhana 9788d85ea3 remove logging for invalid metadata values (#18068) 2023-09-20 15:49:55 -07:00
Anis Eleuch 69c0e18685 perf net: Add the endpoint name related to the perf net error (#18063)
In a perf test, one node will run speed test with all nodes. If there is
an error with a peer node, the peer node name is not included in the
error hence confusing the user.

This commit will add the peer endpoint string to the netperf error.
2023-09-19 22:41:06 -07:00
Aditya Manthramurthy 3cac927348 Load STS policy mappings periodically (#18061)
To ensure that policy mappings are current for service accounts
belonging to (non-derived) STS accounts (like an LDAP user's service
account) we periodically reload such mappings.

This is primarily to handle a case where a policy mapping update
notification is missed by a minio node. Such a node would continue to
have the stale mapping in memory because STS creds/mappings were never
periodically scanned from storage.
2023-09-19 17:57:42 -07:00
Harshavardhana 9081346c40 fix: more regressions listing policy mappings (#18060)
also relax ListServiceAccounts() returning error if
no service accounts exist.
2023-09-19 15:23:18 -07:00
Harshavardhana fcfadb0e51 fix: regression in loading LDAP users policy mappings (#18055)
LDAP users are stored as STS users, we need to load
their policy mappings appropriately.

Fixes a regression caused by #17994
2023-09-19 10:31:56 -07:00
Harshavardhana 2add57cfed apply healing per object at 1024 cycles (#18050)
- we already have MRF for most recent failures
- we trigger healing during HEAD/GET operation

These are enough, also change the default max wait
from 5sec to 1sec for default scanner speed.
2023-09-19 09:24:22 -07:00
Anis Eleuch c5279ec630 fix: building reorder-disks under darwin (#18053)
Also build debugging tools only in tests or with a specific target
2023-09-19 03:19:26 -07:00
Poorna b73699fad8 replication: pass user tags while queueing (#18052)
Continues from #18032 - otherwise replication will fail on tag based rules.
2023-09-19 03:18:28 -07:00
Harshavardhana b8ebe54e53 Revert "skip tiered objects to GLACIER in batch replication (#18044)"
This reverts commit fd421ddd6f.

MinIO already provides `filter` based on metadata that would work
in this scenario already.
2023-09-19 00:05:40 -07:00
Harshavardhana c3d70e0795 cache usage, prefix-usage, and buckets for AccountInfo up to 10 secs (#18051)
AccountInfo is quite frequently called by the Console UI 
login attempts, when many users are logging in it is important
that we provide them with better responsiveness.

- ListBuckets information is cached every second
- Bucket usage info is cached for up to 10 seconds
- Prefix usage (optional) info is cached for up to 10 secs

Failure to update after cache expiration, would still
allow login which would end up providing information
previously cached.

This allows for seamless responsiveness for the Console UI
logins, and overall responsiveness on a heavily loaded
system.
2023-09-18 22:13:03 -07:00
Harshavardhana 8c4561b8da add all missing go.mod for debugging tools (#18049) 2023-09-18 13:47:03 -07:00
Harshavardhana fd421ddd6f skip tiered objects to GLACIER in batch replication (#18044)
tiered objects to GLACIER are not readable until
they are restored, we skip these as unreadable
2023-09-18 10:25:31 -07:00
jiuker 9947c01c8e feat: SSE-KMS use uuid instead of read all data to md5. (#17958) 2023-09-18 10:00:54 -07:00
Eng Zer Jun a00db4267c data-usage-cache: remove redundant nil check (#17970)
From the Go specification:

  "3. If the map is nil, the number of iterations is 0." [1]

Therefore, an additional nil check for before the loop is unnecessary.

[1]: https://go.dev/ref/spec#For_range

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2023-09-16 19:09:29 -07:00
Harshavardhana 36385010f5 use optimized pathJoin instead of path.Join (#18042)
this avoids allocations in scanner routine, they are tiny but 
they allocate a lot over many cycles of the scanner.
2023-09-16 19:08:59 -07:00
Harshavardhana fa6d082bfd reduce all major allocations in replication path (#18032)
- remove targetClient for passing around via replicationObjectInfo{}
- remove cloing to object info unnecessarily
- remove objectInfo from replicationObjectInfo{} (only require necessary fields)
2023-09-16 02:28:06 -07:00
Minio Trusted 9fab91852a Update yaml files to latest version RELEASE.2023-09-16T01-01-47Z 2023-09-16 07:38:18 +00:00
Poorna b733e6e83c site replication turn off retry login for admin API calls (#18039)
additionally also mark site offline if n/w is down
2023-09-15 18:01:47 -07:00
Harshavardhana ce05bb69dc update console v0.39.0 (#18038)
Signed-off-by: Harshavardhana <harsha@minio.io>
2023-09-15 14:01:52 -07:00
Anis Eleuch 37aa5934a1 scanner: Fix loading data usage cache structure (#18037)
Return an empty data usage cache structure when the data usage cache
file does not exist, otherwise, the scanner won't work.
2023-09-15 13:11:08 -07:00
Harshavardhana 1647fc7edc fix: optimize listMultipartUploads to serve via local disks (#18034)
and remove unused getLoadBalancedDisks()
2023-09-15 08:34:03 -07:00
Harshavardhana 7b92687397 remove generating presignedURLs with range header for lambda (#18033) 2023-09-14 21:58:17 -07:00
Anis Eleuch 419e5baf16 fix: webhook notify endpoint with standard ports (#18016) 2023-09-14 20:10:44 -07:00
Alex dc48cd841a Added MINIO_PROMETHEUS_AUTH_TOKEN env support (#18028)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-14 17:28:21 -07:00
Anis Eleuch b0e1776d6d Do not use a chain for S3 tiering to return better error messages (#18030)
When using a chain provider all providers do not return a valid
access and secret key, an anonymous request is sent, which makes it hard
for users to figure out what is going on

In the case of S3 tiering, when AWS IAM temporary account generation returns
an error, an anonymous login will be used because of the chain provider.
Avoid this and use the AWS IAM provider directly to get a good error
message.
2023-09-14 15:28:20 -07:00
Aditya Manthramurthy 7a7068ee47 Move IAM periodic ops to a single go routine (#18026)
This helps reduce disk operations as these periodic routines would not
run concurrently any more.

Also add expired STS purging periodic operation: Since we do not scan
the on-disk STS credentials (and instead only load them on-demand) a
separate routine is needed to purge expired credentials from storage.
Currently this runs about a quarter as often as IAM refresh.

Also fix a bug where with etcd, STS accounts could get loaded into the
iamUsersMap instead of the iamSTSAccountsMap.
2023-09-14 15:25:17 -07:00
Aditya Manthramurthy cbc0ef459b Fix policy package import name (#18031)
We do not need to rename the import of minio/pkg/v2/policy as iampolicy
any more.
2023-09-14 14:50:16 -07:00
Harshavardhana a2aabfabd9 add backups for usage-caches to rely on upon error (#18029)
This allows scanner to avoid lengthy scans, skip
things appropriately and also not lose metrics in
any manner.

reduce longer deadlines for usage-cache loads/saves
to match the disk timeout which is 2minutes now per
IOP.
2023-09-14 11:53:52 -07:00
Harshavardhana 822cbd4b43 add couple of missing things from #18027 2023-09-13 23:26:48 -07:00
Ravind Kumar 3c19a9308d DOCS-987: Reorganizing list.md for better RST compatibility (#18027) 2023-09-13 23:23:37 -07:00
Harshavardhana 32890342ce introduce MINIO_BROWSER_REDIRECT env to enable/disable auto-redirect (#18025) 2023-09-13 18:43:57 -07:00
Aditya Manthramurthy ed2c2a285f Load STS accounts into IAM cache lazily (#17994)
In situations with large number of STS credentials on disk, IAM load
time is high. To mitigate this, STS accounts will now be loaded into
memory only on demand - i.e. when the credential is used.

In each IAM cache (re)load we skip loading STS credentials and STS
policy mappings into memory. Since STS accounts only expire and cannot
be deleted, there is no risk of invalid credentials being reused,
because credential validity is checked when it is used.
2023-09-13 12:43:46 -07:00
Poorna 18e23bafd9 replication resync: report only the on-disk status (#18017)
Avoid reporting in-memory status since results can vary if different
nodes are queried, resync always runs at a single node.
2023-09-13 10:58:38 -07:00
Harshavardhana 8b8be2695f optimize mkdir calls to avoid base-dir Mkdir attempts (#18021)
Currently we have IOPs of these patterns

```
[OS] os.Mkdir play.min.io:9000 /disk1 2.718µs
[OS] os.Mkdir play.min.io:9000 /disk1/data 2.406µs
[OS] os.Mkdir play.min.io:9000 /disk1/data/.minio.sys 4.068µs
[OS] os.Mkdir play.min.io:9000 /disk1/data/.minio.sys/tmp 2.843µs
[OS] os.Mkdir play.min.io:9000 /disk1/data/.minio.sys/tmp/d89c8ceb-f8d1-4cc6-b483-280f87c4719f 20.152µs
```

It can be seen that we can save quite Nx levels such as
if your drive is mounted at `/disk1/minio` you can simply
skip sending an `Mkdir /disk1/` and `Mkdir /disk1/minio`.

Since they are expected to exist already, this PR adds a way
for us to ignore all paths upto the mount or a directory which
ever has been provided to MinIO setup.
2023-09-13 08:14:36 -07:00
Poorna 96fbf18201 replication: queue existing objects to same workers as incoming (#18020)
Previously existing objects were queued to single worker and MRF re-queues
are also handled by same worker - this does not fully use the available
bandwidth in case there is no incoming workload.
2023-09-12 21:59:15 -07:00
Harshavardhana c8a57a8fa2 fix: send content-md5 for AWS S3 proactively (#18018)
fixes #17977
2023-09-12 19:11:13 -07:00
Harshavardhana b1c2dacab3 fix: allow dynamic ports for API only in non-distributed setups (#18019)
fixes #17998
2023-09-12 19:10:49 -07:00
Harshavardhana 65939913b4 update all dependencies (#18012) 2023-09-12 13:16:46 -07:00
Harshavardhana 08b3a466e8 fix: allow concurrent SFTP connections (#18013)
current implementation did not fully implement
the concurrent SFTP connection implementation,
this PR properly handles this.

fixes #17914
2023-09-12 12:41:52 -07:00
Harshavardhana 5aa7c38035 update pkg to v2.0.1 to extend admin actions (#18008) 2023-09-12 01:11:52 -07:00
Harshavardhana 1df5e31706 optimize MRF replication queue to avoid memory leaks (#18007) 2023-09-11 20:59:11 -07:00
Harshavardhana 9f7044aed0 fix: ignore transient errors in read path (#18006)
Errors such as

```
returned an error (context deadline exceeded) (*fmt.wrapError)
```

```
(msgp: too few bytes left to read object) (*fmt.wrapError)
```
2023-09-11 15:29:59 -07:00
Anis Eleuch 41de53996b heal: calculate the number of workers based on NRRequests (#17945) 2023-09-11 14:48:54 -07:00
Harshavardhana 9878031cfd fix: change DISK_ to DRIVE_ for some drive related envs (#18005) 2023-09-11 12:19:22 -07:00
Harshavardhana e3fbcaeb72 allow scanner key cycle to be empty (#18001)
configs from 2020 server throws an
error due to deprecation of the keys
however an attempt is made to parse
them, we should have chosen existing
defaults - this PR fixes that.
2023-09-09 08:53:32 -07:00
Harshavardhana ca6dd8be5e use go1.21.1 for vulncheck 2023-09-07 16:15:31 -07:00
Minio Trusted fba0924b1d Update yaml files to latest version RELEASE.2023-09-07T02-05-02Z 2023-09-07 23:10:40 +00:00
Poorna 703ed46d79 fix: replication of tags while removing (#17989)
A tag removal was not being replicated prior to this change
2023-09-06 19:05:02 -07:00
Harshavardhana f7ca6c63c2 fix: bucket quota clear and honor existing quota config (#17988) 2023-09-06 19:03:58 -07:00
Harshavardhana ad69b9907f fix: report bucket metrics for only existing buckets (#17987) 2023-09-06 12:50:46 -07:00
Anis Eleuch b9269151a4 fix: drive rotational calculation status for partitions (#17986)
Fix drive rotational calculation status

If a MinIO drive path is mounted to a partition and not a real disk,
getting the rotational status would fail because Linux does not expose
that status to partition; In other words,
/sys/block/drive-partition-name/queue/rotational does not exist;

To fix the issue, the code will search for the rotational status of the
disk that hosts the partition, and this can be calculated from the
real path of /sys/class/block/<drive-partition-name>
2023-09-06 12:37:57 -07:00
Shubhendu bfddbb8b40 Embed file in ZIP with custom permissions (#17954)
This change enables embedding files in ZIP with custom permissions.
Also uses default creds for starting MinIO based on inspect data.

Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2023-09-06 09:24:01 -07:00
Poorna 13a2dc8485 replication resync: avoid blocking on results channel. (#17981)
continues fix in #17775
2023-09-05 20:22:39 -07:00
Harshavardhana 1e51424e8a use syscall.Rename() directly instead of os.Rename() (#17982) 2023-09-05 20:22:23 -07:00
Harshavardhana 5b114b43f7 refactor bandwidth throttling for replication target (#17980)
This refactor is to allow using the bandwidth throttling
for other purposes.
2023-09-05 20:21:59 -07:00
Poorna 812f5a02d7 metrics: fix panic in replication stats reporting (#17979) 2023-09-05 10:26:18 -07:00
Minio Trusted 19f70dbfbf Update yaml files to latest version RELEASE.2023-09-04T19-57-37Z 2023-09-04 21:20:49 +00:00
Aditya Manthramurthy 1c99fb106c Update to minio/pkg/v2 (#17967) 2023-09-04 12:57:37 -07:00
Krishnan Parthasarathi 71c32e9b48 Return successorModTime in quorum when available (#17925) 2023-09-04 08:24:17 -07:00
Harshavardhana 380a59520b add missing testdata for benchmarking 2023-09-02 14:40:38 -07:00
Harshavardhana 3995355150 avoid repeated large allocations for large parts (#17968)
objects with 10,000 parts and many of them can
cause a large memory spike which can potentially
lead to OOM due to lack of GC.

with previous PR reducing the memory usage significantly
in #17963, this PR reduces this further by 80% under
repeated calls.

Scanner sub-system has no use for the slice of Parts(),
it is better left empty.

```
benchmark                            old ns/op     new ns/op     delta
BenchmarkToFileInfo/ToFileInfo-8     295658        188143        -36.36%

benchmark                            old allocs     new allocs     delta
BenchmarkToFileInfo/ToFileInfo-8     61             60             -1.64%

benchmark                            old bytes     new bytes     delta
BenchmarkToFileInfo/ToFileInfo-8     1097210       227255        -79.29%
```
2023-09-02 07:49:24 -07:00
Harshavardhana 8208bcb896 remove all unnecessary logging, logOnce when absolutely needed (#17965) 2023-09-01 16:19:18 -07:00
Poorna d665e855de replication: remove check for empty version id (#17964) 2023-09-01 13:46:10 -07:00
Harshavardhana 18b3655c99 with xlv2 format we never had to fill in checksumInfo() (#17963)
- this PR avoids sending a large ChecksumInfo slice
  when its not needed

- also for a file with XLV2 format there is no reason
  to allocate Checksum slice while reading
2023-09-01 13:45:58 -07:00
Anis Eleuch 6a8d8f34a5 kafka: Do not require key when sending a message (#17962)
Keys are helpful to ensure the strict ordering of messages, however currently the
code uses a random request id for every log, hence using the request-id
as a Kafka key is not serve any purpose;

This commit removes the usage of the key, to also fix the audit issue from
internal subsystem that does not have a request ID.
2023-09-01 08:37:22 -07:00
Harshavardhana b1c1f02132 use buffers for pathJoin, to re-use buffers. (#17960)
```
benchmark                        old ns/op     new ns/op     delta
BenchmarkPathJoin/PathJoin-8     79.6          55.3          -30.53%

benchmark                        old allocs     new allocs     delta
BenchmarkPathJoin/PathJoin-8     2              1              -50.00%

benchmark                        old bytes     new bytes     delta
BenchmarkPathJoin/PathJoin-8     48            24            -50.00%
```
2023-08-31 17:58:48 -07:00
Minio Trusted ea93643e6a Update yaml files to latest version RELEASE.2023-08-31T15-31-16Z 2023-09-01 00:15:57 +00:00
254 changed files with 4607 additions and 2675 deletions
+9 -2
View File
@@ -1,10 +1,17 @@
.git .git
.github .github
docs
default.etcd default.etcd
*.gz *.gz
*.tar.gz *.tar.gz
*.bzip2 *.bzip2
*.zip *.zip
browser/node_modules browser/node_modules
node_modules node_modules
docs/debugging/s3-verify/s3-verify
docs/debugging/xl-meta/xl-meta
docs/debugging/s3-check-md5/s3-check-md5
docs/debugging/hash-set/hash-set
docs/debugging/healing-bin/healing-bin
docs/debugging/inspect/inspect
docs/debugging/pprofgoparser/pprofgoparser
docs/debugging/reorder-disks/reorder-disks
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: 1.21.0 go-version: 1.21.1
check-latest: true check-latest: true
- name: Get official govulncheck - name: Get official govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest run: go install golang.org/x/vuln/cmd/govulncheck@latest
+3 -2
View File
@@ -32,6 +32,8 @@ minio.RELEASE*
mc mc
nancy nancy
inspects/* inspects/*
.bin/
*.gz
docs/debugging/s3-verify/s3-verify docs/debugging/s3-verify/s3-verify
docs/debugging/xl-meta/xl-meta docs/debugging/xl-meta/xl-meta
docs/debugging/s3-check-md5/s3-check-md5 docs/debugging/s3-check-md5/s3-check-md5
@@ -39,5 +41,4 @@ docs/debugging/hash-set/hash-set
docs/debugging/healing-bin/healing-bin docs/debugging/healing-bin/healing-bin
docs/debugging/inspect/inspect docs/debugging/inspect/inspect
docs/debugging/pprofgoparser/pprofgoparser docs/debugging/pprofgoparser/pprofgoparser
.bin/ docs/debugging/reorder-disks/reorder-disks
*.gz
-1
View File
@@ -13,7 +13,6 @@ linters:
enable: enable:
- durationcheck - durationcheck
- gocritic - gocritic
- gofmt
- gofumpt - gofumpt
- goimports - goimports
- gomodguard - gomodguard
+671 -28
View File
@@ -2392,6 +2392,7 @@ https://github.com/IBM/sarama
# MIT License # MIT License
Copyright (c) 2013 Shopify Copyright (c) 2013 Shopify
Copyright (c) 2023 IBM Corporation Copyright (c) 2023 IBM Corporation
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
@@ -2922,33 +2923,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================================ ================================================================
github.com/benbjohnson/clock
https://github.com/benbjohnson/clock
----------------------------------------------------------------
The MIT License (MIT)
Copyright (c) 2014 Ben Johnson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================================
github.com/beorn7/perks github.com/beorn7/perks
https://github.com/beorn7/perks https://github.com/beorn7/perks
---------------------------------------------------------------- ----------------------------------------------------------------
@@ -3089,7 +3063,7 @@ https://github.com/charmbracelet/lipgloss
---------------------------------------------------------------- ----------------------------------------------------------------
MIT License MIT License
Copyright (c) 2021 Charmbracelet, Inc Copyright (c) 2021-2023 Charmbracelet, Inc
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -11869,6 +11843,8 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
github.com/hashicorp/golang-lru github.com/hashicorp/golang-lru
https://github.com/hashicorp/golang-lru https://github.com/hashicorp/golang-lru
---------------------------------------------------------------- ----------------------------------------------------------------
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0 Mozilla Public License, version 2.0
1. Definitions 1. Definitions
@@ -20017,6 +19993,673 @@ For more information on this, and how to apply and follow the GNU AGPL, see
================================================================ ================================================================
github.com/minio/pkg/v2
https://github.com/minio/pkg/v2
----------------------------------------------------------------
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
================================================================
github.com/minio/selfupdate github.com/minio/selfupdate
https://github.com/minio/selfupdate https://github.com/minio/selfupdate
---------------------------------------------------------------- ----------------------------------------------------------------
+17 -8
View File
@@ -45,22 +45,22 @@ lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix @$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
check: test check: test
test: verifiers build ## builds minio, runs linters, tests test: verifiers build build-debugging ## builds minio, runs linters, tests
@echo "Running unit tests" @echo "Running unit tests"
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue ./... @MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue ./...
test-root-disable: install test-root-disable: install-race
@echo "Running minio root lockdown tests" @echo "Running minio root lockdown tests"
@env bash $(PWD)/buildscripts/disable-root.sh @env bash $(PWD)/buildscripts/disable-root.sh
test-decom: install test-decom: install-race
@echo "Running minio decom tests" @echo "Running minio decom tests"
@env bash $(PWD)/docs/distributed/decom.sh @env bash $(PWD)/docs/distributed/decom.sh
@env bash $(PWD)/docs/distributed/decom-encrypted.sh @env bash $(PWD)/docs/distributed/decom-encrypted.sh
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh @env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh @env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
test-upgrade: build test-upgrade: install-race
@echo "Running minio upgrade tests" @echo "Running minio upgrade tests"
@(env bash $(PWD)/buildscripts/minio-upgrade.sh) @(env bash $(PWD)/buildscripts/minio-upgrade.sh)
@@ -86,18 +86,18 @@ test-replication-3site:
test-delete-replication: test-delete-replication:
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh) @(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
test-replication: install test-replication-2site test-replication-3site test-delete-replication test-sio-error ## verify multi site replication test-replication: install-race test-replication-2site test-replication-3site test-delete-replication test-sio-error ## verify multi site replication
@echo "Running tests for replicating three sites" @echo "Running tests for replicating three sites"
test-site-replication-ldap: install ## verify automatic site replication test-site-replication-ldap: install-race ## verify automatic site replication
@echo "Running tests for automatic site replication of IAM (with LDAP)" @echo "Running tests for automatic site replication of IAM (with LDAP)"
@(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh) @(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh)
test-site-replication-oidc: install ## verify automatic site replication test-site-replication-oidc: install-race ## verify automatic site replication
@echo "Running tests for automatic site replication of IAM (with OIDC)" @echo "Running tests for automatic site replication of IAM (with OIDC)"
@(env bash $(PWD)/docs/site-replication/run-multi-site-oidc.sh) @(env bash $(PWD)/docs/site-replication/run-multi-site-oidc.sh)
test-site-replication-minio: install ## verify automatic site replication test-site-replication-minio: install-race ## verify automatic site replication
@echo "Running tests for automatic site replication of IAM (with MinIO IDP)" @echo "Running tests for automatic site replication of IAM (with MinIO IDP)"
@(env bash $(PWD)/docs/site-replication/run-multi-site-minio-idp.sh) @(env bash $(PWD)/docs/site-replication/run-multi-site-minio-idp.sh)
@@ -128,6 +128,9 @@ verify-healing-inconsistent-versions: ## verify resolving inconsistent versions
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null @GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@(env bash $(PWD)/buildscripts/resolve-right-versions.sh) @(env bash $(PWD)/buildscripts/resolve-right-versions.sh)
build-debugging:
@(env bash $(PWD)/docs/debugging/build.sh)
build: checks ## builds minio to $(PWD) build: checks ## builds minio to $(PWD)
@echo "Building minio binary to './minio'" @echo "Building minio binary to './minio'"
@CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null @CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@@ -159,6 +162,12 @@ docker: build ## builds minio docker container
@echo "Building minio docker image '$(TAG)'" @echo "Building minio docker image '$(TAG)'"
@docker build -q --no-cache -t $(TAG) . -f Dockerfile @docker build -q --no-cache -t $(TAG) . -f Dockerfile
install-race: checks ## builds minio to $(PWD)
@echo "Building minio binary to './minio'"
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
install: build ## builds minio and installs it to $GOPATH/bin. install: build ## builds minio and installs it to $GOPATH/bin.
@echo "Installing minio binary to '$(GOPATH)/bin/minio'" @echo "Installing minio binary to '$(GOPATH)/bin/minio'"
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio @mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
+2
View File
@@ -56,6 +56,8 @@ done
set +e set +e
sleep 10
./mc ls minioadm/ ./mc ls minioadm/
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "listing failed, 'minioadmin' should be enabled" echo "listing failed, 'minioadmin' should be enabled"
+1 -1
View File
@@ -25,7 +25,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
// Data types used for returning dummy access control // Data types used for returning dummy access control
+12 -13
View File
@@ -41,8 +41,7 @@ import (
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
iampolicy "github.com/minio/pkg/iam/policy"
) )
const ( const (
@@ -58,7 +57,7 @@ const (
func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketQuotaAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SetBucketQuotaAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -95,7 +94,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
Quota: data, Quota: data,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
} }
if quotaConfig.Quota == 0 { if quotaConfig.Size == 0 || quotaConfig.Quota == 0 {
bucketMeta.Quota = nil bucketMeta.Quota = nil
} }
@@ -110,7 +109,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketQuotaAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetBucketQuotaAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -148,7 +147,7 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
update := r.Form.Get("update") == "true" update := r.Form.Get("update") == "true"
// Get current object layer instance. // Get current object layer instance.
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SetBucketTargetAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -291,7 +290,7 @@ func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *htt
arnType := vars["type"] arnType := vars["type"]
// Get current object layer instance. // Get current object layer instance.
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketTargetAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetBucketTargetAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -325,7 +324,7 @@ func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *ht
arn := vars["arn"] arn := vars["arn"]
// Get current object layer instance. // Get current object layer instance.
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SetBucketTargetAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -365,7 +364,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
bucket := pathClean(r.Form.Get("bucket")) bucket := pathClean(r.Form.Get("bucket"))
// Get current object layer instance. // Get current object layer instance.
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportBucketMetadataAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportBucketMetadataAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -647,7 +646,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
ctx := r.Context() ctx := r.Context()
// Get current object layer instance. // Get current object layer instance.
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ImportBucketMetadataAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ImportBucketMetadataAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -853,7 +852,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
continue continue
} }
bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket) bucketPolicy, err := policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyBytes), bucket)
if err != nil { if err != nil {
rpt.SetStatus(bucket, fileName, err) rpt.SetStatus(bucket, fileName, err)
continue continue
@@ -1021,7 +1020,7 @@ func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ReplicationDiff) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ReplicationDiff)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1083,7 +1082,7 @@ func (a adminAPIHandlers) ReplicationMRFHandler(w http.ResponseWriter, r *http.R
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ReplicationDiff) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ReplicationDiff)
if objectAPI == nil { if objectAPI == nil {
return return
} }
+3 -3
View File
@@ -27,14 +27,14 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/policy"
) )
// validateAdminReq will validate request against and return whether it is allowed. // validateAdminReq will validate request against and return whether it is allowed.
// If any of the supplied actions are allowed it will be successful. // If any of the supplied actions are allowed it will be successful.
// If nil ObjectLayer is returned, the operation is not permitted. // If nil ObjectLayer is returned, the operation is not permitted.
// When nil ObjectLayer has been returned an error has always been sent to w. // When nil ObjectLayer has been returned an error has always been sent to w.
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, actions ...iampolicy.AdminAction) (ObjectLayer, auth.Credentials) { func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, actions ...policy.AdminAction) (ObjectLayer, auth.Credentials) {
// Get current object layer instance. // Get current object layer instance.
objectAPI := newObjectLayerFn() objectAPI := newObjectLayerFn()
if objectAPI == nil || globalNotificationSys == nil { if objectAPI == nil || globalNotificationSys == nil {
@@ -78,7 +78,7 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
var apiErr APIError var apiErr APIError
switch e := err.(type) { switch e := err.(type) {
case iampolicy.Error: case policy.Error:
apiErr = APIError{ apiErr = APIError{
Code: "XMinioMalformedIAMPolicy", Code: "XMinioMalformedIAMPolicy",
Description: e.Error(), Description: e.Error(),
+10 -10
View File
@@ -38,14 +38,14 @@ import (
"github.com/minio/minio/internal/config/subnet" "github.com/minio/minio/internal/config/subnet"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/policy"
) )
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv // DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -149,7 +149,7 @@ type setConfigResult struct {
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -242,7 +242,7 @@ func setConfigKV(ctx context.Context, objectAPI ObjectLayer, kvBytes []byte) (re
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -288,7 +288,7 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -321,7 +321,7 @@ func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *
func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -367,7 +367,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -405,7 +405,7 @@ func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *h
func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -430,7 +430,7 @@ func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Req
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -482,7 +482,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
+6 -6
View File
@@ -33,12 +33,12 @@ import (
"github.com/minio/minio/internal/config/identity/openid" "github.com/minio/minio/internal/config/identity/openid"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/ldap"
"github.com/minio/pkg/ldap" "github.com/minio/pkg/v2/policy"
) )
func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) { func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) {
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -220,7 +220,7 @@ func (a adminAPIHandlers) UpdateIdentityProviderCfg(w http.ResponseWriter, r *ht
func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -273,7 +273,7 @@ func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -332,7 +332,7 @@ func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.
func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
+3 -3
View File
@@ -25,7 +25,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/policy"
) )
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies. // ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
@@ -50,7 +50,7 @@ func (a adminAPIHandlers) ListLDAPPolicyMappingEntities(w http.ResponseWriter, r
// Check authorization. // Check authorization.
objectAPI, cred := validateAdminReq(ctx, w, r, objectAPI, cred := validateAdminReq(ctx, w, r,
iampolicy.ListGroupsAdminAction, iampolicy.ListUsersAdminAction, iampolicy.ListUserPoliciesAdminAction) policy.ListGroupsAdminAction, policy.ListUsersAdminAction, policy.ListUserPoliciesAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -96,7 +96,7 @@ func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.
// Check authorization. // Check authorization.
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.UpdatePolicyAssociationAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.UpdatePolicyAssociationAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
+8 -8
View File
@@ -26,7 +26,7 @@ import (
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/policy"
) )
var ( var (
@@ -37,7 +37,7 @@ var (
func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DecommissionAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.DecommissionAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -113,7 +113,7 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DecommissionAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.DecommissionAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -159,7 +159,7 @@ func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction, iampolicy.DecommissionAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction, policy.DecommissionAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -200,7 +200,7 @@ func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction, iampolicy.DecommissionAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction, policy.DecommissionAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -233,7 +233,7 @@ func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -304,7 +304,7 @@ func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request)
func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -344,7 +344,7 @@ func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request
func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
+17 -18
View File
@@ -33,15 +33,14 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
iampolicy "github.com/minio/pkg/iam/policy"
) )
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add // SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -75,7 +74,7 @@ func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -97,7 +96,7 @@ func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -144,7 +143,7 @@ func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request
func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -163,7 +162,7 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
if item.Policy == nil { if item.Policy == nil {
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt) err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt)
} else { } else {
policy, perr := iampolicy.ParseConfig(bytes.NewReader(item.Policy)) policy, perr := policy.ParseConfig(bytes.NewReader(item.Policy))
if perr != nil { if perr != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, perr), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, perr), r.URL)
return return
@@ -196,7 +195,7 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -220,7 +219,7 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
if item.Policy == nil { if item.Policy == nil {
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil, item.UpdatedAt) err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil, item.UpdatedAt)
} else { } else {
bktPolicy, berr := policy.ParseConfig(bytes.NewReader(item.Policy), item.Bucket) bktPolicy, berr := policy.ParseBucketPolicyConfig(bytes.NewReader(item.Policy), item.Bucket)
if berr != nil { if berr != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, berr), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, berr), r.URL)
return return
@@ -265,7 +264,7 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationInfoAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -285,7 +284,7 @@ func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Req
func (a adminAPIHandlers) SRPeerGetIDPSettings(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SRPeerGetIDPSettings(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -322,7 +321,7 @@ func parseJSONBody(ctx context.Context, body io.Reader, v interface{}, encryptio
func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationInfoAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -351,7 +350,7 @@ func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.R
func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationInfoAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -373,7 +372,7 @@ func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http
func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -404,7 +403,7 @@ func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Req
func (a adminAPIHandlers) SRPeerEdit(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SRPeerEdit(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -439,7 +438,7 @@ func getSRStatusOptions(r *http.Request) (opts madmin.SRStatusOptions) {
func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationRemoveAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationRemoveAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -470,7 +469,7 @@ func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.R
func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationRemoveAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationRemoveAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -492,7 +491,7 @@ func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) SiteReplicationResyncOp(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SiteReplicationResyncOp(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationResyncAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationResyncAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
+1 -1
View File
@@ -32,7 +32,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
minio "github.com/minio/minio-go/v7" minio "github.com/minio/minio-go/v7"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
) )
func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) { func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
+96 -77
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2015-2021 MinIO, Inc. // Copyright (c) 2015-2023 MinIO, Inc.
// //
// This file is part of MinIO Object Storage stack // This file is part of MinIO Object Storage stack
// //
@@ -19,6 +19,7 @@ package cmd
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -34,14 +35,14 @@ import (
"github.com/minio/minio/internal/config/dns" "github.com/minio/minio/internal/config/dns"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/policy"
) )
// RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key> // RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key>
func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.DeleteUserAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.DeleteUserAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -85,7 +86,7 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) ListBucketUsers(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ListBucketUsers(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ListUsersAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.ListUsersAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -119,7 +120,7 @@ func (a adminAPIHandlers) ListBucketUsers(w http.ResponseWriter, r *http.Request
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ListUsersAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, policy.ListUsersAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -186,10 +187,10 @@ func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
checkDenyOnly = true checkDenyOnly = true
} }
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.GetUserAdminAction, Action: policy.GetUserAdminAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -218,7 +219,7 @@ func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AddUserToGroupAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.AddUserToGroupAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -288,7 +289,7 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetGroupAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetGroupAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -315,7 +316,7 @@ func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListGroupsAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListGroupsAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -339,7 +340,7 @@ func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.EnableGroupAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.EnableGroupAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -382,7 +383,7 @@ func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request)
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, creds := validateAdminReq(ctx, w, r, iampolicy.EnableUserAdminAction) objectAPI, creds := validateAdminReq(ctx, w, r, policy.EnableUserAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -470,10 +471,10 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
checkDenyOnly = true checkDenyOnly = true
} }
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.CreateUserAdminAction, Action: policy.CreateUserAdminAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -544,10 +545,10 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
return return
} }
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.ListTemporaryAccountsAdminAction, Action: policy.ListTemporaryAccountsAdminAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -556,16 +557,16 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
return return
} }
stsAccount, policy, err := globalIAMSys.GetTemporaryAccount(ctx, accessKey) stsAccount, sessionPolicy, err := globalIAMSys.GetTemporaryAccount(ctx, accessKey)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
var stsAccountPolicy iampolicy.Policy var stsAccountPolicy policy.Policy
if policy != nil { if sessionPolicy != nil {
stsAccountPolicy = *policy stsAccountPolicy = *sessionPolicy
} else { } else {
policiesNames, err := globalIAMSys.PolicyDBGet(stsAccount.ParentUser, false) policiesNames, err := globalIAMSys.PolicyDBGet(stsAccount.ParentUser, false)
if err != nil { if err != nil {
@@ -584,7 +585,7 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
infoResp := madmin.TemporaryAccountInfoResp{ infoResp := madmin.TemporaryAccountInfoResp{
ParentUser: stsAccount.ParentUser, ParentUser: stsAccount.ParentUser,
AccountStatus: stsAccount.Status, AccountStatus: stsAccount.Status,
ImpliedPolicy: policy == nil, ImpliedPolicy: sessionPolicy == nil,
Policy: string(policyJSON), Policy: string(policyJSON),
Expiration: &stsAccount.Expiration, Expiration: &stsAccount.Expiration,
} }
@@ -709,10 +710,10 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
// //
// This allows turning off service accounts for request sender, // This allows turning off service accounts for request sender,
// if there is no deny statement this call is implicitly enabled. // if there is no deny statement this call is implicitly enabled.
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: requestorUser, AccountName: requestorUser,
Groups: requestorGroups, Groups: requestorGroups,
Action: iampolicy.CreateServiceAccountAdminAction, Action: policy.CreateServiceAccountAdminAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -743,10 +744,10 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
} else { } else {
// Need permission if we are creating a service account for a // Need permission if we are creating a service account for a
// user <> to the request sender // user <> to the request sender
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: requestorUser, AccountName: requestorUser,
Groups: requestorGroups, Groups: requestorGroups,
Action: iampolicy.CreateServiceAccountAdminAction, Action: policy.CreateServiceAccountAdminAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -773,9 +774,9 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
// latter, a group notion is not supported. // latter, a group notion is not supported.
} }
var sp *iampolicy.Policy var sp *policy.Policy
if len(createReq.Policy) > 0 { if len(createReq.Policy) > 0 {
sp, err = iampolicy.ParseConfig(bytes.NewReader(createReq.Policy)) sp, err = policy.ParseConfig(bytes.NewReader(createReq.Policy))
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@@ -864,10 +865,10 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
return return
} }
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.UpdateServiceAccountAdminAction, Action: policy.UpdateServiceAccountAdminAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -903,9 +904,9 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
return return
} }
var sp *iampolicy.Policy var sp *policy.Policy
if len(updateReq.NewPolicy) > 0 { if len(updateReq.NewPolicy) > 0 {
sp, err = iampolicy.ParseConfig(bytes.NewReader(updateReq.NewPolicy)) sp, err = policy.ParseConfig(bytes.NewReader(updateReq.NewPolicy))
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@@ -970,16 +971,16 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
return return
} }
svcAccount, policy, err := globalIAMSys.GetServiceAccount(ctx, accessKey) svcAccount, sessionPolicy, err := globalIAMSys.GetServiceAccount(ctx, accessKey)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.ListServiceAccountsAdminAction, Action: policy.ListServiceAccountsAdminAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -995,10 +996,10 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
} }
} }
var svcAccountPolicy iampolicy.Policy var svcAccountPolicy policy.Policy
if policy != nil { if sessionPolicy != nil {
svcAccountPolicy = *policy svcAccountPolicy = *sessionPolicy
} else { } else {
policiesNames, err := globalIAMSys.PolicyDBGet(svcAccount.ParentUser, false) policiesNames, err := globalIAMSys.PolicyDBGet(svcAccount.ParentUser, false)
if err != nil { if err != nil {
@@ -1024,7 +1025,7 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
Name: svcAccount.Name, Name: svcAccount.Name,
Description: svcAccount.Description, Description: svcAccount.Description,
AccountStatus: svcAccount.Status, AccountStatus: svcAccount.Status,
ImpliedPolicy: policy == nil, ImpliedPolicy: sessionPolicy == nil,
Policy: string(policyJSON), Policy: string(policyJSON),
Expiration: expiration, Expiration: expiration,
} }
@@ -1067,10 +1068,10 @@ func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Req
// sender), check that the user has permissions. // sender), check that the user has permissions.
user := r.Form.Get("user") user := r.Form.Get("user")
if user != "" && user != cred.AccessKey { if user != "" && user != cred.AccessKey {
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.ListServiceAccountsAdminAction, Action: policy.ListServiceAccountsAdminAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -1148,10 +1149,10 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
// since this is a delete call we shall allow it to be deleted if possible. // since this is a delete call we shall allow it to be deleted if possible.
svcAccount, _, _ := globalIAMSys.GetServiceAccount(ctx, serviceAccount) svcAccount, _, _ := globalIAMSys.GetServiceAccount(ctx, serviceAccount)
adminPrivilege := globalIAMSys.IsAllowed(iampolicy.Args{ adminPrivilege := globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.RemoveServiceAccountAdminAction, Action: policy.RemoveServiceAccountAdminAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -1192,7 +1193,7 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
writeSuccessNoContent(w) writeSuccessNoContent(w)
} }
// AccountInfoHandler returns usage // AccountInfoHandler returns usage, permissions and other bucket metadata for incoming us
func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
@@ -1219,10 +1220,10 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
enablePrefixUsage := r.Form.Get("prefix-usage") == "true" enablePrefixUsage := r.Form.Get("prefix-usage") == "true"
isAllowedAccess := func(bucketName string) (rd, wr bool) { isAllowedAccess := func(bucketName string) (rd, wr bool) {
if globalIAMSys.IsAllowed(iampolicy.Args{ if globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.ListBucketAction, Action: policy.ListBucketAction,
BucketName: bucketName, BucketName: bucketName,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
@@ -1232,10 +1233,10 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
rd = true rd = true
} }
if globalIAMSys.IsAllowed(iampolicy.Args{ if globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.GetBucketLocationAction, Action: policy.GetBucketLocationAction,
BucketName: bucketName, BucketName: bucketName,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
@@ -1245,10 +1246,10 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
rd = true rd = true
} }
if globalIAMSys.IsAllowed(iampolicy.Args{ if globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.PutObjectAction, Action: policy.PutObjectAction,
BucketName: bucketName, BucketName: bucketName,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
@@ -1261,12 +1262,30 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
return rd, wr return rd, wr
} }
// Load the latest calculated data usage bucketStorageCache.Once.Do(func() {
dataUsageInfo, _ := loadDataUsageFromBackend(ctx, objectAPI) // Set this to 10 secs since its enough, as scanner
// does not update the bucket usage values frequently.
bucketStorageCache.TTL = 10 * time.Second
// Rely on older value if usage loading fails from disk.
bucketStorageCache.Relax = true
bucketStorageCache.Update = func() (interface{}, error) {
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
defer done()
return loadDataUsageFromBackend(ctx, objectAPI)
}
})
var dataUsageInfo DataUsageInfo
v, _ := bucketStorageCache.Get()
if v != nil {
dataUsageInfo, _ = v.(DataUsageInfo)
}
// If etcd, dns federation configured list buckets from etcd. // If etcd, dns federation configured list buckets from etcd.
var buckets []BucketInfo
var err error var err error
var buckets []BucketInfo
if globalDNSConfig != nil && globalBucketFederation { if globalDNSConfig != nil && globalBucketFederation {
dnsBuckets, err := globalDNSConfig.List() dnsBuckets, err := globalDNSConfig.List()
if err != nil && !IsErrIgnored(err, if err != nil && !IsErrIgnored(err,
@@ -1285,7 +1304,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
return buckets[i].Name < buckets[j].Name return buckets[i].Name < buckets[j].Name
}) })
} else { } else {
buckets, err = objectAPI.ListBuckets(ctx, BucketOptions{}) buckets, err = objectAPI.ListBuckets(ctx, BucketOptions{Cached: true})
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@@ -1298,14 +1317,14 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
accountName = cred.ParentUser accountName = cred.ParentUser
} }
roleArn := iampolicy.Args{Claims: cred.Claims}.GetRoleArn() roleArn := policy.Args{Claims: cred.Claims}.GetRoleArn()
policySetFromClaims, hasPolicyClaim := iampolicy.GetPoliciesFromClaims(cred.Claims, iamPolicyClaimNameOpenID()) policySetFromClaims, hasPolicyClaim := policy.GetPoliciesFromClaims(cred.Claims, iamPolicyClaimNameOpenID())
var effectivePolicy iampolicy.Policy var effectivePolicy policy.Policy
var buf []byte var buf []byte
switch { switch {
case accountName == globalActiveCred.AccessKey: case accountName == globalActiveCred.AccessKey:
for _, policy := range iampolicy.DefaultPolicies { for _, policy := range policy.DefaultPolicies {
if policy.Name == "consoleAdmin" { if policy.Name == "consoleAdmin" {
effectivePolicy = policy.Definition effectivePolicy = policy.Definition
break break
@@ -1417,7 +1436,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetPolicyAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetPolicyAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1463,7 +1482,7 @@ func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Reques
func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListUserPoliciesAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1475,7 +1494,7 @@ func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Requ
return return
} }
newPolicies := make(map[string]iampolicy.Policy) newPolicies := make(map[string]policy.Policy)
for name, p := range policies { for name, p := range policies {
_, err = json.Marshal(p) _, err = json.Marshal(p)
if err != nil { if err != nil {
@@ -1494,7 +1513,7 @@ func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListUserPoliciesAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1505,7 +1524,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
return return
} }
newPolicies := make(map[string]iampolicy.Policy) newPolicies := make(map[string]policy.Policy)
for name, p := range policies { for name, p := range policies {
_, err = json.Marshal(p) _, err = json.Marshal(p)
if err != nil { if err != nil {
@@ -1524,7 +1543,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DeletePolicyAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.DeletePolicyAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1550,7 +1569,7 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.CreatePolicyAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.CreatePolicyAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1582,7 +1601,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
return return
} }
iamPolicy, err := iampolicy.ParseConfig(bytes.NewReader(iamPolicyBytes)) iamPolicy, err := policy.ParseConfig(bytes.NewReader(iamPolicyBytes))
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@@ -1614,7 +1633,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AttachPolicyAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.AttachPolicyAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1688,7 +1707,7 @@ func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *ht
// Check authorization. // Check authorization.
objectAPI, cred := validateAdminReq(ctx, w, r, objectAPI, cred := validateAdminReq(ctx, w, r,
iampolicy.ListGroupsAdminAction, iampolicy.ListUsersAdminAction, iampolicy.ListUserPoliciesAdminAction) policy.ListGroupsAdminAction, policy.ListUsersAdminAction, policy.ListUserPoliciesAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1726,8 +1745,8 @@ func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *ht
func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.UpdatePolicyAssociationAction, objectAPI, cred := validateAdminReq(ctx, w, r, policy.UpdatePolicyAssociationAction,
iampolicy.AttachPolicyAdminAction) policy.AttachPolicyAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1826,7 +1845,7 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
// Get current object layer instance. // Get current object layer instance.
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportIAMAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportIAMAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -2096,7 +2115,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
return return
default: default:
defer f.Close() defer f.Close()
var allPolicies map[string]iampolicy.Policy var allPolicies map[string]policy.Policy
data, err = io.ReadAll(f) data, err = io.ReadAll(f)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allPoliciesFile, ""), r.URL) writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allPoliciesFile, ""), r.URL)
@@ -2177,10 +2196,10 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
checkDenyOnly = true checkDenyOnly = true
} }
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.CreateUserAdminAction, Action: policy.CreateUserAdminAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -2257,10 +2276,10 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
return return
} }
for user, svcAcctReq := range serviceAcctReqs { for user, svcAcctReq := range serviceAcctReqs {
var sp *iampolicy.Policy var sp *policy.Policy
var err error var err error
if len(svcAcctReq.SessionPolicy) > 0 { if len(svcAcctReq.SessionPolicy) > 0 {
sp, err = iampolicy.ParseConfig(bytes.NewReader(svcAcctReq.SessionPolicy)) sp, err = policy.ParseConfig(bytes.NewReader(svcAcctReq.SessionPolicy))
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL) writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
return return
@@ -2271,10 +2290,10 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
return return
} }
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.CreateServiceAccountAdminAction, Action: policy.CreateServiceAccountAdminAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
+1 -1
View File
@@ -39,7 +39,7 @@ import (
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio-go/v7/pkg/signer" "github.com/minio/minio-go/v7/pkg/signer"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
) )
const ( const (
+45 -41
View File
@@ -55,9 +55,9 @@ import (
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/logger/message/log"
"github.com/minio/pkg/logger/message/log" xnet "github.com/minio/pkg/v2/net"
xnet "github.com/minio/pkg/net" "github.com/minio/pkg/v2/policy"
"github.com/secure-io/sio-go" "github.com/secure-io/sio-go"
) )
@@ -81,7 +81,7 @@ const (
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerUpdateAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -252,11 +252,11 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
var objectAPI ObjectLayer var objectAPI ObjectLayer
switch serviceSig { switch serviceSig {
case serviceRestart: case serviceRestart:
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceRestartAdminAction) objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceRestartAdminAction)
case serviceStop: case serviceStop:
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceStopAdminAction) objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceStopAdminAction)
case serviceFreeze, serviceUnFreeze: case serviceFreeze, serviceUnFreeze:
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceFreezeAdminAction) objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceFreezeAdminAction)
} }
if objectAPI == nil { if objectAPI == nil {
return return
@@ -331,7 +331,7 @@ type ServerHTTPStats struct {
func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.StorageInfoAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.StorageInfoAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -370,7 +370,7 @@ func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -479,7 +479,7 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DataUsageInfoAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.DataUsageInfoAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -562,7 +562,7 @@ type PeerLocks struct {
func (a adminAPIHandlers) ForceUnlockHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ForceUnlockHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ForceUnlockAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ForceUnlockAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -597,7 +597,7 @@ func (a adminAPIHandlers) ForceUnlockHandler(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.TopLocksAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.TopLocksAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -648,7 +648,7 @@ func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.R
ctx := r.Context() ctx := r.Context()
// Validate request signature. // Validate request signature.
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "") _, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.ProfilingAdminAction, "")
if adminAPIErr != ErrNone { if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return return
@@ -733,7 +733,7 @@ func (a adminAPIHandlers) ProfileHandler(w http.ResponseWriter, r *http.Request)
ctx := r.Context() ctx := r.Context()
// Validate request signature. // Validate request signature.
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "") _, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.ProfilingAdminAction, "")
if adminAPIErr != ErrNone { if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return return
@@ -828,7 +828,7 @@ func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *htt
ctx := r.Context() ctx := r.Context()
// Validate request signature. // Validate request signature.
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "") _, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.ProfilingAdminAction, "")
if adminAPIErr != ErrNone { if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return return
@@ -926,7 +926,7 @@ func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reade
func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1110,7 +1110,7 @@ func getAggregatedBackgroundHealState(ctx context.Context, o ObjectLayer) (madmi
func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1131,7 +1131,7 @@ func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *
func (a adminAPIHandlers) SitePerfHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SitePerfHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1179,7 +1179,7 @@ func (a adminAPIHandlers) SitePerfHandler(w http.ResponseWriter, r *http.Request
func (a adminAPIHandlers) ClientDevNullExtraTime(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ClientDevNullExtraTime(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.BandwidthMonitorAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.BandwidthMonitorAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1196,7 +1196,7 @@ func (a adminAPIHandlers) ClientDevNull(w http.ResponseWriter, r *http.Request)
ctx := r.Context() ctx := r.Context()
timeStart := time.Now() timeStart := time.Now()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.BandwidthMonitorAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.BandwidthMonitorAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1238,7 +1238,7 @@ func (a adminAPIHandlers) ClientDevNull(w http.ResponseWriter, r *http.Request)
func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1284,7 +1284,7 @@ func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request)
func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1445,7 +1445,7 @@ func validateObjPerfOptions(ctx context.Context, storageInfo madmin.StorageInfo,
func (a adminAPIHandlers) DriveSpeedtestHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) DriveSpeedtestHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1566,7 +1566,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
// Validate request signature. // Validate request signature.
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.TraceAdminAction, "") _, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.TraceAdminAction, "")
if adminAPIErr != ErrNone { if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return return
@@ -1637,7 +1637,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConsoleLogAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConsoleLogAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1718,7 +1718,7 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSCreateKeyAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSCreateKeyAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1739,7 +1739,7 @@ func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Req
func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSKeyStatusAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1776,7 +1776,7 @@ func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Reques
func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSKeyStatusAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -2376,7 +2376,7 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -2483,7 +2483,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
ctx := r.Context() ctx := r.Context()
// Validate request signature. // Validate request signature.
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ServerInfoAdminAction, "") _, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.ServerInfoAdminAction, "")
if adminAPIErr != ErrNone { if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return return
@@ -2627,12 +2627,12 @@ func fetchLoggerInfo() ([]madmin.Logger, []madmin.Audit) {
return loggerInfo, auditloggerInfo return loggerInfo, auditloggerInfo
} }
func embedFileInZip(zipWriter *zip.Writer, name string, data []byte) error { func embedFileInZip(zipWriter *zip.Writer, name string, data []byte, fileMode os.FileMode) error {
// Send profiling data to zip as file // Send profiling data to zip as file
header, zerr := zip.FileInfoHeader(dummyFileInfo{ header, zerr := zip.FileInfoHeader(dummyFileInfo{
name: name, name: name,
size: int64(len(data)), size: int64(len(data)),
mode: 0o600, mode: fileMode,
modTime: UTCNow(), modTime: UTCNow(),
isDir: false, isDir: false,
sys: nil, sys: nil,
@@ -2735,7 +2735,7 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
ctx := r.Context() ctx := r.Context()
// Validate request signature. // Validate request signature.
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.InspectDataAction, "") _, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.InspectDataAction, "")
if adminAPIErr != ErrNone { if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return return
@@ -2863,7 +2863,7 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
defer inspectZipW.Close() defer inspectZipW.Close()
if b := getClusterMetaInfo(ctx); len(b) > 0 { if b := getClusterMetaInfo(ctx); len(b) > 0 {
logger.LogIf(ctx, embedFileInZip(inspectZipW, "cluster.info", b)) logger.LogIf(ctx, embedFileInZip(inspectZipW, "cluster.info", b, 0o600))
} }
} }
@@ -2927,11 +2927,16 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
sb.WriteString(pool.CmdLine) sb.WriteString(pool.CmdLine)
} }
sb.WriteString("\n") sb.WriteString("\n")
logger.LogIf(ctx, embedFileInZip(inspectZipW, "inspect-input.txt", sb.Bytes())) logger.LogIf(ctx, embedFileInZip(inspectZipW, "inspect-input.txt", sb.Bytes(), 0o600))
scheme := "https"
if !globalIsTLS {
scheme = "http"
}
// save MinIO start script to inspect command // save MinIO start script to inspect command
var scrb bytes.Buffer var scrb bytes.Buffer
scrb.WriteString(`#!/usr/bin/env bash fmt.Fprintf(&scrb, `#!/usr/bin/env bash
function main() { function main() {
for file in $(ls -1); do for file in $(ls -1); do
@@ -2940,10 +2945,10 @@ function main() {
done done
# Read content of inspect-input.txt # Read content of inspect-input.txt
MINIO_OPTS=$(grep "Server command line args" <./inspect-input.txt | sed "s/Server command line args: //g" | sed -r "s#https:\/\/#\.\/#g") MINIO_OPTS=$(grep "Server command line args" <./inspect-input.txt | sed "s/Server command line args: //g" | sed -r "s#%s:\/\/#\.\/#g")
# Start MinIO instance using the options # Start MinIO instance using the options
START_CMD="CI=on MINIO_ROOT_USER=minio MINIO_ROOT_PASSWORD=minio123 minio server ${MINIO_OPTS} &" START_CMD="CI=on _MINIO_AUTO_DRIVE_HEALING=off minio server ${MINIO_OPTS} &"
echo echo
echo "Starting MinIO instance: ${START_CMD}" echo "Starting MinIO instance: ${START_CMD}"
echo echo
@@ -2955,9 +2960,8 @@ function main() {
sleep 10 sleep 10
} }
main "$@"`, main "$@"`, scheme)
) logger.LogIf(ctx, embedFileInZip(inspectZipW, "start-minio.sh", scrb.Bytes(), 0o755))
logger.LogIf(ctx, embedFileInZip(inspectZipW, "start-minio.sh", scrb.Bytes()))
} }
func getSubnetAdminPublicKey() []byte { func getSubnetAdminPublicKey() []byte {
-7
View File
@@ -683,13 +683,6 @@ func (h *healSequence) healSequenceStart(objAPI ObjectLayer) {
} }
} }
func (h *healSequence) logHeal(healType madmin.HealItemType) {
h.mutex.Lock()
h.scannedItemsMap[healType]++
h.lastHealActivity = UTCNow()
h.mutex.Unlock()
}
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error { func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
// Send heal request // Send heal request
task := healTask{ task := healTask{
+1 -3
View File
@@ -47,7 +47,7 @@ import (
levent "github.com/minio/minio/internal/config/lambda/event" levent "github.com/minio/minio/internal/config/lambda/event"
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
// APIError structure // APIError structure
@@ -2236,8 +2236,6 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrSlowDownWrite apiErr = ErrSlowDownWrite
case InsufficientReadQuorum: case InsufficientReadQuorum:
apiErr = ErrSlowDownRead apiErr = ErrSlowDownRead
case InvalidMarkerPrefixCombination:
apiErr = ErrNotImplemented
case InvalidUploadIDKeyCombination: case InvalidUploadIDKeyCombination:
apiErr = ErrNotImplemented apiErr = ErrNotImplemented
case MalformedUploadID: case MalformedUploadID:
-1
View File
@@ -42,7 +42,6 @@ var toAPIErrorTests = []struct {
{err: InvalidPart{}, errCode: ErrInvalidPart}, {err: InvalidPart{}, errCode: ErrInvalidPart},
{err: InsufficientReadQuorum{}, errCode: ErrSlowDownRead}, {err: InsufficientReadQuorum{}, errCode: ErrSlowDownRead},
{err: InsufficientWriteQuorum{}, errCode: ErrSlowDownWrite}, {err: InsufficientWriteQuorum{}, errCode: ErrSlowDownWrite},
{err: InvalidMarkerPrefixCombination{}, errCode: ErrNotImplemented},
{err: InvalidUploadIDKeyCombination{}, errCode: ErrNotImplemented}, {err: InvalidUploadIDKeyCombination{}, errCode: ErrNotImplemented},
{err: MalformedUploadID{}, errCode: ErrNoSuchUpload}, {err: MalformedUploadID{}, errCode: ErrNoSuchUpload},
{err: PartTooSmall{}, errCode: ErrEntityTooSmall}, {err: PartTooSmall{}, errCode: ErrEntityTooSmall},
+1 -1
View File
@@ -35,7 +35,7 @@ import (
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
xxml "github.com/minio/xxml" xxml "github.com/minio/xxml"
) )
+1 -1
View File
@@ -27,7 +27,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/wildcard" "github.com/minio/pkg/v2/wildcard"
"github.com/rs/cors" "github.com/rs/cors"
) )
+23 -24
View File
@@ -41,8 +41,7 @@ import (
xjwt "github.com/minio/minio/internal/jwt" xjwt "github.com/minio/minio/internal/jwt"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/mcontext" "github.com/minio/minio/internal/mcontext"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
iampolicy "github.com/minio/pkg/iam/policy"
) )
// Verify if request has JWT. // Verify if request has JWT.
@@ -186,15 +185,15 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
// checkAdminRequestAuth checks for authentication and authorization for the incoming // checkAdminRequestAuth checks for authentication and authorization for the incoming
// request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests // request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
// are automatically rejected. // are automatically rejected.
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) { func checkAdminRequestAuth(ctx context.Context, r *http.Request, action policy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
cred, owner, s3Err := validateAdminSignature(ctx, r, region) cred, owner, s3Err := validateAdminSignature(ctx, r, region)
if s3Err != ErrNone { if s3Err != ErrNone {
return cred, s3Err return cred, s3Err
} }
if globalIAMSys.IsAllowed(iampolicy.Args{ if globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.Action(action), Action: policy.Action(action),
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -248,7 +247,7 @@ func getClaimsFromTokenWithSecret(token, secret string) (map[string]interface{},
} }
// Check if a session policy is set. If so, decode it here. // Check if a session policy is set. If so, decode it here.
sp, spok := claims.Lookup(iampolicy.SessionPolicyName) sp, spok := claims.Lookup(policy.SessionPolicyName)
if spok { if spok {
// Looks like subpolicy is set and is a string, if set then its // Looks like subpolicy is set and is a string, if set then its
// base64 encoded, decode it. Decoding fails reject such // base64 encoded, decode it. Decoding fails reject such
@@ -413,7 +412,7 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" { if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
// Anonymous checks are not meant for ListAllBuckets action // Anonymous checks are not meant for ListAllBuckets action
if globalPolicySys.IsAllowed(policy.Args{ if globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: action, Action: action,
@@ -429,7 +428,7 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
if action == policy.ListBucketVersionsAction { if action == policy.ListBucketVersionsAction {
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission // In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
// verify as a fallback. // verify as a fallback.
if globalPolicySys.IsAllowed(policy.Args{ if globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: policy.ListBucketAction, Action: policy.ListBucketAction,
@@ -446,10 +445,10 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
return ErrAccessDenied return ErrAccessDenied
} }
if action == policy.DeleteObjectAction && versionID != "" { if action == policy.DeleteObjectAction && versionID != "" {
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.Action(policy.DeleteObjectVersionAction), Action: policy.Action(policy.DeleteObjectVersionAction),
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
ObjectName: object, ObjectName: object,
@@ -460,10 +459,10 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
return ErrAccessDenied return ErrAccessDenied
} }
} }
if globalIAMSys.IsAllowed(iampolicy.Args{ if globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.Action(action), Action: action,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
ObjectName: object, ObjectName: object,
@@ -477,10 +476,10 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
if action == policy.ListBucketVersionsAction { if action == policy.ListBucketVersionsAction {
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission // In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
// verify as a fallback. // verify as a fallback.
if globalIAMSys.IsAllowed(iampolicy.Args{ if globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.ListBucketAction, Action: policy.ListBucketAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
ObjectName: object, ObjectName: object,
@@ -568,7 +567,7 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present. // Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
// The verification happens implicit during reading. // The verification happens implicit during reading.
reader, err := hash.NewReader(r.Body, -1, clientETag.String(), hex.EncodeToString(contentSHA256), -1) reader, err := hash.NewReader(ctx, r.Body, -1, clientETag.String(), hex.EncodeToString(contentSHA256), -1)
if err != nil { if err != nil {
return toAPIErrorCode(ctx, err) return toAPIErrorCode(ctx, err)
} }
@@ -696,10 +695,10 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)} conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
} }
if retMode == objectlock.RetGovernance && byPassSet { if retMode == objectlock.RetGovernance && byPassSet {
byPassSet = globalIAMSys.IsAllowed(iampolicy.Args{ byPassSet = globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.BypassGovernanceRetentionAction, Action: policy.BypassGovernanceRetentionAction,
BucketName: bucketName, BucketName: bucketName,
ObjectName: objectName, ObjectName: objectName,
ConditionValues: conditions, ConditionValues: conditions,
@@ -707,10 +706,10 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
Claims: cred.Claims, Claims: cred.Claims,
}) })
} }
if globalIAMSys.IsAllowed(iampolicy.Args{ if globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.PutObjectRetentionAction, Action: policy.PutObjectRetentionAction,
BucketName: bucketName, BucketName: bucketName,
ConditionValues: conditions, ConditionValues: conditions,
ObjectName: objectName, ObjectName: objectName,
@@ -728,7 +727,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
// isPutActionAllowed - check if PUT operation is allowed on the resource, this // isPutActionAllowed - check if PUT operation is allowed on the resource, this
// call verifies bucket policies and IAM policies, supports multi user // call verifies bucket policies and IAM policies, supports multi user
// checks etc. // checks etc.
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action iampolicy.Action) (s3Err APIErrorCode) { func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
var cred auth.Credentials var cred auth.Credentials
var owner bool var owner bool
region := globalSite.Region region := globalSite.Region
@@ -751,17 +750,17 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
// Do not check for PutObjectRetentionAction permission, // Do not check for PutObjectRetentionAction permission,
// if mode and retain until date are not set. // if mode and retain until date are not set.
// Can happen when bucket has default lock config set // Can happen when bucket has default lock config set
if action == iampolicy.PutObjectRetentionAction && if action == policy.PutObjectRetentionAction &&
r.Header.Get(xhttp.AmzObjectLockMode) == "" && r.Header.Get(xhttp.AmzObjectLockMode) == "" &&
r.Header.Get(xhttp.AmzObjectLockRetainUntilDate) == "" { r.Header.Get(xhttp.AmzObjectLockRetainUntilDate) == "" {
return ErrNone return ErrNone
} }
if cred.AccessKey == "" { if cred.AccessKey == "" {
if globalPolicySys.IsAllowed(policy.Args{ if globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: policy.Action(action), Action: action,
BucketName: bucketName, BucketName: bucketName,
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials), ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
IsOwner: false, IsOwner: false,
@@ -772,7 +771,7 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
return ErrAccessDenied return ErrAccessDenied
} }
if globalIAMSys.IsAllowed(iampolicy.Args{ if globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: action, Action: action,
+2 -2
View File
@@ -28,7 +28,7 @@ import (
"time" "time"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/policy"
) )
type nullReader struct{} type nullReader struct{}
@@ -443,7 +443,7 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied}, {Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode { if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, policy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error) t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
} }
} }
+1 -1
View File
@@ -26,7 +26,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
) )
// healTask represents what to heal along with options // healTask represents what to heal along with options
+2 -2
View File
@@ -34,7 +34,7 @@ import (
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
) )
const ( const (
@@ -347,7 +347,7 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...) globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
if env.Get("_MINIO_AUTO_DISK_HEALING", config.EnableOn) == config.EnableOn { if env.Get("_MINIO_AUTO_DRIVE_HEALING", config.EnableOn) == config.EnableOn || env.Get("_MINIO_AUTO_DISK_HEALING", config.EnableOn) == config.EnableOn {
go monitorLocalDisksAndHeal(ctx, z) go monitorLocalDisksAndHeal(ctx, z)
} }
} }
+62 -38
View File
@@ -28,7 +28,6 @@ import (
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"
"path"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@@ -38,6 +37,7 @@ import (
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/lithammer/shortuuid/v4" "github.com/lithammer/shortuuid/v4"
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7"
miniogo "github.com/minio/minio-go/v7" miniogo "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/encrypt" "github.com/minio/minio-go/v7/pkg/encrypt"
@@ -47,10 +47,10 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/ioutil" "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/console" "github.com/minio/pkg/v2/console"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/policy"
"github.com/minio/pkg/workers" "github.com/minio/pkg/v2/workers"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@@ -114,7 +114,7 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
srcObject := srcObjInfo.Name srcObject := srcObjInfo.Name
tgtObject := srcObjInfo.Name tgtObject := srcObjInfo.Name
if r.Target.Prefix != "" { if r.Target.Prefix != "" {
tgtObject = path.Join(r.Target.Prefix, srcObjInfo.Name) tgtObject = pathJoin(r.Target.Prefix, srcObjInfo.Name)
} }
versionID := srcObjInfo.VersionID versionID := srcObjInfo.VersionID
@@ -167,7 +167,7 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
} }
defer rd.Close() defer rd.Close()
hr, err := hash.NewReader(rd, objInfo.Size, "", "", objInfo.Size) hr, err := hash.NewReader(ctx, rd, objInfo.Size, "", "", objInfo.Size)
if err != nil { if err != nil {
return err return err
} }
@@ -182,7 +182,7 @@ func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, a
srcObject := srcObjInfo.Name srcObject := srcObjInfo.Name
tgtObject := srcObjInfo.Name tgtObject := srcObjInfo.Name
if r.Target.Prefix != "" { if r.Target.Prefix != "" {
tgtObject = path.Join(r.Target.Prefix, srcObjInfo.Name) tgtObject = pathJoin(r.Target.Prefix, srcObjInfo.Name)
} }
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 { if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
opts.VersionID = "" opts.VersionID = ""
@@ -230,7 +230,7 @@ func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, a
} }
defer rd.Close() defer rd.Close()
hr, err = hash.NewReader(io.LimitReader(rd, objInfo.Size), objInfo.Size, "", "", objInfo.Size) hr, err = hash.NewReader(ctx, io.LimitReader(rd, objInfo.Size), objInfo.Size, "", "", objInfo.Size)
if err != nil { if err != nil {
return err return err
} }
@@ -270,6 +270,10 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
} }
rnd := rand.New(rand.NewSource(time.Now().UnixNano())) rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
isTags := len(r.Flags.Filter.Tags) != 0
isMetadata := len(r.Flags.Filter.Metadata) != 0
isStorageClassOnly := len(r.Flags.Filter.Metadata) == 1 && strings.EqualFold(r.Flags.Filter.Metadata[0].Key, xhttp.AmzStorageClass)
skip := func(oi ObjectInfo) (ok bool) { skip := func(oi ObjectInfo) (ok bool) {
if r.Flags.Filter.OlderThan > 0 && time.Since(oi.ModTime) < r.Flags.Filter.OlderThan { if r.Flags.Filter.OlderThan > 0 && time.Since(oi.ModTime) < r.Flags.Filter.OlderThan {
// skip all objects that are newer than specified older duration // skip all objects that are newer than specified older duration
@@ -290,7 +294,8 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
// skip all objects that are created after the specified time. // skip all objects that are created after the specified time.
return true return true
} }
if len(r.Flags.Filter.Tags) > 0 {
if isTags {
// Only parse object tags if tags filter is specified. // Only parse object tags if tags filter is specified.
tagMap := map[string]string{} tagMap := map[string]string{}
tagStr := oi.UserTags tagStr := oi.UserTags
@@ -313,23 +318,19 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
return false return false
} }
if len(r.Flags.Filter.Metadata) > 0 { for _, kv := range r.Flags.Filter.Metadata {
for _, kv := range r.Flags.Filter.Metadata { for k, v := range oi.UserDefined {
for k, v := range oi.UserDefined { if !stringsHasPrefixFold(k, "x-amz-meta-") && !isStandardHeader(k) {
if !stringsHasPrefixFold(k, "x-amz-meta-") && !isStandardHeader(k) { continue
continue }
} // We only need to match x-amz-meta or standardHeaders
// We only need to match x-amz-meta or standardHeaders if kv.Match(BatchJobKV{Key: k, Value: v}) {
if kv.Match(BatchJobKV{Key: k, Value: v}) { return true
return true
}
} }
} }
// None of the provided metadata filters match skip the object.
return false
} }
// None of the provided filters match
return false return false
} }
@@ -384,17 +385,32 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
for obj := range objInfoCh { for obj := range objInfoCh {
oi := toObjectInfo(r.Source.Bucket, obj.Key, obj) oi := toObjectInfo(r.Source.Bucket, obj.Key, obj)
if !minioSrc { if !minioSrc {
oi2, err := c.StatObject(ctx, r.Source.Bucket, obj.Key, miniogo.StatObjectOptions{}) // Check if metadata filter was requested and it is expected to have
if err == nil { // all user metadata or just storageClass. If its only storageClass
oi = toObjectInfo(r.Source.Bucket, obj.Key, oi2) // List() already returns relevant information for filter to be applied.
} else { if isMetadata && !isStorageClassOnly {
if isErrMethodNotAllowed(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) || oi2, err := c.StatObject(ctx, r.Source.Bucket, obj.Key, miniogo.StatObjectOptions{})
isErrObjectNotFound(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) { if err == nil {
oi = toObjectInfo(r.Source.Bucket, obj.Key, oi2)
} else {
if !isErrMethodNotAllowed(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) &&
!isErrObjectNotFound(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) {
logger.LogIf(ctx, err)
}
continue
}
}
if isTags {
tags, err := c.GetObjectTagging(ctx, r.Source.Bucket, obj.Key, minio.GetObjectTaggingOptions{})
if err == nil {
oi.UserTags = tags.String()
} else {
if !isErrMethodNotAllowed(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) &&
!isErrObjectNotFound(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) {
logger.LogIf(ctx, err)
}
continue continue
} }
logger.LogIf(ctx, err)
cancel()
return err
} }
} }
if skip(oi) { if skip(oi) {
@@ -482,10 +498,12 @@ func toObjectInfo(bucket, object string, objInfo miniogo.ObjectInfo) ObjectInfo
ReplicationStatusInternal: objInfo.ReplicationStatus, ReplicationStatusInternal: objInfo.ReplicationStatus,
UserTags: tags.String(), UserTags: tags.String(),
} }
oi.UserDefined = make(map[string]string, len(objInfo.Metadata)) oi.UserDefined = make(map[string]string, len(objInfo.Metadata))
for k, v := range objInfo.Metadata { for k, v := range objInfo.Metadata {
oi.UserDefined[k] = v[0] oi.UserDefined[k] = v[0]
} }
ce, ok := oi.UserDefined[xhttp.ContentEncoding] ce, ok := oi.UserDefined[xhttp.ContentEncoding]
if !ok { if !ok {
ce, ok = oi.UserDefined[strings.ToLower(xhttp.ContentEncoding)] ce, ok = oi.UserDefined[strings.ToLower(xhttp.ContentEncoding)]
@@ -493,6 +511,12 @@ func toObjectInfo(bucket, object string, objInfo miniogo.ObjectInfo) ObjectInfo
if ok { if ok {
oi.ContentEncoding = ce oi.ContentEncoding = ce
} }
_, ok = oi.UserDefined[xhttp.AmzStorageClass]
if !ok {
oi.UserDefined[xhttp.AmzStorageClass] = objInfo.StorageClass
}
return oi return oi
} }
@@ -822,7 +846,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
} }
rnd := rand.New(rand.NewSource(time.Now().UnixNano())) rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
skip := func(info FileInfo) (ok bool) { selectObj := func(info FileInfo) (ok bool) {
if r.Flags.Filter.OlderThan > 0 && time.Since(info.ModTime) < r.Flags.Filter.OlderThan { if r.Flags.Filter.OlderThan > 0 && time.Since(info.ModTime) < r.Flags.Filter.OlderThan {
// skip all objects that are newer than specified older duration // skip all objects that are newer than specified older duration
return false return false
@@ -932,7 +956,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
results := make(chan ObjectInfo, 100) results := make(chan ObjectInfo, 100)
if err := api.Walk(ctx, r.Source.Bucket, r.Source.Prefix, results, ObjectOptions{ if err := api.Walk(ctx, r.Source.Bucket, r.Source.Prefix, results, ObjectOptions{
WalkMarker: lastObject, WalkMarker: lastObject,
WalkFilter: skip, WalkFilter: selectObj,
}); err != nil { }); err != nil {
cancel() cancel()
// Do not need to retry if we can't list objects on source. // Do not need to retry if we can't list objects on source.
@@ -1258,7 +1282,7 @@ func batchReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (p
func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListBatchJobsAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListBatchJobsAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1308,7 +1332,7 @@ var errNoSuchJob = errors.New("no such job")
func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DescribeBatchJobAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.DescribeBatchJobAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1343,7 +1367,7 @@ func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Reques
func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, creds := validateAdminReq(ctx, w, r, iampolicy.StartBatchJobAction) objectAPI, creds := validateAdminReq(ctx, w, r, policy.StartBatchJobAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -1397,7 +1421,7 @@ func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request)
func (a adminAPIHandlers) CancelBatchJob(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) CancelBatchJob(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.CancelBatchJobAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.CancelBatchJobAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
+1 -1
View File
@@ -21,7 +21,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/minio/pkg/wildcard" "github.com/minio/pkg/v2/wildcard"
) )
//go:generate msgp -file $GOFILE //go:generate msgp -file $GOFILE
+2 -2
View File
@@ -34,8 +34,8 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
"github.com/minio/pkg/workers" "github.com/minio/pkg/v2/workers"
) )
// keyrotate: // keyrotate:
+1 -1
View File
@@ -33,7 +33,7 @@ import (
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/rest" "github.com/minio/minio/internal/rest"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
) )
const ( const (
+1 -1
View File
@@ -30,7 +30,7 @@ import (
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
const ( const (
+20 -18
View File
@@ -60,9 +60,8 @@ import (
"github.com/minio/minio/internal/ioutil" "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/sync/errgroup"
"github.com/minio/pkg/sync/errgroup"
) )
const ( const (
@@ -367,10 +366,10 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
// Use the following trick to filter in place // Use the following trick to filter in place
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place // https://github.com/golang/go/wiki/SliceTricks#filter-in-place
for _, bucketInfo := range bucketsInfo { for _, bucketInfo := range bucketsInfo {
if globalIAMSys.IsAllowed(iampolicy.Args{ if globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.ListBucketAction, Action: policy.ListBucketAction,
BucketName: bucketInfo.Name, BucketName: bucketInfo.Name,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
@@ -379,10 +378,10 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
}) { }) {
bucketsInfo[n] = bucketInfo bucketsInfo[n] = bucketInfo
n++ n++
} else if globalIAMSys.IsAllowed(iampolicy.Args{ } else if globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.GetBucketLocationAction, Action: policy.GetBucketLocationAction,
BucketName: bucketInfo.Name, BucketName: bucketInfo.Name,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
@@ -770,8 +769,8 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
if objectLockEnabled { if objectLockEnabled {
// Creating a bucket with locking requires the user having more permissions // Creating a bucket with locking requires the user having more permissions
for _, action := range []iampolicy.Action{iampolicy.PutBucketObjectLockConfigurationAction, iampolicy.PutBucketVersioningAction} { for _, action := range []policy.Action{policy.PutBucketObjectLockConfigurationAction, policy.PutBucketVersioningAction} {
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: action, Action: action,
@@ -1103,10 +1102,10 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
if len(fanOutEntries) > 0 { if len(fanOutEntries) > 0 {
// Once signature is validated, check if the user has // Once signature is validated, check if the user has
// explicit permissions for the user. // explicit permissions for the user.
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.PutObjectFanOutAction, Action: policy.PutObjectFanOutAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
BucketName: bucket, BucketName: bucket,
ObjectName: object, ObjectName: object,
@@ -1119,10 +1118,10 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
} else { } else {
// Once signature is validated, check if the user has // Once signature is validated, check if the user has
// explicit permissions for the user. // explicit permissions for the user.
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.PutObjectAction, Action: policy.PutObjectAction,
ConditionValues: getConditionValues(r, "", cred), ConditionValues: getConditionValues(r, "", cred),
BucketName: bucket, BucketName: bucket,
ObjectName: object, ObjectName: object,
@@ -1140,7 +1139,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
return return
} }
hashReader, err := hash.NewReader(reader, fileSize, "", "", fileSize) hashReader, err := hash.NewReader(ctx, reader, fileSize, "", "", fileSize)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
@@ -1255,7 +1254,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
return return
} }
// do not try to verify encrypted content/ // do not try to verify encrypted content/
hashReader, err = hash.NewReader(reader, -1, "", "", -1) hashReader, err = hash.NewReader(ctx, reader, -1, "", "", -1)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
@@ -1290,7 +1289,10 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
// instead of "copying" from source, we need the stream to be seekable // instead of "copying" from source, we need the stream to be seekable
// to ensure that we can make fan-out calls concurrently. // to ensure that we can make fan-out calls concurrently.
buf := bytebufferpool.Get() buf := bytebufferpool.Get()
defer bytebufferpool.Put(buf) defer func() {
buf.Reset()
bytebufferpool.Put(buf)
}()
md5w := md5.New() md5w := md5.New()
@@ -1498,7 +1500,7 @@ func (api objectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter,
} }
// Check if anonymous (non-owner) has access to list objects. // Check if anonymous (non-owner) has access to list objects.
readable := globalPolicySys.IsAllowed(policy.Args{ readable := globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
Action: policy.ListBucketAction, Action: policy.ListBucketAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials), ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
@@ -1506,7 +1508,7 @@ func (api objectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter,
}) })
// Check if anonymous (non-owner) has access to upload objects. // Check if anonymous (non-owner) has access to upload objects.
writable := globalPolicySys.IsAllowed(policy.Args{ writable := globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
Action: policy.PutObjectAction, Action: policy.PutObjectAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials), ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
+1 -1
View File
@@ -27,7 +27,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
const ( const (
+2 -2
View File
@@ -41,8 +41,8 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/s3select" "github.com/minio/minio/internal/s3select"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
"github.com/minio/pkg/workers" "github.com/minio/pkg/v2/workers"
) )
const ( const (
+1 -1
View File
@@ -26,7 +26,7 @@ import (
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
// Validate all the ListObjects query arguments, returns an APIErrorCode // Validate all the ListObjects query arguments, returns an APIErrorCode
+3 -3
View File
@@ -35,8 +35,8 @@ import (
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
) )
// BucketMetadataSys captures all bucket metadata for a given cluster. // BucketMetadataSys captures all bucket metadata for a given cluster.
@@ -310,7 +310,7 @@ func (sys *BucketMetadataSys) CreatedAt(bucket string) (time.Time, error) {
// GetPolicyConfig returns configured bucket policy // GetPolicyConfig returns configured bucket policy
// The returned object may not be modified. // The returned object may not be modified.
func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.Policy, time.Time, error) { func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.BucketPolicy, time.Time, error) {
meta, _, err := sys.GetConfig(GlobalContext, bucket) meta, _, err := sys.GetConfig(GlobalContext, bucket)
if err != nil { if err != nil {
if errors.Is(err, errConfigNotFound) { if errors.Is(err, errConfigNotFound) {
+3 -3
View File
@@ -41,7 +41,7 @@ import (
"github.com/minio/minio/internal/fips" "github.com/minio/minio/internal/fips"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
"github.com/minio/sio" "github.com/minio/sio"
) )
@@ -91,7 +91,7 @@ type BucketMetadata struct {
LifecycleConfigUpdatedAt time.Time LifecycleConfigUpdatedAt time.Time
// Unexported fields. Must be updated atomically. // Unexported fields. Must be updated atomically.
policyConfig *policy.Policy policyConfig *policy.BucketPolicy
notificationConfig *event.Config notificationConfig *event.Config
lifecycleConfig *lifecycle.Lifecycle lifecycleConfig *lifecycle.Lifecycle
objectLockConfig *objectlock.Config objectLockConfig *objectlock.Config
@@ -217,7 +217,7 @@ func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket strin
// The first error encountered is returned. // The first error encountered is returned.
func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLayer) (err error) { func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLayer) (err error) {
if len(b.PolicyConfigJSON) != 0 { if len(b.PolicyConfigJSON) != 0 {
b.policyConfig, err = policy.ParseConfig(bytes.NewReader(b.PolicyConfigJSON), b.Name) b.policyConfig, err = policy.ParseBucketPolicyConfig(bytes.NewReader(b.PolicyConfigJSON), b.Name)
if err != nil { if err != nil {
return err return err
} }
+1 -1
View File
@@ -26,7 +26,7 @@ import (
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
const ( const (
+1 -1
View File
@@ -28,7 +28,7 @@ import (
"github.com/minio/minio/internal/bucket/replication" "github.com/minio/minio/internal/bucket/replication"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
// BucketObjectLockSys - map of bucket and retention configuration. // BucketObjectLockSys - map of bucket and retention configuration.
+2 -2
View File
@@ -27,7 +27,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
const ( const (
@@ -84,7 +84,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
return return
} }
bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket) bucketPolicy, err := policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyBytes), bucket)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, APIError{ writeErrorResponse(ctx, w, APIError{
Code: "MalformedPolicy", Code: "MalformedPolicy",
+28 -28
View File
@@ -29,31 +29,31 @@ import (
"testing" "testing"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
"github.com/minio/pkg/bucket/policy/condition" "github.com/minio/pkg/v2/policy/condition"
) )
func getAnonReadOnlyBucketPolicy(bucketName string) *policy.Policy { func getAnonReadOnlyBucketPolicy(bucketName string) *policy.BucketPolicy {
return &policy.Policy{ return &policy.BucketPolicy{
Version: policy.DefaultVersion, Version: policy.DefaultVersion,
Statements: []policy.Statement{ Statements: []policy.BPStatement{
policy.NewStatement( policy.NewBPStatement(
"", "",
policy.Allow, policy.Allow,
policy.NewPrincipal("*"), policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetBucketLocationAction, policy.ListBucketAction), policy.NewActionSet(policy.GetBucketLocationAction, policy.ListBucketAction),
policy.NewResourceSet(policy.NewResource(bucketName, "")), policy.NewResourceSet(policy.NewResource(bucketName)),
condition.NewFunctions(), condition.NewFunctions(),
), ),
}, },
} }
} }
func getAnonWriteOnlyBucketPolicy(bucketName string) *policy.Policy { func getAnonWriteOnlyBucketPolicy(bucketName string) *policy.BucketPolicy {
return &policy.Policy{ return &policy.BucketPolicy{
Version: policy.DefaultVersion, Version: policy.DefaultVersion,
Statements: []policy.Statement{ Statements: []policy.BPStatement{
policy.NewStatement( policy.NewBPStatement(
"", "",
policy.Allow, policy.Allow,
policy.NewPrincipal("*"), policy.NewPrincipal("*"),
@@ -61,34 +61,34 @@ func getAnonWriteOnlyBucketPolicy(bucketName string) *policy.Policy {
policy.GetBucketLocationAction, policy.GetBucketLocationAction,
policy.ListBucketMultipartUploadsAction, policy.ListBucketMultipartUploadsAction,
), ),
policy.NewResourceSet(policy.NewResource(bucketName, "")), policy.NewResourceSet(policy.NewResource(bucketName)),
condition.NewFunctions(), condition.NewFunctions(),
), ),
}, },
} }
} }
func getAnonReadOnlyObjectPolicy(bucketName, prefix string) *policy.Policy { func getAnonReadOnlyObjectPolicy(bucketName, prefix string) *policy.BucketPolicy {
return &policy.Policy{ return &policy.BucketPolicy{
Version: policy.DefaultVersion, Version: policy.DefaultVersion,
Statements: []policy.Statement{ Statements: []policy.BPStatement{
policy.NewStatement( policy.NewBPStatement(
"", "",
policy.Allow, policy.Allow,
policy.NewPrincipal("*"), policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetObjectAction), policy.NewActionSet(policy.GetObjectAction),
policy.NewResourceSet(policy.NewResource(bucketName, prefix)), policy.NewResourceSet(policy.NewResource(bucketName+"/"+prefix)),
condition.NewFunctions(), condition.NewFunctions(),
), ),
}, },
} }
} }
func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.Policy { func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.BucketPolicy {
return &policy.Policy{ return &policy.BucketPolicy{
Version: policy.DefaultVersion, Version: policy.DefaultVersion,
Statements: []policy.Statement{ Statements: []policy.BPStatement{
policy.NewStatement( policy.NewBPStatement(
"", "",
policy.Allow, policy.Allow,
policy.NewPrincipal("*"), policy.NewPrincipal("*"),
@@ -98,7 +98,7 @@ func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.Policy {
policy.ListMultipartUploadPartsAction, policy.ListMultipartUploadPartsAction,
policy.PutObjectAction, policy.PutObjectAction,
), ),
policy.NewResourceSet(policy.NewResource(bucketName, prefix)), policy.NewResourceSet(policy.NewResource(bucketName+"/"+prefix)),
condition.NewFunctions(), condition.NewFunctions(),
), ),
}, },
@@ -493,13 +493,13 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
if recV4.Code != testCase.expectedRespStatus { if recV4.Code != testCase.expectedRespStatus {
// Verify whether the bucket policy fetched is same as the one inserted. // Verify whether the bucket policy fetched is same as the one inserted.
var expectedPolicy *policy.Policy var expectedPolicy *policy.BucketPolicy
expectedPolicy, err = policy.ParseConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName) expectedPolicy, err = policy.ParseBucketPolicyConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName)
if err != nil { if err != nil {
t.Fatalf("unexpected error. %v", err) t.Fatalf("unexpected error. %v", err)
} }
var gotPolicy *policy.Policy var gotPolicy *policy.BucketPolicy
gotPolicy, err = policy.ParseConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName) gotPolicy, err = policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName)
if err != nil { if err != nil {
t.Fatalf("unexpected error. %v", err) t.Fatalf("unexpected error. %v", err)
} }
@@ -530,11 +530,11 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
} }
if recV2.Code == http.StatusOK { if recV2.Code == http.StatusOK {
// Verify whether the bucket policy fetched is same as the one inserted. // Verify whether the bucket policy fetched is same as the one inserted.
expectedPolicy, err := policy.ParseConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName) expectedPolicy, err := policy.ParseBucketPolicyConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName)
if err != nil { if err != nil {
t.Fatalf("unexpected error. %v", err) t.Fatalf("unexpected error. %v", err)
} }
gotPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName) gotPolicy, err := policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName)
if err != nil { if err != nil {
t.Fatalf("unexpected error. %v", err) t.Fatalf("unexpected error. %v", err)
} }
+7 -7
View File
@@ -32,20 +32,20 @@ import (
"github.com/minio/minio/internal/handlers" "github.com/minio/minio/internal/handlers"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
// PolicySys - policy subsystem. // PolicySys - policy subsystem.
type PolicySys struct{} type PolicySys struct{}
// Get returns stored bucket policy // Get returns stored bucket policy
func (sys *PolicySys) Get(bucket string) (*policy.Policy, error) { func (sys *PolicySys) Get(bucket string) (*policy.BucketPolicy, error) {
policy, _, err := globalBucketMetadataSys.GetPolicyConfig(bucket) policy, _, err := globalBucketMetadataSys.GetPolicyConfig(bucket)
return policy, err return policy, err
} }
// IsAllowed - checks given policy args is allowed to continue the Rest API. // IsAllowed - checks given policy args is allowed to continue the Rest API.
func (sys *PolicySys) IsAllowed(args policy.Args) bool { func (sys *PolicySys) IsAllowed(args policy.BucketPolicyArgs) bool {
p, err := sys.Get(args.BucketName) p, err := sys.Get(args.BucketName)
if err == nil { if err == nil {
return p.IsAllowed(args) return p.IsAllowed(args)
@@ -236,7 +236,7 @@ func getConditionValues(r *http.Request, lc string, cred auth.Credentials) map[s
} }
// PolicyToBucketAccessPolicy converts a MinIO policy into a minio-go policy data structure. // PolicyToBucketAccessPolicy converts a MinIO policy into a minio-go policy data structure.
func PolicyToBucketAccessPolicy(bucketPolicy *policy.Policy) (*miniogopolicy.BucketAccessPolicy, error) { func PolicyToBucketAccessPolicy(bucketPolicy *policy.BucketPolicy) (*miniogopolicy.BucketAccessPolicy, error) {
// Return empty BucketAccessPolicy for empty bucket policy. // Return empty BucketAccessPolicy for empty bucket policy.
if bucketPolicy == nil { if bucketPolicy == nil {
return &miniogopolicy.BucketAccessPolicy{Version: policy.DefaultVersion}, nil return &miniogopolicy.BucketAccessPolicy{Version: policy.DefaultVersion}, nil
@@ -258,15 +258,15 @@ func PolicyToBucketAccessPolicy(bucketPolicy *policy.Policy) (*miniogopolicy.Buc
return &policyInfo, nil return &policyInfo, nil
} }
// BucketAccessPolicyToPolicy - converts minio-go/policy.BucketAccessPolicy to policy.Policy. // BucketAccessPolicyToPolicy - converts minio-go/policy.BucketAccessPolicy to policy.BucketPolicy.
func BucketAccessPolicyToPolicy(policyInfo *miniogopolicy.BucketAccessPolicy) (*policy.Policy, error) { func BucketAccessPolicyToPolicy(policyInfo *miniogopolicy.BucketAccessPolicy) (*policy.BucketPolicy, error) {
data, err := json.Marshal(policyInfo) data, err := json.Marshal(policyInfo)
if err != nil { if err != nil {
// This should not happen because policyInfo is valid to convert to JSON data. // This should not happen because policyInfo is valid to convert to JSON data.
return nil, err return nil, err
} }
var bucketPolicy policy.Policy var bucketPolicy policy.BucketPolicy
json := jsoniter.ConfigCompatibleWithStandardLibrary json := jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(data, &bucketPolicy); err != nil { if err = json.Unmarshal(data, &bucketPolicy); err != nil {
// This should not happen because data is valid to JSON data. // This should not happen because data is valid to JSON data.
+29 -20
View File
@@ -29,14 +29,12 @@ import (
) )
// BucketQuotaSys - map of bucket and quota configuration. // BucketQuotaSys - map of bucket and quota configuration.
type BucketQuotaSys struct { type BucketQuotaSys struct{}
bucketStorageCache timedValue
}
// Get - Get quota configuration. // Get - Get quota configuration.
func (sys *BucketQuotaSys) Get(ctx context.Context, bucketName string) (*madmin.BucketQuota, error) { func (sys *BucketQuotaSys) Get(ctx context.Context, bucketName string) (*madmin.BucketQuota, error) {
qCfg, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName) cfg, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName)
return qCfg, err return cfg, err
} }
// NewBucketQuotaSys returns initialized BucketQuotaSys // NewBucketQuotaSys returns initialized BucketQuotaSys
@@ -44,16 +42,18 @@ func NewBucketQuotaSys() *BucketQuotaSys {
return &BucketQuotaSys{} return &BucketQuotaSys{}
} }
var bucketStorageCache timedValue
// Init initialize bucket quota. // Init initialize bucket quota.
func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) { func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
sys.bucketStorageCache.Once.Do(func() { bucketStorageCache.Once.Do(func() {
// Set this to 10 secs since its enough, as scanner // Set this to 10 secs since its enough, as scanner
// does not update the bucket usage values frequently. // does not update the bucket usage values frequently.
sys.bucketStorageCache.TTL = 10 * time.Second bucketStorageCache.TTL = 10 * time.Second
// Rely on older value if usage loading fails from disk. // Rely on older value if usage loading fails from disk.
sys.bucketStorageCache.Relax = true bucketStorageCache.Relax = true
sys.bucketStorageCache.Update = func() (interface{}, error) { bucketStorageCache.Update = func() (interface{}, error) {
ctx, done := context.WithTimeout(context.Background(), 1*time.Second) ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
defer done() defer done()
return loadDataUsageFromBackend(ctx, objAPI) return loadDataUsageFromBackend(ctx, objAPI)
@@ -63,16 +63,17 @@ func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
// GetBucketUsageInfo return bucket usage info for a given bucket // GetBucketUsageInfo return bucket usage info for a given bucket
func (sys *BucketQuotaSys) GetBucketUsageInfo(bucket string) (BucketUsageInfo, error) { func (sys *BucketQuotaSys) GetBucketUsageInfo(bucket string) (BucketUsageInfo, error) {
v, err := sys.bucketStorageCache.Get() v, err := bucketStorageCache.Get()
if err != nil && v != nil { timedout := OperationTimedOut{}
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to retrieve usage information for bucket: %s, relying on older value cached in-memory: err(%v)", bucket, err), "bucket-usage-cache-"+bucket) if err != nil && !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &timedout) {
} if v != nil {
if v == nil { logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to retrieve usage information for bucket: %s, relying on older value cached in-memory: err(%v)", bucket, err), "bucket-usage-cache-"+bucket)
logger.LogOnceIf(GlobalContext, errors.New("unable to retrieve usage information for bucket: %s, no reliable usage value available - quota will not be enforced"), "bucket-usage-empty-"+bucket) } else {
logger.LogOnceIf(GlobalContext, errors.New("unable to retrieve usage information for bucket: %s, no reliable usage value available - quota will not be enforced"), "bucket-usage-empty-"+bucket)
}
} }
var bui BucketUsageInfo var bui BucketUsageInfo
dui, ok := v.(DataUsageInfo) dui, ok := v.(DataUsageInfo)
if ok { if ok {
bui = dui.BucketsUsage[bucket] bui = dui.BucketsUsage[bucket]
@@ -107,8 +108,16 @@ func (sys *BucketQuotaSys) enforceQuotaHard(ctx context.Context, bucket string,
return err return err
} }
if q != nil && q.Type == madmin.HardQuota && q.Quota > 0 { var quotaSize uint64
if uint64(size) >= q.Quota { // check if file size already exceeds the quota if q != nil && q.Type == madmin.HardQuota {
if q.Size > 0 {
quotaSize = q.Size
} else if q.Quota > 0 {
quotaSize = q.Quota
}
}
if quotaSize > 0 {
if uint64(size) >= quotaSize { // check if file size already exceeds the quota
return BucketQuotaExceeded{Bucket: bucket} return BucketQuotaExceeded{Bucket: bucket}
} }
@@ -117,7 +126,7 @@ func (sys *BucketQuotaSys) enforceQuotaHard(ctx context.Context, bucket string,
return err return err
} }
if bui.Size > 0 && ((bui.Size + uint64(size)) >= q.Quota) { if bui.Size > 0 && ((bui.Size + uint64(size)) >= quotaSize) {
return BucketQuotaExceeded{Bucket: bucket} return BucketQuotaExceeded{Bucket: bucket}
} }
} }
+15 -25
View File
@@ -34,7 +34,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration. // PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
@@ -232,10 +232,10 @@ func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseW
enc := json.NewEncoder(w) enc := json.NewEncoder(w)
stats := globalReplicationStats.getLatestReplicationStats(bucket) stats := globalReplicationStats.getLatestReplicationStats(bucket)
bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket) bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket)
bwMap := bwRpt.BucketStats[bucket] bwMap := bwRpt.BucketStats
for arn, st := range stats.ReplicationStats.Stats { for arn, st := range stats.ReplicationStats.Stats {
if bwMap != nil { for opts, bw := range bwMap {
if bw, ok := bwMap[arn]; ok { if opts.ReplicationARN != "" && opts.ReplicationARN == arn {
st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond
st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond
stats.ReplicationStats.Stats[arn] = st stats.ReplicationStats.Stats[arn] = st
@@ -288,10 +288,10 @@ func (api objectAPIHandlers) GetBucketReplicationMetricsV2Handler(w http.Respons
enc := json.NewEncoder(w) enc := json.NewEncoder(w)
stats := globalReplicationStats.getLatestReplicationStats(bucket) stats := globalReplicationStats.getLatestReplicationStats(bucket)
bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket) bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket)
bwMap := bwRpt.BucketStats[bucket] bwMap := bwRpt.BucketStats
for arn, st := range stats.ReplicationStats.Stats { for arn, st := range stats.ReplicationStats.Stats {
if bwMap != nil { for opts, bw := range bwMap {
if bw, ok := bwMap[arn]; ok { if opts.ReplicationARN != "" && opts.ReplicationARN == arn {
st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond
st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond
stats.ReplicationStats.Stats[arn] = st stats.ReplicationStats.Stats[arn] = st
@@ -476,27 +476,17 @@ func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.Response
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
var tgtStats map[string]TargetReplicationResyncStatus brs, err := loadBucketResyncMetadata(ctx, bucket, objectAPI)
globalReplicationPool.resyncer.RLock() if err != nil {
brs, ok := globalReplicationPool.resyncer.statusMap[bucket] writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
if ok { Bucket: bucket,
tgtStats = brs.cloneTgtStats() Err: fmt.Errorf("replication resync status not available for %s (%s)", arn, err.Error()),
} }), r.URL)
globalReplicationPool.resyncer.RUnlock() return
if !ok {
brs, err = loadBucketResyncMetadata(ctx, bucket, objectAPI)
if err != nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
Bucket: bucket,
Err: fmt.Errorf("No replication resync status available for %s", arn),
}), r.URL)
return
}
tgtStats = brs.cloneTgtStats()
} }
var rinfo ResyncTargetsInfo var rinfo ResyncTargetsInfo
for tarn, st := range tgtStats { for tarn, st := range brs.TargetsMap {
if arn != "" && tarn != arn { if arn != "" && tarn != arn {
continue continue
} }
+65 -60
View File
@@ -31,8 +31,8 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/bucket/replication" "github.com/minio/minio/internal/bucket/replication"
"github.com/minio/minio/internal/crypto"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger"
) )
//go:generate msgp -file=$GOFILE //go:generate msgp -file=$GOFILE
@@ -167,7 +167,21 @@ func (ri replicatedInfos) Action() replicationAction {
var replStatusRegex = regexp.MustCompile(`([^=].*?)=([^,].*?);`) var replStatusRegex = regexp.MustCompile(`([^=].*?)=([^,].*?);`)
// TargetReplicationStatus - returns replication status of a target // TargetReplicationStatus - returns replication status of a target
func (o *ObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) { func (ri ReplicateObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) {
repStatMatches := replStatusRegex.FindAllStringSubmatch(ri.ReplicationStatusInternal, -1)
for _, repStatMatch := range repStatMatches {
if len(repStatMatch) != 3 {
return
}
if repStatMatch[1] == arn {
return replication.StatusType(repStatMatch[2])
}
}
return
}
// TargetReplicationStatus - returns replication status of a target
func (o ObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) {
repStatMatches := replStatusRegex.FindAllStringSubmatch(o.ReplicationStatusInternal, -1) repStatMatches := replStatusRegex.FindAllStringSubmatch(o.ReplicationStatusInternal, -1)
for _, repStatMatch := range repStatMatches { for _, repStatMatch := range repStatMatches {
if len(repStatMatch) != 3 { if len(repStatMatch) != 3 {
@@ -185,7 +199,6 @@ type replicateTargetDecision struct {
Synchronous bool // Synchronous replication configured. Synchronous bool // Synchronous replication configured.
Arn string // ARN of replication target Arn string // ARN of replication target
ID string ID string
Tgt *TargetClient
} }
func (t *replicateTargetDecision) String() string { func (t *replicateTargetDecision) String() string {
@@ -207,7 +220,7 @@ type ReplicateDecision struct {
} }
// ReplicateAny returns true if atleast one target qualifies for replication // ReplicateAny returns true if atleast one target qualifies for replication
func (d *ReplicateDecision) ReplicateAny() bool { func (d ReplicateDecision) ReplicateAny() bool {
for _, t := range d.targetsMap { for _, t := range d.targetsMap {
if t.Replicate { if t.Replicate {
return true return true
@@ -217,7 +230,7 @@ func (d *ReplicateDecision) ReplicateAny() bool {
} }
// Synchronous returns true if atleast one target qualifies for synchronous replication // Synchronous returns true if atleast one target qualifies for synchronous replication
func (d *ReplicateDecision) Synchronous() bool { func (d ReplicateDecision) Synchronous() bool {
for _, t := range d.targetsMap { for _, t := range d.targetsMap {
if t.Synchronous { if t.Synchronous {
return true return true
@@ -226,7 +239,7 @@ func (d *ReplicateDecision) Synchronous() bool {
return false return false
} }
func (d *ReplicateDecision) String() string { func (d ReplicateDecision) String() string {
b := new(bytes.Buffer) b := new(bytes.Buffer)
for key, value := range d.targetsMap { for key, value := range d.targetsMap {
fmt.Fprintf(b, "%s=%s,", key, value.String()) fmt.Fprintf(b, "%s=%s,", key, value.String())
@@ -243,7 +256,7 @@ func (d *ReplicateDecision) Set(t replicateTargetDecision) {
} }
// PendingStatus returns a stringified representation of internal replication status with all targets marked as `PENDING` // PendingStatus returns a stringified representation of internal replication status with all targets marked as `PENDING`
func (d *ReplicateDecision) PendingStatus() string { func (d ReplicateDecision) PendingStatus() string {
b := new(bytes.Buffer) b := new(bytes.Buffer)
for _, k := range d.targetsMap { for _, k := range d.targetsMap {
if k.Replicate { if k.Replicate {
@@ -259,11 +272,11 @@ type ResyncDecision struct {
} }
// Empty returns true if no targets with resync decision present // Empty returns true if no targets with resync decision present
func (r *ResyncDecision) Empty() bool { func (r ResyncDecision) Empty() bool {
return r.targets == nil return r.targets == nil
} }
func (r *ResyncDecision) mustResync() bool { func (r ResyncDecision) mustResync() bool {
for _, v := range r.targets { for _, v := range r.targets {
if v.Replicate { if v.Replicate {
return true return true
@@ -272,15 +285,12 @@ func (r *ResyncDecision) mustResync() bool {
return false return false
} }
func (r *ResyncDecision) mustResyncTarget(tgtArn string) bool { func (r ResyncDecision) mustResyncTarget(tgtArn string) bool {
if r.targets == nil { if r.targets == nil {
return false return false
} }
v, ok := r.targets[tgtArn] v, ok := r.targets[tgtArn]
if ok && v.Replicate { return ok && v.Replicate
return true
}
return false
} }
// ResyncTargetDecision is struct that represents resync decision for this target // ResyncTargetDecision is struct that represents resync decision for this target
@@ -301,35 +311,20 @@ func parseReplicateDecision(ctx context.Context, bucket, s string) (r ReplicateD
if len(s) == 0 { if len(s) == 0 {
return return
} }
pairs := strings.Split(s, ",") for _, p := range strings.Split(s, ",") {
for _, p := range pairs { if p == "" {
continue
}
slc := strings.Split(p, "=") slc := strings.Split(p, "=")
if len(slc) != 2 { if len(slc) != 2 {
return r, errInvalidReplicateDecisionFormat return r, errInvalidReplicateDecisionFormat
} }
tgtStr := strings.TrimPrefix(slc[1], "\"") tgtStr := strings.TrimSuffix(strings.TrimPrefix(slc[1], `"`), `"`)
tgtStr = strings.TrimSuffix(tgtStr, "\"")
tgt := strings.Split(tgtStr, ";") tgt := strings.Split(tgtStr, ";")
if len(tgt) != 4 { if len(tgt) != 4 {
return r, errInvalidReplicateDecisionFormat return r, errInvalidReplicateDecisionFormat
} }
var replicate, sync bool r.targetsMap[slc[0]] = replicateTargetDecision{Replicate: tgt[0] == "true", Synchronous: tgt[1] == "true", Arn: tgt[2], ID: tgt[3]}
var err error
replicate, err = strconv.ParseBool(tgt[0])
if err != nil {
return r, err
}
sync, err = strconv.ParseBool(tgt[1])
if err != nil {
return r, err
}
tgtClnt := globalBucketTargetSys.GetRemoteTargetClient(slc[0])
if tgtClnt == nil {
// Skip stale targets if any and log them to be missing atleast once.
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, slc[0]), slc[0])
// We save the targetDecision even when its not configured or stale.
}
r.targetsMap[slc[0]] = replicateTargetDecision{Replicate: replicate, Synchronous: sync, Arn: tgt[2], ID: tgt[3], Tgt: tgtClnt}
} }
return return
} }
@@ -496,8 +491,8 @@ func getCompositeVersionPurgeStatus(m map[string]VersionPurgeStatusType) Version
} }
// getHealReplicateObjectInfo returns info needed by heal replication in ReplicateObjectInfo // getHealReplicateObjectInfo returns info needed by heal replication in ReplicateObjectInfo
func getHealReplicateObjectInfo(objInfo ObjectInfo, rcfg replicationConfig) ReplicateObjectInfo { func getHealReplicateObjectInfo(oi ObjectInfo, rcfg replicationConfig) ReplicateObjectInfo {
oi := objInfo.Clone() userDefined := cloneMSS(oi.UserDefined)
if rcfg.Config != nil && rcfg.Config.RoleArn != "" { if rcfg.Config != nil && rcfg.Config.RoleArn != "" {
// For backward compatibility of objects pending/failed replication. // For backward compatibility of objects pending/failed replication.
// Save replication related statuses in the new internal representation for // Save replication related statuses in the new internal representation for
@@ -508,17 +503,15 @@ func getHealReplicateObjectInfo(objInfo ObjectInfo, rcfg replicationConfig) Repl
if !oi.VersionPurgeStatus.Empty() { if !oi.VersionPurgeStatus.Empty() {
oi.VersionPurgeStatusInternal = fmt.Sprintf("%s=%s;", rcfg.Config.RoleArn, oi.VersionPurgeStatus) oi.VersionPurgeStatusInternal = fmt.Sprintf("%s=%s;", rcfg.Config.RoleArn, oi.VersionPurgeStatus)
} }
for k, v := range oi.UserDefined { for k, v := range userDefined {
if strings.EqualFold(k, ReservedMetadataPrefixLower+ReplicationReset) { if strings.EqualFold(k, ReservedMetadataPrefixLower+ReplicationReset) {
delete(oi.UserDefined, k) delete(userDefined, k)
oi.UserDefined[targetResetHeader(rcfg.Config.RoleArn)] = v userDefined[targetResetHeader(rcfg.Config.RoleArn)] = v
} }
} }
} }
var dsc ReplicateDecision
var tgtStatuses map[string]replication.StatusType
var purgeStatuses map[string]VersionPurgeStatusType
var dsc ReplicateDecision
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() { if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
dsc = checkReplicateDelete(GlobalContext, oi.Bucket, ObjectToDelete{ dsc = checkReplicateDelete(GlobalContext, oi.Bucket, ObjectToDelete{
ObjectV: ObjectV{ ObjectV: ObjectV{
@@ -530,33 +523,45 @@ func getHealReplicateObjectInfo(objInfo ObjectInfo, rcfg replicationConfig) Repl
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name), VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
}, nil) }, nil)
} else { } else {
dsc = mustReplicate(GlobalContext, oi.Bucket, oi.Name, getMustReplicateOptions(ObjectInfo{ dsc = mustReplicate(GlobalContext, oi.Bucket, oi.Name, getMustReplicateOptions(userDefined, oi.UserTags, "", replication.HealReplicationType, ObjectOptions{}))
UserDefined: oi.UserDefined,
}, replication.HealReplicationType, ObjectOptions{}))
} }
tgtStatuses = replicationStatusesMap(oi.ReplicationStatusInternal)
purgeStatuses = versionPurgeStatusesMap(oi.VersionPurgeStatusInternal) tgtStatuses := replicationStatusesMap(oi.ReplicationStatusInternal)
existingObjResync := rcfg.Resync(GlobalContext, oi, &dsc, tgtStatuses) purgeStatuses := versionPurgeStatusesMap(oi.VersionPurgeStatusInternal)
tm, _ := time.Parse(time.RFC3339Nano, oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp]) existingObjResync := rcfg.Resync(GlobalContext, oi, dsc, tgtStatuses)
tm, _ := time.Parse(time.RFC3339Nano, userDefined[ReservedMetadataPrefixLower+ReplicationTimestamp])
rstate := oi.ReplicationState()
rstate.ReplicateDecisionStr = dsc.String()
asz, _ := oi.GetActualSize()
return ReplicateObjectInfo{ return ReplicateObjectInfo{
ObjectInfo: oi, Name: oi.Name,
Size: oi.Size,
ActualSize: asz,
Bucket: oi.Bucket,
VersionID: oi.VersionID,
ETag: oi.ETag,
ModTime: oi.ModTime,
ReplicationStatus: oi.ReplicationStatus,
ReplicationStatusInternal: oi.ReplicationStatusInternal,
DeleteMarker: oi.DeleteMarker,
VersionPurgeStatusInternal: oi.VersionPurgeStatusInternal,
VersionPurgeStatus: oi.VersionPurgeStatus,
ReplicationState: rstate,
OpType: replication.HealReplicationType, OpType: replication.HealReplicationType,
Dsc: dsc, Dsc: dsc,
ExistingObjResync: existingObjResync, ExistingObjResync: existingObjResync,
TargetStatuses: tgtStatuses, TargetStatuses: tgtStatuses,
TargetPurgeStatuses: purgeStatuses, TargetPurgeStatuses: purgeStatuses,
ReplicationTimestamp: tm, ReplicationTimestamp: tm,
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
UserTags: oi.UserTags,
} }
} }
func (ri *ReplicateObjectInfo) getReplicationState() ReplicationState { // ReplicationState - returns replication state using other internal replication metadata in ObjectInfo
rs := ri.ObjectInfo.getReplicationState() func (o ObjectInfo) ReplicationState() ReplicationState {
rs.ReplicateDecisionStr = ri.Dsc.String()
return rs
}
// vID here represents the versionID client specified in request - need to distinguish between delete marker and delete marker deletion
func (o *ObjectInfo) getReplicationState() ReplicationState {
rs := ReplicationState{ rs := ReplicationState{
ReplicationStatusInternal: o.ReplicationStatusInternal, ReplicationStatusInternal: o.ReplicationStatusInternal,
VersionPurgeStatusInternal: o.VersionPurgeStatusInternal, VersionPurgeStatusInternal: o.VersionPurgeStatusInternal,
@@ -577,7 +582,7 @@ func (o *ObjectInfo) getReplicationState() ReplicationState {
} }
// ReplicationState returns replication state using other internal replication metadata in ObjectToDelete // ReplicationState returns replication state using other internal replication metadata in ObjectToDelete
func (o *ObjectToDelete) ReplicationState() ReplicationState { func (o ObjectToDelete) ReplicationState() ReplicationState {
r := ReplicationState{ r := ReplicationState{
ReplicationStatusInternal: o.DeleteMarkerReplicationStatus, ReplicationStatusInternal: o.DeleteMarkerReplicationStatus,
VersionPurgeStatusInternal: o.VersionPurgeStatuses, VersionPurgeStatusInternal: o.VersionPurgeStatuses,
+328 -259
View File
@@ -49,6 +49,7 @@ import (
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/tinylib/msgp/msgp"
"github.com/zeebo/xxh3" "github.com/zeebo/xxh3"
) )
@@ -223,21 +224,19 @@ func (o mustReplicateOptions) isMetadataReplication() bool {
return o.opType == replication.MetadataReplicationType return o.opType == replication.MetadataReplicationType
} }
func getMustReplicateOptions(o ObjectInfo, op replication.Type, opts ObjectOptions) mustReplicateOptions { func (o ObjectInfo) getMustReplicateOptions(op replication.Type, opts ObjectOptions) mustReplicateOptions {
if !op.Valid() { return getMustReplicateOptions(o.UserDefined, o.UserTags, o.ReplicationStatus, op, opts)
op = replication.ObjectReplicationType }
if o.metadataOnly {
op = replication.MetadataReplicationType func getMustReplicateOptions(userDefined map[string]string, userTags string, status replication.StatusType, op replication.Type, opts ObjectOptions) mustReplicateOptions {
} meta := cloneMSS(userDefined)
} if userTags != "" {
meta := cloneMSS(o.UserDefined) meta[xhttp.AmzObjectTagging] = userTags
if o.UserTags != "" {
meta[xhttp.AmzObjectTagging] = o.UserTags
} }
return mustReplicateOptions{ return mustReplicateOptions{
meta: meta, meta: meta,
status: o.ReplicationStatus, status: status,
opType: op, opType: op,
replicationRequest: opts.ReplicationRequest, replicationRequest: opts.ReplicationRequest,
} }
@@ -355,40 +354,41 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
OpType: replication.DeleteReplicationType, OpType: replication.DeleteReplicationType,
} }
tgtArns := rcfg.FilterTargetArns(opts) tgtArns := rcfg.FilterTargetArns(opts)
if len(tgtArns) > 0 { dsc.targetsMap = make(map[string]replicateTargetDecision, len(tgtArns))
dsc.targetsMap = make(map[string]replicateTargetDecision, len(tgtArns)) if len(tgtArns) == 0 {
var sync, replicate bool return dsc
for _, tgtArn := range tgtArns { }
opts.TargetArn = tgtArn var sync, replicate bool
replicate = rcfg.Replicate(opts) for _, tgtArn := range tgtArns {
// when incoming delete is removal of a delete marker(a.k.a versioned delete), opts.TargetArn = tgtArn
// GetObjectInfo returns extra information even though it returns errFileNotFound replicate = rcfg.Replicate(opts)
if gerr != nil { // when incoming delete is removal of a delete marker(a.k.a versioned delete),
validReplStatus := false // GetObjectInfo returns extra information even though it returns errFileNotFound
switch oi.TargetReplicationStatus(tgtArn) { if gerr != nil {
case replication.Pending, replication.Completed, replication.Failed: validReplStatus := false
validReplStatus = true switch oi.TargetReplicationStatus(tgtArn) {
} case replication.Pending, replication.Completed, replication.Failed:
if oi.DeleteMarker && (validReplStatus || replicate) { validReplStatus = true
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
continue
} else {
// can be the case that other cluster is down and duplicate `mc rm --vid`
// is issued - this still needs to be replicated back to the other target
replicate = oi.VersionPurgeStatus == Pending || oi.VersionPurgeStatus == Failed
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
continue
}
} }
tgt := globalBucketTargetSys.GetRemoteTargetClient(tgtArn) if oi.DeleteMarker && (validReplStatus || replicate) {
// the target online status should not be used here while deciding dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
// whether to replicate deletes as the target could be temporarily down continue
tgtDsc := newReplicateTargetDecision(tgtArn, false, false) } else {
if tgt != nil { // can be the case that other cluster is down and duplicate `mc rm --vid`
tgtDsc = newReplicateTargetDecision(tgtArn, replicate, tgt.replicateSync) // is issued - this still needs to be replicated back to the other target
replicate = oi.VersionPurgeStatus == Pending || oi.VersionPurgeStatus == Failed
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
continue
} }
dsc.Set(tgtDsc)
} }
tgt := globalBucketTargetSys.GetRemoteTargetClient(tgtArn)
// the target online status should not be used here while deciding
// whether to replicate deletes as the target could be temporarily down
tgtDsc := newReplicateTargetDecision(tgtArn, false, false)
if tgt != nil {
tgtDsc = newReplicateTargetDecision(tgtArn, replicate, tgt.replicateSync)
}
dsc.Set(tgtDsc)
} }
return dsc return dsc
} }
@@ -482,15 +482,10 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectReplicationInfo, obj
ctx = lkctx.Context() ctx = lkctx.Context()
defer lk.Unlock(lkctx) defer lk.Unlock(lkctx)
rinfos := replicatedInfos{Targets: make([]replicatedTargetInfo, 0, len(dsc.targetsMap))}
var wg sync.WaitGroup var wg sync.WaitGroup
var rinfos replicatedInfos var mu sync.Mutex
rinfos.Targets = make([]replicatedTargetInfo, len(dsc.targetsMap))
idx := -1
for _, tgtEntry := range dsc.targetsMap { for _, tgtEntry := range dsc.targetsMap {
idx++
if tgtEntry.Tgt == nil {
continue
}
if !tgtEntry.Replicate { if !tgtEntry.Replicate {
continue continue
} }
@@ -498,11 +493,33 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectReplicationInfo, obj
if dobj.TargetArn != "" && dobj.TargetArn != tgtEntry.Arn { if dobj.TargetArn != "" && dobj.TargetArn != tgtEntry.Arn {
continue continue
} }
tgtClnt := globalBucketTargetSys.GetRemoteTargetClient(tgtEntry.Arn)
if tgtClnt == nil {
// Skip stale targets if any and log them to be missing atleast once.
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtEntry.Arn), tgtEntry.Arn)
sendEvent(eventArgs{
EventName: event.ObjectReplicationNotTracked,
BucketName: bucket,
Object: ObjectInfo{
Bucket: bucket,
Name: dobj.ObjectName,
VersionID: versionID,
DeleteMarker: dobj.DeleteMarker,
},
UserAgent: "Internal: [Replication]",
Host: globalLocalNodeName,
})
continue
}
wg.Add(1) wg.Add(1)
go func(index int, tgt *TargetClient) { go func(tgt *TargetClient) {
defer wg.Done() defer wg.Done()
rinfos.Targets[index] = replicateDeleteToTarget(ctx, dobj, tgt) tgtInfo := replicateDeleteToTarget(ctx, dobj, tgt)
}(idx, tgtEntry.Tgt)
mu.Lock()
rinfos.Targets = append(rinfos.Targets, tgtInfo)
mu.Unlock()
}(tgtClnt)
} }
wg.Wait() wg.Wait()
@@ -702,10 +719,8 @@ func getCopyObjMetadata(oi ObjectInfo, sc string) map[string]string {
meta[xhttp.ContentType] = oi.ContentType meta[xhttp.ContentType] = oi.ContentType
} }
if oi.UserTags != "" { meta[xhttp.AmzObjectTagging] = oi.UserTags
meta[xhttp.AmzObjectTagging] = oi.UserTags meta[xhttp.AmzTagDirective] = "REPLACE"
meta[xhttp.AmzTagDirective] = "REPLACE"
}
if sc == "" { if sc == "" {
sc = oi.StorageClass sc = oi.StorageClass
@@ -887,7 +902,7 @@ func getReplicationAction(oi1 ObjectInfo, oi2 minio.ObjectInfo, opType replicati
} }
t, _ := tags.ParseObjectTags(oi1.UserTags) t, _ := tags.ParseObjectTags(oi1.UserTags)
if !reflect.DeepEqual(oi2.UserTags, t.ToMap()) || (oi2.UserTagCount != len(t.ToMap())) { if (oi2.UserTagCount > 0 && !reflect.DeepEqual(oi2.UserTags, t.ToMap())) || (oi2.UserTagCount != len(t.ToMap())) {
return replicateMetadata return replicateMetadata
} }
@@ -964,9 +979,8 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
}) })
}() }()
objInfo := ri.ObjectInfo bucket := ri.Bucket
bucket := objInfo.Bucket object := ri.Name
object := objInfo.Name
cfg, err := getReplicationConfig(ctx, bucket) cfg, err := getReplicationConfig(ctx, bucket)
if err != nil { if err != nil {
@@ -974,7 +988,7 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
sendEvent(eventArgs{ sendEvent(eventArgs{
EventName: event.ObjectReplicationNotTracked, EventName: event.ObjectReplicationNotTracked,
BucketName: bucket, BucketName: bucket,
Object: objInfo, Object: ri.ToObjectInfo(),
UserAgent: "Internal: [Replication]", UserAgent: "Internal: [Replication]",
Host: globalLocalNodeName, Host: globalLocalNodeName,
}) })
@@ -982,8 +996,8 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
} }
tgtArns := cfg.FilterTargetArns(replication.ObjectOpts{ tgtArns := cfg.FilterTargetArns(replication.ObjectOpts{
Name: object, Name: object,
SSEC: crypto.SSEC.IsEncrypted(objInfo.UserDefined), SSEC: ri.SSEC,
UserTags: objInfo.UserTags, UserTags: ri.UserTags,
}) })
// Lock the object name before starting replication. // Lock the object name before starting replication.
// Use separate lock that doesn't collide with regular objects. // Use separate lock that doesn't collide with regular objects.
@@ -993,7 +1007,7 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
sendEvent(eventArgs{ sendEvent(eventArgs{
EventName: event.ObjectReplicationNotTracked, EventName: event.ObjectReplicationNotTracked,
BucketName: bucket, BucketName: bucket,
Object: objInfo, Object: ri.ToObjectInfo(),
UserAgent: "Internal: [Replication]", UserAgent: "Internal: [Replication]",
Host: globalLocalNodeName, Host: globalLocalNodeName,
}) })
@@ -1003,32 +1017,38 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
ctx = lkctx.Context() ctx = lkctx.Context()
defer lk.Unlock(lkctx) defer lk.Unlock(lkctx)
rinfos := replicatedInfos{Targets: make([]replicatedTargetInfo, 0, len(tgtArns))}
var wg sync.WaitGroup var wg sync.WaitGroup
var rinfos replicatedInfos var mu sync.Mutex
rinfos.Targets = make([]replicatedTargetInfo, len(tgtArns)) for _, tgtArn := range tgtArns {
for i, tgtArn := range tgtArns {
tgt := globalBucketTargetSys.GetRemoteTargetClient(tgtArn) tgt := globalBucketTargetSys.GetRemoteTargetClient(tgtArn)
if tgt == nil { if tgt == nil {
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtArn), tgtArn) logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtArn), tgtArn)
sendEvent(eventArgs{ sendEvent(eventArgs{
EventName: event.ObjectReplicationNotTracked, EventName: event.ObjectReplicationNotTracked,
BucketName: bucket, BucketName: bucket,
Object: objInfo, Object: ri.ToObjectInfo(),
UserAgent: "Internal: [Replication]", UserAgent: "Internal: [Replication]",
Host: globalLocalNodeName, Host: globalLocalNodeName,
}) })
continue continue
} }
wg.Add(1) wg.Add(1)
go func(index int, tgt *TargetClient) { go func(tgt *TargetClient) {
defer wg.Done() defer wg.Done()
var tgtInfo replicatedTargetInfo
if ri.OpType == replication.ObjectReplicationType { if ri.OpType == replication.ObjectReplicationType {
// all incoming calls go through optimized path. // all incoming calls go through optimized path.
rinfos.Targets[index] = ri.replicateObject(ctx, objectAPI, tgt) tgtInfo = ri.replicateObject(ctx, objectAPI, tgt)
} else { } else {
rinfos.Targets[index] = ri.replicateAll(ctx, objectAPI, tgt) tgtInfo = ri.replicateAll(ctx, objectAPI, tgt)
} }
}(i, tgt)
mu.Lock()
rinfos.Targets = append(rinfos.Targets, tgtInfo)
mu.Unlock()
}(tgt)
} }
wg.Wait() wg.Wait()
@@ -1043,10 +1063,11 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
newReplStatusInternal := rinfos.ReplicationStatusInternal() newReplStatusInternal := rinfos.ReplicationStatusInternal()
// Note that internal replication status(es) may match for previously replicated objects - in such cases // Note that internal replication status(es) may match for previously replicated objects - in such cases
// metadata should be updated with last resync timestamp. // metadata should be updated with last resync timestamp.
if objInfo.ReplicationStatusInternal != newReplStatusInternal || rinfos.ReplicationResynced() { objInfo := ri.ToObjectInfo()
if ri.ReplicationStatusInternal != newReplStatusInternal || rinfos.ReplicationResynced() {
popts := ObjectOptions{ popts := ObjectOptions{
MTime: objInfo.ModTime, MTime: ri.ModTime,
VersionID: objInfo.VersionID, VersionID: ri.VersionID,
EvalMetadataFn: func(oi *ObjectInfo, gerr error) (dsc ReplicateDecision, err error) { EvalMetadataFn: func(oi *ObjectInfo, gerr error) (dsc ReplicateDecision, err error) {
oi.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = newReplStatusInternal oi.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = newReplStatusInternal
oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano) oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
@@ -1056,14 +1077,18 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
oi.UserDefined[targetResetHeader(rinfo.Arn)] = rinfo.ResyncTimestamp oi.UserDefined[targetResetHeader(rinfo.Arn)] = rinfo.ResyncTimestamp
} }
} }
if objInfo.UserTags != "" { if ri.UserTags != "" {
oi.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags oi.UserDefined[xhttp.AmzObjectTagging] = ri.UserTags
} }
return dsc, nil return dsc, nil
}, },
} }
_, _ = objectAPI.PutObjectMetadata(ctx, bucket, object, popts) uobjInfo, _ := objectAPI.PutObjectMetadata(ctx, bucket, object, popts)
if uobjInfo.Name != "" {
objInfo = uobjInfo
}
opType := replication.MetadataReplicationType opType := replication.MetadataReplicationType
if rinfos.Action() == replicateAll { if rinfos.Action() == replicateAll {
opType = replication.ObjectReplicationType opType = replication.ObjectReplicationType
@@ -1099,23 +1124,21 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
// The source object is then updated to reflect the replication status. // The source object is then updated to reflect the replication status.
func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI ObjectLayer, tgt *TargetClient) (rinfo replicatedTargetInfo) { func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI ObjectLayer, tgt *TargetClient) (rinfo replicatedTargetInfo) {
startTime := time.Now() startTime := time.Now()
objInfo := ri.ObjectInfo.Clone() bucket := ri.Bucket
bucket := objInfo.Bucket object := ri.Name
object := objInfo.Name
sz, _ := objInfo.GetActualSize()
rAction := replicateAll rAction := replicateAll
rinfo = replicatedTargetInfo{ rinfo = replicatedTargetInfo{
Size: sz, Size: ri.ActualSize,
Arn: tgt.ARN, Arn: tgt.ARN,
PrevReplicationStatus: objInfo.TargetReplicationStatus(tgt.ARN), PrevReplicationStatus: ri.TargetReplicationStatus(tgt.ARN),
ReplicationStatus: replication.Failed, ReplicationStatus: replication.Failed,
OpType: ri.OpType, OpType: ri.OpType,
ReplicationAction: rAction, ReplicationAction: rAction,
endpoint: tgt.EndpointURL().Host, endpoint: tgt.EndpointURL().Host,
secure: tgt.EndpointURL().Scheme == "https", secure: tgt.EndpointURL().Scheme == "https",
} }
if ri.ObjectInfo.TargetReplicationStatus(tgt.ARN) == replication.Completed && !ri.ExistingObjResync.Empty() && !ri.ExistingObjResync.mustResyncTarget(tgt.ARN) { if ri.TargetReplicationStatus(tgt.ARN) == replication.Completed && !ri.ExistingObjResync.Empty() && !ri.ExistingObjResync.mustResyncTarget(tgt.ARN) {
rinfo.ReplicationStatus = replication.Completed rinfo.ReplicationStatus = replication.Completed
rinfo.ReplicationResynced = true rinfo.ReplicationResynced = true
return return
@@ -1126,7 +1149,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
sendEvent(eventArgs{ sendEvent(eventArgs{
EventName: event.ObjectReplicationNotTracked, EventName: event.ObjectReplicationNotTracked,
BucketName: bucket, BucketName: bucket,
Object: objInfo, Object: ri.ToObjectInfo(),
UserAgent: "Internal: [Replication]", UserAgent: "Internal: [Replication]",
Host: globalLocalNodeName, Host: globalLocalNodeName,
}) })
@@ -1137,12 +1160,13 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object) versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{ gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
VersionID: objInfo.VersionID, VersionID: ri.VersionID,
Versioned: versioned, Versioned: versioned,
VersionSuspended: versionSuspended, VersionSuspended: versionSuspended,
}) })
if err != nil { if err != nil {
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) { if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
objInfo := ri.ToObjectInfo()
sendEvent(eventArgs{ sendEvent(eventArgs{
EventName: event.ObjectReplicationNotTracked, EventName: event.ObjectReplicationNotTracked,
BucketName: bucket, BucketName: bucket,
@@ -1156,7 +1180,8 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
} }
defer gr.Close() defer gr.Close()
objInfo = gr.ObjInfo objInfo := gr.ObjInfo
// make sure we have the latest metadata for metrics calculation // make sure we have the latest metadata for metrics calculation
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN) rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
@@ -1217,8 +1242,10 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
} }
opts := &bandwidth.MonitorReaderOptions{ opts := &bandwidth.MonitorReaderOptions{
Bucket: objInfo.Bucket, BucketOptions: bandwidth.BucketOptions{
TargetARN: tgt.ARN, Name: ri.Bucket,
ReplicationARN: tgt.ARN,
},
HeaderSize: headerSize, HeaderSize: headerSize,
} }
newCtx := ctx newCtx := ctx
@@ -1255,10 +1282,8 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
// The source object is then updated to reflect the replication status. // The source object is then updated to reflect the replication status.
func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI ObjectLayer, tgt *TargetClient) (rinfo replicatedTargetInfo) { func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI ObjectLayer, tgt *TargetClient) (rinfo replicatedTargetInfo) {
startTime := time.Now() startTime := time.Now()
objInfo := ri.ObjectInfo.Clone() bucket := ri.Bucket
bucket := objInfo.Bucket object := ri.Name
object := objInfo.Name
sz, _ := objInfo.GetActualSize()
// set defaults for replication action based on operation being performed - actual // set defaults for replication action based on operation being performed - actual
// replication action can only be determined after stat on remote. This default is // replication action can only be determined after stat on remote. This default is
@@ -1266,9 +1291,9 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
rAction := replicateMetadata rAction := replicateMetadata
rinfo = replicatedTargetInfo{ rinfo = replicatedTargetInfo{
Size: sz, Size: ri.ActualSize,
Arn: tgt.ARN, Arn: tgt.ARN,
PrevReplicationStatus: objInfo.TargetReplicationStatus(tgt.ARN), PrevReplicationStatus: ri.TargetReplicationStatus(tgt.ARN),
ReplicationStatus: replication.Failed, ReplicationStatus: replication.Failed,
OpType: ri.OpType, OpType: ri.OpType,
ReplicationAction: rAction, ReplicationAction: rAction,
@@ -1281,7 +1306,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
sendEvent(eventArgs{ sendEvent(eventArgs{
EventName: event.ObjectReplicationNotTracked, EventName: event.ObjectReplicationNotTracked,
BucketName: bucket, BucketName: bucket,
Object: objInfo, Object: ri.ToObjectInfo(),
UserAgent: "Internal: [Replication]", UserAgent: "Internal: [Replication]",
Host: globalLocalNodeName, Host: globalLocalNodeName,
}) })
@@ -1292,12 +1317,13 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object) versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{ gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
VersionID: objInfo.VersionID, VersionID: ri.VersionID,
Versioned: versioned, Versioned: versioned,
VersionSuspended: versionSuspended, VersionSuspended: versionSuspended,
}) })
if err != nil { if err != nil {
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) { if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
objInfo := ri.ToObjectInfo()
sendEvent(eventArgs{ sendEvent(eventArgs{
EventName: event.ObjectReplicationNotTracked, EventName: event.ObjectReplicationNotTracked,
BucketName: bucket, BucketName: bucket,
@@ -1311,7 +1337,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
} }
defer gr.Close() defer gr.Close()
objInfo = gr.ObjInfo objInfo := gr.ObjInfo
// make sure we have the latest metadata for metrics calculation // make sure we have the latest metadata for metrics calculation
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN) rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
@@ -1378,7 +1404,9 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
} }
// object with same VersionID already exists, replication kicked off by // object with same VersionID already exists, replication kicked off by
// PutObject might have completed // PutObject might have completed
if objInfo.TargetReplicationStatus(tgt.ARN) == replication.Pending || objInfo.TargetReplicationStatus(tgt.ARN) == replication.Failed || ri.OpType == replication.ExistingObjectReplicationType { if objInfo.TargetReplicationStatus(tgt.ARN) == replication.Pending ||
objInfo.TargetReplicationStatus(tgt.ARN) == replication.Failed ||
ri.OpType == replication.ExistingObjectReplicationType {
// if metadata is not updated for some reason after replication, such as // if metadata is not updated for some reason after replication, such as
// 503 encountered while updating metadata - make sure to set ReplicationStatus // 503 encountered while updating metadata - make sure to set ReplicationStatus
// as Completed. // as Completed.
@@ -1456,8 +1484,10 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
} }
opts := &bandwidth.MonitorReaderOptions{ opts := &bandwidth.MonitorReaderOptions{
Bucket: objInfo.Bucket, BucketOptions: bandwidth.BucketOptions{
TargetARN: tgt.ARN, Name: objInfo.Bucket,
ReplicationARN: tgt.ARN,
},
HeaderSize: headerSize, HeaderSize: headerSize,
} }
newCtx := ctx newCtx := ctx
@@ -1498,9 +1528,18 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
var uploadedParts []minio.CompletePart var uploadedParts []minio.CompletePart
// new multipart must not set mtime as it may lead to erroneous cleanups at various intervals. // new multipart must not set mtime as it may lead to erroneous cleanups at various intervals.
opts.Internal.SourceMTime = time.Time{} // this value is saved properly in CompleteMultipartUpload() opts.Internal.SourceMTime = time.Time{} // this value is saved properly in CompleteMultipartUpload()
nctx, cancel := context.WithTimeout(ctx, 5*time.Minute) var uploadID string
defer cancel() attempts := 1
uploadID, err := c.NewMultipartUpload(nctx, bucket, object, opts) for attempts <= 3 {
nctx, cancel := context.WithTimeout(ctx, time.Minute)
uploadID, err = c.NewMultipartUpload(nctx, bucket, object, opts)
cancel()
if err == nil {
break
}
attempts++
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
}
if err != nil { if err != nil {
return err return err
} }
@@ -1521,7 +1560,7 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
fmt.Errorf("trying %s: Unable to cleanup failed multipart replication %s on remote %s/%s: %w - this may consume space on remote cluster", fmt.Errorf("trying %s: Unable to cleanup failed multipart replication %s on remote %s/%s: %w - this may consume space on remote cluster",
humanize.Ordinal(attempts), uploadID, bucket, object, aerr)) humanize.Ordinal(attempts), uploadID, bucket, object, aerr))
attempts++ attempts++
time.Sleep(time.Second) time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
} }
} }
}() }()
@@ -1532,7 +1571,7 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
) )
for _, partInfo := range objInfo.Parts { for _, partInfo := range objInfo.Parts {
hr, err = hash.NewReader(io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize) hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize)
if err != nil { if err != nil {
return err return err
} }
@@ -1658,9 +1697,8 @@ type ReplicationPool struct {
resyncer *replicationResyncer resyncer *replicationResyncer
// workers: // workers:
workers []chan ReplicationWorkerOperation workers []chan ReplicationWorkerOperation
lrgworkers []chan ReplicationWorkerOperation lrgworkers []chan ReplicationWorkerOperation
existingWorkers chan ReplicationWorkerOperation
// mrf: // mrf:
mrfWorkerKillCh chan struct{} mrfWorkerKillCh chan struct{}
@@ -1720,8 +1758,6 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
pool := &ReplicationPool{ pool := &ReplicationPool{
workers: make([]chan ReplicationWorkerOperation, 0, workers), workers: make([]chan ReplicationWorkerOperation, 0, workers),
lrgworkers: make([]chan ReplicationWorkerOperation, 0, LargeWorkerCount), lrgworkers: make([]chan ReplicationWorkerOperation, 0, LargeWorkerCount),
existingWorkers: make(chan ReplicationWorkerOperation, 100000),
mrfReplicaCh: make(chan ReplicationWorkerOperation, 100000), mrfReplicaCh: make(chan ReplicationWorkerOperation, 100000),
mrfWorkerKillCh: make(chan struct{}, failedWorkers), mrfWorkerKillCh: make(chan struct{}, failedWorkers),
resyncer: newresyncer(), resyncer: newresyncer(),
@@ -1735,7 +1771,6 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
pool.AddLargeWorkers() pool.AddLargeWorkers()
pool.ResizeWorkers(workers, 0) pool.ResizeWorkers(workers, 0)
pool.ResizeFailedWorkers(failedWorkers) pool.ResizeFailedWorkers(failedWorkers)
go pool.AddWorker(pool.existingWorkers, nil)
go pool.resyncer.PersistToDisk(ctx, o) go pool.resyncer.PersistToDisk(ctx, o)
go pool.processMRF() go pool.processMRF()
go pool.persistMRF() go pool.persistMRF()
@@ -1962,9 +1997,7 @@ func (p *ReplicationPool) queueReplicaTask(ri ReplicateObjectInfo) {
var ch, healCh chan<- ReplicationWorkerOperation var ch, healCh chan<- ReplicationWorkerOperation
switch ri.OpType { switch ri.OpType {
case replication.ExistingObjectReplicationType: case replication.HealReplicationType, replication.ExistingObjectReplicationType:
ch = p.existingWorkers
case replication.HealReplicationType:
ch = p.mrfReplicaCh ch = p.mrfReplicaCh
healCh = p.getWorkerCh(ri.Name, ri.Bucket, ri.Size) healCh = p.getWorkerCh(ri.Name, ri.Bucket, ri.Size)
default: default:
@@ -2023,9 +2056,7 @@ func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInf
} }
var ch chan<- ReplicationWorkerOperation var ch chan<- ReplicationWorkerOperation
switch doi.OpType { switch doi.OpType {
case replication.ExistingObjectReplicationType: case replication.HealReplicationType, replication.ExistingObjectReplicationType:
ch = p.existingWorkers
case replication.HealReplicationType:
fallthrough fallthrough
default: default:
ch = p.getWorkerCh(doi.Bucket, doi.ObjectName, 0) ch = p.getWorkerCh(doi.Bucket, doi.ObjectName, 0)
@@ -2229,8 +2260,38 @@ func proxyHeadToReplicationTarget(ctx context.Context, bucket, object string, rs
return oi, proxy return oi, proxy
} }
func scheduleReplication(ctx context.Context, objInfo ObjectInfo, o ObjectLayer, dsc ReplicateDecision, opType replication.Type) { func scheduleReplication(ctx context.Context, oi ObjectInfo, o ObjectLayer, dsc ReplicateDecision, opType replication.Type) {
ri := ReplicateObjectInfo{ObjectInfo: objInfo, OpType: opType, Dsc: dsc, EventType: ReplicateIncoming} tgtStatuses := replicationStatusesMap(oi.ReplicationStatusInternal)
purgeStatuses := versionPurgeStatusesMap(oi.VersionPurgeStatusInternal)
tm, _ := time.Parse(time.RFC3339Nano, oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp])
rstate := oi.ReplicationState()
rstate.ReplicateDecisionStr = dsc.String()
asz, _ := oi.GetActualSize()
ri := ReplicateObjectInfo{
Name: oi.Name,
Size: oi.Size,
ActualSize: asz,
Bucket: oi.Bucket,
VersionID: oi.VersionID,
ETag: oi.ETag,
ModTime: oi.ModTime,
ReplicationStatus: oi.ReplicationStatus,
ReplicationStatusInternal: oi.ReplicationStatusInternal,
DeleteMarker: oi.DeleteMarker,
VersionPurgeStatusInternal: oi.VersionPurgeStatusInternal,
VersionPurgeStatus: oi.VersionPurgeStatus,
ReplicationState: rstate,
OpType: opType,
Dsc: dsc,
TargetStatuses: tgtStatuses,
TargetPurgeStatuses: purgeStatuses,
ReplicationTimestamp: tm,
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
UserTags: oi.UserTags,
}
if dsc.Synchronous() { if dsc.Synchronous() {
replicateObject(ctx, ri, o) replicateObject(ctx, ri, o)
} else { } else {
@@ -2259,7 +2320,7 @@ func (c replicationConfig) Replicate(opts replication.ObjectOpts) bool {
} }
// Resync returns true if replication reset is requested // Resync returns true if replication reset is requested
func (c replicationConfig) Resync(ctx context.Context, oi ObjectInfo, dsc *ReplicateDecision, tgtStatuses map[string]replication.StatusType) (r ResyncDecision) { func (c replicationConfig) Resync(ctx context.Context, oi ObjectInfo, dsc ReplicateDecision, tgtStatuses map[string]replication.StatusType) (r ResyncDecision) {
if c.Empty() { if c.Empty() {
return return
} }
@@ -2268,8 +2329,6 @@ func (c replicationConfig) Resync(ctx context.Context, oi ObjectInfo, dsc *Repli
if oi.DeleteMarker { if oi.DeleteMarker {
opts := replication.ObjectOpts{ opts := replication.ObjectOpts{
Name: oi.Name, Name: oi.Name,
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
UserTags: oi.UserTags,
DeleteMarker: oi.DeleteMarker, DeleteMarker: oi.DeleteMarker,
VersionID: oi.VersionID, VersionID: oi.VersionID,
OpType: replication.DeleteReplicationType, OpType: replication.DeleteReplicationType,
@@ -2290,23 +2349,19 @@ func (c replicationConfig) Resync(ctx context.Context, oi ObjectInfo, dsc *Repli
} }
// Ignore previous replication status when deciding if object can be re-replicated // Ignore previous replication status when deciding if object can be re-replicated
objInfo := oi.Clone() userDefined := cloneMSS(oi.UserDefined)
objInfo.ReplicationStatusInternal = "" delete(userDefined, xhttp.AmzBucketReplicationStatus)
objInfo.VersionPurgeStatusInternal = ""
objInfo.ReplicationStatus = "" rdsc := mustReplicate(ctx, oi.Bucket, oi.Name, getMustReplicateOptions(userDefined, oi.UserTags, "", replication.ExistingObjectReplicationType, ObjectOptions{}))
objInfo.VersionPurgeStatus = "" return c.resync(oi, rdsc, tgtStatuses)
delete(objInfo.UserDefined, xhttp.AmzBucketReplicationStatus)
resyncdsc := mustReplicate(ctx, oi.Bucket, oi.Name, getMustReplicateOptions(objInfo, replication.ExistingObjectReplicationType, ObjectOptions{}))
dsc = &resyncdsc
return c.resync(oi, dsc, tgtStatuses)
} }
// wrapper function for testability. Returns true if a new reset is requested on // wrapper function for testability. Returns true if a new reset is requested on
// already replicated objects OR object qualifies for existing object replication // already replicated objects OR object qualifies for existing object replication
// and no reset requested. // and no reset requested.
func (c replicationConfig) resync(oi ObjectInfo, dsc *ReplicateDecision, tgtStatuses map[string]replication.StatusType) (r ResyncDecision) { func (c replicationConfig) resync(oi ObjectInfo, dsc ReplicateDecision, tgtStatuses map[string]replication.StatusType) (r ResyncDecision) {
r = ResyncDecision{ r = ResyncDecision{
targets: make(map[string]ResyncTargetDecision), targets: make(map[string]ResyncTargetDecision, len(dsc.targetsMap)),
} }
if c.remotes == nil { if c.remotes == nil {
return return
@@ -2527,6 +2582,13 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
workers := make([]chan ReplicateObjectInfo, resyncParallelRoutines) workers := make([]chan ReplicateObjectInfo, resyncParallelRoutines)
resultCh := make(chan TargetReplicationResyncStatus, 1) resultCh := make(chan TargetReplicationResyncStatus, 1)
defer close(resultCh) defer close(resultCh)
go func() {
for r := range resultCh {
s.incStats(r, opts)
globalSiteResyncMetrics.updateMetric(r, opts.resyncID)
}
}()
var wg sync.WaitGroup var wg sync.WaitGroup
for i := 0; i < resyncParallelRoutines; i++ { for i := 0; i < resyncParallelRoutines; i++ {
wg.Add(1) wg.Add(1)
@@ -2556,7 +2618,7 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
ObjectName: roi.Name, ObjectName: roi.Name,
DeleteMarkerVersionID: dmVersionID, DeleteMarkerVersionID: dmVersionID,
VersionID: versionID, VersionID: versionID,
ReplicationState: roi.getReplicationState(), ReplicationState: roi.ReplicationState,
DeleteMarkerMTime: DeleteMarkerMTime{roi.ModTime}, DeleteMarkerMTime: DeleteMarkerMTime{roi.ModTime},
DeleteMarker: roi.DeleteMarker, DeleteMarker: roi.DeleteMarker,
}, },
@@ -2631,12 +2693,6 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
for i := 0; i < resyncParallelRoutines; i++ { for i := 0; i < resyncParallelRoutines; i++ {
close(workers[i]) close(workers[i])
} }
go func() {
for r := range resultCh {
s.incStats(r, opts)
globalSiteResyncMetrics.updateMetric(r, opts.resyncID)
}
}()
wg.Wait() wg.Wait()
resyncStatus = ResyncCompleted resyncStatus = ResyncCompleted
} }
@@ -2960,8 +3016,8 @@ func getReplicationDiff(ctx context.Context, objAPI ObjectLayer, bucket string,
// QueueReplicationHeal is a wrapper for queueReplicationHeal // QueueReplicationHeal is a wrapper for queueReplicationHeal
func QueueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, retryCount int) { func QueueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, retryCount int) {
// un-versioned or a prefix // ignore modtime zero objects
if oi.VersionID == "" || oi.ModTime.IsZero() { if oi.ModTime.IsZero() {
return return
} }
rcfg, _ := getReplicationConfig(ctx, bucket) rcfg, _ := getReplicationConfig(ctx, bucket)
@@ -2975,8 +3031,8 @@ func QueueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, ret
// queueReplicationHeal enqueues objects that failed replication OR eligible for resyncing through // queueReplicationHeal enqueues objects that failed replication OR eligible for resyncing through
// an ongoing resync operation or via existing objects replication configuration setting. // an ongoing resync operation or via existing objects replication configuration setting.
func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcfg replicationConfig, retryCount int) (roi ReplicateObjectInfo) { func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcfg replicationConfig, retryCount int) (roi ReplicateObjectInfo) {
// un-versioned or a prefix // ignore modtime zero objects
if oi.VersionID == "" || oi.ModTime.IsZero() { if oi.ModTime.IsZero() {
return roi return roi
} }
@@ -3008,7 +3064,7 @@ func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcf
ObjectName: roi.Name, ObjectName: roi.Name,
DeleteMarkerVersionID: dmVersionID, DeleteMarkerVersionID: dmVersionID,
VersionID: versionID, VersionID: versionID,
ReplicationState: roi.getReplicationState(), ReplicationState: roi.ReplicationState,
DeleteMarkerMTime: DeleteMarkerMTime{roi.ModTime}, DeleteMarkerMTime: DeleteMarkerMTime{roi.ModTime},
DeleteMarker: roi.DeleteMarker, DeleteMarker: roi.DeleteMarker,
}, },
@@ -3048,7 +3104,7 @@ func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcf
const ( const (
mrfSaveInterval = 5 * time.Minute mrfSaveInterval = 5 * time.Minute
mrfQueueInterval = 6 * time.Minute mrfQueueInterval = mrfSaveInterval + time.Minute // A minute higher than save interval
mrfRetryLimit = 3 // max number of retries before letting scanner catch up on this object version mrfRetryLimit = 3 // max number of retries before letting scanner catch up on this object version
mrfMaxEntries = 1000000 mrfMaxEntries = 1000000
@@ -3063,46 +3119,37 @@ func (p *ReplicationPool) persistMRF() {
mTimer := time.NewTimer(mrfSaveInterval) mTimer := time.NewTimer(mrfSaveInterval)
defer mTimer.Stop() defer mTimer.Stop()
saveMRFToDisk := func(drain bool) { saveMRFToDisk := func() {
if len(entries) == 0 { if len(entries) == 0 {
return return
} }
cctx := p.ctx
if drain {
cctx = context.Background()
// drain all mrf entries and save to disk
for e := range p.mrfSaveCh {
entries[e.versionID] = e
}
}
// queue all entries for healing before overwriting the node mrf file // queue all entries for healing before overwriting the node mrf file
if !contextCanceled(p.ctx) { if !contextCanceled(p.ctx) {
p.queueMRFHeal() p.queueMRFHeal()
} }
if err := p.saveMRFEntries(cctx, entries); err != nil { p.saveMRFEntries(p.ctx, entries)
logger.LogOnceIf(p.ctx, fmt.Errorf("unable to persist replication failures to disk:%w", err), string(replicationSubsystem))
}
entries = make(map[string]MRFReplicateEntry) entries = make(map[string]MRFReplicateEntry)
} }
for { for {
select { select {
case <-mTimer.C: case <-mTimer.C:
saveMRFToDisk(false) saveMRFToDisk()
mTimer.Reset(mrfSaveInterval) mTimer.Reset(mrfSaveInterval)
case <-p.ctx.Done(): case <-p.ctx.Done():
p.mrfStopCh <- struct{}{} p.mrfStopCh <- struct{}{}
close(p.mrfSaveCh) close(p.mrfSaveCh)
saveMRFToDisk(true) // We try to save if possible, but we don't care beyond that.
saveMRFToDisk()
return return
case e, ok := <-p.mrfSaveCh: case e, ok := <-p.mrfSaveCh:
if !ok { if !ok {
return return
} }
if len(entries) >= mrfMaxEntries { if len(entries) >= mrfMaxEntries {
saveMRFToDisk(true) saveMRFToDisk()
} }
entries[e.versionID] = e entries[e.versionID] = e
} }
@@ -3132,14 +3179,45 @@ func (p *ReplicationPool) queueMRFSave(entry MRFReplicateEntry) {
} }
} }
func (p *ReplicationPool) persistToDrive(ctx context.Context, v MRFReplicateEntries, data []byte) {
newReader := func() io.ReadCloser {
r, w := io.Pipe()
go func() {
mw := msgp.NewWriter(w)
n, err := mw.Write(data)
if err != nil {
w.CloseWithError(err)
return
}
if n != len(data) {
w.CloseWithError(io.ErrShortWrite)
return
}
err = v.EncodeMsg(mw)
mw.Flush()
w.CloseWithError(err)
}()
return r
}
for _, localDrive := range globalLocalDrives {
r := newReader()
err := localDrive.CreateFile(ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), -1, r)
r.Close()
if err == nil {
break
}
}
}
// save mrf entries to nodenamehex.bin // save mrf entries to nodenamehex.bin
func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string]MRFReplicateEntry) error { func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string]MRFReplicateEntry) {
if !p.initialized() { if !p.initialized() {
return nil return
} }
atomic.StoreUint64(&globalReplicationStats.mrfStats.LastFailedCount, uint64(len(entries))) atomic.StoreUint64(&globalReplicationStats.mrfStats.LastFailedCount, uint64(len(entries)))
if len(entries) == 0 { if len(entries) == 0 {
return nil return
} }
v := MRFReplicateEntries{ v := MRFReplicateEntries{
@@ -3152,53 +3230,60 @@ func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string
binary.LittleEndian.PutUint16(data[0:2], mrfMetaFormat) binary.LittleEndian.PutUint16(data[0:2], mrfMetaFormat)
binary.LittleEndian.PutUint16(data[2:4], mrfMetaVersion) binary.LittleEndian.PutUint16(data[2:4], mrfMetaVersion)
buf, err := v.MarshalMsg(data) p.persistToDrive(ctx, v, data)
if err != nil {
return err
}
for _, localDrive := range globalLocalDrives {
if err := localDrive.WriteAll(ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), buf); err == nil {
break
}
}
return nil
} }
// load mrf entries from disk // load mrf entries from disk
func (p *ReplicationPool) loadMRF(data []byte) (re MRFReplicateEntries, e error) { func (p *ReplicationPool) loadMRF() (mrfRec MRFReplicateEntries, err error) {
if !p.initialized() { loadMRF := func(rc io.ReadCloser) (re MRFReplicateEntries, err error) {
defer rc.Close()
if !p.initialized() {
return re, nil
}
data := make([]byte, 4)
n, err := rc.Read(data)
if err != nil {
return re, err
}
if n != len(data) {
return re, errors.New("replication mrf: no data")
}
// Read resync meta header
switch binary.LittleEndian.Uint16(data[0:2]) {
case mrfMetaFormat:
default:
return re, fmt.Errorf("replication mrf: unknown format: %d", binary.LittleEndian.Uint16(data[0:2]))
}
switch binary.LittleEndian.Uint16(data[2:4]) {
case mrfMetaVersion:
default:
return re, fmt.Errorf("replication mrf: unknown version: %d", binary.LittleEndian.Uint16(data[2:4]))
}
// OK, parse data.
// ignore any parsing errors, we do not care this file is generated again anyways.
re.DecodeMsg(msgp.NewReader(rc))
return re, nil return re, nil
} }
if len(data) == 0 { for _, localDrive := range globalLocalDrives {
// Seems to be empty. rc, err := localDrive.ReadFileStream(p.ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), 0, -1)
return re, nil if err != nil {
} continue
if len(data) <= 4 { }
return re, fmt.Errorf("replication mrf: no data")
} mrfRec, err = loadMRF(rc)
// Read resync meta header if err != nil {
switch binary.LittleEndian.Uint16(data[0:2]) { continue
case mrfMetaFormat: }
default:
return re, fmt.Errorf("replication mrf: unknown format: %d", binary.LittleEndian.Uint16(data[0:2])) // finally delete the file after processing mrf entries
} localDrive.Delete(p.ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), DeleteOptions{})
switch binary.LittleEndian.Uint16(data[2:4]) { break
case mrfMetaVersion:
default:
return re, fmt.Errorf("replication mrf: unknown version: %d", binary.LittleEndian.Uint16(data[2:4]))
}
// OK, parse data.
if _, err := re.UnmarshalMsg(data[4:]); err != nil {
return re, err
} }
switch re.Version { return mrfRec, nil
case mrfMetaVersionV1:
default:
return re, fmt.Errorf("unexpected mrf meta version: %d", re.Version)
}
return re, nil
} }
func (p *ReplicationPool) processMRF() { func (p *ReplicationPool) processMRF() {
@@ -3241,39 +3326,28 @@ func (p *ReplicationPool) queueMRFHeal() error {
return errServerNotInitialized return errServerNotInitialized
} }
for _, localDrive := range globalLocalDrives { mrfRec, err := p.loadMRF()
buf, err := localDrive.ReadAll(p.ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin")) if err != nil {
if err != nil { return err
continue
}
mrfRec, err := p.loadMRF(buf)
if err != nil {
continue
}
// finally delete the file after processing mrf entries
localDrive.Delete(p.ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), DeleteOptions{})
// queue replication heal in a goroutine to avoid holding up mrf save routine
go func(mrfRec MRFReplicateEntries) {
for vID, e := range mrfRec.Entries {
ctx, cancel := context.WithTimeout(p.ctx, time.Second) // Do not waste more than a second on this.
oi, err := p.objLayer.GetObjectInfo(ctx, e.Bucket, e.Object, ObjectOptions{
VersionID: vID,
})
cancel()
if err != nil {
continue
}
QueueReplicationHeal(p.ctx, e.Bucket, oi, e.RetryCount)
}
}(mrfRec)
break
} }
// queue replication heal in a goroutine to avoid holding up mrf save routine
go func() {
for vID, e := range mrfRec.Entries {
ctx, cancel := context.WithTimeout(p.ctx, time.Second) // Do not waste more than a second on this.
oi, err := p.objLayer.GetObjectInfo(ctx, e.Bucket, e.Object, ObjectOptions{
VersionID: vID,
})
cancel()
if err != nil {
continue
}
QueueReplicationHeal(p.ctx, e.Bucket, oi, e.RetryCount)
}
}()
return nil return nil
} }
@@ -3283,33 +3357,28 @@ func (p *ReplicationPool) initialized() bool {
// getMRF returns MRF entries for this node. // getMRF returns MRF entries for this node.
func (p *ReplicationPool) getMRF(ctx context.Context, bucket string) (ch chan madmin.ReplicationMRF, err error) { func (p *ReplicationPool) getMRF(ctx context.Context, bucket string) (ch chan madmin.ReplicationMRF, err error) {
mrfRec, err := p.loadMRF()
if err != nil {
return nil, err
}
mrfCh := make(chan madmin.ReplicationMRF, 100) mrfCh := make(chan madmin.ReplicationMRF, 100)
go func() { go func() {
defer close(mrfCh) defer close(mrfCh)
for _, localDrive := range globalLocalDrives { for vID, e := range mrfRec.Entries {
buf, err := localDrive.ReadAll(ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin")) if e.Bucket != bucket && bucket != "" {
if err != nil {
continue continue
} }
mrfRec, err := p.loadMRF(buf) select {
if err != nil { case mrfCh <- madmin.ReplicationMRF{
continue NodeName: globalLocalNodeName,
} Object: e.Object,
for vID, e := range mrfRec.Entries { VersionID: vID,
if e.Bucket != bucket && bucket != "" { Bucket: e.Bucket,
continue RetryCount: e.RetryCount,
} }:
select { case <-ctx.Done():
case mrfCh <- madmin.ReplicationMRF{ return
NodeName: globalLocalNodeName,
Object: e.Object,
VersionID: vID,
Bucket: e.Bucket,
RetryCount: e.RetryCount,
}:
case <-ctx.Done():
return
}
} }
} }
}() }()
+2 -2
View File
@@ -88,7 +88,7 @@ var replicationConfigTests = []struct {
func TestReplicationResync(t *testing.T) { func TestReplicationResync(t *testing.T) {
ctx := context.Background() ctx := context.Background()
for i, test := range replicationConfigTests { for i, test := range replicationConfigTests {
if sync := test.rcfg.Resync(ctx, test.info, &test.dsc, test.tgtStatuses); sync.mustResync() != test.expectedSync { if sync := test.rcfg.Resync(ctx, test.info, test.dsc, test.tgtStatuses); sync.mustResync() != test.expectedSync {
t.Errorf("Test%d (%s): Resync got %t , want %t", i+1, test.name, sync.mustResync(), test.expectedSync) t.Errorf("Test%d (%s): Resync got %t , want %t", i+1, test.name, sync.mustResync(), test.expectedSync)
} }
} }
@@ -283,7 +283,7 @@ var (
func TestReplicationResyncwrapper(t *testing.T) { func TestReplicationResyncwrapper(t *testing.T) {
for i, test := range replicationConfigTests2 { for i, test := range replicationConfigTests2 {
if sync := test.rcfg.resync(test.info, &test.dsc, test.tgtStatuses); sync.mustResync() != test.expectedSync { if sync := test.rcfg.resync(test.info, test.dsc, test.tgtStatuses); sync.mustResync() != test.expectedSync {
t.Errorf("%s (%s): Replicationresync got %t , want %t", fmt.Sprintf("Test%d - %s", i+1, time.Now().Format(http.TimeFormat)), test.name, sync.mustResync(), test.expectedSync) t.Errorf("%s (%s): Replicationresync got %t , want %t", fmt.Sprintf("Test%d - %s", i+1, time.Now().Format(http.TimeFormat)), test.name, sync.mustResync(), test.expectedSync)
} }
} }
+14 -21
View File
@@ -489,31 +489,27 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
defer sys.Unlock() defer sys.Unlock()
// Remove existingtarget and arn association // Remove existingtarget and arn association
if tgts, ok := sys.targetsMap[bucket]; ok { if stgts, ok := sys.targetsMap[bucket]; ok {
for _, t := range tgts { for _, t := range stgts {
delete(sys.arnRemotesMap, t.Arn) delete(sys.arnRemotesMap, t.Arn)
} }
delete(sys.targetsMap, bucket) delete(sys.targetsMap, bucket)
} }
// No need for more if not adding anything if tgts != nil {
if tgts == nil || tgts.Empty() { for _, tgt := range tgts.Targets {
globalBucketMonitor.DeleteBucket(bucket) tgtClient, err := sys.getRemoteTargetClient(&tgt)
return if err != nil {
} continue
}
if len(tgts.Targets) > 0 { sys.arnRemotesMap[tgt.Arn] = tgtClient
sys.targetsMap[bucket] = tgts.Targets sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
} }
for _, tgt := range tgts.Targets {
tgtClient, err := sys.getRemoteTargetClient(&tgt) if !tgts.Empty() {
if err != nil { sys.targetsMap[bucket] = tgts.Targets
continue
} }
sys.arnRemotesMap[tgt.Arn] = tgtClient
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
} }
sys.targetsMap[bucket] = tgts.Targets
} }
// create minio-go clients for buckets having remote targets // create minio-go clients for buckets having remote targets
@@ -524,9 +520,6 @@ func (sys *BucketTargetSys) set(bucket BucketInfo, meta BucketMetadata) {
} }
sys.Lock() sys.Lock()
defer sys.Unlock() defer sys.Unlock()
if len(cfg.Targets) > 0 {
sys.targetsMap[bucket.Name] = cfg.Targets
}
for _, tgt := range cfg.Targets { for _, tgt := range cfg.Targets {
tgtClient, err := sys.getRemoteTargetClient(&tgt) tgtClient, err := sys.getRemoteTargetClient(&tgt)
if err != nil { if err != nil {
+1 -1
View File
@@ -28,7 +28,7 @@ import (
"github.com/minio/minio/internal/bucket/versioning" "github.com/minio/minio/internal/bucket/versioning"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
const ( const (
+38 -29
View File
@@ -29,7 +29,6 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"net" "net"
"net/http"
"net/url" "net/url"
"os" "os"
"path" "path"
@@ -54,27 +53,23 @@ import (
"github.com/minio/kes-go" "github.com/minio/kes-go"
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/color" "github.com/minio/minio/internal/color"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/certs" "github.com/minio/pkg/v2/certs"
"github.com/minio/pkg/console" "github.com/minio/pkg/v2/console"
"github.com/minio/pkg/ellipses" "github.com/minio/pkg/v2/ellipses"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
xnet "github.com/minio/pkg/net" xnet "github.com/minio/pkg/v2/net"
) )
// serverDebugLog will enable debug printing // serverDebugLog will enable debug printing
var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
var ( var shardDiskTimeDelta time.Duration
shardDiskTimeDelta time.Duration
defaultAWSCredProvider []credentials.Provider
)
func init() { func init() {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
@@ -112,23 +107,16 @@ func init() {
gob.Register(madmin.XFSErrorConfigs{}) gob.Register(madmin.XFSErrorConfigs{})
gob.Register(map[string]interface{}{}) gob.Register(map[string]interface{}{})
defaultAWSCredProvider = []credentials.Provider{
&credentials.IAM{
Client: &http.Client{
Transport: NewHTTPTransport(),
},
},
}
var err error var err error
shardDiskTimeDelta, err = time.ParseDuration(env.Get("_MINIO_SHARD_DISKTIME_DELTA", "1m")) shardDiskTimeDelta, err = time.ParseDuration(env.Get("_MINIO_SHARD_DISKTIME_DELTA", "1m"))
if err != nil { if err != nil {
shardDiskTimeDelta = 1 * time.Minute shardDiskTimeDelta = 1 * time.Minute
} }
// All minio-go API operations shall be performed only once, // All minio-go and madmin-go API operations shall be performed only once,
// another way to look at this is we are turning off retries. // another way to look at this is we are turning off retries.
minio.MaxRetry = 1 minio.MaxRetry = 1
madmin.MaxRetry = 1
} }
const consolePrefix = "CONSOLE_" const consolePrefix = "CONSOLE_"
@@ -143,29 +131,33 @@ func minioConfigToConsoleFeatures() {
// This will save users from providing a certificate with IP or FQDN SAN that points to the local host. // This will save users from providing a certificate with IP or FQDN SAN that points to the local host.
os.Setenv("CONSOLE_MINIO_SERVER", fmt.Sprintf("%s://127.0.0.1:%s", getURLScheme(globalIsTLS), globalMinioPort)) os.Setenv("CONSOLE_MINIO_SERVER", fmt.Sprintf("%s://127.0.0.1:%s", getURLScheme(globalIsTLS), globalMinioPort))
} }
if value := env.Get("MINIO_LOG_QUERY_URL", ""); value != "" { if value := env.Get(config.EnvMinIOLogQueryURL, ""); value != "" {
os.Setenv("CONSOLE_LOG_QUERY_URL", value) os.Setenv("CONSOLE_LOG_QUERY_URL", value)
if value := env.Get("MINIO_LOG_QUERY_AUTH_TOKEN", ""); value != "" { if value := env.Get(config.EnvMinIOLogQueryAuthToken, ""); value != "" {
os.Setenv("CONSOLE_LOG_QUERY_AUTH_TOKEN", value) os.Setenv("CONSOLE_LOG_QUERY_AUTH_TOKEN", value)
} }
} }
// pass the console subpath configuration // pass the console subpath configuration
if value := env.Get(config.EnvBrowserRedirectURL, ""); value != "" { if globalBrowserRedirectURL != nil {
subPath := path.Clean(pathJoin(strings.TrimSpace(globalBrowserRedirectURL.Path), SlashSeparator)) subPath := path.Clean(pathJoin(strings.TrimSpace(globalBrowserRedirectURL.Path), SlashSeparator))
if subPath != SlashSeparator { if subPath != SlashSeparator {
os.Setenv("CONSOLE_SUBPATH", subPath) os.Setenv("CONSOLE_SUBPATH", subPath)
} }
} }
// Enable if prometheus URL is set. // Enable if prometheus URL is set.
if value := env.Get("MINIO_PROMETHEUS_URL", ""); value != "" { if value := env.Get(config.EnvMinIOPrometheusURL, ""); value != "" {
os.Setenv("CONSOLE_PROMETHEUS_URL", value) os.Setenv("CONSOLE_PROMETHEUS_URL", value)
if value := env.Get("MINIO_PROMETHEUS_JOB_ID", "minio-job"); value != "" { if value := env.Get(config.EnvMinIOPrometheusJobID, "minio-job"); value != "" {
os.Setenv("CONSOLE_PROMETHEUS_JOB_ID", value) os.Setenv("CONSOLE_PROMETHEUS_JOB_ID", value)
// Support additional labels for more granular filtering. // Support additional labels for more granular filtering.
if value := env.Get("MINIO_PROMETHEUS_EXTRA_LABELS", ""); value != "" { if value := env.Get(config.EnvMinIOPrometheusExtraLabels, ""); value != "" {
os.Setenv("CONSOLE_PROMETHEUS_EXTRA_LABELS", value) os.Setenv("CONSOLE_PROMETHEUS_EXTRA_LABELS", value)
} }
} }
// Support Prometheus Auth Token
if value := env.Get(config.EnvMinIOPrometheusAuthToken, ""); value != "" {
os.Setenv("CONSOLE_PROMETHEUS_AUTH_TOKEN", value)
}
} }
// Enable if LDAP is enabled. // Enable if LDAP is enabled.
if globalIAMSys.LDAPConfig.Enabled() { if globalIAMSys.LDAPConfig.Enabled() {
@@ -391,7 +383,7 @@ func handleCommonCmdArgs(ctx *cli.Context) {
if consoleAddr == "" { if consoleAddr == "" {
p, err := xnet.GetFreePort() p, err := xnet.GetFreePort()
if err != nil { if err != nil {
logger.FatalIf(err, "Unable to get free port for console on the host") logger.FatalIf(err, "Unable to get free port for Console UI on the host")
} }
consoleAddr = net.JoinHostPort("", p.String()) consoleAddr = net.JoinHostPort("", p.String())
} }
@@ -405,6 +397,15 @@ func handleCommonCmdArgs(ctx *cli.Context) {
} }
globalMinioHost, globalMinioPort = mustSplitHostPort(addr) globalMinioHost, globalMinioPort = mustSplitHostPort(addr)
if globalMinioPort == "0" {
p, err := xnet.GetFreePort()
if err != nil {
logger.FatalIf(err, "Unable to get free port for S3 API on the host")
}
globalMinioPort = p.String()
globalDynamicAPIPort = true
}
globalMinioConsoleHost, globalMinioConsolePort = mustSplitHostPort(consoleAddr) globalMinioConsoleHost, globalMinioConsolePort = mustSplitHostPort(consoleAddr)
if globalMinioPort == globalMinioConsolePort { if globalMinioPort == globalMinioConsolePort {
@@ -640,6 +641,7 @@ func handleCommonEnvVars() {
} }
globalBrowserRedirectURL = u globalBrowserRedirectURL = u
} }
globalBrowserRedirect = env.Get(config.EnvBrowserRedirect, config.EnableOn) == config.EnableOn
} }
if serverURL := env.Get(config.EnvMinIOServerURL, ""); serverURL != "" { if serverURL := env.Get(config.EnvMinIOServerURL, ""); serverURL != "" {
@@ -664,10 +666,14 @@ func handleCommonEnvVars() {
logger.Fatal(config.ErrInvalidFSOSyncValue(err), "Invalid MINIO_FS_OSYNC value in environment variable") logger.Fatal(config.ErrInvalidFSOSyncValue(err), "Invalid MINIO_FS_OSYNC value in environment variable")
} }
if rootDiskSize := env.Get(config.EnvRootDiskThresholdSize, ""); rootDiskSize != "" { rootDiskSize := env.Get(config.EnvRootDriveThresholdSize, "")
if rootDiskSize == "" {
rootDiskSize = env.Get(config.EnvRootDiskThresholdSize, "")
}
if rootDiskSize != "" {
size, err := humanize.ParseBytes(rootDiskSize) size, err := humanize.ParseBytes(rootDiskSize)
if err != nil { if err != nil {
logger.Fatal(err, fmt.Sprintf("Invalid %s value in environment variable", config.EnvRootDiskThresholdSize)) logger.Fatal(err, fmt.Sprintf("Invalid %s value in root drive threshold environment variable", rootDiskSize))
} }
globalRootDiskThreshold = size globalRootDiskThreshold = size
} }
@@ -802,6 +808,9 @@ func handleKMSConfig() {
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientCert)) logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientCert))
} }
} }
if !env.IsSet(kms.EnvKESKeyName) {
logger.Fatal(errors.New("Invalid KES configuration"), fmt.Sprintf("The mandatory environment variable %q not set", kms.EnvKESKeyName))
}
var endpoints []string var endpoints []string
for _, endpoint := range strings.Split(env.Get(kms.EnvKESEndpoint, ""), ",") { for _, endpoint := range strings.Split(env.Get(kms.EnvKESEndpoint, ""), ",") {
+1 -1
View File
@@ -71,7 +71,7 @@ func deleteConfig(ctx context.Context, objAPI objectDeleter, configFile string)
} }
func saveConfigWithOpts(ctx context.Context, store objectIO, configFile string, data []byte, opts ObjectOptions) error { func saveConfigWithOpts(ctx context.Context, store objectIO, configFile string, data []byte, opts ObjectOptions) error {
hashReader, err := hash.NewReader(bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data))) hashReader, err := hash.NewReader(ctx, bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data)))
if err != nil { if err != nil {
return err return err
} }
+1 -1
View File
@@ -48,7 +48,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
) )
func initHelp() { func initHelp() {
+2 -2
View File
@@ -40,8 +40,8 @@ import (
"github.com/minio/minio/internal/event/target" "github.com/minio/minio/internal/event/target"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/logger/target/http" "github.com/minio/minio/internal/logger/target/http"
xnet "github.com/minio/pkg/net" xnet "github.com/minio/pkg/v2/net"
"github.com/minio/pkg/quick" "github.com/minio/pkg/v2/quick"
) )
// Do not edit following message template, please open a GitHub issue to discuss instead. // Do not edit following message template, please open a GitHub issue to discuss instead.
+1 -1
View File
@@ -31,7 +31,7 @@ import (
"github.com/minio/minio/internal/config/storageclass" "github.com/minio/minio/internal/config/storageclass"
"github.com/minio/minio/internal/event/target" "github.com/minio/minio/internal/event/target"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/quick" "github.com/minio/pkg/v2/quick"
) )
type configV1 struct { type configV1 struct {
+2 -2
View File
@@ -28,8 +28,8 @@ import (
"github.com/minio/minio/internal/logger/target/console" "github.com/minio/minio/internal/logger/target/console"
"github.com/minio/minio/internal/logger/target/types" "github.com/minio/minio/internal/logger/target/types"
"github.com/minio/minio/internal/pubsub" "github.com/minio/minio/internal/pubsub"
"github.com/minio/pkg/logger/message/log" "github.com/minio/pkg/v2/logger/message/log"
xnet "github.com/minio/pkg/net" xnet "github.com/minio/pkg/v2/net"
) )
// number of log messages to buffer // number of log messages to buffer
+2
View File
@@ -66,10 +66,12 @@ const (
scannerMetricYield scannerMetricYield
scannerMetricCleanAbandoned scannerMetricCleanAbandoned
scannerMetricApplyNonCurrent scannerMetricApplyNonCurrent
scannerMetricHealAbandonedVersion
// START Trace metrics: // START Trace metrics:
scannerMetricStartTrace scannerMetricStartTrace
scannerMetricScanObject // Scan object. All operations included. scannerMetricScanObject // Scan object. All operations included.
scannerMetricHealAbandonedObject
// END realtime metrics: // END realtime metrics:
scannerMetricLastRealtime scannerMetricLastRealtime
+40 -32
View File
@@ -41,7 +41,7 @@ import (
"github.com/minio/minio/internal/config/heal" "github.com/minio/minio/internal/config/heal"
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/console" "github.com/minio/pkg/v2/console"
uatomic "go.uber.org/atomic" uatomic "go.uber.org/atomic"
) )
@@ -54,11 +54,10 @@ const (
dataScannerForceCompactAtFolders = 1_000_000 // Compact when this many subfolders in a single folder (even top level). dataScannerForceCompactAtFolders = 1_000_000 // Compact when this many subfolders in a single folder (even top level).
dataScannerStartDelay = 1 * time.Minute // Time to wait on startup and between cycles. dataScannerStartDelay = 1 * time.Minute // Time to wait on startup and between cycles.
healDeleteDangling = true healDeleteDangling = true
healFolderIncludeProb = 32 // Include a clean folder one in n cycles. healObjectSelectProb = 1024 // Overall probability of a file being scanned; one in n.
healObjectSelectProb = 512 // Overall probability of a file being scanned; one in n.
dataScannerExcessiveVersionsThreshold = 1000 // Issue a warning when a single object has more versions than this dataScannerExcessiveVersionsThreshold = 100 // Issue a warning when a single object has more versions than this
dataScannerExcessiveFoldersThreshold = 50000 // Issue a warning when a folder has more subfolders than this in a *set* dataScannerExcessiveFoldersThreshold = 50000 // Issue a warning when a folder has more subfolders than this in a *set*
) )
@@ -66,7 +65,7 @@ var (
globalHealConfig heal.Config globalHealConfig heal.Config
// Sleeper values are updated when config is loaded. // Sleeper values are updated when config is loaded.
scannerSleeper = newDynamicSleeper(10, 10*time.Second, true) scannerSleeper = newDynamicSleeper(2, time.Second, true) // Keep defaults same as config defaults
scannerCycle = uatomic.NewDuration(dataScannerStartDelay) scannerCycle = uatomic.NewDuration(dataScannerStartDelay)
) )
@@ -274,7 +273,6 @@ type folderScanner struct {
// rarer if the bloom filter for the path is clean and no lifecycles are applied. // rarer if the bloom filter for the path is clean and no lifecycles are applied.
// Skipped leaves have their totals transferred from the previous cycle. // Skipped leaves have their totals transferred from the previous cycle.
// //
// A clean leaf will be included once every healFolderIncludeProb for partial heal scans.
// When selected there is a one in healObjectSelectProb that any object will be chosen for heal scan. // When selected there is a one in healObjectSelectProb that any object will be chosen for heal scan.
// //
// Compaction happens when either: // Compaction happens when either:
@@ -409,9 +407,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
var existingFolders, newFolders []cachedFolder var existingFolders, newFolders []cachedFolder
var foundObjects bool var foundObjects bool
err := readDirFn(path.Join(f.root, folder.name), func(entName string, typ os.FileMode) error { err := readDirFn(pathJoin(f.root, folder.name), func(entName string, typ os.FileMode) error {
// Parse // Parse
entName = pathClean(path.Join(folder.name, entName)) entName = pathClean(pathJoin(folder.name, entName))
if entName == "" || entName == folder.name { if entName == "" || entName == folder.name {
if f.dataUsageScannerDebug { if f.dataUsageScannerDebug {
console.Debugf(scannerLogPrefix+" no entity (%s,%s)\n", f.root, entName) console.Debugf(scannerLogPrefix+" no entity (%s,%s)\n", f.root, entName)
@@ -461,7 +459,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
// Get file size, ignore errors. // Get file size, ignore errors.
item := scannerItem{ item := scannerItem{
Path: path.Join(f.root, entName), Path: pathJoin(f.root, entName),
Typ: typ, Typ: typ,
bucket: bucket, bucket: bucket,
prefix: path.Dir(prefix), prefix: path.Dir(prefix),
@@ -496,7 +494,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
// Object already accounted for, remove from heal map, // Object already accounted for, remove from heal map,
// simply because getSize() function already heals the // simply because getSize() function already heals the
// object. // object.
delete(abandonedChildren, path.Join(item.bucket, item.objectPath())) delete(abandonedChildren, pathJoin(item.bucket, item.objectPath()))
into.addSizes(sz) into.addSizes(sz)
into.Objects++ into.Objects++
@@ -691,6 +689,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
reportNotFound: true, reportNotFound: true,
minDisks: f.disksQuorum, minDisks: f.disksQuorum,
agreed: func(entry metaCacheEntry) { agreed: func(entry metaCacheEntry) {
f.updateCurrentPath(entry.name)
if f.dataUsageScannerDebug { if f.dataUsageScannerDebug {
console.Debugf(healObjectsPrefix+" got agreement: %v\n", entry.name) console.Debugf(healObjectsPrefix+" got agreement: %v\n", entry.name)
} }
@@ -704,6 +703,13 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
// this object might be dangling. // this object might be dangling.
entry, _ = entries.firstFound() entry, _ = entries.firstFound()
} }
// wait timer per object.
wait := scannerSleeper.Timer(ctx)
defer wait()
f.updateCurrentPath(entry.name)
stopFn := globalScannerMetrics.log(scannerMetricHealAbandonedObject, f.root, entry.name)
custom := make(map[string]string)
defer stopFn(custom)
if f.dataUsageScannerDebug { if f.dataUsageScannerDebug {
console.Debugf(healObjectsPrefix+" resolved to: %v, dir: %v\n", entry.name, entry.isDir()) console.Debugf(healObjectsPrefix+" resolved to: %v, dir: %v\n", entry.name, entry.isDir())
@@ -713,13 +719,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
return return
} }
// wait on timer per object.
wait := scannerSleeper.Timer(ctx)
// We got an entry which we should be able to heal. // We got an entry which we should be able to heal.
fiv, err := entry.fileInfoVersions(bucket) fiv, err := entry.fileInfoVersions(bucket)
if err != nil { if err != nil {
wait()
err := bgSeq.queueHealTask(healSource{ err := bgSeq.queueHealTask(healSource{
bucket: bucket, bucket: bucket,
object: entry.name, object: entry.name,
@@ -732,21 +734,28 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
return return
} }
custom["versions"] = fmt.Sprint(len(fiv.Versions))
var successVersions, failVersions int
for _, ver := range fiv.Versions { for _, ver := range fiv.Versions {
// Sleep and reset. stopFn := globalScannerMetrics.timeSize(scannerMetricHealAbandonedVersion)
wait()
wait = scannerSleeper.Timer(ctx)
err := bgSeq.queueHealTask(healSource{ err := bgSeq.queueHealTask(healSource{
bucket: bucket, bucket: bucket,
object: fiv.Name, object: fiv.Name,
versionID: ver.VersionID, versionID: ver.VersionID,
}, madmin.HealItemObject) }, madmin.HealItemObject)
stopFn(int(ver.Size))
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) { if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
} }
if err == nil {
successVersions++
} else {
failVersions++
}
foundObjs = foundObjs || err == nil foundObjs = foundObjs || err == nil
} }
custom["success_versions"] = fmt.Sprint(successVersions)
custom["failed_versions"] = fmt.Sprint(failVersions)
}, },
// Too many disks failed. // Too many disks failed.
finished: func(errs []error) { finished: func(errs []error) {
@@ -873,7 +882,7 @@ type getSizeFn func(item scannerItem) (sizeSummary, error)
func (i *scannerItem) transformMetaDir() { func (i *scannerItem) transformMetaDir() {
split := strings.Split(i.prefix, SlashSeparator) split := strings.Split(i.prefix, SlashSeparator)
if len(split) > 1 { if len(split) > 1 {
i.prefix = path.Join(split[:len(split)-1]...) i.prefix = pathJoin(split[:len(split)-1]...)
} else { } else {
i.prefix = "" i.prefix = ""
} }
@@ -1061,17 +1070,6 @@ func (i *scannerItem) applyNewerNoncurrentVersionLimit(ctx context.Context, _ Ob
// applyVersionActions will apply lifecycle checks on all versions of a scanned item. Returns versions that remain // applyVersionActions will apply lifecycle checks on all versions of a scanned item. Returns versions that remain
// after applying lifecycle checks configured. // after applying lifecycle checks configured.
func (i *scannerItem) applyVersionActions(ctx context.Context, o ObjectLayer, fivs []FileInfo) ([]ObjectInfo, error) { func (i *scannerItem) applyVersionActions(ctx context.Context, o ObjectLayer, fivs []FileInfo) ([]ObjectInfo, error) {
if i.heal.enabled {
if healDeleteDangling {
done := globalScannerMetrics.time(scannerMetricCleanAbandoned)
err := o.CheckAbandonedParts(ctx, i.bucket, i.objectPath(), madmin.HealOpts{Remove: healDeleteDangling})
done()
if err != nil {
logger.LogIf(ctx, fmt.Errorf("unable to check object %s/%s for abandoned data: %w", i.bucket, i.objectPath(), err))
}
}
}
objInfos, err := i.applyNewerNoncurrentVersionLimit(ctx, o, fivs) objInfos, err := i.applyNewerNoncurrentVersionLimit(ctx, o, fivs)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1112,7 +1110,17 @@ func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi Object
done := globalScannerMetrics.time(scannerMetricHealCheck) done := globalScannerMetrics.time(scannerMetricHealCheck)
size = i.applyHealing(ctx, o, oi) size = i.applyHealing(ctx, o, oi)
done() done()
if healDeleteDangling {
done := globalScannerMetrics.time(scannerMetricCleanAbandoned)
err := o.CheckAbandonedParts(ctx, i.bucket, i.objectPath(), madmin.HealOpts{Remove: healDeleteDangling})
done()
if err != nil {
logger.LogIf(ctx, fmt.Errorf("unable to check object %s/%s for abandoned data: %w", i.bucket, i.objectPath(), err))
}
}
} }
// replicate only if lifecycle rules are not applied. // replicate only if lifecycle rules are not applied.
done := globalScannerMetrics.time(scannerMetricCheckReplication) done := globalScannerMetrics.time(scannerMetricCheckReplication)
i.healReplication(ctx, o, oi.Clone(), sizeS) i.healReplication(ctx, o, oi.Clone(), sizeS)
@@ -1247,7 +1255,7 @@ func applyLifecycleAction(event lifecycle.Event, src lcEventSrc, obj ObjectInfo)
// objectPath returns the prefix and object name. // objectPath returns the prefix and object name.
func (i *scannerItem) objectPath() string { func (i *scannerItem) objectPath() string {
return path.Join(i.prefix, i.objectName) return pathJoin(i.prefix, i.objectName)
} }
// healReplication will heal a scanned item that has failed replication. // healReplication will heal a scanned item that has failed replication.
+80 -66
View File
@@ -38,6 +38,7 @@ import (
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/tinylib/msgp/msgp" "github.com/tinylib/msgp/msgp"
"github.com/valyala/bytebufferpool"
) )
//go:generate msgp -file $GOFILE -unexported //go:generate msgp -file $GOFILE -unexported
@@ -346,20 +347,18 @@ func (e *dataUsageEntry) addSizes(summary sizeSummary) {
e.ReplicationStats.ReplicaSize += uint64(summary.replicaSize) e.ReplicationStats.ReplicaSize += uint64(summary.replicaSize)
e.ReplicationStats.ReplicaCount += uint64(summary.replicaCount) e.ReplicationStats.ReplicaCount += uint64(summary.replicaCount)
if summary.replTargetStats != nil { for arn, st := range summary.replTargetStats {
for arn, st := range summary.replTargetStats { tgtStat, ok := e.ReplicationStats.Targets[arn]
tgtStat, ok := e.ReplicationStats.Targets[arn] if !ok {
if !ok { tgtStat = replicationStats{}
tgtStat = replicationStats{}
}
tgtStat.PendingSize += uint64(st.pendingSize)
tgtStat.FailedSize += uint64(st.failedSize)
tgtStat.ReplicatedSize += uint64(st.replicatedSize)
tgtStat.ReplicatedCount += uint64(st.replicatedCount)
tgtStat.FailedCount += st.failedCount
tgtStat.PendingCount += st.pendingCount
e.ReplicationStats.Targets[arn] = tgtStat
} }
tgtStat.PendingSize += uint64(st.pendingSize)
tgtStat.FailedSize += uint64(st.failedSize)
tgtStat.ReplicatedSize += uint64(st.replicatedSize)
tgtStat.ReplicatedCount += uint64(st.replicatedCount)
tgtStat.FailedCount += st.failedCount
tgtStat.PendingCount += st.pendingCount
e.ReplicationStats.Targets[arn] = tgtStat
} }
if summary.tiers != nil { if summary.tiers != nil {
if e.AllTierStats == nil { if e.AllTierStats == nil {
@@ -927,37 +926,47 @@ type objectIO interface {
// The loader is optimistic and has no locking, but tries 5 times before giving up. // The loader is optimistic and has no locking, but tries 5 times before giving up.
// If the object is not found or unable to deserialize d is cleared and nil error is returned. // If the object is not found or unable to deserialize d is cleared and nil error is returned.
func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string) error { func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string) error {
// Abandon if more than 5 minutes, so we don't hold up scanner. // By defaut, empty data usage cache
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) *d = dataUsageCache{}
defer cancel()
load := func(name string, timeout time.Duration) (bool, error) {
// Abandon if more than time.Minute, so we don't hold up scanner.
// drive timeout by default is 2 minutes, we do not need to wait longer.
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// Caches are read+written without locks,
retries := 0
for retries < 5 {
r, err := store.GetObjectNInfo(ctx, dataUsageBucket, name, nil, http.Header{}, ObjectOptions{NoLock: true}) r, err := store.GetObjectNInfo(ctx, dataUsageBucket, name, nil, http.Header{}, ObjectOptions{NoLock: true})
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case ObjectNotFound, BucketNotFound: case ObjectNotFound, BucketNotFound:
return false, nil
case InsufficientReadQuorum, StorageErr: case InsufficientReadQuorum, StorageErr:
retries++ return true, nil
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
continue
default:
return toObjectErr(err, dataUsageBucket, name)
} }
*d = dataUsageCache{} return false, err
return nil
}
if err := d.deserialize(r); err != nil {
r.Close()
retries++
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
continue
} }
err = d.deserialize(r)
r.Close() r.Close()
return nil return err != nil, nil
}
// Caches are read+written without locks,
retries := 0
for retries < 5 {
retry, err := load(name, time.Minute)
if err != nil {
return toObjectErr(err, dataUsageBucket, name)
}
if !retry {
break
}
retry, _ = load(name+".bkp", 30*time.Second)
if !retry {
break
}
retries++
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
} }
*d = dataUsageCache{}
return nil return nil
} }
@@ -967,47 +976,52 @@ var maxConcurrentScannerSaves = make(chan struct{}, 4)
// save the content of the cache to minioMetaBackgroundOpsBucket with the provided name. // save the content of the cache to minioMetaBackgroundOpsBucket with the provided name.
// Note that no locking is done when saving. // Note that no locking is done when saving.
func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string) error { func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string) error {
var r io.Reader select {
maxConcurrentScannerSaves <- struct{}{} case <-ctx.Done():
return ctx.Err()
case maxConcurrentScannerSaves <- struct{}{}:
}
defer func() { defer func() {
<-maxConcurrentScannerSaves select {
}() case <-ctx.Done():
// If big, do streaming... case <-maxConcurrentScannerSaves:
size := int64(-1)
if len(d.Cache) > 10000 {
pr, pw := io.Pipe()
go func() {
pw.CloseWithError(d.serializeTo(pw))
}()
defer pr.Close()
r = pr
} else {
var buf bytes.Buffer
err := d.serializeTo(&buf)
if err != nil {
return err
} }
r = &buf }()
size = int64(buf.Len())
buf := bytebufferpool.Get()
defer func() {
buf.Reset()
bytebufferpool.Put(buf)
}()
if err := d.serializeTo(buf); err != nil {
return err
} }
hr, err := hash.NewReader(r, size, "", "", size) hr, err := hash.NewReader(ctx, bytes.NewReader(buf.Bytes()), int64(buf.Len()), "", "", int64(buf.Len()))
if err != nil { if err != nil {
return err return err
} }
// Abandon if more than 5 minutes, so we don't hold up scanner. save := func(name string, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) // Abandon if more than a minute, so we don't hold up scanner.
defer cancel() ctx, cancel := context.WithTimeout(ctx, timeout)
_, err = store.PutObject(ctx, defer cancel()
dataUsageBucket,
name, _, err = store.PutObject(ctx,
NewPutObjReader(hr), dataUsageBucket,
ObjectOptions{NoLock: true}) name,
if isErrBucketNotFound(err) { NewPutObjReader(hr),
return nil ObjectOptions{NoLock: true})
if isErrBucketNotFound(err) {
return nil
}
return err
} }
return err defer save(name+".bkp", 30*time.Second) // Keep a backup as well
// drive timeout by default is 2 minutes, we do not need to wait longer.
return save(name, time.Minute)
} }
// dataUsageCacheVer indicates the cache version. // dataUsageCacheVer indicates the cache version.
+48 -19
View File
@@ -21,6 +21,7 @@ import (
"context" "context"
"errors" "errors"
"strings" "strings"
"time"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
@@ -42,6 +43,7 @@ const (
// storeDataUsageInBackend will store all objects sent on the gui channel until closed. // storeDataUsageInBackend will store all objects sent on the gui channel until closed.
func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan DataUsageInfo) { func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan DataUsageInfo) {
attempts := 1
for dataUsageInfo := range dui { for dataUsageInfo := range dui {
json := jsoniter.ConfigCompatibleWithStandardLibrary json := jsoniter.ConfigCompatibleWithStandardLibrary
dataUsageJSON, err := json.Marshal(dataUsageInfo) dataUsageJSON, err := json.Marshal(dataUsageInfo)
@@ -49,12 +51,19 @@ func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
continue continue
} }
if attempts > 10 {
saveConfig(ctx, objAPI, dataUsageObjNamePath+".bkp", dataUsageJSON) // Save a backup every 10th update.
attempts = 1
}
if err = saveConfig(ctx, objAPI, dataUsageObjNamePath, dataUsageJSON); err != nil { if err = saveConfig(ctx, objAPI, dataUsageObjNamePath, dataUsageJSON); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
} }
attempts++
} }
} }
var prefixUsageCache timedValue
// loadPrefixUsageFromBackend returns prefix usages found in passed buckets // loadPrefixUsageFromBackend returns prefix usages found in passed buckets
// //
// e.g.: /testbucket/prefix => 355601334 // e.g.: /testbucket/prefix => 355601334
@@ -67,37 +76,57 @@ func loadPrefixUsageFromBackend(ctx context.Context, objAPI ObjectLayer, bucket
cache := dataUsageCache{} cache := dataUsageCache{}
m := make(map[string]uint64) prefixUsageCache.Once.Do(func() {
for _, pool := range z.serverPools { prefixUsageCache.TTL = 30 * time.Second
for _, er := range pool.sets {
// Load bucket usage prefixes
if err := cache.load(ctx, er, bucket+slashSeparator+dataUsageCacheName); err == nil {
root := cache.find(bucket)
if root == nil {
// We dont have usage information for this bucket in this
// set, go to the next set
continue
}
for id, usageInfo := range cache.flattenChildrens(*root) { // No need to fail upon Update() error, fallback to old value.
prefix := decodeDirObject(strings.TrimPrefix(id, bucket+slashSeparator)) prefixUsageCache.Relax = true
// decodeDirObject to avoid any __XLDIR__ objects prefixUsageCache.Update = func() (interface{}, error) {
m[prefix] += uint64(usageInfo.Size) m := make(map[string]uint64)
for _, pool := range z.serverPools {
for _, er := range pool.sets {
// Load bucket usage prefixes
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
ok := cache.load(ctx, er, bucket+slashSeparator+dataUsageCacheName) == nil
done()
if ok {
root := cache.find(bucket)
if root == nil {
// We dont have usage information for this bucket in this
// set, go to the next set
continue
}
for id, usageInfo := range cache.flattenChildrens(*root) {
prefix := decodeDirObject(strings.TrimPrefix(id, bucket+slashSeparator))
// decodeDirObject to avoid any __XLDIR__ objects
m[prefix] += uint64(usageInfo.Size)
}
}
} }
} }
return m, nil
} }
})
v, _ := prefixUsageCache.Get()
if v != nil {
return v.(map[string]uint64), nil
} }
return m, nil return map[string]uint64{}, nil
} }
func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (DataUsageInfo, error) { func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (DataUsageInfo, error) {
buf, err := readConfig(ctx, objAPI, dataUsageObjNamePath) buf, err := readConfig(ctx, objAPI, dataUsageObjNamePath)
if err != nil { if err != nil {
if errors.Is(err, errConfigNotFound) { buf, err = readConfig(ctx, objAPI, dataUsageObjNamePath+".bkp")
return DataUsageInfo{}, nil if err != nil {
if errors.Is(err, errConfigNotFound) {
return DataUsageInfo{}, nil
}
return DataUsageInfo{}, toObjectErr(err, minioMetaBucket, dataUsageObjNamePath)
} }
return DataUsageInfo{}, toObjectErr(err, minioMetaBucket, dataUsageObjNamePath)
} }
var dataUsageInfo DataUsageInfo var dataUsageInfo DataUsageInfo
+5 -5
View File
@@ -604,7 +604,7 @@ func (c *diskCache) SaveMetadata(ctx context.Context, bucket, object string, met
// move part saved in writeback directory and cache.json atomically // move part saved in writeback directory and cache.json atomically
if finalizeWB { if finalizeWB {
wbdir := getCacheWriteBackSHADir(c.dir, bucket, object) wbdir := getCacheWriteBackSHADir(c.dir, bucket, object)
if err = renameAll(pathJoin(wbdir, cacheDataFile), pathJoin(cachedPath, cacheDataFile)); err != nil { if err = renameAll(pathJoin(wbdir, cacheDataFile), pathJoin(cachedPath, cacheDataFile), c.dir); err != nil {
return err return err
} }
removeAll(wbdir) // cleanup writeback/shadir removeAll(wbdir) // cleanup writeback/shadir
@@ -1273,7 +1273,7 @@ func (c *diskCache) NewMultipartUpload(ctx context.Context, bucket, object, uID
cachePath := getMultipartCacheSHADir(c.dir, bucket, object) cachePath := getMultipartCacheSHADir(c.dir, bucket, object)
uploadIDDir := path.Join(cachePath, uploadID) uploadIDDir := path.Join(cachePath, uploadID)
if err := mkdirAll(uploadIDDir, 0o777); err != nil { if err := mkdirAll(uploadIDDir, 0o777, c.dir); err != nil {
return uploadID, err return uploadID, err
} }
metaPath := pathJoin(uploadIDDir, cacheMetaJSONFile) metaPath := pathJoin(uploadIDDir, cacheMetaJSONFile)
@@ -1441,7 +1441,7 @@ func newCachePartEncryptReader(ctx context.Context, bucket, object string, partI
info := ObjectInfo{Size: size} info := ObjectInfo{Size: size}
wantSize = info.EncryptedSize() wantSize = info.EncryptedSize()
} }
hReader, err := hash.NewReader(content, wantSize, "", "", size) hReader, err := hash.NewReader(ctx, content, wantSize, "", "", size)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1570,9 +1570,9 @@ func (c *diskCache) CompleteMultipartUpload(ctx context.Context, bucket, object,
jsonSave(f, uploadMeta) jsonSave(f, uploadMeta)
for _, pi := range uploadedParts { for _, pi := range uploadedParts {
part := fmt.Sprintf("part.%d", pi.PartNumber) part := fmt.Sprintf("part.%d", pi.PartNumber)
renameAll(pathJoin(uploadIDDir, part), pathJoin(cachePath, part)) renameAll(pathJoin(uploadIDDir, part), pathJoin(cachePath, part), c.dir)
} }
renameAll(pathJoin(uploadIDDir, cacheMetaJSONFile), pathJoin(cachePath, cacheMetaJSONFile)) renameAll(pathJoin(uploadIDDir, cacheMetaJSONFile), pathJoin(cachePath, cacheMetaJSONFile), c.dir)
removeAll(uploadIDDir) // clean up any unused parts in the uploadIDDir removeAll(uploadIDDir) // clean up any unused parts in the uploadIDDir
return uploadMeta.ToObjectInfo(), nil return uploadMeta.ToObjectInfo(), nil
} }
+6 -6
View File
@@ -33,9 +33,9 @@ import (
"github.com/minio/minio/internal/disk" "github.com/minio/minio/internal/disk"
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
xnet "github.com/minio/pkg/net" xnet "github.com/minio/pkg/v2/net"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
"github.com/minio/pkg/wildcard" "github.com/minio/pkg/v2/wildcard"
) )
const ( const (
@@ -734,7 +734,7 @@ func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *
defer cLock.Unlock(lkctx) defer cLock.Unlock(lkctx)
// Initialize pipe to stream data to backend // Initialize pipe to stream data to backend
pipeReader, pipeWriter := io.Pipe() pipeReader, pipeWriter := io.Pipe()
hashReader, err := hash.NewReader(pipeReader, size, "", "", r.ActualSize()) hashReader, err := hash.NewReader(ctx, pipeReader, size, "", "", r.ActualSize())
if err != nil { if err != nil {
return return
} }
@@ -795,7 +795,7 @@ func (c *cacheObjects) uploadObject(ctx context.Context, oi ObjectInfo) {
if st == CommitComplete || st.String() == "" { if st == CommitComplete || st.String() == "" {
return return
} }
hashReader, err := hash.NewReader(cReader, oi.Size, "", "", oi.Size) hashReader, err := hash.NewReader(ctx, cReader, oi.Size, "", "", oi.Size)
if err != nil { if err != nil {
return return
} }
@@ -1059,7 +1059,7 @@ func (c *cacheObjects) PutObjectPart(ctx context.Context, bucket, object, upload
info = PartInfo{} info = PartInfo{}
// Initialize pipe to stream data to backend // Initialize pipe to stream data to backend
pipeReader, pipeWriter := io.Pipe() pipeReader, pipeWriter := io.Pipe()
hashReader, err := hash.NewReader(pipeReader, size, "", "", data.ActualSize()) hashReader, err := hash.NewReader(ctx, pipeReader, size, "", "", data.ActualSize())
if err != nil { if err != nil {
return return
} }
+1 -1
View File
@@ -22,7 +22,7 @@ import (
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
// Data types used for returning dummy tagging XML. // Data types used for returning dummy tagging XML.
+1 -1
View File
@@ -728,7 +728,7 @@ func (d *DecryptBlocksReader) Read(p []byte) (int, error) {
// DecryptedSize returns the size of the object after decryption in bytes. // DecryptedSize returns the size of the object after decryption in bytes.
// It returns an error if the object is not encrypted or marked as encrypted // It returns an error if the object is not encrypted or marked as encrypted
// but has an invalid size. // but has an invalid size.
func (o *ObjectInfo) DecryptedSize() (int64, error) { func (o ObjectInfo) DecryptedSize() (int64, error) {
if _, ok := crypto.IsEncrypted(o.UserDefined); !ok { if _, ok := crypto.IsEncrypted(o.UserDefined); !ok {
return 0, errors.New("Cannot compute decrypted size of an unencrypted object") return 0, errors.New("Cannot compute decrypted size of an unencrypted object")
} }
+2 -2
View File
@@ -26,8 +26,8 @@ import (
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/pkg/ellipses" "github.com/minio/pkg/v2/ellipses"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
) )
// This file implements and supports ellipses pattern for // This file implements and supports ellipses pattern for
+1 -1
View File
@@ -22,7 +22,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/minio/pkg/ellipses" "github.com/minio/pkg/v2/ellipses"
) )
// Tests create endpoints with ellipses and without. // Tests create endpoints with ellipses and without.
+2 -2
View File
@@ -36,8 +36,8 @@ import (
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/mountinfo" "github.com/minio/minio/internal/mountinfo"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
xnet "github.com/minio/pkg/net" xnet "github.com/minio/pkg/v2/net"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
+1 -57
View File
@@ -26,7 +26,7 @@ import (
"time" "time"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
) )
func (er erasureObjects) getOnlineDisks() (newDisks []StorageAPI) { func (er erasureObjects) getOnlineDisks() (newDisks []StorageAPI) {
@@ -74,62 +74,6 @@ func (er erasureObjects) getLoadBalancedLocalDisks() (newDisks []StorageAPI) {
return newDisks return newDisks
} }
// getLoadBalancedDisks - fetches load balanced (sufficiently randomized) disk slice.
// ensures to skip disks if they are not healing and online.
func (er erasureObjects) getLoadBalancedDisks(optimized bool) []StorageAPI {
disks := er.getDisks()
r := rand.New(rand.NewSource(time.Now().UnixNano()))
if !optimized {
var newDisks []StorageAPI
for _, i := range r.Perm(len(disks)) {
newDisks = append(newDisks, disks[i])
}
return newDisks
}
var wg sync.WaitGroup
var mu sync.Mutex
newDisks := map[uint64][]StorageAPI{}
// Based on the random shuffling return back randomized disks.
for _, i := range r.Perm(len(disks)) {
i := i
wg.Add(1)
go func() {
defer wg.Done()
if disks[i] == nil {
return
}
di, err := disks[i].DiskInfo(context.Background(), false)
if err != nil || di.Healing {
// - Do not consume disks which are not reachable
// unformatted or simply not accessible for some reason.
//
// - Do not consume disks which are being healed
//
// - Future: skip busy disks
return
}
mu.Lock()
// Capture disks usage wise upto resolution of MiB
newDisks[di.Used/1024/1024] = append(newDisks[di.Used/1024/1024], disks[i])
mu.Unlock()
}()
}
wg.Wait()
var max uint64
for k := range newDisks {
if k > max {
max = k
}
}
// Return disks which have maximum disk usage common.
return newDisks[max]
}
// readMultipleFiles Reads raw data from all specified files from all disks. // readMultipleFiles Reads raw data from all specified files from all disks.
func readMultipleFiles(ctx context.Context, disks []StorageAPI, req ReadMultipleReq, readQuorum int) ([]ReadMultipleResp, error) { func readMultipleFiles(ctx context.Context, disks []StorageAPI, req ReadMultipleReq, readQuorum int) ([]ReadMultipleResp, error) {
resps := make([]chan ReadMultipleResp, len(disks)) resps := make([]chan ReadMultipleResp, len(disks))
+1 -1
View File
@@ -620,7 +620,7 @@ func TestDisksWithAllParts(t *testing.T) {
diskFailures[15] = "part.1" diskFailures[15] = "part.1"
for diskIndex, partName := range diskFailures { for diskIndex, partName := range diskFailures {
for i := range partsMetadata[diskIndex].Erasure.Checksums { for i := range partsMetadata[diskIndex].Parts {
if fmt.Sprintf("part.%d", i+1) == partName { if fmt.Sprintf("part.%d", i+1) == partName {
filePath := pathJoin(erasureDisks[diskIndex].String(), bucket, object, partsMetadata[diskIndex].DataDir, partName) filePath := pathJoin(erasureDisks[diskIndex].String(), bucket, object, partsMetadata[diskIndex].DataDir, partName)
f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_SYNC, 0) f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_SYNC, 0)
+37 -11
View File
@@ -29,7 +29,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
) )
const reservedMetadataPrefixLowerDataShardFix = ReservedMetadataPrefixLower + "data-shard-fix" const reservedMetadataPrefixLowerDataShardFix = ReservedMetadataPrefixLower + "data-shard-fix"
@@ -563,11 +563,26 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
} }
if !latestMeta.XLV1 && !latestMeta.Deleted && !recreate && disksToHealCount > latestMeta.Erasure.ParityBlocks { if !latestMeta.XLV1 && !latestMeta.Deleted && !recreate && disksToHealCount > latestMeta.Erasure.ParityBlocks {
// When disk to heal count is greater than parity blocks we should simply error out. // Allow for dangling deletes, on versions that have DataDir missing etc.
err := fmt.Errorf("(%d > %d) more drives are expected to heal than parity, returned errors: %v (dataErrs %v) -> %s/%s(%s)", disksToHealCount, latestMeta.Erasure.ParityBlocks, errs, dataErrs, bucket, object, versionID) // this would end up restoring the correct readable versions.
logger.LogOnceIf(ctx, err, "heal-object-count-gt-parity") m, err := er.deleteIfDangling(ctx, bucket, object, partsMetadata, errs, dataErrs, ObjectOptions{
return er.defaultHealResult(latestMeta, storageDisks, storageEndpoints, errs, VersionID: versionID,
bucket, object, versionID), err })
errs = make([]error, len(errs))
for i := range errs {
errs[i] = err
}
if err == nil {
// Dangling object successfully purged, size is '0'
m.Size = 0
}
// Generate file/version not found with default heal result
err = errFileNotFound
if versionID != "" {
err = errFileVersionNotFound
}
return er.defaultHealResult(m, storageDisks, storageEndpoints,
errs, bucket, object, versionID), err
} }
cleanFileInfo := func(fi FileInfo) FileInfo { cleanFileInfo := func(fi FileInfo) FileInfo {
@@ -714,11 +729,6 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
partsMetadata[i].DataDir = dstDataDir partsMetadata[i].DataDir = dstDataDir
partsMetadata[i].AddObjectPart(partNumber, "", partSize, partActualSize, partModTime, partIdx, partChecksums) partsMetadata[i].AddObjectPart(partNumber, "", partSize, partActualSize, partModTime, partIdx, partChecksums)
partsMetadata[i].Erasure.AddChecksumInfo(ChecksumInfo{
PartNumber: partNumber,
Algorithm: checksumAlgo,
Hash: bitrotWriterSum(writers[i]),
})
if len(inlineBuffers) > 0 && inlineBuffers[i] != nil { if len(inlineBuffers) > 0 && inlineBuffers[i] != nil {
partsMetadata[i].Data = inlineBuffers[i].Bytes() partsMetadata[i].Data = inlineBuffers[i].Bytes()
partsMetadata[i].SetInlineData() partsMetadata[i].SetInlineData()
@@ -1082,6 +1092,22 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
} }
if !validMeta.IsValid() { if !validMeta.IsValid() {
// validMeta is invalid because notFoundPartsErrs is
// greater than parity blocks, thus invalidating the FileInfo{}
// every dataErrs[i], metaArr[i] is an empty FileInfo{}
dataBlocks := (len(ndataErrs) + 1) / 2
if notFoundPartsErrs > dataBlocks {
// Not using parity to ensure that we do not delete
// any valid content, if any is recoverable. But if
// notFoundDataDirs are already greater than the data
// blocks all bets are off and it is safe to purge.
//
// This is purely a defensive code, ideally parityBlocks
// is sufficient, however we can't know that since we
// do have the FileInfo{}.
return validMeta, true
}
// We have no idea what this file is, leave it as is. // We have no idea what this file is, leave it as is.
return validMeta, false return validMeta, false
} }
+1 -1
View File
@@ -25,7 +25,7 @@ import (
"io" "io"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
) )
// figure out the most commonVersions across disk that satisfies // figure out the most commonVersions across disk that satisfies
+28 -14
View File
@@ -32,7 +32,7 @@ import (
"github.com/minio/minio/internal/hash/sha256" "github.com/minio/minio/internal/hash/sha256"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
"github.com/minio/sio" "github.com/minio/sio"
) )
@@ -41,17 +41,6 @@ const minIOErasureUpgraded = "x-minio-internal-erasure-upgraded"
const erasureAlgorithm = "rs-vandermonde" const erasureAlgorithm = "rs-vandermonde"
// AddChecksumInfo adds a checksum of a part.
func (e *ErasureInfo) AddChecksumInfo(ckSumInfo ChecksumInfo) {
for i, sum := range e.Checksums {
if sum.PartNumber == ckSumInfo.PartNumber {
e.Checksums[i] = ckSumInfo
return
}
}
e.Checksums = append(e.Checksums, ckSumInfo)
}
// GetChecksumInfo - get checksum of a part. // GetChecksumInfo - get checksum of a part.
func (e ErasureInfo) GetChecksumInfo(partNumber int) (ckSum ChecksumInfo) { func (e ErasureInfo) GetChecksumInfo(partNumber int) (ckSum ChecksumInfo) {
for _, sum := range e.Checksums { for _, sum := range e.Checksums {
@@ -60,7 +49,7 @@ func (e ErasureInfo) GetChecksumInfo(partNumber int) (ckSum ChecksumInfo) {
return sum return sum
} }
} }
return ChecksumInfo{} return ChecksumInfo{Algorithm: DefaultBitrotAlgorithm}
} }
// ShardFileSize - returns final erasure size from original size. // ShardFileSize - returns final erasure size from original size.
@@ -393,14 +382,39 @@ func findFileInfoInQuorum(ctx context.Context, metaArr []FileInfo, modTime time.
return FileInfo{}, errErasureReadQuorum return FileInfo{}, errErasureReadQuorum
} }
// Find the successor mod time in quorum, otherwise leave the
// candidate's successor modTime as found
succModTimeMap := make(map[time.Time]int)
var candidate FileInfo
var found bool
for i, hash := range metaHashes { for i, hash := range metaHashes {
if hash == maxHash { if hash == maxHash {
if metaArr[i].IsValid() { if metaArr[i].IsValid() {
return metaArr[i], nil if !found {
candidate = metaArr[i]
found = true
}
succModTimeMap[metaArr[i].SuccessorModTime]++
} }
} }
} }
var succModTime time.Time
var smodTimeQuorum bool
for smodTime, count := range succModTimeMap {
if count >= quorum {
smodTimeQuorum = true
succModTime = smodTime
break
}
}
if found {
if smodTimeQuorum {
candidate.SuccessorModTime = succModTime
candidate.IsLatest = succModTime.IsZero()
}
return candidate, nil
}
return FileInfo{}, errErasureReadQuorum return FileInfo{}, errErasureReadQuorum
} }
+53 -9
View File
@@ -158,7 +158,7 @@ func TestObjectToPartOffset(t *testing.T) {
} }
func TestFindFileInfoInQuorum(t *testing.T) { func TestFindFileInfoInQuorum(t *testing.T) {
getNFInfo := func(n int, quorum int, t int64, dataDir string) []FileInfo { getNFInfo := func(n int, quorum int, t int64, dataDir string, succModTimes []time.Time) []FileInfo {
fi := newFileInfo("test", 8, 8) fi := newFileInfo("test", 8, 8)
fi.AddObjectPart(1, "etag", 100, 100, UTCNow(), nil, nil) fi.AddObjectPart(1, "etag", 100, 100, UTCNow(), nil, nil)
fi.ModTime = time.Unix(t, 0) fi.ModTime = time.Unix(t, 0)
@@ -167,6 +167,10 @@ func TestFindFileInfoInQuorum(t *testing.T) {
for i := range fis { for i := range fis {
fis[i] = fi fis[i] = fi
fis[i].Erasure.Index = i + 1 fis[i].Erasure.Index = i + 1
if succModTimes != nil {
fis[i].SuccessorModTime = succModTimes[i]
fis[i].IsLatest = succModTimes[i].IsZero()
}
quorum-- quorum--
if quorum == 0 { if quorum == 0 {
break break
@@ -175,39 +179,79 @@ func TestFindFileInfoInQuorum(t *testing.T) {
return fis return fis
} }
commonSuccModTime := time.Date(2023, time.August, 25, 0, 0, 0, 0, time.UTC)
succModTimesInQuorum := make([]time.Time, 16)
succModTimesNoQuorum := make([]time.Time, 16)
for i := 0; i < 16; i++ {
if i < 4 {
continue
}
succModTimesInQuorum[i] = commonSuccModTime
if i < 9 {
continue
}
succModTimesNoQuorum[i] = commonSuccModTime
}
tests := []struct { tests := []struct {
fis []FileInfo fis []FileInfo
modTime time.Time modTime time.Time
expectedErr error succmodTimes []time.Time
expectedQuorum int expectedErr error
expectedQuorum int
expectedSuccModTime time.Time
expectedIsLatest bool
}{ }{
{ {
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21"), fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil),
modTime: time.Unix(1603863445, 0), modTime: time.Unix(1603863445, 0),
expectedErr: nil, expectedErr: nil,
expectedQuorum: 8, expectedQuorum: 8,
}, },
{ {
fis: getNFInfo(16, 7, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21"), fis: getNFInfo(16, 7, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil),
modTime: time.Unix(1603863445, 0), modTime: time.Unix(1603863445, 0),
expectedErr: errErasureReadQuorum, expectedErr: errErasureReadQuorum,
expectedQuorum: 8, expectedQuorum: 8,
}, },
{ {
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21"), fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil),
modTime: time.Unix(1603863445, 0), modTime: time.Unix(1603863445, 0),
expectedErr: errErasureReadQuorum, expectedErr: errErasureReadQuorum,
expectedQuorum: 0, expectedQuorum: 0,
}, },
{
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", succModTimesInQuorum),
modTime: time.Unix(1603863445, 0),
expectedErr: nil,
expectedQuorum: 12,
expectedSuccModTime: commonSuccModTime,
expectedIsLatest: false,
},
{
fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", succModTimesNoQuorum),
modTime: time.Unix(1603863445, 0),
expectedErr: nil,
expectedQuorum: 12,
expectedSuccModTime: time.Time{},
expectedIsLatest: true,
},
} }
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run("", func(t *testing.T) { t.Run("", func(t *testing.T) {
_, err := findFileInfoInQuorum(context.Background(), test.fis, test.modTime, "", test.expectedQuorum) fi, err := findFileInfoInQuorum(context.Background(), test.fis, test.modTime, "", test.expectedQuorum)
if err != test.expectedErr { if err != test.expectedErr {
t.Errorf("Expected %s, got %s", test.expectedErr, err) t.Errorf("Expected %s, got %s", test.expectedErr, err)
} }
if test.succmodTimes != nil {
if !test.expectedSuccModTime.Equal(fi.SuccessorModTime) {
t.Errorf("Expected successor mod time to be %v but got %v", test.expectedSuccModTime, fi.SuccessorModTime)
}
if test.expectedIsLatest != fi.IsLatest {
t.Errorf("Expected IsLatest to be %v but got %v", test.expectedIsLatest, fi.IsLatest)
}
}
}) })
} }
} }
+14 -4
View File
@@ -38,8 +38,8 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/mimedb" "github.com/minio/pkg/v2/mimedb"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
uatomic "go.uber.org/atomic" uatomic "go.uber.org/atomic"
) )
@@ -269,7 +269,13 @@ func (er erasureObjects) ListMultipartUploads(ctx context.Context, bucket, objec
var uploadIDs []string var uploadIDs []string
var disk StorageAPI var disk StorageAPI
for _, disk = range er.getLoadBalancedDisks(true) { disks := er.getLoadBalancedLocalDisks()
if len(disks) == 0 {
// using er.getLoadBalancedLocalDisks() has one side-affect where
// on a pooled setup all disks are remote, add a fallback
disks = er.getOnlineDisks()
}
for _, disk = range disks {
uploadIDs, err = disk.ListDir(ctx, minioMetaMultipartBucket, er.getMultipartSHADir(bucket, object), -1) uploadIDs, err = disk.ListDir(ctx, minioMetaMultipartBucket, er.getMultipartSHADir(bucket, object), -1)
if err != nil { if err != nil {
if errors.Is(err, errDiskNotFound) { if errors.Is(err, errDiskNotFound) {
@@ -1242,7 +1248,11 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
} }
} }
defer er.deleteAll(context.Background(), minioMetaMultipartBucket, uploadIDPath) defer func() {
if err == nil {
er.deleteAll(context.Background(), minioMetaMultipartBucket, uploadIDPath)
}
}()
// Rename the multipart object to final location. // Rename the multipart object to final location.
onlineDisks, versionsDisparity, err := renameData(ctx, onlineDisks, minioMetaMultipartBucket, uploadIDPath, onlineDisks, versionsDisparity, err := renameData(ctx, onlineDisks, minioMetaMultipartBucket, uploadIDPath,
+24 -23
View File
@@ -43,9 +43,10 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/mimedb" "github.com/minio/pkg/v2/mimedb"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
"github.com/minio/pkg/wildcard" "github.com/minio/pkg/v2/wildcard"
"github.com/tinylib/msgp/msgp"
uatomic "go.uber.org/atomic" uatomic "go.uber.org/atomic"
) )
@@ -93,7 +94,7 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
if srcOpts.VersionID != "" { if srcOpts.VersionID != "" {
metaArr, errs = readAllFileInfo(ctx, storageDisks, srcBucket, srcObject, srcOpts.VersionID, true) metaArr, errs = readAllFileInfo(ctx, storageDisks, srcBucket, srcObject, srcOpts.VersionID, true)
} else { } else {
metaArr, errs = readAllXL(ctx, storageDisks, srcBucket, srcObject, true, false) metaArr, errs = readAllXL(ctx, storageDisks, srcBucket, srcObject, true, false, true)
} }
readQuorum, writeQuorum, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount) readQuorum, writeQuorum, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
@@ -497,7 +498,14 @@ func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object st
tags := make(map[string]interface{}, 4) tags := make(map[string]interface{}, 4)
tags["set"] = er.setIndex tags["set"] = er.setIndex
tags["pool"] = er.poolIndex tags["pool"] = er.poolIndex
tags["parity"] = m.Erasure.ParityBlocks tags["merrs"] = errors.Join(errs...)
tags["derrs"] = errors.Join(dataErrs...)
if m.IsValid() {
tags["size"] = m.Size
tags["mtime"] = m.ModTime.Format(http.TimeFormat)
tags["parity"] = m.Erasure.ParityBlocks
}
if cok { if cok {
tags["caller"] = fmt.Sprintf("%s:%d", file, line) tags["caller"] = fmt.Sprintf("%s:%d", file, line)
} }
@@ -533,7 +541,7 @@ func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object st
return m, err return m, err
} }
func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, readData, inclFreeVers bool) ([]FileInfo, []error) { func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, readData, inclFreeVers, allParts bool) ([]FileInfo, []error) {
metadataArray := make([]*xlMetaV2, len(disks)) metadataArray := make([]*xlMetaV2, len(disks))
metaFileInfos := make([]FileInfo, len(metadataArray)) metaFileInfos := make([]FileInfo, len(metadataArray))
metadataShallowVersions := make([][]xlMetaV2ShallowVersion, len(disks)) metadataShallowVersions := make([][]xlMetaV2ShallowVersion, len(disks))
@@ -578,6 +586,9 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
errFileVersionNotFound, errFileVersionNotFound,
io.ErrUnexpectedEOF, // some times we would read without locks, ignore these errors io.ErrUnexpectedEOF, // some times we would read without locks, ignore these errors
io.EOF, // some times we would read without locks, ignore these errors io.EOF, // some times we would read without locks, ignore these errors
msgp.ErrShortBytes,
context.DeadlineExceeded,
context.Canceled,
} }
ignoredErrs = append(ignoredErrs, objectOpIgnoredErrs...) ignoredErrs = append(ignoredErrs, objectOpIgnoredErrs...)
@@ -601,7 +612,7 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
readQuorum := (len(disks) + 1) / 2 readQuorum := (len(disks) + 1) / 2
meta := &xlMetaV2{versions: mergeXLV2Versions(readQuorum, false, 1, metadataShallowVersions...)} meta := &xlMetaV2{versions: mergeXLV2Versions(readQuorum, false, 1, metadataShallowVersions...)}
lfi, err := meta.ToFileInfo(bucket, object, "", inclFreeVers) lfi, err := meta.ToFileInfo(bucket, object, "", inclFreeVers, allParts)
if err != nil { if err != nil {
for i := range errs { for i := range errs {
if errs[i] == nil { if errs[i] == nil {
@@ -631,7 +642,7 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
// make sure to preserve this for diskmtime based healing bugfix. // make sure to preserve this for diskmtime based healing bugfix.
diskMTime := metaFileInfos[index].DiskMTime diskMTime := metaFileInfos[index].DiskMTime
metaFileInfos[index], errs[index] = metadataArray[index].ToFileInfo(bucket, object, versionID, inclFreeVers) metaFileInfos[index], errs[index] = metadataArray[index].ToFileInfo(bucket, object, versionID, inclFreeVers, allParts)
if errs[index] != nil { if errs[index] != nil {
continue continue
} }
@@ -660,7 +671,7 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
if opts.VersionID != "" { if opts.VersionID != "" {
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, readData) metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, readData)
} else { } else {
metaArr, errs = readAllXL(ctx, disks, bucket, object, readData, opts.InclFreeVersions) metaArr, errs = readAllXL(ctx, disks, bucket, object, readData, opts.InclFreeVersions, true)
} }
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount) readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
@@ -960,11 +971,6 @@ func (er erasureObjects) putMetacacheObject(ctx context.Context, key string, r *
} }
partsMetadata[i].Data = inlineBuffers[i].Bytes() partsMetadata[i].Data = inlineBuffers[i].Bytes()
partsMetadata[i].AddObjectPart(1, "", n, data.ActualSize(), modTime, index, nil) partsMetadata[i].AddObjectPart(1, "", n, data.ActualSize(), modTime, index, nil)
partsMetadata[i].Erasure.AddChecksumInfo(ChecksumInfo{
PartNumber: 1,
Algorithm: DefaultBitrotAlgorithm,
Hash: bitrotWriterSum(w),
})
} }
// Fill all the necessary metadata. // Fill all the necessary metadata.
@@ -1296,11 +1302,6 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
} }
// No need to add checksum to part. We already have it on the object. // No need to add checksum to part. We already have it on the object.
partsMetadata[i].AddObjectPart(1, "", n, data.ActualSize(), modTime, compIndex, nil) partsMetadata[i].AddObjectPart(1, "", n, data.ActualSize(), modTime, compIndex, nil)
partsMetadata[i].Erasure.AddChecksumInfo(ChecksumInfo{
PartNumber: 1,
Algorithm: DefaultBitrotAlgorithm,
Hash: bitrotWriterSum(w),
})
partsMetadata[i].Versioned = opts.Versioned || opts.VersionSuspended partsMetadata[i].Versioned = opts.Versioned || opts.VersionSuspended
} }
@@ -1844,7 +1845,7 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s
if opts.VersionID != "" { if opts.VersionID != "" {
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false) metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false)
} else { } else {
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false) metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false, true)
} }
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount) readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
@@ -1917,7 +1918,7 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
if opts.VersionID != "" { if opts.VersionID != "" {
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false) metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false)
} else { } else {
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false) metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false, true)
} }
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount) readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
@@ -2161,7 +2162,7 @@ func (er erasureObjects) restoreTransitionedObject(ctx context.Context, bucket s
return setRestoreHeaderFn(oi, toObjectErr(err, bucket, object)) return setRestoreHeaderFn(oi, toObjectErr(err, bucket, object))
} }
defer gr.Close() defer gr.Close()
hashReader, err := hash.NewReader(gr, gr.ObjInfo.Size, "", "", gr.ObjInfo.Size) hashReader, err := hash.NewReader(ctx, gr, gr.ObjInfo.Size, "", "", gr.ObjInfo.Size)
if err != nil { if err != nil {
return setRestoreHeaderFn(oi, toObjectErr(err, bucket, object)) return setRestoreHeaderFn(oi, toObjectErr(err, bucket, object))
} }
@@ -2186,7 +2187,7 @@ func (er erasureObjects) restoreTransitionedObject(ctx context.Context, bucket s
// rehydrate the parts back on disk as per the original xl.meta prior to transition // rehydrate the parts back on disk as per the original xl.meta prior to transition
for _, partInfo := range oi.Parts { for _, partInfo := range oi.Parts {
hr, err := hash.NewReader(io.LimitReader(gr, partInfo.Size), partInfo.Size, "", "", partInfo.Size) hr, err := hash.NewReader(ctx, io.LimitReader(gr, partInfo.Size), partInfo.Size, "", "", partInfo.Size)
if err != nil { if err != nil {
return setRestoreHeaderFn(oi, err) return setRestoreHeaderFn(oi, err)
} }
+59 -22
View File
@@ -34,9 +34,9 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/console" "github.com/minio/pkg/v2/console"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
"github.com/minio/pkg/workers" "github.com/minio/pkg/v2/workers"
) )
// PoolDecommissionInfo currently decommissioning information // PoolDecommissionInfo currently decommissioning information
@@ -68,6 +68,34 @@ type PoolDecommissionInfo struct {
BytesFailed int64 `json:"bytesDecommissionedFailed" msg:"bf"` BytesFailed int64 `json:"bytesDecommissionedFailed" msg:"bf"`
} }
// Clone make a copy of PoolDecommissionInfo
func (pd *PoolDecommissionInfo) Clone() *PoolDecommissionInfo {
if pd == nil {
return nil
}
if pd.StartTime.IsZero() {
return nil
}
return &PoolDecommissionInfo{
StartTime: pd.StartTime,
StartSize: pd.StartSize,
TotalSize: pd.TotalSize,
CurrentSize: pd.CurrentSize,
Complete: pd.Complete,
Failed: pd.Failed,
Canceled: pd.Canceled,
QueuedBuckets: pd.QueuedBuckets,
DecommissionedBuckets: pd.DecommissionedBuckets,
Bucket: pd.Bucket,
Prefix: pd.Prefix,
Object: pd.Object,
ItemsDecommissioned: pd.ItemsDecommissioned,
ItemsDecommissionFailed: pd.ItemsDecommissionFailed,
BytesDone: pd.BytesDone,
BytesFailed: pd.BytesFailed,
}
}
// bucketPop should be called when a bucket is done decommissioning. // bucketPop should be called when a bucket is done decommissioning.
// Adds the bucket to the list of decommissioned buckets and updates resume numbers. // Adds the bucket to the list of decommissioned buckets and updates resume numbers.
func (pd *PoolDecommissionInfo) bucketPop(bucket string) { func (pd *PoolDecommissionInfo) bucketPop(bucket string) {
@@ -118,6 +146,16 @@ type PoolStatus struct {
Decommission *PoolDecommissionInfo `json:"decommissionInfo,omitempty" msg:"dec"` Decommission *PoolDecommissionInfo `json:"decommissionInfo,omitempty" msg:"dec"`
} }
// Clone returns a copy of PoolStatus
func (ps PoolStatus) Clone() PoolStatus {
return PoolStatus{
ID: ps.ID,
CmdLine: ps.CmdLine,
LastUpdate: ps.LastUpdate,
Decommission: ps.Decommission.Clone(),
}
}
//go:generate msgp -file $GOFILE -unexported //go:generate msgp -file $GOFILE -unexported
type poolMeta struct { type poolMeta struct {
Version int `msg:"v"` Version int `msg:"v"`
@@ -375,16 +413,17 @@ func (p *poolMeta) load(ctx context.Context, pool *erasureSets, pools []*erasure
func (p *poolMeta) CountItem(idx int, size int64, failed bool) { func (p *poolMeta) CountItem(idx int, size int64, failed bool) {
pd := p.Pools[idx].Decommission pd := p.Pools[idx].Decommission
if pd != nil { if pd == nil {
if failed { return
pd.ItemsDecommissionFailed++
pd.BytesFailed += size
} else {
pd.ItemsDecommissioned++
pd.BytesDone += size
}
p.Pools[idx].Decommission = pd
} }
if failed {
pd.ItemsDecommissionFailed++
pd.BytesFailed += size
} else {
pd.ItemsDecommissioned++
pd.BytesDone += size
}
p.Pools[idx].Decommission = pd
} }
func (p *poolMeta) updateAfter(ctx context.Context, idx int, pools []*erasureSets, duration time.Duration) (bool, error) { func (p *poolMeta) updateAfter(ctx context.Context, idx int, pools []*erasureSets, duration time.Duration) (bool, error) {
@@ -569,7 +608,7 @@ func (z *erasureServerPools) decommissionObject(ctx context.Context, bucket stri
defer z.AbortMultipartUpload(ctx, bucket, objInfo.Name, res.UploadID, ObjectOptions{}) defer z.AbortMultipartUpload(ctx, bucket, objInfo.Name, res.UploadID, ObjectOptions{})
parts := make([]CompletePart, len(objInfo.Parts)) parts := make([]CompletePart, len(objInfo.Parts))
for i, part := range objInfo.Parts { for i, part := range objInfo.Parts {
hr, err := hash.NewReader(io.LimitReader(gr, part.Size), part.Size, "", "", part.ActualSize) hr, err := hash.NewReader(ctx, io.LimitReader(gr, part.Size), part.Size, "", "", part.ActualSize)
if err != nil { if err != nil {
return fmt.Errorf("decommissionObject: hash.NewReader() %w", err) return fmt.Errorf("decommissionObject: hash.NewReader() %w", err)
} }
@@ -603,7 +642,7 @@ func (z *erasureServerPools) decommissionObject(ctx context.Context, bucket stri
return err return err
} }
hr, err := hash.NewReader(io.LimitReader(gr, objInfo.Size), objInfo.Size, "", "", actualSize) hr, err := hash.NewReader(ctx, io.LimitReader(gr, objInfo.Size), objInfo.Size, "", "", actualSize)
if err != nil { if err != nil {
return fmt.Errorf("decommissionObject: hash.NewReader() %w", err) return fmt.Errorf("decommissionObject: hash.NewReader() %w", err)
} }
@@ -696,8 +735,6 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
// Check if bucket is object locked. // Check if bucket is object locked.
lr, _ := globalBucketObjectLockSys.Get(bi.Name) lr, _ := globalBucketObjectLockSys.Get(bi.Name)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for setIdx, set := range pool.sets { for setIdx, set := range pool.sets {
set := set set := set
@@ -908,11 +945,11 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
go decommissionEntry(entry) go decommissionEntry(entry)
}, },
) )
if err == nil { if err == nil || errors.Is(err, context.Canceled) {
break break
} }
setN := humanize.Ordinal(setIdx + 1) setN := humanize.Ordinal(setIdx + 1)
retryDur := time.Duration(r.Float64() * float64(5*time.Second)) retryDur := time.Duration(rand.Float64() * float64(5*time.Second))
logger.LogOnceIf(ctx, fmt.Errorf("listing objects from %s set failed with %v, retrying in %v", setN, err, retryDur), "decom-listing-failed"+setN) logger.LogOnceIf(ctx, fmt.Errorf("listing objects from %s set failed with %v, retrying in %v", setN, err, retryDur), "decom-listing-failed"+setN)
time.Sleep(retryDur) time.Sleep(retryDur)
} }
@@ -1185,15 +1222,15 @@ func (z *erasureServerPools) Status(ctx context.Context, idx int) (PoolStatus, e
return PoolStatus{}, errInvalidArgument return PoolStatus{}, errInvalidArgument
} }
z.poolMetaMutex.RLock()
defer z.poolMetaMutex.RUnlock()
pi, err := z.getDecommissionPoolSpaceInfo(idx) pi, err := z.getDecommissionPoolSpaceInfo(idx)
if err != nil { if err != nil {
return PoolStatus{}, err return PoolStatus{}, err
} }
poolInfo := z.poolMeta.Pools[idx] z.poolMetaMutex.RLock()
defer z.poolMetaMutex.RUnlock()
poolInfo := z.poolMeta.Pools[idx].Clone()
if poolInfo.Decommission != nil { if poolInfo.Decommission != nil {
poolInfo.Decommission.TotalSize = pi.Total poolInfo.Decommission.TotalSize = pi.Total
if poolInfo.Decommission.Failed || poolInfo.Decommission.Canceled { if poolInfo.Decommission.Failed || poolInfo.Decommission.Canceled {
+3 -3
View File
@@ -35,7 +35,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
) )
//go:generate msgp -file $GOFILE -unexported //go:generate msgp -file $GOFILE -unexported
@@ -736,7 +736,7 @@ func (z *erasureServerPools) rebalanceObject(ctx context.Context, bucket string,
parts := make([]CompletePart, len(oi.Parts)) parts := make([]CompletePart, len(oi.Parts))
for i, part := range oi.Parts { for i, part := range oi.Parts {
hr, err := hash.NewReader(io.LimitReader(gr, part.Size), part.Size, "", "", part.ActualSize) hr, err := hash.NewReader(ctx, io.LimitReader(gr, part.Size), part.Size, "", "", part.ActualSize)
if err != nil { if err != nil {
return fmt.Errorf("rebalanceObject: hash.NewReader() %w", err) return fmt.Errorf("rebalanceObject: hash.NewReader() %w", err)
} }
@@ -766,7 +766,7 @@ func (z *erasureServerPools) rebalanceObject(ctx context.Context, bucket string,
return err return err
} }
hr, err := hash.NewReader(gr, oi.Size, "", "", actualSize) hr, err := hash.NewReader(ctx, gr, oi.Size, "", "", actualSize)
if err != nil { if err != nil {
return fmt.Errorf("rebalanceObject: hash.NewReader() %w", err) return fmt.Errorf("rebalanceObject: hash.NewReader() %w", err)
} }
+34 -2
View File
@@ -39,8 +39,8 @@ import (
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/internal/config/storageclass" "github.com/minio/minio/internal/config/storageclass"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
"github.com/minio/pkg/wildcard" "github.com/minio/pkg/v2/wildcard"
) )
type erasureServerPools struct { type erasureServerPools struct {
@@ -1716,10 +1716,42 @@ func (z *erasureServerPools) deleteAll(ctx context.Context, bucket, prefix strin
} }
} }
var listBucketsCache timedValue
// List all buckets from one of the serverPools, we are not doing merge // List all buckets from one of the serverPools, we are not doing merge
// sort here just for simplification. As per design it is assumed // sort here just for simplification. As per design it is assumed
// that all buckets are present on all serverPools. // that all buckets are present on all serverPools.
func (z *erasureServerPools) ListBuckets(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) { func (z *erasureServerPools) ListBuckets(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
if opts.Cached {
listBucketsCache.Once.Do(func() {
listBucketsCache.TTL = time.Second
listBucketsCache.Relax = true
listBucketsCache.Update = func() (interface{}, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
buckets, err = z.s3Peer.ListBuckets(ctx, opts)
cancel()
if err != nil {
return nil, err
}
for i := range buckets {
createdAt, err := globalBucketMetadataSys.CreatedAt(buckets[i].Name)
if err == nil {
buckets[i].Created = createdAt
}
}
return buckets, nil
}
})
v, _ := listBucketsCache.Get()
if v != nil {
return v.([]BucketInfo), nil
}
return buckets, nil
}
buckets, err = z.s3Peer.ListBuckets(ctx, opts) buckets, err = z.s3Peer.ListBuckets(ctx, opts)
if err != nil { if err != nil {
return nil, err return nil, err
+2 -2
View File
@@ -39,8 +39,8 @@ import (
"github.com/minio/minio/internal/bpool" "github.com/minio/minio/internal/bpool"
"github.com/minio/minio/internal/dsync" "github.com/minio/minio/internal/dsync"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/console" "github.com/minio/pkg/v2/console"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
) )
// setsDsyncLockers is encapsulated type for Close() // setsDsyncLockers is encapsulated type for Close()
+1 -2
View File
@@ -33,7 +33,7 @@ import (
"github.com/minio/minio/internal/dsync" "github.com/minio/minio/internal/dsync"
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
) )
// list all errors that can be ignore in a bucket operation. // list all errors that can be ignore in a bucket operation.
@@ -182,7 +182,6 @@ func getDisksInfo(disks []StorageAPI, endpoints []Endpoint) (disksInfo []madmin.
DiskIndex: endpoints[index].DiskIdx, DiskIndex: endpoints[index].DiskIdx,
} }
if disks[index] == OfflineDisk { if disks[index] == OfflineDisk {
logger.LogOnceIf(GlobalContext, fmt.Errorf("%s: %s", errDiskNotFound, endpoints[index]), "get-disks-info-offline-"+di.Endpoint)
di.State = diskErrToDriveState(errDiskNotFound) di.State = diskErrToDriveState(errDiskNotFound)
disksInfo[index] = di disksInfo[index] = di
return nil return nil
+1 -1
View File
@@ -29,7 +29,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/pubsub" "github.com/minio/minio/internal/pubsub"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
// EventNotifier - notifies external systems about events in MinIO. // EventNotifier - notifies external systems about events in MinIO.
+2 -2
View File
@@ -33,7 +33,7 @@ import (
"github.com/minio/minio/internal/config/storageclass" "github.com/minio/minio/internal/config/storageclass"
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/sync/errgroup" "github.com/minio/pkg/v2/sync/errgroup"
) )
const ( const (
@@ -275,7 +275,7 @@ func formatErasureMigrateV2ToV3(data []byte, export, version string) ([]byte, er
tmpOld := pathJoin(export, minioMetaTmpDeletedBucket, mustGetUUID()) tmpOld := pathJoin(export, minioMetaTmpDeletedBucket, mustGetUUID())
if err := renameAll(pathJoin(export, minioMetaMultipartBucket), if err := renameAll(pathJoin(export, minioMetaMultipartBucket),
tmpOld); err != nil && err != errFileNotFound { tmpOld, export); err != nil && err != errFileNotFound {
logger.LogIf(GlobalContext, fmt.Errorf("unable to rename (%s -> %s) %w, drive may be faulty please investigate", logger.LogIf(GlobalContext, fmt.Errorf("unable to rename (%s -> %s) %w, drive may be faulty please investigate",
pathJoin(export, minioMetaMultipartBucket), pathJoin(export, minioMetaMultipartBucket),
tmpOld, tmpOld,
+82 -44
View File
@@ -21,11 +21,11 @@ import (
"context" "context"
"crypto/subtle" "crypto/subtle"
"fmt" "fmt"
"io"
"net" "net"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/minio/cli" "github.com/minio/cli"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
@@ -75,6 +75,69 @@ func (log *minioLogger) PrintResponse(sessionID string, code int, message string
} }
} }
func acceptSFTPConnection(conn net.Conn, config *ssh.ServerConfig) {
// For SSH handshake keep a 2 minute deadline, as OpensSSH default.
conn.SetDeadline(time.Now().Add(2 * time.Minute))
// Before use, a handshake must be performed on the incoming net.Conn.
sconn, chans, reqs, err := ssh.NewServerConn(conn, config)
if err != nil {
return
}
// Once we are done with SSH handshake, remove deadline.
conn.SetDeadline(time.Time{})
// The incoming Request channel must be serviced.
go ssh.DiscardRequests(reqs)
// Service the incoming Channel channel.
for newChannel := range chans {
// Channels have a type, depending on the application level
// protocol intended. In the case of an SFTP session, this is "subsystem"
// with a payload string of "<length=4>sftp"
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
logger.LogOnceIf(context.Background(), fmt.Errorf("unable to accept the connection requests channel: %w", err), "accept-channel-sftp")
continue
}
// Sessions have out-of-band requests such as "shell",
// "pty-req" and "env". Here we handle only the
// "subsystem" request.
go func(in <-chan *ssh.Request) {
for req := range in {
ok := false
if req.Type == "subsystem" {
if len(req.Payload) > 4 && string(req.Payload[4:]) == "sftp" {
ok = true
go handleSFTPConnection(channel, sconn)
}
}
if req.WantReply {
// We only reply to SSH packets that have `sftp` payload, all other
// packets are rejected
req.Reply(ok, nil)
}
}
}(requests)
}
}
func handleSFTPConnection(channel ssh.Channel, sconn *ssh.ServerConn) {
// Create the server instance for the channel using the handler we created above.
server := sftp.NewRequestServer(channel, NewSFTPDriver(sconn.Permissions), sftp.WithRSAllocator())
defer server.Close()
server.Serve()
}
func startSFTPServer(c *cli.Context) { func startSFTPServer(c *cli.Context) {
args := c.StringSlice("sftp") args := c.StringSlice("sftp")
@@ -176,54 +239,29 @@ func startSFTPServer(c *cli.Context) {
logger.Info(fmt.Sprintf("MinIO SFTP Server listening on %s", net.JoinHostPort(publicIP, strconv.Itoa(port)))) logger.Info(fmt.Sprintf("MinIO SFTP Server listening on %s", net.JoinHostPort(publicIP, strconv.Itoa(port))))
var tempDelay time.Duration // how long to sleep on accept failure
for { for {
nConn, err := listener.Accept() conn, err := listener.Accept()
if err != nil { if err != nil {
logger.LogIf(context.Background(), err) // From https://github.com/golang/go/blob/4aa1efed4853ea067d665a952eee77c52faac774/src/net/http/server.go#L3046
continue if ne, ok := err.(net.Error); ok && ne.Temporary() {
} if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
// Before use, a handshake must be performed on the incoming net.Conn. } else {
sconn, chans, reqs, err := ssh.NewServerConn(nConn, config) tempDelay *= 2
if err != nil { }
logger.LogIf(context.Background(), err) if max := 1 * time.Second; tempDelay > max {
continue tempDelay = max
} }
logger.LogOnceIf(context.Background(), fmt.Errorf("error while accepting connections: %w, retrying in %s", err, tempDelay), "accept-limit-sftp")
// The incoming Request channel must be serviced. time.Sleep(tempDelay)
go ssh.DiscardRequests(reqs)
// Service the incoming Channel channel.
for newChannel := range chans {
// Channels have a type, depending on the application level
// protocol intended. In the case of an SFTP session, this is "subsystem"
// with a payload string of "<length=4>sftp"
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue continue
} }
channel, requests, err := newChannel.Accept() logger.Fatal(err, "unrecoverable error while accepting new connections")
if err != nil {
logger.Fatal(err, "unable to accept the connection requests channel")
}
// Sessions have out-of-band requests such as "shell",
// "pty-req" and "env". Here we handle only the
// "subsystem" request.
go func(in <-chan *ssh.Request) {
for req := range in {
// We only reply to SSH packets that have `sftp` payload.
req.Reply(req.Type == "subsystem" && string(req.Payload[4:]) == "sftp", nil)
}
}(requests)
server := sftp.NewRequestServer(channel, NewSFTPDriver(sconn.Permissions))
if err := server.Serve(); err == io.EOF {
server.Close()
} else if err != nil {
logger.Fatal(err, "unable to start SFTP server")
}
} }
go acceptSFTPConnection(conn, config)
} }
} }
+2 -2
View File
@@ -31,7 +31,7 @@ import (
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/minio/minio-go/v7/pkg/s3utils" "github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
xnet "github.com/minio/pkg/net" xnet "github.com/minio/pkg/v2/net"
"github.com/minio/minio/internal/amztime" "github.com/minio/minio/internal/amztime"
"github.com/minio/minio/internal/config/dns" "github.com/minio/minio/internal/config/dns"
@@ -152,7 +152,7 @@ func setBrowserRedirectMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
read := r.Method == http.MethodGet || r.Method == http.MethodHead read := r.Method == http.MethodGet || r.Method == http.MethodHead
// Re-direction is handled specifically for browser requests. // Re-direction is handled specifically for browser requests.
if guessIsBrowserReq(r) && read { if guessIsBrowserReq(r) && read && globalBrowserRedirect {
// Fetch the redirect location if any. // Fetch the redirect location if any.
if u := getRedirectLocation(r); u != nil { if u := getRedirectLocation(r); u != nil {
// Employ a temporary re-direct. // Employ a temporary re-direct.
+48 -37
View File
@@ -20,6 +20,7 @@ package cmd
import ( import (
"context" "context"
"fmt" "fmt"
"runtime"
"sort" "sort"
"time" "time"
@@ -28,8 +29,9 @@ import (
"github.com/minio/minio/internal/color" "github.com/minio/minio/internal/color"
"github.com/minio/minio/internal/config/storageclass" "github.com/minio/minio/internal/config/storageclass"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/console" "github.com/minio/pkg/v2/console"
"github.com/minio/pkg/wildcard" "github.com/minio/pkg/v2/wildcard"
"github.com/minio/pkg/v2/workers"
) )
const ( const (
@@ -132,30 +134,8 @@ func getLocalBackgroundHealStatus(ctx context.Context, o ObjectLayer) (madmin.Bg
return status, true return status, true
} }
func mustGetHealSequence(ctx context.Context) *healSequence {
// Get background heal sequence to send elements to heal
for {
globalHealStateLK.RLock()
hstate := globalBackgroundHealState
globalHealStateLK.RUnlock()
if hstate == nil {
time.Sleep(time.Second)
continue
}
bgSeq, ok := hstate.getHealSequenceByToken(bgHealingUUID)
if !ok {
time.Sleep(time.Second)
continue
}
return bgSeq
}
}
// healErasureSet lists and heals all objects in a specific erasure set // healErasureSet lists and heals all objects in a specific erasure set
func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string, tracker *healingTracker) error { func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string, tracker *healingTracker) error {
bgSeq := mustGetHealSequence(ctx)
scanMode := madmin.HealNormalScan scanMode := madmin.HealNormalScan
// Make sure to copy since `buckets slice` // Make sure to copy since `buckets slice`
@@ -173,6 +153,30 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
} }
} }
info, err := tracker.disk.DiskInfo(ctx, false)
if err != nil {
return fmt.Errorf("unable to get disk information before healing it: %w", err)
}
var numHealers uint64
if numCores := uint64(runtime.GOMAXPROCS(0)); info.NRRequests > numCores {
numHealers = numCores / 4
} else {
numHealers = info.NRRequests / 4
}
if numHealers < 4 {
numHealers = 4
}
// allow overriding this value as well..
if v := globalHealConfig.GetWorkers(); v > 0 {
numHealers = uint64(v)
}
logger.Info(fmt.Sprintf("Healing drive '%s' - use %d parallel workers.", tracker.disk.String(), numHealers))
jt, _ := workers.New(int(numHealers))
var retErr error var retErr error
// Heal all buckets with all objects // Heal all buckets with all objects
for _, bucket := range healBuckets { for _, bucket := range healBuckets {
@@ -267,6 +271,8 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
// Note: updates from healEntry to tracker must be sent on results channel. // Note: updates from healEntry to tracker must be sent on results channel.
healEntry := func(bucket string, entry metaCacheEntry) { healEntry := func(bucket string, entry metaCacheEntry) {
defer jt.Give()
if entry.name == "" && len(entry.metadata) == 0 { if entry.name == "" && len(entry.metadata) == 0 {
// ignore entries that don't have metadata. // ignore entries that don't have metadata.
return return
@@ -291,14 +297,17 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
} }
} }
// erasureObjects layer needs object names to be encoded
encodedEntryName := encodeDirObject(entry.name)
var result healEntryResult var result healEntryResult
fivs, err := entry.fileInfoVersions(bucket) fivs, err := entry.fileInfoVersions(bucket)
if err != nil { if err != nil {
err := bgSeq.queueHealTask(healSource{ _, err := er.HealObject(ctx, bucket, encodedEntryName, "",
bucket: bucket, madmin.HealOpts{
object: entry.name, ScanMode: scanMode,
versionID: "", Remove: healDeleteDangling,
}, madmin.HealItemObject) })
if err != nil { if err != nil {
if isErrObjectNotFound(err) { if isErrObjectNotFound(err) {
// queueing happens across namespace, ignore // queueing happens across namespace, ignore
@@ -321,11 +330,11 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
if version.ModTime.After(tracker.Started) { if version.ModTime.After(tracker.Started) {
continue continue
} }
if err := bgSeq.queueHealTask(healSource{ if _, err := er.HealObject(ctx, bucket, encodedEntryName,
bucket: bucket, version.VersionID, madmin.HealOpts{
object: version.Name, ScanMode: scanMode,
versionID: version.VersionID, Remove: healDeleteDangling,
}, madmin.HealItemObject); err != nil { }); err != nil {
if isErrObjectNotFound(err) { if isErrObjectNotFound(err) {
// queueing happens across namespace, ignore // queueing happens across namespace, ignore
// objects that are not found. // objects that are not found.
@@ -344,7 +353,6 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
} else { } else {
result = healEntrySuccess(uint64(version.Size)) result = healEntrySuccess(uint64(version.Size))
} }
bgSeq.logHeal(madmin.HealItemObject)
if !send(result) { if !send(result) {
return return
@@ -382,7 +390,8 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
minDisks: 1, minDisks: 1,
reportNotFound: false, reportNotFound: false,
agreed: func(entry metaCacheEntry) { agreed: func(entry metaCacheEntry) {
healEntry(actualBucket, entry) jt.Take()
go healEntry(actualBucket, entry)
}, },
partial: func(entries metaCacheEntries, _ []error) { partial: func(entries metaCacheEntries, _ []error) {
entry, ok := entries.resolve(&resolver) entry, ok := entries.resolve(&resolver)
@@ -391,10 +400,12 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
// proceed to heal nonetheless. // proceed to heal nonetheless.
entry, _ = entries.firstFound() entry, _ = entries.firstFound()
} }
healEntry(actualBucket, *entry) jt.Take()
go healEntry(actualBucket, *entry)
}, },
finished: nil, finished: nil,
}) })
jt.Wait() // synchronize all the concurrent heal jobs
close(results) close(results)
if err != nil { if err != nil {
// Set this such that when we return this function // Set this such that when we return this function
+8 -2
View File
@@ -52,8 +52,8 @@ import (
levent "github.com/minio/minio/internal/config/lambda/event" levent "github.com/minio/minio/internal/config/lambda/event"
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/pubsub" "github.com/minio/minio/internal/pubsub"
"github.com/minio/pkg/certs" "github.com/minio/pkg/v2/certs"
xnet "github.com/minio/pkg/net" xnet "github.com/minio/pkg/v2/net"
) )
// minio configuration related constants. // minio configuration related constants.
@@ -152,6 +152,9 @@ var (
// and it is automatically deduced. // and it is automatically deduced.
globalBrowserRedirectURL *xnet.URL globalBrowserRedirectURL *xnet.URL
// Disable redirect, default is enabled.
globalBrowserRedirect bool
// This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false. // This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false.
globalInplaceUpdateDisabled = false globalInplaceUpdateDisabled = false
@@ -402,6 +405,9 @@ var (
// Captures all batch jobs metrics globally // Captures all batch jobs metrics globally
globalBatchJobsMetrics batchJobMetrics globalBatchJobsMetrics batchJobMetrics
// Indicates if server was started as `--address ":0"`
globalDynamicAPIPort bool
// Add new variable global values here. // Add new variable global values here.
) )
+4 -3
View File
@@ -32,7 +32,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/mcontext" "github.com/minio/minio/internal/mcontext"
xnet "github.com/minio/pkg/net" xnet "github.com/minio/pkg/v2/net"
) )
const ( const (
@@ -300,7 +300,8 @@ func collectAPIStats(api string, f http.HandlerFunc) http.HandlerFunc {
globalHTTPStats.currentS3Requests.Inc(api) globalHTTPStats.currentS3Requests.Inc(api)
defer globalHTTPStats.currentS3Requests.Dec(api) defer globalHTTPStats.currentS3Requests.Dec(api)
if bucket != "" && bucket != minioReservedBucket { _, err = globalBucketMetadataSys.Get(bucket) // check if this bucket exists.
if bucket != "" && bucket != minioReservedBucket && err == nil {
globalBucketHTTPStats.updateHTTPStats(bucket, api, nil) globalBucketHTTPStats.updateHTTPStats(bucket, api, nil)
} }
@@ -316,7 +317,7 @@ func collectAPIStats(api string, f http.HandlerFunc) http.HandlerFunc {
globalConnStats.incS3InputBytes(int64(tc.RequestRecorder.Size())) globalConnStats.incS3InputBytes(int64(tc.RequestRecorder.Size()))
globalConnStats.incS3OutputBytes(int64(tc.ResponseRecorder.Size())) globalConnStats.incS3OutputBytes(int64(tc.ResponseRecorder.Size()))
if bucket != "" && bucket != minioReservedBucket { if bucket != "" && bucket != minioReservedBucket && err == nil {
globalBucketConnStats.incS3InputBytes(bucket, int64(tc.RequestRecorder.Size())) globalBucketConnStats.incS3InputBytes(bucket, int64(tc.RequestRecorder.Size()))
globalBucketConnStats.incS3OutputBytes(bucket, int64(tc.ResponseRecorder.Size())) globalBucketConnStats.incS3OutputBytes(bucket, int64(tc.ResponseRecorder.Size()))
globalBucketHTTPStats.updateHTTPStats(bucket, api, tc.ResponseRecorder) globalBucketHTTPStats.updateHTTPStats(bucket, api, tc.ResponseRecorder)
+67 -31
View File
@@ -29,6 +29,7 @@ import (
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
@@ -343,6 +344,7 @@ var (
policyDBServiceAccountsListKey = "policydb/service-accounts/" policyDBServiceAccountsListKey = "policydb/service-accounts/"
policyDBGroupsListKey = "policydb/groups/" policyDBGroupsListKey = "policydb/groups/"
// List of directories from which to read iam data into memory.
allListKeys = []string{ allListKeys = []string{
usersListKey, usersListKey,
svcAccListKey, svcAccListKey,
@@ -354,34 +356,60 @@ var (
policyDBServiceAccountsListKey, policyDBServiceAccountsListKey,
policyDBGroupsListKey, policyDBGroupsListKey,
} }
// List of directories to skip: we do not read STS directories for better
// performance. STS credentials would be stored in memory when they are
// first used.
iamLoadSkipListKeySet = set.CreateStringSet(
stsListKey,
policyDBSTSUsersListKey,
)
) )
func (iamOS *IAMObjectStore) listAllIAMConfigItems(ctx context.Context) (map[string][]string, error) { func (iamOS *IAMObjectStore) listAllIAMConfigItems(ctx context.Context) (map[string][]string, error) {
res := make(map[string][]string) res := make(map[string][]string)
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPrefix+SlashSeparator) { for _, listKey := range allListKeys {
if item.Err != nil { if iamLoadSkipListKeySet.Contains(listKey) {
return nil, item.Err continue
} }
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPrefix+SlashSeparator+listKey) {
found := false if item.Err != nil {
for _, listKey := range allListKeys { return nil, item.Err
if strings.HasPrefix(item.Item, listKey) {
found = true
name := strings.TrimPrefix(item.Item, listKey)
res[listKey] = append(res[listKey], name)
break
} }
} res[listKey] = append(res[listKey], item.Item)
if !found && (item.Item != "format.json") {
logger.LogIf(ctx, fmt.Errorf("unknown type of IAM file listed: %v", item.Item))
} }
} }
return res, nil return res, nil
} }
// PurgeExpiredSTS - purge expired STS credentials from object store.
func (iamOS *IAMObjectStore) PurgeExpiredSTS(ctx context.Context) error {
if iamOS.objAPI == nil {
return errServerNotInitialized
}
bootstrapTraceMsg("purging expired STS credentials")
// Scan STS users on disk and purge expired ones. We do not need to hold a
// lock with store.lock() here.
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPrefix+SlashSeparator+stsListKey) {
if item.Err != nil {
return item.Err
}
userName := path.Dir(item.Item)
// loadUser() will delete expired user during the load - we do not need
// to keep the loaded user around in memory, so we reinitialize the map
// each time.
m := map[string]UserIdentity{}
if err := iamOS.loadUser(ctx, userName, stsUser, m); err != nil && err != errNoSuchUser {
logger.LogIf(GlobalContext, fmt.Errorf("unable to load user during STS purge: %w (%s)", err, item.Item))
}
}
return nil
}
// Assumes cache is locked by caller. // Assumes cache is locked by caller.
func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iamCache) error { func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iamCache) error {
if iamOS.objAPI == nil { if iamOS.objAPI == nil {
@@ -448,29 +476,37 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
bootstrapTraceMsg("loading service accounts") bootstrapTraceMsg("loading service accounts")
svcAccList := listedConfigItems[svcAccListKey] svcAccList := listedConfigItems[svcAccListKey]
svcUsersMap := make(map[string]UserIdentity, len(svcAccList))
for _, item := range svcAccList { for _, item := range svcAccList {
userName := path.Dir(item) userName := path.Dir(item)
if err := iamOS.loadUser(ctx, userName, svcUser, cache.iamUsersMap); err != nil && err != errNoSuchUser { if err := iamOS.loadUser(ctx, userName, svcUser, svcUsersMap); err != nil && err != errNoSuchUser {
return fmt.Errorf("unable to load the service account `%s`: %w", userName, err) return fmt.Errorf("unable to load the service account `%s`: %w", userName, err)
} }
} }
for _, svcAcc := range svcUsersMap {
bootstrapTraceMsg("loading STS users") svcParent := svcAcc.Credentials.ParentUser
stsUsersList := listedConfigItems[stsListKey] if _, ok := cache.iamUsersMap[svcParent]; !ok {
for _, item := range stsUsersList { // If a service account's parent user is not in iamUsersMap, the
userName := path.Dir(item) // parent is an STS account. Such accounts may have a policy mapped
if err := iamOS.loadUser(ctx, userName, stsUser, cache.iamUsersMap); err != nil && err != errNoSuchUser { // on the parent user, so we load them. This is not needed for the
return fmt.Errorf("unable to load the STS user `%s`: %w", userName, err) // initial server startup, however, it is needed for the case where
// the STS account's policy mapping (for example in LDAP mode) may
// be changed and the user's policy mapping in memory is stale
// (because the policy change notification was missed by the current
// server).
//
// The "policy not found" error is ignored because the STS account may
// not have a policy mapped via its parent (for e.g. in
// OIDC/AssumeRoleWithCustomToken/AssumeRoleWithCertificate).
err := iamOS.loadMappedPolicy(ctx, svcParent, stsUser, false, cache.iamSTSPolicyMap)
if err != nil && !errors.Is(err, errNoSuchPolicy) {
return fmt.Errorf("unable to load the policy mapping for the STS user `%s`: %w", svcParent, err)
}
} }
} }
// Copy svcUsersMap to cache.iamUsersMap
bootstrapTraceMsg("loading STS policy mapping") for k, v := range svcUsersMap {
stsPolicyMappingsList := listedConfigItems[policyDBSTSUsersListKey] cache.iamUsersMap[k] = v
for _, item := range stsPolicyMappingsList {
stsName := strings.TrimSuffix(item, ".json")
if err := iamOS.loadMappedPolicy(ctx, stsName, stsUser, false, cache.iamUserPolicyMap); err != nil && !errors.Is(err, errNoSuchPolicy) {
return fmt.Errorf("unable to load the policy mapping for the STS user `%s`: %w", stsName, err)
}
} }
cache.buildUserGroupMemberships() cache.buildUserGroupMemberships()
+292 -124
View File
@@ -34,7 +34,7 @@ import (
"github.com/minio/minio/internal/config/identity/openid" "github.com/minio/minio/internal/config/identity/openid"
"github.com/minio/minio/internal/jwt" "github.com/minio/minio/internal/jwt"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/policy"
) )
const ( const (
@@ -212,12 +212,12 @@ func newMappedPolicy(policy string) MappedPolicy {
// PolicyDoc represents an IAM policy with some metadata. // PolicyDoc represents an IAM policy with some metadata.
type PolicyDoc struct { type PolicyDoc struct {
Version int `json:",omitempty"` Version int `json:",omitempty"`
Policy iampolicy.Policy Policy policy.Policy
CreateDate time.Time `json:",omitempty"` CreateDate time.Time `json:",omitempty"`
UpdateDate time.Time `json:",omitempty"` UpdateDate time.Time `json:",omitempty"`
} }
func newPolicyDoc(p iampolicy.Policy) PolicyDoc { func newPolicyDoc(p policy.Policy) PolicyDoc {
now := UTCNow().Round(time.Millisecond) now := UTCNow().Round(time.Millisecond)
return PolicyDoc{ return PolicyDoc{
Version: 1, Version: 1,
@@ -228,14 +228,14 @@ func newPolicyDoc(p iampolicy.Policy) PolicyDoc {
} }
// defaultPolicyDoc - used to wrap a default policy as PolicyDoc. // defaultPolicyDoc - used to wrap a default policy as PolicyDoc.
func defaultPolicyDoc(p iampolicy.Policy) PolicyDoc { func defaultPolicyDoc(p policy.Policy) PolicyDoc {
return PolicyDoc{ return PolicyDoc{
Version: 1, Version: 1,
Policy: p, Policy: p,
} }
} }
func (d *PolicyDoc) update(p iampolicy.Policy) { func (d *PolicyDoc) update(p policy.Policy) {
now := UTCNow().Round(time.Millisecond) now := UTCNow().Round(time.Millisecond)
d.UpdateDate = now d.UpdateDate = now
if d.CreateDate.IsZero() { if d.CreateDate.IsZero() {
@@ -248,7 +248,7 @@ func (d *PolicyDoc) update(p iampolicy.Policy) {
// definitions. // definitions.
// //
// The on-disk format of policy definitions has changed (around early 12/2021) // The on-disk format of policy definitions has changed (around early 12/2021)
// from iampolicy.Policy to PolicyDoc. To avoid a migration, loading supports // from policy.Policy to PolicyDoc. To avoid a migration, loading supports
// both the old and the new formats. // both the old and the new formats.
func (d *PolicyDoc) parseJSON(data []byte) error { func (d *PolicyDoc) parseJSON(data []byte) error {
json := jsoniter.ConfigCompatibleWithStandardLibrary json := jsoniter.ConfigCompatibleWithStandardLibrary
@@ -283,14 +283,22 @@ type iamCache struct {
// map of policy names to policy definitions // map of policy names to policy definitions
iamPolicyDocsMap map[string]PolicyDoc iamPolicyDocsMap map[string]PolicyDoc
// map of usernames to credentials
// map of regular username to credentials
iamUsersMap map[string]UserIdentity iamUsersMap map[string]UserIdentity
// map of regular username to policy names
iamUserPolicyMap map[string]MappedPolicy
// STS accounts are loaded on demand and not via the periodic IAM reload.
// map of STS access key to credentials
iamSTSAccountsMap map[string]UserIdentity
// map of STS access key to policy names
iamSTSPolicyMap map[string]MappedPolicy
// map of group names to group info // map of group names to group info
iamGroupsMap map[string]GroupInfo iamGroupsMap map[string]GroupInfo
// map of user names to groups they are a member of // map of user names to groups they are a member of
iamUserGroupMemberships map[string]set.StringSet iamUserGroupMemberships map[string]set.StringSet
// map of usernames/temporary access keys to policy names
iamUserPolicyMap map[string]MappedPolicy
// map of group names to policy names // map of group names to policy names
iamGroupPolicyMap map[string]MappedPolicy iamGroupPolicyMap map[string]MappedPolicy
} }
@@ -299,9 +307,11 @@ func newIamCache() *iamCache {
return &iamCache{ return &iamCache{
iamPolicyDocsMap: map[string]PolicyDoc{}, iamPolicyDocsMap: map[string]PolicyDoc{},
iamUsersMap: map[string]UserIdentity{}, iamUsersMap: map[string]UserIdentity{},
iamUserPolicyMap: map[string]MappedPolicy{},
iamSTSAccountsMap: map[string]UserIdentity{},
iamSTSPolicyMap: map[string]MappedPolicy{},
iamGroupsMap: map[string]GroupInfo{}, iamGroupsMap: map[string]GroupInfo{},
iamUserGroupMemberships: map[string]set.StringSet{}, iamUserGroupMemberships: map[string]set.StringSet{},
iamUserPolicyMap: map[string]MappedPolicy{},
iamGroupPolicyMap: map[string]MappedPolicy{}, iamGroupPolicyMap: map[string]MappedPolicy{},
} }
} }
@@ -354,9 +364,9 @@ func (c *iamCache) removeGroupFromMembershipsMap(group string) {
// information in IAM (i.e sys.iam*Map) - this info is stored only in the STS // information in IAM (i.e sys.iam*Map) - this info is stored only in the STS
// generated credentials. Thus we skip looking up group memberships, user map, // generated credentials. Thus we skip looking up group memberships, user map,
// and group map and check the appropriate policy maps directly. // and group map and check the appropriate policy maps directly.
func (c *iamCache) policyDBGet(mode UsersSysType, name string, isGroup bool) ([]string, time.Time, error) { func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([]string, time.Time, error) {
if isGroup { if isGroup {
if mode == MinIOUsersSysType { if store.getUsersSysType() == MinIOUsersSysType {
g, ok := c.iamGroupsMap[name] g, ok := c.iamGroupsMap[name]
if !ok { if !ok {
return nil, time.Time{}, errNoSuchGroup return nil, time.Time{}, errNoSuchGroup
@@ -381,7 +391,20 @@ func (c *iamCache) policyDBGet(mode UsersSysType, name string, isGroup bool) ([]
} }
} }
mp := c.iamUserPolicyMap[name] // For internal IDP regular/service account user accounts, the policy
// mapping is iamUserPolicyMap. For STS accounts, the parent user would be
// passed here and we lookup the mapping in iamSTSPolicyMap.
mp, ok := c.iamUserPolicyMap[name]
if !ok {
// Since user "name" could be a parent user of an STS account, we lookup
// mappings for those too.
mp, ok = c.iamSTSPolicyMap[name]
if !ok {
// Attempt to load parent user mapping for STS accounts
store.loadMappedPolicy(context.TODO(), name, stsUser, false, c.iamSTSPolicyMap)
mp = c.iamSTSPolicyMap[name]
}
}
// returned policy could be empty // returned policy could be empty
policies := mp.toSlice() policies := mp.toSlice()
@@ -407,7 +430,11 @@ func (c *iamCache) updateUserWithClaims(key string, u UserIdentity) error {
} }
u.Credentials.Claims = jwtClaims.Map() u.Credentials.Claims = jwtClaims.Map()
} }
c.iamUsersMap[key] = u if !u.Credentials.IsTemp() {
c.iamUsersMap[key] = u
} else {
c.iamSTSAccountsMap[key] = u
}
c.updatedAt = time.Now() c.updatedAt = time.Now()
return nil return nil
} }
@@ -452,13 +479,23 @@ type iamStorageWatcher interface {
// Set default canned policies only if not already overridden by users. // Set default canned policies only if not already overridden by users.
func setDefaultCannedPolicies(policies map[string]PolicyDoc) { func setDefaultCannedPolicies(policies map[string]PolicyDoc) {
for _, v := range iampolicy.DefaultPolicies { for _, v := range policy.DefaultPolicies {
if _, ok := policies[v.Name]; !ok { if _, ok := policies[v.Name]; !ok {
policies[v.Name] = defaultPolicyDoc(v.Definition) policies[v.Name] = defaultPolicyDoc(v.Definition)
} }
} }
} }
// PurgeExpiredSTS - purges expired STS credentials.
func (store *IAMStoreSys) PurgeExpiredSTS(ctx context.Context) error {
iamOS, ok := store.IAMStorageAPI.(*IAMObjectStore)
if !ok {
// No purging is done for non-object storage.
return nil
}
return iamOS.PurgeExpiredSTS(ctx)
}
// LoadIAMCache reads all IAM items and populates a new iamCache object and // LoadIAMCache reads all IAM items and populates a new iamCache object and
// replaces the in-memory cache object. // replaces the in-memory cache object.
func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error { func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
@@ -512,18 +549,6 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
return err return err
} }
bootstrapTraceMsg("loading STS users")
// load STS temp users
if err := store.loadUsers(ctx, stsUser, newCache.iamUsersMap); err != nil {
return err
}
bootstrapTraceMsg("loading STS policy mapping")
// load STS policy mappings
if err := store.loadMappedPolicies(ctx, stsUser, false, newCache.iamUserPolicyMap); err != nil {
return err
}
newCache.buildUserGroupMemberships() newCache.buildUserGroupMemberships()
} }
@@ -535,9 +560,9 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
// were changes to the in-memory cache we should wait for the next // were changes to the in-memory cache we should wait for the next
// cycle until we can safely update the in-memory cache. // cycle until we can safely update the in-memory cache.
// //
// An in-memory cache must be replaced only if we know for sure that // An in-memory cache must be replaced only if we know for sure that the
// the values loaded from disk are not stale. They might be stale // values loaded from disk are not stale. They might be stale if the
// if the cached.updatedAt is recent than the refresh cycle began. // cached.updatedAt is more recent than the refresh cycle began.
if cache.updatedAt.Before(loadedAt) { if cache.updatedAt.Before(loadedAt) {
// No one has updated anything since the config was loaded, // No one has updated anything since the config was loaded,
// so we just replace whatever is on the disk into memory. // so we just replace whatever is on the disk into memory.
@@ -547,6 +572,15 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
cache.iamUserGroupMemberships = newCache.iamUserGroupMemberships cache.iamUserGroupMemberships = newCache.iamUserGroupMemberships
cache.iamUserPolicyMap = newCache.iamUserPolicyMap cache.iamUserPolicyMap = newCache.iamUserPolicyMap
cache.iamUsersMap = newCache.iamUsersMap cache.iamUsersMap = newCache.iamUsersMap
// For STS policy map, we need to merge the new cache with the existing
// cache because the periodic IAM reload is partial. The periodic load
// here is to account for STS policy mapping changes that should apply
// for service accounts derived from such STS accounts (i.e. LDAP STS
// accounts).
for k, v := range newCache.iamSTSPolicyMap {
cache.iamSTSPolicyMap[k] = v
}
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
} }
@@ -571,6 +605,10 @@ func (store *IAMStoreSys) GetUser(user string) (UserIdentity, bool) {
defer store.runlock() defer store.runlock()
u, ok := cache.iamUsersMap[user] u, ok := cache.iamUsersMap[user]
if !ok {
// Check the sts map
u, ok = cache.iamSTSAccountsMap[user]
}
return u, ok return u, ok
} }
@@ -635,14 +673,14 @@ func (store *IAMStoreSys) PolicyDBGet(name string, isGroup bool, groups ...strin
cache := store.rlock() cache := store.rlock()
defer store.runlock() defer store.runlock()
policies, _, err := cache.policyDBGet(store.getUsersSysType(), name, isGroup) policies, _, err := cache.policyDBGet(store, name, isGroup)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !isGroup { if !isGroup {
for _, group := range groups { for _, group := range groups {
ps, _, err := cache.policyDBGet(store.getUsersSysType(), group, true) ps, _, err := cache.policyDBGet(store, group, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -828,7 +866,7 @@ func (store *IAMStoreSys) GetGroupDescription(group string) (gd madmin.GroupDesc
cache := store.rlock() cache := store.rlock()
defer store.runlock() defer store.runlock()
ps, updatedAt, err := cache.policyDBGet(store.getUsersSysType(), group, true) ps, updatedAt, err := cache.policyDBGet(store, group, true)
if err != nil { if err != nil {
return gd, err return gd, err
} }
@@ -928,7 +966,16 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
// Load existing policy mapping // Load existing policy mapping
var mp MappedPolicy var mp MappedPolicy
if !isGroup { if !isGroup {
mp = cache.iamUserPolicyMap[name] if userType == stsUser {
stsMap := map[string]MappedPolicy{}
// Attempt to load parent user mapping for STS accounts
store.loadMappedPolicy(context.TODO(), name, stsUser, false, stsMap)
mp = stsMap[name]
} else {
mp = cache.iamUserPolicyMap[name]
}
} else { } else {
if store.getUsersSysType() == MinIOUsersSysType { if store.getUsersSysType() == MinIOUsersSysType {
g, ok := cache.iamGroupsMap[name] g, ok := cache.iamGroupsMap[name]
@@ -981,7 +1028,11 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
return return
} }
if !isGroup { if !isGroup {
cache.iamUserPolicyMap[name] = newPolicyMapping if userType == stsUser {
cache.iamSTSPolicyMap[name] = newPolicyMapping
} else {
cache.iamUserPolicyMap[name] = newPolicyMapping
}
} else { } else {
cache.iamGroupPolicyMap[name] = newPolicyMapping cache.iamGroupPolicyMap[name] = newPolicyMapping
} }
@@ -1015,7 +1066,11 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string,
return updatedAt, err return updatedAt, err
} }
if !isGroup { if !isGroup {
delete(cache.iamUserPolicyMap, name) if userType == stsUser {
delete(cache.iamSTSPolicyMap, name)
} else {
delete(cache.iamUserPolicyMap, name)
}
} else { } else {
delete(cache.iamGroupPolicyMap, name) delete(cache.iamGroupPolicyMap, name)
} }
@@ -1035,7 +1090,11 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string,
return updatedAt, err return updatedAt, err
} }
if !isGroup { if !isGroup {
cache.iamUserPolicyMap[name] = mp if userType == stsUser {
cache.iamSTSPolicyMap[name] = mp
} else {
cache.iamUserPolicyMap[name] = mp
}
} else { } else {
cache.iamGroupPolicyMap[name] = mp cache.iamGroupPolicyMap[name] = mp
} }
@@ -1104,7 +1163,9 @@ func (store *IAMStoreSys) DeletePolicy(ctx context.Context, policy string) error
cache := store.lock() cache := store.lock()
defer store.unlock() defer store.unlock()
// Check if policy is mapped to any existing user or group. // Check if policy is mapped to any existing user or group. If so, we do not
// allow deletion of the policy. If the policy is mapped to an STS account,
// we do allow deletion.
users := []string{} users := []string{}
groups := []string{} groups := []string{}
for u, mp := range cache.iamUserPolicyMap { for u, mp := range cache.iamUserPolicyMap {
@@ -1148,16 +1209,16 @@ func (store *IAMStoreSys) DeletePolicy(ctx context.Context, policy string) error
// GetPolicy - gets the policy definition. Allows specifying multiple comma // GetPolicy - gets the policy definition. Allows specifying multiple comma
// separated policies - returns a combined policy. // separated policies - returns a combined policy.
func (store *IAMStoreSys) GetPolicy(name string) (iampolicy.Policy, error) { func (store *IAMStoreSys) GetPolicy(name string) (policy.Policy, error) {
if name == "" { if name == "" {
return iampolicy.Policy{}, errInvalidArgument return policy.Policy{}, errInvalidArgument
} }
cache := store.rlock() cache := store.rlock()
defer store.runlock() defer store.runlock()
policies := newMappedPolicy(name).toSlice() policies := newMappedPolicy(name).toSlice()
var combinedPolicy iampolicy.Policy var toMerge []policy.Policy
for _, policy := range policies { for _, policy := range policies {
if policy == "" { if policy == "" {
continue continue
@@ -1166,9 +1227,9 @@ func (store *IAMStoreSys) GetPolicy(name string) (iampolicy.Policy, error) {
if !ok { if !ok {
return v.Policy, errNoSuchPolicy return v.Policy, errNoSuchPolicy
} }
combinedPolicy = combinedPolicy.Merge(v.Policy) toMerge = append(toMerge, v.Policy)
} }
return combinedPolicy, nil return policy.MergePolicies(toMerge...), nil
} }
// GetPolicyDoc - gets the policy doc which has the policy and some metadata. // GetPolicyDoc - gets the policy doc which has the policy and some metadata.
@@ -1190,7 +1251,7 @@ func (store *IAMStoreSys) GetPolicyDoc(name string) (r PolicyDoc, err error) {
} }
// SetPolicy - creates a policy with name. // SetPolicy - creates a policy with name.
func (store *IAMStoreSys) SetPolicy(ctx context.Context, name string, policy iampolicy.Policy) (time.Time, error) { func (store *IAMStoreSys) SetPolicy(ctx context.Context, name string, policy policy.Policy) (time.Time, error) {
if policy.IsEmpty() || name == "" { if policy.IsEmpty() || name == "" {
return time.Time{}, errInvalidArgument return time.Time{}, errInvalidArgument
} }
@@ -1220,7 +1281,7 @@ func (store *IAMStoreSys) SetPolicy(ctx context.Context, name string, policy iam
// ListPolicies - fetches all policies from storage and updates cache as well. // ListPolicies - fetches all policies from storage and updates cache as well.
// If bucketName is non-empty, returns policies matching the bucket. // If bucketName is non-empty, returns policies matching the bucket.
func (store *IAMStoreSys) ListPolicies(ctx context.Context, bucketName string) (map[string]iampolicy.Policy, error) { func (store *IAMStoreSys) ListPolicies(ctx context.Context, bucketName string) (map[string]policy.Policy, error) {
cache := store.lock() cache := store.lock()
defer store.unlock() defer store.unlock()
@@ -1236,7 +1297,7 @@ func (store *IAMStoreSys) ListPolicies(ctx context.Context, bucketName string) (
cache.iamPolicyDocsMap = m cache.iamPolicyDocsMap = m
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
ret := map[string]iampolicy.Policy{} ret := map[string]policy.Policy{}
for k, v := range m { for k, v := range m {
if bucketName == "" || v.Policy.MatchResource(bucketName) { if bucketName == "" || v.Policy.MatchResource(bucketName) {
ret[k] = v.Policy ret[k] = v.Policy
@@ -1289,10 +1350,10 @@ func (store *IAMStoreSys) listPolicyDocs(ctx context.Context, bucketName string)
} }
// helper function - does not take locks. // helper function - does not take locks.
func filterPolicies(cache *iamCache, policyName string, bucketName string) (string, iampolicy.Policy) { func filterPolicies(cache *iamCache, policyName string, bucketName string) (string, policy.Policy) {
var policies []string var policies []string
mp := newMappedPolicy(policyName) mp := newMappedPolicy(policyName)
combinedPolicy := iampolicy.Policy{} var toMerge []policy.Policy
for _, policy := range mp.toSlice() { for _, policy := range mp.toSlice() {
if policy == "" { if policy == "" {
continue continue
@@ -1303,10 +1364,10 @@ func filterPolicies(cache *iamCache, policyName string, bucketName string) (stri
} }
if bucketName == "" || p.Policy.MatchResource(bucketName) { if bucketName == "" || p.Policy.MatchResource(bucketName) {
policies = append(policies, policy) policies = append(policies, policy)
combinedPolicy = combinedPolicy.Merge(p.Policy) toMerge = append(toMerge, p.Policy)
} }
} }
return strings.Join(policies, ","), combinedPolicy return strings.Join(policies, ","), policy.MergePolicies(toMerge...)
} }
// FilterPolicies - accepts a comma separated list of policy names as a string // FilterPolicies - accepts a comma separated list of policy names as a string
@@ -1314,7 +1375,7 @@ func filterPolicies(cache *iamCache, policyName string, bucketName string) (stri
// bucketName is non-empty, additionally filters policies matching the bucket. // bucketName is non-empty, additionally filters policies matching the bucket.
// The first returned value is the list of currently existing policies, and the // The first returned value is the list of currently existing policies, and the
// second is their combined policy definition. // second is their combined policy definition.
func (store *IAMStoreSys) FilterPolicies(policyName string, bucketName string) (string, iampolicy.Policy) { func (store *IAMStoreSys) FilterPolicies(policyName string, bucketName string) (string, policy.Policy) {
cache := store.rlock() cache := store.rlock()
defer store.runlock() defer store.runlock()
@@ -1403,6 +1464,9 @@ func (store *IAMStoreSys) GetUsersWithMappedPolicies() map[string]string {
for k, v := range cache.iamUserPolicyMap { for k, v := range cache.iamUserPolicyMap {
result[k] = v.Policies result[k] = v.Policies
} }
for k, v := range cache.iamSTSPolicyMap {
result[k] = v.Policies
}
return result return result
} }
@@ -1425,10 +1489,25 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error
break break
} }
} }
for _, v := range cache.iamSTSAccountsMap {
if v.Credentials.ParentUser == name {
groups = v.Credentials.Groups
break
}
}
mappedPolicy, ok := cache.iamUserPolicyMap[name] mappedPolicy, ok := cache.iamUserPolicyMap[name]
if !ok { if !ok {
return u, errNoSuchUser mappedPolicy, ok = cache.iamSTSPolicyMap[name]
} }
if !ok {
// Attempt to load parent user mapping for STS accounts
store.loadMappedPolicy(context.TODO(), name, stsUser, false, cache.iamSTSPolicyMap)
mappedPolicy, ok = cache.iamSTSPolicyMap[name]
if !ok {
return u, errNoSuchUser
}
}
return madmin.UserInfo{ return madmin.UserInfo{
PolicyName: mappedPolicy.Policies, PolicyName: mappedPolicy.Policies,
MemberOf: groups, MemberOf: groups,
@@ -1467,8 +1546,11 @@ func (store *IAMStoreSys) PolicyMappingNotificationHandler(ctx context.Context,
cache := store.lock() cache := store.lock()
defer store.unlock() defer store.unlock()
m := cache.iamGroupPolicyMap var m map[string]MappedPolicy
if !isGroup { switch {
case isGroup:
m = cache.iamGroupPolicyMap
default:
m = cache.iamUserPolicyMap m = cache.iamUserPolicyMap
} }
err := store.loadMappedPolicy(ctx, userOrGroup, userType, isGroup, m) err := store.loadMappedPolicy(ctx, userOrGroup, userType, isGroup, m)
@@ -1493,10 +1575,23 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey
cache := store.lock() cache := store.lock()
defer store.unlock() defer store.unlock()
err := store.loadUser(ctx, accessKey, userType, cache.iamUsersMap) var m map[string]UserIdentity
switch userType {
case stsUser:
m = cache.iamSTSAccountsMap
default:
m = cache.iamUsersMap
}
err := store.loadUser(ctx, accessKey, userType, m)
if err == errNoSuchUser { if err == errNoSuchUser {
// User was deleted - we update the cache. // User was deleted - we update the cache.
delete(cache.iamUsersMap, accessKey) delete(m, accessKey)
// Since cache was updated, we update the timestamp.
defer func() {
cache.updatedAt = time.Now()
}()
// 1. Start with updating user-group memberships // 1. Start with updating user-group memberships
if store.getUsersSysType() == MinIOUsersSysType { if store.getUsersSysType() == MinIOUsersSysType {
@@ -1514,12 +1609,14 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey
// 2. Remove any derived credentials from memory // 2. Remove any derived credentials from memory
if userType == regUser { if userType == regUser {
for _, u := range cache.iamUsersMap { for k, u := range cache.iamUsersMap {
if u.Credentials.IsServiceAccount() && u.Credentials.ParentUser == accessKey { if u.Credentials.IsServiceAccount() && u.Credentials.ParentUser == accessKey {
delete(cache.iamUsersMap, u.Credentials.AccessKey) delete(cache.iamUsersMap, k)
} }
if u.Credentials.IsTemp() && u.Credentials.ParentUser == accessKey { }
delete(cache.iamUsersMap, u.Credentials.AccessKey) for k, u := range cache.iamSTSAccountsMap {
if u.Credentials.ParentUser == accessKey {
delete(cache.iamSTSAccountsMap, k)
} }
} }
} }
@@ -1527,7 +1624,6 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey
// 3. Delete any mapped policy // 3. Delete any mapped policy
delete(cache.iamUserPolicyMap, accessKey) delete(cache.iamUserPolicyMap, accessKey)
cache.updatedAt = time.Now()
return nil return nil
} }
@@ -1535,31 +1631,43 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey
return err return err
} }
if userType != svcUser { // Since cache was updated, we update the timestamp.
err = store.loadMappedPolicy(ctx, accessKey, userType, false, cache.iamUserPolicyMap) defer func() {
// Ignore policy not mapped error cache.updatedAt = time.Now()
if err != nil && !errors.Is(err, errNoSuchPolicy) { }()
return err
}
}
// We are on purpose not persisting the policy map for parent cred := m[accessKey].Credentials
// user, although this is a hack, it is a good enough hack switch userType {
// at this point in time - we need to overhaul our OIDC case stsUser:
// usage with service accounts with a more cleaner implementation // For STS accounts a policy is mapped to the parent user (if a mapping exists).
// err = store.loadMappedPolicy(ctx, cred.ParentUser, userType, false, cache.iamSTSPolicyMap)
// This mapping is necessary to ensure that valid credentials case svcUser:
// have necessary ParentUser present - this is mainly for only // For service accounts, the parent may be a regular (internal) IDP
// webIdentity based STS tokens. // user or a "virtual" user (parent of an STS account).
u, ok := cache.iamUsersMap[accessKey] //
if ok { // If parent is a regular user => policy mapping is done on that parent itself.
cred := u.Credentials //
if cred.IsTemp() && cred.ParentUser != "" && cred.ParentUser != globalActiveCred.AccessKey { // If parent is "virtual" => policy mapping is done on the virtual
if _, ok := cache.iamUserPolicyMap[cred.ParentUser]; !ok { // parent and that virtual parent is an stsUser.
cache.iamUserPolicyMap[cred.ParentUser] = cache.iamUserPolicyMap[accessKey] //
cache.updatedAt = time.Now() // To load the appropriate mapping, we check the parent user type.
} _, parentIsRegularUser := cache.iamUsersMap[cred.ParentUser]
if parentIsRegularUser {
err = store.loadMappedPolicy(ctx, cred.ParentUser, regUser, false, cache.iamUserPolicyMap)
} else {
err = store.loadMappedPolicy(ctx, cred.ParentUser, stsUser, false, cache.iamSTSPolicyMap)
} }
case regUser:
// For regular users, we load the mapped policy.
err = store.loadMappedPolicy(ctx, accessKey, userType, false, cache.iamUserPolicyMap)
default:
// This is just to ensure that we have covered all cases for new
// code in future.
panic("unknown user type")
}
// Ignore policy not mapped error
if err != nil && !errors.Is(err, errNoSuchPolicy) {
return err
} }
return nil return nil
@@ -1648,7 +1756,7 @@ func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cre
return time.Time{}, err return time.Time{}, err
} }
cache.iamUserPolicyMap[cred.ParentUser] = mp cache.iamSTSPolicyMap[cred.ParentUser] = mp
} }
u := newUserIdentity(cred) u := newUserIdentity(cred)
@@ -1657,8 +1765,7 @@ func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cre
return time.Time{}, err return time.Time{}, err
} }
cache.iamUsersMap[accessKey] = u cache.iamSTSAccountsMap[accessKey] = u
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return u.UpdatedAt, nil return u.UpdatedAt, nil
@@ -1800,6 +1907,25 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string
}) })
} }
stsMap := map[string]MappedPolicy{}
for _, user := range users {
// Attempt to load parent user mapping for STS accounts
store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap)
}
for user, mappedPolicy := range stsMap {
if userPredicate != nil && !userPredicate(user) {
continue
}
ps := mappedPolicy.toSlice()
sort.Strings(ps)
r = append(r, madmin.UserPolicyEntities{
User: user,
Policies: ps,
})
}
sort.Slice(r, func(i, j int) bool { sort.Slice(r, func(i, j int) bool {
return r[i].User < r[j].User return r[i].User < r[j].User
}) })
@@ -1864,6 +1990,32 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
} }
} }
if iamOS, ok := store.IAMStorageAPI.(*IAMObjectStore); ok {
for item := range listIAMConfigItems(context.Background(), iamOS.objAPI, iamConfigPrefix+SlashSeparator+policyDBSTSUsersListKey) {
user := strings.TrimSuffix(item.Item, ".json")
if userPredicate != nil && !userPredicate(user) {
continue
}
var mappedPolicy MappedPolicy
store.loadIAMConfig(context.Background(), &mappedPolicy, getMappedPolicyPath(user, stsUser, false))
commonPolicySet := mappedPolicy.policySet()
if !queryPolSet.IsEmpty() {
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
}
for _, policy := range commonPolicySet.ToSlice() {
s, ok := policyToUsersMap[policy]
if !ok {
policyToUsersMap[policy] = set.CreateStringSet(user)
} else {
s.Add(user)
policyToUsersMap[policy] = s
}
}
}
}
policyToGroupsMap := make(map[string]set.StringSet) policyToGroupsMap := make(map[string]set.StringSet)
for group, mappedPolicy := range cache.iamGroupPolicyMap { for group, mappedPolicy := range cache.iamGroupPolicyMap {
if groupPredicate != nil && !groupPredicate(group) { if groupPredicate != nil && !groupPredicate(group) {
@@ -2076,8 +2228,8 @@ func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey st
// sessionPolicy is nil and there is embedded policy attached we remove // sessionPolicy is nil and there is embedded policy attached we remove
// embedded policy at that point. // embedded policy at that point.
if _, ok := m[iampolicy.SessionPolicyName]; ok && opts.sessionPolicy == nil { if _, ok := m[policy.SessionPolicyName]; ok && opts.sessionPolicy == nil {
delete(m, iampolicy.SessionPolicyName) delete(m, policy.SessionPolicyName)
m[iamPolicyClaimNameSA()] = inheritedPolicyType m[iamPolicyClaimNameSA()] = inheritedPolicyType
} }
@@ -2096,7 +2248,7 @@ func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey st
} }
// Overwrite session policy claims. // Overwrite session policy claims.
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString(policyBuf) m[policy.SessionPolicyName] = base64.StdEncoding.EncodeToString(policyBuf)
m[iamPolicyClaimNameSA()] = embeddedPolicyType m[iamPolicyClaimNameSA()] = embeddedPolicyType
} }
@@ -2155,19 +2307,10 @@ func (store *IAMStoreSys) ListServiceAccounts(ctx context.Context, accessKey str
cache := store.rlock() cache := store.rlock()
defer store.runlock() defer store.runlock()
userExists := false
var serviceAccounts []auth.Credentials var serviceAccounts []auth.Credentials
for _, u := range cache.iamUsersMap { for _, u := range cache.iamUsersMap {
isDerived := false
v := u.Credentials v := u.Credentials
if v.IsServiceAccount() || v.IsTemp() { if accessKey != "" && v.ParentUser == accessKey {
isDerived = true
}
if !isDerived && v.AccessKey == accessKey {
userExists = true
} else if isDerived && v.ParentUser == accessKey {
userExists = true
if v.IsServiceAccount() { if v.IsServiceAccount() {
// Hide secret key & session key here // Hide secret key & session key here
v.SecretKey = "" v.SecretKey = ""
@@ -2177,12 +2320,6 @@ func (store *IAMStoreSys) ListServiceAccounts(ctx context.Context, accessKey str
} }
} }
// If root user has no STS/Service Accounts, userExists would be false here,
// so we handle this exception.
if !userExists && globalActiveCred.AccessKey != accessKey {
return nil, errNoSuchUser
}
return serviceAccounts, nil return serviceAccounts, nil
} }
@@ -2251,10 +2388,20 @@ func (store *IAMStoreSys) GetSTSAndServiceAccounts() []auth.Credentials {
var res []auth.Credentials var res []auth.Credentials
for _, u := range cache.iamUsersMap { for _, u := range cache.iamUsersMap {
cred := u.Credentials cred := u.Credentials
if cred.IsTemp() || cred.IsServiceAccount() { if cred.IsTemp() {
panic("unexpected STS credential found in iamUsersMap")
}
if cred.IsServiceAccount() {
res = append(res, cred) res = append(res, cred)
} }
} }
for _, u := range cache.iamSTSAccountsMap {
if !u.Credentials.IsTemp() {
panic("unexpected non STS credential found in iamSTSAccountsMap")
}
res = append(res, u.Credentials)
}
return res return res
} }
@@ -2289,35 +2436,56 @@ func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) {
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
_, found := cache.iamUsersMap[accessKey] _, found := cache.iamUsersMap[accessKey]
// Check for regular user access key
if !found { if !found {
store.loadUser(ctx, accessKey, regUser, cache.iamUsersMap) store.loadUser(ctx, accessKey, regUser, cache.iamUsersMap)
if _, found = cache.iamUsersMap[accessKey]; found { if _, found = cache.iamUsersMap[accessKey]; found {
// load mapped policies // load mapped policies
store.loadMappedPolicy(ctx, accessKey, regUser, false, cache.iamUserPolicyMap) store.loadMappedPolicy(ctx, accessKey, regUser, false, cache.iamUserPolicyMap)
} else { }
// check for service account }
store.loadUser(ctx, accessKey, svcUser, cache.iamUsersMap)
if svc, found := cache.iamUsersMap[accessKey]; found { // Check for service account
// Load parent user and mapped policies. if !found {
if store.getUsersSysType() == MinIOUsersSysType { store.loadUser(ctx, accessKey, svcUser, cache.iamUsersMap)
store.loadUser(ctx, svc.Credentials.ParentUser, regUser, cache.iamUsersMap) if svc, found := cache.iamUsersMap[accessKey]; found {
} // Load parent user and mapped policies.
if store.getUsersSysType() == MinIOUsersSysType {
store.loadUser(ctx, svc.Credentials.ParentUser, regUser, cache.iamUsersMap)
store.loadMappedPolicy(ctx, svc.Credentials.ParentUser, regUser, false, cache.iamUserPolicyMap) store.loadMappedPolicy(ctx, svc.Credentials.ParentUser, regUser, false, cache.iamUserPolicyMap)
} else { } else {
// check for STS account // In case of LDAP the parent user's policy mapping needs to be
store.loadUser(ctx, accessKey, stsUser, cache.iamUsersMap) // loaded into sts map
if _, found = cache.iamUsersMap[accessKey]; found { store.loadMappedPolicy(ctx, svc.Credentials.ParentUser, stsUser, false, cache.iamSTSPolicyMap)
// Load mapped policy
store.loadMappedPolicy(ctx, accessKey, stsUser, false, cache.iamUserPolicyMap)
}
} }
} }
} }
// Check for STS account
stsAccountFound := false
var stsUserCred UserIdentity
if !found {
store.loadUser(ctx, accessKey, stsUser, cache.iamSTSAccountsMap)
if stsUserCred, found = cache.iamSTSAccountsMap[accessKey]; found {
// Load mapped policy
store.loadMappedPolicy(ctx, stsUserCred.Credentials.ParentUser, stsUser, false, cache.iamSTSPolicyMap)
stsAccountFound = true
}
}
// Load any associated policy definitions // Load any associated policy definitions
for _, policy := range cache.iamUserPolicyMap[accessKey].toSlice() { if !stsAccountFound {
if _, found = cache.iamPolicyDocsMap[policy]; !found { for _, policy := range cache.iamUserPolicyMap[accessKey].toSlice() {
store.loadPolicyDoc(ctx, policy, cache.iamPolicyDocsMap) if _, found = cache.iamPolicyDocsMap[policy]; !found {
store.loadPolicyDoc(ctx, policy, cache.iamPolicyDocsMap)
}
}
} else {
for _, policy := range cache.iamSTSPolicyMap[stsUserCred.Credentials.AccessKey].toSlice() {
if _, found = cache.iamPolicyDocsMap[policy]; !found {
store.loadPolicyDoc(ctx, policy, cache.iamPolicyDocsMap)
}
} }
} }
} }
+117 -120
View File
@@ -47,7 +47,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/jwt" "github.com/minio/minio/internal/jwt"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/policy"
etcd "go.etcd.io/etcd/client/v3" etcd "go.etcd.io/etcd/client/v3"
) )
@@ -319,44 +319,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
refreshInterval := sys.iamRefreshInterval refreshInterval := sys.iamRefreshInterval
// Set up polling for expired accounts and credentials purging. go sys.periodicRoutines(ctx, refreshInterval)
switch {
case sys.OpenIDConfig.ProviderEnabled():
go func() {
timer := time.NewTimer(refreshInterval)
defer timer.Stop()
for {
select {
case <-timer.C:
sys.purgeExpiredCredentialsForExternalSSO(ctx)
timer.Reset(refreshInterval)
case <-ctx.Done():
return
}
}
}()
case sys.LDAPConfig.Enabled():
go func() {
timer := time.NewTimer(refreshInterval)
defer timer.Stop()
for {
select {
case <-timer.C:
sys.purgeExpiredCredentialsForLDAP(ctx)
sys.updateGroupMembershipsForLDAP(ctx)
timer.Reset(refreshInterval)
case <-ctx.Done():
return
}
}
}()
}
// Start watching changes to storage.
go sys.watch(ctx)
// Load RoleARNs // Load RoleARNs
sys.rolesMap = make(map[arn.ARN]string) sys.rolesMap = make(map[arn.ARN]string)
@@ -377,6 +340,79 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
bootstrapTraceMsg("finishing IAM loading") bootstrapTraceMsg("finishing IAM loading")
} }
func (sys *IAMSys) periodicRoutines(ctx context.Context, baseInterval time.Duration) {
// Watch for IAM config changes for iamStorageWatcher.
watcher, isWatcher := sys.store.IAMStorageAPI.(iamStorageWatcher)
if isWatcher {
go func() {
ch := watcher.watch(ctx, iamConfigPrefix)
for event := range ch {
if err := sys.loadWatchedEvent(ctx, event); err != nil {
// we simply log errors
logger.LogIf(ctx, fmt.Errorf("Failure in loading watch event: %v", err))
}
}
}()
}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// Add a random interval of up to 20% of the base interval.
randInterval := func() time.Duration {
return time.Duration(r.Float64() * float64(baseInterval) * 0.2)
}
var maxDurationSecondsForLog float64 = 5
timer := time.NewTimer(baseInterval + randInterval())
defer timer.Stop()
for {
select {
case <-timer.C:
// Load all IAM items (except STS creds) periodically.
refreshStart := time.Now()
if err := sys.Load(ctx, false); err != nil {
logger.LogIf(ctx, fmt.Errorf("Failure in periodic refresh for IAM (took %.2fs): %v", time.Since(refreshStart).Seconds(), err))
} else {
took := time.Since(refreshStart).Seconds()
if took > maxDurationSecondsForLog {
// Log if we took a lot of time to load.
logger.Info("IAM refresh took %.2fs", took)
}
}
// The following actions are performed about once in 4 times that
// IAM is refreshed:
if r.Intn(4) == 0 {
// Purge expired STS credentials.
purgeStart := time.Now()
if err := sys.store.PurgeExpiredSTS(ctx); err != nil {
logger.LogIf(ctx, fmt.Errorf("Failure in periodic STS purge for IAM (took %.2fs): %v", time.Since(purgeStart).Seconds(), err))
} else {
took := time.Since(purgeStart).Seconds()
if took > maxDurationSecondsForLog {
// Log if we took a lot of time to load.
logger.Info("IAM expired STS purge took %.2fs", took)
}
}
// Poll and remove accounts for those users who were removed
// from LDAP/OpenID.
if sys.LDAPConfig.Enabled() {
sys.purgeExpiredCredentialsForLDAP(ctx)
sys.updateGroupMembershipsForLDAP(ctx)
}
if sys.OpenIDConfig.ProviderEnabled() {
sys.purgeExpiredCredentialsForExternalSSO(ctx)
}
}
timer.Reset(baseInterval + randInterval())
case <-ctx.Done():
return
}
}
}
func (sys *IAMSys) validateAndAddRolePolicyMappings(ctx context.Context, m map[arn.ARN]string) { func (sys *IAMSys) validateAndAddRolePolicyMappings(ctx context.Context, m map[arn.ARN]string) {
// Validate that policies associated with roles are defined. If // Validate that policies associated with roles are defined. If
// authZ plugin is set, role policies are just claims sent to // authZ plugin is set, role policies are just claims sent to
@@ -428,45 +464,6 @@ func (sys *IAMSys) HasWatcher() bool {
return sys.store.HasWatcher() return sys.store.HasWatcher()
} }
func (sys *IAMSys) watch(ctx context.Context) {
watcher, ok := sys.store.IAMStorageAPI.(iamStorageWatcher)
if ok {
ch := watcher.watch(ctx, iamConfigPrefix)
for event := range ch {
if err := sys.loadWatchedEvent(ctx, event); err != nil {
// we simply log errors
logger.LogIf(ctx, fmt.Errorf("Failure in loading watch event: %v", err))
}
}
return
}
var maxRefreshDurationSecondsForLog float64 = 10
// Load all items periodically
timer := time.NewTimer(sys.iamRefreshInterval)
defer timer.Stop()
for {
select {
case <-timer.C:
refreshStart := time.Now()
if err := sys.Load(ctx, false); err != nil {
logger.LogIf(ctx, fmt.Errorf("Failure in periodic refresh for IAM (took %.2fs): %v", time.Since(refreshStart).Seconds(), err))
} else {
took := time.Since(refreshStart).Seconds()
if took > maxRefreshDurationSecondsForLog {
// Log if we took a lot of time to load.
logger.Info("IAM refresh took %.2fs", took)
}
}
timer.Reset(sys.iamRefreshInterval)
case <-ctx.Done():
return
}
}
}
func (sys *IAMSys) loadWatchedEvent(ctx context.Context, event iamWatchEvent) (err error) { func (sys *IAMSys) loadWatchedEvent(ctx context.Context, event iamWatchEvent) (err error) {
usersPrefix := strings.HasPrefix(event.keyPath, iamConfigUsersPrefix) usersPrefix := strings.HasPrefix(event.keyPath, iamConfigUsersPrefix)
groupsPrefix := strings.HasPrefix(event.keyPath, iamConfigGroupsPrefix) groupsPrefix := strings.HasPrefix(event.keyPath, iamConfigGroupsPrefix)
@@ -536,7 +533,7 @@ func (sys *IAMSys) DeletePolicy(ctx context.Context, policyName string, notifyPe
return errServerNotInitialized return errServerNotInitialized
} }
for _, v := range iampolicy.DefaultPolicies { for _, v := range policy.DefaultPolicies {
if v.Name == policyName { if v.Name == policyName {
if err := checkConfig(ctx, globalObjectAPI, getPolicyDocPath(policyName)); err != nil && err == errConfigNotFound { if err := checkConfig(ctx, globalObjectAPI, getPolicyDocPath(policyName)); err != nil && err == errConfigNotFound {
return fmt.Errorf("inbuilt policy `%s` not allowed to be deleted", policyName) return fmt.Errorf("inbuilt policy `%s` not allowed to be deleted", policyName)
@@ -589,7 +586,7 @@ func (sys *IAMSys) InfoPolicy(policyName string) (*madmin.PolicyInfo, error) {
} }
// ListPolicies - lists all canned policies. // ListPolicies - lists all canned policies.
func (sys *IAMSys) ListPolicies(ctx context.Context, bucketName string) (map[string]iampolicy.Policy, error) { func (sys *IAMSys) ListPolicies(ctx context.Context, bucketName string) (map[string]policy.Policy, error) {
if !sys.Initialized() { if !sys.Initialized() {
return nil, errServerNotInitialized return nil, errServerNotInitialized
} }
@@ -607,7 +604,7 @@ func (sys *IAMSys) ListPolicyDocs(ctx context.Context, bucketName string) (map[s
} }
// SetPolicy - sets a new named policy. // SetPolicy - sets a new named policy.
func (sys *IAMSys) SetPolicy(ctx context.Context, policyName string, p iampolicy.Policy) (time.Time, error) { func (sys *IAMSys) SetPolicy(ctx context.Context, policyName string, p policy.Policy) (time.Time, error) {
if !sys.Initialized() { if !sys.Initialized() {
return time.Time{}, errServerNotInitialized return time.Time{}, errServerNotInitialized
} }
@@ -849,13 +846,20 @@ func (sys *IAMSys) GetUserInfo(ctx context.Context, name string) (u madmin.UserI
return u, errServerNotInitialized return u, errServerNotInitialized
} }
loadUserCalled := false
select { select {
case <-sys.configLoaded: case <-sys.configLoaded:
default: default:
sys.store.LoadUser(ctx, name) sys.store.LoadUser(ctx, name)
loadUserCalled = true
} }
return sys.store.GetUserInfo(name) userInfo, err := sys.store.GetUserInfo(name)
if err == errNoSuchUser && !loadUserCalled {
sys.store.LoadUser(ctx, name)
userInfo, err = sys.store.GetUserInfo(name)
}
return userInfo, err
} }
// QueryPolicyEntities - queries policy associations for builtin users/groups/policies. // QueryPolicyEntities - queries policy associations for builtin users/groups/policies.
@@ -915,7 +919,7 @@ func (sys *IAMSys) notifyForServiceAccount(ctx context.Context, accessKey string
} }
type newServiceAccountOpts struct { type newServiceAccountOpts struct {
sessionPolicy *iampolicy.Policy sessionPolicy *policy.Policy
accessKey string accessKey string
secretKey string secretKey string
name, description string name, description string
@@ -962,7 +966,7 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro
m[parentClaim] = parentUser m[parentClaim] = parentUser
if len(policyBuf) > 0 { if len(policyBuf) > 0 {
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString(policyBuf) m[policy.SessionPolicyName] = base64.StdEncoding.EncodeToString(policyBuf)
m[iamPolicyClaimNameSA()] = embeddedPolicyType m[iamPolicyClaimNameSA()] = embeddedPolicyType
} else { } else {
m[iamPolicyClaimNameSA()] = inheritedPolicyType m[iamPolicyClaimNameSA()] = inheritedPolicyType
@@ -1014,7 +1018,7 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro
} }
type updateServiceAccountOpts struct { type updateServiceAccountOpts struct {
sessionPolicy *iampolicy.Policy sessionPolicy *policy.Policy
secretKey string secretKey string
status string status string
name, description string name, description string
@@ -1065,7 +1069,7 @@ func (sys *IAMSys) ListTempAccounts(ctx context.Context, accessKey string) ([]Us
} }
// GetServiceAccount - wrapper method to get information about a service account // GetServiceAccount - wrapper method to get information about a service account
func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (auth.Credentials, *iampolicy.Policy, error) { func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (auth.Credentials, *policy.Policy, error) {
sa, embeddedPolicy, err := sys.getServiceAccount(ctx, accessKey) sa, embeddedPolicy, err := sys.getServiceAccount(ctx, accessKey)
if err != nil { if err != nil {
return auth.Credentials{}, nil, err return auth.Credentials{}, nil, err
@@ -1076,7 +1080,7 @@ func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (aut
return sa.Credentials, embeddedPolicy, nil return sa.Credentials, embeddedPolicy, nil
} }
func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (UserIdentity, *iampolicy.Policy, error) { func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (UserIdentity, *policy.Policy, error) {
sa, jwtClaims, err := sys.getAccountWithClaims(ctx, accessKey) sa, jwtClaims, err := sys.getAccountWithClaims(ctx, accessKey)
if err != nil { if err != nil {
if err == errNoSuchAccount { if err == errNoSuchAccount {
@@ -1088,16 +1092,16 @@ func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (Use
return UserIdentity{}, nil, errNoSuchServiceAccount return UserIdentity{}, nil, errNoSuchServiceAccount
} }
var embeddedPolicy *iampolicy.Policy var embeddedPolicy *policy.Policy
pt, ptok := jwtClaims.Lookup(iamPolicyClaimNameSA()) pt, ptok := jwtClaims.Lookup(iamPolicyClaimNameSA())
sp, spok := jwtClaims.Lookup(iampolicy.SessionPolicyName) sp, spok := jwtClaims.Lookup(policy.SessionPolicyName)
if ptok && spok && pt == embeddedPolicyType { if ptok && spok && pt == embeddedPolicyType {
policyBytes, err := base64.StdEncoding.DecodeString(sp) policyBytes, err := base64.StdEncoding.DecodeString(sp)
if err != nil { if err != nil {
return UserIdentity{}, nil, err return UserIdentity{}, nil, err
} }
embeddedPolicy, err = iampolicy.ParseConfig(bytes.NewReader(policyBytes)) embeddedPolicy, err = policy.ParseConfig(bytes.NewReader(policyBytes))
if err != nil { if err != nil {
return UserIdentity{}, nil, err return UserIdentity{}, nil, err
} }
@@ -1107,7 +1111,7 @@ func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (Use
} }
// GetTemporaryAccount - wrapper method to get information about a temporary account // GetTemporaryAccount - wrapper method to get information about a temporary account
func (sys *IAMSys) GetTemporaryAccount(ctx context.Context, accessKey string) (auth.Credentials, *iampolicy.Policy, error) { func (sys *IAMSys) GetTemporaryAccount(ctx context.Context, accessKey string) (auth.Credentials, *policy.Policy, error) {
tmpAcc, embeddedPolicy, err := sys.getTempAccount(ctx, accessKey) tmpAcc, embeddedPolicy, err := sys.getTempAccount(ctx, accessKey)
if err != nil { if err != nil {
return auth.Credentials{}, nil, err return auth.Credentials{}, nil, err
@@ -1118,7 +1122,7 @@ func (sys *IAMSys) GetTemporaryAccount(ctx context.Context, accessKey string) (a
return tmpAcc.Credentials, embeddedPolicy, nil return tmpAcc.Credentials, embeddedPolicy, nil
} }
func (sys *IAMSys) getTempAccount(ctx context.Context, accessKey string) (UserIdentity, *iampolicy.Policy, error) { func (sys *IAMSys) getTempAccount(ctx context.Context, accessKey string) (UserIdentity, *policy.Policy, error) {
tmpAcc, claims, err := sys.getAccountWithClaims(ctx, accessKey) tmpAcc, claims, err := sys.getAccountWithClaims(ctx, accessKey)
if err != nil { if err != nil {
if err == errNoSuchAccount { if err == errNoSuchAccount {
@@ -1130,15 +1134,15 @@ func (sys *IAMSys) getTempAccount(ctx context.Context, accessKey string) (UserId
return UserIdentity{}, nil, errNoSuchTempAccount return UserIdentity{}, nil, errNoSuchTempAccount
} }
var embeddedPolicy *iampolicy.Policy var embeddedPolicy *policy.Policy
sp, spok := claims.Lookup(iampolicy.SessionPolicyName) sp, spok := claims.Lookup(policy.SessionPolicyName)
if spok { if spok {
policyBytes, err := base64.StdEncoding.DecodeString(sp) policyBytes, err := base64.StdEncoding.DecodeString(sp)
if err != nil { if err != nil {
return UserIdentity{}, nil, err return UserIdentity{}, nil, err
} }
embeddedPolicy, err = iampolicy.ParseConfig(bytes.NewReader(policyBytes)) embeddedPolicy, err = policy.ParseConfig(bytes.NewReader(policyBytes))
if err != nil { if err != nil {
return UserIdentity{}, nil, err return UserIdentity{}, nil, err
} }
@@ -1421,31 +1425,24 @@ func (sys *IAMSys) GetUser(ctx context.Context, accessKey string) (u UserIdentit
return u, false return u, false
} }
fallback := false if accessKey == globalActiveCred.AccessKey {
return newUserIdentity(globalActiveCred), true
}
loadUserCalled := false
select { select {
case <-sys.configLoaded: case <-sys.configLoaded:
default: default:
sys.store.LoadUser(ctx, accessKey) sys.store.LoadUser(ctx, accessKey)
fallback = true loadUserCalled = true
} }
u, ok = sys.store.GetUser(accessKey) u, ok = sys.store.GetUser(accessKey)
if !ok && !fallback { if !ok && !loadUserCalled {
// accessKey not found, also
// IAM store is not in fallback mode
// we can try to reload again from
// the IAM store and see if credential
// exists now. If it doesn't proceed to
// fail.
sys.store.LoadUser(ctx, accessKey) sys.store.LoadUser(ctx, accessKey)
u, ok = sys.store.GetUser(accessKey) u, ok = sys.store.GetUser(accessKey)
} }
if !ok {
if accessKey == globalActiveCred.AccessKey {
return newUserIdentity(globalActiveCred), true
}
}
return u, ok && u.Credentials.IsValid() return u, ok && u.Credentials.IsValid()
} }
@@ -1723,11 +1720,11 @@ func (sys *IAMSys) PolicyDBGet(name string, isGroup bool, groups ...string) ([]s
return sys.store.PolicyDBGet(name, isGroup, groups...) return sys.store.PolicyDBGet(name, isGroup, groups...)
} }
const sessionPolicyNameExtracted = iampolicy.SessionPolicyName + "-extracted" const sessionPolicyNameExtracted = policy.SessionPolicyName + "-extracted"
// IsAllowedServiceAccount - checks if the given service account is allowed to perform // IsAllowedServiceAccount - checks if the given service account is allowed to perform
// actions. The permission of the parent user is checked first // actions. The permission of the parent user is checked first
func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser string) bool { func (sys *IAMSys) IsAllowedServiceAccount(args policy.Args, parentUser string) bool {
// Verify if the parent claim matches the parentUser. // Verify if the parent claim matches the parentUser.
p, ok := args.Claims[parentClaim] p, ok := args.Claims[parentClaim]
if ok { if ok {
@@ -1778,7 +1775,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin
// Finally, if there is no parent policy, check if a policy claim is // Finally, if there is no parent policy, check if a policy claim is
// present. // present.
if len(svcPolicies) == 0 { if len(svcPolicies) == 0 {
policySet, _ := iampolicy.GetPoliciesFromClaims(args.Claims, iamPolicyClaimNameOpenID()) policySet, _ := policy.GetPoliciesFromClaims(args.Claims, iamPolicyClaimNameOpenID())
svcPolicies = policySet.ToSlice() svcPolicies = policySet.ToSlice()
} }
} }
@@ -1788,7 +1785,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin
return false return false
} }
var combinedPolicy iampolicy.Policy var combinedPolicy policy.Policy
// Policies were found, evaluate all of them. // Policies were found, evaluate all of them.
if !isOwnerDerived { if !isOwnerDerived {
availablePoliciesStr, c := sys.store.FilterPolicies(strings.Join(svcPolicies, ","), "") availablePoliciesStr, c := sys.store.FilterPolicies(strings.Join(svcPolicies, ","), "")
@@ -1831,7 +1828,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin
} }
// Check if policy is parseable. // Check if policy is parseable.
subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr))) subPolicy, err := policy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
if err != nil { if err != nil {
// Log any error in input session policy config. // Log any error in input session policy config.
logger.LogIf(GlobalContext, err) logger.LogIf(GlobalContext, err)
@@ -1853,7 +1850,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin
// IsAllowedSTS is meant for STS based temporary credentials, // IsAllowedSTS is meant for STS based temporary credentials,
// 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, parentUser string) bool { func (sys *IAMSys) IsAllowedSTS(args policy.Args, parentUser string) bool {
// 1. Determine mapped policies // 1. Determine mapped policies
isOwnerDerived := parentUser == globalActiveCred.AccessKey isOwnerDerived := parentUser == globalActiveCred.AccessKey
@@ -1905,7 +1902,7 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args, parentUser string) bool {
// 2. Combine the mapped policies into a single combined policy. // 2. Combine the mapped policies into a single combined policy.
var combinedPolicy iampolicy.Policy var combinedPolicy policy.Policy
if !isOwnerDerived { if !isOwnerDerived {
var err error var err error
combinedPolicy, err = sys.store.GetPolicy(strings.Join(policies, ",")) combinedPolicy, err = sys.store.GetPolicy(strings.Join(policies, ","))
@@ -1937,7 +1934,7 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args, parentUser string) bool {
return isOwnerDerived || combinedPolicy.IsAllowed(args) return isOwnerDerived || combinedPolicy.IsAllowed(args)
} }
func isAllowedBySessionPolicy(args iampolicy.Args) (hasSessionPolicy bool, isAllowed bool) { func isAllowedBySessionPolicy(args policy.Args) (hasSessionPolicy bool, isAllowed bool) {
hasSessionPolicy = false hasSessionPolicy = false
isAllowed = false isAllowed = false
@@ -1957,7 +1954,7 @@ func isAllowedBySessionPolicy(args iampolicy.Args) (hasSessionPolicy bool, isAll
} }
// Check if policy is parseable. // Check if policy is parseable.
subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr))) subPolicy, err := policy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
if err != nil { if err != nil {
// Log any error in input session policy config. // Log any error in input session policy config.
logger.LogIf(GlobalContext, err) logger.LogIf(GlobalContext, err)
@@ -1974,13 +1971,13 @@ func isAllowedBySessionPolicy(args iampolicy.Args) (hasSessionPolicy bool, isAll
} }
// GetCombinedPolicy returns a combined policy combining all policies // GetCombinedPolicy returns a combined policy combining all policies
func (sys *IAMSys) GetCombinedPolicy(policies ...string) iampolicy.Policy { func (sys *IAMSys) GetCombinedPolicy(policies ...string) policy.Policy {
_, policy := sys.store.FilterPolicies(strings.Join(policies, ","), "") _, policy := sys.store.FilterPolicies(strings.Join(policies, ","), "")
return policy return policy
} }
// IsAllowed - checks given policy args is allowed to continue the Rest API. // IsAllowed - checks given policy args is allowed to continue the Rest API.
func (sys *IAMSys) IsAllowed(args iampolicy.Args) bool { func (sys *IAMSys) IsAllowed(args policy.Args) bool {
// If opa is configured, use OPA always. // If opa is configured, use OPA always.
if authz := newGlobalAuthZPluginFn(); authz != nil { if authz := newGlobalAuthZPluginFn(); authz != nil {
ok, err := authz.IsAllowed(args) ok, err := authz.IsAllowed(args)
+2 -2
View File
@@ -28,7 +28,7 @@ import (
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
xjwt "github.com/minio/minio/internal/jwt" xjwt "github.com/minio/minio/internal/jwt"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/policy"
) )
const ( const (
@@ -148,7 +148,7 @@ func metricsRequestAuthenticate(req *http.Request) (*xjwt.MapClaims, []string, b
} }
// Now check if we have a sessionPolicy. // Now check if we have a sessionPolicy.
if _, ok = eclaims[iampolicy.SessionPolicyName]; ok { if _, ok = eclaims[policy.SessionPolicyName]; ok {
owner = false owner = false
} else { } else {
owner = globalActiveCred.AccessKey == ucred.ParentUser owner = globalActiveCred.AccessKey == ucred.ParentUser
+19 -19
View File
@@ -29,14 +29,14 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
iampolicy "github.com/minio/pkg/iam/policy" "github.com/minio/pkg/v2/policy"
) )
// KMSStatusHandler - GET /minio/kms/v1/status // KMSStatusHandler - GET /minio/kms/v1/status
func (a kmsAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) { func (a kmsAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "KMSStatus") ctx := newContext(r, w, "KMSStatus")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSStatusAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSStatusAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -74,7 +74,7 @@ func (a kmsAPIHandlers) KMSMetricsHandler(w http.ResponseWriter, r *http.Request
ctx := newContext(r, w, "KMSMetrics") ctx := newContext(r, w, "KMSMetrics")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSMetricsAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSMetricsAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -106,7 +106,7 @@ func (a kmsAPIHandlers) KMSAPIsHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "KMSAPIs") ctx := newContext(r, w, "KMSAPIs")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSAPIAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSAPIAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -143,7 +143,7 @@ func (a kmsAPIHandlers) KMSVersionHandler(w http.ResponseWriter, r *http.Request
ctx := newContext(r, w, "KMSVersion") ctx := newContext(r, w, "KMSVersion")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSVersionAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSVersionAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -183,7 +183,7 @@ func (a kmsAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Reque
} }
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSCreateKeyAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSCreateKeyAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -211,7 +211,7 @@ func (a kmsAPIHandlers) KMSDeleteKeyHandler(w http.ResponseWriter, r *http.Reque
ctx := newContext(r, w, "KMSDeleteKey") ctx := newContext(r, w, "KMSDeleteKey")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeleteKeyAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSDeleteKeyAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -246,7 +246,7 @@ func (a kmsAPIHandlers) KMSListKeysHandler(w http.ResponseWriter, r *http.Reques
} }
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListKeysAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSListKeysAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -297,7 +297,7 @@ func (a kmsAPIHandlers) KMSImportKeyHandler(w http.ResponseWriter, r *http.Reque
ctx := newContext(r, w, "KMSImportKey") ctx := newContext(r, w, "KMSImportKey")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSImportKeyAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSImportKeyAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -328,7 +328,7 @@ func (a kmsAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Reque
ctx := newContext(r, w, "KMSKeyStatus") ctx := newContext(r, w, "KMSKeyStatus")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSKeyStatusAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -404,7 +404,7 @@ func (a kmsAPIHandlers) KMSDescribePolicyHandler(w http.ResponseWriter, r *http.
ctx := newContext(r, w, "KMSDescribePolicy") ctx := newContext(r, w, "KMSDescribePolicy")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribePolicyAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSDescribePolicyAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -440,7 +440,7 @@ func (a kmsAPIHandlers) KMSAssignPolicyHandler(w http.ResponseWriter, r *http.Re
ctx := newContext(r, w, "KMSAssignPolicy") ctx := newContext(r, w, "KMSAssignPolicy")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSAssignPolicyAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSAssignPolicyAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -472,7 +472,7 @@ func (a kmsAPIHandlers) KMSDeletePolicyHandler(w http.ResponseWriter, r *http.Re
ctx := newContext(r, w, "KMSDeletePolicy") ctx := newContext(r, w, "KMSDeletePolicy")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeletePolicyAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSDeletePolicyAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -499,7 +499,7 @@ func (a kmsAPIHandlers) KMSListPoliciesHandler(w http.ResponseWriter, r *http.Re
ctx := newContext(r, w, "KMSListPolicies") ctx := newContext(r, w, "KMSListPolicies")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListPoliciesAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSListPoliciesAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -546,7 +546,7 @@ func (a kmsAPIHandlers) KMSGetPolicyHandler(w http.ResponseWriter, r *http.Reque
ctx := newContext(r, w, "KMSGetPolicy") ctx := newContext(r, w, "KMSGetPolicy")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSGetPolicyAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSGetPolicyAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -578,7 +578,7 @@ func (a kmsAPIHandlers) KMSDescribeIdentityHandler(w http.ResponseWriter, r *htt
ctx := newContext(r, w, "KMSDescribeIdentity") ctx := newContext(r, w, "KMSDescribeIdentity")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribeIdentityAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSDescribeIdentityAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -619,7 +619,7 @@ func (a kmsAPIHandlers) KMSDescribeSelfIdentityHandler(w http.ResponseWriter, r
ctx := newContext(r, w, "KMSDescribeSelfIdentity") ctx := newContext(r, w, "KMSDescribeSelfIdentity")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribeSelfIdentityAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSDescribeSelfIdentityAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -659,7 +659,7 @@ func (a kmsAPIHandlers) KMSDeleteIdentityHandler(w http.ResponseWriter, r *http.
ctx := newContext(r, w, "KMSDeleteIdentity") ctx := newContext(r, w, "KMSDeleteIdentity")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeleteIdentityAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSDeleteIdentityAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
@@ -686,7 +686,7 @@ func (a kmsAPIHandlers) KMSListIdentitiesHandler(w http.ResponseWriter, r *http.
ctx := newContext(r, w, "KMSListIdentities") ctx := newContext(r, w, "KMSListIdentities")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListIdentitiesAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSListIdentitiesAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
+1 -1
View File
@@ -27,7 +27,7 @@ import (
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/pubsub" "github.com/minio/minio/internal/pubsub"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/v2/policy"
) )
func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r *http.Request) {
+4 -4
View File
@@ -30,10 +30,10 @@ import (
"github.com/minio/cli" "github.com/minio/cli"
"github.com/minio/minio/internal/color" "github.com/minio/minio/internal/color"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/console" "github.com/minio/pkg/v2/console"
"github.com/minio/pkg/env" "github.com/minio/pkg/v2/env"
"github.com/minio/pkg/trie" "github.com/minio/pkg/v2/trie"
"github.com/minio/pkg/words" "github.com/minio/pkg/v2/words"
) )
// GlobalFlags - global flags for minio. // GlobalFlags - global flags for minio.
+1 -1
View File
@@ -26,7 +26,7 @@ import (
"time" "time"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/console" "github.com/minio/pkg/v2/console"
) )
// a bucketMetacache keeps track of all caches generated // a bucketMetacache keeps track of all caches generated
+4 -4
View File
@@ -27,7 +27,7 @@ import (
"strings" "strings"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/console" "github.com/minio/pkg/v2/console"
) )
// metaCacheEntry is an object or a directory within an unknown bucket. // metaCacheEntry is an object or a directory within an unknown bucket.
@@ -248,9 +248,9 @@ func (e *metaCacheEntry) fileInfo(bucket string) (FileInfo, error) {
ModTime: timeSentinel1970, ModTime: timeSentinel1970,
}, nil }, nil
} }
return e.cached.ToFileInfo(bucket, e.name, "", false) return e.cached.ToFileInfo(bucket, e.name, "", false, false)
} }
return getFileInfo(e.metadata, bucket, e.name, "", false) return getFileInfo(e.metadata, bucket, e.name, "", false, false)
} }
// xlmeta returns the decoded metadata. // xlmeta returns the decoded metadata.
@@ -291,7 +291,7 @@ func (e *metaCacheEntry) fileInfoVersions(bucket string) (FileInfoVersions, erro
}, nil }, nil
} }
// Too small gains to reuse cache here. // Too small gains to reuse cache here.
return getFileInfoVersions(e.metadata, bucket, e.name) return getFileInfoVersions(e.metadata, bucket, e.name, false)
} }
// metaCacheEntries is a slice of metacache entries. // metaCacheEntries is a slice of metacache entries.
+5 -1
View File
@@ -38,7 +38,7 @@ func renameAllBucketMetacache(epPath string) error {
if typ == os.ModeDir { if typ == os.ModeDir {
tmpMetacacheOld := pathutil.Join(epPath, minioMetaTmpDeletedBucket, mustGetUUID()) tmpMetacacheOld := pathutil.Join(epPath, minioMetaTmpDeletedBucket, mustGetUUID())
if err := renameAll(pathJoin(epPath, minioMetaBucket, metacachePrefixForID(name, slashSeparator)), if err := renameAll(pathJoin(epPath, minioMetaBucket, metacachePrefixForID(name, slashSeparator)),
tmpMetacacheOld); err != nil && err != errFileNotFound { tmpMetacacheOld, epPath); err != nil && err != errFileNotFound {
return fmt.Errorf("unable to rename (%s -> %s) %w", return fmt.Errorf("unable to rename (%s -> %s) %w",
pathJoin(epPath, minioMetaBucket+metacachePrefixForID(minioMetaBucket, slashSeparator)), pathJoin(epPath, minioMetaBucket+metacachePrefixForID(minioMetaBucket, slashSeparator)),
tmpMetacacheOld, tmpMetacacheOld,
@@ -60,6 +60,10 @@ func (z *erasureServerPools) listPath(ctx context.Context, o *listPathOptions) (
if err := checkListObjsArgs(ctx, o.Bucket, o.Prefix, o.Marker, z); err != nil { if err := checkListObjsArgs(ctx, o.Bucket, o.Prefix, o.Marker, z); err != nil {
return entries, err return entries, err
} }
// Marker points to before the prefix, just ignore it.
if o.Marker < o.Prefix {
o.Marker = ""
}
// Marker is set validate pre-condition. // Marker is set validate pre-condition.
if o.Marker != "" && o.Prefix != "" { if o.Marker != "" && o.Prefix != "" {
+2 -2
View File
@@ -38,7 +38,7 @@ import (
"github.com/minio/minio/internal/color" "github.com/minio/minio/internal/color"
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/console" "github.com/minio/pkg/v2/console"
) )
type listPathOptions struct { type listPathOptions struct {
@@ -761,7 +761,7 @@ func (er *erasureObjects) saveMetaCacheStream(ctx context.Context, mc *metaCache
return nil return nil
} }
o.debugln(color.Green("saveMetaCacheStream:")+" saving block", b.n, "to", o.objectPath(b.n)) o.debugln(color.Green("saveMetaCacheStream:")+" saving block", b.n, "to", o.objectPath(b.n))
r, err := hash.NewReader(bytes.NewReader(b.data), int64(len(b.data)), "", "", int64(len(b.data))) r, err := hash.NewReader(ctx, bytes.NewReader(b.data), int64(len(b.data)), "", "", int64(len(b.data)))
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
custom := b.headerKV() custom := b.headerKV()
_, err = er.putMetacacheObject(ctx, o.objectPath(b.n), NewPutObjReader(r), ObjectOptions{ _, err = er.putMetacacheObject(ctx, o.objectPath(b.n), NewPutObjReader(r), ObjectOptions{
+15 -10
View File
@@ -18,7 +18,6 @@
package cmd package cmd
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"io" "io"
@@ -32,6 +31,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/valyala/bytebufferpool"
) )
// WalkDirOptions provides options for WalkDir operations. // WalkDirOptions provides options for WalkDir operations.
@@ -144,7 +144,12 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
scanDir = func(current string) error { scanDir = func(current string) error {
// Skip forward, if requested... // Skip forward, if requested...
var sb bytes.Buffer sb := bytebufferpool.Get()
defer func() {
sb.Reset()
bytebufferpool.Put(sb)
}()
forward := "" forward := ""
if len(opts.ForwardTo) > 0 && strings.HasPrefix(opts.ForwardTo, current) { if len(opts.ForwardTo) > 0 && strings.HasPrefix(opts.ForwardTo, current) {
forward = strings.TrimPrefix(opts.ForwardTo, current) forward = strings.TrimPrefix(opts.ForwardTo, current)
@@ -228,7 +233,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
if s.walkReadMu != nil { if s.walkReadMu != nil {
s.walkReadMu.Lock() s.walkReadMu.Lock()
} }
meta.metadata, err = s.readMetadata(ctx, pathJoinBuf(&sb, volumeDir, current, entry)) meta.metadata, err = s.readMetadata(ctx, pathJoinBuf(sb, volumeDir, current, entry))
if s.walkReadMu != nil { if s.walkReadMu != nil {
s.walkReadMu.Unlock() s.walkReadMu.Unlock()
} }
@@ -244,7 +249,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
} }
meta.name = strings.TrimSuffix(entry, xlStorageFormatFile) meta.name = strings.TrimSuffix(entry, xlStorageFormatFile)
meta.name = strings.TrimSuffix(meta.name, SlashSeparator) meta.name = strings.TrimSuffix(meta.name, SlashSeparator)
meta.name = pathJoinBuf(&sb, current, meta.name) meta.name = pathJoinBuf(sb, current, meta.name)
meta.name = decodeDirObject(meta.name) meta.name = decodeDirObject(meta.name)
if err := send(meta); err != nil { if err := send(meta); err != nil {
return err return err
@@ -254,7 +259,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
// Check legacy. // Check legacy.
if HasSuffix(entry, xlStorageFormatFileV1) && legacy { if HasSuffix(entry, xlStorageFormatFileV1) && legacy {
var meta metaCacheEntry var meta metaCacheEntry
meta.metadata, err = xioutil.ReadFile(pathJoinBuf(&sb, volumeDir, current, entry)) meta.metadata, err = xioutil.ReadFile(pathJoinBuf(sb, volumeDir, current, entry))
diskHealthCheckOK(ctx, err) diskHealthCheckOK(ctx, err)
if err != nil { if err != nil {
if !IsErrIgnored(err, io.EOF, io.ErrUnexpectedEOF) { if !IsErrIgnored(err, io.EOF, io.ErrUnexpectedEOF) {
@@ -264,7 +269,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
} }
meta.name = strings.TrimSuffix(entry, xlStorageFormatFileV1) meta.name = strings.TrimSuffix(entry, xlStorageFormatFileV1)
meta.name = strings.TrimSuffix(meta.name, SlashSeparator) meta.name = strings.TrimSuffix(meta.name, SlashSeparator)
meta.name = pathJoinBuf(&sb, current, meta.name) meta.name = pathJoinBuf(sb, current, meta.name)
if err := send(meta); err != nil { if err := send(meta); err != nil {
return err return err
} }
@@ -297,7 +302,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
if contextCanceled(ctx) { if contextCanceled(ctx) {
return ctx.Err() return ctx.Err()
} }
meta := metaCacheEntry{name: pathJoinBuf(&sb, current, entry)} meta := metaCacheEntry{name: pathJoinBuf(sb, current, entry)}
// If directory entry on stack before this, pop it now. // If directory entry on stack before this, pop it now.
for len(dirStack) > 0 && dirStack[len(dirStack)-1] < meta.name { for len(dirStack) > 0 && dirStack[len(dirStack)-1] < meta.name {
@@ -323,7 +328,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
if s.walkReadMu != nil { if s.walkReadMu != nil {
s.walkReadMu.Lock() s.walkReadMu.Lock()
} }
meta.metadata, err = s.readMetadata(ctx, pathJoinBuf(&sb, volumeDir, meta.name, xlStorageFormatFile)) meta.metadata, err = s.readMetadata(ctx, pathJoinBuf(sb, volumeDir, meta.name, xlStorageFormatFile))
if s.walkReadMu != nil { if s.walkReadMu != nil {
s.walkReadMu.Unlock() s.walkReadMu.Unlock()
} }
@@ -339,7 +344,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
} }
case osIsNotExist(err), isSysErrIsDir(err): case osIsNotExist(err), isSysErrIsDir(err):
if legacy { if legacy {
meta.metadata, err = xioutil.ReadFile(pathJoinBuf(&sb, volumeDir, meta.name, xlStorageFormatFileV1)) meta.metadata, err = xioutil.ReadFile(pathJoinBuf(sb, volumeDir, meta.name, xlStorageFormatFileV1))
diskHealthCheckOK(ctx, err) diskHealthCheckOK(ctx, err)
if err == nil { if err == nil {
// It was an object // It was an object
@@ -353,7 +358,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
// NOT an object, append to stack (with slash) // NOT an object, append to stack (with slash)
// If dirObject, but no metadata (which is unexpected) we skip it. // If dirObject, but no metadata (which is unexpected) we skip it.
if !isDirObj { if !isDirObj {
if !isDirEmpty(pathJoinBuf(&sb, volumeDir, meta.name)) { if !isDirEmpty(pathJoinBuf(sb, volumeDir, meta.name)) {
dirStack = append(dirStack, meta.name+slashSeparator) dirStack = append(dirStack, meta.name+slashSeparator)
} }
} }

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