You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							344 lines
						
					
					
						
							9.6 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							344 lines
						
					
					
						
							9.6 KiB
						
					
					
				
								package integration
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"context"
							 | 
						|
									"encoding/json"
							 | 
						|
									"fmt"
							 | 
						|
									"strings"
							 | 
						|
									"sync"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/iam/policy"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/pb"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
							 | 
						|
									"google.golang.org/grpc"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// RoleStore defines the interface for storing IAM role definitions
							 | 
						|
								type RoleStore interface {
							 | 
						|
									// StoreRole stores a role definition (filerAddress ignored for memory stores)
							 | 
						|
									StoreRole(ctx context.Context, filerAddress string, roleName string, role *RoleDefinition) error
							 | 
						|
								
							 | 
						|
									// GetRole retrieves a role definition (filerAddress ignored for memory stores)
							 | 
						|
									GetRole(ctx context.Context, filerAddress string, roleName string) (*RoleDefinition, error)
							 | 
						|
								
							 | 
						|
									// ListRoles lists all role names (filerAddress ignored for memory stores)
							 | 
						|
									ListRoles(ctx context.Context, filerAddress string) ([]string, error)
							 | 
						|
								
							 | 
						|
									// DeleteRole deletes a role definition (filerAddress ignored for memory stores)
							 | 
						|
									DeleteRole(ctx context.Context, filerAddress string, roleName string) error
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// MemoryRoleStore implements RoleStore using in-memory storage
							 | 
						|
								type MemoryRoleStore struct {
							 | 
						|
									roles map[string]*RoleDefinition
							 | 
						|
									mutex sync.RWMutex
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// NewMemoryRoleStore creates a new memory-based role store
							 | 
						|
								func NewMemoryRoleStore() *MemoryRoleStore {
							 | 
						|
									return &MemoryRoleStore{
							 | 
						|
										roles: make(map[string]*RoleDefinition),
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// StoreRole stores a role definition in memory (filerAddress ignored for memory store)
							 | 
						|
								func (m *MemoryRoleStore) StoreRole(ctx context.Context, filerAddress string, roleName string, role *RoleDefinition) error {
							 | 
						|
									if roleName == "" {
							 | 
						|
										return fmt.Errorf("role name cannot be empty")
							 | 
						|
									}
							 | 
						|
									if role == nil {
							 | 
						|
										return fmt.Errorf("role cannot be nil")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									m.mutex.Lock()
							 | 
						|
									defer m.mutex.Unlock()
							 | 
						|
								
							 | 
						|
									// Deep copy the role to prevent external modifications
							 | 
						|
									m.roles[roleName] = copyRoleDefinition(role)
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// GetRole retrieves a role definition from memory (filerAddress ignored for memory store)
							 | 
						|
								func (m *MemoryRoleStore) GetRole(ctx context.Context, filerAddress string, roleName string) (*RoleDefinition, error) {
							 | 
						|
									if roleName == "" {
							 | 
						|
										return nil, fmt.Errorf("role name cannot be empty")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									m.mutex.RLock()
							 | 
						|
									defer m.mutex.RUnlock()
							 | 
						|
								
							 | 
						|
									role, exists := m.roles[roleName]
							 | 
						|
									if !exists {
							 | 
						|
										return nil, fmt.Errorf("role not found: %s", roleName)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Return a copy to prevent external modifications
							 | 
						|
									return copyRoleDefinition(role), nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ListRoles lists all role names in memory (filerAddress ignored for memory store)
							 | 
						|
								func (m *MemoryRoleStore) ListRoles(ctx context.Context, filerAddress string) ([]string, error) {
							 | 
						|
									m.mutex.RLock()
							 | 
						|
									defer m.mutex.RUnlock()
							 | 
						|
								
							 | 
						|
									names := make([]string, 0, len(m.roles))
							 | 
						|
									for name := range m.roles {
							 | 
						|
										names = append(names, name)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return names, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// DeleteRole deletes a role definition from memory (filerAddress ignored for memory store)
							 | 
						|
								func (m *MemoryRoleStore) DeleteRole(ctx context.Context, filerAddress string, roleName string) error {
							 | 
						|
									if roleName == "" {
							 | 
						|
										return fmt.Errorf("role name cannot be empty")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									m.mutex.Lock()
							 | 
						|
									defer m.mutex.Unlock()
							 | 
						|
								
							 | 
						|
									delete(m.roles, roleName)
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// copyRoleDefinition creates a deep copy of a role definition
							 | 
						|
								func copyRoleDefinition(original *RoleDefinition) *RoleDefinition {
							 | 
						|
									if original == nil {
							 | 
						|
										return nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									copied := &RoleDefinition{
							 | 
						|
										RoleName:    original.RoleName,
							 | 
						|
										RoleArn:     original.RoleArn,
							 | 
						|
										Description: original.Description,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Deep copy trust policy if it exists
							 | 
						|
									if original.TrustPolicy != nil {
							 | 
						|
										// Use JSON marshaling for deep copy of the complex policy structure
							 | 
						|
										trustPolicyData, _ := json.Marshal(original.TrustPolicy)
							 | 
						|
										var trustPolicyCopy policy.PolicyDocument
							 | 
						|
										json.Unmarshal(trustPolicyData, &trustPolicyCopy)
							 | 
						|
										copied.TrustPolicy = &trustPolicyCopy
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Copy attached policies slice
							 | 
						|
									if original.AttachedPolicies != nil {
							 | 
						|
										copied.AttachedPolicies = make([]string, len(original.AttachedPolicies))
							 | 
						|
										copy(copied.AttachedPolicies, original.AttachedPolicies)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return copied
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// FilerRoleStore implements RoleStore using SeaweedFS filer
							 | 
						|
								type FilerRoleStore struct {
							 | 
						|
									grpcDialOption grpc.DialOption
							 | 
						|
									basePath       string
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// NewFilerRoleStore creates a new filer-based role store
							 | 
						|
								func NewFilerRoleStore(config map[string]interface{}) (*FilerRoleStore, error) {
							 | 
						|
									store := &FilerRoleStore{
							 | 
						|
										basePath: "/etc/iam/roles", // Default path for role storage - aligned with /etc/ convention
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Parse configuration - only basePath and other settings, NOT filerAddress
							 | 
						|
									if config != nil {
							 | 
						|
										if basePath, ok := config["basePath"].(string); ok && basePath != "" {
							 | 
						|
											store.basePath = strings.TrimSuffix(basePath, "/")
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									glog.V(2).Infof("Initialized FilerRoleStore with basePath %s", store.basePath)
							 | 
						|
								
							 | 
						|
									return store, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// StoreRole stores a role definition in filer
							 | 
						|
								func (f *FilerRoleStore) StoreRole(ctx context.Context, filerAddress string, roleName string, role *RoleDefinition) error {
							 | 
						|
									if filerAddress == "" {
							 | 
						|
										return fmt.Errorf("filer address is required for FilerRoleStore")
							 | 
						|
									}
							 | 
						|
									if roleName == "" {
							 | 
						|
										return fmt.Errorf("role name cannot be empty")
							 | 
						|
									}
							 | 
						|
									if role == nil {
							 | 
						|
										return fmt.Errorf("role cannot be nil")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Serialize role to JSON
							 | 
						|
									roleData, err := json.MarshalIndent(role, "", "  ")
							 | 
						|
									if err != nil {
							 | 
						|
										return fmt.Errorf("failed to serialize role: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									rolePath := f.getRolePath(roleName)
							 | 
						|
								
							 | 
						|
									// Store in filer
							 | 
						|
									return f.withFilerClient(filerAddress, func(client filer_pb.SeaweedFilerClient) error {
							 | 
						|
										request := &filer_pb.CreateEntryRequest{
							 | 
						|
											Directory: f.basePath,
							 | 
						|
											Entry: &filer_pb.Entry{
							 | 
						|
												Name:        f.getRoleFileName(roleName),
							 | 
						|
												IsDirectory: false,
							 | 
						|
												Attributes: &filer_pb.FuseAttributes{
							 | 
						|
													Mtime:    time.Now().Unix(),
							 | 
						|
													Crtime:   time.Now().Unix(),
							 | 
						|
													FileMode: uint32(0600), // Read/write for owner only
							 | 
						|
													Uid:      uint32(0),
							 | 
						|
													Gid:      uint32(0),
							 | 
						|
												},
							 | 
						|
												Content: roleData,
							 | 
						|
											},
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										glog.V(3).Infof("Storing role %s at %s", roleName, rolePath)
							 | 
						|
										_, err := client.CreateEntry(ctx, request)
							 | 
						|
										if err != nil {
							 | 
						|
											return fmt.Errorf("failed to store role %s: %v", roleName, err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										return nil
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// GetRole retrieves a role definition from filer
							 | 
						|
								func (f *FilerRoleStore) GetRole(ctx context.Context, filerAddress string, roleName string) (*RoleDefinition, error) {
							 | 
						|
									if filerAddress == "" {
							 | 
						|
										return nil, fmt.Errorf("filer address is required for FilerRoleStore")
							 | 
						|
									}
							 | 
						|
									if roleName == "" {
							 | 
						|
										return nil, fmt.Errorf("role name cannot be empty")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									var roleData []byte
							 | 
						|
									err := f.withFilerClient(filerAddress, func(client filer_pb.SeaweedFilerClient) error {
							 | 
						|
										request := &filer_pb.LookupDirectoryEntryRequest{
							 | 
						|
											Directory: f.basePath,
							 | 
						|
											Name:      f.getRoleFileName(roleName),
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										glog.V(3).Infof("Looking up role %s", roleName)
							 | 
						|
										response, err := client.LookupDirectoryEntry(ctx, request)
							 | 
						|
										if err != nil {
							 | 
						|
											return fmt.Errorf("role not found: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if response.Entry == nil {
							 | 
						|
											return fmt.Errorf("role not found")
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										roleData = response.Entry.Content
							 | 
						|
										return nil
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Deserialize role from JSON
							 | 
						|
									var role RoleDefinition
							 | 
						|
									if err := json.Unmarshal(roleData, &role); err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to deserialize role: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &role, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ListRoles lists all role names in filer
							 | 
						|
								func (f *FilerRoleStore) ListRoles(ctx context.Context, filerAddress string) ([]string, error) {
							 | 
						|
									if filerAddress == "" {
							 | 
						|
										return nil, fmt.Errorf("filer address is required for FilerRoleStore")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									var roleNames []string
							 | 
						|
								
							 | 
						|
									err := f.withFilerClient(filerAddress, func(client filer_pb.SeaweedFilerClient) error {
							 | 
						|
										request := &filer_pb.ListEntriesRequest{
							 | 
						|
											Directory:          f.basePath,
							 | 
						|
											Prefix:             "",
							 | 
						|
											StartFromFileName:  "",
							 | 
						|
											InclusiveStartFrom: false,
							 | 
						|
											Limit:              1000, // Process in batches of 1000
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										glog.V(3).Infof("Listing roles in %s", f.basePath)
							 | 
						|
										stream, err := client.ListEntries(ctx, request)
							 | 
						|
										if err != nil {
							 | 
						|
											return fmt.Errorf("failed to list roles: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										for {
							 | 
						|
											resp, err := stream.Recv()
							 | 
						|
											if err != nil {
							 | 
						|
												break // End of stream or error
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											if resp.Entry == nil || resp.Entry.IsDirectory {
							 | 
						|
												continue
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											// Extract role name from filename
							 | 
						|
											filename := resp.Entry.Name
							 | 
						|
											if strings.HasSuffix(filename, ".json") {
							 | 
						|
												roleName := strings.TrimSuffix(filename, ".json")
							 | 
						|
												roleNames = append(roleNames, roleName)
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										return nil
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return roleNames, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// DeleteRole deletes a role definition from filer
							 | 
						|
								func (f *FilerRoleStore) DeleteRole(ctx context.Context, filerAddress string, roleName string) error {
							 | 
						|
									if filerAddress == "" {
							 | 
						|
										return fmt.Errorf("filer address is required for FilerRoleStore")
							 | 
						|
									}
							 | 
						|
									if roleName == "" {
							 | 
						|
										return fmt.Errorf("role name cannot be empty")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return f.withFilerClient(filerAddress, func(client filer_pb.SeaweedFilerClient) error {
							 | 
						|
										request := &filer_pb.DeleteEntryRequest{
							 | 
						|
											Directory:    f.basePath,
							 | 
						|
											Name:         f.getRoleFileName(roleName),
							 | 
						|
											IsDeleteData: true,
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										glog.V(3).Infof("Deleting role %s", roleName)
							 | 
						|
										_, err := client.DeleteEntry(ctx, request)
							 | 
						|
										if err != nil {
							 | 
						|
											return fmt.Errorf("failed to delete role %s: %v", roleName, err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										return nil
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Helper methods for FilerRoleStore
							 | 
						|
								
							 | 
						|
								func (f *FilerRoleStore) getRoleFileName(roleName string) string {
							 | 
						|
									return roleName + ".json"
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (f *FilerRoleStore) getRolePath(roleName string) string {
							 | 
						|
									return f.basePath + "/" + f.getRoleFileName(roleName)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (f *FilerRoleStore) withFilerClient(filerAddress string, fn func(filer_pb.SeaweedFilerClient) error) error {
							 | 
						|
									if filerAddress == "" {
							 | 
						|
										return fmt.Errorf("filer address is required for FilerRoleStore")
							 | 
						|
									}
							 | 
						|
									return pb.WithGrpcFilerClient(false, 0, pb.ServerAddress(filerAddress), f.grpcDialOption, fn)
							 | 
						|
								}
							 |