diff --git a/daprovider/anytrust/google_cloud_storage_service.go b/daprovider/anytrust/google_cloud_storage_service.go index 0d9b89638e0..8864a083bc6 100644 --- a/daprovider/anytrust/google_cloud_storage_service.go +++ b/daprovider/anytrust/google_cloud_storage_service.go @@ -4,14 +4,13 @@ package anytrust import ( "context" + "errors" "fmt" "io" "math" - "sort" "time" googlestorage "cloud.google.com/go/storage" - "github.com/google/go-cmp/cmp" "github.com/spf13/pflag" "google.golang.org/api/option" @@ -133,7 +132,12 @@ func (gcs *GoogleCloudStorageService) GetByHash(ctx context.Context, key common. log.Trace("anytrust.GoogleCloudStorageService.GetByHash", "key", pretty.PrettyHash(key), "this", gcs) buf, err := gcs.operator.Download(ctx, gcs.bucket, gcs.objectPrefix, key) if err != nil { - log.Error("anytrust.GoogleCloudStorageService.GetByHash", "err", err) + // context.Canceled is expected when RedundantStorageService cancels + // the shared sub-context after a faster inner service already returned + // successfully. Logging it at ERROR level would be misleading. + if !errors.Is(err, context.Canceled) { + log.Error("anytrust.GoogleCloudStorageService.GetByHash", "err", err) + } return nil, err } return buf, nil @@ -159,22 +163,37 @@ func (gcs *GoogleCloudStorageService) String() string { } func (gcs *GoogleCloudStorageService) HealthCheck(ctx context.Context) error { + // GCP's testIamPermissions API does not evaluate conditional IAM bindings, + // so it always returns empty results when permissions are granted via a + // condition (e.g. resource.name.startsWith(...)). Instead, we verify + // storage access by performing an actual write/read/delete round-trip on a + // small probe object within our configured prefix. + probeKey := gcs.objectPrefix + ".health-check-probe" bucket := gcs.operator.Bucket(gcs.bucket) - // check if we have bucket permissions - permissions := []string{ - "storage.objects.create", - "storage.objects.delete", - "storage.objects.list", - "storage.objects.get", + + // write + w := bucket.Object(probeKey).NewWriter(ctx) + if _, err := w.Write([]byte("health-check")); err != nil { + return fmt.Errorf("health check: failed to write probe object: %w", err) + } + if err := w.Close(); err != nil { + return fmt.Errorf("health check: failed to close probe object writer: %w", err) } - perms, err := bucket.IAM().TestPermissions(ctx, permissions) + + // read + r, err := bucket.Object(probeKey).NewReader(ctx) if err != nil { - return fmt.Errorf("could not check permissions: %w", err) + return fmt.Errorf("health check: failed to read probe object: %w", err) + } + if _, err := io.ReadAll(r); err != nil { + r.Close() + return fmt.Errorf("health check: failed to read probe object data: %w", err) } - sort.Strings(permissions) - sort.Strings(perms) - if !cmp.Equal(perms, permissions) { - return fmt.Errorf("permissions mismatch (-want +got):\n%s", cmp.Diff(permissions, perms)) + r.Close() + + // delete + if err := bucket.Object(probeKey).Delete(ctx); err != nil { + return fmt.Errorf("health check: failed to delete probe object: %w", err) } return nil