From ef1acaa98cd4f94419aeeee3390dca68b1aa4b44 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 4 May 2026 20:09:39 -0700 Subject: [PATCH] fix(iam): preserve CreatedAt across boots + paginate ListProviders Two medium-priority issues gemini flagged on the read-only IAM API: 1. The static-config bootstrap was setting CreatedAt = time.Now() on every server start, so the IAM GetOpenIDConnectProvider response's CreateDate shifted on each restart even when backed by a persistent store. Look up the existing record via GetProviderByARN first and preserve its CreatedAt; only the UpdatedAt advances. 2. FilerOIDCProviderStore.ListProviders had a hardcoded Limit: 1000 that silently truncated above that. Stream-paginate via StartFromFileName, returning io.EOF naturally and surfacing all other errors instead of swallowing them. Addresses two gemini medium reviews on PR #9319. --- weed/iam/integration/iam_manager.go | 14 ++++- weed/iam/integration/oidc_provider_store.go | 61 ++++++++++++++------- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/weed/iam/integration/iam_manager.go b/weed/iam/integration/iam_manager.go index 842c351be..fcf432f8f 100644 --- a/weed/iam/integration/iam_manager.go +++ b/weed/iam/integration/iam_manager.go @@ -286,16 +286,24 @@ func (m *IAMManager) initOIDCProviderStore(config *IAMConfig) error { continue } clientIDs := extractClientIDs(pc.Config) - now := time.Now() + ctx := context.Background() + // Preserve CreatedAt across reboots when a persistent store already + // has this provider — IAM's GetOpenIDConnectProvider response + // shouldn't shift its CreateDate every time the server restarts. + now := time.Now().UTC() + createdAt := now + if existing, err := store.GetProviderByARN(ctx, m.getFilerAddress(), arn); err == nil && existing != nil && !existing.CreatedAt.IsZero() { + createdAt = existing.CreatedAt + } rec := &OIDCProviderRecord{ AccountID: accountID, ARN: arn, URL: issuer, ClientIDs: clientIDs, - CreatedAt: now, + CreatedAt: createdAt, UpdatedAt: now, } - if err := store.StoreProvider(context.Background(), m.getFilerAddress(), rec); err != nil { + if err := store.StoreProvider(ctx, m.getFilerAddress(), rec); err != nil { glog.Warningf("mirror static OIDC provider %s into store: %v", pc.Name, err) } } diff --git a/weed/iam/integration/oidc_provider_store.go b/weed/iam/integration/oidc_provider_store.go index 836266f6b..749ba7853 100644 --- a/weed/iam/integration/oidc_provider_store.go +++ b/weed/iam/integration/oidc_provider_store.go @@ -5,7 +5,9 @@ import ( "crypto/sha1" "encoding/hex" "encoding/json" + "errors" "fmt" + "io" "net/url" "sort" "strings" @@ -281,30 +283,49 @@ func (f *FilerOIDCProviderStore) ListProviders(ctx context.Context, filerAddress var out []*OIDCProviderRecord err := f.withFilerClient(filerAddress, func(client filer_pb.SeaweedFilerClient) error { - stream, err := client.ListEntries(ctx, &filer_pb.ListEntriesRequest{ - Directory: f.basePath, - Limit: 1000, - }) - if err != nil { - return fmt.Errorf("list OIDC providers: %v", err) - } + // Stream-paginate via StartFromFileName so deployments with more + // than 1000 providers don't get a silently truncated list. + const pageSize = 1000 + startFrom := "" for { - resp, err := stream.Recv() + stream, err := client.ListEntries(ctx, &filer_pb.ListEntriesRequest{ + Directory: f.basePath, + Limit: pageSize, + StartFromFileName: startFrom, + InclusiveStartFrom: false, + }) if err != nil { + return fmt.Errorf("list OIDC providers: %v", err) + } + lastName := "" + pageCount := 0 + for { + resp, recvErr := stream.Recv() + if recvErr != nil { + if errors.Is(recvErr, io.EOF) { + break + } + return fmt.Errorf("recv OIDC provider entry: %w", recvErr) + } + if resp.Entry == nil || resp.Entry.IsDirectory { + continue + } + lastName = resp.Entry.Name + pageCount++ + if !strings.HasSuffix(resp.Entry.Name, ".json") { + continue + } + var rec OIDCProviderRecord + if err := json.Unmarshal(resp.Entry.Content, &rec); err != nil { + glog.Warningf("skipping malformed OIDC provider record %s: %v", resp.Entry.Name, err) + continue + } + out = append(out, &rec) + } + if pageCount < pageSize { break } - if resp.Entry == nil || resp.Entry.IsDirectory { - continue - } - if !strings.HasSuffix(resp.Entry.Name, ".json") { - continue - } - var rec OIDCProviderRecord - if err := json.Unmarshal(resp.Entry.Content, &rec); err != nil { - glog.Warningf("skipping malformed OIDC provider record %s: %v", resp.Entry.Name, err) - continue - } - out = append(out, &rec) + startFrom = lastName } return nil })