Compare commits

...

36 Commits

Author SHA1 Message Date
jiuker f56a182b71 fix: close http body when webhook send (#18487) 2023-11-20 14:40:07 -08:00
Shubhendu 317b40ef90 Fixed broken docs link (#18486)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2023-11-20 12:04:49 -08:00
Shubhendu e938ece492 Added guidelines for setting prometheus alerts (#18479)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2023-11-19 10:16:08 -08:00
Anis Eleuch 02331a612c batch-repl: Replicate missing metadata and standard headers (#18484)
- Replicate Expires when the source is local or remote
- Replicate metadata when the source is remote
2023-11-18 19:12:44 -08:00
Anis Eleuch 8317557f70 decom: Fix listing quorum to be equal to deletion quorum (#18476)
With an odd number of drives per erasure set setup, the write/quorum is
the half + 1; however the decommissioning listing will still list those
objects and does not consider those as stale.

Fix it by using (N+1)/2 formula.

Co-authored-by: Anis Elleuch <anis@min.io>
2023-11-17 21:09:09 -08:00
Anis Eleuch 1bb7a2a295 Immediate transition ILM to avoid quick deferring to the scanner (#18475)
Immediate transition use case and is mostly used to fill warm
backend with a lot of data when a new deployment is created

Currently, if the transition queue is complete, the transition will be
deferred to the scanner; change this behavior by blocking the PUT request
until the transition queue has a new place for a transition task.
2023-11-17 16:16:46 -08:00
jiuker 215ca58d6a fix: close the http.Body when WebhookTarget isActive (#18467) 2023-11-17 12:02:26 -08:00
Anis Eleuch 12f570a307 audit: Try to send audit even if the status is offline (#18458)
Currently, once the audit becomes offline, there is no code that tries
to reconnect to the audit, at the same time Send() quickly returns with
an error without really trying to send a message the audit endpoint; so
the audit endpoint will never be online again.

Fixing this behavior; the current downside is that we miss printing some
logs when the audit becomes offline; however this information is
available in prometheus

Later, we can refactor internal/logger so the http endpoint can send errors to
console target.
2023-11-17 10:40:28 -08:00
Shubhendu e4b619ce1a Added graph for Erasure Set Tolerance value (#18472)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2023-11-17 10:38:15 -08:00
Harshavardhana 0a286153bb remove checking for BucketInfo() peer call for every PUT() (#18464)
we already validate if the bucket doesn't exist in RenameData()
which can handle this cleanly, instead of making a network call
and returning errors.
2023-11-17 05:29:50 -08:00
Anis Eleuch 22d59e757d Remove stale data in HEAD/GET object (#18460)
Currently if the object does not exist in quorum disks of an erasure
set, the dangling code is never called because the returned error will
be errFileNotFound or errFileVersionNotFound;

With this commit, when errFileNotFound or errFileVersionNotFound is
returning when trying to calculate the quorum of a given object, the
code checks if a disk returned nil, which means a stale object exists in
that disk, that will trigger deleteIfDangling() function
2023-11-16 08:39:53 -08:00
Andreas Auernhammer 0daa2dbf59 health: split liveness and readiness handler (#18457)
This commit splits the liveness and readiness
handler into two separate handlers. In K8S, a
liveness probe is used to determine whether the
pod is in "live" state and functioning at all.
In contrast, the readiness probe is used to
determine whether the pod is ready to serve
requests.

A failing liveness probe causes pod restarts while
a failing readiness probe causes k8s to stop routing
traffic to the pod. Hence, a liveness probe should
be as robust as possible while a readiness probe
should be used to load balancing.

Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

Signed-off-by: Andreas Auernhammer <github@aead.dev>
2023-11-16 01:51:27 -08:00
Adrian Najera 96c2304ae8 allow MINIO_STS_DURATION to increase the IDP token expiration (#18396)
Share link duration is based on the IDP token expiration,
for the share link to last longer, you may now use
MINIO_STS_DURATION environment variable.
2023-11-15 20:42:31 -08:00
Minio Trusted 343dd2f491 Update yaml files to latest version RELEASE.2023-11-15T20-43-25Z 2023-11-16 01:32:11 +00:00
Praveen raj Mani 38f35463b7 Load bucket configs during the metadata refresh (#18449)
This patch takes care of loading the bucket configs of failed buckets
during the periodic refresh. This makes sure the event notifiers and
remote bucket targets are properly initialized.
2023-11-15 12:43:25 -08:00
Harshavardhana 5573986e8e fix: relax free inode check for single drive deployments (#18437)
users might use MinIO on NFS, GPFS that provide dynamic
inodes and may not even have a concept of free inodes.

to allow users to use MinIO on top of GPFS relax the
free inode check.
2023-11-14 09:31:16 -08:00
Sveinn f3367a1b20 Adding error handling for network errors in the SFTP layer (#18442) 2023-11-14 09:31:00 -08:00
Sveinn a3c2f7b0e8 small fix for the PR template (#18443) 2023-11-14 09:29:11 -08:00
Sveinn 8fbec30998 Adding a missing return to fix SFTP Rmdir message (#18438) 2023-11-14 09:26:46 -08:00
Harshavardhana a7466eeb0e fix: ignore dperf on unformatted/unavailable/unmounted drives (#18435) 2023-11-13 22:32:08 -08:00
Harshavardhana 8b1e819bf3 fix: make sure to purge all the completed in resume() (#18429)
currently previously completed jobs would re-run
even if they are completed, causing incorrect behavior.
2023-11-13 08:15:00 -08:00
Anis Eleuch fe63664164 prom: Add drive failure tolerance per erasure set (#18424) 2023-11-13 00:59:48 -08:00
Minio Trusted 4598827dcb Update yaml files to latest version RELEASE.2023-11-11T08-14-41Z 2023-11-11 18:01:27 +00:00
Sveinn 9afdb05bf4 fix: file consistency issue on SFTP upload (#18422)
* creating a byte buffer for SFTP file segments
* Adding an error condition for when there are 
  remaining segments in the queue
* Simplification of the queue using a map
2023-11-11 00:14:41 -08:00
Krishnan Parthasarathi 9569a85cee Avoid allocs for MRF on-disk header (#18425) 2023-11-10 19:54:46 -08:00
Harshavardhana 54721b7c7b fix: batch replication from source allow out of band deletes (#18423)
it is possible that ILM or Deletes got triggered on batch
of objects that we are attempting to batch replicate, ignore
this scenario as valid behavior.
2023-11-10 16:12:35 -08:00
Harshavardhana 91d8bddbd1 use sendfile/splice implementation to perform DMA (#18411)
sendfile implementation to perform DMA on all platforms

Go stdlib already supports sendfile/splice implementations
for

- Linux
- Windows
- *BSD
- Solaris

Along with this change however O_DIRECT for reads() must be
removed as well since we need to use sendfile() implementation

The main reason to add O_DIRECT for reads was to reduce the
chances of page-cache causing OOMs for MinIO, however it would
seem that avoiding buffer copies from user-space to kernel space
this issue is not a problem anymore.

There is no Go based memory allocation required, and neither
the page-cache is referenced back to MinIO. This page-
cache reference is fully owned by kernel at this point, this
essentially should solve the problem of page-cache build up.

With this now we also support SG - when NIC supports Scatter/Gather
https://en.wikipedia.org/wiki/Gather/scatter_(vector_addressing)
2023-11-10 10:10:14 -08:00
Harshavardhana 80adc87a14 converge WARM tier object name to hash of deployment+bucket (#18410)
this is to ensure that we can converge and save IOPs
when hot-tier accesses MinIO.
2023-11-10 02:15:13 -08:00
Taran Pelkey 117ad1b65b Loosen requirements to detach policies for LDAP (#18419) 2023-11-09 14:44:43 -08:00
Klaus Post 2229509362 fix: leaking offline disks in MarkOffline() thread (#18414)
`monitorAndConnectEndpoints` will continue to attempt to reconnect offline disks.

Since disks were never closed, a `MarkOffline` would continue to try to check these disks forever.

Close previous disks.
2023-11-09 09:33:32 -08:00
Anis Eleuch 6ef8e87492 Support case insensitive kafka SASL mechanism config values (#18398) 2023-11-08 20:04:01 -08:00
Krishnan Parthasarathi 0a25083fdb Tiered objects require ns locks unlike inlined (#18409) 2023-11-08 20:00:02 -08:00
Sveinn 15137d0327 refactor SFTP to use the new minio/pkg implementation (#18406) 2023-11-08 09:47:05 -08:00
Poorna 8c9974bc0f site replication: avoid propagating bucket b/w settings (#18399)
replication mode and bucket bandwidth are one-way and should not be
propagated to peer cluster.

Regression from #18062
2023-11-08 00:40:25 -08:00
jiuker 079b6c2b50 fix: add err when all bucket resync failed (#18401) 2023-11-08 00:40:08 -08:00
Minio Trusted 0924b34a17 Update yaml files to latest version RELEASE.2023-11-06T22-26-08Z 2023-11-08 08:04:46 +00:00
47 changed files with 797 additions and 442 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
## Community Contribution License
All community contributions in this pull request are licensed to the project maintainers
under the terms of the [Apache 2 license] (https://www.apache.org/licenses/LICENSE-2.0).
under the terms of the [Apache 2 license](https://www.apache.org/licenses/LICENSE-2.0).
By creating this pull request I represent that I have the right to license the
contributions to the project maintainers under the Apache 2 license.
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.21.3
go-version: 1.21.4
check-latest: true
- name: Get official govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
+2
View File
@@ -561,6 +561,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
}
content.UserMetadata.Set(k, v)
}
content.UserMetadata.Set("expires", object.Expires.Format(http.TimeFormat))
content.Internal = &ObjectInternalInfo{
K: object.DataBlocks,
M: object.ParityBlocks,
@@ -704,6 +705,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
}
content.UserMetadata.Set(k, v)
}
content.UserMetadata.Set("expires", object.Expires.Format(http.TimeFormat))
content.Internal = &ObjectInternalInfo{
K: object.DataBlocks,
M: object.ParityBlocks,
+12 -2
View File
@@ -163,7 +163,7 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
}
rd, objInfo, _, err := core.GetObject(ctx, srcBucket, srcObject, gopts)
if err != nil {
return err
return ErrorRespToObjectError(err, srcBucket, srcObject, srcObjInfo.VersionID)
}
defer rd.Close()
@@ -226,7 +226,7 @@ func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, a
}
rd, objInfo, _, err := c.GetObject(ctx, srcBucket, srcObject, gopts)
if err != nil {
return err
return ErrorRespToObjectError(err, srcBucket, srcObject, srcObjInfo.VersionID)
}
defer rd.Close()
@@ -262,6 +262,9 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
if err := ri.load(ctx, api, job); err != nil {
return err
}
if ri.Complete {
return nil
}
globalBatchJobsMetrics.save(job.ID, ri)
delay := job.Replicate.Flags.Retry.Delay
@@ -517,6 +520,10 @@ func toObjectInfo(bucket, object string, objInfo miniogo.ObjectInfo) ObjectInfo
oi.UserDefined[xhttp.AmzStorageClass] = objInfo.StorageClass
}
for k, v := range objInfo.UserMetadata {
oi.UserDefined[k] = v
}
return oi
}
@@ -837,6 +844,9 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
if err := ri.load(ctx, api, job); err != nil {
return err
}
if ri.Complete {
return nil
}
globalBatchJobsMetrics.save(job.ID, ri)
lastObject := ri.Object
+3
View File
@@ -261,6 +261,9 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
if err := ri.load(ctx, api, job); err != nil {
return err
}
if ri.Complete {
return nil
}
globalBatchJobsMetrics.save(job.ID, ri)
lastObject := ri.Object
+18 -8
View File
@@ -43,6 +43,7 @@ import (
"github.com/minio/minio/internal/s3select"
"github.com/minio/pkg/v2/env"
"github.com/minio/pkg/v2/workers"
"github.com/zeebo/xxh3"
)
const (
@@ -223,11 +224,19 @@ type transitionState struct {
lastDayStats map[string]*lastDayTierStats
}
func (t *transitionState) queueTransitionTask(oi ObjectInfo, event lifecycle.Event, src lcEventSrc) {
select {
case <-t.ctx.Done():
case t.transitionCh <- transitionTask{objInfo: oi, event: event, src: src}:
default:
func (t *transitionState) queueTransitionTask(oi ObjectInfo, event lifecycle.Event, src lcEventSrc, blocking bool) {
task := transitionTask{objInfo: oi, event: event, src: src}
if blocking {
select {
case <-t.ctx.Done():
case t.transitionCh <- task:
}
} else {
select {
case <-t.ctx.Done():
case t.transitionCh <- task:
default:
}
}
}
@@ -237,7 +246,7 @@ var globalTransitionState *transitionState
// via its Init method.
func newTransitionState(ctx context.Context) *transitionState {
return &transitionState{
transitionCh: make(chan transitionTask, 10000),
transitionCh: make(chan transitionTask, 100000),
ctx: ctx,
killCh: make(chan struct{}),
lastDayStats: make(map[string]*lastDayTierStats),
@@ -367,7 +376,7 @@ func enqueueTransitionImmediate(obj ObjectInfo, src lcEventSrc) {
if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil {
switch event := lc.Eval(obj.ToLifecycleOpts()); event.Action {
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
globalTransitionState.queueTransitionTask(obj, event, src)
globalTransitionState.queueTransitionTask(obj, event, src, true)
}
}
}
@@ -444,7 +453,8 @@ func genTransitionObjName(bucket string) (string, error) {
return "", err
}
us := u.String()
obj := fmt.Sprintf("%s/%s/%s/%s/%s", globalDeploymentID(), bucket, us[0:2], us[2:4], us)
hash := xxh3.HashString(pathJoin(globalDeploymentID(), bucket))
obj := fmt.Sprintf("%s/%s/%s/%s", strconv.FormatUint(hash, 16), us[0:2], us[2:4], us)
return obj, nil
}
+25 -23
View File
@@ -437,21 +437,8 @@ func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, ob
return nil
}
func (sys *BucketMetadataSys) loadBucketMetadata(ctx context.Context, bucket BucketInfo) error {
meta, err := loadBucketMetadata(ctx, sys.objAPI, bucket.Name)
if err != nil {
return err
}
sys.Lock()
sys.metadataMap[bucket.Name] = meta
sys.Unlock()
return nil
}
// concurrently load bucket metadata to speed up loading bucket metadata.
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo) {
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo, failedBuckets map[string]struct{}) {
g := errgroup.WithNErrs(len(buckets))
bucketMetas := make([]BucketMetadata, len(buckets))
for index := range buckets {
@@ -491,6 +478,10 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
for i, meta := range bucketMetas {
if errs[i] != nil {
if failedBuckets == nil {
failedBuckets = make(map[string]struct{})
}
failedBuckets[buckets[i].Name] = struct{}{}
continue
}
globalEventNotifier.set(buckets[i], meta) // set notification targets
@@ -498,7 +489,7 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
}
}
func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context, failedBuckets map[string]struct{}) {
const bucketMetadataRefresh = 15 * time.Minute
sleeper := newDynamicSleeper(2, 150*time.Millisecond, false)
@@ -513,7 +504,7 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{})
if err != nil {
logger.LogIf(ctx, err)
continue
break
}
// Handle if we have some buckets in-memory those are stale.
@@ -525,38 +516,49 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
}
sys.RemoveStaleBuckets(diskBuckets)
for _, bucket := range buckets {
for i := range buckets {
wait := sleeper.Timer(ctx)
err := sys.loadBucketMetadata(ctx, bucket)
meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[i].Name)
if err != nil {
logger.LogIf(ctx, err)
wait() // wait to proceed to next entry.
continue
}
sys.Lock()
sys.metadataMap[buckets[i].Name] = meta
sys.Unlock()
// Initialize the failed buckets
if _, ok := failedBuckets[buckets[i].Name]; ok {
globalEventNotifier.set(buckets[i], meta)
globalBucketTargetSys.set(buckets[i], meta)
delete(failedBuckets, buckets[i].Name)
}
wait() // wait to proceed to next entry.
}
t.Reset(bucketMetadataRefresh)
}
t.Reset(bucketMetadataRefresh)
}
}
// Loads bucket metadata for all buckets into BucketMetadataSys.
func (sys *BucketMetadataSys) init(ctx context.Context, buckets []BucketInfo) {
count := 100 // load 100 bucket metadata at a time.
failedBuckets := make(map[string]struct{})
for {
if len(buckets) < count {
sys.concurrentLoad(ctx, buckets)
sys.concurrentLoad(ctx, buckets, failedBuckets)
break
}
sys.concurrentLoad(ctx, buckets[:count])
sys.concurrentLoad(ctx, buckets[:count], failedBuckets)
buckets = buckets[count:]
}
if globalIsDistErasure {
go sys.refreshBucketsMetadataLoop(ctx)
go sys.refreshBucketsMetadataLoop(ctx, failedBuckets)
}
}
+9 -9
View File
@@ -777,6 +777,7 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (put
UserMetadata: meta,
ContentType: objInfo.ContentType,
ContentEncoding: objInfo.ContentEncoding,
Expires: objInfo.Expires,
StorageClass: sc,
Internal: minio.AdvancedPutOptions{
SourceVersionID: objInfo.VersionID,
@@ -3187,12 +3188,16 @@ func (p *ReplicationPool) queueMRFSave(entry MRFReplicateEntry) {
}
}
func (p *ReplicationPool) persistToDrive(ctx context.Context, v MRFReplicateEntries, data []byte) {
func (p *ReplicationPool) persistToDrive(ctx context.Context, v MRFReplicateEntries) {
newReader := func() io.ReadCloser {
r, w := io.Pipe()
go func() {
// Initialize MRF meta header.
var data [4]byte
binary.LittleEndian.PutUint16(data[0:2], mrfMetaFormat)
binary.LittleEndian.PutUint16(data[2:4], mrfMetaVersion)
mw := msgp.NewWriter(w)
n, err := mw.Write(data)
n, err := mw.Write(data[:])
if err != nil {
w.CloseWithError(err)
return
@@ -3230,15 +3235,10 @@ func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string
v := MRFReplicateEntries{
Entries: entries,
Version: mrfMetaVersionV1,
Version: mrfMetaVersion,
}
data := make([]byte, 4, v.Msgsize()+4)
// Initialize the resync meta header.
binary.LittleEndian.PutUint16(data[0:2], mrfMetaFormat)
binary.LittleEndian.PutUint16(data[2:4], mrfMetaVersion)
p.persistToDrive(ctx, v, data)
p.persistToDrive(ctx, v)
}
// load mrf entries from disk
+1 -1
View File
@@ -1173,7 +1173,7 @@ func applyTransitionRule(event lifecycle.Event, src lcEventSrc, obj ObjectInfo)
if obj.DeleteMarker {
return false
}
globalTransitionState.queueTransitionTask(obj, event, src)
globalTransitionState.queueTransitionTask(obj, event, src, false)
return true
}
+28 -2
View File
@@ -666,6 +666,32 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
return metaFileInfos, errs
}
// Checking if an object is dangling costs some IOPS; hence implementing this function
// which decides which condition it is useful to check if an object is dangling
//
// errs: errors from reading xl.meta in all disks
// err: reduced errs
// bucket: the object name in question
func shouldCheckForDangling(err error, errs []error, bucket string) bool {
// Avoid data in .minio.sys for now
if bucket == minioMetaBucket {
return false
}
switch {
// Check if we have a read quorum issue
case errors.Is(err, errErasureReadQuorum):
return true
// Check if the object is inexistent in most disks but not all of them
case errors.Is(err, errFileNotFound) || errors.Is(err, errFileVersionNotFound):
for i := range errs {
if errs[i] == nil {
return true
}
}
}
return false
}
func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object string, opts ObjectOptions, readData bool) (fi FileInfo, metaArr []FileInfo, onlineDisks []StorageAPI, err error) {
disks := er.getDisks()
@@ -680,7 +706,7 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
if err != nil {
if errors.Is(err, errErasureReadQuorum) && !strings.HasPrefix(bucket, minioMetaBucket) {
if shouldCheckForDangling(err, errs, bucket) {
_, derr := er.deleteIfDangling(ctx, bucket, object, metaArr, errs, nil, opts)
if derr != nil {
err = derr
@@ -690,7 +716,7 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
}
if reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum); reducedErr != nil {
if errors.Is(reducedErr, errErasureReadQuorum) && !strings.HasPrefix(bucket, minioMetaBucket) {
if shouldCheckForDangling(err, errs, bucket) {
_, derr := er.deleteIfDangling(ctx, bucket, object, metaArr, errs, nil, opts)
if derr != nil {
reducedErr = derr
+5 -3
View File
@@ -701,10 +701,12 @@ func (set *erasureObjects) listObjectsToDecommission(ctx context.Context, bi dec
return fmt.Errorf("no online drives found for set with endpoints %s", set.getEndpoints())
}
listQuorum := (len(disks) + 1) / 2
// How to resolve partial results.
resolver := metadataResolutionParams{
dirQuorum: len(disks) / 2, // make sure to capture all quorum ratios
objQuorum: len(disks) / 2, // make sure to capture all quorum ratios
dirQuorum: listQuorum, // make sure to capture all quorum ratios
objQuorum: listQuorum, // make sure to capture all quorum ratios
bucket: bi.Name,
}
@@ -714,7 +716,7 @@ func (set *erasureObjects) listObjectsToDecommission(ctx context.Context, bi dec
path: bi.Prefix,
recursive: true,
forwardTo: "",
minDisks: len(disks) / 2, // to capture all quorum ratios
minDisks: len(disks) / 2,
reportNotFound: false,
agreed: fn,
partial: func(entries metaCacheEntries, _ []error) {
+23 -32
View File
@@ -970,7 +970,7 @@ func (z *erasureServerPools) GetObjectInfo(ctx context.Context, bucket, object s
// PutObject - writes an object to least used erasure pool.
func (z *erasureServerPools) PutObject(ctx context.Context, bucket string, object string, data *PutObjReader, opts ObjectOptions) (ObjectInfo, error) {
// Validate put object input args.
if err := checkPutObjectArgs(ctx, bucket, object, z); err != nil {
if err := checkPutObjectArgs(ctx, bucket, object); err != nil {
return ObjectInfo{}, err
}
@@ -2253,11 +2253,12 @@ type HealthOptions struct {
// additionally with any specific heuristic information which
// was queried
type HealthResult struct {
Healthy bool
HealingDrives int
UnhealthyPools []struct {
Healthy bool
HealingDrives int
ESHealth []struct {
Maintenance bool
PoolID, SetID int
HealthyDrives int
WriteQuorum int
}
WriteQuorum int
@@ -2372,50 +2373,40 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
}
result := HealthResult{
HealingDrives: len(aggHealStateResult.HealDisks),
Healthy: true,
WriteQuorum: maximumWriteQuorum,
UsingDefaults: usingDefaults, // indicates if config was not initialized and we are using defaults on this node.
}
for poolIdx := range erasureSetUpCount {
for setIdx := range erasureSetUpCount[poolIdx] {
result.ESHealth = append(result.ESHealth, struct {
Maintenance bool
PoolID, SetID int
HealthyDrives, WriteQuorum int
}{
Maintenance: opts.Maintenance,
SetID: setIdx,
PoolID: poolIdx,
HealthyDrives: erasureSetUpCount[poolIdx][setIdx],
WriteQuorum: poolWriteQuorums[poolIdx],
})
if erasureSetUpCount[poolIdx][setIdx] < poolWriteQuorums[poolIdx] {
logger.LogIf(logger.SetReqInfo(ctx, reqInfo),
fmt.Errorf("Write quorum may be lost on pool: %d, set: %d, expected write quorum: %d",
poolIdx, setIdx, poolWriteQuorums[poolIdx]))
result.UnhealthyPools = append(result.UnhealthyPools, struct {
Maintenance bool
PoolID, SetID, WriteQuorum int
}{
Maintenance: opts.Maintenance,
SetID: setIdx,
PoolID: poolIdx,
WriteQuorum: poolWriteQuorums[poolIdx],
})
result.Healthy = false
}
}
if len(result.UnhealthyPools) > 0 {
// We have unhealthy pools return error.
return result
}
}
// when maintenance is not specified we don't have
// to look at the healing side of the code.
if !opts.Maintenance {
return HealthResult{
Healthy: true,
WriteQuorum: maximumWriteQuorum,
UsingDefaults: usingDefaults, // indicates if config was not initialized and we are using defaults on this node.
}
if opts.Maintenance {
result.Healthy = result.Healthy && len(aggHealStateResult.HealDisks) == 0
result.HealingDrives = len(aggHealStateResult.HealDisks)
}
return HealthResult{
Healthy: len(aggHealStateResult.HealDisks) == 0,
HealingDrives: len(aggHealStateResult.HealDisks),
WriteQuorum: maximumWriteQuorum,
UsingDefaults: usingDefaults, // indicates if config was not initialized and we are using defaults on this node.
}
return result
}
// PutObjectMetadata - replace or add tags to an existing object
+4
View File
@@ -217,6 +217,10 @@ func (s *erasureSets) connectDisks() {
continue
}
}
if cdisk != nil {
// Close previous offline disk.
cdisk.Close()
}
wg.Add(1)
go func(endpoint Endpoint) {
-34
View File
@@ -128,40 +128,6 @@ func (evnot *EventNotifier) AddRulesMap(bucketName string, rulesMap event.RulesM
}
}
// RemoveRulesMap - removes rules map for bucket name.
func (evnot *EventNotifier) RemoveRulesMap(bucketName string, rulesMap event.RulesMap) {
evnot.Lock()
defer evnot.Unlock()
evnot.bucketRulesMap[bucketName].Remove(rulesMap)
if len(evnot.bucketRulesMap[bucketName]) == 0 {
delete(evnot.bucketRulesMap, bucketName)
}
}
// ConfiguredTargetIDs - returns list of configured target id's
func (evnot *EventNotifier) ConfiguredTargetIDs() []event.TargetID {
if evnot == nil {
return nil
}
evnot.RLock()
defer evnot.RUnlock()
var targetIDs []event.TargetID
for _, rmap := range evnot.bucketRulesMap {
for _, rules := range rmap {
for _, targetSet := range rules {
for id := range targetSet {
targetIDs = append(targetIDs, id)
}
}
}
}
return targetIDs
}
// RemoveNotification - removes all notification configuration for bucket name.
func (evnot *EventNotifier) RemoveNotification(bucketName string) {
evnot.Lock()
-196
View File
@@ -18,20 +18,14 @@
package cmd
import (
"context"
"crypto/subtle"
"fmt"
"net"
"os"
"strconv"
"strings"
"time"
"github.com/minio/cli"
"github.com/minio/minio/internal/logger"
"github.com/pkg/sftp"
ftp "goftp.io/server/v2"
"golang.org/x/crypto/ssh"
)
var globalRemoteFTPClientTransport = NewRemoteTargetHTTPTransport(true)()
@@ -75,196 +69,6 @@ func (log *minioLogger) PrintResponse(sessionID string, code int, message string
}
}
func acceptSFTPConnection(conn net.Conn, config *ssh.ServerConfig) {
// For SSH handshake keep a 2 minute deadline, as OpensSSH default.
conn.SetDeadline(time.Now().Add(2 * time.Minute))
// Before use, a handshake must be performed on the incoming net.Conn.
sconn, chans, reqs, err := ssh.NewServerConn(conn, config)
if err != nil {
return
}
// Once we are done with SSH handshake, remove deadline.
conn.SetDeadline(time.Time{})
// The incoming Request channel must be serviced.
go ssh.DiscardRequests(reqs)
// Service the incoming Channel channel.
for newChannel := range chans {
// Channels have a type, depending on the application level
// protocol intended. In the case of an SFTP session, this is "subsystem"
// with a payload string of "<length=4>sftp"
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
logger.LogOnceIf(context.Background(), fmt.Errorf("unable to accept the connection requests channel: %w", err), "accept-channel-sftp")
continue
}
// Sessions have out-of-band requests such as "shell",
// "pty-req" and "env". Here we handle only the
// "subsystem" request.
go func(in <-chan *ssh.Request) {
for req := range in {
ok := false
if req.Type == "subsystem" {
if len(req.Payload) > 4 && string(req.Payload[4:]) == "sftp" {
ok = true
go handleSFTPConnection(channel, sconn)
}
}
if req.WantReply {
// We only reply to SSH packets that have `sftp` payload, all other
// packets are rejected
req.Reply(ok, nil)
}
}
}(requests)
}
}
func handleSFTPConnection(channel ssh.Channel, sconn *ssh.ServerConn) {
// Create the server instance for the channel using the handler we created above.
server := sftp.NewRequestServer(channel, NewSFTPDriver(sconn.Permissions), sftp.WithRSAllocator())
defer server.Close()
server.Serve()
}
func startSFTPServer(c *cli.Context) {
args := c.StringSlice("sftp")
var (
port int
publicIP string
sshPrivateKey string
)
var err error
for _, arg := range args {
tokens := strings.SplitN(arg, "=", 2)
if len(tokens) != 2 {
logger.Fatal(fmt.Errorf("invalid arguments passed to --sftp=%s", arg), "unable to start SFTP server")
}
switch tokens[0] {
case "address":
host, portStr, err := net.SplitHostPort(tokens[1])
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed to --sftp=%s (%v)", arg, err), "unable to start SFTP server")
}
port, err = strconv.Atoi(portStr)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed to --sftp=%s (%v)", arg, err), "unable to start SFTP server")
}
if port < 1 || port > 65535 {
logger.Fatal(fmt.Errorf("invalid arguments passed to --sftp=%s, (port number must be between 1 to 65535)", arg), "unable to start SFTP server")
}
publicIP = host
case "ssh-private-key":
sshPrivateKey = tokens[1]
}
}
if port == 0 {
port = 8022 // Default SFTP port, since no port was given.
}
if sshPrivateKey == "" {
logger.Fatal(fmt.Errorf("invalid arguments passed, private key file is mandatory for --sftp='ssh-private-key=path/to/id_ecdsa'"), "unable to start SFTP server")
}
privateBytes, err := os.ReadFile(sshPrivateKey)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed, private key file is not accessible: %v", err), "unable to start SFTP server")
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed, private key file is not parseable: %v", err), "unable to start SFTP server")
}
// An SSH server is represented by a ServerConfig, which holds
// certificate details and handles authentication of ServerConns.
config := &ssh.ServerConfig{
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
if globalIAMSys.LDAPConfig.Enabled() {
targetUser, targetGroups, err := globalIAMSys.LDAPConfig.Bind(c.User(), string(pass))
if err != nil {
return nil, err
}
ldapPolicies, _ := globalIAMSys.PolicyDBGet(targetUser, false, targetGroups...)
if len(ldapPolicies) == 0 {
return nil, errAuthentication
}
return &ssh.Permissions{
CriticalOptions: map[string]string{
ldapUser: targetUser,
ldapUserN: c.User(),
},
Extensions: make(map[string]string),
}, nil
}
ui, ok := globalIAMSys.GetUser(context.Background(), c.User())
if !ok {
return nil, errNoSuchUser
}
if subtle.ConstantTimeCompare([]byte(ui.Credentials.SecretKey), pass) == 1 {
return &ssh.Permissions{
CriticalOptions: map[string]string{
"accessKey": c.User(),
},
Extensions: make(map[string]string),
}, nil
}
return nil, errAuthentication
},
}
config.AddHostKey(private)
// Once a ServerConfig has been configured, connections can be accepted.
listener, err := net.Listen("tcp", net.JoinHostPort(publicIP, strconv.Itoa(port)))
if err != nil {
logger.Fatal(err, "unable to start listening on --sftp='port=%d'", port)
}
logger.Info(fmt.Sprintf("MinIO SFTP Server listening on %s", net.JoinHostPort(publicIP, strconv.Itoa(port))))
var tempDelay time.Duration // how long to sleep on accept failure
for {
conn, err := listener.Accept()
if err != nil {
// From https://github.com/golang/go/blob/4aa1efed4853ea067d665a952eee77c52faac774/src/net/http/server.go#L3046
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
logger.LogOnceIf(context.Background(), fmt.Errorf("error while accepting connections: %w, retrying in %s", err, tempDelay), "accept-limit-sftp")
time.Sleep(tempDelay)
continue
}
logger.Fatal(err, "unrecoverable error while accepting new connections")
}
go acceptSFTPConnection(conn, config)
}
}
func startFTPServer(c *cli.Context) {
args := c.StringSlice("ftp")
+33 -15
View File
@@ -90,21 +90,15 @@ func ClusterReadCheckHandler(w http.ResponseWriter, r *http.Request) {
writeResponse(w, http.StatusOK, nil, mimeNone)
}
// ReadinessCheckHandler Checks if the process is up. Always returns success.
// ReadinessCheckHandler checks whether MinIO is up and ready to serve requests.
// It also checks whether the KMS is available and whether etcd is reachable,
// if configured.
func ReadinessCheckHandler(w http.ResponseWriter, r *http.Request) {
LivenessCheckHandler(w, r)
}
// LivenessCheckHandler - Checks if the process is up. Always returns success.
func LivenessCheckHandler(w http.ResponseWriter, r *http.Request) {
objLayer := newObjectLayerFn()
if objLayer == nil {
// Service not initialized yet
w.Header().Set(xhttp.MinIOServerStatus, unavailable)
if objLayer := newObjectLayerFn(); objLayer == nil {
w.Header().Set(xhttp.MinIOServerStatus, unavailable) // Service not initialized yet
}
peerCall := r.Header.Get(xhttp.MinIOPeerCall) != ""
if peerCall {
if r.Header.Get(xhttp.MinIOPeerCall) != "" {
writeResponse(w, http.StatusOK, nil, mimeNone)
return
}
@@ -120,7 +114,7 @@ func LivenessCheckHandler(w http.ResponseWriter, r *http.Request) {
}
// Verify if KMS is reachable if its configured
if GlobalKMS != nil && !peerCall {
if GlobalKMS != nil {
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
defer cancel()
@@ -153,6 +147,30 @@ func LivenessCheckHandler(w http.ResponseWriter, r *http.Request) {
return
}
}
writeResponse(w, http.StatusOK, nil, mimeNone)
}
// LivenessCheckHandler checks whether MinIO is up. It differs from the
// readiness handler since a failing liveness check causes pod restarts
// in K8S enviromnents. Therefore, it does not contact external systems.
func LivenessCheckHandler(w http.ResponseWriter, r *http.Request) {
if objLayer := newObjectLayerFn(); objLayer == nil {
w.Header().Set(xhttp.MinIOServerStatus, unavailable) // Service not initialized yet
}
if r.Header.Get(xhttp.MinIOPeerCall) != "" {
writeResponse(w, http.StatusOK, nil, mimeNone)
return
}
if int(globalHTTPStats.loadRequestsInQueue()) > globalAPIConfig.getRequestsPoolCapacity() {
apiErr := getAPIError(ErrBusy)
switch r.Method {
case http.MethodHead:
writeResponse(w, apiErr.HTTPStatusCode, nil, mimeNone)
case http.MethodGet:
writeErrorResponse(r.Context(), w, apiErr, r.URL)
}
return
}
writeResponse(w, http.StatusOK, nil, mimeNone)
}
+16 -9
View File
@@ -1663,18 +1663,25 @@ func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool,
return
}
if dn == "" {
err = errNoSuchUser
return
// Still attempt to detach if provided user is a DN.
if !isAttach && sys.LDAPConfig.IsLDAPUserDN(r.User) {
dn = r.User
} else {
err = errNoSuchUser
return
}
}
isGroup = false
} else {
var exists bool
if exists, err = sys.LDAPConfig.DoesGroupDNExist(r.Group); err != nil {
logger.LogIf(ctx, err)
return
} else if !exists {
err = errNoSuchGroup
return
if isAttach {
var exists bool
if exists, err = sys.LDAPConfig.DoesGroupDNExist(r.Group); err != nil {
logger.LogIf(ctx, err)
return
} else if !exists {
err = errNoSuchGroup
return
}
}
dn = r.Group
isGroup = true
+23
View File
@@ -22,6 +22,7 @@ import (
"fmt"
"net/http"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
@@ -3187,6 +3188,16 @@ func getClusterHealthStatusMD() MetricDescription {
}
}
func getClusterErasureSetToleranceMD() MetricDescription {
return MetricDescription{
Namespace: clusterMetricNamespace,
Subsystem: "health",
Name: "erasure_set_tolerance",
Help: "Get erasure set tolerance status",
Type: gaugeMetric,
}
}
func getClusterHealthMetrics() *MetricsGroup {
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
@@ -3218,6 +3229,18 @@ func getClusterHealthMetrics() *MetricsGroup {
Value: float64(health),
})
for _, h := range result.ESHealth {
labels := map[string]string{
"pool": strconv.Itoa(h.PoolID),
"set": strconv.Itoa(h.SetID),
}
metrics = append(metrics, Metric{
Description: getClusterErasureSetToleranceMD(),
VariableLabels: labels,
Value: float64(h.HealthyDrives - h.WriteQuorum),
})
}
return
})
+1 -11
View File
@@ -149,7 +149,6 @@ func checkObjectArgs(ctx context.Context, bucket, object string, obj ObjectLayer
// This is done on purpose since the order of errors is
// important here bucket does not exist error should
// happen before we return an error for invalid object name.
// FIXME: should be moved to handler layer.
if err := checkBucketExist(ctx, bucket, obj); err != nil {
return err
}
@@ -170,16 +169,7 @@ func checkObjectArgs(ctx context.Context, bucket, object string, obj ObjectLayer
}
// Checks for PutObject arguments validity, also validates if bucket exists.
func checkPutObjectArgs(ctx context.Context, bucket, object string, obj getBucketInfoI) error {
// Verify if bucket exists before validating object name.
// This is done on purpose since the order of errors is
// important here bucket does not exist error should
// happen before we return an error for invalid object name.
// FIXME: should be moved to handler layer.
if err := checkBucketExist(ctx, bucket, obj); err != nil {
return err
}
func checkPutObjectArgs(ctx context.Context, bucket, object string) error {
if err := checkObjectNameForLengthAndSlash(bucket, object); err != nil {
return err
}
+1 -1
View File
@@ -1198,7 +1198,7 @@ func hasSpaceFor(di []*DiskInfo, size int64) (bool, error) {
if disk == nil || disk.Total == 0 {
continue
}
if disk.FreeInodes < diskMinInodes && disk.UsedInodes > 0 {
if !globalIsErasureSD && disk.FreeInodes < diskMinInodes && disk.UsedInodes > 0 {
// We have an inode count, but not enough inodes.
return false, nil
}
+68 -7
View File
@@ -201,19 +201,74 @@ func (f *sftpDriver) Fileread(r *sftp.Request) (ra io.ReaderAt, err error) {
return obj, nil
}
type writerAt struct {
w *io.PipeWriter
wg *sync.WaitGroup
// TransferError will catch network errors during transfer.
// When TransferError() is called Close() will also
// be called, so we do not need to Wait() here.
func (w *writerAt) TransferError(err error) {
_ = w.w.CloseWithError(err)
_ = w.r.CloseWithError(err)
w.err = err
}
func (w *writerAt) Close() error {
err := w.w.Close()
func (w *writerAt) Close() (err error) {
switch {
case len(w.buffer) > 0:
err = errors.New("some file segments were not flushed from the queue")
_ = w.w.CloseWithError(err)
case w.err != nil:
// No need to close here since both pipes were
// closing inside TransferError()
err = w.err
default:
err = w.w.Close()
}
for i := range w.buffer {
delete(w.buffer, i)
}
w.wg.Wait()
return err
}
type writerAt struct {
w *io.PipeWriter
r *io.PipeReader
wg *sync.WaitGroup
buffer map[int64][]byte
err error
nextOffset int64
m sync.Mutex
}
func (w *writerAt) WriteAt(b []byte, offset int64) (n int, err error) {
return w.w.Write(b)
w.m.Lock()
defer w.m.Unlock()
if w.nextOffset == offset {
n, err = w.w.Write(b)
w.nextOffset += int64(n)
} else {
w.buffer[offset] = make([]byte, len(b))
copy(w.buffer[offset], b)
n = len(b)
}
again:
nextOut, ok := w.buffer[w.nextOffset]
if ok {
n, err = w.w.Write(nextOut)
delete(w.buffer, w.nextOffset)
w.nextOffset += int64(n)
if n != len(nextOut) {
return 0, fmt.Errorf("expected write size %d but wrote %d bytes", len(nextOut), n)
}
if err != nil {
return 0, err
}
goto again
}
return len(b), nil
}
func (f *sftpDriver) Filewrite(r *sftp.Request) (w io.WriterAt, err error) {
@@ -238,7 +293,12 @@ func (f *sftpDriver) Filewrite(r *sftp.Request) (w io.WriterAt, err error) {
pr, pw := io.Pipe()
wa := &writerAt{w: pw, wg: &sync.WaitGroup{}}
wa := &writerAt{
buffer: make(map[int64][]byte),
w: pw,
r: pr,
wg: &sync.WaitGroup{},
}
wa.wg.Add(1)
go func() {
_, err := clnt.PutObject(r.Context(), bucket, object, pr, -1, minio.PutObjectOptions{SendContentMd5: true})
@@ -298,6 +358,7 @@ func (f *sftpDriver) Filecmd(r *sftp.Request) (err error) {
return err.Err
}
}
return err
case "Remove":
bucket, object := path2BucketObject(r.Filepath)
+172
View File
@@ -0,0 +1,172 @@
// Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"context"
"crypto/subtle"
"fmt"
"net"
"os"
"strconv"
"strings"
"time"
"github.com/minio/cli"
"github.com/minio/minio/internal/logger"
xsftp "github.com/minio/pkg/v2/sftp"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
type sftpLogger struct{}
func (s *sftpLogger) Info(tag xsftp.LogType, msg string) {
logger.Info(msg)
}
func (s *sftpLogger) Error(tag xsftp.LogType, err error) {
switch tag {
case xsftp.AcceptNetworkError:
logger.LogOnceIf(context.Background(), err, "accept-limit-sftp")
case xsftp.AcceptChannelError:
logger.LogOnceIf(context.Background(), err, "accept-channel-sftp")
case xsftp.SSHKeyExchangeError:
logger.LogOnceIf(context.Background(), err, "key-exchange-sftp")
default:
logger.LogOnceIf(context.Background(), err, "unknown-error-sftp")
}
}
func startSFTPServer(c *cli.Context) {
args := c.StringSlice("sftp")
var (
port int
publicIP string
sshPrivateKey string
)
var err error
for _, arg := range args {
tokens := strings.SplitN(arg, "=", 2)
if len(tokens) != 2 {
logger.Fatal(fmt.Errorf("invalid arguments passed to --sftp=%s", arg), "unable to start SFTP server")
}
switch tokens[0] {
case "address":
host, portStr, err := net.SplitHostPort(tokens[1])
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed to --sftp=%s (%v)", arg, err), "unable to start SFTP server")
}
port, err = strconv.Atoi(portStr)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed to --sftp=%s (%v)", arg, err), "unable to start SFTP server")
}
if port < 1 || port > 65535 {
logger.Fatal(fmt.Errorf("invalid arguments passed to --sftp=%s, (port number must be between 1 to 65535)", arg), "unable to start SFTP server")
}
publicIP = host
case "ssh-private-key":
sshPrivateKey = tokens[1]
}
}
if port == 0 {
port = 8022 // Default SFTP port, since no port was given.
}
if sshPrivateKey == "" {
logger.Fatal(fmt.Errorf("invalid arguments passed, private key file is mandatory for --sftp='ssh-private-key=path/to/id_ecdsa'"), "unable to start SFTP server")
}
privateBytes, err := os.ReadFile(sshPrivateKey)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed, private key file is not accessible: %v", err), "unable to start SFTP server")
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed, private key file is not parseable: %v", err), "unable to start SFTP server")
}
// An SSH server is represented by a ServerConfig, which holds
// certificate details and handles authentication of ServerConns.
sshConfig := &ssh.ServerConfig{
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
if globalIAMSys.LDAPConfig.Enabled() {
targetUser, targetGroups, err := globalIAMSys.LDAPConfig.Bind(c.User(), string(pass))
if err != nil {
return nil, err
}
ldapPolicies, _ := globalIAMSys.PolicyDBGet(targetUser, false, targetGroups...)
if len(ldapPolicies) == 0 {
return nil, errAuthentication
}
return &ssh.Permissions{
CriticalOptions: map[string]string{
ldapUser: targetUser,
ldapUserN: c.User(),
},
Extensions: make(map[string]string),
}, nil
}
ui, ok := globalIAMSys.GetUser(context.Background(), c.User())
if !ok {
return nil, errNoSuchUser
}
if subtle.ConstantTimeCompare([]byte(ui.Credentials.SecretKey), pass) == 1 {
return &ssh.Permissions{
CriticalOptions: map[string]string{
"accessKey": c.User(),
},
Extensions: make(map[string]string),
}, nil
}
return nil, errAuthentication
},
}
sshConfig.AddHostKey(private)
handleSFTPSession := func(channel ssh.Channel, sconn *ssh.ServerConn) {
server := sftp.NewRequestServer(channel, NewSFTPDriver(sconn.Permissions), sftp.WithRSAllocator())
defer server.Close()
server.Serve()
}
sftpServer, err := xsftp.NewServer(&xsftp.Options{
PublicIP: publicIP,
Port: port,
// OpensSSH default handshake timeout is 2 minutes.
SSHHandshakeDeadline: 2 * time.Minute,
Logger: new(sftpLogger),
SSHConfig: sshConfig,
HandleSFTPSession: handleSFTPSession,
})
if err != nil {
logger.Fatal(err, "Unable to start SFTP Server")
}
err = sftpServer.Listen()
if err != nil {
logger.Fatal(err, "SFTP Server had an unrecoverable error while accepting connections")
}
}
+3 -8
View File
@@ -3787,14 +3787,6 @@ func (c *SiteReplicationSys) PeerEditReq(ctx context.Context, arg madmin.PeerInf
p := c.state.Peers[i]
if p.DeploymentID == arg.DeploymentID {
p.Endpoint = arg.Endpoint
if arg.DefaultBandwidth.IsSet {
if arg.DefaultBandwidth.UpdatedAt.After(p.DefaultBandwidth.UpdatedAt) {
p.DefaultBandwidth = arg.DefaultBandwidth
}
}
if !arg.SyncState.Empty() {
p.SyncState = arg.SyncState
}
c.state.Peers[arg.DeploymentID] = p
}
if p.DeploymentID == globalDeploymentID() {
@@ -5239,6 +5231,9 @@ func (c *SiteReplicationSys) startResync(ctx context.Context, objAPI ObjectLayer
if len(res.Buckets) > 0 {
res.ErrDetail = "partial failure in starting site resync"
}
if len(buckets) != 0 && len(res.Buckets) == len(buckets) {
return res, fmt.Errorf("all buckets resync failed")
}
return res, nil
}
+13 -1
View File
@@ -238,9 +238,15 @@ func driveSpeedTest(ctx context.Context, opts madmin.DriveSpeedTestOpts) madmin.
}
localPaths := globalEndpoints.LocalDisksPaths()
var ignoredPaths []string
paths := func() (tmpPaths []string) {
for _, lp := range localPaths {
tmpPaths = append(tmpPaths, pathJoin(lp, minioMetaTmpBucket))
if _, err := Lstat(pathJoin(lp, minioMetaBucket, formatConfigFile)); err == nil {
tmpPaths = append(tmpPaths, pathJoin(lp, minioMetaTmpBucket))
} else {
// Use dperf on only formatted drives.
ignoredPaths = append(ignoredPaths, lp)
}
}
return tmpPaths
}()
@@ -274,6 +280,12 @@ func driveSpeedTest(ctx context.Context, opts madmin.DriveSpeedTestOpts) madmin.
}
results = append(results, result)
}
for _, inp := range ignoredPaths {
results = append(results, madmin.DrivePerf{
Path: inp,
Error: errFaultyDisk.Error(),
})
}
return results
}(),
Error: func() string {
+5 -1
View File
@@ -296,7 +296,11 @@ func (fi FileInfo) GetDataDir() string {
// InlineData returns true if object contents are inlined alongside its metadata.
func (fi FileInfo) InlineData() bool {
_, ok := fi.Metadata[ReservedMetadataPrefixLower+"inline-data"]
return ok
// Earlier MinIO versions didn't reset "x-minio-internal-inline-data"
// from fi.Metadata when the object was tiered. So, tiered objects
// would return true for InlineData() in these versions even though the
// object isn't inlined in xl.meta
return ok && !fi.IsRemote()
}
// SetInlineData marks object (version) as inline.
+23 -5
View File
@@ -611,6 +611,8 @@ func (s *storageRESTServer) ReadFileStreamHandler(w http.ResponseWriter, r *http
return
}
w.Header().Set(xhttp.ContentLength, strconv.Itoa(length))
rc, err := s.storage.ReadFileStream(r.Context(), volume, filePath, int64(offset), int64(length))
if err != nil {
s.writeErrorResponse(w, err)
@@ -618,12 +620,28 @@ func (s *storageRESTServer) ReadFileStreamHandler(w http.ResponseWriter, r *http
}
defer rc.Close()
w.Header().Set(xhttp.ContentLength, strconv.Itoa(length))
if _, err = xioutil.Copy(w, rc); err != nil {
if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients
logger.LogIf(r.Context(), err)
rf, ok := w.(io.ReaderFrom)
if ok {
// Attempt to use splice/sendfile() optimization, A very specific behavior mentioned below is necessary.
// See https://github.com/golang/go/blob/f7c5cbb82087c55aa82081e931e0142783700ce8/src/net/sendfile_linux.go#L20
dr, ok := rc.(*xioutil.DeadlineReader)
if ok {
sr, ok := dr.ReadCloser.(*sendFileReader)
if ok {
_, err = rf.ReadFrom(sr.Reader)
if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients
logger.LogIf(r.Context(), err)
}
if err == nil || !errors.Is(err, xhttp.ErrNotImplemented) {
return
}
}
}
return
} // Fallback to regular copy
_, err = xioutil.Copy(w, rc)
if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients
logger.LogIf(r.Context(), err)
}
}
+10
View File
@@ -99,12 +99,16 @@ func ErrorRespToObjectError(err error, params ...string) error {
bucket := ""
object := ""
versionID := ""
if len(params) >= 1 {
bucket = params[0]
}
if len(params) == 2 {
object = params[1]
}
if len(params) == 3 {
versionID = params[2]
}
if xnet.IsNetworkOrHostDown(err, false) {
return BackendDown{Err: err.Error()}
@@ -142,6 +146,12 @@ func ErrorRespToObjectError(err error, params ...string) error {
} else {
err = BucketNotFound{Bucket: bucket}
}
case "NoSuchVersion":
if object != "" {
err = ObjectNotFound{Bucket: bucket, Object: object, VersionID: versionID}
} else {
err = BucketNotFound{Bucket: bucket}
}
case "XMinioInvalidObjectName":
err = ObjectNameInvalid{}
case "AccessDenied":
+21 -5
View File
@@ -421,7 +421,13 @@ func (p *xlStorageDiskIDCheck) ReadFile(ctx context.Context, volume string, path
}
defer done(&err)
return p.storage.ReadFile(ctx, volume, path, offset, buf, verifier)
w := xioutil.NewDeadlineWorker(diskMaxTimeout)
err = w.Run(func() error {
n, err = p.storage.ReadFile(ctx, volume, path, offset, buf, verifier)
return err
})
return n, err
}
// Legacy API - does not have any deadlines
@@ -432,7 +438,10 @@ func (p *xlStorageDiskIDCheck) AppendFile(ctx context.Context, volume string, pa
}
defer done(&err)
return p.storage.AppendFile(ctx, volume, path, buf)
w := xioutil.NewDeadlineWorker(diskMaxTimeout)
return w.Run(func() error {
return p.storage.AppendFile(ctx, volume, path, buf)
})
}
func (p *xlStorageDiskIDCheck) CreateFile(ctx context.Context, volume, path string, size int64, reader io.Reader) (err error) {
@@ -442,7 +451,7 @@ func (p *xlStorageDiskIDCheck) CreateFile(ctx context.Context, volume, path stri
}
defer done(&err)
return p.storage.CreateFile(ctx, volume, path, size, reader)
return p.storage.CreateFile(ctx, volume, path, size, xioutil.NewDeadlineReader(io.NopCloser(reader), diskMaxTimeout))
}
func (p *xlStorageDiskIDCheck) ReadFileStream(ctx context.Context, volume, path string, offset, length int64) (io.ReadCloser, error) {
@@ -452,9 +461,16 @@ func (p *xlStorageDiskIDCheck) ReadFileStream(ctx context.Context, volume, path
}
defer done(&err)
rc, err := p.storage.ReadFileStream(ctx, volume, path, offset, length)
w := xioutil.NewDeadlineWorker(diskMaxTimeout)
var rc io.ReadCloser
err = w.Run(func() error {
var ierr error
rc, ierr = p.storage.ReadFileStream(ctx, volume, path, offset, length)
return ierr
})
if err != nil {
return rc, err
return nil, err
}
return xioutil.NewDeadlineReader(rc, diskMaxTimeout), nil
+5
View File
@@ -536,6 +536,10 @@ func (j xlMetaV2Object) InlineData() bool {
return ok
}
func (j *xlMetaV2Object) ResetInlineData() {
delete(j.MetaSys, ReservedMetadataPrefixLower+"inline-data")
}
const (
metaTierStatus = ReservedMetadataPrefixLower + TransitionStatus
metaTierObjName = ReservedMetadataPrefixLower + TransitionedObjectName
@@ -1440,6 +1444,7 @@ func (x *xlMetaV2) DeleteVersion(fi FileInfo) (string, error) {
err = x.setIdx(i, *ver)
case fi.TransitionStatus == lifecycle.TransitionComplete:
ver.ObjectV2.SetTransition(fi)
ver.ObjectV2.ResetInlineData()
err = x.setIdx(i, *ver)
default:
x.versions = append(x.versions[:i], x.versions[i+1:]...)
+7 -36
View File
@@ -1779,6 +1779,11 @@ func (s *xlStorage) openFile(filePath string, mode int) (f *os.File, err error)
return w, nil
}
type sendFileReader struct {
io.Reader
io.Closer
}
// ReadFileStream - Returns the read stream of the file.
func (s *xlStorage) ReadFileStream(ctx context.Context, volume, path string, offset, length int64) (io.ReadCloser, error) {
if offset < 0 {
@@ -1796,14 +1801,7 @@ func (s *xlStorage) ReadFileStream(ctx context.Context, volume, path string, off
return nil, err
}
odirectEnabled := globalAPIConfig.odirectEnabled() && s.oDirect && length >= 0
var file *os.File
if odirectEnabled {
file, err = OpenFileDirectIO(filePath, readMode, 0o666)
} else {
file, err = OpenFile(filePath, readMode, 0o666)
}
file, err := OpenFile(filePath, readMode, 0o666)
if err != nil {
switch {
case osIsNotExist(err):
@@ -1852,14 +1850,6 @@ func (s *xlStorage) ReadFileStream(ctx context.Context, volume, path string, off
return nil, errFileCorrupt
}
alignment := offset%xioutil.DirectioAlignSize == 0
if !alignment && odirectEnabled {
if err = disk.DisableDirectIO(file); err != nil {
file.Close()
return nil, err
}
}
if offset > 0 {
if _, err = file.Seek(offset, io.SeekStart); err != nil {
file.Close()
@@ -1867,26 +1857,7 @@ func (s *xlStorage) ReadFileStream(ctx context.Context, volume, path string, off
}
}
or := &xioutil.ODirectReader{
File: file,
// Select bigger blocks when reading at least 50% of a big block.
SmallFile: length <= xioutil.BlockSizeLarge/2,
}
r := struct {
io.Reader
io.Closer
}{Reader: io.LimitReader(diskHealthReader(ctx, or), length), Closer: closeWrapper(func() error {
if (!alignment || offset+length%xioutil.DirectioAlignSize != 0) && odirectEnabled {
// invalidate page-cache for unaligned reads.
// skip removing from page-cache only
// if O_DIRECT was disabled.
disk.FadviseDontNeed(file)
}
return or.Close()
})}
return r, nil
return &sendFileReader{Reader: io.LimitReader(file, length), Closer: file}, nil
}
// closeWrapper converts a function to an io.Closer
+4
View File
@@ -151,3 +151,7 @@ curl https://play.min.io/minio/v2/metrics/cluster
### List of metrics reported Cluster and Bucket level
[The list of metrics reported can be here](https://github.com/minio/minio/blob/master/docs/metrics/prometheus/list.md)
### Configure Alerts for Prometheus
[The Prometheus AlertManager and alerts can be configured following this](https://github.com/minio/minio/blob/master/docs/metrics/prometheus/alerts.md)
+120
View File
@@ -0,0 +1,120 @@
# How to configure Prometheus AlertManager
Alerting with prometheus is two step process. First we setup alerts in Prometheus server and then we need to send alerts to the AlertManager.
Prometheus AlertManager is the component that manages sending, inhibition and silencing of the alerts generated from Prometheus. The AlertManager can be configured to send alerts to variety of receivers. Refer [Prometheus AlertManager receivers](https://prometheus.io/docs/alerting/latest/configuration/#receiver) for more details.
Follow below steps to enable and use AlertManager.
## Deploy and start AlertManager
Install Prometheus AlertManager from https://prometheus.io/download/ and create configuration as below
```yaml
route:
group_by: ['alertname']
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
receiver: 'web.hook'
receivers:
- name: 'web.hook'
webhook_configs:
- url: 'http://127.0.0.1:8010/webhook'
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
```
This sample confoguration uses a `webhook` at http://127.0.0.1:8010/webhook to post the alerts.
Start the AlertManager and it listens on port `9093` by default. Make sure your webhook is up and listening for the alerts.
## Configure Prometheus to use AlertManager
Add below section to your `prometheus.yml`
```yaml
alerting:
alertmanagers:
- static_configs:
- targets: ['localhost:9093']
rule_files:
- rules.yml
```
Here `rules.yml` is the file which should contain the alerting rules defined.
## Add rules for your deployment
Below is a sample alerting rules configuration for MinIO. Refer https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ for more instructions on writing alerting rules for Prometheus.
```yaml
groups:
- name: example
rules:
- alert: MinIOClusterTolerance
expr: minio_cluster_health_erasure_set_tolerance <= 0
for: 5m
labels:
severity: critical
annotations:
summary: "Instance {{ $labels.server }} unable to tolerate node failures"
description: "MinIO instance {{ $labels.server }} of job {{ $labels.job }} has tolerance <=0 for more than 5 minutes."
```
## Verify the configuration and alerts
To verify the above sample alert follow below steps
1. Start a distributed MinIO instance (4 nodes setup)
2. Start Prometheus server and AlertManager
3. Bring down couple of MinIO instances to bring down the Erasure Set tolerance to -1 and verify the same with `mc admin prometheus metrics ALIAS | grep minio_cluster_health_erasure_set_tolerance`
4. Wait for 5 mins (as alert is configured to be firing after 5 mins), and verify that you see an entry in webhook for the alert as well as in Prometheus console as shown below
```json
{
"receiver": "web\\.hook",
"status": "firing",
"alerts": [
{
"status": "firing",
"labels": {
"alertname": "MinIOClusterTolerance",
"instance": "localhost:9000",
"job": "minio-job-node",
"pool": "0",
"server": "127.0.0.1:9000",
"set": "0",
"severity": "critical"
},
"annotations": {
"description": "MinIO instance 127.0.0.1:9000 of job minio-job has tolerance <=0 for more than 5 minutes.",
"summary": "Instance 127.0.0.1:9000 unable to tolerate node failures"
},
"startsAt": "2023-11-18T06:20:09.456Z",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://fedora-shubhendu:9090/graph?g0.expr=minio_cluster_health_erasure_set_tolerance+%3C%3D+0&g0.tab=1",
"fingerprint": "2255608b0da28ca3"
}
],
"groupLabels": {
"alertname": "MinIOClusterTolerance"
},
"commonLabels": {
"alertname": "MinIOClusterTolerance",
"instance": "localhost:9000",
"job": "minio-job-node",
"pool": "0",
"server": "127.0.0.1:9000",
"set": "0",
"severity": "critical"
},
"commonAnnotations": {
"description": "MinIO instance 127.0.0.1:9000 of job minio-job has tolerance <=0 for more than 5 minutes.",
"summary": "Instance 127.0.0.1:9000 unable to tolerate node failures"
},
"externalURL": "http://fedora-shubhendu:9093",
"version": "4",
"groupKey": "{}:{alertname=\"MinIOClusterTolerance\"}",
"truncatedAlerts": 0
}
```
![Prometheus](https://raw.githubusercontent.com/minio/minio/master/docs/metrics/prometheus/minio-es-tolerance-alert.png)
@@ -4540,7 +4540,7 @@
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 6,
"h": 5,
"w": 6,
"x": 18,
"y": 109
@@ -4616,6 +4616,94 @@
"yaxis": {
"align": false
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 6,
"w": 12,
"x": 0,
"y": 115
},
"hiddenSeries": false,
"id": 94,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "10.0.2",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"exemplar": true,
"expr": "minio_cluster_health_erasure_set_tolerance{job=\"$scrape_jobs\"}",
"interval": "",
"legendFormat": "Pool: {{pool}} Set: {{set}}",
"refId": "A"
}
],
"thresholds": [],
"timeRegions": [],
"title": "Erasure Set Tolerance",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:1043",
"format": "none",
"logBase": 1,
"show": true
},
{
"$$hashKey": "object:1044",
"format": "short",
"logBase": 1,
"show": true
}
],
"yaxis": {
"align": false
}
}
],
"refresh": "",
Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

@@ -2,7 +2,7 @@ version: '3.7'
# Settings and configurations that are common for all containers
x-minio-common: &minio-common
image: quay.io/minio/minio:RELEASE.2023-11-01T18-37-25Z
image: quay.io/minio/minio:RELEASE.2023-11-15T20-43-25Z
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
expose:
- "9000"
+2 -2
View File
@@ -49,9 +49,9 @@ require (
github.com/minio/highwayhash v1.0.2
github.com/minio/kes-go v0.2.0
github.com/minio/madmin-go/v3 v3.0.29
github.com/minio/minio-go/v7 v7.0.64-0.20230920204636-e783c9ba11b3
github.com/minio/minio-go/v7 v7.0.64-0.20231119012610-6eebdd6d5eba
github.com/minio/mux v1.9.0
github.com/minio/pkg/v2 v2.0.2
github.com/minio/pkg/v2 v2.0.3-0.20231107172951-8a60b89ec9b4
github.com/minio/selfupdate v0.6.0
github.com/minio/sha256-simd v1.0.1
github.com/minio/simdjson-go v0.4.5
+4 -4
View File
@@ -485,14 +485,14 @@ github.com/minio/mc v0.0.0-20231030184332-9f2fb2b6a9f8/go.mod h1:SoPU55ntH5d6IEq
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
github.com/minio/minio-go/v7 v7.0.64-0.20230920204636-e783c9ba11b3 h1:0DtfDxg67S/IRcGnIBKgzyjZ0GXyk3jN1Fy5/+8CQlM=
github.com/minio/minio-go/v7 v7.0.64-0.20230920204636-e783c9ba11b3/go.mod h1:Q6X7Qjb7WMhvG65qKf4gUgA5XaiSox74kR1uAEjxRS4=
github.com/minio/minio-go/v7 v7.0.64-0.20231119012610-6eebdd6d5eba h1:OF4FT9fUnXReMpMLQyoydIYJYPN3W3omFuD/TTL8cZg=
github.com/minio/minio-go/v7 v7.0.64-0.20231119012610-6eebdd6d5eba/go.mod h1:R4WVUR6ZTedlCcGwZRauLMIKjgyaWxhs4Mqi/OMPmEc=
github.com/minio/mux v1.9.0 h1:dWafQFyEfGhJvK6AwLOt83bIG5bxKxKJnKMCi0XAaoA=
github.com/minio/mux v1.9.0/go.mod h1:1pAare17ZRL5GpmNL+9YmqHoWnLmMZF9C/ioUCfy0BQ=
github.com/minio/pkg v1.7.5 h1:UOUJjewE5zoaDPlCMJtNx/swc1jT1ZR+IajT7hrLd44=
github.com/minio/pkg v1.7.5/go.mod h1:mEfGMTm5Z0b5EGxKNuPwyb5A2d+CC/VlUyRj6RJtIwo=
github.com/minio/pkg/v2 v2.0.2 h1:cytXmC21fBNS+0NVKEE5FuYmQfY+HFTqis6Kkj3U9ac=
github.com/minio/pkg/v2 v2.0.2/go.mod h1:6xTAr5M9yobpUroXAAaTrGJ9fhOZIqKYOT0I87u2yZ4=
github.com/minio/pkg/v2 v2.0.3-0.20231107172951-8a60b89ec9b4 h1:5eHjHtFZrrCQ3eO0sesXomdAUTtcGh0Fpp7Qa6dtjrY=
github.com/minio/pkg/v2 v2.0.3-0.20231107172951-8a60b89ec9b4/go.mod h1:6xTAr5M9yobpUroXAAaTrGJ9fhOZIqKYOT0I87u2yZ4=
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
+1 -1
View File
@@ -94,7 +94,7 @@ var (
},
config.HelpKV{
Key: apiODirect,
Description: "set to enable or disable O_DIRECT for read and writes under special conditions. NOTE: do not disable O_DIRECT without prior testing" + defaultHelpPostfix(apiODirect),
Description: "set to enable or disable O_DIRECT for writes under special conditions. NOTE: do not disable O_DIRECT without prior testing" + defaultHelpPostfix(apiODirect),
Optional: true,
Type: "boolean",
},
+8
View File
@@ -83,3 +83,11 @@ const (
EnvRegion = "MINIO_REGION" // legacy
EnvRegionName = "MINIO_REGION_NAME" // legacy
)
// Expiration Token durations
// These values are used to validate the expiration time range from
// either the exp claim or MINI_STS_DURATION value
const (
MinExpiration = 900
MaxExpiration = 31536000
)
+1 -9
View File
@@ -114,8 +114,7 @@ func updateClaimsExpiry(dsecs string, claims map[string]interface{}) error {
return nil
}
expAt, err := auth.ExpToInt64(expStr)
if err != nil {
if _, err := auth.ExpToInt64(expStr); err != nil {
return err
}
@@ -124,13 +123,6 @@ func updateClaimsExpiry(dsecs string, claims map[string]interface{}) error {
return err
}
// Verify if JWT expiry is lesser than default expiry duration,
// if that is the case then set the default expiration to be
// from the JWT expiry claim.
if time.Unix(expAt, 0).UTC().Sub(time.Now().UTC()) < defaultExpiryDuration {
defaultExpiryDuration = time.Unix(expAt, 0).UTC().Sub(time.Now().UTC())
} // else honor the specified expiry duration.
claims["exp"] = time.Now().UTC().Add(defaultExpiryDuration).Unix() // update with new expiry.
return nil
}
+10 -3
View File
@@ -603,9 +603,9 @@ func GetDefaultExpiration(dsecs string) (time.Duration, error) {
timeout := env.Get(config.EnvMinioStsDuration, "")
defaultExpiryDuration, err := time.ParseDuration(timeout)
if err != nil {
defaultExpiryDuration = time.Duration(60) * time.Minute
defaultExpiryDuration = time.Hour
}
if dsecs != "" {
if timeout == "" && dsecs != "" {
expirySecs, err := strconv.ParseInt(dsecs, 10, 64)
if err != nil {
return 0, auth.ErrInvalidDuration
@@ -614,11 +614,18 @@ func GetDefaultExpiration(dsecs string) (time.Duration, error) {
// The duration, in seconds, of the role session.
// The value can range from 900 seconds (15 minutes)
// up to 365 days.
if expirySecs < 900 || expirySecs > 31536000 {
if expirySecs < config.MinExpiration || expirySecs > config.MaxExpiration {
return 0, auth.ErrInvalidDuration
}
defaultExpiryDuration = time.Duration(expirySecs) * time.Second
} else if timeout == "" && dsecs == "" {
return time.Hour, nil
}
if defaultExpiryDuration.Seconds() < config.MinExpiration || defaultExpiryDuration.Seconds() > config.MaxExpiration {
return 0, auth.ErrInvalidDuration
}
return defaultExpiryDuration, nil
}
+2 -2
View File
@@ -30,7 +30,7 @@ import (
"time"
"github.com/minio/minio/internal/config/lambda/event"
xioutil "github.com/minio/minio/internal/ioutil"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/certs"
xnet "github.com/minio/pkg/v2/net"
@@ -133,7 +133,7 @@ func (target *WebhookTarget) isActive() (bool, error) {
}
return false, err
}
xioutil.DiscardReader(resp.Body)
xhttp.DrainBody(resp.Body)
// No network failure i.e response from the target means its up
return true, nil
}
@@ -18,6 +18,7 @@ package target
import (
"crypto/sha512"
"strings"
"github.com/IBM/sarama"
"github.com/xdg/scram"
@@ -26,7 +27,7 @@ import (
)
func initScramClient(args KafkaArgs, config *sarama.Config) {
switch args.SASL.Mechanism {
switch strings.ToLower(args.SASL.Mechanism) {
case "sha512":
config.Net.SASL.SCRAMClientGeneratorFunc = func() sarama.SCRAMClient { return &XDGSCRAMClient{HashGeneratorFcn: KafkaSHA512} }
config.Net.SASL.Mechanism = sarama.SASLMechanism(sarama.SASLTypeSCRAMSHA512)
+2 -2
View File
@@ -34,7 +34,7 @@ import (
"time"
"github.com/minio/minio/internal/event"
xioutil "github.com/minio/minio/internal/ioutil"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/once"
"github.com/minio/minio/internal/store"
@@ -196,7 +196,7 @@ func (target *WebhookTarget) send(eventData event.Event) error {
if err != nil {
return err
}
defer xioutil.DiscardReader(resp.Body)
defer xhttp.DrainBody(resp.Body)
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return fmt.Errorf("sending event failed with %v", resp.Status)
+16
View File
@@ -19,6 +19,7 @@ package http
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
@@ -29,6 +30,7 @@ import (
// status code and to record the response body
type ResponseRecorder struct {
http.ResponseWriter
io.ReaderFrom
StatusCode int
// Log body of 4xx or 5xx responses
LogErrBody bool
@@ -51,13 +53,27 @@ type ResponseRecorder struct {
// NewResponseRecorder - returns a wrapped response writer to trap
// http status codes for auditing purposes.
func NewResponseRecorder(w http.ResponseWriter) *ResponseRecorder {
rf, _ := w.(io.ReaderFrom)
return &ResponseRecorder{
ResponseWriter: w,
ReaderFrom: rf,
StatusCode: http.StatusOK,
StartTime: time.Now().UTC(),
}
}
// ErrNotImplemented when a functionality is not implemented
var ErrNotImplemented = errors.New("not implemented")
// ReadFrom implements support for calling internal io.ReaderFrom implementations
// returns an error if the underlying ResponseWriter does not implement io.ReaderFrom
func (lrw *ResponseRecorder) ReadFrom(r io.Reader) (int64, error) {
if lrw.ReaderFrom != nil {
return lrw.ReaderFrom.ReadFrom(r)
}
return 0, ErrNotImplemented
}
func (lrw *ResponseRecorder) Write(p []byte) (int, error) {
if !lrw.headersLogged {
// We assume the response code to be '200 OK' when WriteHeader() is not called,
-4
View File
@@ -379,10 +379,6 @@ func (h *Target) SendFromStore(key store.Key) (err error) {
// Messages are queued in the disk if the store is enabled
// If Cancel has been called the message is ignored.
func (h *Target) Send(ctx context.Context, entry interface{}) error {
if atomic.LoadInt32(&h.status) == statusOffline {
h.config.LogOnce(ctx, fmt.Errorf("target %s is offline", h.Endpoint()), h.Endpoint())
return nil
}
if atomic.LoadInt32(&h.status) == statusClosed {
return nil
}
@@ -19,6 +19,7 @@ package kafka
import (
"crypto/sha512"
"strings"
"github.com/IBM/sarama"
"github.com/xdg/scram"
@@ -27,7 +28,7 @@ import (
)
func initScramClient(cfg Config, config *sarama.Config) {
switch cfg.SASL.Mechanism {
switch strings.ToLower(cfg.SASL.Mechanism) {
case "sha512":
config.Net.SASL.SCRAMClientGeneratorFunc = func() sarama.SCRAMClient { return &XDGSCRAMClient{HashGeneratorFcn: KafkaSHA512} }
config.Net.SASL.Mechanism = sarama.SASLMechanism(sarama.SASLTypeSCRAMSHA512)