diff --git a/cmd/bucket-resource-boundary_test.go b/cmd/bucket-resource-boundary_test.go new file mode 100644 index 000000000..07f797f3c --- /dev/null +++ b/cmd/bucket-resource-boundary_test.go @@ -0,0 +1,379 @@ +// Copyright (c) 2015-2026 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 . + +package cmd + +import ( + "bytes" + "context" + "fmt" + "net/http" + "net/http/httptest" + "runtime" + "strings" + "testing" + "time" + + "github.com/minio/madmin-go/v3" + "github.com/minio/minio-go/v7" + "github.com/minio/minio/internal/auth" + "github.com/minio/pkg/v3/policy" +) + +// These tests pin the IAM bucket/object resource boundary end to end, through +// the real handlers rather than the matcher alone. An object-only resource +// pattern ("arn:aws:s3:::bucket/*") must not authorize the bucket-level writes +// that hand a caller something its object access does not already provide — +// upstream minio/minio issue #20449 — while every shape the fix deliberately +// leaves alone keeps working. Both directions are asserted, because a change +// here that only removes permissions is correct and one that adds any is not. + +// A bucket-level write reached only through an object-only grant must be +// refused, and refusing it must not destroy the bucket. A grant that names the +// bucket must still succeed, and succeeding must actually remove it. +func assertBucketDelete(ctx context.Context, c *check, admin, client *minio.Client, bucket string, wantAllowed bool) { + c.Helper() + err := client.RemoveBucket(ctx, bucket) + if wantAllowed { + if err != nil { + c.Fatalf("RemoveBucket(%s) denied, want allowed: %v", bucket, err) + } + exists, existsErr := admin.BucketExists(ctx, bucket) + if existsErr != nil { + c.Fatalf("check removed bucket %s: %v", bucket, existsErr) + } + if exists { + c.Fatalf("RemoveBucket(%s) returned nil but bucket still exists", bucket) + } + return + } + + if err == nil { + c.Fatalf("RemoveBucket(%s) returned nil, want AccessDenied", bucket) + } + if response := minio.ToErrorResponse(err); response.Code != "AccessDenied" { + c.Fatalf("RemoveBucket(%s) error code=%q, want AccessDenied (err=%v)", bucket, response.Code, err) + } + exists, existsErr := admin.BucketExists(ctx, bucket) + if existsErr != nil { + c.Fatalf("check protected bucket %s: %v", bucket, existsErr) + } + if !exists { + c.Fatalf("RemoveBucket(%s) returned AccessDenied but bucket disappeared", bucket) + } + if err := admin.RemoveBucket(ctx, bucket); err != nil { + c.Fatalf("cleanup protected bucket %s: %v", bucket, err) + } +} + +func createUserWithPolicy(ctx context.Context, c *check, s *TestSuiteIAM, policyJSON []byte) (*minio.Client, string) { + c.Helper() + accessKey, secretKey := mustGenerateCredentials(c) + if err := s.adm.SetUser(ctx, accessKey, secretKey, madmin.AccountEnabled); err != nil { + c.Fatalf("set boundary user: %v", err) + } + policyName := "boundary-" + mustGetUUID() + if err := s.adm.AddCannedPolicy(ctx, policyName, policyJSON); err != nil { + c.Fatalf("add boundary policy: %v", err) + } + if _, err := s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{ + Policies: []string{policyName}, + User: accessKey, + }); err != nil { + c.Fatalf("attach boundary policy: %v", err) + } + return s.getUserClient(c, accessKey, secretKey, ""), accessKey +} + +func TestBucketResourceBoundaryEndToEnd(t *testing.T) { + if runtime.GOOS == globalWindowsOSName { + t.Skip("IAM integration harness is disabled on Windows") + } + suite := newTestSuiteIAM(TestSuiteCommon{serverType: "ErasureSD", signer: signerV4}, false) + c := &check{t, suite.serverType} + suite.SetUpSuite(c) + defer suite.TearDownSuite(c) + + ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) + defer cancel() + + // The #20449 reproduction: "s3:*" on "bucket/*" and nothing else. + coreBucket := getRandomBucketName() + if err := suite.client.MakeBucket(ctx, coreBucket, minio.MakeBucketOptions{}); err != nil { + c.Fatalf("create core bucket: %v", err) + } + corePolicy := fmt.Appendf(nil, `{ + "Version":"2012-10-17", + "Statement":[{"Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::%s/*"}] +}`, coreBucket) + coreClient, _ := createUserWithPolicy(ctx, c, suite, corePolicy) + assertBucketDelete(ctx, c, suite.client, coreClient, coreBucket, false) + + // The conventional pairing of bucket and object ARNs stays authorized. + pairedBucket := getRandomBucketName() + if err := suite.client.MakeBucket(ctx, pairedBucket, minio.MakeBucketOptions{}); err != nil { + c.Fatalf("create paired bucket: %v", err) + } + pairedPolicy := fmt.Appendf(nil, `{ + "Version":"2012-10-17", + "Statement":[{"Effect":"Allow","Action":"s3:*","Resource":[ + "arn:aws:s3:::%s","arn:aws:s3:::%s/*" + ]}] +}`, pairedBucket, pairedBucket) + pairedClient, _ := createUserWithPolicy(ctx, c, suite, pairedPolicy) + assertBucketDelete(ctx, c, suite.client, pairedClient, pairedBucket, true) + + // CreateBucket and ListBucket keep the historical object-pattern matching. + compatBucket := getRandomBucketName() + compatPolicy := fmt.Appendf(nil, `{ + "Version":"2012-10-17", + "Statement":[{"Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::%s/*"}] +}`, compatBucket) + compatClient, _ := createUserWithPolicy(ctx, c, suite, compatPolicy) + if err := compatClient.MakeBucket(ctx, compatBucket, minio.MakeBucketOptions{}); err != nil { + c.Fatalf("CreateBucket compatibility path denied: %v", err) + } + for item := range compatClient.ListObjects(ctx, compatBucket, minio.ListObjectsOptions{}) { + if item.Err != nil { + c.Fatalf("ListBucket compatibility path denied: %v", item.Err) + } + } + if err := suite.client.RemoveBucket(ctx, compatBucket); err != nil { + c.Fatalf("cleanup compatibility bucket: %v", err) + } + + // Withholding the trailing slash changes the string patterns match against, + // so a fixed-width wildcard can match the bare bucket name without ever + // having matched "bucket/". Honoring it would GRANT a delete the historical + // matcher refused, which the hardening must never do. + wildcardBucket := getRandomBucketName() + if err := suite.client.MakeBucket(ctx, wildcardBucket, minio.MakeBucketOptions{}); err != nil { + c.Fatalf("create fixed-width wildcard bucket: %v", err) + } + wildcardPolicy := fmt.Appendf(nil, `{ + "Version":"2012-10-17", + "Statement":[{"Effect":"Allow","Action":"s3:DeleteBucket","Resource":"arn:aws:s3:::%s?"}] +}`, wildcardBucket[:len(wildcardBucket)-1]) + wildcardClient, _ := createUserWithPolicy(ctx, c, suite, wildcardPolicy) + assertBucketDelete(ctx, c, suite.client, wildcardClient, wildcardBucket, false) + + // A NotResource exclusion keeps its historical reach, so narrowing it — and + // thereby broadening the Allow it qualifies — cannot happen unnoticed. + notResourceBucket := getRandomBucketName() + if err := suite.client.MakeBucket(ctx, notResourceBucket, minio.MakeBucketOptions{}); err != nil { + c.Fatalf("create NotResource bucket: %v", err) + } + notResourcePolicy := fmt.Appendf(nil, `{ + "Version":"2012-10-17", + "Statement":[{"Effect":"Allow","Action":"s3:*","NotResource":"arn:aws:s3:::%s/*"}] +}`, notResourceBucket) + notResourceClient, _ := createUserWithPolicy(ctx, c, suite, notResourcePolicy) + assertBucketDelete(ctx, c, suite.client, notResourceClient, notResourceBucket, false) + + // A Deny written against "bucket/*" keeps covering the bucket-level request. + denyBucket := getRandomBucketName() + if err := suite.client.MakeBucket(ctx, denyBucket, minio.MakeBucketOptions{}); err != nil { + c.Fatalf("create Deny bucket: %v", err) + } + denyPolicy := fmt.Appendf(nil, `{ + "Version":"2012-10-17", + "Statement":[ + {"Effect":"Allow","Action":"s3:*","Resource":"*"}, + {"Effect":"Deny","Action":"s3:DeleteBucket","Resource":"arn:aws:s3:::%s/*"} + ] +}`, denyBucket) + denyClient, _ := createUserWithPolicy(ctx, c, suite, denyPolicy) + assertBucketDelete(ctx, c, suite.client, denyClient, denyBucket, false) + + // A service account whose parent policy allows everything but whose inline + // policy is object-only exercises the nested AND evaluation path, where the + // boundary has to hold on the inline side. + serviceBucket := getRandomBucketName() + if err := suite.client.MakeBucket(ctx, serviceBucket, minio.MakeBucketOptions{}); err != nil { + c.Fatalf("create service-account bucket: %v", err) + } + parentPolicy := []byte(`{ + "Version":"2012-10-17", + "Statement":[{"Effect":"Allow","Action":"s3:*","Resource":"*"}] +}`) + _, parentUser := createUserWithPolicy(ctx, c, suite, parentPolicy) + servicePolicy := fmt.Appendf(nil, `{ + "Version":"2012-10-17", + "Statement":[{"Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::%s/*"}] +}`, serviceBucket) + serviceAccess, serviceSecret := mustGenerateCredentials(c) + serviceAccount, err := suite.adm.AddServiceAccount(ctx, madmin.AddServiceAccountReq{ + TargetUser: parentUser, + AccessKey: serviceAccess, + SecretKey: serviceSecret, + Policy: bytes.Clone(servicePolicy), + }) + if err != nil { + c.Fatalf("create restricted service account: %v", err) + } + serviceClient := suite.getUserClient(c, serviceAccount.AccessKey, serviceAccount.SecretKey, "") + assertBucketDelete(ctx, c, suite.client, serviceClient, serviceBucket, false) +} + +// The same boundary, asserted against an inline session policy evaluated +// directly, so a regression in the STS and service-account paths is caught even +// if the integration harness above is skipped. +func TestBucketResourceBoundaryInlineSessionPolicy(t *testing.T) { + inline := `{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": "s3:*", + "Resource": "arn:aws:s3:::mybucket/*" + }] +}` + args := policy.Args{ + Action: policy.DeleteBucketAction, + BucketName: "mybucket", + Claims: map[string]any{ + sessionPolicyNameExtracted: inline, + }, + } + + for name, evaluate := range map[string]func(policy.Args) (bool, bool){ + "STS inline policy": isAllowedBySessionPolicy, + "service-account inline policy": isAllowedBySessionPolicyForServiceAccount, + } { + hasPolicy, allowed := evaluate(args) + if !hasPolicy { + t.Errorf("%s was not detected", name) + continue + } + if allowed { + t.Errorf("%s authorized DeleteBucket through an object-only resource", name) + } + } +} + +// The boundary driven through the real S3 router and DeleteBucket handler, +// without opening a TCP listener. +func TestBucketResourceBoundaryHandler(t *testing.T) { + ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{ + t: t, + endpoints: nil, // Register the full router; the focused list omits DeleteBucket. + objAPITest: func(obj ObjectLayer, instanceType, _ string, apiRouter http.Handler, _ auth.Credentials, t *testing.T) { + ctx := t.Context() + if !globalReplicationPool.IsSet() { + // Match initTestServerWithBackend: DeleteBucket calls through this + // singleton after the object-layer deletion, and a nil receiver is safe. + globalReplicationPool.Set(nil) + } + + newPolicyClient := func(policyJSON string) auth.Credentials { + t.Helper() + accessKey, secretKey, err := auth.GenerateCredentials() + if err != nil { + t.Fatal(err) + } + credentials := auth.Credentials{AccessKey: accessKey, SecretKey: secretKey} + if _, err = globalIAMSys.CreateUser(ctx, credentials.AccessKey, madmin.AddOrUpdateUserReq{ + SecretKey: credentials.SecretKey, + Status: madmin.AccountEnabled, + }); err != nil { + t.Fatalf("%s: create boundary user: %v", instanceType, err) + } + parsed, err := policy.ParseConfig(strings.NewReader(policyJSON)) + if err != nil { + t.Fatalf("%s: parse boundary policy: %v", instanceType, err) + } + policyName := "boundary-" + mustGetUUID() + if _, err = globalIAMSys.SetPolicy(ctx, policyName, *parsed); err != nil { + t.Fatalf("%s: install boundary policy: %v", instanceType, err) + } + if _, err = globalIAMSys.PolicyDBSet(ctx, credentials.AccessKey, policyName, regUser, false); err != nil { + t.Fatalf("%s: attach boundary policy: %v", instanceType, err) + } + return credentials + } + + deleteCase := func(label, policyJSON string, wantAllowed bool) { + t.Helper() + bucket := getRandomBucketName() + if err := obj.MakeBucket(ctx, bucket, MakeBucketOptions{}); err != nil { + t.Fatalf("%s/%s: create bucket: %v", instanceType, label, err) + } + policyJSON = strings.ReplaceAll(policyJSON, "BUCKET_WILDCARD", bucket[:len(bucket)-1]+"?") + policyJSON = strings.ReplaceAll(policyJSON, "BUCKET", bucket) + credentials := newPolicyClient(policyJSON) + req, err := newTestSignedRequestV4(http.MethodDelete, getDeleteBucketURL("", bucket), + 0, nil, credentials.AccessKey, credentials.SecretKey, nil) + if err != nil { + t.Fatalf("%s/%s: sign DeleteBucket: %v", instanceType, label, err) + } + recorder := httptest.NewRecorder() + apiRouter.ServeHTTP(recorder, req) + + if wantAllowed { + if recorder.Code != http.StatusNoContent { + t.Fatalf("%s/%s: DeleteBucket status=%d body=%s, want 204", + instanceType, label, recorder.Code, recorder.Body.String()) + } + if _, err = obj.GetBucketInfo(ctx, bucket, BucketOptions{}); err == nil { + t.Fatalf("%s/%s: handler returned 204 but bucket still exists", instanceType, label) + } + return + } + + if recorder.Code != http.StatusForbidden || !strings.Contains(recorder.Body.String(), "AccessDenied") { + t.Fatalf("%s/%s: DeleteBucket status=%d body=%s, want AccessDenied", + instanceType, label, recorder.Code, recorder.Body.String()) + } + if _, err = obj.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil { + t.Fatalf("%s/%s: denied bucket disappeared: %v", instanceType, label, err) + } + if err = obj.DeleteBucket(ctx, bucket, DeleteBucketOptions{}); err != nil { + t.Fatalf("%s/%s: cleanup bucket: %v", instanceType, label, err) + } + } + + deleteCase("object-only s3:*", `{ + "Version":"2012-10-17", + "Statement":[{"Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::BUCKET/*"}] +}`, false) + + deleteCase("paired resource", `{ + "Version":"2012-10-17", + "Statement":[{"Effect":"Allow","Action":"s3:*","Resource":[ + "arn:aws:s3:::BUCKET","arn:aws:s3:::BUCKET/*" + ]}] +}`, true) + + deleteCase("fixed-width bucket wildcard", `{ + "Version":"2012-10-17", + "Statement":[{"Effect":"Allow","Action":"s3:DeleteBucket","Resource":"arn:aws:s3:::BUCKET_WILDCARD"}] +}`, false) + + deleteCase("NotResource exclusion", `{ + "Version":"2012-10-17", + "Statement":[{"Effect":"Allow","Action":"s3:*","NotResource":"arn:aws:s3:::BUCKET/*"}] +}`, false) + + deleteCase("Deny coverage", `{ + "Version":"2012-10-17", + "Statement":[ + {"Effect":"Allow","Action":"s3:*","Resource":"*"}, + {"Effect":"Deny","Action":"s3:DeleteBucket","Resource":"arn:aws:s3:::BUCKET/*"} + ] +}`, false) + }, + }) +} diff --git a/go.mod b/go.mod index 762e8e5e1..5fbb98a2a 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,11 @@ replace github.com/minio/console => github.com/pgsty/silo-console v0.0.0-2026080 // Use Pigsty's maintained mc fork for Console's embedded client code. replace github.com/minio/mc => github.com/pgsty/mc v0.0.0-20260801042411-ad10a2a10b76 -// Fix LDAP TLS regression: DialURL() was not passing TLS config for ldaps:// connections, -// causing InsecureSkipVerify, RootCAs, and other TLS settings to be silently ignored. -// See: https://github.com/pgsty/minio/issues/15 -replace github.com/minio/pkg/v3 => github.com/pgsty/minio-pkg/v3 v3.6.3 +// Use Pigsty's maintained SILO package fork while preserving upstream import paths. +// This retains the LDAP TLS fix tracked in https://github.com/pgsty/minio/issues/15. +// v3.11.0 follows upstream minio/pkg's 3.11 line and carries the +// minio/minio#20449 bucket-write boundary hardening. +replace github.com/minio/pkg/v3 => github.com/pgsty/silo-pkg/v3 v3.11.0 // v22.7.0 does not compile on NetBSD because its unix implementation uses // CLOCK_MONOTONIC, which is unavailable there. Keep the last portable release @@ -47,7 +48,7 @@ require ( github.com/fatih/color v1.19.0 github.com/felixge/fgprof v0.9.5 github.com/fraugster/parquet-go v0.12.0 - github.com/go-ldap/ldap/v3 v3.4.12 + github.com/go-ldap/ldap/v3 v3.4.14 github.com/go-openapi/loads v0.23.3 github.com/go-sql-driver/mysql v1.9.3 github.com/gobwas/ws v1.4.0 @@ -103,7 +104,7 @@ require ( github.com/rs/cors v1.11.1 github.com/secure-io/sio-go v0.3.1 github.com/shirou/gopsutil/v3 v3.24.5 - github.com/tinylib/msgp v1.6.3 + github.com/tinylib/msgp v1.6.4 github.com/valyala/bytebufferpool v1.0.0 github.com/xdg/scram v1.0.5 github.com/zeebo/xxh3 v1.1.0 @@ -111,7 +112,7 @@ require ( go.etcd.io/etcd/client/v3 v3.6.9 go.uber.org/atomic v1.11.0 go.uber.org/zap v1.28.0 - go.yaml.in/yaml/v3 v3.0.4 + go.yaml.in/yaml/v3 v3.0.5 goftp.io/server/v2 v2.0.2 golang.org/x/crypto v0.54.0 golang.org/x/oauth2 v0.36.0 @@ -169,7 +170,7 @@ require ( github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -184,7 +185,7 @@ require ( github.com/go-openapi/strfmt v0.26.2 // indirect github.com/go-openapi/swag v0.25.5 // indirect github.com/go-openapi/swag/cmdutils v0.25.5 // indirect - github.com/go-openapi/swag/conv v0.25.5 // indirect + github.com/go-openapi/swag/conv v0.28.0 // indirect github.com/go-openapi/swag/fileutils v0.25.5 // indirect github.com/go-openapi/swag/jsonname v0.26.0 // indirect github.com/go-openapi/swag/jsonutils v0.25.5 // indirect @@ -192,7 +193,7 @@ require ( github.com/go-openapi/swag/mangling v0.25.5 // indirect github.com/go-openapi/swag/netutils v0.25.5 // indirect github.com/go-openapi/swag/stringutils v0.25.5 // indirect - github.com/go-openapi/swag/typeutils v0.25.5 // indirect + github.com/go-openapi/swag/typeutils v0.28.0 // indirect github.com/go-openapi/swag/yamlutils v0.25.5 // indirect github.com/go-openapi/validate v0.25.2 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect @@ -238,9 +239,9 @@ require ( github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-ieproxy v0.0.12 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.21 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect diff --git a/go.sum b/go.sum index 42e79abf1..a0a22277c 100644 --- a/go.sum +++ b/go.sum @@ -184,14 +184,14 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu github.com/fraugster/parquet-go v0.12.0 h1:1slnC5y2VWEOUSlzbeXatM0BvSWcLUDsR/EcZsXXCZc= github.com/fraugster/parquet-go v0.12.0/go.mod h1:dGzUxdNqXsAijatByVgbAWVPlFirnhknQbdazcUIjY0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= -github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ= +github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= -github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= -github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= +github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs= +github.com/go-ldap/ldap/v3 v3.4.14/go.mod h1:S4eJUMUNjDkE0ZJtIZdybwyb03sGGLW6gxXT1Hs8VKA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -220,8 +220,8 @@ github.com/go-openapi/swag v0.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+T github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA= github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c= github.com/go-openapi/swag/cmdutils v0.25.5/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= -github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g= -github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k= +github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8= +github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk= github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc= github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= @@ -238,14 +238,14 @@ github.com/go-openapi/swag/netutils v0.25.5 h1:LZq2Xc2QI8+7838elRAaPCeqJnHODfSyO github.com/go-openapi/swag/netutils v0.25.5/go.mod h1:lHbtmj4m57APG/8H7ZcMMSWzNqIQcu0RFiXrPUara14= github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M= github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII= -github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E= -github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc= +github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY= +github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ= github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ= github.com/go-openapi/testify/enable/yaml/v2 v2.4.1 h1:NZOrZmIb6PTv5LTFxr5/mKV/FjbUzGE7E6gLz7vFoOQ= github.com/go-openapi/testify/enable/yaml/v2 v2.4.1/go.mod h1:r7dwsujEHawapMsxA69i+XMGZrQ5tRauhLAjV/sxg3Q= -github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= -github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-openapi/validate v0.25.2 h1:12NsfLAwGegqbGWr2CnvT65X/Q2USJipmJ9b7xDJZz0= github.com/go-openapi/validate v0.25.2/go.mod h1:Pgl1LpPPGFnZ+ys4/hTlDiRYQdI1ocKypgE+8Q8BLfY= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -431,15 +431,15 @@ github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIv github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-ieproxy v0.0.12 h1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M= github.com/mattn/go-ieproxy v0.0.12/go.mod h1:Vn+N61199DAnVeTgaF8eoB9PvLO8P3OBnG95ENh7B7c= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -549,10 +549,10 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pgsty/mc v0.0.0-20260801042411-ad10a2a10b76 h1:UIlUuz0LQKw4QlAljhv7nPDDFC1+n+e0iED7rWZrgZ8= github.com/pgsty/mc v0.0.0-20260801042411-ad10a2a10b76/go.mod h1:cTbS+9jGR4Qs7xTf5DEhmCTbzcDWrKMs8ZmTUnCU49E= -github.com/pgsty/minio-pkg/v3 v3.6.3 h1:lskojLFaoMfn9dWTgfRAd0n3RdQ/J+XsCKr77UlBl4g= -github.com/pgsty/minio-pkg/v3 v3.6.3/go.mod h1:Uyp31bhmWTpGpBiVir0wgouN9iUYipSm4nYc/veAgjI= github.com/pgsty/silo-console v0.0.0-20260804042150-b952a1202869 h1:HLfc2ZdAycnI/bLl+TdsuckyzSWrwQarK38pS14yH/Q= github.com/pgsty/silo-console v0.0.0-20260804042150-b952a1202869/go.mod h1:7J8wCQsNT5S7GqCHnQqgj0T7Nagp1fklJhfBJI+v0XI= +github.com/pgsty/silo-pkg/v3 v3.11.0 h1:wjN5d+tWD8Twq+e7k/KBBVhnWXC8xTIlfTcnGIKkmjc= +github.com/pgsty/silo-pkg/v3 v3.11.0/go.mod h1:E2AB4oOgfDeb9In1KDBTrn9wzfvr0WzoPkbXW7wbwBQ= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= @@ -654,8 +654,8 @@ github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= -github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= +github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ= +github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= @@ -726,8 +726,8 @@ go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goftp.io/server/v2 v2.0.2 h1:tkZpqyXys+vC15W5yGMi8Kzmbv1QSgeKr8qJXBnJbm8= goftp.io/server/v2 v2.0.2/go.mod h1:Fl1WdcV7fx1pjOWx7jEHb7tsJ8VwE7+xHu6bVJ6r2qg= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=