Merge branch 'master' into fix-delete-offline-replica

This commit is contained in:
Chris Lu authored and GitHub committed 2026-11-02 17:47:28 -08:00
commit aba3eab8d9
12 files changed
+1670 -1142

No files matched your search

+2 -2
View File
@@ -93,7 +93,7 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-go@v5
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: ${{ runner.environment == 'github-hosted' }}
@@ -124,7 +124,7 @@ jobs:
set -e
make ./weed ./weed-vector CARGO_PROFILE=dev
if [ "${{ matrix.volume }}" = "rust" ]; then make ./weed-volume CARGO_PROFILE=dev; fi
- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
with:
python-version: "3.12"
- name: Install test dependencies
+30 -29
View File
@@ -32,7 +32,7 @@ require (
github.com/google/btree v1.1.3
github.com/google/uuid v1.6.0
github.com/google/wire v0.7.0 // indirect
github.com/googleapis/gax-go/v2 v2.24.1 // indirect
github.com/googleapis/gax-go/v2 v2.27.0 // indirect
github.com/gorilla/mux v1.8.1
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
@@ -53,7 +53,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/olivere/elastic/v7 v7.0.32
github.com/peterh/liner v1.2.2
github.com/peterh/liner v1.2.3
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/posener/complete v1.2.3
@@ -99,8 +99,8 @@ require (
golang.org/x/text v0.43.0 // indirect
golang.org/x/tools v0.51.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/api v0.297.0
google.golang.org/genproto v0.0.0-20260904194346-d0f1323225a4 // indirect
google.golang.org/api v0.310.0
google.golang.org/genproto v0.0.0-20261012220534-4341edf3f941 // indirect
google.golang.org/grpc v1.86.0-dev.0.20261027044109-d619b5d02089
google.golang.org/protobuf v1.36.12
gopkg.in/inf.v0 v0.9.1 // indirect
@@ -111,7 +111,7 @@ require (
)
require (
cloud.google.com/go/kms v1.35.0
cloud.google.com/go/kms v1.36.1
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/Jille/raft-grpc-transport v1.6.1
@@ -123,7 +123,7 @@ require (
github.com/apple/foundationdb/bindings/go v0.0.0-20250911184653-27f7192f47c3
github.com/arangodb/go-driver v1.6.9
github.com/armon/go-metrics v0.4.1
github.com/aws/aws-sdk-go-v2 v1.47.3
github.com/aws/aws-sdk-go-v2 v1.47.5
github.com/aws/aws-sdk-go-v2/config v1.33.6
github.com/aws/aws-sdk-go-v2/credentials v1.20.6
github.com/aws/aws-sdk-go-v2/service/s3 v1.114.1
@@ -159,7 +159,7 @@ require (
github.com/xeipuuv/gojsonschema v1.2.0
github.com/ydb-platform/ydb-go-sdk-auth-environ v0.5.3
github.com/ydb-platform/ydb-go-sdk/v3 v3.155.1
go.etcd.io/etcd/client/pkg/v3 v3.7.1
go.etcd.io/etcd/client/pkg/v3 v3.7.3
go.uber.org/atomic v1.12.0
golang.org/x/sync v0.24.0
golang.org/x/tools/godoc v0.1.0-deprecated
@@ -169,7 +169,7 @@ require (
require github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
require (
cloud.google.com/go/longrunning v1.2.0 // indirect
cloud.google.com/go/longrunning v1.3.0 // indirect
cloud.google.com/go/pubsub/v2 v2.7.0 // indirect
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect
@@ -278,20 +278,21 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0 // indirect
go.opentelemetry.io/otel/exporters/zipkin v1.45.0 // indirect
go.opentelemetry.io/otel/log v1.47.0 // indirect
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
go.uber.org/mock v0.5.2 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/mod v0.41.0 // indirect
golang.org/x/mod v0.42.0 // indirect
gonum.org/v1/gonum v0.17.0 // indirect
)
require (
cel.dev/expr v0.25.3 // indirect
cloud.google.com/go/auth v0.23.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/auth v0.24.1-0.20261001053825-dbc26066f70a // indirect
cloud.google.com/go/auth/oauth2adapt v0.3.0 // indirect
cloud.google.com/go/compute/metadata v0.10.0 // indirect
cloud.google.com/go/iam v1.13.0 // indirect
cloud.google.com/go/iam v1.14.0 // indirect
cloud.google.com/go/monitoring v1.30.0 // indirect
filippo.io/edwards25519 v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.3
@@ -321,19 +322,19 @@ require (
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.1 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.23 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sns v1.46.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sqs v1.51.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.38.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.51.1
github.com/aws/smithy-go v1.28.5
github.com/aws/aws-sdk-go-v2/service/sts v1.51.6
github.com/aws/smithy-go v1.28.7
github.com/boltdb/bolt v1.3.1 // indirect
github.com/bradenaw/juniper v0.15.3 // indirect
github.com/buengese/sgzip v0.1.1 // indirect
@@ -379,8 +380,8 @@ require (
github.com/gofrs/flock v0.13.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.21 // indirect
github.com/google/s2a-go v0.1.11 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.24 // indirect
github.com/gorilla/schema v1.4.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.4.0
@@ -412,7 +413,7 @@ require (
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20260627054121-477a66015f15 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-runewidth v0.0.24 // indirect
github.com/mattn/go-runewidth v0.0.31 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4
@@ -484,17 +485,17 @@ require (
go.opentelemetry.io/contrib/detectors/gcp v1.46.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 // indirect
go.opentelemetry.io/otel v1.46.0 // indirect
go.opentelemetry.io/otel/metric v1.46.0 // indirect
go.opentelemetry.io/otel/sdk v1.46.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.46.0 // indirect
go.opentelemetry.io/otel/trace v1.46.0 // indirect
go.opentelemetry.io/otel v1.47.0 // indirect
go.opentelemetry.io/otel/metric v1.47.0 // indirect
go.opentelemetry.io/otel/sdk v1.47.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.47.0 // indirect
go.opentelemetry.io/otel/trace v1.47.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.28.0 // indirect
golang.org/x/term v0.47.0
golang.org/x/time v0.16.0
google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20261012220534-4341edf3f941 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20261019180522-ce908d29299b // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/validator.v2 v2.0.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
+64 -64
View File
@@ -4,20 +4,20 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/auth v0.23.2 h1:pxSCpfiji41hpzpPdMCftEUCezpgpqmmDdYiAjCKXxo=
cloud.google.com/go/auth v0.23.2/go.mod h1:4DhBRcqvtljQN3dJ57qtqbib5ZGCYE5f2crfiiC2EM0=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/auth v0.24.1-0.20261001053825-dbc26066f70a h1:jW1ZLFy9oa5ai8LOI40z6s+DL1cO/L1KSm9hVGnibn8=
cloud.google.com/go/auth v0.24.1-0.20261001053825-dbc26066f70a/go.mod h1:1dmBXa9xfBvBr39ZnDUDkaaA91ccq9CcqENDgJA4d7c=
cloud.google.com/go/auth/oauth2adapt v0.3.0 h1:FY8oSZpCYoUNv6QxVODuMjQz4IlSOVeiQtZ08vLPz88=
cloud.google.com/go/auth/oauth2adapt v0.3.0/go.mod h1:7+2uCm7++XFO+/lN06c2HXpDXb/NMNn2/UwyBPbTnkk=
cloud.google.com/go/compute/metadata v0.10.0 h1:pyKMUQSwchgkIBBJGdILqQbs/BNJXqwSA7Ej6LAvvtY=
cloud.google.com/go/compute/metadata v0.10.0/go.mod h1:rGFHRrIif570kSibjFTMbt6/4/tzgJWFGI/HVol4GIk=
cloud.google.com/go/iam v1.13.0 h1:ufT3FPT5rFFXu6UtLkNoxaOaV5EuA1dsSkmemCSTo6U=
cloud.google.com/go/iam v1.13.0/go.mod h1:gHXdDEiPDvqd1q1KwBDGQlgZY/BwY760zU2LhOZS5w0=
cloud.google.com/go/kms v1.35.0 h1:nJ/ktaqspx1nPM9vIcO0SHbhqCAm8nvAxL1siuVgKm0=
cloud.google.com/go/kms v1.35.0/go.mod h1:0++71pIHvJL+GmMa8K4jOWFq7gNOX3jm2PRMSJwTKJw=
cloud.google.com/go/logging v1.19.1 h1:7SsLhyTDBDrJw+Ll6Ns3I2mByqHXvJUc3rGjSlwiWgU=
cloud.google.com/go/logging v1.19.1/go.mod h1:2IkQ/d8jVJqV2qW8ZUGUiMjdZG1gkLD2JReGbZ8isqg=
cloud.google.com/go/longrunning v1.2.0 h1:WjYH3YHBGCxGJP9M4dWGHBfXr/cFIjMkNgWcJj7/iMM=
cloud.google.com/go/longrunning v1.2.0/go.mod h1:5KMQALFGOCtFoi2xSOA1u3H7WKlhmckgiyFw7+LGQp0=
cloud.google.com/go/iam v1.14.0 h1:YRIB1/4xxwJ+NfI6RQr1WaOoHHeV0MizJEj940nBXJA=
cloud.google.com/go/iam v1.14.0/go.mod h1:JTPZfbHdGXJrOYREtkRoFzCDGaBdbA7ILLKO/DDuav0=
cloud.google.com/go/kms v1.36.1 h1:jIXDoN/lxjVt/zc219tIwqAWPVWVzcLJ7s6Oq3n0Y5U=
cloud.google.com/go/kms v1.36.1/go.mod h1:1oVAwTvmgswE8PiQeyffdNxpRLw2F0UfQlLwTrYlRNg=
cloud.google.com/go/logging v1.20.0 h1:l7yzCB8/JSY5PHT9RsaqdT1jaBGbM6KlSJDX210HlXM=
cloud.google.com/go/logging v1.20.0/go.mod h1:ALUaJQZM691Eeujy9kFx0xREq3AMPuGd+pTRZuTYv+w=
cloud.google.com/go/longrunning v1.3.0 h1:lZ6c8SJJBmILp/Wnr3vGnBLa0q+cYfSHRCcUfVI2hpI=
cloud.google.com/go/longrunning v1.3.0/go.mod h1:wpgLxCn+UA3/TZ1bj/fW39mfDpwVlMPu1xKJyoMt7V0=
cloud.google.com/go/monitoring v1.30.0 h1:r/d+JUbyKmJ8b07iznuKfzVzrIXTWxHQ3lBRm3x2LlY=
cloud.google.com/go/monitoring v1.30.0/go.mod h1:htlUR0QWVMrjFzZmN4LGnMAve9xB/eduwjmINxVZ8RM=
cloud.google.com/go/pubsub v1.51.1 h1:R3G1wCOxBO7jRpL8x2pdZMv1GAJDF6ax/m2zPOtvTNE=
@@ -173,8 +173,8 @@ github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJ
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
github.com/aws/aws-sdk-go-v2 v1.47.3 h1:HRoT/MkNi3bjO5xF4dCzLzvVmkvRGHj3/cLINDnJejk=
github.com/aws/aws-sdk-go-v2 v1.47.3/go.mod h1:xSl+OSI5ZUw6bSMupWc218zE5Pl+65MCCu3ySWFY99A=
github.com/aws/aws-sdk-go-v2 v1.47.5 h1:5yA+A+BiXbDq1ZLbYwPViwhEGDMdR0MEhL8Jh2LnLS4=
github.com/aws/aws-sdk-go-v2 v1.47.5/go.mod h1:iNIkogrRvFwm+1OQIUOP2lPBtlXZboOIUWiCa+pHEMw=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 h1:GPRlPwz40I2B2VrBEASOA3Bi77NyeqejNLkifosX0rs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20/go.mod h1:g7PNzKcsOKWb4fkSRBA7BZVAS6Y8IcxzN+nRohhQ1Q8=
github.com/aws/aws-sdk-go-v2/config v1.33.6 h1:MBjkSTLczek/UgiK+EYPIoRTqE7gP8vtW3OFbFo7Nug=
@@ -187,18 +187,18 @@ github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.34 h1:Pn7OsMwBLbkZ6OnCxWHA
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.34/go.mod h1:eToXR/Gk1uqpn04eSmdgVXwfS0WvH8aG4eBFr8ygbpU=
github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager v0.4.3 h1:vsFkmFrfzODkxVbyuPLscv5j5lC8KbG48LZwV+p56Y0=
github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager v0.4.3/go.mod h1:pjQYVtvVBEtlsf2p6nXMsjPuDaWs9GM44FMdHbmnmYU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4 h1:CLq4+8UHCI+ZZYl/EuJxXovaIVN2xeeT8JV+dsApQ5E=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4/go.mod h1:Wv4q5sAM04xAMkoOedxLx2inVf6K5FdxYp+A61L+q/0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.4 h1:dD4MR81I7YkpEBRk6UP9rocC2QnT3qVuXwzlYTtfGEs=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.4/go.mod h1:EcXV1kAFd5XwSkDHlj94gnF3q5CkJyYiIJfH8N0VmrE=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.4 h1:7Wo47d/xn/7KttCSBd8EGYeZ7ULRFRkUHr6vkZPBzVQ=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.4/go.mod h1:tDB2IVC1xC3vX8o+6uRlzhTxP3g1b77CZXFX/oD2FnQ=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 h1:bAdDl/HkGCcGPoe25ToSHEw23VIxt6CT5fLcg111BKg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19/go.mod h1:KaUzbLxv4CeSxh6ZCl9B4m7CuFenS8kUEaDs+f/DQr4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.8 h1:gzIWc3DTPfPbWKrQnmL6BV6WD7NPD1htxzgdk/bQ/Z4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.8/go.mod h1:vvUA5cDAZWGo4R14FZPZfXfJCmgsafOWEq78Kg0D+ug=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.8 h1:PWv+vG8aCLS8qd+HPdYhK7Vees90aKHWFFQ1AGYD/8E=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.8/go.mod h1:xxPZEpflYHJA12366ZGyq66z9dHPCoeCIu99Jck+MPw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.8 h1:WOp5De9iWdEHDPh2Kb0tBP3vGBm/dVAuRGh4a4tqduo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.8/go.mod h1:z8W8YgfhbWl62XqcTtK+CxPrE+iHGRVUOtrrbIOp7ZY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.23 h1:MCIR6iZaCKO4UE0hUvoWWLI6JVjppopV64JfAJaMjng=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.23/go.mod h1:pphWFS+z0MmIPivj8c/6bATd1Z+Av7Ust75DPdEFsPA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.5 h1:/TYsZXdA8UTa+WCtCYSAJIr1vwl0+eho6TUgJGwFFO8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.5/go.mod h1:qPqp1Uwd/BqdhPufv6oem9j5J7HNsgc2V22dUiDPn+s=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.4 h1:29SvnfGhXjTl8ONxFwbj2rs6lbhiFXD2CgFQmbT/bXY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.4/go.mod h1:wm04I5DMuNVvZHFe/dHnUxincvNbbK7AiNBbYsQivek=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.8 h1:+PMpWlJYOn2tm7sm6OZ/ZLd4jStl17T7wjNapd1Rczs=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.8/go.mod h1:cnG3xZLGQL4c/bt8pjdzlFtZsW7epBBuNS04a5xMjdo=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.4 h1:pPiWfgeNxqluKEph7hvU88kuGKBPOWzO+Dk9t2zqqNs=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.4/go.mod h1:YlwGoIUDG/3kBQbdNOVs/xKZ9J01G8e/6D1mRBj9uTk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.114.1 h1:9T6eO8/WXhij4PQaa2WaYsdekSCmT9Hw66Pw0S8do0Q=
@@ -213,10 +213,10 @@ github.com/aws/aws-sdk-go-v2/service/sso v1.38.1 h1:Umtl/0YZhng4xndfW3lKJrYYP7NL
github.com/aws/aws-sdk-go-v2/service/sso v1.38.1/go.mod h1:rRD/dnm7q0HYE/I5TMaPgkWyyUGLcwuxHLABsLnQ3e0=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.1 h1:orIWdNiLgzrhu/11RcPPKO/SBzUUymbUQuZbSPImghg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.1/go.mod h1:skwM/xsbR/1ReUTesv9BhpJp1VjajR7DWQnuVLwiXsQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.51.1 h1:0HOqZXRvMytH6bFHVIc0oJX07sZjfhz0zXtjs6gdE8s=
github.com/aws/aws-sdk-go-v2/service/sts v1.51.1/go.mod h1:26zA0GhDrLo+yiLI2yXWxqB1PdsShfLikoI7GOEgugM=
github.com/aws/smithy-go v1.28.5 h1:OqbqKtzY9NUnr2yAe9+hiTdFb20tNaK/lI0PMOGXX9k=
github.com/aws/smithy-go v1.28.5/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aws/aws-sdk-go-v2/service/sts v1.51.6 h1:BVjKrtRUtSYKztBCCfE1oAB+d3ViKcW2cKZfserOqMQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.51.6/go.mod h1:eSukFLeLkPMm+1OxDR+F0joauZdyBkF47Ow7MqjZiJw=
github.com/aws/smithy-go v1.28.7 h1:h/n1SHrwVtaDYLz8mR1/Kq4Xh4RaFbFW7bOYckg9mfE=
github.com/aws/smithy-go v1.28.7/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/bazelbuild/rules_go v0.46.0 h1:CTefzjN/D3Cdn3rkrM6qMWuQj59OBcuOjyIp3m4hZ7s=
@@ -619,8 +619,8 @@ github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/s2a-go v0.1.11 h1:7v1V1TraNGP63WptsAkNqr2w8jgm4vM6A6qx6Lsw9iM=
github.com/google/s2a-go v0.1.11/go.mod h1:pz4tyvwXvJLLbyrkh6FW1eS2zPUXMaTmyNhYtyP2tNw=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -629,10 +629,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
github.com/googleapis/enterprise-certificate-proxy v0.3.21 h1:OFdQ3tnCX/zaQ0Cedur3D3z7kI6HiLX9g3TiAN4/DFU=
github.com/googleapis/enterprise-certificate-proxy v0.3.21/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w=
github.com/googleapis/gax-go/v2 v2.24.1 h1:AtqTN21IXMMWo99LiEVAiBfNNQmO40d8xUfZI640mc0=
github.com/googleapis/gax-go/v2 v2.24.1/go.mod h1:bWeBei0NVwaNZKb2y1HUBS7gLXIF3/Tu3pq7j8D2Tb0=
github.com/googleapis/enterprise-certificate-proxy v0.3.24 h1:G9iCJIY+AYVmUskKdjX8FVy7sG+nbzjOQ41cvlmhcvg=
github.com/googleapis/enterprise-certificate-proxy v0.3.24/go.mod h1:hEjCmnoakQzv5ImLUp6alkY7XgLlNoAxwTXmhHHNIsI=
github.com/googleapis/gax-go/v2 v2.27.0 h1:qe2iQOUzxb3fd6x6ewpIEixPQhJ+0Ihc/RRZhKA/2g0=
github.com/googleapis/gax-go/v2 v2.27.0/go.mod h1:RofiqDw3ehcG4L0H1zO3D2Itpwd3/uS/7wdmbNDF1DE=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
@@ -840,9 +840,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
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-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-runewidth v0.0.31 h1:1oCV4ReTlosDBc6rYRw5cL8S/4y8iv5oRl4SJ5IBgwo=
github.com/mattn/go-runewidth v0.0.31/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4=
github.com/mattn/go-shellwords v1.0.13/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
@@ -971,8 +970,8 @@ github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdD
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14 h1:XeOYlK9W1uCmhjJSsY78Mcuh7MVkNjTzmHx1yBzizSU=
github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14/go.mod h1:jVblp62SafmidSkvWrXyxAme3gaTfEtWwRPGz5cpvHg=
github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
github.com/peterh/liner v1.2.3 h1:JF4G03iaZjtPyJE7gdOk76/fvs4OObMJWQHwR3wphxE=
github.com/peterh/liner v1.2.3/go.mod h1:IC6gIBWLzuPGXmiX/86/ieO9K+9ITsaE6SKm1XkU9uw=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14=
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
@@ -1353,8 +1352,8 @@ go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU=
go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk=
go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E=
go.etcd.io/etcd/api/v3 v3.7.1/go.mod h1:8bXIpCMeV7E3/XL0Ix123ATn3dB+0V7d9zklHbB0m78=
go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mfFv4=
go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0=
go.etcd.io/etcd/client/pkg/v3 v3.7.3 h1:5zMwNu3ycrzCeA5NwOfQbxFRhqq974chq1DrYMua4QI=
go.etcd.io/etcd/client/pkg/v3 v3.7.3/go.mod h1:/OhzqYrF0U7d1NOk55VMyUQGbYD1WoIe4bJwxb47wOk=
go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg=
go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0=
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
@@ -1371,8 +1370,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.69.0/go.mod h1:3jnStNwSufK+f5ktjL4EPcwtig4rtd81NS70lqHuXl8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 h1:3g7B90UzBltIDKq1/5mrTGxTnOFDV0ICOhLoxiZ8jlg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0/go.mod h1:Ef8SuTh59BT7+ofpDxN9z+yOlc4t2GjLmKDgYNJL/NU=
go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc=
go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE=
go.opentelemetry.io/otel v1.47.0 h1:j7ALJ/zgkS7Z6aeJW09p8VC9804bC+PpeTfCD4XPnOM=
go.opentelemetry.io/otel v1.47.0/go.mod h1:8wS9O2qfXrYrzp6hIF/HOYJJf/wIhFPhR2xLuP+iXQU=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.46.0 h1:qkDYCAFiZXLcs1L4aY+tP2wguQ4kURANqHOQMA2et2s=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.46.0/go.mod h1:tkipS4DRzmpAmvg+Gw4++O1IdDq6TVDnvnYU6cmbQVs=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 h1:RuynHbfU8JUEw7DyONgkVYg2SVtsoF28y0LGIr69jgA=
@@ -1387,16 +1386,18 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.46.0 h1:PR9eAf7o0dQs3h
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.46.0/go.mod h1:2Z4KyNdH1uuzivdinyfGsxzNNT/Rl45pwtVwfYVI0xk=
go.opentelemetry.io/otel/exporters/zipkin v1.45.0 h1:KN3btaILMTxR4QDHVGAO87lq5ButzK7l+kIfLuxQ1oA=
go.opentelemetry.io/otel/exporters/zipkin v1.45.0/go.mod h1:yNcodmUclM4InyWoOwX/YW4Jri0Gj5FWAlM+NqCrtqY=
go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8=
go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o=
go.opentelemetry.io/otel/metric/x v0.68.0 h1:TA/cBT23D3MnxYPwHL7YFOdYGdx0A0v+s7Mzotpd1dU=
go.opentelemetry.io/otel/metric/x v0.68.0/go.mod h1:agudOmvWhwUTjgibWDzxD2PoWYnpw5Ht5jISYOD2Hd4=
go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI=
go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM=
go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE=
go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4=
go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c=
go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI=
go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI=
go.opentelemetry.io/otel/log v1.47.0/go.mod h1:9byitSQ5pLC6PpqwGXjqdMKya6ZTswHRZh2vvXT33nw=
go.opentelemetry.io/otel/metric v1.47.0 h1:4PptaldXx3Eat1XjMZ68pPJEs5wrhlemctZE9a3UdWY=
go.opentelemetry.io/otel/metric v1.47.0/go.mod h1:ADGSXxRrXM6bjbvLo535EstVFlPpPYZm4LBKixjDHwU=
go.opentelemetry.io/otel/metric/x v0.69.0 h1:DjRLr15H83v+hCW7JA9NoJvOkYTtmq5YoDRbe9deYpM=
go.opentelemetry.io/otel/metric/x v0.69.0/go.mod h1:uVvsMPMFFyj/HUQfrUnH3JjnOQ1dwFDorgFLRBasM0k=
go.opentelemetry.io/otel/sdk v1.47.0 h1:zWXEr4j2lFefG87TU6Yg8a7ngfohIKFZHKp0Hf5hC6I=
go.opentelemetry.io/otel/sdk v1.47.0/go.mod h1:VUc24kiOeoGsxG8G9ULx3fWKvB7jMhnGE8Oi607lgR0=
go.opentelemetry.io/otel/sdk/metric v1.47.0 h1:lfISg2j93VT6yqdk9OfUaZmw/GfcZqCCV3jdXtsPnKw=
go.opentelemetry.io/otel/sdk/metric v1.47.0/go.mod h1:ypLp+mW1Nt2x+Szt3b5/i1syodyts49lMOwxpDI3VGw=
go.opentelemetry.io/otel/trace v1.47.0 h1:JOjX/Oci8K94QHddo+bbfya/Ai/nf6/dt9ZfrFNWSrM=
go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08uDmPg28lTghu0=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk=
go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E=
@@ -1468,8 +1469,8 @@ golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1545,7 +1546,6 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1620,8 +1620,8 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.297.0 h1:WktxTsnnx0yZNnsR6j0q6hR21RnnK81FHTOPy/ux4OE=
google.golang.org/api v0.297.0/go.mod h1:S4m8x0M6OkQpkOzGk1y9JG2sm4fFQrMh6dxzjCTszhE=
google.golang.org/api v0.310.0 h1:537ccDDmbwuwX1iof3Vtdz9vow6uJCgrSZJWedAWM0I=
google.golang.org/api v0.310.0/go.mod h1:5NUthU+LmGMKzYgRaScb+TTe1Lo7x4A+J7g+Yj8BRzA=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
@@ -1631,12 +1631,12 @@ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20260904194346-d0f1323225a4 h1:6QRNo34FxJcE1LG6XfnmaQ17m6w21oXaKlKoBaF+UqE=
google.golang.org/genproto v0.0.0-20260904194346-d0f1323225a4/go.mod h1:eifJ91nefXnfP/bMkZsACVGm14ZtEL4eUXRLTryfbY4=
google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8=
google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc=
google.golang.org/genproto v0.0.0-20261012220534-4341edf3f941 h1:CXq2XEi5aEyLjjPZIwqrmQH8ZY4ctUwK7uyAx3mjX/U=
google.golang.org/genproto v0.0.0-20261012220534-4341edf3f941/go.mod h1:3gK0ehnhP0V7/x3ypGoLyT3d0n6TSi/EcsphIjHrcz4=
google.golang.org/genproto/googleapis/api v0.0.0-20261012220534-4341edf3f941 h1:DS/w3N8rsfrwxwgGSj6WhMsPo7aNQGmDx74J81zd0NQ=
google.golang.org/genproto/googleapis/api v0.0.0-20261012220534-4341edf3f941/go.mod h1:AuGAdi9KnXss7xbLxIKhCvk+Ro/SxpET/iS45DNYN+M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20261019180522-ce908d29299b h1:6WNWHZk91dp4gZ25642OALkuG1UVNZbwENJD19DnB8k=
google.golang.org/genproto/googleapis/rpc v0.0.0-20261019180522-ce908d29299b/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+1011 -1008
View File
File diff suppressed because it is too large. Load diff

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

+41
View File
@@ -451,6 +451,47 @@ def test_create_with_tags(vectors):
vectors.delete_vector_bucket(vectorBucketName=bucket)
def test_tag_conditions(vectors, vector_clients, index):
reader, writer = vector_clients["reader"], vector_clients["writer"]
bucket = index["vectorBucketName"]
bucket_arn = vectors.get_vector_bucket(vectorBucketName=bucket)["vectorBucket"]["vectorBucketArn"]
index_arn = vectors.get_index(**index)["index"]["indexArn"]
other = dict(index, indexName="other-index")
vectors.create_index(**other, dataType="float32", dimension=4, distanceMetric="euclidean")
vectors.tag_resource(resourceArn=bucket_arn, tags={"env": "prod"})
vectors.tag_resource(resourceArn=index_arn, tags={"stage": "live"})
indexes = bucket_arn + "/index/*"
statements = [
{"Effect": "Allow", "Principal": {"AWS": "reader"}, "Action": "s3vectors:GetIndex", "Resource": indexes,
"Condition": {"StringEquals": {"aws:ResourceTag/stage": "live"}}},
{"Effect": "Allow", "Principal": {"AWS": "reader"}, "Action": "s3vectors:QueryVectors", "Resource": indexes,
"Condition": {"StringEquals": {"s3vectors:VectorBucketTag/env": "prod"}}},
{"Effect": "Allow", "Principal": {"AWS": "writer"}, "Action": "s3vectors:TagResource", "Resource": indexes,
"Condition": {"StringEquals": {"aws:RequestTag/team": "search"},
"ForAllValues:StringEquals": {"aws:TagKeys": ["team"]}}},
]
try:
vectors.put_vector_bucket_policy(vectorBucketName=bucket,
policy=json.dumps({"Version": "2012-10-17", "Statement": statements}))
assert reader.get_index(**index)["index"]["indexArn"] == index_arn
expect_error("AccessDeniedException", reader.get_index, **other)
assert reader.query_vectors(**other, queryVector={"float32": [1, 0, 0, 0]}, topK=1)["vectors"] == []
writer.tag_resource(resourceArn=index_arn, tags={"team": "search"})
expect_error("AccessDeniedException", writer.tag_resource, resourceArn=index_arn, tags={"team": "other"})
expect_error("AccessDeniedException", writer.tag_resource, resourceArn=index_arn,
tags={"team": "search", "env": "dev"})
assert vectors.list_tags_for_resource(resourceArn=index_arn)["tags"] == {"stage": "live", "team": "search"}
vectors.tag_resource(resourceArn=bucket_arn, tags={"env": "dev"})
expect_error("AccessDeniedException", reader.query_vectors, **other,
queryVector={"float32": [1, 0, 0, 0]}, topK=1)
unsupported = dict(statements[0], Condition={"IpAddress": {"aws:SourceIp": "10.0.0.0/8"}})
expect_error("ValidationException", vectors.put_vector_bucket_policy, vectorBucketName=bucket,
policy=json.dumps({"Version": "2012-10-17", "Statement": [unsupported]}))
finally:
vectors.delete_vector_bucket_policy(vectorBucketName=bucket)
vectors.delete_index(**other)
def test_invalid_credentials(cluster):
client = boto3.client("s3vectors", endpoint_url=cluster["endpoint"], aws_access_key_id="unknown",
aws_secret_access_key="unknown", region_name="us-east-1")
+1 -1
View File
@@ -120,7 +120,7 @@ func TestCreateNeedleFromRequest(t *testing.T) {
}
func TestUploadKeepsMarginalGzipUncompressed(t *testing.T) {
data := make([]byte, 4300)
data := make([]byte, 4450)
rand.New(rand.NewSource(1)).Read(data[:4000])
gzipped, _ := util.GzipData(data)
if len(gzipped) >= len(data) || len(gzipped)*10 < len(data)*9 {
+6 -3
View File
@@ -586,9 +586,12 @@ func getConditionContextValues(key string, ctx *PolicyContext) []string {
return []string{val}
}
}
if strings.HasPrefix(key, "s3tables:TableBucketTag/") {
tagKey := strings.TrimPrefix(key, "s3tables:TableBucketTag/")
if val, ok := ctx.TableBucketTags[tagKey]; ok {
tagKey, ok := strings.CutPrefix(key, "s3tables:TableBucketTag/")
if !ok {
tagKey, ok = strings.CutPrefix(key, "s3vectors:VectorBucketTag/")
}
if ok {
if val, found := ctx.TableBucketTags[tagKey]; found {
return []string{val}
}
}
+19
View File
@@ -538,3 +538,22 @@ func TestServiceWildcardScopesToOwnService(t *testing.T) {
t.Error("the global admin action keeps the bypass on vector operations")
}
}
func TestBucketTagConditionKeys(t *testing.T) {
ctx := &PolicyContext{
TableBucketTags: map[string]string{"env": "prod"},
ResourceTags: map[string]string{"env": "dev"},
}
for key, want := range map[string]string{
"s3tables:TableBucketTag/env": "prod",
"s3vectors:VectorBucketTag/env": "prod",
"aws:ResourceTag/env": "dev",
} {
if got := getConditionContextValues(key, ctx); len(got) != 1 || got[0] != want {
t.Errorf("%s = %v, want %s", key, got, want)
}
}
if got := getConditionContextValues("s3vectors:VectorBucketTag/team", ctx); got != nil {
t.Errorf("absent bucket tag = %v, want none", got)
}
}
+174 -29
View File
@@ -5,8 +5,10 @@ import (
"encoding/json"
"errors"
"fmt"
"maps"
"math"
"net/http"
"slices"
"strconv"
"strings"
"time"
@@ -16,6 +18,7 @@ import (
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/pb/vector_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3tables"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -385,14 +388,14 @@ func (s *Server) handleListVectorBuckets(w http.ResponseWriter, r *http.Request)
if !IsVectorBucketFormat(b.Format) {
continue
}
owner, policy, err := s.vectorPolicyView(r.Context(), b.Name)
owner, policy, tags, err := s.vectorPolicyView(r.Context(), b.Name)
if err != nil {
// A bucket that vanished mid-list drops out of the page rather
// than failing the whole listing.
continue
}
arn := s.vectorBucketArn(b.Name)
pctx := s.tables.PolicyContext(r, b.Name)
pctx := s.policyContext(r, b.Name, tags, tags, tagRequest{})
if !s3tables.CheckPermissionWithContext("s3vectors:ListVectorBuckets", principal, owner, policy, arn, pctx) &&
!s3tables.CheckPermissionWithContext("s3vectors:GetVectorBucket", principal, owner, policy, arn, pctx) {
continue
@@ -484,10 +487,10 @@ var errNoVectorPolicy = errors.New("vector bucket policy not found")
// validateVectorPolicy rejects a document the evaluator cannot honor: the
// shared Statement shape has no negated fields, so a rule relying on one would
// silently lose its exclusion; conditions are unsupported since the vector
// evaluation supplies no condition keys; and a rule naming another service is
// a dead rule the writer believes is enforced. The document must decode into
// the same typed form the evaluator uses.
// silently lose its exclusion; a condition on a key the vector evaluation does
// not supply, or with an operator it does not know, never matches; and a rule
// naming another service is a dead rule the writer believes is enforced. The
// document must decode into the same typed form the evaluator uses.
func validateVectorPolicy(raw string) error {
var typed s3tables.PolicyDocument
if err := json.Unmarshal([]byte(raw), &typed); err != nil {
@@ -499,14 +502,30 @@ func validateVectorPolicy(raw string) error {
}
for _, stmt := range policyStatements(doc) {
for key := range stmt {
for _, field := range []string{"NotAction", "NotResource", "NotPrincipal", "Condition"} {
for _, field := range []string{"NotAction", "NotResource", "NotPrincipal"} {
if strings.EqualFold(key, field) {
return fmt.Errorf("invalid policy document: %s is not supported", field)
}
}
if key != "Condition" && strings.EqualFold(key, "Condition") {
return fmt.Errorf("invalid policy document: %s must be spelled Condition", key)
}
}
}
for _, stmt := range typed.Statement {
for operator, keys := range stmt.Condition {
if _, err := policy_engine.GetConditionEvaluator(operator); err != nil {
return fmt.Errorf("invalid policy document: condition operator %q is not supported", operator)
}
for key, value := range keys {
if !vectorConditionKey(key) {
return fmt.Errorf("invalid policy document: condition key %q is not supported in a vector bucket policy", key)
}
if !conditionValue(value) {
return fmt.Errorf("invalid policy document: condition %s on %q needs a string, number or boolean, or a non-empty list of them", operator, key)
}
}
}
for _, action := range statementActions(stmt.Action) {
if service, _, found := strings.Cut(action, ":"); found && service != "s3vectors" {
return fmt.Errorf("invalid policy document: action %q is not enforceable in a vector bucket policy", action)
@@ -521,6 +540,36 @@ func validateVectorPolicy(raw string) error {
return nil
}
// conditionValue reports whether the evaluator can compare v: a null value
// would panic it, and an empty list never matches.
func conditionValue(v interface{}) bool {
list, ok := v.([]interface{})
if !ok {
list = []interface{}{v}
}
for _, item := range list {
switch item.(type) {
case string, float64, bool:
default:
return false
}
}
return len(list) > 0
}
// vectorConditionKey reports whether the vector evaluation supplies key.
func vectorConditionKey(key string) bool {
if key == "aws:TagKeys" {
return true
}
for _, prefix := range []string{"aws:ResourceTag/", "aws:RequestTag/", "s3vectors:VectorBucketTag/"} {
if tagKey, ok := strings.CutPrefix(key, prefix); ok {
return tagKey != ""
}
}
return false
}
// arnService returns the service field of an ARN in any partition.
func arnService(resource string) string {
parts := strings.SplitN(resource, ":", 4)
@@ -672,14 +721,13 @@ func (s *Server) vectorBucket(ctx context.Context, bucket string) (*s3tables.Get
return &resp, nil
}
// vectorPolicy reads the bucket's stored s3vectors policy. A bucket written
// before the dedicated key carried the raw document in the shared policy
// slot; one that still names vector actions keeps working until the next
// vector policy write migrates it out.
func (s *Server) vectorPolicy(ctx context.Context, bucket string) (string, error) {
// vectorPolicy reads the bucket's stored s3vectors policy and its tags. A
// bucket written before the dedicated key carried the raw document in the
// shared policy slot; one that still names vector actions keeps working until
// the next vector policy write migrates it out.
func (s *Server) vectorPolicy(ctx context.Context, bucket string) (policy string, tags map[string]string, err error) {
dir, name := splitEntryPath(s3tables.GetTableBucketPath(bucket))
var policy string
err := s.filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
err = s.filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
resp, err := filer_pb.LookupEntry(ctx, client, &filer_pb.LookupDirectoryEntryRequest{
Directory: dir,
Name: name,
@@ -688,14 +736,94 @@ func (s *Server) vectorPolicy(ctx context.Context, bucket string) (string, error
return err
}
policy = vectorPolicyFromExt(resp.Entry.Extended)
return nil
tags, err = tagsFromExt(resp.Entry.Extended)
return err
})
return policy, err
return policy, tags, err
}
// vectorPolicyView resolves a bucket's owner and stored s3vectors policy in
// one filer lookup — the per-bucket check the vector listing runs.
func (s *Server) vectorPolicyView(ctx context.Context, bucket string) (owner, policy string, err error) {
// indexTags reads an index's tags; one not created yet has none.
func (s *Server) indexTags(ctx context.Context, bucket, index string) (map[string]string, error) {
dir, name := splitEntryPath(s3tables.GetTablePath(bucket, indexNamespace, index))
var tags map[string]string
err := s.filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
resp, err := filer_pb.LookupEntry(ctx, client, &filer_pb.LookupDirectoryEntryRequest{
Directory: dir,
Name: name,
})
if errors.Is(err, filer_pb.ErrNotFound) {
return nil
}
if err != nil {
return err
}
tags, err = tagsFromExt(resp.Entry.Extended)
return err
})
return tags, err
}
func tagsFromExt(ext map[string][]byte) (map[string]string, error) {
raw := ext[s3tables.ExtendedKeyTags]
if len(raw) == 0 {
return nil, nil
}
var tags map[string]string
if err := json.Unmarshal(raw, &tags); err != nil {
return nil, fmt.Errorf("decode tags: %w", err)
}
return tags, nil
}
// readsResourceTags reports whether a policy has a condition on
// aws:ResourceTag, which on an index needs the index's tags read. A document
// that does not decode is treated as having one.
func readsResourceTags(policy string) bool {
if policy == "" {
return false
}
var doc s3tables.PolicyDocument
if json.Unmarshal([]byte(policy), &doc) != nil {
return true
}
for _, stmt := range doc.Statement {
for _, keys := range stmt.Condition {
for key := range keys {
if strings.HasPrefix(key, "aws:ResourceTag/") {
return true
}
}
}
}
return false
}
// tagRequest is what a request sets or removes, for aws:RequestTag and
// aws:TagKeys.
type tagRequest struct {
tags map[string]string
keys []string
}
func setTags(tags map[string]string) tagRequest {
return tagRequest{tags: tags, keys: slices.Sorted(maps.Keys(tags))}
}
// policyContext is the catalog's evaluation context plus the tag condition
// keys: aws:ResourceTag reads the target's tags and s3vectors:VectorBucketTag
// the bucket's.
func (s *Server) policyContext(r *http.Request, bucket string, bucketTags, resourceTags map[string]string, req tagRequest) *s3tables.PolicyContext {
pctx := s.tables.PolicyContext(r, bucket)
pctx.TableBucketTags = bucketTags
pctx.ResourceTags = resourceTags
pctx.RequestTags = req.tags
pctx.TagKeys = req.keys
return pctx
}
// vectorPolicyView resolves a bucket's owner, stored s3vectors policy and
// tags in one filer lookup — the per-bucket check the vector listing runs.
func (s *Server) vectorPolicyView(ctx context.Context, bucket string) (owner, policy string, tags map[string]string, err error) {
dir, name := splitEntryPath(s3tables.GetTableBucketPath(bucket))
err = s.filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
resp, err := filer_pb.LookupEntry(ctx, client, &filer_pb.LookupDirectoryEntryRequest{
@@ -713,9 +841,10 @@ func (s *Server) vectorPolicyView(ctx context.Context, bucket string) (owner, po
}
owner = meta.OwnerAccountID
policy = vectorPolicyFromExt(resp.Entry.Extended)
return nil
tags, err = tagsFromExt(resp.Entry.Extended)
return err
})
return owner, policy, err
return owner, policy, tags, err
}
// mutateVectorPolicy applies a change to the bucket's vector-policy
@@ -787,9 +916,13 @@ func (s *Server) checkVectorPolicyDoc(r *http.Request, action, bucket string, re
if json.Unmarshal(ext[s3tables.ExtendedKeyMetadata], &meta) != nil || !meta.CreatedAt.Equal(record.CreatedAt) {
return errVectorBucketReplaced
}
tags, err := tagsFromExt(ext)
if err != nil {
return err
}
principal := s.tables.PrincipalFor(r)
if !s3tables.CheckPermissionWithContext(action, principal, record.OwnerAccountID,
vectorPolicyFromExt(ext), s.vectorBucketArn(bucket), s.tables.PolicyContext(r, bucket)) {
vectorPolicyFromExt(ext), s.vectorBucketArn(bucket), s.policyContext(r, bucket, tags, tags, tagRequest{})) {
return errVectorDenied
}
return nil
@@ -802,20 +935,32 @@ func (s *Server) checkVectorPolicyDoc(r *http.Request, action, bucket string, re
// trusted internal path, the same pattern the implicit namespace uses.
// The returned record saves the handler a second bucket lookup.
func (s *Server) authorizeVector(ctx context.Context, r *http.Request, action, bucket, index string) (*s3tables.GetTableBucketResponse, error) {
return s.authorizeVectorTags(ctx, r, action, bucket, index, tagRequest{})
}
// authorizeVectorTags is authorizeVector for a request that sets or removes
// tags.
func (s *Server) authorizeVectorTags(ctx context.Context, r *http.Request, action, bucket, index string, req tagRequest) (*s3tables.GetTableBucketResponse, error) {
record, err := s.vectorBucket(ctx, bucket)
if err != nil {
return nil, err
}
policy, err := s.vectorPolicy(ctx, bucket)
policy, bucketTags, err := s.vectorPolicy(ctx, bucket)
if err != nil {
return nil, err
}
resourceARN := s.vectorBucketArn(bucket)
resourceARN, resourceTags := s.vectorBucketArn(bucket), bucketTags
if index != "" {
resourceARN = s.indexArn(bucket, index)
resourceARN, resourceTags = s.indexArn(bucket, index), nil
if readsResourceTags(policy) {
if resourceTags, err = s.indexTags(ctx, bucket, index); err != nil {
return nil, err
}
}
}
principal := s.tables.PrincipalFor(r)
if !s3tables.CheckPermissionWithContext(action, principal, record.OwnerAccountID, policy, resourceARN, s.tables.PolicyContext(r, bucket)) {
if !s3tables.CheckPermissionWithContext(action, principal, record.OwnerAccountID, policy, resourceARN,
s.policyContext(r, bucket, bucketTags, resourceTags, req)) {
return nil, &s3tables.S3TablesError{Type: s3tables.ErrCodeAccessDenied, Message: "access denied"}
}
if !isVectorBucket(record) {
@@ -888,7 +1033,7 @@ func (s *Server) handleGetVectorBucketPolicy(w http.ResponseWriter, r *http.Requ
catalogErr(w, err)
return
}
policy, err := s.vectorPolicy(r.Context(), arns.bucket)
policy, _, err := s.vectorPolicy(r.Context(), arns.bucket)
if err != nil {
catalogErr(w, err)
return
@@ -986,13 +1131,13 @@ func (s *Server) handleCreateIndex(w http.ResponseWriter, r *http.Request) {
}
// The create grant is exercised before any write: a denied caller must not
// leave even the implicit namespace behind.
bucketRecord, err := s.authorizeVector(r.Context(), r, "s3vectors:CreateIndex", arns.bucket, arns.index)
bucketRecord, err := s.authorizeVectorTags(r.Context(), r, "s3vectors:CreateIndex", arns.bucket, arns.index, setTags(req.Tags))
if err != nil {
catalogErr(w, err)
return
}
if len(req.Tags) > 0 {
if _, err := s.authorizeVector(r.Context(), r, "s3vectors:TagResource", arns.bucket, arns.index); err != nil {
if _, err := s.authorizeVectorTags(r.Context(), r, "s3vectors:TagResource", arns.bucket, arns.index, setTags(req.Tags)); err != nil {
catalogErr(w, err)
return
}
+1 -1
View File
@@ -786,7 +786,7 @@ func TestValidateVectorPolicy(t *testing.T) {
if err := validateVectorPolicy("not-json"); err == nil {
t.Fatal("malformed policy should be rejected")
}
for _, field := range []string{"NotAction", "NotResource", "NotPrincipal", "Condition"} {
for _, field := range []string{"NotAction", "NotResource", "NotPrincipal"} {
bad := fmt.Sprintf(`{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Principal":"*","%s":"s3vectors:GetIndex","Action":"s3vectors:PutVectors","Resource":"*"}]}`, field)
if err := validateVectorPolicy(bad); err == nil {
t.Fatalf("%s should be rejected", field)
+316
View File
@@ -0,0 +1,316 @@
package s3vectors
import (
"encoding/json"
"maps"
"net/http"
"testing"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3tables"
)
// storePolicy writes a vector bucket policy straight to the bucket entry.
func (c *tagClient) storePolicy(bucket, policy string) {
c.t.Helper()
dir, name := splitEntryPath(s3tables.GetTableBucketPath(bucket))
entry := c.fs.Get(dir, name)
if entry == nil {
c.t.Fatalf("bucket %s has no entry", bucket)
}
ext := maps.Clone(entry.Extended)
ext[vectorPolicyKey] = []byte(policy)
c.fs.Put(dir, name, ext)
}
func (c *tagClient) setup(bucket string, indexes ...string) {
c.t.Helper()
code, body := c.op("", "CreateVectorBucket", &CreateVectorBucketRequest{VectorBucketName: bucket})
mustStatus(c.t, "create bucket "+bucket, code, body, http.StatusOK)
for _, index := range indexes {
code, body = c.op("", "CreateIndex", &CreateIndexRequest{
VectorBucketName: bucket, IndexName: index, DataType: "float32", Dimension: 3,
})
mustStatus(c.t, "create index "+index, code, body, http.StatusOK)
}
}
func (c *tagClient) getIndex(identity, bucket, index string) int {
code, _ := c.op(identity, "GetIndex", &GetIndexRequest{VectorBucketName: bucket, IndexName: index})
return code
}
func statement(effect, action, resource, conditions string) string {
return `{"Effect":"` + effect + `","Principal":"*","Action":` + action + `,"Resource":"` + resource + `","Condition":` + conditions + `}`
}
func policyOf(statements ...string) string {
out := `{"Version":"2012-10-17","Statement":[`
for i, stmt := range statements {
if i > 0 {
out += ","
}
out += stmt
}
return out + `]}`
}
func TestResourceTagReadsTheTargetsOwnTags(t *testing.T) {
s, c := newTagClient(t)
c.setup("vec-bucket", "live-idx", "test-idx")
bucketArn := s.vectorBucketArn("vec-bucket")
mustStatus(t, "tag bucket", tagCode(c, bucketArn, map[string]string{"env": "prod"}), "", http.StatusOK)
mustStatus(t, "tag live", tagCode(c, s.indexArn("vec-bucket", "live-idx"), map[string]string{"stage": "live"}), "", http.StatusOK)
mustStatus(t, "tag test", tagCode(c, s.indexArn("vec-bucket", "test-idx"), map[string]string{"stage": "test"}), "", http.StatusOK)
c.storePolicy("vec-bucket", policyOf(
statement("Allow", `"s3vectors:GetIndex"`, bucketArn+"/index/*", `{"StringEquals":{"aws:ResourceTag/stage":"live"}}`),
statement("Allow", `"s3vectors:GetVectorBucket"`, bucketArn, `{"StringEquals":{"aws:ResourceTag/env":"prod"}}`),
))
if code := c.getIndex("reader", "vec-bucket", "live-idx"); code != http.StatusOK {
t.Fatalf("GetIndex on the index tagged stage=live: %d", code)
}
if code := c.getIndex("reader", "vec-bucket", "test-idx"); code != http.StatusForbidden {
t.Fatalf("GetIndex on the index tagged stage=test: %d, want 403", code)
}
getBucket := func() int {
code, _ := c.op("reader", "GetVectorBucket", &GetVectorBucketRequest{VectorBucketName: "vec-bucket"})
return code
}
if code := getBucket(); code != http.StatusOK {
t.Fatalf("GetVectorBucket on the bucket tagged env=prod: %d", code)
}
mustStatus(t, "retag bucket", tagCode(c, bucketArn, map[string]string{"env": "dev"}), "", http.StatusOK)
if code := getBucket(); code != http.StatusForbidden {
t.Fatalf("GetVectorBucket after retagging env=dev: %d, want 403", code)
}
}
func TestVectorBucketTagReachesIndexes(t *testing.T) {
s, c := newTagClient(t)
c.setup("vec-bucket", "idx")
bucketArn := s.vectorBucketArn("vec-bucket")
mustStatus(t, "tag bucket", tagCode(c, bucketArn, map[string]string{"env": "prod"}), "", http.StatusOK)
mustStatus(t, "tag index", tagCode(c, s.indexArn("vec-bucket", "idx"), map[string]string{"env": "dev"}), "", http.StatusOK)
c.storePolicy("vec-bucket", policyOf(
statement("Allow", `"s3vectors:GetIndex"`, bucketArn+"/index/*", `{"StringEquals":{"s3vectors:VectorBucketTag/env":"prod"}}`),
))
if code := c.getIndex("reader", "vec-bucket", "idx"); code != http.StatusOK {
t.Fatalf("GetIndex in a bucket tagged env=prod: %d", code)
}
mustStatus(t, "retag bucket", tagCode(c, bucketArn, map[string]string{"env": "dev"}), "", http.StatusOK)
if code := c.getIndex("reader", "vec-bucket", "idx"); code != http.StatusForbidden {
t.Fatalf("GetIndex after retagging the bucket env=dev: %d, want 403", code)
}
}
func TestResourceTagDenyProtectsTaggedIndexes(t *testing.T) {
s, c := newTagClient(t)
c.setup("vec-bucket", "kept", "open")
bucketArn := s.vectorBucketArn("vec-bucket")
mustStatus(t, "tag index", tagCode(c, s.indexArn("vec-bucket", "kept"), map[string]string{"protected": "true"}), "", http.StatusOK)
c.storePolicy("vec-bucket", policyOf(
`{"Effect":"Allow","Principal":"*","Action":"s3vectors:*","Resource":["`+bucketArn+`","`+bucketArn+`/index/*"]}`,
statement("Deny", `["s3vectors:DeleteIndex","s3vectors:GetIndex"]`, bucketArn+"/index/*", `{"StringEquals":{"aws:ResourceTag/protected":"true"}}`),
))
if code := c.getIndex("reader", "vec-bucket", "kept"); code != http.StatusForbidden {
t.Fatalf("GetIndex on a protected index: %d, want 403", code)
}
if code := c.getIndex("reader", "vec-bucket", "open"); code != http.StatusOK {
t.Fatalf("GetIndex on an unprotected index: %d", code)
}
code, body := c.op("reader", "DeleteIndex", &DeleteIndexRequest{VectorBucketName: "vec-bucket", IndexName: "kept"})
mustStatus(t, "DeleteIndex on a protected index", code, body, http.StatusForbidden)
if c.fs.Get(s3tables.GetNamespacePath("vec-bucket", indexNamespace), "kept") == nil {
t.Fatal("a refused delete removed the protected index")
}
}
func TestRequestTagsAndTagKeysGateTagging(t *testing.T) {
s, c := newTagClient(t)
c.setup("vec-bucket")
bucketArn := s.vectorBucketArn("vec-bucket")
mustStatus(t, "tag bucket", tagCode(c, bucketArn, map[string]string{"team": "search", "env": "dev"}), "", http.StatusOK)
c.storePolicy("vec-bucket", policyOf(
statement("Allow", `"s3vectors:TagResource"`, bucketArn,
`{"StringEquals":{"aws:RequestTag/team":"search"},"ForAllValues:StringEquals":{"aws:TagKeys":["team","env"]}}`),
statement("Allow", `"s3vectors:UntagResource"`, bucketArn, `{"ForAllValues:StringEquals":{"aws:TagKeys":["env"]}}`),
))
for _, tc := range []struct {
tags map[string]string
want int
}{
{map[string]string{"team": "search"}, http.StatusOK},
{map[string]string{"team": "search", "env": "prod"}, http.StatusOK},
{map[string]string{"team": "other"}, http.StatusForbidden},
{map[string]string{"team": "search", "owner": "x"}, http.StatusForbidden},
{map[string]string{"env": "prod"}, http.StatusForbidden},
} {
code, body := c.tag("reader", bucketArn, tc.tags)
mustStatus(t, "TagResource "+jsonOf(tc.tags), code, body, tc.want)
}
code, body := c.untag("reader", bucketArn, "team")
mustStatus(t, "UntagResource team", code, body, http.StatusForbidden)
code, body = c.untag("reader", bucketArn, "env")
mustStatus(t, "UntagResource env", code, body, http.StatusOK)
if _, tags := c.list("", bucketArn); len(tags) != 1 || tags["team"] != "search" {
t.Fatalf("tags = %v, want only team=search", tags)
}
}
func TestRequestTagGatesCreateIndex(t *testing.T) {
s, c := newTagClient(t)
c.setup("vec-bucket")
c.storePolicy("vec-bucket", policyOf(
statement("Allow", `["s3vectors:CreateIndex","s3vectors:TagResource"]`, s.vectorBucketArn("vec-bucket")+"/index/*",
`{"StringEquals":{"aws:RequestTag/team":"search"}}`),
))
create := func(name string, tags map[string]string) int {
code, _ := c.op("reader", "CreateIndex", &CreateIndexRequest{
VectorBucketName: "vec-bucket", IndexName: name, DataType: "float32", Dimension: 3, Tags: tags,
})
return code
}
if code := create("other", map[string]string{"team": "other"}); code != http.StatusForbidden {
t.Fatalf("CreateIndex tagged team=other: %d, want 403", code)
}
if code := create("untagged", nil); code != http.StatusForbidden {
t.Fatalf("CreateIndex without tags: %d, want 403", code)
}
if code := create("search", map[string]string{"team": "search"}); code != http.StatusOK {
t.Fatalf("CreateIndex tagged team=search: %d", code)
}
if code := c.getIndex("", "vec-bucket", "other"); code != http.StatusNotFound {
t.Fatalf("refused create left an index: %d", code)
}
}
func TestResourceTagFiltersVectorBucketListing(t *testing.T) {
s, c := newTagClient(t)
for bucket, env := range map[string]string{"prod-bucket": "prod", "dev-bucket": "dev"} {
c.setup(bucket)
arn := s.vectorBucketArn(bucket)
mustStatus(t, "tag "+bucket, tagCode(c, arn, map[string]string{"env": env}), "", http.StatusOK)
c.storePolicy(bucket, policyOf(
statement("Allow", `"s3vectors:ListVectorBuckets"`, arn, `{"StringEquals":{"aws:ResourceTag/env":"prod"}}`),
))
}
code, body := c.op("reader", "ListVectorBuckets", &ListVectorBucketsRequest{})
mustStatus(t, "ListVectorBuckets", code, body, http.StatusOK)
var resp ListVectorBucketsResponse
if err := json.Unmarshal([]byte(body), &resp); err != nil {
t.Fatalf("decode %s: %v", body, err)
}
if len(resp.VectorBuckets) != 1 || resp.VectorBuckets[0].VectorBucketName != "prod-bucket" {
t.Fatalf("listed %s, want only prod-bucket", body)
}
}
func TestResourceTagGatesPolicyDelete(t *testing.T) {
s, c := newTagClient(t)
c.setup("vec-bucket")
bucketArn := s.vectorBucketArn("vec-bucket")
mustStatus(t, "tag bucket", tagCode(c, bucketArn, map[string]string{"env": "dev"}), "", http.StatusOK)
c.storePolicy("vec-bucket", policyOf(
statement("Allow", `"s3vectors:DeleteVectorBucketPolicy"`, bucketArn, `{"StringEquals":{"aws:ResourceTag/env":"prod"}}`),
))
del := func() int {
code, _ := c.op("reader", "DeleteVectorBucketPolicy", &DeleteVectorBucketPolicyRequest{VectorBucketName: "vec-bucket"})
return code
}
if code := del(); code != http.StatusForbidden {
t.Fatalf("DeleteVectorBucketPolicy on env=dev: %d, want 403", code)
}
mustStatus(t, "retag bucket", tagCode(c, bucketArn, map[string]string{"env": "prod"}), "", http.StatusOK)
if code := del(); code != http.StatusOK {
t.Fatalf("DeleteVectorBucketPolicy on env=prod: %d", code)
}
}
func TestReadsResourceTags(t *testing.T) {
for policy, want := range map[string]bool{
"": false,
`{"Statement":[{"Effect":"Allow","Principal":"*","Action":"s3vectors:*","Resource":"*"}]}`: false,
`{"Statement":[{"Effect":"Allow","Principal":"*","Action":"s3vectors:*","Resource":"*","Condition":{"StringEquals":{"s3vectors:VectorBucketTag/a":"b"}}}]}`: false,
`{"Statement":[{"Effect":"Allow","Principal":"*","Action":"s3vectors:*","Resource":"*","Condition":{"StringEquals":{"aws:ResourceTag/a":"b"}}}]}`: true,
`{"Statement":[{"Effect":"Deny","Principal":"*","Action":"s3vectors:*","Resource":"*","Condition":{"StringEquals":{"aws:ResourceTag\/a":"b"}}}]}`: true,
`{"Statement":[{"Effect":"Deny","Principal":"*","Action":"s3vectors:*","Resource":"*","Condition":{"StringEquals":{"aws:ResourceTag/a":"b"}}}]}`: true,
`not-json`: true,
} {
if got := readsResourceTags(policy); got != want {
t.Errorf("readsResourceTags(%s) = %v, want %v", policy, got, want)
}
}
}
func tagCode(c *tagClient, arn string, tags map[string]string) int {
code, _ := c.tag("", arn, tags)
return code
}
func jsonOf(v interface{}) string {
raw, _ := json.Marshal(v)
return string(raw)
}
func TestValidateVectorPolicyConditions(t *testing.T) {
withCondition := func(condition string) string {
return `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":"*","Action":"s3vectors:GetIndex","Resource":"*","Condition":` + condition + `}]}`
}
for _, ok := range []string{
`{"StringEquals":{"aws:ResourceTag/env":"prod"}}`,
`{"StringLike":{"aws:RequestTag/team":"search-*"}}`,
`{"ForAllValues:StringEquals":{"aws:TagKeys":["team","env"]}}`,
`{"StringNotEquals":{"s3vectors:VectorBucketTag/env":"dev"}}`,
`{"Null":{"aws:ResourceTag/owner":true}}`,
`{"StringEquals":{"aws:ResourceTag/env":["prod","staging"]}}`,
`{"NumericLessThan":{"aws:RequestTag/priority":10}}`,
} {
if err := validateVectorPolicy(withCondition(ok)); err != nil {
t.Errorf("%s rejected: %v", ok, err)
}
}
for _, bad := range []string{
`{"StringEquals":{"aws:SourceIp":"10.0.0.1"}}`,
`{"StringEquals":{"s3tables:TableBucketTag/env":"prod"}}`,
`{"StringEquals":{"aws:ResourceTag/":"prod"}}`,
`{"StringEquals":{"aws:resourcetag/env":"prod"}}`,
`{"StringEqualsIfExists":{"aws:ResourceTag/env":"prod"}}`,
`{"NoSuchOperator":{"aws:ResourceTag/env":"prod"}}`,
`"aws:ResourceTag/env"`,
`{"StringEquals":{"aws:ResourceTag/env":null}}`,
`{"StringEquals":{"aws:ResourceTag/env":[]}}`,
`{"StringEquals":{"aws:ResourceTag/env":[null]}}`,
`{"StringEquals":{"aws:ResourceTag/env":{"a":"b"}}}`,
`{"StringEquals":{"aws:ResourceTag/env":["prod",["dev"]]}}`,
} {
if err := validateVectorPolicy(withCondition(bad)); err == nil {
t.Errorf("%s accepted", bad)
}
}
lower := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":"*","Action":"s3vectors:GetIndex","Resource":"*","condition":{"StringEquals":{"aws:ResourceTag/env":"prod"}}}]}`
if err := validateVectorPolicy(lower); err == nil {
t.Error("a lower-case condition field was accepted")
}
}
func TestPutVectorBucketPolicyWithTagConditions(t *testing.T) {
s, c := newTagClient(t)
c.setup("vec-bucket", "live-idx", "test-idx")
mustStatus(t, "tag live", tagCode(c, s.indexArn("vec-bucket", "live-idx"), map[string]string{"stage": "live"}), "", http.StatusOK)
policy := policyOf(statement("Allow", `"s3vectors:GetIndex"`, s.vectorBucketArn("vec-bucket")+"/index/*",
`{"StringEquals":{"aws:ResourceTag/stage":"live"}}`))
code, body := c.op("", "PutVectorBucketPolicy", &PutVectorBucketPolicyRequest{VectorBucketName: "vec-bucket", Policy: policy})
mustStatus(t, "PutVectorBucketPolicy", code, body, http.StatusOK)
code, body = c.op("", "GetVectorBucketPolicy", &GetVectorBucketPolicyRequest{VectorBucketName: "vec-bucket"})
mustStatus(t, "GetVectorBucketPolicy", code, body, http.StatusOK)
var got GetVectorBucketPolicyResponse
if err := json.Unmarshal([]byte(body), &got); err != nil || got.Policy != policy {
t.Fatalf("stored policy = %s (%v), want %s", body, err, policy)
}
if code := c.getIndex("reader", "vec-bucket", "live-idx"); code != http.StatusOK {
t.Fatalf("GetIndex on stage=live: %d", code)
}
if code := c.getIndex("reader", "vec-bucket", "test-idx"); code != http.StatusForbidden {
t.Fatalf("GetIndex on an untagged index: %d, want 403", code)
}
}
+5 -5
View File
@@ -63,8 +63,8 @@ func (s *Server) tagTarget(r *http.Request, bodyArn string) (vectorBucketARNs, s
// authorizeTagging grants action on the tagged bucket or index and returns a
// context binding the catalog calls to the bucket it authorized. An index
// target must be a vector index, not another table in its namespace.
func (s *Server) authorizeTagging(r *http.Request, action string, arns vectorBucketARNs) (context.Context, error) {
record, err := s.authorizeVector(r.Context(), r, action, arns.bucket, arns.index)
func (s *Server) authorizeTagging(r *http.Request, action string, arns vectorBucketARNs, req tagRequest) (context.Context, error) {
record, err := s.authorizeVectorTags(r.Context(), r, action, arns.bucket, arns.index, req)
if err != nil {
return nil, err
}
@@ -113,7 +113,7 @@ func (s *Server) handleTagResource(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusBadRequest, "ValidationException", err.Error())
return
}
ctx, err := s.authorizeTagging(r, "s3vectors:TagResource", arns)
ctx, err := s.authorizeTagging(r, "s3vectors:TagResource", arns, setTags(req.Tags))
if err != nil {
catalogErr(w, err)
return
@@ -144,7 +144,7 @@ func (s *Server) handleUntagResource(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusBadRequest, "ValidationException", "tagKeys are required")
return
}
ctx, err := s.authorizeTagging(r, "s3vectors:UntagResource", arns)
ctx, err := s.authorizeTagging(r, "s3vectors:UntagResource", arns, tagRequest{keys: req.TagKeys})
if err != nil {
catalogErr(w, err)
return
@@ -177,7 +177,7 @@ func (s *Server) handleListTagsForResource(w http.ResponseWriter, r *http.Reques
writeError(w, http.StatusBadRequest, "ValidationException", err.Error())
return
}
ctx, err := s.authorizeTagging(r, "s3vectors:ListTagsForResource", arns)
ctx, err := s.authorizeTagging(r, "s3vectors:ListTagsForResource", arns, tagRequest{})
if err != nil {
catalogErr(w, err)
return