From 30069f3e4544df0c3a82a33dff238cae3dd72f10 Mon Sep 17 00:00:00 2001 From: Khris Richardson Date: Fri, 2 Oct 2026 17:36:19 -0700 Subject: [PATCH] iam: manage OIDC providers and roles over the filer IAM gRPC service (#11523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * s3/iam: manage roles through the IAM API, with an opt-in persistent role store Roles could only come from the IAM config file: the S3 server pinned the role store to memory and the embedded IAM API had no role actions, so a role could not be created, retrusted or revoked without editing the file and restarting every gateway. Role store - Read the `roleStore` key (the IAMConfig field already existed). With an IAM config file the default stays memory; with none it is the filer, as for OIDC providers, so zero-config clusters keep runtime-created roles. - Roles from the IAM config file never go into a persistent role store, which outlives the file and may be shared by S3 servers with different files. They are served from memory beneath the store, as OIDC providers are: a stored role of the same name takes precedence, and deleting it restores the file's. A config-file role cannot be changed or deleted through the API (UnmodifiableEntity), and removing one from the file removes it at the next start. An in-memory store holds them as records, as before. They have no creation time, so CreateDate is omitted rather than reporting when this server started. SetRoleStore installs a store the same way, so a store set after startup keeps the config-file roles, as SetOIDCProviderStore does for providers. - Watch /etc/iam/roles and drop the cached role definitions on change. The cached filer store otherwise serves a peer's stale role for up to its 5m TTL, which keeps a revoked trust policy in force on the other gateways. - Role stores wrap ErrRoleNotFound for a missing role; the filer store used to report any failed lookup as "role not found". CreateRole proceeds only on a confirmed absence, so an unreadable store cannot let it write over an existing role. IAM actions - CreateRole, GetRole, ListRoles, DeleteRole, UpdateAssumeRolePolicy, AttachRolePolicy, DetachRolePolicy, ListAttachedRolePolicies. The reads are allowed in read-only mode. - A role defined in the config file is reloaded from it at every start, so changing or deleting it through the API is refused (UnmodifiableEntity) rather than silently reverted. - DeleteRole with policies attached is refused (DeleteConflict), as on AWS. - Role names follow AWS's rules ([\w+=,.@-]{1,64}); a role is stored as .json in the filer, so this also keeps a name from leaving the role store's directory. At most 10 managed policies per role (AWS's default quota; MaxManagedPoliciesPerUser is 10 too), LimitExceeded beyond. - DeletePolicy is refused (DeleteConflict) while a role attaches the policy, as it already is for users and groups: roles attach policies by name, so a policy created later under the deleted one's name would otherwise take effect on the role. - Role paths other than "/" and role tags are not stored, so they are refused rather than dropped. Role IDs and sessions - Roles get a unique RoleId when first stored (random, AWS AROA form), kept across updates; a config-file role gets a stable ID derived from its name, since it is created again at every start. - Sessions issued through AssumeRoleWithWebIdentity, AssumeRoleWithCredentials and AssumeRole carry the role's ID (claim "rid"), and a request under a role whose current ID differs is denied. Resolving a session's policies by role name let a session outlive its role: once a role was deleted, a role later created under the same name — with a different trust policy and different policies — revived every unexpired session of the old one with the new role's permissions. Sessions issued before this change carry no ID and are unaffected until they expire. Integration test (test/s3/iam, run with `make start-services`): TestWebIdentityWithProviderAndRoleManagedThroughIAMAPI configures an OIDC provider, a managed policy and a role entirely through the IAM API against a JWKS served by the test, then checks the trusted subject gets credentials scoped to the attached policy; another subject, a token signed by another key, an unsigned token and a token for another audience are refused; and UpdateAssumeRolePolicy moves the trust at once. Co-Authored-By: Claude Opus 5.5 (1M context) * iam: manage OIDC providers and roles over the filer IAM gRPC service The filer's SeaweedIdentityAccessManagement service covers users, access keys, policies and service accounts, but not the OIDC providers and roles that STS web-identity federation needs. A controller that already manages IAM over this service (seaweedfs-operator's S3OIDCProvider) has no transport for them; its swadmin client returns ErrOIDCNotWired and names this as the recommended fix. - PutOIDCProvider / GetOIDCProvider / DeleteOIDCProvider / ListOIDCProviders and PutRole / GetRole / DeleteRole / ListRoles. - They write the filer-backed stores at their default paths, which S3 servers read when configured with a filer-typed "oidcProviderStore" and "roleStore"; the S3 servers' /etc/iam subscription applies changes without a restart. - Put is an upsert, so a controller can reconcile to it. Deleting a provider or role that does not exist returns NotFound, as DeleteUser does for a user; clients treat that as already deleted. The provider's account ID travels in the request, since the filer does not know the STS accountId. - PutRole applies the IAM API's rules: AWS role names, at most 10 managed policies. - An S3 server serves the roles and providers of its own IAM config file ahead of the store, so a stored entry with the same name has no effect on that server. - PutRole keeps a replaced role's RoleId and gives a role created anew a fresh one, so sessions of a deleted role do not carry over to a later role of the same name. - DeletePolicy returns FailedPrecondition while a role attaches the policy (see the IAM API's DeleteConflict in the previous change). DeletePolicy on this service still does not check user attachments, which predates this. - PutOIDCProvider requires an https issuer (http only for a loopback host): STS fetches the issuer's signing keys from it, so over plain HTTP anyone on the network path could substitute their own. - The OIDC provider and role RPCs refuse to run on an unauthenticated service (FailedPrecondition until jwt.filer_signing.key is set). Users and policies keep the service's opt-in auth, but these grant STS access outright: otherwise anyone who can reach the port could register an issuer they control, create a role trusting it, and exchange a token for S3 credentials. The filer's unauthenticated notice becomes a warning that says so. - A store that cannot be read is Unavailable, never "not found", so a Put never writes over an entry it could not see. - Validation is shared with the IAM API through PrepareRoleDefinition and PrepareOIDCProviderRecord. Co-Authored-By: Claude Opus 5.5 (1M context) * s3/iam: bind every role session to its role, and change roles atomically Review follow-ups. Session binding - The role-ID check ran only when a session carried no policy names, and AssumeRole embeds the role's attached policies, so those sessions kept their permissions after the role was deleted or recreated. The check now runs for every session carrying a role ID, before policy selection. - A named role that cannot be resolved at issuance gets no session, instead of one with no role ID (which nothing binds). - A config-file role's ID is derived from its name and trust policy, not the name alone: a different role put in the file under the same name gets a new ID, while an unchanged role keeps its sessions across restarts. Role writes - RoleStore gains UpdateRole, a read-modify-write that lands only if the role is unchanged since the read, and otherwise re-reads and retries. The filer store uses the filer's write conditions (IF_NOT_EXISTS for a new role, IF_ENTRY_EQUAL otherwise). CreateRole, UpdateAssumeRolePolicy and Attach/DetachRolePolicy all go through it, so two gateways no longer overwrite each other's changes, a change racing a delete no longer writes the role back, and of two concurrent creates one gets EntityAlreadyExists. - The filer store's ListRoles pages past 1,000 entries and fails on a broken stream instead of returning what arrived, so DeletePolicy's attachment check sees every role. ListRoles skips a role deleted between listing and reading it. - CreateRole validates first; a failed write is ServiceFailure, not InvalidInput. Any Tags.* parameter is refused, not only the first key. - ExecuteAction's skipPersist covers the S3ApiConfiguration only; the comment now says so. Role and OIDC provider actions write their own stores. Each fix has a test that fails without it. Against a real filer with two gateways, concurrent AttachRolePolicy calls lost 1-4 of 8 attachments per run before this change and none after. Co-Authored-By: Claude Opus 5.5 (1M context) * iam: PutRole changes roles atomically and checks its ARN; https issuers' keys stay on https Review follow-ups on top of the role-store changes. - PutRole goes through RoleStore.UpdateRole, so the decision to keep an existing role's ID or mint a new one is made against the role as it is when written. A PutRole racing a DeleteRole can no longer write the deleted role back with its old ID, which would revive its sessions. A failed store read or write is Unavailable. - PutRole refuses a role_arn that does not name the role: STS resolves a role by the name in the ARN it is given. - PutOIDCProvider requires an https issuer, but discovery could still name a plain-http jwks_uri, and a key fetch could be redirected to http. For an https issuer, a non-https jwks_uri from discovery is refused (the issuer's own /.well-known/jwks.json is used instead), and the client that fetches discovery and keys refuses any https-to-http redirect. An operator-set jwksUri is left as configured. Each has a test that fails without its guard. Co-Authored-By: Claude Opus 5.5 (1M context) * s3/iam: one role snapshot per decision; DeleteRole is atomic; watch a custom role store path Review follow-ups. - Authorization evaluates the policies of the role definition the session's binding was checked against, instead of reading the role again: a role replaced in between cannot lend a session its policies. - AssumeRole and AssumeRoleWithLDAPIdentity issue the session from the definition whose trust admits the caller (IAMManager.ResolveRoleForPrincipal), and take its ID, duration cap and embedded policies from that same definition. A role replaced after the caller's trust check by one that does not trust the caller now yields AccessDenied, not a session bound to the replacement. - A RoleUpdate that returns nil deletes the role, on the same condition as a write: the filer store deletes with ObjectTransaction on IF_ENTRY_EQUAL, routed and locked like the conditional CreateEntry. DeleteRole decides against the role it deletes, so a policy attached meanwhile on another server is a DeleteConflict, and a delete never removes a role written after its check. - S3 servers watch the role store's configured basePath, not only /etc/iam/roles, so a custom path also drops peers' cached roles on change. Each has a test that fails without it. Live against a real filer: DeleteRole refuses while a policy is attached and removes the entry once detached; all test/s3/iam CI stages pass. Co-Authored-By: Claude Opus 5.5 (1M context) * s3/iam: state which roles DeletePolicy's attachment check can see RolesAttachingPolicy sees the stored roles and this server's config-file roles. A role defined only in another server's IAM config file is invisible to it, so a config-file role that attaches a managed policy is protected only on the servers whose file defines it. The doc comment now says so and how to avoid it: keep such roles in every server's file, or attach only config-file policies to config-file roles. Co-Authored-By: Claude Opus 5.5 (1M context) * iam: note that a role store set after startup is not watched for peer changes S3 servers build their metadata watch list once, at startup, from the role store installed then. SetRoleStore's doc now says that a filer-backed store installed later with a different basePath is not watched, so peers' changes to it reach this server's cached roles only when the cache expires. Co-Authored-By: Claude Opus 5.5 (1M context) * iam: DeleteRole deletes only the role it saw; issuer URLs are bare Review follow-ups. - The filer IAM service's DeleteRole looked the role up, then deleted by name, so a PutRole landing in between had its new definition deleted. It now deletes through RoleStore.UpdateRole, conditional on the entry it read. If the role was replaced meanwhile, it returns Aborted rather than deleting the replacement, and the caller decides again. - PutOIDCProvider refuses an issuer URL with userinfo, a query or a fragment. The provider's ARN comes from host and path alone, while STS matches a token's iss claim against the stored URL exactly, so such a provider shared the bare issuer's ARN and matched no token. A loopback "localhost" is now matched without regard to case. Both have tests that fail without them. Co-Authored-By: Claude Opus 5.5 (1M context) * iam: write OIDC providers atomically over the filer IAM gRPC service PutOIDCProvider read the record, then stored unconditionally; a racing DeleteOIDCProvider left the put's stale read merged into the rewritten record. DeleteOIDCProvider read, then deleted unconditionally; a racing PutOIDCProvider's newer record could be removed instead. These are the races the role RPCs closed with UpdateRole. OIDCProviderStore gains UpdateProvider with the same contract: memory under its lock, filer as a conditional write (IF_ENTRY_EQUAL / IF_NOT_EXISTS) or conditional delete retrying a changed entry. PutOIDCProvider merges the fields the request cannot carry against the record as it is written; DeleteOIDCProvider aborts rather than delete a record replaced meanwhile. isRoleWriteConflict is renamed isEntryWriteConflict — the conditional- write check is shared by both stores now. * iam: guard PutRole against a nil credential manager, fix its doc comment PutRole read attached policies through s.credentialManager without the nil check its sibling handlers make, so a server built without one panicked on a PutRole naming a policy. It now fails the call as FailedPrecondition like the others. The doc comment also had the store/static precedence backwards: a stored role shadows a same-named config-file role (as the overlay serves it), not the other way around. --------- Co-authored-by: Claude Opus 5.5 (1M context) Co-authored-by: Chris Lu --- weed/command/filer.go | 12 +- weed/iam/integration/iam_manager.go | 21 + weed/iam/integration/oidc_provider_store.go | 166 +++ weed/iam/integration/role_store.go | 6 +- weed/iam/oidc/oidc_https_keys_test.go | 81 ++ weed/iam/oidc/oidc_provider.go | 29 +- weed/pb/iam.proto | 107 ++ weed/pb/iam_pb/iam.pb.go | 1149 +++++++++++++++-- weed/pb/iam_pb/iam_grpc.pb.go | 312 +++++ weed/server/filer_server_handlers_iam_grpc.go | 15 + .../filer_server_handlers_iam_grpc_sts.go | 457 +++++++ ...filer_server_handlers_iam_grpc_sts_test.go | 499 +++++++ 12 files changed, 2763 insertions(+), 91 deletions(-) create mode 100644 weed/iam/oidc/oidc_https_keys_test.go create mode 100644 weed/server/filer_server_handlers_iam_grpc_sts.go create mode 100644 weed/server/filer_server_handlers_iam_grpc_sts_test.go diff --git a/weed/command/filer.go b/weed/command/filer.go index db17fa5fd..a23f31544 100644 --- a/weed/command/filer.go +++ b/weed/command/filer.go @@ -23,6 +23,7 @@ import ( _ "github.com/seaweedfs/seaweedfs/weed/credential/postgres" "github.com/seaweedfs/seaweedfs/weed/filer" "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/iam/integration" "github.com/seaweedfs/seaweedfs/weed/pb" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" @@ -475,9 +476,18 @@ func (fo *FilerOptions) startFiler() { if credentialManager != nil { adminSigningKey := security.SigningKey(util.GetViper().GetString("jwt.filer_signing.key")) iamGrpcServer := weed_server.NewIamGrpcServer(credentialManager, adminSigningKey) + // The OIDC provider and role RPCs write where S3 servers configured with + // filer-typed "oidcProviderStore" and "roleStore" read: this filer, at + // the stores' default base paths. + selfAddress := func() string { return string(filerAddress) } + if roleStore, err := integration.NewFilerRoleStore(nil, selfAddress); err != nil { + glog.Warningf("IAM gRPC: role RPCs disabled: %v", err) + } else { + iamGrpcServer.SetSTSStores(integration.NewFilerOIDCProviderStore(nil, selfAddress), roleStore) + } iam_pb.RegisterSeaweedIdentityAccessManagementServer(grpcS, iamGrpcServer) if len(adminSigningKey) == 0 { - glog.V(0).Info("Registered IAM gRPC service on filer (unauthenticated; set jwt.filer_signing.key in security.toml to require admin Bearer token)") + glog.Warning("IAM gRPC service on filer is UNAUTHENTICATED: anyone who can reach this port can create users and policies, and its OIDC provider and role RPCs are refused; set jwt.filer_signing.key in security.toml to require an admin Bearer token") } else { glog.V(0).Info("Registered IAM gRPC service on filer (admin Bearer token required)") } diff --git a/weed/iam/integration/iam_manager.go b/weed/iam/integration/iam_manager.go index 3254025d5..b3af9c7b3 100644 --- a/weed/iam/integration/iam_manager.go +++ b/weed/iam/integration/iam_manager.go @@ -2155,3 +2155,24 @@ func (m *IAMManager) ValidateTrustPolicyForCredentials(ctx context.Context, role // Use existing trust policy validation logic return m.validateTrustPolicyForCredentials(ctx, roleDef, mockRequest) } + +// PrepareOIDCProviderRecord builds and validates the record that +// CreateOpenIDConnectProvider stores for an issuer, deriving its ARN from the +// account ID and issuer URL. +func PrepareOIDCProviderRecord(accountID, issuerURL string, clientIDs, thumbprints []string) (*OIDCProviderRecord, error) { + arn, err := DeriveOIDCProviderARN(accountID, issuerURL) + if err != nil { + return nil, err + } + rec := &OIDCProviderRecord{ + AccountID: accountID, + ARN: arn, + URL: issuerURL, + ClientIDs: append([]string(nil), clientIDs...), + Thumbprints: append([]string(nil), thumbprints...), + } + if err := validateOIDCProviderRecord(rec); err != nil { + return nil, err + } + return rec, nil +} diff --git a/weed/iam/integration/oidc_provider_store.go b/weed/iam/integration/oidc_provider_store.go index 77eb1b8a5..0eaea1f4c 100644 --- a/weed/iam/integration/oidc_provider_store.go +++ b/weed/iam/integration/oidc_provider_store.go @@ -17,7 +17,10 @@ import ( "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/pb" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" + "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) // Sentinel errors returned by the IAM manager and OIDCProviderStore. Callers @@ -87,8 +90,27 @@ type OIDCProviderStore interface { GetProviderByIssuerAndAccount(ctx context.Context, filerAddress string, issuer, accountID string) (*OIDCProviderRecord, error) ListProviders(ctx context.Context, filerAddress string) ([]*OIDCProviderRecord, error) DeleteProvider(ctx context.Context, filerAddress string, arn string) error + // UpdateProvider replaces a provider's record with update's result, + // atomically: the write or delete happens only against the record update + // saw, so a change in between applies update again to what the other + // writer left. + UpdateProvider(ctx context.Context, filerAddress string, arn string, update OIDCProviderUpdate) error } +// OIDCProviderUpdate computes a provider's new record from its current one, +// nil when the provider does not exist. It returns nil to delete the +// provider, and an error to leave it unchanged. It may run more than once: it +// is called again with the fresh record when another writer changed the +// provider in between. +type OIDCProviderUpdate func(current *OIDCProviderRecord) (*OIDCProviderRecord, error) + +// maxProviderUpdateAttempts bounds UpdateProvider's retries under contention. +const maxProviderUpdateAttempts = 10 + +// errProviderUpdateContended is returned when the provider kept changing +// under UpdateProvider for maxProviderUpdateAttempts reads. +var errProviderUpdateContended = errors.New("OIDC provider changed concurrently; retry") + // MemoryOIDCProviderStore is a process-local store, suitable for tests and // single-node deployments. It also acts as the in-memory cache hydrated from // static config at boot. @@ -184,6 +206,27 @@ func (m *MemoryOIDCProviderStore) DeleteProvider(ctx context.Context, _ string, return nil } +// UpdateProvider applies update under the store's lock (filerAddress ignored +// for the memory store). +func (m *MemoryOIDCProviderStore) UpdateProvider(ctx context.Context, _ string, arn string, update OIDCProviderUpdate) error { + if arn == "" { + return fmt.Errorf("ARN is required") + } + m.mu.Lock() + defer m.mu.Unlock() + next, err := update(copyOIDCProviderRecord(m.providers[arn])) + if err != nil { + return err + } + if next == nil { + delete(m.providers, arn) + return nil + } + next.ARN = arn + m.providers[arn] = copyOIDCProviderRecord(next) + return nil +} + // FilerOIDCProviderStore persists records as JSON files in a filer directory, // mirroring FilerRoleStore. type FilerOIDCProviderStore struct { @@ -421,6 +464,129 @@ func (f *FilerOIDCProviderStore) DeleteProvider(ctx context.Context, filerAddres }) } +// UpdateProvider reads the provider's entry, applies update, and writes the +// result on the condition that the entry is unchanged since the read — +// absent, when the provider did not exist — so the filer refuses a write +// racing another writer's change or delete, and update is applied again to +// what that writer left. A delete is made on the same condition, so it +// removes the record update saw and not one written after it. +func (f *FilerOIDCProviderStore) UpdateProvider(ctx context.Context, filerAddress string, arn string, update OIDCProviderUpdate) error { + filerAddress = f.resolveFilerAddress(filerAddress) + if filerAddress == "" { + return fmt.Errorf("filer address is required") + } + if arn == "" { + return fmt.Errorf("ARN is required") + } + return f.withFilerClient(filerAddress, func(client filer_pb.SeaweedFilerClient) error { + for attempt := 0; attempt < maxProviderUpdateAttempts; attempt++ { + var entry *filer_pb.Entry + var current *OIDCProviderRecord + resp, err := filer_pb.LookupEntry(ctx, client, &filer_pb.LookupDirectoryEntryRequest{ + Directory: f.basePath, + Name: f.fileName(arn), + }) + switch { + case errors.Is(err, filer_pb.ErrNotFound): + case err != nil: + return fmt.Errorf("lookup OIDC provider %s: %w", arn, err) + case resp.Entry != nil: + entry = resp.Entry + current = &OIDCProviderRecord{} + if err := json.Unmarshal(entry.Content, current); err != nil { + return fmt.Errorf("failed to deserialize OIDC provider %s: %v", arn, err) + } + } + + next, err := update(current) + if err != nil { + return err + } + if next == nil { + if entry == nil { + return nil + } + deleted, err := f.deleteProviderEntryIfUnchanged(ctx, client, entry) + if err != nil { + return fmt.Errorf("failed to delete OIDC provider %s: %w", arn, err) + } + if !deleted { + glog.V(3).Infof("OIDC provider %s changed before its delete; retrying", arn) + continue + } + return nil + } + next.ARN = arn + data, err := json.MarshalIndent(next, "", " ") + if err != nil { + return fmt.Errorf("failed to serialize OIDC provider %s: %v", arn, err) + } + + clause := &filer_pb.WriteCondition_Clause{Kind: filer_pb.WriteCondition_IF_NOT_EXISTS} + if entry != nil { + clause = &filer_pb.WriteCondition_Clause{Kind: filer_pb.WriteCondition_IF_ENTRY_EQUAL, ExpectedEntry: entry} + } + now := time.Now().Unix() + created, err := client.CreateEntry(ctx, &filer_pb.CreateEntryRequest{ + Directory: f.basePath, + Entry: &filer_pb.Entry{ + Name: f.fileName(arn), + Attributes: &filer_pb.FuseAttributes{ + Mtime: now, + Crtime: now, + FileMode: uint32(0600), + }, + Content: data, + }, + Condition: &filer_pb.WriteCondition{Clauses: []*filer_pb.WriteCondition_Clause{clause}}, + }) + if isEntryWriteConflict(created, err) { + glog.V(3).Infof("OIDC provider %s changed during update; retrying", arn) + continue + } + if err != nil { + return fmt.Errorf("failed to store OIDC provider %s: %v", arn, err) + } + if created.Error != "" { + return fmt.Errorf("failed to store OIDC provider %s: %s", arn, created.Error) + } + return nil + } + return fmt.Errorf("update OIDC provider %s: %w", arn, errProviderUpdateContended) + }) +} + +// deleteProviderEntryIfUnchanged deletes the provider's entry if it still +// equals entry, reporting false when it changed. The delete is routed and +// locked as the conditional CreateEntry of the same path is, so the two +// serialize. +func (f *FilerOIDCProviderStore) deleteProviderEntryIfUnchanged(ctx context.Context, client filer_pb.SeaweedFilerClient, entry *filer_pb.Entry) (bool, error) { + fullPath := f.basePath + "/" + entry.Name + resp, err := client.ObjectTransaction(ctx, &filer_pb.ObjectTransactionRequest{ + LockKey: fullPath, + RouteKey: s3_constants.ObjectWriteRouteKeyPrefix + fullPath, + Condition: &filer_pb.WriteCondition{Clauses: []*filer_pb.WriteCondition_Clause{{ + Kind: filer_pb.WriteCondition_IF_ENTRY_EQUAL, ExpectedEntry: entry, + }}}, + Mutations: []*filer_pb.ObjectMutation{{ + Type: filer_pb.ObjectMutation_DELETE, Directory: f.basePath, Name: entry.Name, IsDeleteData: true, + }}, + }) + if err != nil { + if status.Code(err) == codes.FailedPrecondition { + return false, nil + } + return false, err + } + if resp.ErrorCode == filer_pb.FilerError_PRECONDITION_FAILED { + return false, nil + } + if resp.Error != "" { + return false, errors.New(resp.Error) + } + return true, nil +} + func (f *FilerOIDCProviderStore) withFilerClient(filerAddress string, fn func(filer_pb.SeaweedFilerClient) error) error { return pb.WithGrpcFilerClient(false, 0, pb.ServerAddress(filerAddress), f.grpcDialOption, fn) } diff --git a/weed/iam/integration/role_store.go b/weed/iam/integration/role_store.go index b1841224e..27ff79f4d 100644 --- a/weed/iam/integration/role_store.go +++ b/weed/iam/integration/role_store.go @@ -358,7 +358,7 @@ func (f *FilerRoleStore) UpdateRole(ctx context.Context, filerAddress string, ro }, Condition: &filer_pb.WriteCondition{Clauses: []*filer_pb.WriteCondition_Clause{clause}}, }) - if isRoleWriteConflict(created, err) { + if isEntryWriteConflict(created, err) { glog.V(3).Infof("Role %s changed during update; retrying", roleName) continue } @@ -404,10 +404,10 @@ func (f *FilerRoleStore) deleteRoleEntryIfUnchanged(ctx context.Context, client return true, nil } -// isRoleWriteConflict reports a write the filer refused because its condition +// isEntryWriteConflict reports a write the filer refused because its condition // no longer held: in the response, or as FailedPrecondition when the write // was forwarded to the entry's owner filer. -func isRoleWriteConflict(resp *filer_pb.CreateEntryResponse, err error) bool { +func isEntryWriteConflict(resp *filer_pb.CreateEntryResponse, err error) bool { if err != nil { return status.Code(err) == codes.FailedPrecondition } diff --git a/weed/iam/oidc/oidc_https_keys_test.go b/weed/iam/oidc/oidc_https_keys_test.go new file mode 100644 index 000000000..d1e19f857 --- /dev/null +++ b/weed/iam/oidc/oidc_https_keys_test.go @@ -0,0 +1,81 @@ +package oidc + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" +) + +// plainKeyServer serves a JWKS over plain http and counts the fetches: an +// https issuer's keys must never be read from it. +func plainKeyServer(t *testing.T) (*httptest.Server, *atomic.Int32) { + t.Helper() + var hits atomic.Int32 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + hits.Add(1) + _ = json.NewEncoder(w).Encode(JWKS{Keys: []JWK{{Kty: "RSA", Kid: "attacker", Use: "sig", Alg: "RS256", N: "AQAB", E: "AQAB"}}}) + })) + t.Cleanup(server.Close) + return server, &hits +} + +// httpsIssuer starts a TLS issuer whose handlers the test supplies, and a +// provider for it. +func httpsIssuer(t *testing.T, mux *http.ServeMux) (*httptest.Server, *OIDCProvider) { + t.Helper() + server := httptest.NewTLSServer(mux) + t.Cleanup(server.Close) + p := NewOIDCProvider("https-keys") + if err := p.Initialize(&OIDCConfig{Issuer: server.URL, ClientID: "c", TLSInsecureSkipVerify: true}); err != nil { + t.Fatalf("Initialize: %v", err) + } + return server, p +} + +// Discovery naming a plain-http jwks_uri for an https issuer is refused; the +// keys come from the issuer's own https host instead. +func TestAnHTTPSIssuersDiscoveredKeysMustBeHTTPS(t *testing.T) { + plain, plainHits := plainKeyServer(t) + var server *httptest.Server + var ownKeyHits atomic.Int32 + mux := http.NewServeMux() + mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) { + _ = json.NewEncoder(w).Encode(map[string]string{"issuer": server.URL, "jwks_uri": plain.URL + "/jwks"}) + }) + mux.HandleFunc("/.well-known/jwks.json", func(w http.ResponseWriter, r *http.Request) { + ownKeyHits.Add(1) + _ = json.NewEncoder(w).Encode(JWKS{Keys: []JWK{{Kty: "RSA", Kid: "k1", Use: "sig", Alg: "RS256", N: "AQAB", E: "AQAB"}}}) + }) + server, p := httpsIssuer(t, mux) + + if err := p.fetchJWKS(context.Background()); err != nil { + t.Fatalf("fetchJWKS: %v", err) + } + if got := plainHits.Load(); got != 0 { + t.Fatalf("keys were fetched over http %d time(s)", got) + } + if got := ownKeyHits.Load(); got != 1 { + t.Fatalf("expected the issuer's own https jwks to be used, got %d hits", got) + } +} + +// An https key fetch redirected to plain http fails rather than follow it. +func TestAnHTTPSKeyFetchIsNotRedirectedToHTTP(t *testing.T) { + plain, plainHits := plainKeyServer(t) + mux := http.NewServeMux() + mux.HandleFunc("/.well-known/openid-configuration", http.NotFound) + mux.HandleFunc("/.well-known/jwks.json", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, plain.URL+"/jwks", http.StatusFound) + }) + _, p := httpsIssuer(t, mux) + + if err := p.fetchJWKS(context.Background()); err == nil { + t.Fatal("fetchJWKS followed a redirect to http") + } + if got := plainHits.Load(); got != 0 { + t.Fatalf("keys were fetched over http %d time(s)", got) + } +} diff --git a/weed/iam/oidc/oidc_provider.go b/weed/iam/oidc/oidc_provider.go index fddd3ddd3..4f947cf2b 100644 --- a/weed/iam/oidc/oidc_provider.go +++ b/weed/iam/oidc/oidc_provider.go @@ -318,10 +318,25 @@ type JWK struct { func NewOIDCProvider(name string) *OIDCProvider { return &OIDCProvider{ name: name, - httpClient: &http.Client{Timeout: 30 * time.Second}, + httpClient: &http.Client{Timeout: 30 * time.Second, CheckRedirect: refuseDowngradeRedirect}, } } +// refuseDowngradeRedirect is the redirect policy of the client that fetches +// discovery documents and signing keys: a request that began over https may +// not be redirected to plain http, where anyone on the network path could +// substitute the keys and mint tokens STS accepts. It otherwise keeps +// net/http's default limit of 10 redirects. +func refuseDowngradeRedirect(req *http.Request, via []*http.Request) error { + if len(via) >= 10 { + return errors.New("stopped after 10 redirects") + } + if via[0].URL.Scheme == "https" && req.URL.Scheme != "https" { + return fmt.Errorf("refusing redirect from %s to non-https %s", via[0].URL.Redacted(), req.URL.Redacted()) + } + return nil +} + // Name returns the provider name func (p *OIDCProvider) Name() string { return p.name @@ -409,8 +424,9 @@ func (p *OIDCProvider) Initialize(config interface{}) error { TLSClientConfig: tlsConfig, } p.httpClient = &http.Client{ - Timeout: 30 * time.Second, - Transport: transport, + Timeout: 30 * time.Second, + Transport: transport, + CheckRedirect: refuseDowngradeRedirect, } // For testing, we'll skip the actual OIDC client initialization @@ -971,6 +987,13 @@ func (p *OIDCProvider) fetchDiscoveryJWKSUri(ctx context.Context, discoveryURL s return "", fmt.Errorf("discovery issuer %q does not match configured issuer %q", doc.Issuer, p.config.Issuer) } + // An https issuer's keys must come over https too; otherwise the issuer's + // TLS protects nothing. Refused here, discovery falls back to the + // issuer's own /.well-known/jwks.json. + if strings.HasPrefix(p.config.Issuer, "https://") && !strings.HasPrefix(doc.JWKSUri, "https://") { + return "", fmt.Errorf("discovery jwks_uri %q is not https for https issuer %q", doc.JWKSUri, p.config.Issuer) + } + return doc.JWKSUri, nil } diff --git a/weed/pb/iam.proto b/weed/pb/iam.proto index 45a08d8f3..89c9002df 100644 --- a/weed/pb/iam.proto +++ b/weed/pb/iam.proto @@ -38,6 +38,20 @@ service SeaweedIdentityAccessManagement { rpc GetServiceAccount (GetServiceAccountRequest) returns (GetServiceAccountResponse); rpc ListServiceAccounts (ListServiceAccountsRequest) returns (ListServiceAccountsResponse); rpc GetServiceAccountByAccessKey (GetServiceAccountByAccessKeyRequest) returns (GetServiceAccountByAccessKeyResponse); + + // OIDC Provider Management. Writes the records the S3 servers' STS trusts + // when they run with "oidcProviderStore": {"storeType": "filer"}. + rpc PutOIDCProvider (PutOIDCProviderRequest) returns (PutOIDCProviderResponse); + rpc GetOIDCProvider (GetOIDCProviderRequest) returns (GetOIDCProviderResponse); + rpc DeleteOIDCProvider (DeleteOIDCProviderRequest) returns (DeleteOIDCProviderResponse); + rpc ListOIDCProviders (ListOIDCProvidersRequest) returns (ListOIDCProvidersResponse); + + // Role Management. Writes the roles the S3 servers' STS assumes when they + // run with "roleStore": {"storeType": "filer"}. + rpc PutRole (PutRoleRequest) returns (PutRoleResponse); + rpc GetRole (GetRoleRequest) returns (GetRoleResponse); + rpc DeleteRole (DeleteRoleRequest) returns (DeleteRoleResponse); + rpc ListRoles (ListRolesRequest) returns (ListRolesResponse); } ////////////////////////////////////////////////// @@ -306,6 +320,99 @@ message GetServiceAccountByAccessKeyResponse { } +////////////////////////////////////////////////// +// OIDC Provider Messages + +message OIDCProvider { + string issuer_url = 1; + repeated string client_ids = 2; + repeated string thumbprints = 3; + // account_id scopes the provider; empty means global. The ARN is derived + // from it and the issuer URL. + string account_id = 4; + string arn = 5; // output only +} + +// PutOIDCProviderRequest creates the provider, or replaces the client IDs and +// thumbprints of an existing one. +message PutOIDCProviderRequest { + string issuer_url = 1; + repeated string client_ids = 2; + repeated string thumbprints = 3; + string account_id = 4; +} + +message PutOIDCProviderResponse { + string arn = 1; +} + +message GetOIDCProviderRequest { + string issuer_url = 1; + string account_id = 2; +} + +message GetOIDCProviderResponse { + OIDCProvider provider = 1; +} + +message DeleteOIDCProviderRequest { + string issuer_url = 1; + string account_id = 2; +} + +message DeleteOIDCProviderResponse { +} + +message ListOIDCProvidersRequest { +} + +message ListOIDCProvidersResponse { + repeated OIDCProvider providers = 1; +} + +////////////////////////////////////////////////// +// Role Messages + +message Role { + string role_name = 1; + string role_arn = 2; // defaults to arn:aws:iam::role/ + string trust_policy = 3; // JSON trust policy document + repeated string attached_policies = 4; // managed policy names + string description = 5; + int64 max_session_duration = 6; // seconds; 0 uses the STS default +} + +// PutRoleRequest creates the role or replaces it. +message PutRoleRequest { + Role role = 1; +} + +message PutRoleResponse { + string role_arn = 1; +} + +message GetRoleRequest { + string role_name = 1; +} + +message GetRoleResponse { + Role role = 1; +} + +message DeleteRoleRequest { + string role_name = 1; +} + +message DeleteRoleResponse { +} + +message ListRolesRequest { +} + +message ListRolesResponse { + repeated Role roles = 1; +} + ////////////////////////////////////////////////// // S3 IAM Cache Management // Designed for unidirectional propagation from Filer to S3 Servers diff --git a/weed/pb/iam_pb/iam.pb.go b/weed/pb/iam_pb/iam.pb.go index 22e0c5dc3..2c43bf880 100644 --- a/weed/pb/iam_pb/iam.pb.go +++ b/weed/pb/iam_pb/iam.pb.go @@ -2680,6 +2680,883 @@ func (x *GetServiceAccountByAccessKeyResponse) GetServiceAccount() *ServiceAccou return nil } +type OIDCProvider struct { + state protoimpl.MessageState `protogen:"open.v1"` + IssuerUrl string `protobuf:"bytes,1,opt,name=issuer_url,json=issuerUrl,proto3" json:"issuer_url,omitempty"` + ClientIds []string `protobuf:"bytes,2,rep,name=client_ids,json=clientIds,proto3" json:"client_ids,omitempty"` + Thumbprints []string `protobuf:"bytes,3,rep,name=thumbprints,proto3" json:"thumbprints,omitempty"` + // account_id scopes the provider; empty means global. The ARN is derived + // from it and the issuer URL. + AccountId string `protobuf:"bytes,4,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` + Arn string `protobuf:"bytes,5,opt,name=arn,proto3" json:"arn,omitempty"` // output only + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OIDCProvider) Reset() { + *x = OIDCProvider{} + mi := &file_iam_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OIDCProvider) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OIDCProvider) ProtoMessage() {} + +func (x *OIDCProvider) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[56] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OIDCProvider.ProtoReflect.Descriptor instead. +func (*OIDCProvider) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{56} +} + +func (x *OIDCProvider) GetIssuerUrl() string { + if x != nil { + return x.IssuerUrl + } + return "" +} + +func (x *OIDCProvider) GetClientIds() []string { + if x != nil { + return x.ClientIds + } + return nil +} + +func (x *OIDCProvider) GetThumbprints() []string { + if x != nil { + return x.Thumbprints + } + return nil +} + +func (x *OIDCProvider) GetAccountId() string { + if x != nil { + return x.AccountId + } + return "" +} + +func (x *OIDCProvider) GetArn() string { + if x != nil { + return x.Arn + } + return "" +} + +// PutOIDCProviderRequest creates the provider, or replaces the client IDs and +// thumbprints of an existing one. +type PutOIDCProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + IssuerUrl string `protobuf:"bytes,1,opt,name=issuer_url,json=issuerUrl,proto3" json:"issuer_url,omitempty"` + ClientIds []string `protobuf:"bytes,2,rep,name=client_ids,json=clientIds,proto3" json:"client_ids,omitempty"` + Thumbprints []string `protobuf:"bytes,3,rep,name=thumbprints,proto3" json:"thumbprints,omitempty"` + AccountId string `protobuf:"bytes,4,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PutOIDCProviderRequest) Reset() { + *x = PutOIDCProviderRequest{} + mi := &file_iam_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PutOIDCProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutOIDCProviderRequest) ProtoMessage() {} + +func (x *PutOIDCProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[57] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutOIDCProviderRequest.ProtoReflect.Descriptor instead. +func (*PutOIDCProviderRequest) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{57} +} + +func (x *PutOIDCProviderRequest) GetIssuerUrl() string { + if x != nil { + return x.IssuerUrl + } + return "" +} + +func (x *PutOIDCProviderRequest) GetClientIds() []string { + if x != nil { + return x.ClientIds + } + return nil +} + +func (x *PutOIDCProviderRequest) GetThumbprints() []string { + if x != nil { + return x.Thumbprints + } + return nil +} + +func (x *PutOIDCProviderRequest) GetAccountId() string { + if x != nil { + return x.AccountId + } + return "" +} + +type PutOIDCProviderResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Arn string `protobuf:"bytes,1,opt,name=arn,proto3" json:"arn,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PutOIDCProviderResponse) Reset() { + *x = PutOIDCProviderResponse{} + mi := &file_iam_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PutOIDCProviderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutOIDCProviderResponse) ProtoMessage() {} + +func (x *PutOIDCProviderResponse) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[58] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutOIDCProviderResponse.ProtoReflect.Descriptor instead. +func (*PutOIDCProviderResponse) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{58} +} + +func (x *PutOIDCProviderResponse) GetArn() string { + if x != nil { + return x.Arn + } + return "" +} + +type GetOIDCProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + IssuerUrl string `protobuf:"bytes,1,opt,name=issuer_url,json=issuerUrl,proto3" json:"issuer_url,omitempty"` + AccountId string `protobuf:"bytes,2,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetOIDCProviderRequest) Reset() { + *x = GetOIDCProviderRequest{} + mi := &file_iam_proto_msgTypes[59] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetOIDCProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOIDCProviderRequest) ProtoMessage() {} + +func (x *GetOIDCProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[59] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOIDCProviderRequest.ProtoReflect.Descriptor instead. +func (*GetOIDCProviderRequest) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{59} +} + +func (x *GetOIDCProviderRequest) GetIssuerUrl() string { + if x != nil { + return x.IssuerUrl + } + return "" +} + +func (x *GetOIDCProviderRequest) GetAccountId() string { + if x != nil { + return x.AccountId + } + return "" +} + +type GetOIDCProviderResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider *OIDCProvider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetOIDCProviderResponse) Reset() { + *x = GetOIDCProviderResponse{} + mi := &file_iam_proto_msgTypes[60] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetOIDCProviderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOIDCProviderResponse) ProtoMessage() {} + +func (x *GetOIDCProviderResponse) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[60] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOIDCProviderResponse.ProtoReflect.Descriptor instead. +func (*GetOIDCProviderResponse) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{60} +} + +func (x *GetOIDCProviderResponse) GetProvider() *OIDCProvider { + if x != nil { + return x.Provider + } + return nil +} + +type DeleteOIDCProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + IssuerUrl string `protobuf:"bytes,1,opt,name=issuer_url,json=issuerUrl,proto3" json:"issuer_url,omitempty"` + AccountId string `protobuf:"bytes,2,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteOIDCProviderRequest) Reset() { + *x = DeleteOIDCProviderRequest{} + mi := &file_iam_proto_msgTypes[61] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteOIDCProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteOIDCProviderRequest) ProtoMessage() {} + +func (x *DeleteOIDCProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[61] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteOIDCProviderRequest.ProtoReflect.Descriptor instead. +func (*DeleteOIDCProviderRequest) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{61} +} + +func (x *DeleteOIDCProviderRequest) GetIssuerUrl() string { + if x != nil { + return x.IssuerUrl + } + return "" +} + +func (x *DeleteOIDCProviderRequest) GetAccountId() string { + if x != nil { + return x.AccountId + } + return "" +} + +type DeleteOIDCProviderResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteOIDCProviderResponse) Reset() { + *x = DeleteOIDCProviderResponse{} + mi := &file_iam_proto_msgTypes[62] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteOIDCProviderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteOIDCProviderResponse) ProtoMessage() {} + +func (x *DeleteOIDCProviderResponse) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[62] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteOIDCProviderResponse.ProtoReflect.Descriptor instead. +func (*DeleteOIDCProviderResponse) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{62} +} + +type ListOIDCProvidersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListOIDCProvidersRequest) Reset() { + *x = ListOIDCProvidersRequest{} + mi := &file_iam_proto_msgTypes[63] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListOIDCProvidersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListOIDCProvidersRequest) ProtoMessage() {} + +func (x *ListOIDCProvidersRequest) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[63] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListOIDCProvidersRequest.ProtoReflect.Descriptor instead. +func (*ListOIDCProvidersRequest) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{63} +} + +type ListOIDCProvidersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Providers []*OIDCProvider `protobuf:"bytes,1,rep,name=providers,proto3" json:"providers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListOIDCProvidersResponse) Reset() { + *x = ListOIDCProvidersResponse{} + mi := &file_iam_proto_msgTypes[64] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListOIDCProvidersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListOIDCProvidersResponse) ProtoMessage() {} + +func (x *ListOIDCProvidersResponse) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[64] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListOIDCProvidersResponse.ProtoReflect.Descriptor instead. +func (*ListOIDCProvidersResponse) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{64} +} + +func (x *ListOIDCProvidersResponse) GetProviders() []*OIDCProvider { + if x != nil { + return x.Providers + } + return nil +} + +type Role struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoleName string `protobuf:"bytes,1,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + RoleArn string `protobuf:"bytes,2,opt,name=role_arn,json=roleArn,proto3" json:"role_arn,omitempty"` // defaults to arn:aws:iam::role/ + TrustPolicy string `protobuf:"bytes,3,opt,name=trust_policy,json=trustPolicy,proto3" json:"trust_policy,omitempty"` // JSON trust policy document + AttachedPolicies []string `protobuf:"bytes,4,rep,name=attached_policies,json=attachedPolicies,proto3" json:"attached_policies,omitempty"` // managed policy names + Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty"` + MaxSessionDuration int64 `protobuf:"varint,6,opt,name=max_session_duration,json=maxSessionDuration,proto3" json:"max_session_duration,omitempty"` // seconds; 0 uses the STS default + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Role) Reset() { + *x = Role{} + mi := &file_iam_proto_msgTypes[65] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Role) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Role) ProtoMessage() {} + +func (x *Role) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[65] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Role.ProtoReflect.Descriptor instead. +func (*Role) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{65} +} + +func (x *Role) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +func (x *Role) GetRoleArn() string { + if x != nil { + return x.RoleArn + } + return "" +} + +func (x *Role) GetTrustPolicy() string { + if x != nil { + return x.TrustPolicy + } + return "" +} + +func (x *Role) GetAttachedPolicies() []string { + if x != nil { + return x.AttachedPolicies + } + return nil +} + +func (x *Role) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Role) GetMaxSessionDuration() int64 { + if x != nil { + return x.MaxSessionDuration + } + return 0 +} + +// PutRoleRequest creates the role or replaces it. +type PutRoleRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Role *Role `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PutRoleRequest) Reset() { + *x = PutRoleRequest{} + mi := &file_iam_proto_msgTypes[66] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PutRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutRoleRequest) ProtoMessage() {} + +func (x *PutRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[66] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutRoleRequest.ProtoReflect.Descriptor instead. +func (*PutRoleRequest) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{66} +} + +func (x *PutRoleRequest) GetRole() *Role { + if x != nil { + return x.Role + } + return nil +} + +type PutRoleResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoleArn string `protobuf:"bytes,1,opt,name=role_arn,json=roleArn,proto3" json:"role_arn,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PutRoleResponse) Reset() { + *x = PutRoleResponse{} + mi := &file_iam_proto_msgTypes[67] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PutRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutRoleResponse) ProtoMessage() {} + +func (x *PutRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[67] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutRoleResponse.ProtoReflect.Descriptor instead. +func (*PutRoleResponse) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{67} +} + +func (x *PutRoleResponse) GetRoleArn() string { + if x != nil { + return x.RoleArn + } + return "" +} + +type GetRoleRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoleName string `protobuf:"bytes,1,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRoleRequest) Reset() { + *x = GetRoleRequest{} + mi := &file_iam_proto_msgTypes[68] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoleRequest) ProtoMessage() {} + +func (x *GetRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[68] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRoleRequest.ProtoReflect.Descriptor instead. +func (*GetRoleRequest) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{68} +} + +func (x *GetRoleRequest) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +type GetRoleResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Role *Role `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRoleResponse) Reset() { + *x = GetRoleResponse{} + mi := &file_iam_proto_msgTypes[69] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoleResponse) ProtoMessage() {} + +func (x *GetRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[69] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRoleResponse.ProtoReflect.Descriptor instead. +func (*GetRoleResponse) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{69} +} + +func (x *GetRoleResponse) GetRole() *Role { + if x != nil { + return x.Role + } + return nil +} + +type DeleteRoleRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoleName string `protobuf:"bytes,1,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteRoleRequest) Reset() { + *x = DeleteRoleRequest{} + mi := &file_iam_proto_msgTypes[70] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRoleRequest) ProtoMessage() {} + +func (x *DeleteRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[70] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRoleRequest.ProtoReflect.Descriptor instead. +func (*DeleteRoleRequest) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{70} +} + +func (x *DeleteRoleRequest) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +type DeleteRoleResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteRoleResponse) Reset() { + *x = DeleteRoleResponse{} + mi := &file_iam_proto_msgTypes[71] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRoleResponse) ProtoMessage() {} + +func (x *DeleteRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[71] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRoleResponse.ProtoReflect.Descriptor instead. +func (*DeleteRoleResponse) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{71} +} + +type ListRolesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRolesRequest) Reset() { + *x = ListRolesRequest{} + mi := &file_iam_proto_msgTypes[72] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRolesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRolesRequest) ProtoMessage() {} + +func (x *ListRolesRequest) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[72] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRolesRequest.ProtoReflect.Descriptor instead. +func (*ListRolesRequest) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{72} +} + +type ListRolesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Roles []*Role `protobuf:"bytes,1,rep,name=roles,proto3" json:"roles,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRolesResponse) Reset() { + *x = ListRolesResponse{} + mi := &file_iam_proto_msgTypes[73] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRolesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRolesResponse) ProtoMessage() {} + +func (x *ListRolesResponse) ProtoReflect() protoreflect.Message { + mi := &file_iam_proto_msgTypes[73] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRolesResponse.ProtoReflect.Descriptor instead. +func (*ListRolesResponse) Descriptor() ([]byte, []int) { + return file_iam_proto_rawDescGZIP(), []int{73} +} + +func (x *ListRolesResponse) GetRoles() []*Role { + if x != nil { + return x.Roles + } + return nil +} + type PutIdentityRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Identity *Identity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` @@ -2689,7 +3566,7 @@ type PutIdentityRequest struct { func (x *PutIdentityRequest) Reset() { *x = PutIdentityRequest{} - mi := &file_iam_proto_msgTypes[56] + mi := &file_iam_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2701,7 +3578,7 @@ func (x *PutIdentityRequest) String() string { func (*PutIdentityRequest) ProtoMessage() {} func (x *PutIdentityRequest) ProtoReflect() protoreflect.Message { - mi := &file_iam_proto_msgTypes[56] + mi := &file_iam_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2714,7 +3591,7 @@ func (x *PutIdentityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PutIdentityRequest.ProtoReflect.Descriptor instead. func (*PutIdentityRequest) Descriptor() ([]byte, []int) { - return file_iam_proto_rawDescGZIP(), []int{56} + return file_iam_proto_rawDescGZIP(), []int{74} } func (x *PutIdentityRequest) GetIdentity() *Identity { @@ -2732,7 +3609,7 @@ type PutIdentityResponse struct { func (x *PutIdentityResponse) Reset() { *x = PutIdentityResponse{} - mi := &file_iam_proto_msgTypes[57] + mi := &file_iam_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2744,7 +3621,7 @@ func (x *PutIdentityResponse) String() string { func (*PutIdentityResponse) ProtoMessage() {} func (x *PutIdentityResponse) ProtoReflect() protoreflect.Message { - mi := &file_iam_proto_msgTypes[57] + mi := &file_iam_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2757,7 +3634,7 @@ func (x *PutIdentityResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PutIdentityResponse.ProtoReflect.Descriptor instead. func (*PutIdentityResponse) Descriptor() ([]byte, []int) { - return file_iam_proto_rawDescGZIP(), []int{57} + return file_iam_proto_rawDescGZIP(), []int{75} } type RemoveIdentityRequest struct { @@ -2769,7 +3646,7 @@ type RemoveIdentityRequest struct { func (x *RemoveIdentityRequest) Reset() { *x = RemoveIdentityRequest{} - mi := &file_iam_proto_msgTypes[58] + mi := &file_iam_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2781,7 +3658,7 @@ func (x *RemoveIdentityRequest) String() string { func (*RemoveIdentityRequest) ProtoMessage() {} func (x *RemoveIdentityRequest) ProtoReflect() protoreflect.Message { - mi := &file_iam_proto_msgTypes[58] + mi := &file_iam_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2794,7 +3671,7 @@ func (x *RemoveIdentityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveIdentityRequest.ProtoReflect.Descriptor instead. func (*RemoveIdentityRequest) Descriptor() ([]byte, []int) { - return file_iam_proto_rawDescGZIP(), []int{58} + return file_iam_proto_rawDescGZIP(), []int{76} } func (x *RemoveIdentityRequest) GetUsername() string { @@ -2812,7 +3689,7 @@ type RemoveIdentityResponse struct { func (x *RemoveIdentityResponse) Reset() { *x = RemoveIdentityResponse{} - mi := &file_iam_proto_msgTypes[59] + mi := &file_iam_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2824,7 +3701,7 @@ func (x *RemoveIdentityResponse) String() string { func (*RemoveIdentityResponse) ProtoMessage() {} func (x *RemoveIdentityResponse) ProtoReflect() protoreflect.Message { - mi := &file_iam_proto_msgTypes[59] + mi := &file_iam_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2837,7 +3714,7 @@ func (x *RemoveIdentityResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveIdentityResponse.ProtoReflect.Descriptor instead. func (*RemoveIdentityResponse) Descriptor() ([]byte, []int) { - return file_iam_proto_rawDescGZIP(), []int{59} + return file_iam_proto_rawDescGZIP(), []int{77} } type PutGroupRequest struct { @@ -2849,7 +3726,7 @@ type PutGroupRequest struct { func (x *PutGroupRequest) Reset() { *x = PutGroupRequest{} - mi := &file_iam_proto_msgTypes[60] + mi := &file_iam_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2861,7 +3738,7 @@ func (x *PutGroupRequest) String() string { func (*PutGroupRequest) ProtoMessage() {} func (x *PutGroupRequest) ProtoReflect() protoreflect.Message { - mi := &file_iam_proto_msgTypes[60] + mi := &file_iam_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2874,7 +3751,7 @@ func (x *PutGroupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PutGroupRequest.ProtoReflect.Descriptor instead. func (*PutGroupRequest) Descriptor() ([]byte, []int) { - return file_iam_proto_rawDescGZIP(), []int{60} + return file_iam_proto_rawDescGZIP(), []int{78} } func (x *PutGroupRequest) GetGroup() *Group { @@ -2892,7 +3769,7 @@ type PutGroupResponse struct { func (x *PutGroupResponse) Reset() { *x = PutGroupResponse{} - mi := &file_iam_proto_msgTypes[61] + mi := &file_iam_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2904,7 +3781,7 @@ func (x *PutGroupResponse) String() string { func (*PutGroupResponse) ProtoMessage() {} func (x *PutGroupResponse) ProtoReflect() protoreflect.Message { - mi := &file_iam_proto_msgTypes[61] + mi := &file_iam_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2917,7 +3794,7 @@ func (x *PutGroupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PutGroupResponse.ProtoReflect.Descriptor instead. func (*PutGroupResponse) Descriptor() ([]byte, []int) { - return file_iam_proto_rawDescGZIP(), []int{61} + return file_iam_proto_rawDescGZIP(), []int{79} } type RemoveGroupRequest struct { @@ -2929,7 +3806,7 @@ type RemoveGroupRequest struct { func (x *RemoveGroupRequest) Reset() { *x = RemoveGroupRequest{} - mi := &file_iam_proto_msgTypes[62] + mi := &file_iam_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2941,7 +3818,7 @@ func (x *RemoveGroupRequest) String() string { func (*RemoveGroupRequest) ProtoMessage() {} func (x *RemoveGroupRequest) ProtoReflect() protoreflect.Message { - mi := &file_iam_proto_msgTypes[62] + mi := &file_iam_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2954,7 +3831,7 @@ func (x *RemoveGroupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveGroupRequest.ProtoReflect.Descriptor instead. func (*RemoveGroupRequest) Descriptor() ([]byte, []int) { - return file_iam_proto_rawDescGZIP(), []int{62} + return file_iam_proto_rawDescGZIP(), []int{80} } func (x *RemoveGroupRequest) GetGroupName() string { @@ -2972,7 +3849,7 @@ type RemoveGroupResponse struct { func (x *RemoveGroupResponse) Reset() { *x = RemoveGroupResponse{} - mi := &file_iam_proto_msgTypes[63] + mi := &file_iam_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2984,7 +3861,7 @@ func (x *RemoveGroupResponse) String() string { func (*RemoveGroupResponse) ProtoMessage() {} func (x *RemoveGroupResponse) ProtoReflect() protoreflect.Message { - mi := &file_iam_proto_msgTypes[63] + mi := &file_iam_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2997,7 +3874,7 @@ func (x *RemoveGroupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveGroupResponse.ProtoReflect.Descriptor instead. func (*RemoveGroupResponse) Descriptor() ([]byte, []int) { - return file_iam_proto_rawDescGZIP(), []int{63} + return file_iam_proto_rawDescGZIP(), []int{81} } var File_iam_proto protoreflect.FileDescriptor @@ -3162,7 +4039,63 @@ const file_iam_proto_rawDesc = "" + "\n" + "access_key\x18\x01 \x01(\tR\taccessKey\"g\n" + "$GetServiceAccountByAccessKeyResponse\x12?\n" + - "\x0fservice_account\x18\x01 \x01(\v2\x16.iam_pb.ServiceAccountR\x0eserviceAccount\"B\n" + + "\x0fservice_account\x18\x01 \x01(\v2\x16.iam_pb.ServiceAccountR\x0eserviceAccount\"\x9f\x01\n" + + "\fOIDCProvider\x12\x1d\n" + + "\n" + + "issuer_url\x18\x01 \x01(\tR\tissuerUrl\x12\x1d\n" + + "\n" + + "client_ids\x18\x02 \x03(\tR\tclientIds\x12 \n" + + "\vthumbprints\x18\x03 \x03(\tR\vthumbprints\x12\x1d\n" + + "\n" + + "account_id\x18\x04 \x01(\tR\taccountId\x12\x10\n" + + "\x03arn\x18\x05 \x01(\tR\x03arn\"\x97\x01\n" + + "\x16PutOIDCProviderRequest\x12\x1d\n" + + "\n" + + "issuer_url\x18\x01 \x01(\tR\tissuerUrl\x12\x1d\n" + + "\n" + + "client_ids\x18\x02 \x03(\tR\tclientIds\x12 \n" + + "\vthumbprints\x18\x03 \x03(\tR\vthumbprints\x12\x1d\n" + + "\n" + + "account_id\x18\x04 \x01(\tR\taccountId\"+\n" + + "\x17PutOIDCProviderResponse\x12\x10\n" + + "\x03arn\x18\x01 \x01(\tR\x03arn\"V\n" + + "\x16GetOIDCProviderRequest\x12\x1d\n" + + "\n" + + "issuer_url\x18\x01 \x01(\tR\tissuerUrl\x12\x1d\n" + + "\n" + + "account_id\x18\x02 \x01(\tR\taccountId\"K\n" + + "\x17GetOIDCProviderResponse\x120\n" + + "\bprovider\x18\x01 \x01(\v2\x14.iam_pb.OIDCProviderR\bprovider\"Y\n" + + "\x19DeleteOIDCProviderRequest\x12\x1d\n" + + "\n" + + "issuer_url\x18\x01 \x01(\tR\tissuerUrl\x12\x1d\n" + + "\n" + + "account_id\x18\x02 \x01(\tR\taccountId\"\x1c\n" + + "\x1aDeleteOIDCProviderResponse\"\x1a\n" + + "\x18ListOIDCProvidersRequest\"O\n" + + "\x19ListOIDCProvidersResponse\x122\n" + + "\tproviders\x18\x01 \x03(\v2\x14.iam_pb.OIDCProviderR\tproviders\"\xe2\x01\n" + + "\x04Role\x12\x1b\n" + + "\trole_name\x18\x01 \x01(\tR\broleName\x12\x19\n" + + "\brole_arn\x18\x02 \x01(\tR\aroleArn\x12!\n" + + "\ftrust_policy\x18\x03 \x01(\tR\vtrustPolicy\x12+\n" + + "\x11attached_policies\x18\x04 \x03(\tR\x10attachedPolicies\x12 \n" + + "\vdescription\x18\x05 \x01(\tR\vdescription\x120\n" + + "\x14max_session_duration\x18\x06 \x01(\x03R\x12maxSessionDuration\"2\n" + + "\x0ePutRoleRequest\x12 \n" + + "\x04role\x18\x01 \x01(\v2\f.iam_pb.RoleR\x04role\",\n" + + "\x0fPutRoleResponse\x12\x19\n" + + "\brole_arn\x18\x01 \x01(\tR\aroleArn\"-\n" + + "\x0eGetRoleRequest\x12\x1b\n" + + "\trole_name\x18\x01 \x01(\tR\broleName\"3\n" + + "\x0fGetRoleResponse\x12 \n" + + "\x04role\x18\x01 \x01(\v2\f.iam_pb.RoleR\x04role\"0\n" + + "\x11DeleteRoleRequest\x12\x1b\n" + + "\trole_name\x18\x01 \x01(\tR\broleName\"\x14\n" + + "\x12DeleteRoleResponse\"\x12\n" + + "\x10ListRolesRequest\"7\n" + + "\x11ListRolesResponse\x12\"\n" + + "\x05roles\x18\x01 \x03(\v2\f.iam_pb.RoleR\x05roles\"B\n" + "\x12PutIdentityRequest\x12,\n" + "\bidentity\x18\x01 \x01(\v2\x10.iam_pb.IdentityR\bidentity\"\x15\n" + "\x13PutIdentityResponse\"3\n" + @@ -3175,7 +4108,7 @@ const file_iam_proto_rawDesc = "" + "\x12RemoveGroupRequest\x12\x1d\n" + "\n" + "group_name\x18\x01 \x01(\tR\tgroupName\"\x15\n" + - "\x13RemoveGroupResponse2\x99\r\n" + + "\x13RemoveGroupResponse2\xf7\x11\n" + "\x1fSeaweedIdentityAccessManagement\x12U\n" + "\x10GetConfiguration\x12\x1f.iam_pb.GetConfigurationRequest\x1a .iam_pb.GetConfigurationResponse\x12U\n" + "\x10PutConfiguration\x12\x1f.iam_pb.PutConfigurationRequest\x1a .iam_pb.PutConfigurationResponse\x12C\n" + @@ -3199,7 +4132,16 @@ const file_iam_proto_rawDesc = "" + "\x14DeleteServiceAccount\x12#.iam_pb.DeleteServiceAccountRequest\x1a$.iam_pb.DeleteServiceAccountResponse\x12X\n" + "\x11GetServiceAccount\x12 .iam_pb.GetServiceAccountRequest\x1a!.iam_pb.GetServiceAccountResponse\x12^\n" + "\x13ListServiceAccounts\x12\".iam_pb.ListServiceAccountsRequest\x1a#.iam_pb.ListServiceAccountsResponse\x12y\n" + - "\x1cGetServiceAccountByAccessKey\x12+.iam_pb.GetServiceAccountByAccessKeyRequest\x1a,.iam_pb.GetServiceAccountByAccessKeyResponseBK\n" + + "\x1cGetServiceAccountByAccessKey\x12+.iam_pb.GetServiceAccountByAccessKeyRequest\x1a,.iam_pb.GetServiceAccountByAccessKeyResponse\x12R\n" + + "\x0fPutOIDCProvider\x12\x1e.iam_pb.PutOIDCProviderRequest\x1a\x1f.iam_pb.PutOIDCProviderResponse\x12R\n" + + "\x0fGetOIDCProvider\x12\x1e.iam_pb.GetOIDCProviderRequest\x1a\x1f.iam_pb.GetOIDCProviderResponse\x12[\n" + + "\x12DeleteOIDCProvider\x12!.iam_pb.DeleteOIDCProviderRequest\x1a\".iam_pb.DeleteOIDCProviderResponse\x12X\n" + + "\x11ListOIDCProviders\x12 .iam_pb.ListOIDCProvidersRequest\x1a!.iam_pb.ListOIDCProvidersResponse\x12:\n" + + "\aPutRole\x12\x16.iam_pb.PutRoleRequest\x1a\x17.iam_pb.PutRoleResponse\x12:\n" + + "\aGetRole\x12\x16.iam_pb.GetRoleRequest\x1a\x17.iam_pb.GetRoleResponse\x12C\n" + + "\n" + + "DeleteRole\x12\x19.iam_pb.DeleteRoleRequest\x1a\x1a.iam_pb.DeleteRoleResponse\x12@\n" + + "\tListRoles\x12\x18.iam_pb.ListRolesRequest\x1a\x19.iam_pb.ListRolesResponseBK\n" + "\x10seaweedfs.clientB\bIamProtoZ-github.com/seaweedfs/seaweedfs/weed/pb/iam_pbb\x06proto3" var ( @@ -3214,7 +4156,7 @@ func file_iam_proto_rawDescGZIP() []byte { return file_iam_proto_rawDescData } -var file_iam_proto_msgTypes = make([]protoimpl.MessageInfo, 64) +var file_iam_proto_msgTypes = make([]protoimpl.MessageInfo, 82) var file_iam_proto_goTypes = []any{ (*GetConfigurationRequest)(nil), // 0: iam_pb.GetConfigurationRequest (*GetConfigurationResponse)(nil), // 1: iam_pb.GetConfigurationResponse @@ -3272,14 +4214,32 @@ var file_iam_proto_goTypes = []any{ (*ListServiceAccountsResponse)(nil), // 53: iam_pb.ListServiceAccountsResponse (*GetServiceAccountByAccessKeyRequest)(nil), // 54: iam_pb.GetServiceAccountByAccessKeyRequest (*GetServiceAccountByAccessKeyResponse)(nil), // 55: iam_pb.GetServiceAccountByAccessKeyResponse - (*PutIdentityRequest)(nil), // 56: iam_pb.PutIdentityRequest - (*PutIdentityResponse)(nil), // 57: iam_pb.PutIdentityResponse - (*RemoveIdentityRequest)(nil), // 58: iam_pb.RemoveIdentityRequest - (*RemoveIdentityResponse)(nil), // 59: iam_pb.RemoveIdentityResponse - (*PutGroupRequest)(nil), // 60: iam_pb.PutGroupRequest - (*PutGroupResponse)(nil), // 61: iam_pb.PutGroupResponse - (*RemoveGroupRequest)(nil), // 62: iam_pb.RemoveGroupRequest - (*RemoveGroupResponse)(nil), // 63: iam_pb.RemoveGroupResponse + (*OIDCProvider)(nil), // 56: iam_pb.OIDCProvider + (*PutOIDCProviderRequest)(nil), // 57: iam_pb.PutOIDCProviderRequest + (*PutOIDCProviderResponse)(nil), // 58: iam_pb.PutOIDCProviderResponse + (*GetOIDCProviderRequest)(nil), // 59: iam_pb.GetOIDCProviderRequest + (*GetOIDCProviderResponse)(nil), // 60: iam_pb.GetOIDCProviderResponse + (*DeleteOIDCProviderRequest)(nil), // 61: iam_pb.DeleteOIDCProviderRequest + (*DeleteOIDCProviderResponse)(nil), // 62: iam_pb.DeleteOIDCProviderResponse + (*ListOIDCProvidersRequest)(nil), // 63: iam_pb.ListOIDCProvidersRequest + (*ListOIDCProvidersResponse)(nil), // 64: iam_pb.ListOIDCProvidersResponse + (*Role)(nil), // 65: iam_pb.Role + (*PutRoleRequest)(nil), // 66: iam_pb.PutRoleRequest + (*PutRoleResponse)(nil), // 67: iam_pb.PutRoleResponse + (*GetRoleRequest)(nil), // 68: iam_pb.GetRoleRequest + (*GetRoleResponse)(nil), // 69: iam_pb.GetRoleResponse + (*DeleteRoleRequest)(nil), // 70: iam_pb.DeleteRoleRequest + (*DeleteRoleResponse)(nil), // 71: iam_pb.DeleteRoleResponse + (*ListRolesRequest)(nil), // 72: iam_pb.ListRolesRequest + (*ListRolesResponse)(nil), // 73: iam_pb.ListRolesResponse + (*PutIdentityRequest)(nil), // 74: iam_pb.PutIdentityRequest + (*PutIdentityResponse)(nil), // 75: iam_pb.PutIdentityResponse + (*RemoveIdentityRequest)(nil), // 76: iam_pb.RemoveIdentityRequest + (*RemoveIdentityResponse)(nil), // 77: iam_pb.RemoveIdentityResponse + (*PutGroupRequest)(nil), // 78: iam_pb.PutGroupRequest + (*PutGroupResponse)(nil), // 79: iam_pb.PutGroupResponse + (*RemoveGroupRequest)(nil), // 80: iam_pb.RemoveGroupRequest + (*RemoveGroupResponse)(nil), // 81: iam_pb.RemoveGroupResponse } var file_iam_proto_depIdxs = []int32{ 28, // 0: iam_pb.GetConfigurationResponse.configuration:type_name -> iam_pb.S3ApiConfiguration @@ -3305,53 +4265,74 @@ var file_iam_proto_depIdxs = []int32{ 34, // 20: iam_pb.GetServiceAccountResponse.service_account:type_name -> iam_pb.ServiceAccount 34, // 21: iam_pb.ListServiceAccountsResponse.service_accounts:type_name -> iam_pb.ServiceAccount 34, // 22: iam_pb.GetServiceAccountByAccessKeyResponse.service_account:type_name -> iam_pb.ServiceAccount - 30, // 23: iam_pb.PutIdentityRequest.identity:type_name -> iam_pb.Identity - 29, // 24: iam_pb.PutGroupRequest.group:type_name -> iam_pb.Group - 0, // 25: iam_pb.SeaweedIdentityAccessManagement.GetConfiguration:input_type -> iam_pb.GetConfigurationRequest - 2, // 26: iam_pb.SeaweedIdentityAccessManagement.PutConfiguration:input_type -> iam_pb.PutConfigurationRequest - 4, // 27: iam_pb.SeaweedIdentityAccessManagement.CreateUser:input_type -> iam_pb.CreateUserRequest - 6, // 28: iam_pb.SeaweedIdentityAccessManagement.GetUser:input_type -> iam_pb.GetUserRequest - 8, // 29: iam_pb.SeaweedIdentityAccessManagement.UpdateUser:input_type -> iam_pb.UpdateUserRequest - 10, // 30: iam_pb.SeaweedIdentityAccessManagement.DeleteUser:input_type -> iam_pb.DeleteUserRequest - 12, // 31: iam_pb.SeaweedIdentityAccessManagement.ListUsers:input_type -> iam_pb.ListUsersRequest - 14, // 32: iam_pb.SeaweedIdentityAccessManagement.CreateAccessKey:input_type -> iam_pb.CreateAccessKeyRequest - 16, // 33: iam_pb.SeaweedIdentityAccessManagement.DeleteAccessKey:input_type -> iam_pb.DeleteAccessKeyRequest - 18, // 34: iam_pb.SeaweedIdentityAccessManagement.GetUserByAccessKey:input_type -> iam_pb.GetUserByAccessKeyRequest - 35, // 35: iam_pb.SeaweedIdentityAccessManagement.PutPolicy:input_type -> iam_pb.PutPolicyRequest - 37, // 36: iam_pb.SeaweedIdentityAccessManagement.GetPolicy:input_type -> iam_pb.GetPolicyRequest - 39, // 37: iam_pb.SeaweedIdentityAccessManagement.ListPolicies:input_type -> iam_pb.ListPoliciesRequest - 41, // 38: iam_pb.SeaweedIdentityAccessManagement.DeletePolicy:input_type -> iam_pb.DeletePolicyRequest - 44, // 39: iam_pb.SeaweedIdentityAccessManagement.CreateServiceAccount:input_type -> iam_pb.CreateServiceAccountRequest - 46, // 40: iam_pb.SeaweedIdentityAccessManagement.UpdateServiceAccount:input_type -> iam_pb.UpdateServiceAccountRequest - 48, // 41: iam_pb.SeaweedIdentityAccessManagement.DeleteServiceAccount:input_type -> iam_pb.DeleteServiceAccountRequest - 50, // 42: iam_pb.SeaweedIdentityAccessManagement.GetServiceAccount:input_type -> iam_pb.GetServiceAccountRequest - 52, // 43: iam_pb.SeaweedIdentityAccessManagement.ListServiceAccounts:input_type -> iam_pb.ListServiceAccountsRequest - 54, // 44: iam_pb.SeaweedIdentityAccessManagement.GetServiceAccountByAccessKey:input_type -> iam_pb.GetServiceAccountByAccessKeyRequest - 1, // 45: iam_pb.SeaweedIdentityAccessManagement.GetConfiguration:output_type -> iam_pb.GetConfigurationResponse - 3, // 46: iam_pb.SeaweedIdentityAccessManagement.PutConfiguration:output_type -> iam_pb.PutConfigurationResponse - 5, // 47: iam_pb.SeaweedIdentityAccessManagement.CreateUser:output_type -> iam_pb.CreateUserResponse - 7, // 48: iam_pb.SeaweedIdentityAccessManagement.GetUser:output_type -> iam_pb.GetUserResponse - 9, // 49: iam_pb.SeaweedIdentityAccessManagement.UpdateUser:output_type -> iam_pb.UpdateUserResponse - 11, // 50: iam_pb.SeaweedIdentityAccessManagement.DeleteUser:output_type -> iam_pb.DeleteUserResponse - 13, // 51: iam_pb.SeaweedIdentityAccessManagement.ListUsers:output_type -> iam_pb.ListUsersResponse - 15, // 52: iam_pb.SeaweedIdentityAccessManagement.CreateAccessKey:output_type -> iam_pb.CreateAccessKeyResponse - 17, // 53: iam_pb.SeaweedIdentityAccessManagement.DeleteAccessKey:output_type -> iam_pb.DeleteAccessKeyResponse - 19, // 54: iam_pb.SeaweedIdentityAccessManagement.GetUserByAccessKey:output_type -> iam_pb.GetUserByAccessKeyResponse - 36, // 55: iam_pb.SeaweedIdentityAccessManagement.PutPolicy:output_type -> iam_pb.PutPolicyResponse - 38, // 56: iam_pb.SeaweedIdentityAccessManagement.GetPolicy:output_type -> iam_pb.GetPolicyResponse - 40, // 57: iam_pb.SeaweedIdentityAccessManagement.ListPolicies:output_type -> iam_pb.ListPoliciesResponse - 42, // 58: iam_pb.SeaweedIdentityAccessManagement.DeletePolicy:output_type -> iam_pb.DeletePolicyResponse - 45, // 59: iam_pb.SeaweedIdentityAccessManagement.CreateServiceAccount:output_type -> iam_pb.CreateServiceAccountResponse - 47, // 60: iam_pb.SeaweedIdentityAccessManagement.UpdateServiceAccount:output_type -> iam_pb.UpdateServiceAccountResponse - 49, // 61: iam_pb.SeaweedIdentityAccessManagement.DeleteServiceAccount:output_type -> iam_pb.DeleteServiceAccountResponse - 51, // 62: iam_pb.SeaweedIdentityAccessManagement.GetServiceAccount:output_type -> iam_pb.GetServiceAccountResponse - 53, // 63: iam_pb.SeaweedIdentityAccessManagement.ListServiceAccounts:output_type -> iam_pb.ListServiceAccountsResponse - 55, // 64: iam_pb.SeaweedIdentityAccessManagement.GetServiceAccountByAccessKey:output_type -> iam_pb.GetServiceAccountByAccessKeyResponse - 45, // [45:65] is the sub-list for method output_type - 25, // [25:45] is the sub-list for method input_type - 25, // [25:25] is the sub-list for extension type_name - 25, // [25:25] is the sub-list for extension extendee - 0, // [0:25] is the sub-list for field type_name + 56, // 23: iam_pb.GetOIDCProviderResponse.provider:type_name -> iam_pb.OIDCProvider + 56, // 24: iam_pb.ListOIDCProvidersResponse.providers:type_name -> iam_pb.OIDCProvider + 65, // 25: iam_pb.PutRoleRequest.role:type_name -> iam_pb.Role + 65, // 26: iam_pb.GetRoleResponse.role:type_name -> iam_pb.Role + 65, // 27: iam_pb.ListRolesResponse.roles:type_name -> iam_pb.Role + 30, // 28: iam_pb.PutIdentityRequest.identity:type_name -> iam_pb.Identity + 29, // 29: iam_pb.PutGroupRequest.group:type_name -> iam_pb.Group + 0, // 30: iam_pb.SeaweedIdentityAccessManagement.GetConfiguration:input_type -> iam_pb.GetConfigurationRequest + 2, // 31: iam_pb.SeaweedIdentityAccessManagement.PutConfiguration:input_type -> iam_pb.PutConfigurationRequest + 4, // 32: iam_pb.SeaweedIdentityAccessManagement.CreateUser:input_type -> iam_pb.CreateUserRequest + 6, // 33: iam_pb.SeaweedIdentityAccessManagement.GetUser:input_type -> iam_pb.GetUserRequest + 8, // 34: iam_pb.SeaweedIdentityAccessManagement.UpdateUser:input_type -> iam_pb.UpdateUserRequest + 10, // 35: iam_pb.SeaweedIdentityAccessManagement.DeleteUser:input_type -> iam_pb.DeleteUserRequest + 12, // 36: iam_pb.SeaweedIdentityAccessManagement.ListUsers:input_type -> iam_pb.ListUsersRequest + 14, // 37: iam_pb.SeaweedIdentityAccessManagement.CreateAccessKey:input_type -> iam_pb.CreateAccessKeyRequest + 16, // 38: iam_pb.SeaweedIdentityAccessManagement.DeleteAccessKey:input_type -> iam_pb.DeleteAccessKeyRequest + 18, // 39: iam_pb.SeaweedIdentityAccessManagement.GetUserByAccessKey:input_type -> iam_pb.GetUserByAccessKeyRequest + 35, // 40: iam_pb.SeaweedIdentityAccessManagement.PutPolicy:input_type -> iam_pb.PutPolicyRequest + 37, // 41: iam_pb.SeaweedIdentityAccessManagement.GetPolicy:input_type -> iam_pb.GetPolicyRequest + 39, // 42: iam_pb.SeaweedIdentityAccessManagement.ListPolicies:input_type -> iam_pb.ListPoliciesRequest + 41, // 43: iam_pb.SeaweedIdentityAccessManagement.DeletePolicy:input_type -> iam_pb.DeletePolicyRequest + 44, // 44: iam_pb.SeaweedIdentityAccessManagement.CreateServiceAccount:input_type -> iam_pb.CreateServiceAccountRequest + 46, // 45: iam_pb.SeaweedIdentityAccessManagement.UpdateServiceAccount:input_type -> iam_pb.UpdateServiceAccountRequest + 48, // 46: iam_pb.SeaweedIdentityAccessManagement.DeleteServiceAccount:input_type -> iam_pb.DeleteServiceAccountRequest + 50, // 47: iam_pb.SeaweedIdentityAccessManagement.GetServiceAccount:input_type -> iam_pb.GetServiceAccountRequest + 52, // 48: iam_pb.SeaweedIdentityAccessManagement.ListServiceAccounts:input_type -> iam_pb.ListServiceAccountsRequest + 54, // 49: iam_pb.SeaweedIdentityAccessManagement.GetServiceAccountByAccessKey:input_type -> iam_pb.GetServiceAccountByAccessKeyRequest + 57, // 50: iam_pb.SeaweedIdentityAccessManagement.PutOIDCProvider:input_type -> iam_pb.PutOIDCProviderRequest + 59, // 51: iam_pb.SeaweedIdentityAccessManagement.GetOIDCProvider:input_type -> iam_pb.GetOIDCProviderRequest + 61, // 52: iam_pb.SeaweedIdentityAccessManagement.DeleteOIDCProvider:input_type -> iam_pb.DeleteOIDCProviderRequest + 63, // 53: iam_pb.SeaweedIdentityAccessManagement.ListOIDCProviders:input_type -> iam_pb.ListOIDCProvidersRequest + 66, // 54: iam_pb.SeaweedIdentityAccessManagement.PutRole:input_type -> iam_pb.PutRoleRequest + 68, // 55: iam_pb.SeaweedIdentityAccessManagement.GetRole:input_type -> iam_pb.GetRoleRequest + 70, // 56: iam_pb.SeaweedIdentityAccessManagement.DeleteRole:input_type -> iam_pb.DeleteRoleRequest + 72, // 57: iam_pb.SeaweedIdentityAccessManagement.ListRoles:input_type -> iam_pb.ListRolesRequest + 1, // 58: iam_pb.SeaweedIdentityAccessManagement.GetConfiguration:output_type -> iam_pb.GetConfigurationResponse + 3, // 59: iam_pb.SeaweedIdentityAccessManagement.PutConfiguration:output_type -> iam_pb.PutConfigurationResponse + 5, // 60: iam_pb.SeaweedIdentityAccessManagement.CreateUser:output_type -> iam_pb.CreateUserResponse + 7, // 61: iam_pb.SeaweedIdentityAccessManagement.GetUser:output_type -> iam_pb.GetUserResponse + 9, // 62: iam_pb.SeaweedIdentityAccessManagement.UpdateUser:output_type -> iam_pb.UpdateUserResponse + 11, // 63: iam_pb.SeaweedIdentityAccessManagement.DeleteUser:output_type -> iam_pb.DeleteUserResponse + 13, // 64: iam_pb.SeaweedIdentityAccessManagement.ListUsers:output_type -> iam_pb.ListUsersResponse + 15, // 65: iam_pb.SeaweedIdentityAccessManagement.CreateAccessKey:output_type -> iam_pb.CreateAccessKeyResponse + 17, // 66: iam_pb.SeaweedIdentityAccessManagement.DeleteAccessKey:output_type -> iam_pb.DeleteAccessKeyResponse + 19, // 67: iam_pb.SeaweedIdentityAccessManagement.GetUserByAccessKey:output_type -> iam_pb.GetUserByAccessKeyResponse + 36, // 68: iam_pb.SeaweedIdentityAccessManagement.PutPolicy:output_type -> iam_pb.PutPolicyResponse + 38, // 69: iam_pb.SeaweedIdentityAccessManagement.GetPolicy:output_type -> iam_pb.GetPolicyResponse + 40, // 70: iam_pb.SeaweedIdentityAccessManagement.ListPolicies:output_type -> iam_pb.ListPoliciesResponse + 42, // 71: iam_pb.SeaweedIdentityAccessManagement.DeletePolicy:output_type -> iam_pb.DeletePolicyResponse + 45, // 72: iam_pb.SeaweedIdentityAccessManagement.CreateServiceAccount:output_type -> iam_pb.CreateServiceAccountResponse + 47, // 73: iam_pb.SeaweedIdentityAccessManagement.UpdateServiceAccount:output_type -> iam_pb.UpdateServiceAccountResponse + 49, // 74: iam_pb.SeaweedIdentityAccessManagement.DeleteServiceAccount:output_type -> iam_pb.DeleteServiceAccountResponse + 51, // 75: iam_pb.SeaweedIdentityAccessManagement.GetServiceAccount:output_type -> iam_pb.GetServiceAccountResponse + 53, // 76: iam_pb.SeaweedIdentityAccessManagement.ListServiceAccounts:output_type -> iam_pb.ListServiceAccountsResponse + 55, // 77: iam_pb.SeaweedIdentityAccessManagement.GetServiceAccountByAccessKey:output_type -> iam_pb.GetServiceAccountByAccessKeyResponse + 58, // 78: iam_pb.SeaweedIdentityAccessManagement.PutOIDCProvider:output_type -> iam_pb.PutOIDCProviderResponse + 60, // 79: iam_pb.SeaweedIdentityAccessManagement.GetOIDCProvider:output_type -> iam_pb.GetOIDCProviderResponse + 62, // 80: iam_pb.SeaweedIdentityAccessManagement.DeleteOIDCProvider:output_type -> iam_pb.DeleteOIDCProviderResponse + 64, // 81: iam_pb.SeaweedIdentityAccessManagement.ListOIDCProviders:output_type -> iam_pb.ListOIDCProvidersResponse + 67, // 82: iam_pb.SeaweedIdentityAccessManagement.PutRole:output_type -> iam_pb.PutRoleResponse + 69, // 83: iam_pb.SeaweedIdentityAccessManagement.GetRole:output_type -> iam_pb.GetRoleResponse + 71, // 84: iam_pb.SeaweedIdentityAccessManagement.DeleteRole:output_type -> iam_pb.DeleteRoleResponse + 73, // 85: iam_pb.SeaweedIdentityAccessManagement.ListRoles:output_type -> iam_pb.ListRolesResponse + 58, // [58:86] is the sub-list for method output_type + 30, // [30:58] is the sub-list for method input_type + 30, // [30:30] is the sub-list for extension type_name + 30, // [30:30] is the sub-list for extension extendee + 0, // [0:30] is the sub-list for field type_name } func init() { file_iam_proto_init() } @@ -3365,7 +4346,7 @@ func file_iam_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_iam_proto_rawDesc), len(file_iam_proto_rawDesc)), NumEnums: 0, - NumMessages: 64, + NumMessages: 82, NumExtensions: 0, NumServices: 1, }, diff --git a/weed/pb/iam_pb/iam_grpc.pb.go b/weed/pb/iam_pb/iam_grpc.pb.go index 9fb76c456..17df34b2a 100644 --- a/weed/pb/iam_pb/iam_grpc.pb.go +++ b/weed/pb/iam_pb/iam_grpc.pb.go @@ -39,6 +39,14 @@ const ( SeaweedIdentityAccessManagement_GetServiceAccount_FullMethodName = "/iam_pb.SeaweedIdentityAccessManagement/GetServiceAccount" SeaweedIdentityAccessManagement_ListServiceAccounts_FullMethodName = "/iam_pb.SeaweedIdentityAccessManagement/ListServiceAccounts" SeaweedIdentityAccessManagement_GetServiceAccountByAccessKey_FullMethodName = "/iam_pb.SeaweedIdentityAccessManagement/GetServiceAccountByAccessKey" + SeaweedIdentityAccessManagement_PutOIDCProvider_FullMethodName = "/iam_pb.SeaweedIdentityAccessManagement/PutOIDCProvider" + SeaweedIdentityAccessManagement_GetOIDCProvider_FullMethodName = "/iam_pb.SeaweedIdentityAccessManagement/GetOIDCProvider" + SeaweedIdentityAccessManagement_DeleteOIDCProvider_FullMethodName = "/iam_pb.SeaweedIdentityAccessManagement/DeleteOIDCProvider" + SeaweedIdentityAccessManagement_ListOIDCProviders_FullMethodName = "/iam_pb.SeaweedIdentityAccessManagement/ListOIDCProviders" + SeaweedIdentityAccessManagement_PutRole_FullMethodName = "/iam_pb.SeaweedIdentityAccessManagement/PutRole" + SeaweedIdentityAccessManagement_GetRole_FullMethodName = "/iam_pb.SeaweedIdentityAccessManagement/GetRole" + SeaweedIdentityAccessManagement_DeleteRole_FullMethodName = "/iam_pb.SeaweedIdentityAccessManagement/DeleteRole" + SeaweedIdentityAccessManagement_ListRoles_FullMethodName = "/iam_pb.SeaweedIdentityAccessManagement/ListRoles" ) // SeaweedIdentityAccessManagementClient is the client API for SeaweedIdentityAccessManagement service. @@ -70,6 +78,18 @@ type SeaweedIdentityAccessManagementClient interface { GetServiceAccount(ctx context.Context, in *GetServiceAccountRequest, opts ...grpc.CallOption) (*GetServiceAccountResponse, error) ListServiceAccounts(ctx context.Context, in *ListServiceAccountsRequest, opts ...grpc.CallOption) (*ListServiceAccountsResponse, error) GetServiceAccountByAccessKey(ctx context.Context, in *GetServiceAccountByAccessKeyRequest, opts ...grpc.CallOption) (*GetServiceAccountByAccessKeyResponse, error) + // OIDC Provider Management. Writes the records the S3 servers' STS trusts + // when they run with "oidcProviderStore": {"storeType": "filer"}. + PutOIDCProvider(ctx context.Context, in *PutOIDCProviderRequest, opts ...grpc.CallOption) (*PutOIDCProviderResponse, error) + GetOIDCProvider(ctx context.Context, in *GetOIDCProviderRequest, opts ...grpc.CallOption) (*GetOIDCProviderResponse, error) + DeleteOIDCProvider(ctx context.Context, in *DeleteOIDCProviderRequest, opts ...grpc.CallOption) (*DeleteOIDCProviderResponse, error) + ListOIDCProviders(ctx context.Context, in *ListOIDCProvidersRequest, opts ...grpc.CallOption) (*ListOIDCProvidersResponse, error) + // Role Management. Writes the roles the S3 servers' STS assumes when they + // run with "roleStore": {"storeType": "filer"}. + PutRole(ctx context.Context, in *PutRoleRequest, opts ...grpc.CallOption) (*PutRoleResponse, error) + GetRole(ctx context.Context, in *GetRoleRequest, opts ...grpc.CallOption) (*GetRoleResponse, error) + DeleteRole(ctx context.Context, in *DeleteRoleRequest, opts ...grpc.CallOption) (*DeleteRoleResponse, error) + ListRoles(ctx context.Context, in *ListRolesRequest, opts ...grpc.CallOption) (*ListRolesResponse, error) } type seaweedIdentityAccessManagementClient struct { @@ -280,6 +300,86 @@ func (c *seaweedIdentityAccessManagementClient) GetServiceAccountByAccessKey(ctx return out, nil } +func (c *seaweedIdentityAccessManagementClient) PutOIDCProvider(ctx context.Context, in *PutOIDCProviderRequest, opts ...grpc.CallOption) (*PutOIDCProviderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PutOIDCProviderResponse) + err := c.cc.Invoke(ctx, SeaweedIdentityAccessManagement_PutOIDCProvider_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *seaweedIdentityAccessManagementClient) GetOIDCProvider(ctx context.Context, in *GetOIDCProviderRequest, opts ...grpc.CallOption) (*GetOIDCProviderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetOIDCProviderResponse) + err := c.cc.Invoke(ctx, SeaweedIdentityAccessManagement_GetOIDCProvider_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *seaweedIdentityAccessManagementClient) DeleteOIDCProvider(ctx context.Context, in *DeleteOIDCProviderRequest, opts ...grpc.CallOption) (*DeleteOIDCProviderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteOIDCProviderResponse) + err := c.cc.Invoke(ctx, SeaweedIdentityAccessManagement_DeleteOIDCProvider_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *seaweedIdentityAccessManagementClient) ListOIDCProviders(ctx context.Context, in *ListOIDCProvidersRequest, opts ...grpc.CallOption) (*ListOIDCProvidersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListOIDCProvidersResponse) + err := c.cc.Invoke(ctx, SeaweedIdentityAccessManagement_ListOIDCProviders_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *seaweedIdentityAccessManagementClient) PutRole(ctx context.Context, in *PutRoleRequest, opts ...grpc.CallOption) (*PutRoleResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PutRoleResponse) + err := c.cc.Invoke(ctx, SeaweedIdentityAccessManagement_PutRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *seaweedIdentityAccessManagementClient) GetRole(ctx context.Context, in *GetRoleRequest, opts ...grpc.CallOption) (*GetRoleResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetRoleResponse) + err := c.cc.Invoke(ctx, SeaweedIdentityAccessManagement_GetRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *seaweedIdentityAccessManagementClient) DeleteRole(ctx context.Context, in *DeleteRoleRequest, opts ...grpc.CallOption) (*DeleteRoleResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteRoleResponse) + err := c.cc.Invoke(ctx, SeaweedIdentityAccessManagement_DeleteRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *seaweedIdentityAccessManagementClient) ListRoles(ctx context.Context, in *ListRolesRequest, opts ...grpc.CallOption) (*ListRolesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListRolesResponse) + err := c.cc.Invoke(ctx, SeaweedIdentityAccessManagement_ListRoles_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // SeaweedIdentityAccessManagementServer is the server API for SeaweedIdentityAccessManagement service. // All implementations must embed UnimplementedSeaweedIdentityAccessManagementServer // for forward compatibility. @@ -309,6 +409,18 @@ type SeaweedIdentityAccessManagementServer interface { GetServiceAccount(context.Context, *GetServiceAccountRequest) (*GetServiceAccountResponse, error) ListServiceAccounts(context.Context, *ListServiceAccountsRequest) (*ListServiceAccountsResponse, error) GetServiceAccountByAccessKey(context.Context, *GetServiceAccountByAccessKeyRequest) (*GetServiceAccountByAccessKeyResponse, error) + // OIDC Provider Management. Writes the records the S3 servers' STS trusts + // when they run with "oidcProviderStore": {"storeType": "filer"}. + PutOIDCProvider(context.Context, *PutOIDCProviderRequest) (*PutOIDCProviderResponse, error) + GetOIDCProvider(context.Context, *GetOIDCProviderRequest) (*GetOIDCProviderResponse, error) + DeleteOIDCProvider(context.Context, *DeleteOIDCProviderRequest) (*DeleteOIDCProviderResponse, error) + ListOIDCProviders(context.Context, *ListOIDCProvidersRequest) (*ListOIDCProvidersResponse, error) + // Role Management. Writes the roles the S3 servers' STS assumes when they + // run with "roleStore": {"storeType": "filer"}. + PutRole(context.Context, *PutRoleRequest) (*PutRoleResponse, error) + GetRole(context.Context, *GetRoleRequest) (*GetRoleResponse, error) + DeleteRole(context.Context, *DeleteRoleRequest) (*DeleteRoleResponse, error) + ListRoles(context.Context, *ListRolesRequest) (*ListRolesResponse, error) mustEmbedUnimplementedSeaweedIdentityAccessManagementServer() } @@ -379,6 +491,30 @@ func (UnimplementedSeaweedIdentityAccessManagementServer) ListServiceAccounts(co func (UnimplementedSeaweedIdentityAccessManagementServer) GetServiceAccountByAccessKey(context.Context, *GetServiceAccountByAccessKeyRequest) (*GetServiceAccountByAccessKeyResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetServiceAccountByAccessKey not implemented") } +func (UnimplementedSeaweedIdentityAccessManagementServer) PutOIDCProvider(context.Context, *PutOIDCProviderRequest) (*PutOIDCProviderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PutOIDCProvider not implemented") +} +func (UnimplementedSeaweedIdentityAccessManagementServer) GetOIDCProvider(context.Context, *GetOIDCProviderRequest) (*GetOIDCProviderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOIDCProvider not implemented") +} +func (UnimplementedSeaweedIdentityAccessManagementServer) DeleteOIDCProvider(context.Context, *DeleteOIDCProviderRequest) (*DeleteOIDCProviderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteOIDCProvider not implemented") +} +func (UnimplementedSeaweedIdentityAccessManagementServer) ListOIDCProviders(context.Context, *ListOIDCProvidersRequest) (*ListOIDCProvidersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListOIDCProviders not implemented") +} +func (UnimplementedSeaweedIdentityAccessManagementServer) PutRole(context.Context, *PutRoleRequest) (*PutRoleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PutRole not implemented") +} +func (UnimplementedSeaweedIdentityAccessManagementServer) GetRole(context.Context, *GetRoleRequest) (*GetRoleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRole not implemented") +} +func (UnimplementedSeaweedIdentityAccessManagementServer) DeleteRole(context.Context, *DeleteRoleRequest) (*DeleteRoleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteRole not implemented") +} +func (UnimplementedSeaweedIdentityAccessManagementServer) ListRoles(context.Context, *ListRolesRequest) (*ListRolesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListRoles not implemented") +} func (UnimplementedSeaweedIdentityAccessManagementServer) mustEmbedUnimplementedSeaweedIdentityAccessManagementServer() { } func (UnimplementedSeaweedIdentityAccessManagementServer) testEmbeddedByValue() {} @@ -761,6 +897,150 @@ func _SeaweedIdentityAccessManagement_GetServiceAccountByAccessKey_Handler(srv i return interceptor(ctx, in, info, handler) } +func _SeaweedIdentityAccessManagement_PutOIDCProvider_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PutOIDCProviderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SeaweedIdentityAccessManagementServer).PutOIDCProvider(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SeaweedIdentityAccessManagement_PutOIDCProvider_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SeaweedIdentityAccessManagementServer).PutOIDCProvider(ctx, req.(*PutOIDCProviderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SeaweedIdentityAccessManagement_GetOIDCProvider_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOIDCProviderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SeaweedIdentityAccessManagementServer).GetOIDCProvider(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SeaweedIdentityAccessManagement_GetOIDCProvider_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SeaweedIdentityAccessManagementServer).GetOIDCProvider(ctx, req.(*GetOIDCProviderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SeaweedIdentityAccessManagement_DeleteOIDCProvider_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteOIDCProviderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SeaweedIdentityAccessManagementServer).DeleteOIDCProvider(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SeaweedIdentityAccessManagement_DeleteOIDCProvider_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SeaweedIdentityAccessManagementServer).DeleteOIDCProvider(ctx, req.(*DeleteOIDCProviderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SeaweedIdentityAccessManagement_ListOIDCProviders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListOIDCProvidersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SeaweedIdentityAccessManagementServer).ListOIDCProviders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SeaweedIdentityAccessManagement_ListOIDCProviders_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SeaweedIdentityAccessManagementServer).ListOIDCProviders(ctx, req.(*ListOIDCProvidersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SeaweedIdentityAccessManagement_PutRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PutRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SeaweedIdentityAccessManagementServer).PutRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SeaweedIdentityAccessManagement_PutRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SeaweedIdentityAccessManagementServer).PutRole(ctx, req.(*PutRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SeaweedIdentityAccessManagement_GetRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SeaweedIdentityAccessManagementServer).GetRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SeaweedIdentityAccessManagement_GetRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SeaweedIdentityAccessManagementServer).GetRole(ctx, req.(*GetRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SeaweedIdentityAccessManagement_DeleteRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SeaweedIdentityAccessManagementServer).DeleteRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SeaweedIdentityAccessManagement_DeleteRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SeaweedIdentityAccessManagementServer).DeleteRole(ctx, req.(*DeleteRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SeaweedIdentityAccessManagement_ListRoles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRolesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SeaweedIdentityAccessManagementServer).ListRoles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SeaweedIdentityAccessManagement_ListRoles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SeaweedIdentityAccessManagementServer).ListRoles(ctx, req.(*ListRolesRequest)) + } + return interceptor(ctx, in, info, handler) +} + // SeaweedIdentityAccessManagement_ServiceDesc is the grpc.ServiceDesc for SeaweedIdentityAccessManagement service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -848,6 +1128,38 @@ var SeaweedIdentityAccessManagement_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetServiceAccountByAccessKey", Handler: _SeaweedIdentityAccessManagement_GetServiceAccountByAccessKey_Handler, }, + { + MethodName: "PutOIDCProvider", + Handler: _SeaweedIdentityAccessManagement_PutOIDCProvider_Handler, + }, + { + MethodName: "GetOIDCProvider", + Handler: _SeaweedIdentityAccessManagement_GetOIDCProvider_Handler, + }, + { + MethodName: "DeleteOIDCProvider", + Handler: _SeaweedIdentityAccessManagement_DeleteOIDCProvider_Handler, + }, + { + MethodName: "ListOIDCProviders", + Handler: _SeaweedIdentityAccessManagement_ListOIDCProviders_Handler, + }, + { + MethodName: "PutRole", + Handler: _SeaweedIdentityAccessManagement_PutRole_Handler, + }, + { + MethodName: "GetRole", + Handler: _SeaweedIdentityAccessManagement_GetRole_Handler, + }, + { + MethodName: "DeleteRole", + Handler: _SeaweedIdentityAccessManagement_DeleteRole_Handler, + }, + { + MethodName: "ListRoles", + Handler: _SeaweedIdentityAccessManagement_ListRoles_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "iam.proto", diff --git a/weed/server/filer_server_handlers_iam_grpc.go b/weed/server/filer_server_handlers_iam_grpc.go index 7142d53d9..be9835661 100644 --- a/weed/server/filer_server_handlers_iam_grpc.go +++ b/weed/server/filer_server_handlers_iam_grpc.go @@ -7,6 +7,7 @@ import ( "github.com/seaweedfs/seaweedfs/weed/credential" "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/iam/integration" "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" "github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine" "github.com/seaweedfs/seaweedfs/weed/security" @@ -25,6 +26,8 @@ type IamGrpcServer struct { iam_pb.UnimplementedSeaweedIdentityAccessManagementServer credentialManager *credential.CredentialManager adminSigningKey security.SigningKey + oidcProviderStore integration.OIDCProviderStore + roleStore integration.RoleStore } // NewIamGrpcServer creates a new IAM gRPC server. If adminSigningKey is empty @@ -479,6 +482,18 @@ func (s *IamGrpcServer) DeletePolicy(ctx context.Context, req *iam_pb.DeletePoli return nil, status.Errorf(codes.FailedPrecondition, "credential manager is not configured") } + // Roles attach policies by name; deleting one still attached would let a + // policy created later under that name take effect on the role. + if s.roleStore != nil { + roles, err := integration.RolesAttachingPolicy(ctx, s.roleStore, req.Name) + if err != nil { + return nil, status.Errorf(codes.Unavailable, "check role attachments: %v", err) + } + if len(roles) > 0 { + return nil, status.Errorf(codes.FailedPrecondition, "policy %s is attached to role %s", req.Name, roles[0]) + } + } + err := s.credentialManager.DeletePolicy(ctx, req.Name) if err != nil { glog.Errorf("Failed to delete policy %s: %v", req.Name, err) diff --git a/weed/server/filer_server_handlers_iam_grpc_sts.go b/weed/server/filer_server_handlers_iam_grpc_sts.go new file mode 100644 index 000000000..fff0a0874 --- /dev/null +++ b/weed/server/filer_server_handlers_iam_grpc_sts.go @@ -0,0 +1,457 @@ +package weed_server + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net" + "net/url" + "strings" + "time" + + "github.com/seaweedfs/seaweedfs/weed/iam/integration" + "github.com/seaweedfs/seaweedfs/weed/iam/policy" + "github.com/seaweedfs/seaweedfs/weed/iam/utils" + "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// SetSTSStores gives the IAM service the stores that S3 servers read when they +// run with a filer-typed "oidcProviderStore" and "roleStore". Records written +// here are the ones their STS trusts; the S3 servers pick up changes through +// their /etc/iam metadata subscription. Without the stores, the OIDC provider +// and role RPCs return FailedPrecondition. +func (s *IamGrpcServer) SetSTSStores(oidcProviders integration.OIDCProviderStore, roles integration.RoleStore) { + s.oidcProviderStore = oidcProviders + s.roleStore = roles +} + +// errSTSRequiresAuth refuses the OIDC provider and role RPCs on a filer whose +// IAM service runs unauthenticated. Unlike users and policies, which keep the +// service's opt-in auth, these grant STS access outright: with them, anyone +// who can reach the port could register an issuer they control, create a role +// trusting it, and exchange a token for S3 credentials. +var errSTSRequiresAuth = status.Error(codes.FailedPrecondition, + "OIDC provider and role management requires admin authentication: set jwt.filer_signing.key in security.toml") + +func (s *IamGrpcServer) requireOIDCProviderStore() (integration.OIDCProviderStore, error) { + if len(s.adminSigningKey) == 0 { + return nil, errSTSRequiresAuth + } + if s.oidcProviderStore == nil { + return nil, status.Error(codes.FailedPrecondition, "OIDC provider store not configured on this filer") + } + return s.oidcProviderStore, nil +} + +func (s *IamGrpcServer) requireRoleStore() (integration.RoleStore, error) { + if len(s.adminSigningKey) == 0 { + return nil, errSTSRequiresAuth + } + if s.roleStore == nil { + return nil, status.Error(codes.FailedPrecondition, "role store not configured on this filer") + } + return s.roleStore, nil +} + +func toPbOIDCProvider(rec *integration.OIDCProviderRecord) *iam_pb.OIDCProvider { + return &iam_pb.OIDCProvider{ + IssuerUrl: rec.URL, + ClientIds: rec.ClientIDs, + Thumbprints: rec.Thumbprints, + AccountId: rec.AccountID, + Arn: rec.ARN, + } +} + +// lookupOIDCProvider returns the stored record, nil when there is none, or an +// error when the store could not be read. +func lookupOIDCProvider(ctx context.Context, store integration.OIDCProviderStore, arn string) (*integration.OIDCProviderRecord, error) { + rec, err := store.GetProviderByARN(ctx, "", arn) + if errors.Is(err, integration.ErrOIDCProviderNotFound) { + return nil, nil + } + if err != nil { + return nil, status.Errorf(codes.Unavailable, "read OIDC provider %s: %v", arn, err) + } + return rec, nil +} + +func (s *IamGrpcServer) PutOIDCProvider(ctx context.Context, req *iam_pb.PutOIDCProviderRequest) (*iam_pb.PutOIDCProviderResponse, error) { + if err := s.checkAdminAuth(ctx); err != nil { + return nil, err + } + store, err := s.requireOIDCProviderStore() + if err != nil { + return nil, err + } + if err := requireSecureIssuer(req.IssuerUrl); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + rec, err := integration.PrepareOIDCProviderRecord(req.AccountId, req.IssuerUrl, req.ClientIds, req.Thumbprints) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + // Put replaces what the request carries and keeps what it cannot. The + // store's atomic update decides which against the record as it is when + // written, so a delete racing the put is not merged back from a stale read. + err = store.UpdateProvider(ctx, "", rec.ARN, func(existing *integration.OIDCProviderRecord) (*integration.OIDCProviderRecord, error) { + next := *rec + next.ClientIDs = append([]string(nil), rec.ClientIDs...) + next.Thumbprints = append([]string(nil), rec.Thumbprints...) + now := time.Now().UTC() + next.CreatedAt, next.UpdatedAt = now, now + if existing != nil { + next.CreatedAt = existing.CreatedAt + next.Tags = existing.Tags + next.AllowedPrincipalTagKeys = existing.AllowedPrincipalTagKeys + next.PolicyClaim = existing.PolicyClaim + } + return &next, nil + }) + if err != nil { + return nil, status.Errorf(codes.Unavailable, "store OIDC provider: %v", err) + } + return &iam_pb.PutOIDCProviderResponse{Arn: rec.ARN}, nil +} + +// requireSecureIssuer refuses an issuer served over plain HTTP, as AWS does: +// STS fetches the issuer's signing keys from it, so over HTTP anyone on the +// network path could substitute their own and mint tokens STS accepts. A +// loopback issuer is allowed for local testing. +func requireSecureIssuer(issuerURL string) error { + u, err := url.Parse(issuerURL) + if err != nil { + return fmt.Errorf("invalid issuer URL: %w", err) + } + // STS matches a token's iss claim against the stored URL exactly, and the + // provider's ARN is derived from host and path alone: an issuer with + // userinfo, a query or a fragment would share its ARN with the bare + // issuer and match no token. + if u.User != nil || u.RawQuery != "" || u.ForceQuery || u.Fragment != "" { + return fmt.Errorf("issuer URL must not contain userinfo, a query or a fragment: %s", issuerURL) + } + switch u.Scheme { + case "https": + return nil + case "http": + host := u.Hostname() + if strings.EqualFold(host, "localhost") { + return nil + } + if ip := net.ParseIP(host); ip != nil && ip.IsLoopback() { + return nil + } + return fmt.Errorf("issuer URL must use https (http is allowed only for a loopback host): %s", issuerURL) + default: + return fmt.Errorf("issuer URL must use https: %s", issuerURL) + } +} + +func (s *IamGrpcServer) GetOIDCProvider(ctx context.Context, req *iam_pb.GetOIDCProviderRequest) (*iam_pb.GetOIDCProviderResponse, error) { + if err := s.checkAdminAuth(ctx); err != nil { + return nil, err + } + store, err := s.requireOIDCProviderStore() + if err != nil { + return nil, err + } + arn, err := integration.DeriveOIDCProviderARN(req.AccountId, req.IssuerUrl) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + rec, err := lookupOIDCProvider(ctx, store, arn) + if err != nil { + return nil, err + } + if rec == nil { + return nil, status.Errorf(codes.NotFound, "OIDC provider %s not found", arn) + } + return &iam_pb.GetOIDCProviderResponse{Provider: toPbOIDCProvider(rec)}, nil +} + +// DeleteOIDCProvider returns NotFound for a provider that does not exist, as +// DeleteUser does for a user; callers treat that as already deleted. +func (s *IamGrpcServer) DeleteOIDCProvider(ctx context.Context, req *iam_pb.DeleteOIDCProviderRequest) (*iam_pb.DeleteOIDCProviderResponse, error) { + if err := s.checkAdminAuth(ctx); err != nil { + return nil, err + } + store, err := s.requireOIDCProviderStore() + if err != nil { + return nil, err + } + arn, err := integration.DeriveOIDCProviderARN(req.AccountId, req.IssuerUrl) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + // Delete the provider as this request first saw it. The store's delete is + // conditional (OIDCProviderStore.UpdateProvider), and a retry that finds + // the record replaced by a PutOIDCProvider in between refuses rather than + // delete the newer record: the caller decides again against it. + var seen []byte + err = store.UpdateProvider(ctx, "", arn, func(existing *integration.OIDCProviderRecord) (*integration.OIDCProviderRecord, error) { + if existing == nil { + return nil, integration.ErrOIDCProviderNotFound + } + current, err := json.Marshal(existing) + if err != nil { + return nil, err + } + if seen == nil { + seen = current + } else if !bytes.Equal(seen, current) { + return nil, errProviderReplacedDuringDelete + } + return nil, nil + }) + if errors.Is(err, integration.ErrOIDCProviderNotFound) { + return nil, status.Errorf(codes.NotFound, "OIDC provider %s not found", arn) + } + if errors.Is(err, errProviderReplacedDuringDelete) { + return nil, status.Errorf(codes.Aborted, "OIDC provider %s changed while it was being deleted; retry", arn) + } + if err != nil { + return nil, status.Errorf(codes.Unavailable, "delete OIDC provider: %v", err) + } + return &iam_pb.DeleteOIDCProviderResponse{}, nil +} + +func (s *IamGrpcServer) ListOIDCProviders(ctx context.Context, req *iam_pb.ListOIDCProvidersRequest) (*iam_pb.ListOIDCProvidersResponse, error) { + if err := s.checkAdminAuth(ctx); err != nil { + return nil, err + } + store, err := s.requireOIDCProviderStore() + if err != nil { + return nil, err + } + records, err := store.ListProviders(ctx, "") + if err != nil { + return nil, status.Errorf(codes.Unavailable, "list OIDC providers: %v", err) + } + resp := &iam_pb.ListOIDCProvidersResponse{} + for _, rec := range records { + resp.Providers = append(resp.Providers, toPbOIDCProvider(rec)) + } + return resp, nil +} + +func toPbRole(role *integration.RoleDefinition) (*iam_pb.Role, error) { + out := &iam_pb.Role{ + RoleName: role.RoleName, + RoleArn: role.RoleArn, + AttachedPolicies: role.AttachedPolicies, + Description: role.Description, + MaxSessionDuration: role.MaxSessionDuration, + } + if role.TrustPolicy != nil { + doc, err := json.Marshal(role.TrustPolicy) + if err != nil { + return nil, status.Errorf(codes.Internal, "encode trust policy of role %s: %v", role.RoleName, err) + } + out.TrustPolicy = string(doc) + } + return out, nil +} + +// lookupRole returns the stored role, nil when there is none, or an error when +// the store could not be read. +func lookupRole(ctx context.Context, store integration.RoleStore, name string) (*integration.RoleDefinition, error) { + role, err := store.GetRole(ctx, "", name) + if errors.Is(err, integration.ErrRoleNotFound) { + return nil, nil + } + if err != nil { + return nil, status.Errorf(codes.Unavailable, "read role %s: %v", name, err) + } + return role, nil +} + +// PutRole creates or replaces a role in the filer's role store. A stored role +// takes precedence over a same-named role in an S3 server's IAM config file; +// deleting the stored one restores the config-file role. +func (s *IamGrpcServer) PutRole(ctx context.Context, req *iam_pb.PutRoleRequest) (*iam_pb.PutRoleResponse, error) { + if err := s.checkAdminAuth(ctx); err != nil { + return nil, err + } + store, err := s.requireRoleStore() + if err != nil { + return nil, err + } + in := req.GetRole() + if in == nil || in.RoleName == "" { + return nil, status.Error(codes.InvalidArgument, "role.role_name is required") + } + if err := integration.ValidateRoleName(in.RoleName); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + if len(in.AttachedPolicies) > integration.MaxManagedPoliciesPerRole { + return nil, status.Errorf(codes.InvalidArgument, "at most %d managed policies may be attached to a role", integration.MaxManagedPoliciesPerRole) + } + if in.TrustPolicy == "" { + return nil, status.Error(codes.InvalidArgument, "role.trust_policy is required") + } + var trust policy.PolicyDocument + if err := json.Unmarshal([]byte(in.TrustPolicy), &trust); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "parse trust policy: %v", err) + } + // STS finds a role by the name in the ARN a caller presents, so a stored + // ARN naming another role would be honoured for neither name correctly. + if in.RoleArn != "" && utils.ExtractRoleNameFromArn(in.RoleArn) != in.RoleName { + return nil, status.Errorf(codes.InvalidArgument, "role.role_arn %s does not name role %s", in.RoleArn, in.RoleName) + } + role := &integration.RoleDefinition{ + RoleName: in.RoleName, + RoleArn: in.RoleArn, + TrustPolicy: &trust, + AttachedPolicies: in.AttachedPolicies, + Description: in.Description, + MaxSessionDuration: in.MaxSessionDuration, + } + if err := integration.PrepareRoleDefinition(in.RoleName, role); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + if len(role.AttachedPolicies) > 0 && s.credentialManager == nil { + return nil, status.Errorf(codes.FailedPrecondition, "credential manager is not configured") + } + for _, name := range role.AttachedPolicies { + existing, err := s.credentialManager.GetPolicy(ctx, name) + if err != nil { + return nil, status.Errorf(codes.Unavailable, "read policy %s: %v", name, err) + } + if existing == nil { + return nil, status.Errorf(codes.NotFound, "attached policy %s not found", name) + } + } + // A replaced role keeps its ID, so its sessions stay valid; a role created + // anew — including after a delete — gets a new one, so sessions of an + // earlier role of the same name do not carry over. The store's atomic + // update decides which, against the role as it is when written: a Put + // racing a DeleteRole cannot write the deleted role back with its old ID. + err = store.UpdateRole(ctx, "", in.RoleName, func(existing *integration.RoleDefinition) (*integration.RoleDefinition, error) { + next := *role + next.CreatedAt = time.Now().UTC() + next.RoleId = integration.NewRoleID() + if existing != nil { + next.CreatedAt = existing.CreatedAt + if existing.RoleId != "" { + next.RoleId = existing.RoleId + } + } + return &next, nil + }) + if err != nil { + return nil, status.Errorf(codes.Unavailable, "store role: %v", err) + } + return &iam_pb.PutRoleResponse{RoleArn: role.RoleArn}, nil +} + +func (s *IamGrpcServer) GetRole(ctx context.Context, req *iam_pb.GetRoleRequest) (*iam_pb.GetRoleResponse, error) { + if err := s.checkAdminAuth(ctx); err != nil { + return nil, err + } + store, err := s.requireRoleStore() + if err != nil { + return nil, err + } + if req.RoleName == "" { + return nil, status.Error(codes.InvalidArgument, "role_name is required") + } + role, err := lookupRole(ctx, store, req.RoleName) + if err != nil { + return nil, err + } + if role == nil { + return nil, status.Errorf(codes.NotFound, "role %s not found", req.RoleName) + } + out, err := toPbRole(role) + if err != nil { + return nil, err + } + return &iam_pb.GetRoleResponse{Role: out}, nil +} + +// DeleteRole returns NotFound for a role that does not exist, like +// DeleteOIDCProvider. Unlike the IAM API's DeleteRole it does not require the +// policies to be detached first: this API is declarative, and the role and its +// attachments are one object here. +// errRoleReplacedDuringDelete aborts a DeleteRole whose role was replaced +// between the request's read and its delete. errProviderReplacedDuringDelete +// is the same for a DeleteOIDCProvider. +var errRoleReplacedDuringDelete = errors.New("role replaced during delete") +var errProviderReplacedDuringDelete = errors.New("OIDC provider replaced during delete") + +func (s *IamGrpcServer) DeleteRole(ctx context.Context, req *iam_pb.DeleteRoleRequest) (*iam_pb.DeleteRoleResponse, error) { + if err := s.checkAdminAuth(ctx); err != nil { + return nil, err + } + store, err := s.requireRoleStore() + if err != nil { + return nil, err + } + if req.RoleName == "" { + return nil, status.Error(codes.InvalidArgument, "role_name is required") + } + // Delete the role as this request first saw it. The store's delete is + // conditional (RoleStore.UpdateRole), and a retry that finds the role + // replaced by a PutRole in between refuses rather than delete the newer + // definition: the caller decides again against it. + var seen []byte + err = store.UpdateRole(ctx, "", req.RoleName, func(existing *integration.RoleDefinition) (*integration.RoleDefinition, error) { + if existing == nil { + return nil, integration.ErrRoleNotFound + } + current, err := json.Marshal(existing) + if err != nil { + return nil, err + } + if seen == nil { + seen = current + } else if !bytes.Equal(seen, current) { + return nil, errRoleReplacedDuringDelete + } + return nil, nil + }) + if errors.Is(err, integration.ErrRoleNotFound) { + return nil, status.Errorf(codes.NotFound, "role %s not found", req.RoleName) + } + if errors.Is(err, errRoleReplacedDuringDelete) { + return nil, status.Errorf(codes.Aborted, "role %s was replaced while it was being deleted; retry", req.RoleName) + } + if err != nil { + return nil, status.Errorf(codes.Unavailable, "delete role: %v", err) + } + return &iam_pb.DeleteRoleResponse{}, nil +} + +func (s *IamGrpcServer) ListRoles(ctx context.Context, req *iam_pb.ListRolesRequest) (*iam_pb.ListRolesResponse, error) { + if err := s.checkAdminAuth(ctx); err != nil { + return nil, err + } + store, err := s.requireRoleStore() + if err != nil { + return nil, err + } + names, err := store.ListRoles(ctx, "") + if err != nil { + return nil, status.Errorf(codes.Unavailable, "list roles: %v", err) + } + resp := &iam_pb.ListRolesResponse{} + for _, name := range names { + role, err := lookupRole(ctx, store, name) + if err != nil { + return nil, err + } + if role == nil { + continue // deleted between list and read + } + out, err := toPbRole(role) + if err != nil { + return nil, err + } + resp.Roles = append(resp.Roles, out) + } + return resp, nil +} diff --git a/weed/server/filer_server_handlers_iam_grpc_sts_test.go b/weed/server/filer_server_handlers_iam_grpc_sts_test.go new file mode 100644 index 000000000..c45865be7 --- /dev/null +++ b/weed/server/filer_server_handlers_iam_grpc_sts_test.go @@ -0,0 +1,499 @@ +package weed_server + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/seaweedfs/seaweedfs/weed/credential" + "github.com/seaweedfs/seaweedfs/weed/iam/integration" + "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" + "github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine" + "github.com/seaweedfs/seaweedfs/weed/security" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const stsTestTrust = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow",` + + `"Principal":{"Federated":"https://oidc.example"},"Action":["sts:AssumeRoleWithWebIdentity"],` + + `"Condition":{"StringEquals":{"oidc:sub":"spiffe://example.org/ns/app/sa/app"}}}]}` + +func newSTSTestServer(t *testing.T) (*IamGrpcServer, context.Context, *integration.MemoryOIDCProviderStore, *integration.MemoryRoleStore) { + t.Helper() + s := newTestIamGrpcServer(t) + providers, roles := integration.NewMemoryOIDCProviderStore(), integration.NewMemoryRoleStore() + s.SetSTSStores(providers, roles) + doc := policy_engine.PolicyDocument{Version: "2012-10-17", Statement: []policy_engine.PolicyStatement{{ + Effect: policy_engine.PolicyEffectAllow, + Action: policy_engine.NewStringOrStringSlice("s3:GetObject"), + Resource: policy_engine.NewStringOrStringSlicePtr("arn:aws:s3:::bucket/*"), + }}} + require.NoError(t, s.credentialManager.CreatePolicy(context.Background(), "read-bucket", doc)) + ctx := ctxWithBearer(string(security.GenJwtForFilerAdmin(security.SigningKey(testIamSigningKey), 60))) + return s, ctx, providers, roles +} + +func requireCode(t *testing.T, err error, code codes.Code) { + t.Helper() + require.Error(t, err, "expected %s", code) + assert.Equal(t, code, status.Code(err), "error: %v", err) +} + +func TestIamGrpc_OIDCProviderPutGetListDelete(t *testing.T) { + s, ctx, _, _ := newSTSTestServer(t) + put, err := s.PutOIDCProvider(ctx, &iam_pb.PutOIDCProviderRequest{ + IssuerUrl: "https://oidc.example", ClientIds: []string{"aud"}, AccountId: "111122223333", + }) + require.NoError(t, err) + assert.Equal(t, "arn:aws:iam::111122223333:oidc-provider/oidc.example", put.Arn) + + got, err := s.GetOIDCProvider(ctx, &iam_pb.GetOIDCProviderRequest{IssuerUrl: "https://oidc.example", AccountId: "111122223333"}) + require.NoError(t, err) + assert.Equal(t, []string{"aud"}, got.Provider.ClientIds) + + list, err := s.ListOIDCProviders(ctx, &iam_pb.ListOIDCProvidersRequest{}) + require.NoError(t, err) + require.Len(t, list.Providers, 1) + + _, err = s.DeleteOIDCProvider(ctx, &iam_pb.DeleteOIDCProviderRequest{IssuerUrl: "https://oidc.example", AccountId: "111122223333"}) + require.NoError(t, err) + _, err = s.GetOIDCProvider(ctx, &iam_pb.GetOIDCProviderRequest{IssuerUrl: "https://oidc.example", AccountId: "111122223333"}) + requireCode(t, err, codes.NotFound) + _, err = s.DeleteOIDCProvider(ctx, &iam_pb.DeleteOIDCProviderRequest{IssuerUrl: "https://oidc.example", AccountId: "111122223333"}) + requireCode(t, err, codes.NotFound) +} + +// Put replaces what the request carries and keeps what it cannot express. +func TestIamGrpc_PutOIDCProviderKeepsWhatTheRequestCannotCarry(t *testing.T) { + s, ctx, providers, _ := newSTSTestServer(t) + created := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC) + arn, err := integration.DeriveOIDCProviderARN("", "https://oidc.example") + require.NoError(t, err) + require.NoError(t, providers.StoreProvider(context.Background(), "", &integration.OIDCProviderRecord{ + ARN: arn, URL: "https://oidc.example", ClientIDs: []string{"old"}, + Tags: map[string]string{"team": "infra"}, PolicyClaim: "policy", CreatedAt: created, + })) + + _, err = s.PutOIDCProvider(ctx, &iam_pb.PutOIDCProviderRequest{IssuerUrl: "https://oidc.example", ClientIds: []string{"new"}}) + require.NoError(t, err) + rec, err := providers.GetProviderByARN(context.Background(), "", arn) + require.NoError(t, err) + assert.Equal(t, []string{"new"}, rec.ClientIDs) + assert.Equal(t, map[string]string{"team": "infra"}, rec.Tags) + assert.Equal(t, "policy", rec.PolicyClaim) + assert.True(t, rec.CreatedAt.Equal(created)) +} + +func TestIamGrpc_RolePutGetListDelete(t *testing.T) { + s, ctx, _, roles := newSTSTestServer(t) + put, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{ + RoleName: "app", TrustPolicy: stsTestTrust, AttachedPolicies: []string{"read-bucket"}, MaxSessionDuration: 3600, + }}) + require.NoError(t, err) + assert.Equal(t, "arn:aws:iam::role/app", put.RoleArn) + + stored, err := roles.GetRole(context.Background(), "", "app") + require.NoError(t, err) + assert.Equal(t, []string{"read-bucket"}, stored.AttachedPolicies) + assert.False(t, stored.CreatedAt.IsZero()) + + got, err := s.GetRole(ctx, &iam_pb.GetRoleRequest{RoleName: "app"}) + require.NoError(t, err) + assert.Contains(t, got.Role.TrustPolicy, "spiffe://example.org/ns/app/sa/app") + + // Replacing keeps CreatedAt. + _, err = s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app", TrustPolicy: stsTestTrust}}) + require.NoError(t, err) + replaced, err := roles.GetRole(context.Background(), "", "app") + require.NoError(t, err) + assert.True(t, replaced.CreatedAt.Equal(stored.CreatedAt)) + assert.Empty(t, replaced.AttachedPolicies) + assert.NotEmpty(t, stored.RoleId, "a created role has an ID") + assert.Equal(t, stored.RoleId, replaced.RoleId, "replacing a role changed its ID") + + list, err := s.ListRoles(ctx, &iam_pb.ListRolesRequest{}) + require.NoError(t, err) + require.Len(t, list.Roles, 1) + + _, err = s.DeleteRole(ctx, &iam_pb.DeleteRoleRequest{RoleName: "app"}) + require.NoError(t, err) + _, err = s.GetRole(ctx, &iam_pb.GetRoleRequest{RoleName: "app"}) + requireCode(t, err, codes.NotFound) + _, err = s.DeleteRole(ctx, &iam_pb.DeleteRoleRequest{RoleName: "app"}) + requireCode(t, err, codes.NotFound) + + _, err = s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app", TrustPolicy: stsTestTrust}}) + require.NoError(t, err) + recreated, err := roles.GetRole(context.Background(), "", "app") + require.NoError(t, err) + assert.NotEqual(t, stored.RoleId, recreated.RoleId, "a role created again under a deleted role's name reuses its ID") +} + +func TestIamGrpc_STSRefusals(t *testing.T) { + s, ctx, _, _ := newSTSTestServer(t) + cases := []struct { + name string + call func() error + code codes.Code + }{ + {"provider without client IDs", func() error { + _, err := s.PutOIDCProvider(ctx, &iam_pb.PutOIDCProviderRequest{IssuerUrl: "https://oidc.example"}) + return err + }, codes.InvalidArgument}, + {"provider with bad thumbprint", func() error { + _, err := s.PutOIDCProvider(ctx, &iam_pb.PutOIDCProviderRequest{IssuerUrl: "https://oidc.example", ClientIds: []string{"a"}, Thumbprints: []string{"nope"}}) + return err + }, codes.InvalidArgument}, + {"role without trust policy", func() error { + _, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app"}}) + return err + }, codes.InvalidArgument}, + {"role with malformed trust policy", func() error { + _, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app", TrustPolicy: "{"}}) + return err + }, codes.InvalidArgument}, + {"role with session out of bounds", func() error { + _, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app", TrustPolicy: stsTestTrust, MaxSessionDuration: 60}}) + return err + }, codes.InvalidArgument}, + {"role name leaving the role store", func() error { + _, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "../identities/admin", TrustPolicy: stsTestTrust}}) + return err + }, codes.InvalidArgument}, + {"role over the policy quota", func() error { + policies := make([]string, integration.MaxManagedPoliciesPerRole+1) + for i := range policies { + policies[i] = "read-bucket" + } + _, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app", TrustPolicy: stsTestTrust, AttachedPolicies: policies}}) + return err + }, codes.InvalidArgument}, + {"role whose ARN names another role", func() error { + _, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app", RoleArn: "arn:aws:iam::role/admin", TrustPolicy: stsTestTrust}}) + return err + }, codes.InvalidArgument}, + {"role whose ARN is not a role ARN", func() error { + _, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app", RoleArn: "arn:aws:iam::user/app", TrustPolicy: stsTestTrust}}) + return err + }, codes.InvalidArgument}, + {"role attaching a missing policy", func() error { + _, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app", TrustPolicy: stsTestTrust, AttachedPolicies: []string{"nope"}}}) + return err + }, codes.NotFound}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { requireCode(t, tc.call(), tc.code) }) + } +} + +type unreadableProviders struct { + *integration.MemoryOIDCProviderStore +} + +func (unreadableProviders) GetProviderByARN(context.Context, string, string) (*integration.OIDCProviderRecord, error) { + return nil, errors.New("lookup OIDC provider: filer unavailable") +} + +// UpdateProvider reads the record first, as the filer store's does. +func (unreadableProviders) UpdateProvider(context.Context, string, string, integration.OIDCProviderUpdate) error { + return errors.New("lookup OIDC provider: filer unavailable") +} + +type unreadableRoles struct{ *integration.MemoryRoleStore } + +func (unreadableRoles) GetRole(context.Context, string, string) (*integration.RoleDefinition, error) { + return nil, errors.New("lookup role: filer unavailable") +} + +// UpdateRole reads the role first, as the filer store's does. +func (unreadableRoles) UpdateRole(context.Context, string, string, integration.RoleUpdate) error { + return errors.New("lookup role: filer unavailable") +} + +// An unreadable store is not an absent entry: Put must not write over what it +// could not see (a config-file entry included), and Delete must not report +// success. +func TestIamGrpc_UnreadableStoreIsUnavailableNotAbsent(t *testing.T) { + s, ctx, _, _ := newSTSTestServer(t) + providers, roles := unreadableProviders{integration.NewMemoryOIDCProviderStore()}, unreadableRoles{integration.NewMemoryRoleStore()} + s.SetSTSStores(providers, roles) + + _, err := s.PutOIDCProvider(ctx, &iam_pb.PutOIDCProviderRequest{IssuerUrl: "https://oidc.example", ClientIds: []string{"aud"}}) + requireCode(t, err, codes.Unavailable) + _, err = s.DeleteOIDCProvider(ctx, &iam_pb.DeleteOIDCProviderRequest{IssuerUrl: "https://oidc.example"}) + requireCode(t, err, codes.Unavailable) + _, err = s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app", TrustPolicy: stsTestTrust}}) + requireCode(t, err, codes.Unavailable) + _, err = s.DeleteRole(ctx, &iam_pb.DeleteRoleRequest{RoleName: "app"}) + requireCode(t, err, codes.Unavailable) + + recs, err := providers.ListProviders(context.Background(), "") + require.NoError(t, err) + assert.Empty(t, recs, "PutOIDCProvider wrote through a store it could not read") + names, err := roles.ListRoles(context.Background(), "") + require.NoError(t, err) + assert.Empty(t, names, "PutRole wrote through a store it could not read") +} + +func TestIamGrpc_STSRPCsWithoutStoresAreFailedPrecondition(t *testing.T) { + s := newTestIamGrpcServer(t) + ctx := ctxWithBearer(string(security.GenJwtForFilerAdmin(security.SigningKey(testIamSigningKey), 60))) + _, err := s.ListOIDCProviders(ctx, &iam_pb.ListOIDCProvidersRequest{}) + requireCode(t, err, codes.FailedPrecondition) + _, err = s.ListRoles(ctx, &iam_pb.ListRolesRequest{}) + requireCode(t, err, codes.FailedPrecondition) +} + +func TestIamGrpc_STSRPCsRequireAuth(t *testing.T) { + s, _, _, _ := newSTSTestServer(t) + ctx := context.Background() + calls := map[string]func() error{ + "PutOIDCProvider": func() error { _, err := s.PutOIDCProvider(ctx, &iam_pb.PutOIDCProviderRequest{}); return err }, + "GetOIDCProvider": func() error { _, err := s.GetOIDCProvider(ctx, &iam_pb.GetOIDCProviderRequest{}); return err }, + "DeleteOIDCProvider": func() error { + _, err := s.DeleteOIDCProvider(ctx, &iam_pb.DeleteOIDCProviderRequest{}) + return err + }, + "ListOIDCProviders": func() error { _, err := s.ListOIDCProviders(ctx, &iam_pb.ListOIDCProvidersRequest{}); return err }, + "PutRole": func() error { _, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{}); return err }, + "GetRole": func() error { _, err := s.GetRole(ctx, &iam_pb.GetRoleRequest{}); return err }, + "DeleteRole": func() error { _, err := s.DeleteRole(ctx, &iam_pb.DeleteRoleRequest{}); return err }, + "ListRoles": func() error { _, err := s.ListRoles(ctx, &iam_pb.ListRolesRequest{}); return err }, + } + for name, call := range calls { + t.Run(name, func(t *testing.T) { requireCode(t, call(), codes.Unauthenticated) }) + } +} + +func TestIamGrpc_DeletePolicyAttachedToARoleIsRefused(t *testing.T) { + s, ctx, _, _ := newSTSTestServer(t) + _, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{ + RoleName: "app", TrustPolicy: stsTestTrust, AttachedPolicies: []string{"read-bucket"}, + }}) + require.NoError(t, err) + + _, err = s.DeletePolicy(ctx, &iam_pb.DeletePolicyRequest{Name: "read-bucket"}) + requireCode(t, err, codes.FailedPrecondition) + + _, err = s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app", TrustPolicy: stsTestTrust}}) + require.NoError(t, err) + _, err = s.DeletePolicy(ctx, &iam_pb.DeletePolicyRequest{Name: "read-bucket"}) + assert.NoError(t, err, "a policy no role attaches could not be deleted") +} + +func TestIamGrpc_PutOIDCProviderRequiresHTTPSExceptOnLoopback(t *testing.T) { + s, ctx, _, _ := newSTSTestServer(t) + for issuer, ok := range map[string]bool{ + "https://oidc.example": true, + "http://localhost:8080": true, + "http://127.0.0.1:18999": true, + "http://[::1]:8080": true, + "http://oidc.example": false, + "http://10.0.0.5": false, + "ftp://oidc.example": false, + "http://localhost.attacker.example": false, + "http://LOCALHOST:8080": true, + "https://oidc.example?x=1": false, + "https://oidc.example?": false, + "https://oidc.example#frag": false, + "https://user@oidc.example": false, + } { + _, err := s.PutOIDCProvider(ctx, &iam_pb.PutOIDCProviderRequest{IssuerUrl: issuer, ClientIds: []string{"aud"}}) + if ok { + assert.NoError(t, err, issuer) + } else { + requireCode(t, err, codes.InvalidArgument) + } + } +} + +// Without an admin signing key the IAM service accepts any caller. Users and +// policies keep that opt-in behaviour, but the OIDC provider and role RPCs +// grant STS access outright, so they refuse to run unauthenticated. +func TestIamGrpc_STSRPCsRefuseAnUnauthenticatedService(t *testing.T) { + cm, err := credential.NewCredentialManager(credential.StoreTypeMemory, nil, "") + require.NoError(t, err) + s := NewIamGrpcServer(cm, nil) + s.SetSTSStores(integration.NewMemoryOIDCProviderStore(), integration.NewMemoryRoleStore()) + ctx := context.Background() + calls := map[string]func() error{ + "PutOIDCProvider": func() error { + _, err := s.PutOIDCProvider(ctx, &iam_pb.PutOIDCProviderRequest{IssuerUrl: "https://oidc.example", ClientIds: []string{"aud"}}) + return err + }, + "GetOIDCProvider": func() error { + _, err := s.GetOIDCProvider(ctx, &iam_pb.GetOIDCProviderRequest{IssuerUrl: "https://oidc.example"}) + return err + }, + "DeleteOIDCProvider": func() error { + _, err := s.DeleteOIDCProvider(ctx, &iam_pb.DeleteOIDCProviderRequest{IssuerUrl: "https://oidc.example"}) + return err + }, + "ListOIDCProviders": func() error { _, err := s.ListOIDCProviders(ctx, &iam_pb.ListOIDCProvidersRequest{}); return err }, + "PutRole": func() error { + _, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app", TrustPolicy: stsTestTrust}}) + return err + }, + "GetRole": func() error { _, err := s.GetRole(ctx, &iam_pb.GetRoleRequest{RoleName: "app"}); return err }, + "DeleteRole": func() error { _, err := s.DeleteRole(ctx, &iam_pb.DeleteRoleRequest{RoleName: "app"}); return err }, + "ListRoles": func() error { _, err := s.ListRoles(ctx, &iam_pb.ListRolesRequest{}); return err }, + } + for name, call := range calls { + t.Run(name, func(t *testing.T) { requireCode(t, call(), codes.FailedPrecondition) }) + } + + // Users keep the service's opt-in auth. + _, err = s.ListUsers(ctx, &iam_pb.ListUsersRequest{}) + assert.NoError(t, err) +} + +// deletedDuringPutRoles has UpdateRole behave as the filer store's does when +// a DeleteRole lands between its read and its write: the conditional write +// fails, and the update is applied again to the role as it now is — absent. +type deletedDuringPutRoles struct { + *integration.MemoryRoleStore + earlier *integration.RoleDefinition +} + +// GetRole is a read made before the delete landed. +func (s deletedDuringPutRoles) GetRole(context.Context, string, string) (*integration.RoleDefinition, error) { + return s.earlier, nil +} + +func (s deletedDuringPutRoles) UpdateRole(ctx context.Context, addr, name string, update integration.RoleUpdate) error { + if _, err := update(s.earlier); err != nil { // the write that loses to the delete + return err + } + next, err := update(nil) + if err != nil { + return err + } + return s.MemoryRoleStore.StoreRole(ctx, addr, name, next) +} + +// A PutRole racing a DeleteRole must not write the deleted role back under +// its old ID, which would revive the deleted role's sessions. +func TestIamGrpc_PutRoleRacingADeleteDoesNotReviveTheOldRoleID(t *testing.T) { + s, ctx, providers, _ := newSTSTestServer(t) + store := deletedDuringPutRoles{MemoryRoleStore: integration.NewMemoryRoleStore(), earlier: &integration.RoleDefinition{RoleName: "app", RoleId: "AROA-DELETED"}} + s.SetSTSStores(providers, store) + + _, err := s.PutRole(ctx, &iam_pb.PutRoleRequest{Role: &iam_pb.Role{RoleName: "app", TrustPolicy: stsTestTrust}}) + require.NoError(t, err) + role, err := store.MemoryRoleStore.GetRole(context.Background(), "", "app") + require.NoError(t, err) + assert.NotEqual(t, "AROA-DELETED", role.RoleId, "the deleted role's ID was written back") + assert.NotEmpty(t, role.RoleId) +} + +// replacedDuringDeleteRoles has UpdateRole behave as the filer store's does +// when a PutRole replaces the role between the delete's read and its write: +// the conditional delete fails and the update runs again on the replacement. +type replacedDuringDeleteRoles struct { + *integration.MemoryRoleStore + earlier *integration.RoleDefinition +} + +func (s replacedDuringDeleteRoles) UpdateRole(ctx context.Context, addr, name string, update integration.RoleUpdate) error { + if _, err := update(s.earlier); err != nil { // the delete that loses to the PutRole + return err + } + return s.MemoryRoleStore.UpdateRole(ctx, addr, name, update) +} + +// A DeleteRole racing a PutRole does not delete the definition the PutRole +// wrote: it is refused as Aborted, and the caller decides again. +func TestIamGrpc_DeleteRoleDoesNotDeleteARoleReplacedMeanwhile(t *testing.T) { + s, ctx, providers, _ := newSTSTestServer(t) + store := replacedDuringDeleteRoles{MemoryRoleStore: integration.NewMemoryRoleStore(), earlier: &integration.RoleDefinition{RoleName: "app", RoleId: "AROA-OLD"}} + require.NoError(t, store.StoreRole(context.Background(), "", "app", &integration.RoleDefinition{RoleName: "app", RoleId: "AROA-NEW"})) + s.SetSTSStores(providers, store) + + _, err := s.DeleteRole(ctx, &iam_pb.DeleteRoleRequest{RoleName: "app"}) + requireCode(t, err, codes.Aborted) + role, err := store.GetRole(context.Background(), "", "app") + require.NoError(t, err, "the replacement role was deleted") + assert.Equal(t, "AROA-NEW", role.RoleId) +} + +// deletedDuringPutProviders has UpdateProvider behave as the filer store's +// does when a delete lands between its read and its write: the conditional +// write fails, and the update is applied again to the record as it now +// is — absent. +type deletedDuringPutProviders struct { + *integration.MemoryOIDCProviderStore + earlier *integration.OIDCProviderRecord +} + +func (s deletedDuringPutProviders) UpdateProvider(ctx context.Context, addr, arn string, update integration.OIDCProviderUpdate) error { + if _, err := update(s.earlier); err != nil { // the write that loses to the delete + return err + } + next, err := update(nil) + if err != nil { + return err + } + return s.MemoryOIDCProviderStore.StoreProvider(ctx, addr, next) +} + +// A PutOIDCProvider racing a DeleteOIDCProvider must not carry the deleted +// record's fields over to the new one — the request decides them. +func TestIamGrpc_PutOIDCProviderRacingADeleteKeepsNoOldFields(t *testing.T) { + s, ctx, _, roles := newSTSTestServer(t) + arn := "arn:aws:iam::111122223333:oidc-provider/oidc.example" + store := deletedDuringPutProviders{ + MemoryOIDCProviderStore: integration.NewMemoryOIDCProviderStore(), + earlier: &integration.OIDCProviderRecord{ + ARN: arn, URL: "https://oidc.example", Tags: map[string]string{"env": "deleted"}, PolicyClaim: "stale", + }, + } + s.SetSTSStores(store, roles) + + _, err := s.PutOIDCProvider(ctx, &iam_pb.PutOIDCProviderRequest{ + IssuerUrl: "https://oidc.example", ClientIds: []string{"aud"}, AccountId: "111122223333", + }) + require.NoError(t, err) + rec, err := store.MemoryOIDCProviderStore.GetProviderByARN(context.Background(), "", arn) + require.NoError(t, err) + assert.Empty(t, rec.Tags, "the deleted record's tags were carried over") + assert.Empty(t, rec.PolicyClaim, "the deleted record's policy claim was carried over") + assert.Equal(t, []string{"aud"}, rec.ClientIDs) +} + +// replacedDuringDeleteProviders has UpdateProvider behave as the filer +// store's does when a PutOIDCProvider replaces the record between the +// delete's read and its write: the conditional delete fails and the update +// runs again on the replacement. +type replacedDuringDeleteProviders struct { + *integration.MemoryOIDCProviderStore + earlier *integration.OIDCProviderRecord +} + +func (s replacedDuringDeleteProviders) UpdateProvider(ctx context.Context, addr, arn string, update integration.OIDCProviderUpdate) error { + if _, err := update(s.earlier); err != nil { // the delete that loses to the put + return err + } + return s.MemoryOIDCProviderStore.UpdateProvider(ctx, addr, arn, update) +} + +// A DeleteOIDCProvider racing a PutOIDCProvider does not delete the record +// the put wrote: it is refused as Aborted, and the caller decides again. +func TestIamGrpc_DeleteOIDCProviderDoesNotDeleteAProviderReplacedMeanwhile(t *testing.T) { + s, ctx, _, roles := newSTSTestServer(t) + arn := "arn:aws:iam::111122223333:oidc-provider/oidc.example" + store := replacedDuringDeleteProviders{ + MemoryOIDCProviderStore: integration.NewMemoryOIDCProviderStore(), + earlier: &integration.OIDCProviderRecord{ARN: arn, URL: "https://oidc.example", ClientIDs: []string{"old"}}, + } + require.NoError(t, store.StoreProvider(context.Background(), "", &integration.OIDCProviderRecord{ARN: arn, URL: "https://oidc.example", ClientIDs: []string{"new"}})) + s.SetSTSStores(store, roles) + + _, err := s.DeleteOIDCProvider(ctx, &iam_pb.DeleteOIDCProviderRequest{IssuerUrl: "https://oidc.example", AccountId: "111122223333"}) + requireCode(t, err, codes.Aborted) + rec, err := store.GetProviderByARN(context.Background(), "", arn) + require.NoError(t, err, "the replacement record was deleted") + assert.Equal(t, []string{"new"}, rec.ClientIDs) +}