diff --git a/.github/workflows/rust-vector-tests.yml b/.github/workflows/rust-vector-tests.yml index 431b0d9d99..4bea1ed29c 100644 --- a/.github/workflows/rust-vector-tests.yml +++ b/.github/workflows/rust-vector-tests.yml @@ -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 diff --git a/go.mod b/go.mod index a883daee07..ae6aa6f979 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 4ba7dbd4d9..0bec2ec331 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/note/star_history.svg b/note/star_history.svg index c82cc2d852..1d1ee3acee 100644 --- a/note/star_history.svg +++ b/note/star_history.svg @@ -6,7 +6,7 @@ - 2026-11-02T00:59:30.216249 + 2026-11-03T01:08:51.340051 image/svg+xml @@ -40,23 +40,23 @@ z - + - - + - + - + - + - + - + - + - + - + - + - + @@ -301,18 +301,18 @@ L 352.854865 26.215385 - + - + - + @@ -325,18 +325,18 @@ L 448.569443 26.215385 - + - + - + @@ -349,18 +349,18 @@ L 544.153084 26.215385 - + - + - + @@ -484,41 +484,41 @@ z - + - - + - + - + - + - + - + - + - + @@ -577,18 +577,18 @@ L 709.2 268.150053 - + - + - + @@ -599,18 +599,18 @@ L 709.2 224.40191 - + - + - + @@ -621,18 +621,18 @@ L 709.2 180.653767 - + - + - + @@ -643,18 +643,18 @@ L 709.2 136.905624 - + - + - + - + - + - + @@ -813,937 +813,940 @@ z +" clip-path="url(#p32e8e80803)" style="fill: none; stroke: #0969da; stroke-width: 1.6; stroke-linecap: square"/> + diff --git a/test/s3vectors/s3vectors_test.py b/test/s3vectors/s3vectors_test.py index 05f83943ee..375e2d6242 100644 --- a/test/s3vectors/s3vectors_test.py +++ b/test/s3vectors/s3vectors_test.py @@ -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") diff --git a/weed/operation/needle_parse_test.go b/weed/operation/needle_parse_test.go index 6251ca771d..b597a5e8c6 100644 --- a/weed/operation/needle_parse_test.go +++ b/weed/operation/needle_parse_test.go @@ -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 { diff --git a/weed/s3api/s3tables/permissions.go b/weed/s3api/s3tables/permissions.go index 3cbd3a55d7..2184668776 100644 --- a/weed/s3api/s3tables/permissions.go +++ b/weed/s3api/s3tables/permissions.go @@ -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} } } diff --git a/weed/s3api/s3tables/permissions_test.go b/weed/s3api/s3tables/permissions_test.go index f2dabcda8e..037cdbb2ce 100644 --- a/weed/s3api/s3tables/permissions_test.go +++ b/weed/s3api/s3tables/permissions_test.go @@ -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) + } +} diff --git a/weed/s3api/s3vectors/handlers.go b/weed/s3api/s3vectors/handlers.go index 0f629d2cc1..edd6609711 100644 --- a/weed/s3api/s3vectors/handlers.go +++ b/weed/s3api/s3vectors/handlers.go @@ -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 } diff --git a/weed/s3api/s3vectors/handlers_test.go b/weed/s3api/s3vectors/handlers_test.go index 6ed043d8bc..ed1914d09e 100644 --- a/weed/s3api/s3vectors/handlers_test.go +++ b/weed/s3api/s3vectors/handlers_test.go @@ -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) diff --git a/weed/s3api/s3vectors/tag_conditions_test.go b/weed/s3api/s3vectors/tag_conditions_test.go new file mode 100644 index 0000000000..78f2786db9 --- /dev/null +++ b/weed/s3api/s3vectors/tag_conditions_test.go @@ -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) + } +} diff --git a/weed/s3api/s3vectors/tagging.go b/weed/s3api/s3vectors/tagging.go index c7370a67f1..2b0b02d1a0 100644 --- a/weed/s3api/s3vectors/tagging.go +++ b/weed/s3api/s3vectors/tagging.go @@ -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