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.
This commit is contained in:
Chris Lu
2026-05-04 20:09:39 -07:00
parent 12688c249e
commit ef1acaa98c
2 changed files with 52 additions and 23 deletions

View File

@@ -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)
}
}

View File

@@ -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
})