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