Compare commits

...

41 Commits

Author SHA1 Message Date
Klaus Post 8c2c92f7af Fix healing probability for skipped folders (#20988)
We must update the heal probability when selectively skipping folders.
2025-02-28 01:55:16 -08:00
Aditya Manthramurthy 4c71f1b4ec fix: SFTP auth bypass with no pub key in LDAP (#20986)
If a user attempts to authenticate with a key but does not have an
sshpubkey attribute in LDAP, the server allows the connection, which 
means the server trusted the key without reason. This is now fixed, 
and a test has been added for validation.
2025-02-27 10:43:32 -08:00
Poorna 6cd8a372cb replication: set checksum type correctly (#20985)
Fixes: #20978
2025-02-26 15:17:28 -08:00
Anis Eleuch 953a3e2bbd check for errors on bitrotWriter Close() (#20982) 2025-02-26 11:26:13 -08:00
Mark Theunissen 7cc0c69228 Allow disabling of all X-Forwarded-For header processing (#20977) 2025-02-26 11:25:49 -08:00
Anis Eleuch f129fd48f2 Update golang.org/x/crypto to address govulncheck complaint (#20983) 2025-02-26 08:15:09 -08:00
TripleChecker bc4008ced4 Fix typos (#20970) 2025-02-26 01:25:50 -08:00
dependabot[bot] 526053339b build(deps): bump github.com/go-jose/go-jose/v4 from 4.0.4 to 4.0.5 (#20976)
Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.0.4 to 4.0.5.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v4.0.4...v4.0.5)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-26 01:25:19 -08:00
Taran Pelkey 62a35b3e77 Update SRSvcAccCreate with new type (#20974) 2025-02-24 17:43:59 -08:00
Taran Pelkey 39df134204 Fix importIAM issue with importing implied policies (#20956) 2025-02-19 10:10:53 -08:00
Minio Trusted ad4cbce22d Update yaml files to latest version RELEASE.2025-02-18T16-25-55Z 2025-02-18 20:59:14 +00:00
Klaus Post 90f5e1e5f6 tests: Do not allow forced type asserts (#20905) 2025-02-18 08:25:55 -08:00
Klaus Post aeabac9181 Test checksum types for invalid combinations (#20953) 2025-02-18 08:24:01 -08:00
Klaus Post b312f13473 Extract all files from encrypted stream with inspect (#20937)
Allow multiple private keys and extract all files from streams.

Place files in the folder with `.enc` removed.

Do basic checks so streams cannot traverse outside of the folder.
2025-02-17 09:09:42 -08:00
Rodrigo dos Santos Felix 727a803bc0 fix(docs): update mc admin trace link to MinIO official docs (#20943) 2025-02-16 20:52:27 -08:00
Name d0e443172d chore: remove unused and incorrect IsEmpty method from TargetIDSet (#20939) 2025-02-16 08:43:15 -08:00
Jeeva Kandasamy 60446e7ac0 ftp: Enable trailing headers, just like sftp (#20938) 2025-02-15 02:32:09 -08:00
Harshavardhana b8544266e5 fix: typo in queuestore.go 2025-02-15 02:31:50 -08:00
Ramon de Klein 437dd4e32a Fix missing authorization check for PutObjectRetentionHandler (#20929) 2025-02-12 08:08:13 -08:00
Cesar N. 447054b841 Update console to 1.7.6 (#20925) 2025-02-11 15:43:04 -08:00
Harshavardhana 9bf43e54cd allow ARCH specific hotfixes 2025-02-11 14:33:31 -08:00
Manuel Reis 60f8423157 Quick patch for Snowball AutoExtract: #20883 (#20885)
* Checking allowance on empty prefix or Snowball-prefix - fixes #20883
* Check the policy for each object during Snowball auto-extraction
2025-02-10 15:52:59 -08:00
Klaus Post 4355ea3c3f (s)ftp: Enable trailing headers for upload (#20914)
Since we always "connect" to minio, it is fine.
2025-02-10 08:35:49 -08:00
Klaus Post e30f1ad7bd Fix nil pointer deref in PeerPolicyMappingHandler (#20913)
The following lines will attempt to de-reference the nil value. Instead just return the error at once.
2025-02-10 08:35:13 -08:00
Minio Trusted f00c8c4cce Update yaml files to latest version RELEASE.2025-02-07T23-21-09Z 2025-02-08 21:03:40 +00:00
Andreas Auernhammer 703f51164d kms: add MINIO_KMS_REPLICATE_KEYID option (#20909)
This commit adds the `MINIO_KMS_REPLICATE_KEYID` env. variable.
By default - if not specified or not set to `off` - MinIO will
replicate the KMS key ID of an object.

If `MINIO_KMS_REPLICATE_KEYID=off`, MinIO does not include the
object's KMS Key ID when replicating an object. However, it always
sets the SSE-KMS encryption header. This ensures that the object
gets encrypted using SSE-KMS. The target site chooses the KMS key
ID that gets used based on the site and bucket config.

Signed-off-by: Andreas Auernhammer <github@aead.dev>
2025-02-07 15:21:09 -08:00
Klaus Post b8dde47d4e fix: multipart replication with single part objects (#20895)
x-amz-checksum-algorithm is not set, causing all multipart single-part objects
to fail to replicate going via sftp/FTP uploads.
2025-02-05 15:06:02 -08:00
Andreas Auernhammer 7fa3e39f85 sts: allow client-provided intermediate CAs (#20896)
This commit allows clients to provide a set of intermediate CA
certificates (up to `MaxIntermediateCAs`) that the server will
use as intermediate CAs when verifying the trust chain from the
client leaf certificate up to one trusted root CA.

This is required if the client leaf certificate is not issued by
a trusted CA directly but by an intermediate CA. Without this commit,
MinIO rejects such certificates.

Signed-off-by: Andreas Auernhammer <github@aead.dev>
2025-02-04 16:29:41 -08:00
Poorna 4df7a3aa8f fix: site replication of bucket deletion sync (#352)
Bucket deletion timestamp was not being passed back
in GetBucketInfo, which is needed to decide on the bucket
creation/deletion
2025-02-04 00:36:03 -08:00
Poorna 64a8f2e554 replication: default tag timestamps in CopyObject call (#20891)
If object is uploaded with tags, the internal tagging-timestamp tracked
for replication will be missing. Default to ModTime in such cases to
allow tags to be synced correctly.

Also fixing a regression in fetching tags and tag comparison
2025-02-04 00:35:55 -08:00
Minio Trusted f4fd4ea66d Update yaml files to latest version RELEASE.2025-02-03T21-03-04Z 2025-02-04 06:55:11 +00:00
Anis Eleuch 712fe1a8df fix: proxy requests to honor global transport
* fix: proxy requests to honor global transport 
Load the globalProxyEndpoint properly

also, currently, the proxy requests will fail silently for batch cancel
even if the proxy fails; instead,d properly send the corresponding error back
for such proxy failures if opted

* pass the transport to the GetProxyEnpoints function

---------

Co-authored-by: Praveen raj Mani <praveen@minio.io>
2025-02-03 22:03:04 +01:00
Klaus Post 4a319bedc9 Redact sensitive fields from DescribeBatchJob (#20881)
Redacts the following if set:

* replicate/credentials/secretKey
* replicate/credentials/sessionToken
* expire/notify/token
2025-02-03 08:56:26 -08:00
Klaus Post bdb3db6dad Add lock overload protection (#20876)
Reject new lock requests immediately when 1000 goroutines are queued 
for the local lock mutex.

We do not reject unlocking, refreshing, or maintenance; they add to the count.

The limit is set to allow for bursty behavior but prevent requests from 
overloading the server completely.
2025-01-31 11:54:34 -08:00
Klaus Post abb385af41 Check for valid checksum (#20878)
Add a few safety measures for checksums.
2025-01-28 16:59:23 -08:00
Harshavardhana 4ee62606e4 update govulncheck 2025-01-28 11:11:08 -08:00
Anis Eleuch 079d64c801 DeleteObjects: Send delete to all pools (#172) (#20821)
Currently, DeleteObjects() tries to find the object's pool before
sending a delete request. This only works well when an object has
multiple versions in different pools since looking for the pool does
not consider the version-id. When an S3 client wants to
remove a version-id that exists in pool 2, the delete request will be
directed to pool one because it has another version of the same object.

This commit will remove looking for pool logic and will send a delete
request to all pools in parallel. This should not cause any performance
regression in most of the cases since the object will unlikely exist
in only one pool, and the performance price will be similar to
getPoolIndex() in that case.
2025-01-28 08:57:18 -08:00
Klaus Post dcc000ae2c Allow URLs up to 32KB and improve parsing speed (#20874)
Before/after...
```
Benchmark_hasBadPathComponent/long-32          	   43936	     27232 ns/op	 146.89 MB/s	   32768 B/op	       1 allocs/op
Benchmark_hasBadPathComponent/long-32          	   89956	     13375 ns/op	 299.07 MB/s	       0 B/op	       0 allocs/op
```

* Remove unused.
2025-01-27 08:42:45 -08:00
Harshavardhana c5d19ecebb do not expose secret-key to lambda event handler (#20870) 2025-01-24 11:27:43 -08:00
Harshavardhana ed29a525b3 remove fips builds 2025-01-21 02:10:10 -08:00
Minio Trusted 020c46cd3c Update yaml files to latest version RELEASE.2025-01-20T14-49-07Z 2025-01-21 09:44:32 +00:00
156 changed files with 1568 additions and 1046 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.23.2
go-version: 1.23.5
- name: Get official govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
shell: bash
+10 -1
View File
@@ -21,16 +21,25 @@ linters:
- misspell
- revive
- staticcheck
- tenv
- typecheck
- unconvert
- unused
- usetesting
- forcetypeassert
- whitespace
issues:
exclude-use-default: false
max-issues-per-linter: 100
max-same-issues: 100
exclude:
- "empty-block:"
- "unused-parameter:"
- "dot-imports:"
- should have a package comment
- error strings should not be capitalized or end with punctuation or a newline
exclude-rules:
# Exclude some linters from running on tests files.
- path: _test\.go
linters:
- forcetypeassert
-69
View File
@@ -1,69 +0,0 @@
FROM golang:1.23-alpine AS build
ARG TARGETARCH
ARG RELEASE
ENV GOPATH=/go
ENV CGO_ENABLED=0
# Install curl and minisign
RUN apk add -U --no-cache ca-certificates && \
apk add -U --no-cache curl && \
go install aead.dev/minisign/cmd/minisign@v0.2.1
# Download minio binary and signature files
RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips -o /go/bin/minio && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.minisig -o /go/bin/minio.minisig && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.sha256sum -o /go/bin/minio.sha256sum && \
chmod +x /go/bin/minio
# Download mc binary and signature files
RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.fips -o /go/bin/mc && \
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.fips.minisig -o /go/bin/mc.minisig && \
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.fips.sha256sum -o /go/bin/mc.sha256sum && \
chmod +x /go/bin/mc
RUN if [ "$TARGETARCH" = "amd64" ]; then \
curl -L -s -q https://github.com/moparisthebest/static-curl/releases/latest/download/curl-${TARGETARCH} -o /go/bin/curl; \
chmod +x /go/bin/curl; \
fi
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
FROM registry.access.redhat.com/ubi9/ubi-micro:latest
ARG RELEASE
LABEL name="MinIO" \
vendor="MinIO Inc <dev@min.io>" \
maintainer="MinIO Inc <dev@min.io>" \
version="${RELEASE}" \
release="${RELEASE}" \
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \
MINIO_ROOT_USER_FILE=access_key \
MINIO_ROOT_PASSWORD_FILE=secret_key \
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
MINIO_CONFIG_ENV_FILE=config.env
RUN chmod -R 777 /usr/bin
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /go/bin/minio* /usr/bin/
COPY --from=build /go/bin/mc* /usr/bin/
COPY --from=build /go/bin/cur* /usr/bin/
COPY CREDITS /licenses/CREDITS
COPY LICENSE /licenses/LICENSE
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
EXPOSE 9000
VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
CMD ["minio"]
+11 -11
View File
@@ -2,8 +2,8 @@ PWD := $(shell pwd)
GOPATH := $(shell go env GOPATH)
LDFLAGS := $(shell go run buildscripts/gen-ldflags.go)
GOARCH := $(shell go env GOARCH)
GOOS := $(shell go env GOOS)
GOOS ?= $(shell go env GOOS)
GOARCH ?= $(shell go env GOARCH)
VERSION ?= $(shell git describe --tags)
REPO ?= quay.io/minio
@@ -180,7 +180,7 @@ build-debugging:
build: checks build-debugging ## builds minio to $(PWD)
@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 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
hotfix-vars:
$(eval LDFLAGS := $(shell MINIO_RELEASE="RELEASE" MINIO_HOTFIX="hotfix.$(shell git rev-parse --short HEAD)" go run buildscripts/gen-ldflags.go $(shell git describe --tags --abbrev=0 | \
@@ -188,9 +188,9 @@ hotfix-vars:
$(eval VERSION := $(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD))
hotfix: hotfix-vars clean install ## builds minio binary with hotfix tags
@wget -q -c https://github.com/minio/pkger/releases/download/v2.3.1/pkger_2.3.1_linux_amd64.deb
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.0.1/linux-systemd/distributed/minio.service
@sudo apt install ./pkger_2.3.1_linux_amd64.deb --yes
@wget -q -c https://github.com/minio/pkger/releases/download/v2.3.10/pkger_2.3.10_linux_amd64.deb
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.1.0/linux-systemd/distributed/minio.service
@sudo apt install ./pkger_2.3.10_linux_amd64.deb --yes
@mkdir -p minio-release/$(GOOS)-$(GOARCH)/archive
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION)
@@ -200,11 +200,11 @@ hotfix: hotfix-vars clean install ## builds minio binary with hotfix tags
@pkger -r $(VERSION) --ignore
hotfix-push: hotfix
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-0.minio.io:~/releases/server/minio/hotfixes/linux-amd64/
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-0.minio.io:~/releases/server/minio/hotfixes/linux-amd64/archive
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-1.minio.io:~/releases/server/minio/hotfixes/linux-amd64/
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-1.minio.io:~/releases/server/minio/hotfixes/linux-amd64/archive
@echo "Published new hotfix binaries at https://dl.min.io/server/minio/hotfixes/linux-amd64/archive/minio.$(VERSION)"
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-0.minio.io:~/releases/server/minio/hotfixes/linux-$(GOOS)/
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-0.minio.io:~/releases/server/minio/hotfixes/linux-$(GOOS)/archive
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-1.minio.io:~/releases/server/minio/hotfixes/linux-$(GOOS)/
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-1.minio.io:~/releases/server/minio/hotfixes/linux-$(GOOS)/archive
@echo "Published new hotfix binaries at https://dl.min.io/server/minio/hotfixes/linux-$(GOOS)/archive/minio.$(VERSION)"
docker-hotfix-push: docker-hotfix
@docker push -q $(TAG) && echo "Published new container $(TAG)"
+5 -5
View File
@@ -38,6 +38,7 @@ import (
objectlock "github.com/minio/minio/internal/bucket/object/lock"
"github.com/minio/minio/internal/bucket/versioning"
"github.com/minio/minio/internal/event"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/kms"
"github.com/minio/mux"
"github.com/minio/pkg/v3/policy"
@@ -980,7 +981,6 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
rpt.SetStatus(bucket, "", err)
continue
}
}
rptData, err := json.Marshal(rpt.BucketMetaImportErrs)
@@ -1039,7 +1039,7 @@ func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.
}
if len(diffCh) == 0 {
// Flush if nothing is queued
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
case <-keepAliveTicker.C:
if len(diffCh) > 0 {
@@ -1048,7 +1048,7 @@ func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.
if _, err := w.Write([]byte(" ")); err != nil {
return
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
case <-ctx.Done():
return
}
@@ -1098,7 +1098,7 @@ func (a adminAPIHandlers) ReplicationMRFHandler(w http.ResponseWriter, r *http.R
}
if len(mrfCh) == 0 {
// Flush if nothing is queued
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
case <-keepAliveTicker.C:
if len(mrfCh) > 0 {
@@ -1107,7 +1107,7 @@ func (a adminAPIHandlers) ReplicationMRFHandler(w http.ResponseWriter, r *http.R
if _, err := w.Write([]byte(" ")); err != nil {
return
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
case <-ctx.Done():
return
}
-2
View File
@@ -125,7 +125,6 @@ func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.R
}
if err = validateConfig(ctx, cfg, subSys); err != nil {
var validationErr ldap.Validation
if errors.As(err, &validationErr) {
// If we got an LDAP validation error, we need to send appropriate
@@ -416,7 +415,6 @@ func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *ht
return
}
if err = validateConfig(ctx, cfg, subSys); err != nil {
var validationErr ldap.Validation
if errors.As(err, &validationErr) {
// If we got an LDAP validation error, we need to send appropriate
+1 -1
View File
@@ -345,7 +345,7 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
Name: newCred.Name,
Description: newCred.Description,
Claims: opts.claims,
SessionPolicy: createReq.Policy,
SessionPolicy: madmin.SRSessionPolicy(createReq.Policy),
Status: auth.AccountOn,
Expiration: createReq.Expiration,
},
+3 -3
View File
@@ -259,7 +259,7 @@ func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request)
if ep := globalEndpoints[0].Endpoints[0]; !ep.IsLocal {
for nodeIdx, proxyEp := range globalProxyEndpoints {
if proxyEp.Endpoint.Host == ep.Host {
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
if proxied, success := proxyRequestByNodeIndex(ctx, w, r, nodeIdx, false); proxied && success {
return
}
}
@@ -330,7 +330,7 @@ func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request
if ep := globalEndpoints[0].Endpoints[0]; !ep.IsLocal {
for nodeIdx, proxyEp := range globalProxyEndpoints {
if proxyEp.Endpoint.Host == ep.Host {
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
if proxied, success := proxyRequestByNodeIndex(ctx, w, r, nodeIdx, false); proxied && success {
return
}
}
@@ -384,7 +384,7 @@ func proxyDecommissionRequest(ctx context.Context, defaultEndPoint Endpoint, w h
}
for nodeIdx, proxyEp := range globalProxyEndpoints {
if proxyEp.Endpoint.Host == host && !proxyEp.IsLocal {
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
if proxied, success := proxyRequestByNodeIndex(ctx, w, r, nodeIdx, false); proxied && success {
return true
}
}
+4 -6
View File
@@ -783,7 +783,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
Name: newCred.Name,
Description: newCred.Description,
Claims: opts.claims,
SessionPolicy: createReq.Policy,
SessionPolicy: madmin.SRSessionPolicy(createReq.Policy),
Status: auth.AccountOn,
Expiration: createReq.Expiration,
},
@@ -907,7 +907,7 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
Status: opts.status,
Name: opts.name,
Description: opts.description,
SessionPolicy: updateReq.NewPolicy,
SessionPolicy: madmin.SRSessionPolicy(updateReq.NewPolicy),
Expiration: updateReq.NewExpiration,
},
},
@@ -1487,8 +1487,8 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
return
}
effectivePolicy = globalIAMSys.GetCombinedPolicy(policies...)
}
buf, err = json.MarshalIndent(effectivePolicy, "", " ")
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
@@ -2169,7 +2169,7 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
SecretKey: acc.Credentials.SecretKey,
Groups: acc.Credentials.Groups,
Claims: claims,
SessionPolicy: json.RawMessage(policyJSON),
SessionPolicy: policyJSON,
Status: acc.Credentials.Status,
Name: sa.Name,
Description: sa.Description,
@@ -2279,7 +2279,6 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
// import policies first
{
f, err := zr.Open(pathJoin(iamAssetsDir, allPoliciesFile))
switch {
case errors.Is(err, os.ErrNotExist):
@@ -2362,7 +2361,6 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
} else {
added.Users = append(added.Users, accessKey)
}
}
}
}
+15 -16
View File
@@ -829,7 +829,7 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
}
// Flush before waiting for next...
w.(http.Flusher).Flush()
xhttp.Flush(w)
select {
case <-ticker.C:
@@ -1320,7 +1320,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
}
// Analyze the heal token and route the request accordingly
token, success := proxyRequestByToken(ctx, w, r, hip.clientToken)
token, _, success := proxyRequestByToken(ctx, w, r, hip.clientToken, false)
if success {
return
}
@@ -1359,7 +1359,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
if _, err := w.Write([]byte(" ")); err != nil {
return
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
case hr := <-respCh:
switch hr.apiErr {
case noError:
@@ -1367,7 +1367,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
if _, err := w.Write(hr.respBytes); err != nil {
return
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
} else {
writeSuccessResponseJSON(w, hr.respBytes)
}
@@ -1394,7 +1394,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
if _, err := w.Write(errorRespJSON); err != nil {
return
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
break forLoop
}
@@ -1611,7 +1611,6 @@ func (a adminAPIHandlers) ClientDevNull(w http.ResponseWriter, r *http.Request)
if err != nil || ctx.Err() != nil || totalRx > 100*humanize.GiByte {
break
}
}
w.WriteHeader(http.StatusOK)
}
@@ -1840,7 +1839,7 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
return
}
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
case result, ok := <-ch:
if !ok {
return
@@ -1849,7 +1848,7 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
return
}
prevResult = result
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
}
}
@@ -1958,7 +1957,7 @@ func (a adminAPIHandlers) DriveSpeedtestHandler(w http.ResponseWriter, r *http.R
if err := enc.Encode(madmin.DriveSpeedTestResult{}); err != nil {
return
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
case result, ok := <-ch:
if !ok {
return
@@ -1966,7 +1965,7 @@ func (a adminAPIHandlers) DriveSpeedtestHandler(w http.ResponseWriter, r *http.R
if err := enc.Encode(result); err != nil {
return
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
}
}
@@ -2083,7 +2082,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
grid.PutByteBuffer(entry)
if len(traceCh) == 0 {
// Flush if nothing is queued
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
case <-keepAliveTicker.C:
if len(traceCh) > 0 {
@@ -2092,7 +2091,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
if _, err := w.Write([]byte(" ")); err != nil {
return
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
case <-ctx.Done():
return
}
@@ -2184,7 +2183,7 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
grid.PutByteBuffer(log)
if len(logCh) == 0 {
// Flush if nothing is queued
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
case <-keepAliveTicker.C:
if len(logCh) > 0 {
@@ -2193,7 +2192,7 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
if _, err := w.Write([]byte(" ")); err != nil {
return
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
case <-ctx.Done():
return
}
@@ -2963,13 +2962,13 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
}
if len(healthInfoCh) == 0 {
// Flush if nothing is queued
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
case <-ticker.C:
if _, err := w.Write([]byte(" ")); err != nil {
return
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
case <-healthCtx.Done():
return
}
+2
View File
@@ -32,6 +32,8 @@ type DeletedObject struct {
DeleteMarkerMTime DeleteMarkerMTime `xml:"-"`
// MinIO extensions to support delete marker replication
ReplicationState ReplicationState `xml:"-"`
found bool // the object was found during deletion
}
// DeleteMarkerMTime is an embedded type containing time.Time for XML marshal
+2
View File
@@ -2254,6 +2254,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrServerNotInitialized
case errBucketMetadataNotInitialized:
apiErr = ErrBucketMetadataNotInitialized
case hash.ErrInvalidChecksum:
apiErr = ErrInvalidChecksum
}
// Compression errors
+1 -2
View File
@@ -520,7 +520,6 @@ func cleanReservedKeys(metadata map[string]string) map[string]string {
}
case crypto.SSEC:
m[xhttp.AmzServerSideEncryptionCustomerAlgorithm] = xhttp.AmzEncryptionAES
}
var toRemove []string
@@ -791,7 +790,7 @@ func generateInitiateMultipartUploadResponse(bucket, key, uploadID string) Initi
// generates CompleteMultipartUploadResponse for given bucket, key, location and ETag.
func generateCompleteMultipartUploadResponse(bucket, key, location string, oi ObjectInfo, h http.Header) CompleteMultipartUploadResponse {
cs := oi.decryptChecksums(0, h)
cs, _ := oi.decryptChecksums(0, h)
c := CompleteMultipartUploadResponse{
Location: location,
Bucket: bucket,
-1
View File
@@ -162,7 +162,6 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
s3Err := ErrAccessDenied
if _, ok := r.Header[xhttp.AmzContentSha256]; ok &&
getRequestAuthType(r) == authTypeSigned {
// Get credential information from the request.
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
if s3Err != ErrNone {
+14 -11
View File
@@ -195,8 +195,8 @@ func (ef BatchJobExpireFilter) Matches(obj ObjectInfo, now time.Time) bool {
return false
}
}
}
if len(ef.Metadata) > 0 && !obj.DeleteMarker {
for _, kv := range ef.Metadata {
// Object (version) must match all x-amz-meta and
@@ -289,6 +289,16 @@ type BatchJobExpire struct {
var _ yaml.Unmarshaler = &BatchJobExpire{}
// RedactSensitive will redact any sensitive information in b.
func (r *BatchJobExpire) RedactSensitive() {
if r == nil {
return
}
if r.NotificationCfg.Token != "" {
r.NotificationCfg.Token = redactedText
}
}
// UnmarshalYAML - BatchJobExpire extends default unmarshal to extract line, col information.
func (r *BatchJobExpire) UnmarshalYAML(val *yaml.Node) error {
type expireJob BatchJobExpire
@@ -435,14 +445,14 @@ func batchObjsForDelete(ctx context.Context, r *BatchJobExpire, ri *batchJobInfo
oiCache.Add(od, &exp.ObjectInfo)
}
var done bool
// DeleteObject(deletePrefix: true) to expire all versions of an object
for _, exp := range toExpireAll {
var success bool
for attempts := 1; attempts <= retryAttempts; attempts++ {
select {
case <-ctx.Done():
done = true
ri.trackMultipleObjectVersions(exp, success)
return
default:
}
stopFn := globalBatchJobsMetrics.trace(batchJobMetricExpire, ri.JobID, attempts)
@@ -459,14 +469,7 @@ func batchObjsForDelete(ctx context.Context, r *BatchJobExpire, ri *batchJobInfo
break
}
}
ri.trackMultipleObjectVersions(r.Bucket, exp, success)
if done {
break
}
}
if done {
return
ri.trackMultipleObjectVersions(exp, success)
}
// DeleteMultiple objects
+37 -12
View File
@@ -61,6 +61,8 @@ var globalBatchConfig batch.Config
const (
// Keep the completed/failed job stats 3 days before removing it
oldJobsExpiration = 3 * 24 * time.Hour
redactedText = "**REDACTED**"
)
// BatchJobRequest this is an internal data structure not for external consumption.
@@ -74,6 +76,29 @@ type BatchJobRequest struct {
ctx context.Context `msg:"-"`
}
// RedactSensitive will redact any sensitive information in b.
func (j *BatchJobRequest) RedactSensitive() {
j.Replicate.RedactSensitive()
j.Expire.RedactSensitive()
j.KeyRotate.RedactSensitive()
}
// RedactSensitive will redact any sensitive information in b.
func (r *BatchJobReplicateV1) RedactSensitive() {
if r == nil {
return
}
if r.Target.Creds.SecretKey != "" {
r.Target.Creds.SecretKey = redactedText
}
if r.Target.Creds.SessionToken != "" {
r.Target.Creds.SessionToken = redactedText
}
}
// RedactSensitive will redact any sensitive information in b.
func (r *BatchJobKeyRotateV1) RedactSensitive() {}
func notifyEndpoint(ctx context.Context, ri *batchJobInfo, endpoint, token string) error {
if endpoint == "" {
return nil
@@ -597,12 +622,12 @@ func (r BatchJobReplicateV1) writeAsArchive(ctx context.Context, objAPI ObjectLa
},
}
opts, err := batchReplicationOpts(ctx, "", gr.ObjInfo)
opts, _, err := batchReplicationOpts(ctx, "", gr.ObjInfo)
if err != nil {
batchLogIf(ctx, err)
continue
}
// TODO: I am not sure we read it back, but we aren't sending whether checksums are single/multipart.
for k, vals := range opts.Header() {
for _, v := range vals {
snowballObj.Headers.Add(k, v)
@@ -687,14 +712,14 @@ func (r *BatchJobReplicateV1) ReplicateToTarget(ctx context.Context, api ObjectL
return err
}
putOpts, err := batchReplicationOpts(ctx, "", objInfo)
putOpts, isMP, err := batchReplicationOpts(ctx, "", objInfo)
if err != nil {
return err
}
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
putOpts.Internal = miniogo.AdvancedPutOptions{}
}
if objInfo.isMultipart() {
if isMP {
if err := replicateObjectWithMultipart(ctx, c, tgtBucket, pathJoin(tgtPrefix, objInfo.Name), rd, objInfo, putOpts); err != nil {
return err
}
@@ -971,7 +996,7 @@ func (ri *batchJobInfo) updateAfter(ctx context.Context, api ObjectLayer, durati
// Note: to be used only with batch jobs that affect multiple versions through
// a single action. e.g batch-expire has an option to expire all versions of an
// object which matches the given filters.
func (ri *batchJobInfo) trackMultipleObjectVersions(bucket string, info ObjectInfo, success bool) {
func (ri *batchJobInfo) trackMultipleObjectVersions(info ObjectInfo, success bool) {
if success {
ri.Objects += int64(info.NumVersions)
} else {
@@ -1425,7 +1450,6 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
cred = r.Source.Creds
remoteBkt = r.Source.Bucket
pathStyle = r.Source.Path
}
u, err := url.Parse(remoteEp)
@@ -1551,11 +1575,11 @@ func (j *BatchJobRequest) load(ctx context.Context, api ObjectLayer, name string
return err
}
func batchReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions, err error) {
func batchReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions, isMP bool, err error) {
// TODO: support custom storage class for remote replication
putOpts, err = putReplicationOpts(ctx, "", objInfo, 0)
putOpts, isMP, err = putReplicationOpts(ctx, "", objInfo)
if err != nil {
return putOpts, err
return putOpts, isMP, err
}
putOpts.Internal = miniogo.AdvancedPutOptions{
SourceVersionID: objInfo.VersionID,
@@ -1563,7 +1587,7 @@ func batchReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (p
SourceETag: objInfo.ETag,
ReplicationRequest: true,
}
return putOpts, nil
return putOpts, isMP, nil
}
// ListBatchJobs - lists all currently active batch jobs, optionally takes {jobType}
@@ -1695,6 +1719,8 @@ func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Reques
return
}
// Remove sensitive fields.
req.RedactSensitive()
buf, err := yaml.Marshal(req)
if err != nil {
batchLogIf(ctx, err)
@@ -1802,7 +1828,7 @@ func (a adminAPIHandlers) CancelBatchJob(w http.ResponseWriter, r *http.Request)
return
}
if _, success := proxyRequestByToken(ctx, w, r, jobID); success {
if _, proxied, _ := proxyRequestByToken(ctx, w, r, jobID, true); proxied {
return
}
@@ -2283,7 +2309,6 @@ func lookupStyle(s string) miniogo.BucketLookupType {
lookup = miniogo.BucketLookupDNS
default:
lookup = miniogo.BucketLookupAuto
}
return lookup
}
+13 -7
View File
@@ -128,14 +128,20 @@ func closeBitrotReaders(rs []io.ReaderAt) {
}
// Close all the writers.
func closeBitrotWriters(ws []io.Writer) {
for _, w := range ws {
if w != nil {
if bw, ok := w.(io.Closer); ok {
bw.Close()
}
func closeBitrotWriters(ws []io.Writer) []error {
errs := make([]error, len(ws))
for i, w := range ws {
if w == nil {
errs[i] = errDiskNotFound
continue
}
if bw, ok := w.(io.Closer); ok {
errs[i] = bw.Close()
} else {
errs[i] = nil
}
}
return errs
}
// Returns hash sum for whole-bitrot, nil for streaming-bitrot.
@@ -178,7 +184,7 @@ func bitrotVerify(r io.Reader, wantSize, partSize int64, algo BitrotAlgorithm, w
return errFileCorrupt
}
bufp := xioutil.ODirectPoolSmall.Get().(*[]byte)
bufp := xioutil.ODirectPoolSmall.Get()
defer xioutil.ODirectPoolSmall.Put(bufp)
for left > 0 {
-3
View File
@@ -344,11 +344,9 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
Created: dnsRecords[0].CreationDate,
})
}
sort.Slice(bucketsInfo, func(i, j int) bool {
return bucketsInfo[i].Name < bucketsInfo[j].Name
})
} else {
// Invoke the list buckets.
var err error
@@ -841,7 +839,6 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
}
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
apiErr := ErrBucketAlreadyExists
if !globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
-2
View File
@@ -188,7 +188,6 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName stri
if errorResponse.Code != testCase.errorResponse.Code {
t.Errorf("Test %d: %s: Expected the error code to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Code, errorResponse.Code)
}
}
// Test for Anonymous/unsigned http request.
@@ -290,7 +289,6 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
if recV2.Code != testCase.expectedRespStatus {
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code)
}
}
// Test for Anonymous/unsigned http request.
+10 -10
View File
@@ -243,26 +243,26 @@ func parseRequestToken(token string) (subToken string, nodeIndex int) {
return subToken, nodeIndex
}
func proxyRequestByToken(ctx context.Context, w http.ResponseWriter, r *http.Request, token string) (string, bool) {
subToken, nodeIndex := parseRequestToken(token)
if nodeIndex >= 0 {
return subToken, proxyRequestByNodeIndex(ctx, w, r, nodeIndex)
func proxyRequestByToken(ctx context.Context, w http.ResponseWriter, r *http.Request, token string, returnErr bool) (subToken string, proxied bool, success bool) {
var nodeIndex int
if subToken, nodeIndex = parseRequestToken(token); nodeIndex >= 0 {
proxied, success = proxyRequestByNodeIndex(ctx, w, r, nodeIndex, returnErr)
}
return subToken, false
return
}
func proxyRequestByNodeIndex(ctx context.Context, w http.ResponseWriter, r *http.Request, index int) (success bool) {
func proxyRequestByNodeIndex(ctx context.Context, w http.ResponseWriter, r *http.Request, index int, returnErr bool) (proxied, success bool) {
if len(globalProxyEndpoints) == 0 {
return false
return
}
if index < 0 || index >= len(globalProxyEndpoints) {
return false
return
}
ep := globalProxyEndpoints[index]
if ep.IsLocal {
return false
return
}
return proxyRequest(ctx, w, r, ep)
return true, proxyRequest(ctx, w, r, ep, returnErr)
}
// ListObjectsV1Handler - GET Bucket (List Objects) Version 1.
-1
View File
@@ -112,7 +112,6 @@ func (r *ReplicationStats) collectWorkerMetrics(ctx context.Context) {
r.wlock.Lock()
r.workers.update()
r.wlock.Unlock()
}
}
}
+67 -39
View File
@@ -49,6 +49,7 @@ import (
"github.com/minio/minio/internal/hash"
xhttp "github.com/minio/minio/internal/http"
xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/once"
"github.com/tinylib/msgp/msgp"
@@ -145,7 +146,7 @@ func validateReplicationDestination(ctx context.Context, bucket string, rCfg *re
if errInt == nil {
err = nil
} else {
err = errInt.(error)
err, _ = errInt.(error)
}
}
switch err.(type) {
@@ -773,7 +774,7 @@ func (m caseInsensitiveMap) Lookup(key string) (string, bool) {
return "", false
}
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, partNum int) (putOpts minio.PutObjectOptions, err error) {
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts minio.PutObjectOptions, isMP bool, err error) {
meta := make(map[string]string)
isSSEC := crypto.SSEC.IsEncrypted(objInfo.UserDefined)
@@ -794,23 +795,22 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, part
meta[k] = v
}
}
isMP = objInfo.isMultipart()
if len(objInfo.Checksum) > 0 {
// Add encrypted CRC to metadata for SSE-C objects.
if isSSEC {
meta[ReplicationSsecChecksumHeader] = base64.StdEncoding.EncodeToString(objInfo.Checksum)
} else {
if objInfo.isMultipart() && partNum > 0 {
for _, pi := range objInfo.Parts {
if pi.Number == partNum {
for k, v := range pi.Checksums { // for PutObjectPart
meta[k] = v
}
}
}
} else {
for k, v := range getCRCMeta(objInfo, 0, nil) { // for PutObject/NewMultipartUpload
meta[k] = v
}
cs, mp := getCRCMeta(objInfo, 0, nil)
// Set object checksum.
for k, v := range cs {
meta[k] = v
}
isMP = mp
if !objInfo.isMultipart() && cs[xhttp.AmzChecksumType] == xhttp.AmzChecksumTypeFullObject {
// For objects where checksum is full object, it will be the same.
// Therefore, we use the cheaper PutObject replication.
isMP = false
}
}
}
@@ -841,7 +841,7 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, part
if tagTmstampStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+TaggingTimestamp]; ok {
tagTimestamp, err = time.Parse(time.RFC3339Nano, tagTmstampStr)
if err != nil {
return putOpts, err
return putOpts, false, err
}
}
putOpts.Internal.TaggingTimestamp = tagTimestamp
@@ -865,7 +865,7 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, part
if retainDateStr, ok := lkMap.Lookup(xhttp.AmzObjectLockRetainUntilDate); ok {
rdate, err := amztime.ISO8601Parse(retainDateStr)
if err != nil {
return putOpts, err
return putOpts, false, err
}
putOpts.RetainUntilDate = rdate
// set retention timestamp in opts
@@ -873,7 +873,7 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, part
if retainTmstampStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockRetentionTimestamp]; ok {
retTimestamp, err = time.Parse(time.RFC3339Nano, retainTmstampStr)
if err != nil {
return putOpts, err
return putOpts, false, err
}
}
putOpts.Internal.RetentionTimestamp = retTimestamp
@@ -885,7 +885,7 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, part
if lholdTmstampStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockLegalHoldTimestamp]; ok {
lholdTimestamp, err = time.Parse(time.RFC3339Nano, lholdTmstampStr)
if err != nil {
return putOpts, err
return putOpts, false, err
}
}
putOpts.Internal.LegalholdTimestamp = lholdTimestamp
@@ -895,9 +895,19 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, part
}
if crypto.S3KMS.IsEncrypted(objInfo.UserDefined) {
sseEnc, err := encrypt.NewSSEKMS(objInfo.KMSKeyID(), nil)
// If KMS key ID replication is enabled (as by default)
// we include the object's KMS key ID. In any case, we
// always set the SSE-KMS header. If no KMS key ID is
// specified, MinIO is supposed to use whatever default
// config applies on the site or bucket.
var keyID string
if kms.ReplicateKeyID() {
keyID = objInfo.KMSKeyID()
}
sseEnc, err := encrypt.NewSSEKMS(keyID, nil)
if err != nil {
return putOpts, err
return putOpts, false, err
}
putOpts.ServerSideEncryption = sseEnc
}
@@ -958,7 +968,11 @@ func getReplicationAction(oi1 ObjectInfo, oi2 minio.ObjectInfo, opType replicati
}
t, _ := tags.ParseObjectTags(oi1.UserTags)
if (oi2.UserTagCount > 0 && !reflect.DeepEqual(oi2.UserTags, t.ToMap())) || (oi2.UserTagCount != len(t.ToMap())) {
oi2Map := make(map[string]string)
for k, v := range oi2.UserTags {
oi2Map[k] = v
}
if (oi2.UserTagCount > 0 && !reflect.DeepEqual(oi2Map, t.ToMap())) || (oi2.UserTagCount != len(t.ToMap())) {
return replicateMetadata
}
@@ -1286,7 +1300,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
// use core client to avoid doing multipart on PUT
c := &minio.Core{Client: tgt.Client}
putOpts, err := putReplicationOpts(ctx, tgt.StorageClass, objInfo, 0)
putOpts, isMP, err := putReplicationOpts(ctx, tgt.StorageClass, objInfo)
if err != nil {
replLogIf(ctx, fmt.Errorf("failure setting options for replication bucket:%s err:%w", bucket, err))
sendEvent(eventArgs{
@@ -1318,7 +1332,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
defer cancel()
}
r := bandwidth.NewMonitoredReader(newCtx, globalBucketMonitor, gr, opts)
if objInfo.isMultipart() {
if isMP {
rinfo.Err = replicateObjectWithMultipart(ctx, c, tgt.Bucket, object, r, objInfo, putOpts)
} else {
_, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts)
@@ -1447,13 +1461,14 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
}
rinfo.Duration = time.Since(startTime)
}()
oi, cerr := tgt.StatObject(ctx, tgt.Bucket, object, minio.StatObjectOptions{
sOpts := minio.StatObjectOptions{
VersionID: objInfo.VersionID,
Internal: minio.AdvancedGetOptions{
ReplicationProxyRequest: "false",
},
})
}
sOpts.Set(xhttp.AmzTagDirective, "ACCESS")
oi, cerr := tgt.StatObject(ctx, tgt.Bucket, object, sOpts)
if cerr == nil {
rAction = getReplicationAction(objInfo, oi, ri.OpType)
rinfo.ReplicationStatus = replication.Completed
@@ -1538,19 +1553,30 @@ applyAction:
ReplicationRequest: true, // always set this to distinguish between `mc mirror` replication and serverside
},
}
if tagTmStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+TaggingTimestamp]; ok {
// default timestamps to ModTime unless present in metadata
lkMap := caseInsensitiveMap(objInfo.UserDefined)
if _, ok := lkMap.Lookup(xhttp.AmzObjectLockLegalHold); ok {
dstOpts.Internal.LegalholdTimestamp = objInfo.ModTime
}
if _, ok := lkMap.Lookup(xhttp.AmzObjectLockRetainUntilDate); ok {
dstOpts.Internal.RetentionTimestamp = objInfo.ModTime
}
if objInfo.UserTags != "" {
dstOpts.Internal.TaggingTimestamp = objInfo.ModTime
}
if tagTmStr, ok := lkMap.Lookup(ReservedMetadataPrefixLower + TaggingTimestamp); ok {
ondiskTimestamp, err := time.Parse(time.RFC3339, tagTmStr)
if err == nil {
dstOpts.Internal.TaggingTimestamp = ondiskTimestamp
}
}
if retTmStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockRetentionTimestamp]; ok {
if retTmStr, ok := lkMap.Lookup(ReservedMetadataPrefixLower + ObjectLockRetentionTimestamp); ok {
ondiskTimestamp, err := time.Parse(time.RFC3339, retTmStr)
if err == nil {
dstOpts.Internal.RetentionTimestamp = ondiskTimestamp
}
}
if lholdTmStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockLegalHoldTimestamp]; ok {
if lholdTmStr, ok := lkMap.Lookup(ReservedMetadataPrefixLower + ObjectLockLegalHoldTimestamp); ok {
ondiskTimestamp, err := time.Parse(time.RFC3339, lholdTmStr)
if err == nil {
dstOpts.Internal.LegalholdTimestamp = ondiskTimestamp
@@ -1561,8 +1587,7 @@ applyAction:
replLogIf(ctx, fmt.Errorf("unable to replicate metadata for object %s/%s(%s) to target %s: %w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
}
} else {
var putOpts minio.PutObjectOptions
putOpts, err = putReplicationOpts(ctx, tgt.StorageClass, objInfo, 0)
putOpts, isMP, err := putReplicationOpts(ctx, tgt.StorageClass, objInfo)
if err != nil {
replLogIf(ctx, fmt.Errorf("failed to set replicate options for object %s/%s(%s) (target %s) err:%w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), err))
sendEvent(eventArgs{
@@ -1593,7 +1618,7 @@ applyAction:
defer cancel()
}
r := bandwidth.NewMonitoredReader(newCtx, globalBucketMonitor, gr, opts)
if objInfo.isMultipart() {
if isMP {
rinfo.Err = replicateObjectWithMultipart(ctx, c, tgt.Bucket, object, r, objInfo, putOpts)
} else {
_, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts)
@@ -1671,7 +1696,8 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
cHeader := http.Header{}
cHeader.Add(xhttp.MinIOSourceReplicationRequest, "true")
if !isSSEC {
for k, v := range getCRCMeta(objInfo, partInfo.Number, nil) {
cs, _ := getCRCMeta(objInfo, partInfo.Number, nil)
for k, v := range cs {
cHeader.Add(k, v)
}
}
@@ -1716,8 +1742,9 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
defer ccancel()
if len(objInfo.Checksum) > 0 {
for k, v := range getCRCMeta(objInfo, 0, nil) {
userMeta[k] = v
cs, _ := getCRCMeta(objInfo, 0, nil)
for k, v := range cs {
userMeta[k] = strings.Split(v, "-")[0]
}
}
_, err = c.CompleteMultipartUpload(cctx, bucket, object, uploadID, uploadedParts, minio.PutObjectOptions{
@@ -3766,9 +3793,9 @@ type validateReplicationDestinationOptions struct {
checkReadyErr sync.Map
}
func getCRCMeta(oi ObjectInfo, partNum int, h http.Header) map[string]string {
func getCRCMeta(oi ObjectInfo, partNum int, h http.Header) (cs map[string]string, isMP bool) {
meta := make(map[string]string)
cs := oi.decryptChecksums(partNum, h)
cs, isMP = oi.decryptChecksums(partNum, h)
for k, v := range cs {
cksum := hash.NewChecksumString(k, v)
if cksum == nil {
@@ -3776,8 +3803,9 @@ func getCRCMeta(oi ObjectInfo, partNum int, h http.Header) map[string]string {
}
if cksum.Valid() {
meta[cksum.Type.Key()] = v
meta[xhttp.AmzChecksumType] = cs[xhttp.AmzChecksumType]
meta[xhttp.AmzChecksumAlgo] = cksum.Type.String()
}
meta[xhttp.AmzChecksumType] = cksum.Type.ObjType()
}
return meta
return meta, isMP
}
+2 -2
View File
@@ -45,7 +45,7 @@ func Test_readFromSecret(t *testing.T) {
for _, testCase := range testCases {
testCase := testCase
t.Run("", func(t *testing.T) {
tmpfile, err := os.CreateTemp("", "testfile")
tmpfile, err := os.CreateTemp(t.TempDir(), "testfile")
if err != nil {
t.Error(err)
}
@@ -157,7 +157,7 @@ MINIO_ROOT_PASSWORD=minio123`,
for _, testCase := range testCases {
testCase := testCase
t.Run("", func(t *testing.T) {
tmpfile, err := os.CreateTemp("", "testfile")
tmpfile, err := os.CreateTemp(t.TempDir(), "testfile")
if err != nil {
t.Error(err)
}
+7 -2
View File
@@ -664,6 +664,12 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
into.addChild(h)
continue
}
// Adjust the probability of healing.
// This first removes lowest x from the mod check and makes it x times more likely.
// So if duudc = 10 and we want heal check every 50 cycles, we check
// if (cycle/10) % (50/10) == 0, which would make heal checks run once every 50 cycles,
// if the objects are pre-selected as 1:10.
folder.objectHealProbDiv = dataUsageUpdateDirCycles
}
f.updateCurrentPath(folder.name)
stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, folder.name)
@@ -858,8 +864,8 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
}
}
}
}
if compact {
stop := globalScannerMetrics.log(scannerMetricCompactFolder, folder.name)
f.newCache.deleteRecursive(thisHash)
@@ -873,7 +879,6 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
}
stop(total)
}
}
// Compact if too many children...
if !into.Compacted {
-1
View File
@@ -98,7 +98,6 @@ func (dt *dynamicTimeout) logEntry(duration time.Duration) {
// We leak entries while we copy
if entries == dynamicTimeoutLogSize {
// Make copy on stack in order to call adjust()
logCopy := [dynamicTimeoutLogSize]time.Duration{}
copy(logCopy[:], dt.log[:])
-1
View File
@@ -167,7 +167,6 @@ func testDynamicTimeoutAdjust(t *testing.T, timeout *dynamicTimeout, f func() fl
const successTimeout = 20 * time.Second
for i := 0; i < dynamicTimeoutLogSize; i++ {
rnd := f()
duration := time.Duration(float64(successTimeout) * rnd)
+6 -5
View File
@@ -347,8 +347,8 @@ func rotateKey(ctx context.Context, oldKey []byte, newKeyID string, newKey []byt
return errInvalidSSEParameters // AWS returns special error for equal but invalid keys.
}
return crypto.ErrInvalidCustomerKey // To provide strict AWS S3 compatibility we return: access denied.
}
if subtle.ConstantTimeCompare(oldKey, newKey) == 1 && sealedKey.Algorithm == crypto.SealAlgorithm {
return nil // don't rotate on equal keys if seal algorithm is latest
}
@@ -1155,16 +1155,17 @@ func (o *ObjectInfo) metadataEncryptFn(headers http.Header) (objectMetaEncryptFn
// decryptChecksums will attempt to decode checksums and return it/them if set.
// if part > 0, and we have the checksum for the part that will be returned.
func (o *ObjectInfo) decryptChecksums(part int, h http.Header) map[string]string {
// Returns whether the checksum (main part 0) is a multipart checksum.
func (o *ObjectInfo) decryptChecksums(part int, h http.Header) (cs map[string]string, isMP bool) {
data := o.Checksum
if len(data) == 0 {
return nil
return nil, false
}
if part > 0 && !crypto.SSEC.IsEncrypted(o.UserDefined) {
// already decrypted in ToObjectInfo for multipart objects
for _, pi := range o.Parts {
if pi.Number == part {
return pi.Checksums
return pi.Checksums, true
}
}
}
@@ -1174,7 +1175,7 @@ func (o *ObjectInfo) decryptChecksums(part int, h http.Header) map[string]string
if err != crypto.ErrSecretKeyMismatch {
encLogIf(GlobalContext, err)
}
return nil
return nil, part > 0
}
data = decrypted
}
-2
View File
@@ -362,7 +362,6 @@ func TestGetDecryptedRange(t *testing.T) {
t.Errorf("Case %d: test failed: %d %d %d %d %d", i, o, l, skip, sn, ps)
}
}
}
// Multipart object tests
@@ -538,7 +537,6 @@ func TestGetDecryptedRange(t *testing.T) {
i, o, l, skip, sn, ps, oRef, lRef, skipRef, snRef, psRef)
}
}
}
}
+2 -3
View File
@@ -213,7 +213,6 @@ func NewEndpoint(arg string) (ep Endpoint, e error) {
u.Path = u.Path[1:]
}
}
} else {
// Only check if the arg is an ip address and ask for scheme since its absent.
// localhost, example.com, any FQDN cannot be disambiguated from a regular file path such as
@@ -1194,7 +1193,7 @@ func GetProxyEndpointLocalIndex(proxyEps []ProxyEndpoint) int {
}
// GetProxyEndpoints - get all endpoints that can be used to proxy list request.
func GetProxyEndpoints(endpointServerPools EndpointServerPools) []ProxyEndpoint {
func GetProxyEndpoints(endpointServerPools EndpointServerPools, transport http.RoundTripper) []ProxyEndpoint {
var proxyEps []ProxyEndpoint
proxyEpSet := set.NewStringSet()
@@ -1213,7 +1212,7 @@ func GetProxyEndpoints(endpointServerPools EndpointServerPools) []ProxyEndpoint
proxyEps = append(proxyEps, ProxyEndpoint{
Endpoint: endpoint,
Transport: globalRemoteTargetTransport,
Transport: transport,
})
}
}
-1
View File
@@ -201,7 +201,6 @@ func erasureSelfTest() {
ok = false
continue
}
}
}
if !ok {
-2
View File
@@ -304,7 +304,6 @@ func TestListOnlineDisks(t *testing.T) {
f.Close()
break
}
}
rQuorum := len(errs) - z.serverPools[0].sets[0].defaultParityCount
@@ -485,7 +484,6 @@ func TestListOnlineDisksSmallObjects(t *testing.T) {
f.Close()
break
}
}
rQuorum := len(errs) - z.serverPools[0].sets[0].defaultParityCount
+8 -4
View File
@@ -584,7 +584,6 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
readers[i] = newBitrotReader(disk, copyPartsMetadata[i].Data, bucket, partPath, tillOffset, checksumAlgo,
checksumInfo.Hash, erasure.ShardSize())
prefer[i] = disk.Hostname() == ""
}
writers := make([]io.Writer, len(outDatedDisks))
for i, disk := range outDatedDisks {
@@ -609,7 +608,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
// later to the final location.
err = erasure.Heal(ctx, writers, readers, partSize, prefer)
closeBitrotReaders(readers)
closeBitrotWriters(writers)
closeErrs := closeBitrotWriters(writers)
if err != nil {
return result, err
}
@@ -629,6 +628,13 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
continue
}
// A non-nil stale disk which got error on Close()
if closeErrs[i] != nil {
outDatedDisks[i] = nil
disksToHealCount--
continue
}
partsMetadata[i].DataDir = dstDataDir
partsMetadata[i].AddObjectPart(partNumber, "", partSize, partActualSize, partModTime, partIdx, partChecksums)
if len(inlineBuffers) > 0 && inlineBuffers[i] != nil {
@@ -643,9 +649,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
if disksToHealCount == 0 {
return result, fmt.Errorf("all drives had write errors, unable to heal %s/%s", bucket, object)
}
}
}
defer er.deleteAll(context.Background(), minioMetaTmpBucket, tmpID)
+4 -2
View File
@@ -668,10 +668,13 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
}
n, err := erasure.Encode(ctx, toEncode, writers, buffer, writeQuorum)
closeBitrotWriters(writers)
closeErrs := closeBitrotWriters(writers)
if err != nil {
return pi, toObjectErr(err, bucket, object)
}
if closeErr := reduceWriteQuorumErrs(ctx, closeErrs, objectOpIgnoredErrs, writeQuorum); closeErr != nil {
return pi, toObjectErr(closeErr, bucket, object)
}
// Should return IncompleteBody{} error when reader has fewer bytes
// than specified in request header.
@@ -1049,7 +1052,6 @@ func readParts(ctx context.Context, disks []StorageAPI, bucket string, partMetaP
PartNumber: partNumbers[pidx],
}.Error(),
}
}
return partInfosInQuorum, nil
}
+34 -10
View File
@@ -27,6 +27,8 @@ import (
"net/http"
"path"
"runtime"
"slices"
"sort"
"strconv"
"strings"
"sync"
@@ -1176,11 +1178,15 @@ func (er erasureObjects) putMetacacheObject(ctx context.Context, key string, r *
}
n, erasureErr := erasure.Encode(ctx, data, writers, buffer, writeQuorum)
closeBitrotWriters(writers)
closeErrs := closeBitrotWriters(writers)
if erasureErr != nil {
return ObjectInfo{}, toObjectErr(erasureErr, minioMetaBucket, key)
}
if closeErr := reduceWriteQuorumErrs(ctx, closeErrs, objectOpIgnoredErrs, writeQuorum); closeErr != nil {
return ObjectInfo{}, toObjectErr(closeErr, minioMetaBucket, key)
}
// Should return IncompleteBody{} error when reader has fewer bytes
// than specified in request header.
if n < data.Size() {
@@ -1423,11 +1429,15 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
bugLogIf(ctx, err)
}
n, erasureErr := erasure.Encode(ctx, toEncode, writers, buffer, writeQuorum)
closeBitrotWriters(writers)
closeErrs := closeBitrotWriters(writers)
if erasureErr != nil {
return ObjectInfo{}, toObjectErr(erasureErr, bucket, object)
}
if closeErr := reduceWriteQuorumErrs(ctx, closeErrs, objectOpIgnoredErrs, writeQuorum); closeErr != nil {
return ObjectInfo{}, toObjectErr(closeErr, bucket, object)
}
// Should return IncompleteBody{} error when reader has fewer bytes
// than specified in request header.
if n < data.Size() {
@@ -1706,8 +1716,21 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
}
dedupVersions := make([]FileInfoVersions, 0, len(versionsMap))
for _, version := range versionsMap {
dedupVersions = append(dedupVersions, version)
for _, fivs := range versionsMap {
// Removal of existing versions and adding a delete marker in the same
// request is supported. At the same time, we cannot allow adding
// two delete markers on top of any object. To avoid this situation,
// we will sort deletions to execute existing deletion first,
// then add only one delete marker if requested
sort.SliceStable(fivs.Versions, func(i, j int) bool {
return !fivs.Versions[i].Deleted
})
if idx := slices.IndexFunc(fivs.Versions, func(fi FileInfo) bool {
return fi.Deleted
}); idx > -1 {
fivs.Versions = fivs.Versions[:idx+1]
}
dedupVersions = append(dedupVersions, fivs)
}
// Initialize list of errors.
@@ -1732,12 +1755,6 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
continue
}
for _, v := range dedupVersions[i].Versions {
if err == errFileNotFound || err == errFileVersionNotFound {
if !dobjects[v.Idx].DeleteMarker {
// Not delete marker, if not found, ok.
continue
}
}
delObjErrs[index][v.Idx] = err
}
}
@@ -1757,6 +1774,13 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
}
}
err := reduceWriteQuorumErrs(ctx, diskErrs, objectOpIgnoredErrs, writeQuorums[objIndex])
if err == nil {
dobjects[objIndex].found = true
} else if isErrVersionNotFound(err) || isErrObjectNotFound(err) {
if !dobjects[objIndex].DeleteMarker {
err = nil
}
}
if objects[objIndex].VersionID != "" {
errs[objIndex] = toObjectErr(err, bucket, objects[objIndex].ObjectName, objects[objIndex].VersionID)
} else {
+69 -1
View File
@@ -131,6 +131,75 @@ func TestErasureDeleteObjectBasic(t *testing.T) {
removeRoots(fsDirs)
}
func TestDeleteObjectsVersionedTwoPools(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
obj, fsDirs, err := prepareErasurePools()
if err != nil {
t.Fatal("Unable to initialize 'Erasure' object layer.", err)
}
// Remove all dirs.
for _, dir := range fsDirs {
defer os.RemoveAll(dir)
}
bucketName := "bucket"
objectName := "myobject"
err = obj.MakeBucket(ctx, bucketName, MakeBucketOptions{
VersioningEnabled: true,
})
if err != nil {
t.Fatal(err)
}
z, ok := obj.(*erasureServerPools)
if !ok {
t.Fatal("unexpected object layer type")
}
versions := make([]string, 2)
for i := range z.serverPools {
objInfo, err := z.serverPools[i].PutObject(ctx, bucketName, objectName,
mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), ObjectOptions{
Versioned: true,
})
if err != nil {
t.Fatalf("Erasure Object upload failed: <ERROR> %s", err)
}
versions[i] = objInfo.VersionID
}
// Remove and check the version in the second pool, then
// remove and check the version in the first pool
for testIdx, vid := range []string{versions[1], versions[0]} {
names := []ObjectToDelete{
{
ObjectV: ObjectV{
ObjectName: objectName,
VersionID: vid,
},
},
}
_, delErrs := obj.DeleteObjects(ctx, bucketName, names, ObjectOptions{
Versioned: true,
})
for i := range delErrs {
if delErrs[i] != nil {
t.Errorf("Test %d: Failed to remove object `%v` with the error: `%v`", testIdx, names[i], delErrs[i])
}
_, statErr := obj.GetObjectInfo(ctx, bucketName, objectName, ObjectOptions{
VersionID: names[i].ObjectV.VersionID,
})
switch statErr.(type) {
case VersionNotFound:
default:
t.Errorf("Test %d: Object %s is not removed", testIdx, objectName)
}
}
}
}
func TestDeleteObjectsVersioned(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -177,7 +246,6 @@ func TestDeleteObjectsVersioned(t *testing.T) {
VersionID: objInfo.VersionID,
},
}
}
names = append(names, ObjectToDelete{
ObjectV: ObjectV{
+2 -2
View File
@@ -32,9 +32,9 @@ func prepareErasurePools() (ObjectLayer, []string, error) {
pools := mustGetPoolEndpoints(0, fsDirs[:16]...)
pools = append(pools, mustGetPoolEndpoints(1, fsDirs[16:]...)...)
// Everything is fine, should return nil
objLayer, err := newErasureServerPools(context.Background(), pools)
objLayer, _, err := initObjectLayer(context.Background(), pools)
if err != nil {
removeRoots(fsDirs)
return nil, nil, err
}
return objLayer, fsDirs, nil
+27 -74
View File
@@ -1248,89 +1248,42 @@ func (z *erasureServerPools) DeleteObjects(ctx context.Context, bucket string, o
ctx = lkctx.Context()
defer multiDeleteLock.Unlock(lkctx)
// Fetch location of up to 10 objects concurrently.
poolObjIdxMap := map[int][]ObjectToDelete{}
origIndexMap := map[int][]int{}
dObjectsByPool := make([][]DeletedObject, len(z.serverPools))
dErrsByPool := make([][]error, len(z.serverPools))
// Always perform 1/10th of the number of objects per delete
concurrent := len(objects) / 10
if concurrent <= 10 {
// if we cannot get 1/10th then choose the number of
// objects as concurrent.
concurrent = len(objects)
}
var mu sync.Mutex
eg := errgroup.WithNErrs(len(objects)).WithConcurrency(concurrent)
for j, obj := range objects {
j := j
obj := obj
eg := errgroup.WithNErrs(len(z.serverPools)).WithConcurrency(len(z.serverPools))
for i, pool := range z.serverPools {
i := i
pool := pool
eg.Go(func() error {
pinfo, _, err := z.getPoolInfoExistingWithOpts(ctx, bucket, obj.ObjectName, ObjectOptions{
NoLock: true,
})
if err != nil {
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
derrs[j] = err
}
dobjects[j] = DeletedObject{
ObjectName: decodeDirObject(obj.ObjectName),
VersionID: obj.VersionID,
}
return nil
}
// Delete marker already present we are not going to create new delete markers.
if pinfo.ObjInfo.DeleteMarker && obj.VersionID == "" {
dobjects[j] = DeletedObject{
DeleteMarker: pinfo.ObjInfo.DeleteMarker,
DeleteMarkerVersionID: pinfo.ObjInfo.VersionID,
DeleteMarkerMTime: DeleteMarkerMTime{pinfo.ObjInfo.ModTime},
ObjectName: decodeDirObject(pinfo.ObjInfo.Name),
}
return nil
}
idx := pinfo.Index
mu.Lock()
defer mu.Unlock()
poolObjIdxMap[idx] = append(poolObjIdxMap[idx], obj)
origIndexMap[idx] = append(origIndexMap[idx], j)
dObjectsByPool[i], dErrsByPool[i] = pool.DeleteObjects(ctx, bucket, objects, opts)
return nil
}, j)
}, i)
}
eg.Wait() // wait to check all the pools.
if len(poolObjIdxMap) > 0 {
// Delete concurrently in all server pools.
var wg sync.WaitGroup
wg.Add(len(z.serverPools))
for idx, pool := range z.serverPools {
go func(idx int, pool *erasureSets) {
defer wg.Done()
objs := poolObjIdxMap[idx]
if len(objs) == 0 {
return
}
orgIndexes := origIndexMap[idx]
deletedObjects, errs := pool.DeleteObjects(ctx, bucket, objs, opts)
mu.Lock()
for i, derr := range errs {
if derr != nil {
derrs[orgIndexes[i]] = derr
}
deletedObjects[i].ObjectName = decodeDirObject(deletedObjects[i].ObjectName)
dobjects[orgIndexes[i]] = deletedObjects[i]
}
mu.Unlock()
}(idx, pool)
for i := range dobjects {
// Iterate over pools
for pool := range z.serverPools {
if dErrsByPool[pool][i] == nil && dObjectsByPool[pool][i].found {
// A fast exit when the object is found and removed
dobjects[i] = dObjectsByPool[pool][i]
derrs[i] = nil
break
}
if derrs[i] == nil {
// No error related to this object is found, assign this pool result
// whether it is nil because there is no object found or because of
// some other errors such erasure quorum errors.
dobjects[i] = dObjectsByPool[pool][i]
derrs[i] = dErrsByPool[pool][i]
}
}
wg.Wait()
}
for i := range dobjects {
dobjects[i].ObjectName = decodeDirObject(dobjects[i].ObjectName)
}
return dobjects, derrs
}
+9 -6
View File
@@ -367,9 +367,10 @@ func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error)
}
return minio.New(driver.endpoint, &minio.Options{
Creds: mcreds,
Secure: globalIsTLS,
Transport: tr,
Creds: mcreds,
Secure: globalIsTLS,
Transport: tr,
TrailingHeaders: true,
})
}
@@ -381,9 +382,10 @@ func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error)
}
return minio.New(driver.endpoint, &minio.Options{
Creds: credentials.NewStaticV4(ui.Credentials.AccessKey, ui.Credentials.SecretKey, ""),
Secure: globalIsTLS,
Transport: tr,
Creds: credentials.NewStaticV4(ui.Credentials.AccessKey, ui.Credentials.SecretKey, ""),
Secure: globalIsTLS,
Transport: tr,
TrailingHeaders: true,
})
}
@@ -552,6 +554,7 @@ func (driver *ftpDriver) PutFile(ctx *ftp.Context, objPath string, data io.Reade
info, err := clnt.PutObject(context.Background(), bucket, object, data, -1, minio.PutObjectOptions{
ContentType: mimedb.TypeByExtension(path.Ext(object)),
DisableContentSha256: true,
Checksum: minio.ChecksumFullObjectCRC32C,
})
n = info.Size
return n, err
+31 -17
View File
@@ -22,11 +22,11 @@ import (
"net"
"net/http"
"path"
"path/filepath"
"runtime/debug"
"strings"
"sync/atomic"
"time"
"unicode"
"github.com/dustin/go-humanize"
"github.com/minio/minio-go/v7/pkg/s3utils"
@@ -293,12 +293,6 @@ func parseAmzDateHeader(req *http.Request) (time.Time, APIErrorCode) {
return time.Time{}, ErrMissingDateHeader
}
// Bad path components to be rejected by the path validity handler.
const (
dotdotComponent = ".."
dotComponent = "."
)
func hasBadHost(host string) error {
if globalIsCICD && strings.TrimSpace(host) == "" {
// under CI/CD test setups ignore empty hosts as invalid hosts
@@ -311,21 +305,41 @@ func hasBadHost(host string) error {
// Check if the incoming path has bad path components,
// such as ".." and "."
func hasBadPathComponent(path string) bool {
if len(path) > 4096 {
// path cannot be greater than Linux PATH_MAX
// this is to avoid a busy loop, that can happen
// if the caller sends path of following style
// a/a/a/a/a/a/a/a...
n := len(path)
if n > 32<<10 {
// At 32K we are beyond reasonable.
return true
}
path = filepath.ToSlash(strings.TrimSpace(path)) // For windows '\' must be converted to '/'
for _, p := range strings.Split(path, SlashSeparator) {
switch strings.TrimSpace(p) {
case dotdotComponent:
i := 0
// Skip leading slashes (for sake of Windows \ is included as well)
for i < n && (path[i] == SlashSeparatorChar || path[i] == '\\') {
i++
}
for i < n {
// Find the next segment
start := i
for i < n && path[i] != SlashSeparatorChar && path[i] != '\\' {
i++
}
// Trim whitespace of segment
segmentStart, segmentEnd := start, i
for segmentStart < segmentEnd && unicode.IsSpace(rune(path[segmentStart])) {
segmentStart++
}
for segmentEnd > segmentStart && unicode.IsSpace(rune(path[segmentEnd-1])) {
segmentEnd--
}
// Check for ".." or "."
switch {
case segmentEnd-segmentStart == 2 && path[segmentStart] == '.' && path[segmentStart+1] == '.':
return true
case dotComponent:
case segmentEnd-segmentStart == 1 && path[segmentStart] == '.':
return true
}
i++
}
return false
}
+25
View File
@@ -22,6 +22,7 @@ import (
"net/http/httptest"
"net/url"
"strconv"
"strings"
"testing"
"github.com/minio/minio/internal/crypto"
@@ -184,3 +185,27 @@ func TestSSETLSHandler(t *testing.T) {
}
}
}
func Benchmark_hasBadPathComponent(t *testing.B) {
tests := []struct {
name string
input string
want bool
}{
{name: "empty", input: "", want: false},
{name: "backslashes", input: `\a\a\ \\ \\\\\\\`, want: false},
{name: "long", input: strings.Repeat("a/", 2000), want: false},
{name: "long-fail", input: strings.Repeat("a/", 2000) + "../..", want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(b *testing.B) {
b.SetBytes(int64(len(tt.input)))
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if got := hasBadPathComponent(tt.input); got != tt.want {
t.Fatalf("hasBadPathComponent() = %v, want %v", got, tt.want)
}
}
})
}
}
-1
View File
@@ -366,7 +366,6 @@ func maxClients(f http.HandlerFunc) http.HandlerFunc {
writeErrorResponse(ctx, w,
errorCodes.ToAPIErr(ErrTooManyRequests),
r.URL)
}
}
}
+5 -2
View File
@@ -447,7 +447,7 @@ func getHostName(r *http.Request) (hostName string) {
}
// Proxy any request to an endpoint.
func proxyRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, ep ProxyEndpoint) (success bool) {
func proxyRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, ep ProxyEndpoint, returnErr bool) (success bool) {
success = true
// Make sure we remove any existing headers before
@@ -462,7 +462,10 @@ func proxyRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, e
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
success = false
if err != nil && !errors.Is(err, context.Canceled) {
replLogIf(GlobalContext, err)
proxyLogIf(GlobalContext, err)
}
if returnErr {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
}
},
})
-1
View File
@@ -493,7 +493,6 @@ func (ies *IAMEtcdStore) watch(ctx context.Context, keyPath string) <-chan iamWa
keyPath: string(event.Kv.Key),
}
}
}
}
}
-1
View File
@@ -278,7 +278,6 @@ func (iamOS *IAMObjectStore) loadUserIdentity(ctx context.Context, user string,
iamOS.deleteIAMConfig(ctx, getMappedPolicyPath(user, userType, false))
}
return u, errNoSuchUser
}
u.Credentials.Claims = jwtClaims.Map()
}
+9 -4
View File
@@ -845,7 +845,11 @@ func (store *IAMStoreSys) PolicyDBGet(name string, groups ...string) ([]string,
if err != nil {
return nil, err
}
return val.([]string), nil
res, ok := val.([]string)
if !ok {
return nil, errors.New("unexpected policy type")
}
return res, nil
}
return getPolicies()
}
@@ -1218,7 +1222,6 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
cache.iamGroupPolicyMap.Delete(name)
}
} else {
if err = store.saveMappedPolicy(ctx, name, userType, isGroup, newPolicyMapping); err != nil {
return
}
@@ -1620,7 +1623,6 @@ func (store *IAMStoreSys) MergePolicies(policyName string) (string, policy.Polic
policies = append(policies, policy)
toMerge = append(toMerge, p.Policy)
}
}
return strings.Join(policies, ","), policy.MergePolicies(toMerge...)
@@ -2917,7 +2919,10 @@ func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) error
return err
}
newCache := val.(*iamCache)
newCache, ok := val.(*iamCache)
if !ok {
return nil
}
cache := store.lock()
defer store.unlock()
-1
View File
@@ -2286,7 +2286,6 @@ func (sys *IAMSys) IsAllowedSTS(args policy.Args, parentUser string) bool {
}
policies = policySet.ToSlice()
}
}
// Defensive code: Do not allow any operation if no policy is found in the session token
+4 -3
View File
@@ -26,6 +26,7 @@ import (
"github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/grid"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/pubsub"
"github.com/minio/mux"
@@ -200,19 +201,19 @@ func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r
}
if len(mergeCh) == 0 {
// Flush if nothing is queued
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
grid.PutByteBuffer(ev)
case <-emptyEventTicker:
if err := enc.Encode(struct{ Records []event.Event }{}); err != nil {
return
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
case <-keepAliveTicker:
if _, err := w.Write([]byte(" ")); err != nil {
return
}
w.(http.Flusher).Flush()
xhttp.Flush(w)
case <-ctx.Done():
return
}
+152 -111
View File
@@ -24,11 +24,20 @@ import (
"fmt"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/minio/minio/internal/dsync"
)
// Reject new lock requests immediately when this many are queued
// for the local lock mutex.
// We do not block unlocking or maintenance, but they add to the count.
// The limit is set to allow for bursty behavior,
// but prevent requests to overload the server completely.
// Rejected clients are expected to retry.
const lockMutexWaitLimit = 1000
// lockRequesterInfo stores various info from the client for each lock that is requested.
type lockRequesterInfo struct {
Name string // name of the resource lock was requested for
@@ -52,9 +61,25 @@ func isWriteLock(lri []lockRequesterInfo) bool {
//
//msgp:ignore localLocker
type localLocker struct {
mutex sync.Mutex
lockMap map[string][]lockRequesterInfo
lockUID map[string]string // UUID -> resource map.
mutex sync.Mutex
waitMutex atomic.Int32
lockMap map[string][]lockRequesterInfo
lockUID map[string]string // UUID -> resource map.
// the following are updated on every cleanup defined in lockValidityDuration
readers atomic.Int32
writers atomic.Int32
lastCleanup atomic.Pointer[time.Time]
locksOverloaded atomic.Int64
}
// getMutex will lock the mutex.
// Call the returned function to unlock.
func (l *localLocker) getMutex() func() {
l.waitMutex.Add(1)
l.mutex.Lock()
l.waitMutex.Add(-1)
return l.mutex.Unlock
}
func (l *localLocker) String() string {
@@ -76,9 +101,16 @@ func (l *localLocker) Lock(ctx context.Context, args dsync.LockArgs) (reply bool
return false, fmt.Errorf("internal error: localLocker.Lock called with more than %d resources", maxDeleteList)
}
l.mutex.Lock()
defer l.mutex.Unlock()
// If we have too many waiting, reject this at once.
if l.waitMutex.Load() > lockMutexWaitLimit {
l.locksOverloaded.Add(1)
return false, nil
}
// Wait for mutex
defer l.getMutex()()
if ctx.Err() != nil {
return false, ctx.Err()
}
if !l.canTakeLock(args.Resources...) {
// Not all locks can be taken on resources,
// reject it completely.
@@ -117,9 +149,7 @@ func (l *localLocker) Unlock(_ context.Context, args dsync.LockArgs) (reply bool
return false, fmt.Errorf("internal error: localLocker.Unlock called with more than %d resources", maxDeleteList)
}
l.mutex.Lock()
defer l.mutex.Unlock()
err = nil
defer l.getMutex()()
for _, resource := range args.Resources {
lri, ok := l.lockMap[resource]
@@ -164,9 +194,17 @@ func (l *localLocker) RLock(ctx context.Context, args dsync.LockArgs) (reply boo
if len(args.Resources) != 1 {
return false, fmt.Errorf("internal error: localLocker.RLock called with more than one resource")
}
// If we have too many waiting, reject this at once.
if l.waitMutex.Load() > lockMutexWaitLimit {
l.locksOverloaded.Add(1)
return false, nil
}
l.mutex.Lock()
defer l.mutex.Unlock()
// Wait for mutex
defer l.getMutex()()
if ctx.Err() != nil {
return false, ctx.Err()
}
resource := args.Resources[0]
now := UTCNow()
lrInfo := lockRequesterInfo{
@@ -200,8 +238,7 @@ func (l *localLocker) RUnlock(_ context.Context, args dsync.LockArgs) (reply boo
return false, fmt.Errorf("internal error: localLocker.RUnlock called with more than one resource")
}
l.mutex.Lock()
defer l.mutex.Unlock()
defer l.getMutex()()
var lri []lockRequesterInfo
resource := args.Resources[0]
@@ -218,35 +255,28 @@ func (l *localLocker) RUnlock(_ context.Context, args dsync.LockArgs) (reply boo
}
type lockStats struct {
Total int
Writes int
Reads int
Total int
Writes int
Reads int
LockQueue int
LocksAbandoned int
LastCleanup *time.Time
}
func (l *localLocker) stats() lockStats {
l.mutex.Lock()
defer l.mutex.Unlock()
st := lockStats{Total: len(l.lockMap)}
for _, v := range l.lockMap {
if len(v) == 0 {
continue
}
entry := v[0]
if entry.Writer {
st.Writes++
} else {
st.Reads += len(v)
}
return lockStats{
Total: len(l.lockMap),
Reads: int(l.readers.Load()),
Writes: int(l.writers.Load()),
LockQueue: int(l.waitMutex.Load()),
LastCleanup: l.lastCleanup.Load(),
}
return st
}
type localLockMap map[string][]lockRequesterInfo
func (l *localLocker) DupLockMap() localLockMap {
l.mutex.Lock()
defer l.mutex.Unlock()
defer l.getMutex()()
lockCopy := make(map[string][]lockRequesterInfo, len(l.lockMap))
for k, v := range l.lockMap {
@@ -274,108 +304,110 @@ func (l *localLocker) IsLocal() bool {
}
func (l *localLocker) ForceUnlock(ctx context.Context, args dsync.LockArgs) (reply bool, err error) {
select {
case <-ctx.Done():
if ctx.Err() != nil {
return false, ctx.Err()
default:
l.mutex.Lock()
defer l.mutex.Unlock()
if len(args.UID) == 0 {
for _, resource := range args.Resources {
lris, ok := l.lockMap[resource]
if !ok {
continue
}
// Collect uids, so we don't mutate while we delete
uids := make([]string, 0, len(lris))
for _, lri := range lris {
uids = append(uids, lri.UID)
}
}
// Delete collected uids:
for _, uid := range uids {
lris, ok := l.lockMap[resource]
if !ok {
// Just to be safe, delete uuids.
for idx := 0; idx < maxDeleteList; idx++ {
mapID := formatUUID(uid, idx)
if _, ok := l.lockUID[mapID]; !ok {
break
}
delete(l.lockUID, mapID)
}
continue
}
l.removeEntry(resource, dsync.LockArgs{UID: uid}, &lris)
}
}
return true, nil
}
idx := 0
for {
mapID := formatUUID(args.UID, idx)
resource, ok := l.lockUID[mapID]
if !ok {
return idx > 0, nil
}
defer l.getMutex()()
if ctx.Err() != nil {
return false, ctx.Err()
}
if len(args.UID) == 0 {
for _, resource := range args.Resources {
lris, ok := l.lockMap[resource]
if !ok {
// Unexpected inconsistency, delete.
delete(l.lockUID, mapID)
idx++
continue
}
reply = true
l.removeEntry(resource, dsync.LockArgs{UID: args.UID}, &lris)
idx++
// Collect uids, so we don't mutate while we delete
uids := make([]string, 0, len(lris))
for _, lri := range lris {
uids = append(uids, lri.UID)
}
// Delete collected uids:
for _, uid := range uids {
lris, ok := l.lockMap[resource]
if !ok {
// Just to be safe, delete uuids.
for idx := 0; idx < maxDeleteList; idx++ {
mapID := formatUUID(uid, idx)
if _, ok := l.lockUID[mapID]; !ok {
break
}
delete(l.lockUID, mapID)
}
continue
}
l.removeEntry(resource, dsync.LockArgs{UID: uid}, &lris)
}
}
return true, nil
}
idx := 0
for {
mapID := formatUUID(args.UID, idx)
resource, ok := l.lockUID[mapID]
if !ok {
return idx > 0, nil
}
lris, ok := l.lockMap[resource]
if !ok {
// Unexpected inconsistency, delete.
delete(l.lockUID, mapID)
idx++
continue
}
reply = true
l.removeEntry(resource, dsync.LockArgs{UID: args.UID}, &lris)
idx++
}
}
func (l *localLocker) Refresh(ctx context.Context, args dsync.LockArgs) (refreshed bool, err error) {
select {
case <-ctx.Done():
if ctx.Err() != nil {
return false, ctx.Err()
default:
l.mutex.Lock()
defer l.mutex.Unlock()
}
// Check whether uid is still active.
resource, ok := l.lockUID[formatUUID(args.UID, 0)]
defer l.getMutex()()
if ctx.Err() != nil {
return false, ctx.Err()
}
// Check whether uid is still active.
resource, ok := l.lockUID[formatUUID(args.UID, 0)]
if !ok {
return false, nil
}
idx := 0
for {
lris, ok := l.lockMap[resource]
if !ok {
return false, nil
// Inconsistent. Delete UID.
delete(l.lockUID, formatUUID(args.UID, idx))
return idx > 0, nil
}
idx := 0
for {
lris, ok := l.lockMap[resource]
if !ok {
// Inconsistent. Delete UID.
delete(l.lockUID, formatUUID(args.UID, idx))
return idx > 0, nil
}
now := UTCNow()
for i := range lris {
if lris[i].UID == args.UID {
lris[i].TimeLastRefresh = now.UnixNano()
}
}
idx++
resource, ok = l.lockUID[formatUUID(args.UID, idx)]
if !ok {
// No more resources for UID, but we did update at least one.
return true, nil
now := UTCNow()
for i := range lris {
if lris[i].UID == args.UID {
lris[i].TimeLastRefresh = now.UnixNano()
}
}
idx++
resource, ok = l.lockUID[formatUUID(args.UID, idx)]
if !ok {
// No more resources for UID, but we did update at least one.
return true, nil
}
}
}
// Similar to removeEntry but only removes an entry only if the lock entry exists in map.
// Caller must hold 'l.mutex' lock.
func (l *localLocker) expireOldLocks(interval time.Duration) {
l.mutex.Lock()
defer l.mutex.Unlock()
defer l.getMutex()()
var readers, writers int32
for k, lris := range l.lockMap {
modified := false
for i := 0; i < len(lris); {
@@ -393,6 +425,11 @@ func (l *localLocker) expireOldLocks(interval time.Duration) {
lris = append(lris[:i], lris[i+1:]...)
// Check same i
} else {
if lri.Writer {
writers++
} else {
readers++
}
// Move to next
i++
}
@@ -401,6 +438,10 @@ func (l *localLocker) expireOldLocks(interval time.Duration) {
l.lockMap[k] = lris
}
}
t := time.Now()
l.lastCleanup.Store(&t)
l.readers.Store(readers)
l.writers.Store(writers)
}
func newLocker() *localLocker {
+124 -8
View File
@@ -3,6 +3,8 @@ package cmd
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"time"
"github.com/tinylib/msgp/msgp"
)
@@ -506,6 +508,36 @@ func (z *lockStats) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Reads")
return
}
case "LockQueue":
z.LockQueue, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "LockQueue")
return
}
case "LocksAbandoned":
z.LocksAbandoned, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "LocksAbandoned")
return
}
case "LastCleanup":
if dc.IsNil() {
err = dc.ReadNil()
if err != nil {
err = msgp.WrapError(err, "LastCleanup")
return
}
z.LastCleanup = nil
} else {
if z.LastCleanup == nil {
z.LastCleanup = new(time.Time)
}
*z.LastCleanup, err = dc.ReadTime()
if err != nil {
err = msgp.WrapError(err, "LastCleanup")
return
}
}
default:
err = dc.Skip()
if err != nil {
@@ -518,10 +550,10 @@ func (z *lockStats) DecodeMsg(dc *msgp.Reader) (err error) {
}
// EncodeMsg implements msgp.Encodable
func (z lockStats) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 3
func (z *lockStats) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 6
// write "Total"
err = en.Append(0x83, 0xa5, 0x54, 0x6f, 0x74, 0x61, 0x6c)
err = en.Append(0x86, 0xa5, 0x54, 0x6f, 0x74, 0x61, 0x6c)
if err != nil {
return
}
@@ -550,15 +582,52 @@ func (z lockStats) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "Reads")
return
}
// write "LockQueue"
err = en.Append(0xa9, 0x4c, 0x6f, 0x63, 0x6b, 0x51, 0x75, 0x65, 0x75, 0x65)
if err != nil {
return
}
err = en.WriteInt(z.LockQueue)
if err != nil {
err = msgp.WrapError(err, "LockQueue")
return
}
// write "LocksAbandoned"
err = en.Append(0xae, 0x4c, 0x6f, 0x63, 0x6b, 0x73, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x65, 0x64)
if err != nil {
return
}
err = en.WriteInt(z.LocksAbandoned)
if err != nil {
err = msgp.WrapError(err, "LocksAbandoned")
return
}
// write "LastCleanup"
err = en.Append(0xab, 0x4c, 0x61, 0x73, 0x74, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70)
if err != nil {
return
}
if z.LastCleanup == nil {
err = en.WriteNil()
if err != nil {
return
}
} else {
err = en.WriteTime(*z.LastCleanup)
if err != nil {
err = msgp.WrapError(err, "LastCleanup")
return
}
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z lockStats) MarshalMsg(b []byte) (o []byte, err error) {
func (z *lockStats) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 3
// map header, size 6
// string "Total"
o = append(o, 0x83, 0xa5, 0x54, 0x6f, 0x74, 0x61, 0x6c)
o = append(o, 0x86, 0xa5, 0x54, 0x6f, 0x74, 0x61, 0x6c)
o = msgp.AppendInt(o, z.Total)
// string "Writes"
o = append(o, 0xa6, 0x57, 0x72, 0x69, 0x74, 0x65, 0x73)
@@ -566,6 +635,19 @@ func (z lockStats) MarshalMsg(b []byte) (o []byte, err error) {
// string "Reads"
o = append(o, 0xa5, 0x52, 0x65, 0x61, 0x64, 0x73)
o = msgp.AppendInt(o, z.Reads)
// string "LockQueue"
o = append(o, 0xa9, 0x4c, 0x6f, 0x63, 0x6b, 0x51, 0x75, 0x65, 0x75, 0x65)
o = msgp.AppendInt(o, z.LockQueue)
// string "LocksAbandoned"
o = append(o, 0xae, 0x4c, 0x6f, 0x63, 0x6b, 0x73, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x65, 0x64)
o = msgp.AppendInt(o, z.LocksAbandoned)
// string "LastCleanup"
o = append(o, 0xab, 0x4c, 0x61, 0x73, 0x74, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70)
if z.LastCleanup == nil {
o = msgp.AppendNil(o)
} else {
o = msgp.AppendTime(o, *z.LastCleanup)
}
return
}
@@ -605,6 +687,35 @@ func (z *lockStats) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Reads")
return
}
case "LockQueue":
z.LockQueue, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "LockQueue")
return
}
case "LocksAbandoned":
z.LocksAbandoned, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "LocksAbandoned")
return
}
case "LastCleanup":
if msgp.IsNil(bts) {
bts, err = msgp.ReadNilBytes(bts)
if err != nil {
return
}
z.LastCleanup = nil
} else {
if z.LastCleanup == nil {
z.LastCleanup = new(time.Time)
}
*z.LastCleanup, bts, err = msgp.ReadTimeBytes(bts)
if err != nil {
err = msgp.WrapError(err, "LastCleanup")
return
}
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
@@ -618,7 +729,12 @@ func (z *lockStats) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z lockStats) Msgsize() (s int) {
s = 1 + 6 + msgp.IntSize + 7 + msgp.IntSize + 6 + msgp.IntSize
func (z *lockStats) Msgsize() (s int) {
s = 1 + 6 + msgp.IntSize + 7 + msgp.IntSize + 6 + msgp.IntSize + 10 + msgp.IntSize + 15 + msgp.IntSize + 12
if z.LastCleanup == nil {
s += msgp.NilSize
} else {
s += msgp.TimeSize
}
return
}
-1
View File
@@ -136,7 +136,6 @@ func TestLocalLockerUnlock(t *testing.T) {
}
wResources[i] = names
wUIDs[i] = uid
}
for i := range rResources {
name := mustGetUUID()
+13 -3
View File
@@ -33,8 +33,12 @@ type lockRESTServer struct {
// RefreshHandler - refresh the current lock
func (l *lockRESTServer) RefreshHandler(args *dsync.LockArgs) (*dsync.LockResp, *grid.RemoteErr) {
// Add a timeout similar to what we expect upstream.
ctx, cancel := context.WithTimeout(context.Background(), dsync.DefaultTimeouts.RefreshCall)
defer cancel()
resp := lockRPCRefresh.NewResponse()
refreshed, err := l.ll.Refresh(context.Background(), *args)
refreshed, err := l.ll.Refresh(ctx, *args)
if err != nil {
return l.makeResp(resp, err)
}
@@ -46,8 +50,11 @@ func (l *lockRESTServer) RefreshHandler(args *dsync.LockArgs) (*dsync.LockResp,
// LockHandler - Acquires a lock.
func (l *lockRESTServer) LockHandler(args *dsync.LockArgs) (*dsync.LockResp, *grid.RemoteErr) {
// Add a timeout similar to what we expect upstream.
ctx, cancel := context.WithTimeout(context.Background(), dsync.DefaultTimeouts.Acquire)
defer cancel()
resp := lockRPCLock.NewResponse()
success, err := l.ll.Lock(context.Background(), *args)
success, err := l.ll.Lock(ctx, *args)
if err == nil && !success {
return l.makeResp(resp, errLockConflict)
}
@@ -65,8 +72,11 @@ func (l *lockRESTServer) UnlockHandler(args *dsync.LockArgs) (*dsync.LockResp, *
// RLockHandler - Acquires an RLock.
func (l *lockRESTServer) RLockHandler(args *dsync.LockArgs) (*dsync.LockResp, *grid.RemoteErr) {
// Add a timeout similar to what we expect upstream.
ctx, cancel := context.WithTimeout(context.Background(), dsync.DefaultTimeouts.Acquire)
defer cancel()
resp := lockRPCRLock.NewResponse()
success, err := l.ll.RLock(context.Background(), *args)
success, err := l.ll.RLock(ctx, *args)
if err == nil && !success {
err = errLockConflict
}
+3 -2
View File
@@ -27,6 +27,7 @@ import (
jsoniter "github.com/json-iterator/go"
"github.com/klauspost/compress/s2"
"github.com/minio/minio/internal/bpool"
xioutil "github.com/minio/minio/internal/ioutil"
"github.com/tinylib/msgp/msgp"
"github.com/valyala/bytebufferpool"
@@ -236,7 +237,7 @@ func (w *metacacheWriter) Reset(out io.Writer) {
}
}
var s2DecPool = sync.Pool{New: func() interface{} {
var s2DecPool = bpool.Pool[*s2.Reader]{New: func() *s2.Reader {
// Default alloc block for network transfer.
return s2.NewReader(nil, s2.ReaderAllocBlock(16<<10))
}}
@@ -253,7 +254,7 @@ type metacacheReader struct {
// newMetacacheReader creates a new cache reader.
// Nothing will be read from the stream yet.
func newMetacacheReader(r io.Reader) *metacacheReader {
dec := s2DecPool.Get().(*s2.Reader)
dec := s2DecPool.Get()
dec.Reset(r)
mr := msgpNewReader(dec)
return &metacacheReader{
-1
View File
@@ -2474,7 +2474,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroupV2 {
}
downtimeDuration.Value = float64(dwntime / time.Second)
ml = append(ml, downtimeDuration)
}
return ml
})
-1
View File
@@ -177,7 +177,6 @@ func loadClusterUsageBucketMetrics(ctx context.Context, m MetricValues, c *metri
for k, v := range usage.ObjectVersionsHistogram {
m.Set(usageBucketObjectVersionCountDistribution, float64(v), "range", k, "bucket", bucket)
}
}
return nil
}
-1
View File
@@ -1184,7 +1184,6 @@ func (sys *NotificationSys) GetPeerOnlineCount() (nodesOnline, nodesOffline int)
defer wg.Done()
nodesOnlineIndex[idx] = client.restClient.HealthCheckFn()
}(idx, client)
}
wg.Wait()
-5
View File
@@ -389,7 +389,6 @@ func _testListObjects(obj ObjectLayer, instanceType string, t1 TestErrHandler, v
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
}
// Formulating the result data set to be expected from ListObjects call inside the tests,
@@ -1014,7 +1013,6 @@ func _testListObjects(obj ObjectLayer, instanceType string, t1 TestErrHandler, v
t.Errorf("Test %d: %s: Expected NextMarker to be empty since listing is not truncated, but instead found `%v`", i+1, instanceType, result.NextMarker)
}
}
}
})
}
@@ -1166,7 +1164,6 @@ func testListObjectVersions(obj ObjectLayer, instanceType string, t1 TestErrHand
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
}
// Formualting the result data set to be expected from ListObjects call inside the tests,
@@ -1785,12 +1782,10 @@ func testListObjectsContinuation(obj ObjectLayer, instanceType string, t1 TestEr
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
}
// Formulating the result data set to be expected from ListObjects call inside the tests,
// This will be used in testCases and used for asserting the correctness of ListObjects output in the tests.
resultCases := []ListObjectsInfo{
{
Objects: []ObjectInfo{
-1
View File
@@ -443,7 +443,6 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
}
// Expected Results set for asserting ListObjectMultipart test.
+2 -5
View File
@@ -467,13 +467,10 @@ func completeMultipartOpts(ctx context.Context, r *http.Request, bucket, object
}
}
}
opts.WantChecksum, err = hash.GetContentChecksum(r.Header)
if err != nil {
return opts, InvalidArgument{
Bucket: bucket,
Object: object,
Err: fmt.Errorf("invalid/unknown checksum sent: %v", err),
}
return opts, err
}
opts.MTime = mtime
opts.UserDefined = make(map[string]string)
+2 -1
View File
@@ -374,7 +374,8 @@ func setPutObjHeaders(w http.ResponseWriter, objInfo ObjectInfo, del bool, h htt
lc.SetPredictionHeaders(w, objInfo.ToLifecycleOpts())
}
}
hash.AddChecksumHeader(w, objInfo.decryptChecksums(0, h))
cs, _ := objInfo.decryptChecksums(0, h)
hash.AddChecksumHeader(w, cs)
}
func deleteObjectVersions(ctx context.Context, o ObjectLayer, bucket string, toDel []ObjectToDelete, lcEvent lifecycle.Event) {
+25 -18
View File
@@ -521,7 +521,8 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
if r.Header.Get(xhttp.AmzChecksumMode) == "ENABLED" && rs == nil {
// AWS S3 silently drops checksums on range requests.
hash.AddChecksumHeader(w, objInfo.decryptChecksums(opts.PartNumber, r.Header))
cs, _ := objInfo.decryptChecksums(opts.PartNumber, r.Header)
hash.AddChecksumHeader(w, cs)
}
if err = setObjectHeaders(ctx, w, objInfo, rs, opts); err != nil {
@@ -632,7 +633,7 @@ func (api objectAPIHandlers) getObjectAttributesHandler(ctx context.Context, obj
w.Header().Del(xhttp.ContentType)
if _, ok := opts.ObjectAttributes[xhttp.Checksum]; ok {
chkSums := objInfo.decryptChecksums(0, r.Header)
chkSums, _ := objInfo.decryptChecksums(0, r.Header)
// AWS does not appear to append part number on this API call.
if len(chkSums) > 0 {
OA.Checksum = &objectAttributesChecksum{
@@ -945,7 +946,8 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
if r.Header.Get(xhttp.AmzChecksumMode) == "ENABLED" && rs == nil {
// AWS S3 silently drops checksums on range requests.
hash.AddChecksumHeader(w, objInfo.decryptChecksums(opts.PartNumber, r.Header))
cs, _ := objInfo.decryptChecksums(opts.PartNumber, r.Header)
hash.AddChecksumHeader(w, cs)
}
// Set standard object headers.
@@ -1536,7 +1538,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
srcInfo.UserDefined[xhttp.AmzObjectTagging] = objTags
srcInfo.UserDefined[ReservedMetadataPrefixLower+TaggingTimestamp] = UTCNow().Format(time.RFC3339Nano)
}
}
srcInfo.UserDefined = filterReplicationStatusMetadata(srcInfo.UserDefined)
@@ -1629,7 +1630,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
srcInfo.metadataOnly && srcOpts.VersionID == "" &&
!crypto.Requested(r.Header) &&
!crypto.IsSourceEncrypted(srcInfo.UserDefined) {
// If x-amz-metadata-directive is not set to REPLACE then we need
// to error out if source and destination are same.
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopyDest), r.URL)
@@ -2229,8 +2229,15 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
putObject = objectAPI.PutObject
)
// Check if put is allowed
if s3Err = isPutActionAllowed(ctx, rAuthType, bucket, object, r, policy.PutObjectAction); s3Err != ErrNone {
var opts untarOptions
opts.ignoreDirs = strings.EqualFold(r.Header.Get(xhttp.MinIOSnowballIgnoreDirs), "true")
opts.ignoreErrs = strings.EqualFold(r.Header.Get(xhttp.MinIOSnowballIgnoreErrors), "true")
opts.prefixAll = r.Header.Get(xhttp.MinIOSnowballPrefix)
if opts.prefixAll != "" {
opts.prefixAll = trimLeadingSlash(pathJoin(opts.prefixAll, slashSeparator))
}
// Check if put is allow for specified prefix.
if s3Err = isPutActionAllowed(ctx, rAuthType, bucket, opts.prefixAll, r, policy.PutObjectAction); s3Err != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return
}
@@ -2298,7 +2305,10 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
putObjectTar := func(reader io.Reader, info os.FileInfo, object string) error {
size := info.Size()
if s3Err = isPutActionAllowed(ctx, getRequestAuthType(r), bucket, object, r, policy.PutObjectAction); s3Err != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return errors.New(errorCodes.ToAPIErr(s3Err).Code)
}
metadata := map[string]string{
xhttp.AmzStorageClass: sc, // save same storage-class as incoming stream.
}
@@ -2334,7 +2344,7 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
if r.Header.Get(xhttp.AmzBucketReplicationStatus) == replication.Replica.String() {
if s3Err = isPutActionAllowed(ctx, getRequestAuthType(r), bucket, object, r, policy.ReplicateObjectAction); s3Err != ErrNone {
return err
return errors.New(errorCodes.ToAPIErr(s3Err).Code)
}
metadata[ReservedMetadataPrefixLower+ReplicaStatus] = replication.Replica.String()
metadata[ReservedMetadataPrefixLower+ReplicaTimestamp] = UTCNow().Format(time.RFC3339Nano)
@@ -2398,7 +2408,6 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
if dsc := mustReplicate(ctx, bucket, object, getMustReplicateOptions(metadata, "", "", replication.ObjectReplicationType, opts)); dsc.ReplicateAny() {
metadata[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
metadata[ReservedMetadataPrefixLower+ReplicationStatus] = dsc.PendingStatus()
}
var objectEncryptionKey crypto.ObjectKey
@@ -2481,14 +2490,6 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
return nil
}
var opts untarOptions
opts.ignoreDirs = strings.EqualFold(r.Header.Get(xhttp.MinIOSnowballIgnoreDirs), "true")
opts.ignoreErrs = strings.EqualFold(r.Header.Get(xhttp.MinIOSnowballIgnoreErrors), "true")
opts.prefixAll = r.Header.Get(xhttp.MinIOSnowballPrefix)
if opts.prefixAll != "" {
opts.prefixAll = trimLeadingSlash(pathJoin(opts.prefixAll, slashSeparator))
}
if err = untar(ctx, hreader, putObjectTar, opts); err != nil {
apiErr := errorCodes.ToAPIErr(s3Err)
// If not set, convert or use BadRequest
@@ -2880,6 +2881,12 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r
return
}
// Check permissions to perform this object retention operation
if s3Err := checkRequestAuthType(ctx, r, policy.PutObjectRetentionAction, bucket, object); s3Err != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return
}
cred, owner, s3Err := validateSignature(getRequestAuthType(r), r)
if s3Err != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
+1 -4
View File
@@ -814,7 +814,6 @@ func testAPIGetObjectWithMPHandler(obj ObjectLayer, instanceType, bucketName str
caseNumber++
}
}
}
// HTTP request for testing when `objectLayer` is set to `nil`.
@@ -1567,8 +1566,8 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
}
}
}
}
if testCase.expectedRespStatus == http.StatusOK {
buffer := new(bytes.Buffer)
// Fetch the object to check whether the content is same as the one uploaded via PutObject.
@@ -3520,7 +3519,6 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string
t.Errorf("Case %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`", i+1,
instanceType, testCase.expectedRespStatus, recV2.Code)
}
}
// Test for Anonymous/unsigned http request.
@@ -4198,7 +4196,6 @@ func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName str
// validate the error response.
if test.expectedErr != noAPIErr {
var errBytes []byte
// read the response body.
errBytes, err = io.ReadAll(rec.Result().Body)
+2 -2
View File
@@ -93,8 +93,8 @@ func getLambdaEventData(bucket, object string, cred auth.Credentials, r *http.Re
},
UserIdentity: levent.Identity{
Type: "IAMUser",
PrincipalID: cred.AccessKey,
AccessKeyID: cred.SecretKey,
PrincipalID: cred.ParentUser,
AccessKeyID: cred.AccessKey,
},
}
return eventData, nil
+1 -1
View File
@@ -216,7 +216,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
checksumType := hash.NewChecksumHeader(r.Header)
if checksumType.Is(hash.ChecksumInvalid) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequestParameter), r.URL)
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
return
} else if checksumType.IsSet() && !checksumType.Is(hash.ChecksumTrailing) {
opts.WantChecksum = &hash.Checksum{Type: checksumType}
-2
View File
@@ -228,7 +228,6 @@ func testMultipleObjectCreation(obj ObjectLayer, instanceType string, t TestErrH
if objInfo.Size != int64(len(value)) {
t.Errorf("%s: Size mismatch of the GetObject data.", instanceType)
}
}
}
@@ -384,7 +383,6 @@ func testPaging(obj ObjectLayer, instanceType string, t TestErrHandler) {
// check results with Marker.
{
result, err = obj.ListObjects(context.Background(), "bucket", "", "newPrefix", "", 3)
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
+8 -9
View File
@@ -25,10 +25,10 @@ import (
"fmt"
"os"
"strings"
"sync"
"syscall"
"unsafe"
"github.com/minio/minio/internal/bpool"
"golang.org/x/sys/unix"
)
@@ -106,15 +106,15 @@ const blockSize = 8 << 10 // 8192
// By default at least 128 entries in single getdents call (1MiB buffer)
var (
direntPool = sync.Pool{
New: func() interface{} {
direntPool = bpool.Pool[*[]byte]{
New: func() *[]byte {
buf := make([]byte, blockSize*128)
return &buf
},
}
direntNamePool = sync.Pool{
New: func() interface{} {
direntNamePool = bpool.Pool[*[]byte]{
New: func() *[]byte {
buf := make([]byte, blockSize)
return &buf
},
@@ -183,11 +183,10 @@ func readDirFn(dirPath string, fn func(name string, typ os.FileMode) error) erro
}
return osErrToFileErr(err)
}
}
defer syscall.Close(fd)
bufp := direntPool.Get().(*[]byte)
bufp := direntPool.Get()
defer direntPool.Put(bufp)
buf := *bufp
@@ -273,11 +272,11 @@ func readDirWithOpts(dirPath string, opts readDirOpts) (entries []string, err er
}
defer syscall.Close(fd)
bufp := direntPool.Get().(*[]byte)
bufp := direntPool.Get()
defer direntPool.Put(bufp)
buf := *bufp
nameTmp := direntNamePool.Get().(*[]byte)
nameTmp := direntNamePool.Get()
defer direntNamePool.Put(nameTmp)
tmp := *nameTmp
-1
View File
@@ -173,7 +173,6 @@ func readDirWithOpts(dirPath string, opts readDirOpts) (entries []string, err er
count--
entries = append(entries, name)
}
return entries, nil
+1
View File
@@ -1308,6 +1308,7 @@ func (s *peerRESTServer) HeadBucketHandler(mss *grid.MSS) (info *VolInfo, nerr *
return &VolInfo{
Name: bucketInfo.Name,
Created: bucketInfo.Created,
Deleted: bucketInfo.Deleted, // needed for site replication
}, nil
}
+1
View File
@@ -393,6 +393,7 @@ func (client *remotePeerS3Client) GetBucketInfo(ctx context.Context, bucket stri
return BucketInfo{
Name: volInfo.Name,
Created: volInfo.Created,
Deleted: volInfo.Deleted,
}, nil
}
+8 -7
View File
@@ -131,16 +131,17 @@ func sanitizePolicy(r io.Reader) (io.Reader, error) {
d := jstream.NewDecoder(r, 0).ObjectAsKVS().MaxDepth(10)
sset := set.NewStringSet()
for mv := range d.Stream() {
var kvs jstream.KVS
if mv.ValueType == jstream.Object {
// This is a JSON object type (that preserves key order)
kvs = mv.Value.(jstream.KVS)
for _, kv := range kvs {
if sset.Contains(kv.Key) {
// Reject duplicate conditions or expiration.
return nil, fmt.Errorf("input policy has multiple %s, please fix your client code", kv.Key)
kvs, ok := mv.Value.(jstream.KVS)
if ok {
for _, kv := range kvs {
if sset.Contains(kv.Key) {
// Reject duplicate conditions or expiration.
return nil, fmt.Errorf("input policy has multiple %s, please fix your client code", kv.Key)
}
sset.Add(kv.Key)
}
sset.Add(kv.Key)
}
e.Encode(kvs)
}
+2 -1
View File
@@ -425,9 +425,10 @@ func serverHandleCmdArgs(ctxt serverCtxt) {
}
// allow transport to be HTTP/1.1 for proxying.
globalProxyEndpoints = GetProxyEndpoints(globalEndpoints)
globalInternodeTransport = NewInternodeHTTPTransport(ctxt.MaxIdleConnsPerHost)()
globalRemoteTargetTransport = NewRemoteTargetHTTPTransport(false)()
globalProxyEndpoints = GetProxyEndpoints(globalEndpoints, globalRemoteTargetTransport)
globalForwarder = handlers.NewForwarder(&handlers.Forwarder{
PassHost: true,
RoundTripper: globalRemoteTargetTransport,
+5 -3
View File
@@ -127,9 +127,10 @@ func (f *sftpDriver) getMinIOClient() (*minio.Client, error) {
tr = forwardForTransport{tr: tr, fwd: f.remoteIP}
}
return minio.New(f.endpoint, &minio.Options{
Creds: mcreds,
Secure: globalIsTLS,
Transport: tr,
TrailingHeaders: true,
Creds: mcreds,
Secure: globalIsTLS,
Transport: tr,
})
}
@@ -289,6 +290,7 @@ func (f *sftpDriver) Filewrite(r *sftp.Request) (w io.WriterAt, err error) {
oi, err := clnt.PutObject(r.Context(), bucket, object, pr, -1, minio.PutObjectOptions{
ContentType: mimedb.TypeByExtension(path.Ext(object)),
DisableContentSha256: true,
Checksum: minio.ChecksumFullObjectCRC32C,
})
stopFn(oi.Size, err)
pr.CloseWithError(err)
+17 -12
View File
@@ -62,7 +62,7 @@ var (
// if the sftp parameter --trusted-user-ca-key is set, then
// the final form of the key file will be set as this variable.
var caPublicKey ssh.PublicKey
var globalSFTPTrustedCAPubkey ssh.PublicKey
// https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.22.0:ssh/common.go;l=46
// preferredKexAlgos specifies the default preference for key-exchange
@@ -161,8 +161,8 @@ internalAuth:
return nil, errNoSuchUser
}
if caPublicKey != nil && pass == nil {
err := validateKey(c, key)
if globalSFTPTrustedCAPubkey != nil && pass == nil {
err := validateClientKeyIsTrusted(c, key)
if err != nil {
return nil, errAuthentication
}
@@ -174,7 +174,6 @@ internalAuth:
if subtle.ConstantTimeCompare([]byte(ui.Credentials.SecretKey), pass) != 1 {
return nil, errAuthentication
}
}
copts := map[string]string{
@@ -223,14 +222,11 @@ func processLDAPAuthentication(key ssh.PublicKey, pass []byte, user string) (per
if err != nil {
return nil, err
}
} else if key != nil {
lookupResult, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(user)
if err != nil {
return nil, err
}
}
if lookupResult == nil {
@@ -260,9 +256,18 @@ func processLDAPAuthentication(key ssh.PublicKey, pass []byte, user string) (per
return nil, errAuthentication
}
}
// Save each attribute to claims.
claims[ldapAttribPrefix+attribKey] = attribValue[0]
}
if key != nil {
// If a key was provided, we expect the user to have an sshPublicKey
// attribute.
if _, ok := claims[ldapAttribPrefix+"sshPublicKey"]; !ok {
return nil, errAuthentication
}
}
expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("")
if err != nil {
return nil, err
@@ -314,8 +319,8 @@ func processLDAPAuthentication(key ssh.PublicKey, pass []byte, user string) (per
}, nil
}
func validateKey(c ssh.ConnMetadata, clientKey ssh.PublicKey) (err error) {
if caPublicKey == nil {
func validateClientKeyIsTrusted(c ssh.ConnMetadata, clientKey ssh.PublicKey) (err error) {
if globalSFTPTrustedCAPubkey == nil {
return errors.New("public key authority validation requested but no ca public key specified.")
}
@@ -335,7 +340,7 @@ func validateKey(c ssh.ConnMetadata, clientKey ssh.PublicKey) (err error) {
// and that certificate type is correct.
checker := ssh.CertChecker{}
checker.IsUserAuthority = func(k ssh.PublicKey) bool {
return subtle.ConstantTimeCompare(caPublicKey.Marshal(), k.Marshal()) == 1
return subtle.ConstantTimeCompare(globalSFTPTrustedCAPubkey.Marshal(), k.Marshal()) == 1
}
_, err = checker.Authenticate(c, clientKey)
@@ -432,7 +437,7 @@ func startSFTPServer(args []string) {
allowMACs = filterAlgos(arg, strings.Split(tokens[1], ","), supportedMACs)
case "trusted-user-ca-key":
userCaKeyFile = tokens[1]
case "password-auth":
case "disable-password-auth":
disablePassAuth, _ = strconv.ParseBool(tokens[1])
}
}
@@ -461,7 +466,7 @@ func startSFTPServer(args []string) {
logger.Fatal(fmt.Errorf("invalid arguments passed, trusted user certificate authority public key file is not accessible: %v", err), "unable to start SFTP server")
}
caPublicKey, _, _, _, err = ssh.ParseAuthorizedKey(keyBytes)
globalSFTPTrustedCAPubkey, _, _, _, err = ssh.ParseAuthorizedKey(keyBytes)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed, trusted user certificate authority public key file is not parseable: %v", err), "unable to start SFTP server")
}
+68 -17
View File
@@ -91,6 +91,7 @@ func TestSFTPAuthentication(t *testing.T) {
suite.SFTPPublicKeyAuthentication(c)
suite.SFTPFailedPublicKeyAuthenticationInvalidKey(c)
suite.SFTPPublicKeyAuthNoPubKey(c)
suite.TearDownSuite(c)
},
@@ -146,6 +147,32 @@ func (s *TestSuiteIAM) SFTPPublicKeyAuthentication(c *check) {
}
}
// A user without an sshpubkey attribute in LDAP (here: fahim) should not be
// able to authenticate.
func (s *TestSuiteIAM) SFTPPublicKeyAuthNoPubKey(c *check) {
keyBytes, err := os.ReadFile("./testdata/dillon_test_key.pub")
if err != nil {
c.Fatalf("could not read test key file: %s", err)
}
testKey, _, _, _, err := ssh.ParseAuthorizedKey(keyBytes)
if err != nil {
c.Fatalf("could not parse test key file: %s", err)
}
newSSHCon := newSSHConnMock("fahim=ldap")
_, err = sshPubKeyAuth(newSSHCon, testKey)
if err == nil {
c.Fatalf("expected error but got none")
}
newSSHCon = newSSHConnMock("fahim")
_, err = sshPubKeyAuth(newSSHCon, testKey)
if err == nil {
c.Fatalf("expected error but got none")
}
}
func (s *TestSuiteIAM) SFTPFailedAuthDueToMissingPolicy(c *check) {
newSSHCon := newSSHConnMock("dillon=ldap")
_, err := sshPasswordAuth(newSSHCon, []byte("dillon"))
@@ -275,24 +302,48 @@ func (s *TestSuiteIAM) SFTPValidLDAPLoginWithPassword(c *check) {
c.Fatalf("policy add error: %v", err)
}
userDN := "uid=dillon,ou=people,ou=swengg,dc=min,dc=io"
userReq := madmin.PolicyAssociationReq{
Policies: []string{policy},
User: userDN,
}
if _, err := s.adm.AttachPolicy(ctx, userReq); err != nil {
c.Fatalf("Unable to attach policy: %v", err)
}
{
userDN := "uid=dillon,ou=people,ou=swengg,dc=min,dc=io"
userReq := madmin.PolicyAssociationReq{
Policies: []string{policy},
User: userDN,
}
if _, err := s.adm.AttachPolicyLDAP(ctx, userReq); err != nil {
c.Fatalf("Unable to attach policy: %v", err)
}
newSSHCon := newSSHConnMock("dillon=ldap")
_, err = sshPasswordAuth(newSSHCon, []byte("dillon"))
if err != nil {
c.Fatal("Password authentication failed for user (dillon):", err)
}
newSSHCon := newSSHConnMock("dillon=ldap")
_, err = sshPasswordAuth(newSSHCon, []byte("dillon"))
if err != nil {
c.Fatal("Password authentication failed for user (dillon):", err)
}
newSSHCon = newSSHConnMock("dillon")
_, err = sshPasswordAuth(newSSHCon, []byte("dillon"))
if err != nil {
c.Fatal("Password authentication failed for user (dillon):", err)
newSSHCon = newSSHConnMock("dillon")
_, err = sshPasswordAuth(newSSHCon, []byte("dillon"))
if err != nil {
c.Fatal("Password authentication failed for user (dillon):", err)
}
}
{
userDN := "uid=fahim,ou=people,ou=swengg,dc=min,dc=io"
userReq := madmin.PolicyAssociationReq{
Policies: []string{policy},
User: userDN,
}
if _, err := s.adm.AttachPolicyLDAP(ctx, userReq); err != nil {
c.Fatalf("Unable to attach policy: %v", err)
}
newSSHCon := newSSHConnMock("fahim=ldap")
_, err = sshPasswordAuth(newSSHCon, []byte("fahim"))
if err != nil {
c.Fatal("Password authentication failed for user (fahim):", err)
}
newSSHCon = newSSHConnMock("fahim")
_, err = sshPasswordAuth(newSSHCon, []byte("fahim"))
if err != nil {
c.Fatal("Password authentication failed for user (fahim):", err)
}
}
}
-1
View File
@@ -159,7 +159,6 @@ func TestDoesPresignedV2SignatureMatch(t *testing.T) {
t.Errorf("(%d) expected to get success, instead got %s", i, niceError(errCode))
}
}
}
}
-4
View File
@@ -298,7 +298,6 @@ func TestParseSignature(t *testing.T) {
t.Errorf("Test %d: Expected the result to be \"%s\", but got \"%s\". ", i+1, testCase.expectedSignStr, actualSignStr)
}
}
}
}
@@ -343,7 +342,6 @@ func TestParseSignedHeaders(t *testing.T) {
t.Errorf("Test %d: Expected the result to be \"%v\", but got \"%v\". ", i+1, testCase.expectedSignedHeaders, actualSignedHeaders)
}
}
}
}
@@ -514,7 +512,6 @@ func TestParseSignV4(t *testing.T) {
t.Errorf("Test %d: Expected the result to be \"%v\", but got \"%v\". ", i+1, testCase.expectedAuthField, parsedAuthField.SignedHeaders)
}
}
}
}
@@ -880,6 +877,5 @@ func TestParsePreSignV4(t *testing.T) {
t.Errorf("Test %d: Expected date to be %v, but got %v", i+1, testCase.expectedPreSignValues.Date.UTC().Format(iso8601Format), parsedPreSign.Date.UTC().Format(iso8601Format))
}
}
}
}
+5 -8
View File
@@ -1037,7 +1037,6 @@ func (c *SiteReplicationSys) PeerBucketConfigureReplHandler(ctx context.Context,
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketTargetsFile, tgtBytes); err != nil {
return wrapSRErr(err)
}
}
// no replication rule for this peer or target ARN missing in bucket targets
if targetARN == "" {
@@ -1406,7 +1405,6 @@ func (c *SiteReplicationSys) PeerSvcAccChangeHandler(ctx context.Context, change
if err := globalIAMSys.DeleteServiceAccount(ctx, change.Delete.AccessKey, true); err != nil {
return wrapSRErr(err)
}
}
return nil
@@ -1430,8 +1428,8 @@ func (c *SiteReplicationSys) PeerPolicyMappingHandler(ctx context.Context, mappi
userType := IAMUserType(mapping.UserType)
isGroup := mapping.IsGroup
entityName := mapping.UserOrGroup
if globalIAMSys.GetUsersSysType() == LDAPUsersSysType && userType == stsUser {
if globalIAMSys.GetUsersSysType() == LDAPUsersSysType && userType == stsUser {
// Validate that the user or group exists in LDAP and use the normalized
// form of the entityName (which will be an LDAP DN).
var err error
@@ -1441,7 +1439,7 @@ func (c *SiteReplicationSys) PeerPolicyMappingHandler(ctx context.Context, mappi
if foundGroupDN, underBaseDN, err = globalIAMSys.LDAPConfig.GetValidatedGroupDN(nil, entityName); err != nil {
iamLogIf(ctx, err)
} else if foundGroupDN == nil || !underBaseDN {
err = errNoSuchGroup
return wrapSRErr(errNoSuchGroup)
}
entityName = foundGroupDN.NormDN
} else {
@@ -1449,7 +1447,7 @@ func (c *SiteReplicationSys) PeerPolicyMappingHandler(ctx context.Context, mappi
if foundUserDN, err = globalIAMSys.LDAPConfig.GetValidatedDNForUsername(entityName); err != nil {
iamLogIf(ctx, err)
} else if foundUserDN == nil {
err = errNoSuchUser
return wrapSRErr(errNoSuchUser)
}
entityName = foundUserDN.NormDN
}
@@ -2154,7 +2152,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context, addOpts madmin.
SecretKey: acc.Credentials.SecretKey,
Groups: acc.Credentials.Groups,
Claims: claims,
SessionPolicy: json.RawMessage(policyJSON),
SessionPolicy: policyJSON,
Status: acc.Credentials.Status,
Name: acc.Credentials.Name,
Description: acc.Credentials.Description,
@@ -3062,7 +3060,6 @@ func (c *SiteReplicationSys) siteReplicationStatus(ctx context.Context, objAPI O
sum.ReplicatedGroupPolicyMappings++
info.StatsSummary[ps.DeploymentID] = sum
}
}
}
@@ -5552,7 +5549,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
SecretKey: creds.SecretKey,
Groups: creds.Groups,
Claims: claims,
SessionPolicy: json.RawMessage(policyJSON),
SessionPolicy: policyJSON,
Status: creds.Status,
Name: creds.Name,
Description: creds.Description,
+3
View File
@@ -111,6 +111,9 @@ type VolInfo struct {
// total VolInfo counts
count int
// Date and time when the volume was deleted, if Deleted
Deleted time.Time
}
// FilesInfo represent a list of files, additionally
+50 -18
View File
@@ -6504,8 +6504,8 @@ func (z *VolInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err)
return
}
if zb0001 != 2 {
err = msgp.ArrayError{Wanted: 2, Got: zb0001}
if zb0001 != 3 {
err = msgp.ArrayError{Wanted: 3, Got: zb0001}
return
}
z.Name, err = dc.ReadString()
@@ -6518,13 +6518,18 @@ func (z *VolInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Created")
return
}
z.Deleted, err = dc.ReadTime()
if err != nil {
err = msgp.WrapError(err, "Deleted")
return
}
return
}
// EncodeMsg implements msgp.Encodable
func (z VolInfo) EncodeMsg(en *msgp.Writer) (err error) {
// array header, size 2
err = en.Append(0x92)
// array header, size 3
err = en.Append(0x93)
if err != nil {
return
}
@@ -6538,16 +6543,22 @@ func (z VolInfo) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "Created")
return
}
err = en.WriteTime(z.Deleted)
if err != nil {
err = msgp.WrapError(err, "Deleted")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z VolInfo) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// array header, size 2
o = append(o, 0x92)
// array header, size 3
o = append(o, 0x93)
o = msgp.AppendString(o, z.Name)
o = msgp.AppendTime(o, z.Created)
o = msgp.AppendTime(o, z.Deleted)
return
}
@@ -6559,8 +6570,8 @@ func (z *VolInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err)
return
}
if zb0001 != 2 {
err = msgp.ArrayError{Wanted: 2, Got: zb0001}
if zb0001 != 3 {
err = msgp.ArrayError{Wanted: 3, Got: zb0001}
return
}
z.Name, bts, err = msgp.ReadStringBytes(bts)
@@ -6573,13 +6584,18 @@ func (z *VolInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Created")
return
}
z.Deleted, bts, err = msgp.ReadTimeBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Deleted")
return
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z VolInfo) Msgsize() (s int) {
s = 1 + msgp.StringPrefixSize + len(z.Name) + msgp.TimeSize
s = 1 + msgp.StringPrefixSize + len(z.Name) + msgp.TimeSize + msgp.TimeSize
return
}
@@ -6603,8 +6619,8 @@ func (z *VolsInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, zb0001)
return
}
if zb0003 != 2 {
err = msgp.ArrayError{Wanted: 2, Got: zb0003}
if zb0003 != 3 {
err = msgp.ArrayError{Wanted: 3, Got: zb0003}
return
}
(*z)[zb0001].Name, err = dc.ReadString()
@@ -6617,6 +6633,11 @@ func (z *VolsInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, zb0001, "Created")
return
}
(*z)[zb0001].Deleted, err = dc.ReadTime()
if err != nil {
err = msgp.WrapError(err, zb0001, "Deleted")
return
}
}
return
}
@@ -6629,8 +6650,8 @@ func (z VolsInfo) EncodeMsg(en *msgp.Writer) (err error) {
return
}
for zb0004 := range z {
// array header, size 2
err = en.Append(0x92)
// array header, size 3
err = en.Append(0x93)
if err != nil {
return
}
@@ -6644,6 +6665,11 @@ func (z VolsInfo) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, zb0004, "Created")
return
}
err = en.WriteTime(z[zb0004].Deleted)
if err != nil {
err = msgp.WrapError(err, zb0004, "Deleted")
return
}
}
return
}
@@ -6653,10 +6679,11 @@ func (z VolsInfo) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
o = msgp.AppendArrayHeader(o, uint32(len(z)))
for zb0004 := range z {
// array header, size 2
o = append(o, 0x92)
// array header, size 3
o = append(o, 0x93)
o = msgp.AppendString(o, z[zb0004].Name)
o = msgp.AppendTime(o, z[zb0004].Created)
o = msgp.AppendTime(o, z[zb0004].Deleted)
}
return
}
@@ -6681,8 +6708,8 @@ func (z *VolsInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, zb0001)
return
}
if zb0003 != 2 {
err = msgp.ArrayError{Wanted: 2, Got: zb0003}
if zb0003 != 3 {
err = msgp.ArrayError{Wanted: 3, Got: zb0003}
return
}
(*z)[zb0001].Name, bts, err = msgp.ReadStringBytes(bts)
@@ -6695,6 +6722,11 @@ func (z *VolsInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, zb0001, "Created")
return
}
(*z)[zb0001].Deleted, bts, err = msgp.ReadTimeBytes(bts)
if err != nil {
err = msgp.WrapError(err, zb0001, "Deleted")
return
}
}
o = bts
return
@@ -6704,7 +6736,7 @@ func (z *VolsInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
func (z VolsInfo) Msgsize() (s int) {
s = msgp.ArrayHeaderSize
for zb0004 := range z {
s += 1 + msgp.StringPrefixSize + len(z[zb0004].Name) + msgp.TimeSize
s += 1 + msgp.StringPrefixSize + len(z[zb0004].Name) + msgp.TimeSize + msgp.TimeSize
}
return
}
+3 -3
View File
@@ -29,11 +29,11 @@ import (
"path"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/bpool"
"github.com/minio/minio/internal/cachevalue"
"github.com/minio/minio/internal/grid"
xhttp "github.com/minio/minio/internal/http"
@@ -508,13 +508,13 @@ func (client *storageRESTClient) RenameData(ctx context.Context, srcVolume, srcP
}
// where we keep old *Readers
var readMsgpReaderPool = sync.Pool{New: func() interface{} { return &msgp.Reader{} }}
var readMsgpReaderPool = bpool.Pool[*msgp.Reader]{New: func() *msgp.Reader { return &msgp.Reader{} }}
// mspNewReader returns a *Reader that reads from the provided reader.
// The reader will be buffered.
// Return with readMsgpReaderPoolPut when done.
func msgpNewReader(r io.Reader) *msgp.Reader {
p := readMsgpReaderPool.Get().(*msgp.Reader)
p := readMsgpReaderPool.Get()
if p.R == nil {
p.R = xbufio.NewReaderSize(r, 32<<10)
} else {
+9 -15
View File
@@ -34,6 +34,7 @@ import (
"sync"
"time"
"github.com/minio/minio/internal/bpool"
"github.com/minio/minio/internal/grid"
"github.com/tinylib/msgp/msgp"
@@ -831,7 +832,7 @@ func keepHTTPReqResponseAlive(w http.ResponseWriter, r *http.Request) (resp func
// Response not ready, write a filler byte.
write([]byte{32})
if canWrite {
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
case err := <-doneCh:
if err != nil {
@@ -905,7 +906,7 @@ func keepHTTPResponseAlive(w http.ResponseWriter) func(error) {
// Response not ready, write a filler byte.
write([]byte{32})
if canWrite {
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
case err := <-doneCh:
if err != nil {
@@ -1025,7 +1026,7 @@ func streamHTTPResponse(w http.ResponseWriter) *httpStreamResponse {
// Response not ready, write a filler byte.
write([]byte{32})
if canWrite {
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
case err := <-doneCh:
if err != nil {
@@ -1043,7 +1044,7 @@ func streamHTTPResponse(w http.ResponseWriter) *httpStreamResponse {
write(tmp[:])
write(block)
if canWrite {
w.(http.Flusher).Flush()
xhttp.Flush(w)
}
}
}
@@ -1051,29 +1052,23 @@ func streamHTTPResponse(w http.ResponseWriter) *httpStreamResponse {
return &h
}
var poolBuf8k = sync.Pool{
New: func() interface{} {
var poolBuf8k = bpool.Pool[*[]byte]{
New: func() *[]byte {
b := make([]byte, 8192)
return &b
},
}
var poolBuf128k = sync.Pool{
New: func() interface{} {
b := make([]byte, 128<<10)
return b
},
}
// waitForHTTPStream will wait for responses where
// streamHTTPResponse has been used.
// The returned reader contains the payload and must be closed if no error is returned.
func waitForHTTPStream(respBody io.ReadCloser, w io.Writer) error {
var tmp [1]byte
// 8K copy buffer, reused for less allocs...
bufp := poolBuf8k.Get().(*[]byte)
bufp := poolBuf8k.Get()
buf := *bufp
defer poolBuf8k.Put(bufp)
for {
_, err := io.ReadFull(respBody, tmp[:])
if err != nil {
@@ -1438,7 +1433,6 @@ func registerStorageRESTHandlers(router *mux.Router, endpointServerPools Endpoin
}
}
}(endpoint)
}
}
}
+6
View File
@@ -81,6 +81,7 @@ const (
ErrSTSMalformedPolicyDocument
ErrSTSInsecureConnection
ErrSTSInvalidClientCertificate
ErrSTSTooManyIntermediateCAs
ErrSTSNotInitialized
ErrSTSIAMNotInitialized
ErrSTSUpstreamError
@@ -145,6 +146,11 @@ var stsErrCodes = stsErrorCodeMap{
Description: "The provided client certificate is invalid. Retry with a different certificate.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrSTSTooManyIntermediateCAs: {
Code: "TooManyIntermediateCAs",
Description: "The provided client certificate contains too many intermediate CA certificates",
HTTPStatusCode: http.StatusBadRequest,
},
ErrSTSNotInitialized: {
Code: "STSNotInitialized",
Description: "STS API not initialized, please try again.",
+19 -4
View File
@@ -780,12 +780,26 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h
// policy mapping would be ambiguous.
// However, we can filter all CA certificates and only check
// whether they client has sent exactly one (non-CA) leaf certificate.
peerCertificates := make([]*x509.Certificate, 0, len(r.TLS.PeerCertificates))
const MaxIntermediateCAs = 10
var (
peerCertificates = make([]*x509.Certificate, 0, len(r.TLS.PeerCertificates))
intermediates *x509.CertPool
numIntermediates int
)
for _, cert := range r.TLS.PeerCertificates {
if cert.IsCA {
continue
numIntermediates++
if numIntermediates > MaxIntermediateCAs {
writeSTSErrorResponse(ctx, w, ErrSTSTooManyIntermediateCAs, fmt.Errorf("client certificate contains more than %d intermediate CAs", MaxIntermediateCAs))
return
}
if intermediates == nil {
intermediates = x509.NewCertPool()
}
intermediates.AddCert(cert)
} else {
peerCertificates = append(peerCertificates, cert)
}
peerCertificates = append(peerCertificates, cert)
}
r.TLS.PeerCertificates = peerCertificates
@@ -806,7 +820,8 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h
KeyUsages: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
},
Roots: globalRootCAs,
Intermediates: intermediates,
Roots: globalRootCAs,
})
if err != nil {
writeSTSErrorResponse(ctx, w, ErrSTSInvalidClientCertificate, err)
+7 -6
View File
@@ -18,15 +18,16 @@ func _() {
_ = x[ErrSTSMalformedPolicyDocument-7]
_ = x[ErrSTSInsecureConnection-8]
_ = x[ErrSTSInvalidClientCertificate-9]
_ = x[ErrSTSNotInitialized-10]
_ = x[ErrSTSIAMNotInitialized-11]
_ = x[ErrSTSUpstreamError-12]
_ = x[ErrSTSInternalError-13]
_ = x[ErrSTSTooManyIntermediateCAs-10]
_ = x[ErrSTSNotInitialized-11]
_ = x[ErrSTSIAMNotInitialized-12]
_ = x[ErrSTSUpstreamError-13]
_ = x[ErrSTSInternalError-14]
}
const _STSErrorCode_name = "STSNoneSTSAccessDeniedSTSMissingParameterSTSInvalidParameterValueSTSWebIdentityExpiredTokenSTSClientGrantsExpiredTokenSTSInvalidClientGrantsTokenSTSMalformedPolicyDocumentSTSInsecureConnectionSTSInvalidClientCertificateSTSNotInitializedSTSIAMNotInitializedSTSUpstreamErrorSTSInternalError"
const _STSErrorCode_name = "STSNoneSTSAccessDeniedSTSMissingParameterSTSInvalidParameterValueSTSWebIdentityExpiredTokenSTSClientGrantsExpiredTokenSTSInvalidClientGrantsTokenSTSMalformedPolicyDocumentSTSInsecureConnectionSTSInvalidClientCertificateSTSTooManyIntermediateCAsSTSNotInitializedSTSIAMNotInitializedSTSUpstreamErrorSTSInternalError"
var _STSErrorCode_index = [...]uint16{0, 7, 22, 41, 65, 91, 118, 145, 171, 192, 219, 236, 256, 272, 288}
var _STSErrorCode_index = [...]uint16{0, 7, 22, 41, 65, 91, 118, 145, 171, 192, 219, 244, 261, 281, 297, 313}
func (i STSErrorCode) String() string {
if i < 0 || i >= STSErrorCode(len(_STSErrorCode_index)-1) {
-1
View File
@@ -794,7 +794,6 @@ func assembleStreamingChunks(req *http.Request, body io.ReadSeeker, chunkSize in
if n <= 0 {
break
}
}
req.Body = io.NopCloser(bytes.NewReader(stream))
return req, nil
+5 -9
View File
@@ -36,6 +36,7 @@ import (
"github.com/klauspost/compress/s2"
"github.com/klauspost/compress/zstd"
gzip "github.com/klauspost/pgzip"
xioutil "github.com/minio/minio/internal/ioutil"
"github.com/pierrec/lz4/v4"
)
@@ -182,7 +183,6 @@ func untar(ctx context.Context, r io.Reader, putObject func(reader io.Reader, in
header, err := tarReader.Next()
switch {
// if no more files are found return
case err == io.EOF:
wg.Wait()
@@ -226,13 +226,10 @@ func untar(ctx context.Context, r io.Reader, putObject func(reader io.Reader, in
// Do small files async
n++
if header.Size <= smallFileThreshold {
if header.Size <= xioutil.MediumBlock {
asyncWriters <- struct{}{}
b := poolBuf128k.Get().([]byte)
if cap(b) < int(header.Size) {
b = make([]byte, smallFileThreshold)
}
b = b[:header.Size]
bufp := xioutil.ODirectPoolMedium.Get()
b := (*bufp)[:header.Size]
if _, err := io.ReadFull(tarReader, b); err != nil {
return err
}
@@ -243,8 +240,7 @@ func untar(ctx context.Context, r io.Reader, putObject func(reader io.Reader, in
rc.Close()
<-asyncWriters
wg.Done()
//nolint:staticcheck // SA6002 we are fine with the tiny alloc
poolBuf128k.Put(b)
xioutil.ODirectPoolMedium.Put(bufp)
}()
if err := putObject(&rc, fi, name); err != nil {
if o.ignoreErrs {
+1 -1
View File
@@ -210,7 +210,7 @@ func TestIsKubernetes(t *testing.T) {
// Tests if the environment we are running is Helm chart.
func TestGetHelmVersion(t *testing.T) {
createTempFile := func(content string) string {
tmpfile, err := os.CreateTemp("", "helm-testfile-")
tmpfile, err := os.CreateTemp(t.TempDir(), "helm-testfile-")
if err != nil {
t.Fatalf("Unable to create temporary file. %s", err)
}
+4 -2
View File
@@ -271,10 +271,12 @@ func validateLengthAndChecksum(r *http.Request) bool {
if err != nil {
return false
}
if !cs.Type.IsSet() {
if cs == nil || !cs.Type.IsSet() {
return false
}
r.Body = hash.NewChecker(r.Body, cs.Type.Hasher(), cs.Raw, r.ContentLength)
if cs.Valid() && !cs.Type.Trailing() {
r.Body = hash.NewChecker(r.Body, cs.Type.Hasher(), cs.Raw, r.ContentLength)
}
return true
}
+3 -5
View File
@@ -26,12 +26,12 @@ import (
"io"
"sort"
"strings"
"sync"
"time"
"github.com/cespare/xxhash/v2"
"github.com/google/uuid"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/internal/bpool"
"github.com/minio/minio/internal/bucket/lifecycle"
"github.com/minio/minio/internal/bucket/replication"
"github.com/minio/minio/internal/config/storageclass"
@@ -703,18 +703,17 @@ func (j xlMetaV2Object) ToFileInfo(volume, path string, allParts bool) (FileInfo
const metaDataReadDefault = 4 << 10
// Return used metadata byte slices here.
var metaDataPool = sync.Pool{New: func() interface{} { return make([]byte, 0, metaDataReadDefault) }}
var metaDataPool = bpool.Pool[[]byte]{New: func() []byte { return make([]byte, 0, metaDataReadDefault) }}
// metaDataPoolGet will return a byte slice with capacity at least metaDataReadDefault.
// It will be length 0.
func metaDataPoolGet() []byte {
return metaDataPool.Get().([]byte)[:0]
return metaDataPool.Get()[:0]
}
// metaDataPoolPut will put an unused small buffer back into the pool.
func metaDataPoolPut(buf []byte) {
if cap(buf) >= metaDataReadDefault && cap(buf) < metaDataReadDefault*4 {
//nolint:staticcheck // SA6002 we are fine with the tiny alloc
metaDataPool.Put(buf)
}
}
@@ -1982,7 +1981,6 @@ func mergeXLV2Versions(quorum int, strict bool, requestedVersions int, versions
if !latest.header.FreeVersion() {
nVersions++
}
} else {
// Find latest.
var latestCount int
-1
View File
@@ -225,7 +225,6 @@ func compareXLMetaV1(t *testing.T, unMarshalXLMeta, jsoniterXLMeta xlMetaV1Objec
if val != jsoniterVal {
t.Errorf("Expected the value for Meta data key \"%s\" to be \"%s\", but got \"%s\".", key, val, jsoniterVal)
}
}
}
+2 -2
View File
@@ -2166,10 +2166,10 @@ func (s *xlStorage) writeAllDirect(ctx context.Context, filePath string, fileSiz
var bufp *[]byte
switch {
case fileSize <= xioutil.SmallBlock:
bufp = xioutil.ODirectPoolSmall.Get().(*[]byte)
bufp = xioutil.ODirectPoolSmall.Get()
defer xioutil.ODirectPoolSmall.Put(bufp)
default:
bufp = xioutil.ODirectPoolLarge.Get().(*[]byte)
bufp = xioutil.ODirectPoolLarge.Get()
defer xioutil.ODirectPoolLarge.Put(bufp)
}
-8
View File
@@ -32,12 +32,4 @@ docker buildx build --push --no-cache \
docker buildx prune -f
docker buildx build --push --no-cache \
--build-arg RELEASE="${release}" \
-t "minio/minio:${release}.fips" \
-t "quay.io/minio/minio:${release}.fips" \
--platform=linux/amd64 -f Dockerfile.release.fips .
docker buildx prune -f
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
+1 -1
View File
@@ -10,7 +10,7 @@ Replication relies on immutability provided by versioning to sync objects betwee
If an object meets replication rules as set in the replication configuration, `X-Amz-Replication-Status` is first set to `PENDING` as the PUT operation completes and replication is queued (unless synchronous replication is in place). After replication is performed, the metadata on the source object version changes to `COMPLETED` or `FAILED` depending on whether replication succeeded. The object version on the target shows `X-Amz-Replication-Status` of `REPLICA`
All replication failures are picked up by the scanner which runs at a one minute frequency, each time scanning upto a sixteenth of the namespace. Object versions marked `PENDING` or `FAILED` are re-queued for replication.
All replication failures are picked up by the scanner which runs at a one minute frequency, each time scanning up to a sixteenth of the namespace. Object versions marked `PENDING` or `FAILED` are re-queued for replication.
Replication speed depends on the cluster load, number of objects in the object store as well as storage speed. In addition, any bandwidth limits set via `mc admin bucket remote add` could also contribute to replication speed. The number of workers used for replication defaults to 100. Based on network bandwidth and system load, the number of workers used in replication can be configured using `mc admin config set alias api` to set the `replication_workers`. The prometheus metrics exposed by MinIO can be used to plan resource allocation and bandwidth management to optimize replication speed.
+1 -1
View File
@@ -8,7 +8,7 @@ To replicate objects in a bucket to a destination bucket on a target site either
## Highlights
- Supports source and destination buckets to have the same name unlike AWS S3, addresses variety of usecases such as *Splunk*, *Veeam* site to site DR.
- Supports source and destination buckets to have the same name unlike AWS S3, addresses variety of use-cases such as *Splunk*, *Veeam* site to site DR.
- Supports object locking/retention across source and destination buckets natively out of the box, unlike AWS S3.
- Simpler implementation than [AWS S3 Bucket Replication Config](https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-add-config.html) with requirements such as IAM Role, AccessControlTranslation, Metrics and SourceSelectionCriteria are not needed with MinIO.
- Active-Active replication
+1 -1
View File
@@ -1,4 +1,4 @@
# Object Lock and Immutablity Guide [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io)
# Object Lock and Immutability Guide [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io)
MinIO server allows WORM for specific objects or by configuring a bucket with default object lock configuration that applies default retention mode and retention duration to all objects. This makes objects in the bucket immutable i.e. delete of the version are not allowed until an expiry specified in the bucket's object lock configuration or object retention.
+2 -2
View File
@@ -79,7 +79,7 @@ Duplicate delete markers are not created on MinIO buckets with versioning, if an
### Motivation
**PLEASE READ: This feature is meant for advanced usecases only where the setup is using bucket versioning or with replicated buckets, use this feature to optimize versioning behavior for some specific applications. MinIO experts will evaluate and guide on the benefits for your application, please reach out to us on <https://subnet.min.io>.**
**PLEASE READ: This feature is meant for advanced use-cases only where the setup is using bucket versioning or with replicated buckets, use this feature to optimize versioning behavior for some specific applications. MinIO experts will evaluate and guide on the benefits for your application, please reach out to us on <https://subnet.min.io>.**
Spark/Hadoop workloads which use Hadoop MR Committer v1/v2 algorithm upload objects to a temporary prefix in a bucket. These objects are 'renamed' to a different prefix on Job commit. Object storage admins are forced to configure separate ILM policies to expire these objects and their versions to reclaim space.
@@ -212,6 +212,6 @@ public class IsVersioningEnabled {
## Explore Further
- [Use `minio-java` SDK with MinIO Server](https://min.io/docs/minio/linux/developers/java/minio-java.html)
- [Object Lock and Immutablity Guide](https://min.io/docs/minio/linux/administration/object-management/object-retention.html)
- [Object Lock and Immutability Guide](https://min.io/docs/minio/linux/administration/object-management/object-retention.html)
- [MinIO Admin Complete Guide](https://min.io/docs/minio/linux/reference/minio-mc-admin.html)
- [The MinIO documentation website](https://min.io/docs/minio/linux/index.html)
+1 -1
View File
@@ -2,7 +2,7 @@
## HTTP Trace
HTTP tracing can be enabled by using [`mc admin trace`](https://github.com/minio/mc/blob/master/docs/minio-admin-complete-guide.md#command-trace---display-minio-server-http-trace) command.
HTTP tracing can be enabled by using [`mc admin trace`](https://min.io/docs/minio/linux/reference/minio-mc-admin/mc-admin-trace.html) command.
Example:
+26 -27
View File
@@ -22,6 +22,9 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"unicode/utf8"
"github.com/minio/madmin-go/v3/estream"
)
@@ -30,18 +33,18 @@ type keepFileErr struct {
error
}
func extractInspectV2(pk []byte, r io.Reader, w io.Writer, okMsg string) error {
privKey, err := bytesToPrivateKey(pk)
if err != nil {
return fmt.Errorf("decoding key returned: %w", err)
}
func extractInspectV2(pks [][]byte, r io.Reader, extractDir string) error {
sr, err := estream.NewReader(r)
if err != nil {
return err
}
sr.SetPrivateKey(privKey)
for _, pk := range pks {
privKey, err := bytesToPrivateKey(pk)
if err != nil {
return fmt.Errorf("decoding key returned: %w", err)
}
sr.SetPrivateKey(privKey)
}
sr.ReturnNonDecryptable(true)
// Debug corrupted streams.
@@ -60,33 +63,29 @@ func extractInspectV2(pk []byte, r io.Reader, w io.Writer, okMsg string) error {
return errors.New("no data found on stream")
}
if errors.Is(err, estream.ErrNoKey) {
if stream.Name == "inspect.zip" {
return errors.New("incorrect private key")
}
fmt.Println("Skipping", stream.Name, "no private key")
if err := stream.Skip(); err != nil {
return fmt.Errorf("stream skip: %w", err)
}
continue
}
if extracted {
return keepFileErr{fmt.Errorf("next stream: %w", err)}
}
return fmt.Errorf("next stream: %w", err)
}
if stream.Name == "inspect.zip" {
if extracted {
return keepFileErr{errors.New("multiple inspect.zip streams found")}
}
_, err := io.Copy(w, stream)
if err != nil {
return fmt.Errorf("reading inspect stream: %w", err)
}
fmt.Println(okMsg)
extracted = true
continue
if strings.Contains(stream.Name, "..") || !utf8.ValidString(stream.Name) {
return fmt.Errorf("invalid stream name: %q", stream.Name)
}
if err := stream.Skip(); err != nil {
return fmt.Errorf("stream skip: %w", err)
dst := filepath.Join(extractDir, stream.Name)
os.Mkdir(extractDir, 0o755)
w, err := os.Create(dst)
if err != nil {
return fmt.Errorf("creating output file: %w", err)
}
_, err = io.Copy(w, stream)
if err != nil {
return fmt.Errorf("reading inspect stream: %w", err)
}
fmt.Printf("Extracted: %s\n", dst)
extracted = true
}
}
+44 -23
View File
@@ -51,15 +51,34 @@ func main() {
generateKeys()
os.Exit(0)
}
var privateKey []byte
var privateKeys [][]byte
if *keyHex == "" {
if b, err := os.ReadFile(*privKeyPath); err == nil {
privateKey = b
fmt.Println("Using private key from", *privKeyPath)
// Attempt to load private key(s)
n := 1
var base, ext string
base = *privKeyPath
if idx := strings.LastIndexByte(base, '.'); idx != -1 {
ext = base[idx:]
base = base[:idx]
}
for {
// Automatically read "file.ext", "file-2.ext", "file-3.ext"...
fn := base + ext
if n > 1 {
fn = fmt.Sprintf("%s-%d%s", base, n, ext)
}
if b, err := os.ReadFile(fn); err == nil {
privateKeys = append(privateKeys, b)
fmt.Println("Added private key from", fn)
} else {
break
}
n++
}
// Prompt for decryption key if no --key or --private-key are provided
if len(privateKey) == 0 && !*stdin {
if len(privateKeys) == 0 && !*stdin {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter Decryption Key: ")
@@ -91,17 +110,20 @@ func main() {
var err error
inputs, err = filepathx.Glob(flag.Args()[0])
fatalErr(err)
if len(inputs) == 0 {
fmt.Println("Usage: No input found")
}
default:
flag.Usage()
fatalIf(true, "Only 1 file can be decrypted")
os.Exit(1)
}
for _, input := range inputs {
processFile(input, privateKey)
processFile(input, privateKeys)
}
}
func processFile(inputFileName string, privateKey []byte) {
func processFile(inputFileName string, privateKeys [][]byte) {
// Calculate the output file name
var outputFileName string
switch {
@@ -115,32 +137,31 @@ func processFile(inputFileName string, privateKey []byte) {
outputFileName = inputFileName + ".decrypted"
}
// Backup any already existing output file
_, err := os.Stat(outputFileName)
if err == nil {
err := os.Rename(outputFileName, outputFileName+"."+time.Now().Format("20060102150405"))
if err != nil {
fatalErr(err)
}
}
// Open the input and create the output file
input, err := os.Open(inputFileName)
fatalErr(err)
defer input.Close()
output, err := os.Create(outputFileName)
fatalErr(err)
// Decrypt the inspect data
msg := fmt.Sprintf("output written to %s", outputFileName)
switch {
case *keyHex != "":
// Backup any already existing output file
_, err := os.Stat(outputFileName)
if err == nil {
err := os.Rename(outputFileName, outputFileName+"."+time.Now().Format("20060102150405"))
if err != nil {
fatalErr(err)
}
}
output, err := os.Create(outputFileName)
fatalErr(err)
msg := fmt.Sprintf("output written to %s", outputFileName)
err = extractInspectV1(*keyHex, input, output, msg)
case len(privateKey) != 0:
err = extractInspectV2(privateKey, input, output, msg)
output.Close()
case len(privateKeys) != 0:
outputFileName := strings.TrimSuffix(outputFileName, ".zip")
err = extractInspectV2(privateKeys, input, outputFileName)
}
output.Close()
if err != nil {
var keep keepFileErr
+2 -2
View File
@@ -369,8 +369,8 @@ FLAGS:
defer f.Close()
r = f
}
if strings.HasSuffix(file, ".zip") {
zr, err := zip.NewReader(r.(io.ReaderAt), sz)
if ra, ok := r.(io.ReaderAt); ok && strings.HasSuffix(file, ".zip") {
zr, err := zip.NewReader(ra, sz)
if err != nil {
return err
}
+1 -1
View File
@@ -41,7 +41,7 @@ Expansion of ellipses and choice of erasure sets based on this expansion is an a
- Erasure coding used by MinIO is [Reed-Solomon](https://github.com/klauspost/reedsolomon) erasure coding scheme, which has a total shard maximum of 256 i.e 128 data and 128 parity. MinIO design goes beyond this limitation by doing some practical architecture choices.
- Erasure set is a single erasure coding unit within a MinIO deployment. An object is sharded within an erasure set. Erasure set size is automatically calculated based on the number of drives. MinIO supports unlimited number of drives but each erasure set can be upto 16 drives and a minimum of 2 drives.
- Erasure set is a single erasure coding unit within a MinIO deployment. An object is sharded within an erasure set. Erasure set size is automatically calculated based on the number of drives. MinIO supports unlimited number of drives but each erasure set can be up to 16 drives and a minimum of 2 drives.
- We limited the number of drives to 16 for erasure set because, erasure code shards more than 16 can become chatty and do not have any performance advantages. Additionally since 16 drive erasure set gives you tolerance of 8 drives per object by default which is plenty in any practical scenario.

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