| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- package policy
- import (
- "context"
- "encoding/json"
- "fmt"
- "strings"
- "sync"
- "time"
- "github.com/seaweedfs/seaweedfs/weed/glog"
- "github.com/seaweedfs/seaweedfs/weed/pb"
- "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
- "google.golang.org/grpc"
- )
- // MemoryPolicyStore implements PolicyStore using in-memory storage
- type MemoryPolicyStore struct {
- policies map[string]*PolicyDocument
- mutex sync.RWMutex
- }
- // NewMemoryPolicyStore creates a new memory-based policy store
- func NewMemoryPolicyStore() *MemoryPolicyStore {
- return &MemoryPolicyStore{
- policies: make(map[string]*PolicyDocument),
- }
- }
- // StorePolicy stores a policy document in memory (filerAddress ignored for memory store)
- func (s *MemoryPolicyStore) StorePolicy(ctx context.Context, filerAddress string, name string, policy *PolicyDocument) error {
- if name == "" {
- return fmt.Errorf("policy name cannot be empty")
- }
- if policy == nil {
- return fmt.Errorf("policy cannot be nil")
- }
- s.mutex.Lock()
- defer s.mutex.Unlock()
- // Deep copy the policy to prevent external modifications
- s.policies[name] = copyPolicyDocument(policy)
- return nil
- }
- // GetPolicy retrieves a policy document from memory (filerAddress ignored for memory store)
- func (s *MemoryPolicyStore) GetPolicy(ctx context.Context, filerAddress string, name string) (*PolicyDocument, error) {
- if name == "" {
- return nil, fmt.Errorf("policy name cannot be empty")
- }
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- policy, exists := s.policies[name]
- if !exists {
- return nil, fmt.Errorf("policy not found: %s", name)
- }
- // Return a copy to prevent external modifications
- return copyPolicyDocument(policy), nil
- }
- // DeletePolicy deletes a policy document from memory (filerAddress ignored for memory store)
- func (s *MemoryPolicyStore) DeletePolicy(ctx context.Context, filerAddress string, name string) error {
- if name == "" {
- return fmt.Errorf("policy name cannot be empty")
- }
- s.mutex.Lock()
- defer s.mutex.Unlock()
- delete(s.policies, name)
- return nil
- }
- // ListPolicies lists all policy names in memory (filerAddress ignored for memory store)
- func (s *MemoryPolicyStore) ListPolicies(ctx context.Context, filerAddress string) ([]string, error) {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- names := make([]string, 0, len(s.policies))
- for name := range s.policies {
- names = append(names, name)
- }
- return names, nil
- }
- // copyPolicyDocument creates a deep copy of a policy document
- func copyPolicyDocument(original *PolicyDocument) *PolicyDocument {
- if original == nil {
- return nil
- }
- copied := &PolicyDocument{
- Version: original.Version,
- Id: original.Id,
- }
- // Copy statements
- copied.Statement = make([]Statement, len(original.Statement))
- for i, stmt := range original.Statement {
- copied.Statement[i] = Statement{
- Sid: stmt.Sid,
- Effect: stmt.Effect,
- Principal: stmt.Principal,
- NotPrincipal: stmt.NotPrincipal,
- }
- // Copy action slice
- if stmt.Action != nil {
- copied.Statement[i].Action = make([]string, len(stmt.Action))
- copy(copied.Statement[i].Action, stmt.Action)
- }
- // Copy NotAction slice
- if stmt.NotAction != nil {
- copied.Statement[i].NotAction = make([]string, len(stmt.NotAction))
- copy(copied.Statement[i].NotAction, stmt.NotAction)
- }
- // Copy resource slice
- if stmt.Resource != nil {
- copied.Statement[i].Resource = make([]string, len(stmt.Resource))
- copy(copied.Statement[i].Resource, stmt.Resource)
- }
- // Copy NotResource slice
- if stmt.NotResource != nil {
- copied.Statement[i].NotResource = make([]string, len(stmt.NotResource))
- copy(copied.Statement[i].NotResource, stmt.NotResource)
- }
- // Copy condition map (shallow copy for now)
- if stmt.Condition != nil {
- copied.Statement[i].Condition = make(map[string]map[string]interface{})
- for k, v := range stmt.Condition {
- copied.Statement[i].Condition[k] = v
- }
- }
- }
- return copied
- }
- // FilerPolicyStore implements PolicyStore using SeaweedFS filer
- type FilerPolicyStore struct {
- grpcDialOption grpc.DialOption
- basePath string
- filerAddressProvider func() string
- }
- // NewFilerPolicyStore creates a new filer-based policy store
- func NewFilerPolicyStore(config map[string]interface{}, filerAddressProvider func() string) (*FilerPolicyStore, error) {
- store := &FilerPolicyStore{
- basePath: "/etc/iam/policies", // Default path for policy storage - aligned with /etc/ convention
- filerAddressProvider: filerAddressProvider,
- }
- // 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 FilerPolicyStore with basePath %s", store.basePath)
- return store, nil
- }
- // StorePolicy stores a policy document in filer
- func (s *FilerPolicyStore) StorePolicy(ctx context.Context, filerAddress string, name string, policy *PolicyDocument) error {
- // Use provider function if filerAddress is not provided
- if filerAddress == "" && s.filerAddressProvider != nil {
- filerAddress = s.filerAddressProvider()
- }
- if filerAddress == "" {
- return fmt.Errorf("filer address is required for FilerPolicyStore")
- }
- if name == "" {
- return fmt.Errorf("policy name cannot be empty")
- }
- if policy == nil {
- return fmt.Errorf("policy cannot be nil")
- }
- // Serialize policy to JSON
- policyData, err := json.MarshalIndent(policy, "", " ")
- if err != nil {
- return fmt.Errorf("failed to serialize policy: %v", err)
- }
- policyPath := s.getPolicyPath(name)
- // Store in filer
- return s.withFilerClient(filerAddress, func(client filer_pb.SeaweedFilerClient) error {
- request := &filer_pb.CreateEntryRequest{
- Directory: s.basePath,
- Entry: &filer_pb.Entry{
- Name: s.getPolicyFileName(name),
- 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: policyData,
- },
- }
- glog.V(3).Infof("Storing policy %s at %s", name, policyPath)
- _, err := client.CreateEntry(ctx, request)
- if err != nil {
- return fmt.Errorf("failed to store policy %s: %v", name, err)
- }
- return nil
- })
- }
- // GetPolicy retrieves a policy document from filer
- func (s *FilerPolicyStore) GetPolicy(ctx context.Context, filerAddress string, name string) (*PolicyDocument, error) {
- // Use provider function if filerAddress is not provided
- if filerAddress == "" && s.filerAddressProvider != nil {
- filerAddress = s.filerAddressProvider()
- }
- if filerAddress == "" {
- return nil, fmt.Errorf("filer address is required for FilerPolicyStore")
- }
- if name == "" {
- return nil, fmt.Errorf("policy name cannot be empty")
- }
- var policyData []byte
- err := s.withFilerClient(filerAddress, func(client filer_pb.SeaweedFilerClient) error {
- request := &filer_pb.LookupDirectoryEntryRequest{
- Directory: s.basePath,
- Name: s.getPolicyFileName(name),
- }
- glog.V(3).Infof("Looking up policy %s", name)
- response, err := client.LookupDirectoryEntry(ctx, request)
- if err != nil {
- return fmt.Errorf("policy not found: %v", err)
- }
- if response.Entry == nil {
- return fmt.Errorf("policy not found")
- }
- policyData = response.Entry.Content
- return nil
- })
- if err != nil {
- return nil, err
- }
- // Deserialize policy from JSON
- var policy PolicyDocument
- if err := json.Unmarshal(policyData, &policy); err != nil {
- return nil, fmt.Errorf("failed to deserialize policy: %v", err)
- }
- return &policy, nil
- }
- // DeletePolicy deletes a policy document from filer
- func (s *FilerPolicyStore) DeletePolicy(ctx context.Context, filerAddress string, name string) error {
- // Use provider function if filerAddress is not provided
- if filerAddress == "" && s.filerAddressProvider != nil {
- filerAddress = s.filerAddressProvider()
- }
- if filerAddress == "" {
- return fmt.Errorf("filer address is required for FilerPolicyStore")
- }
- if name == "" {
- return fmt.Errorf("policy name cannot be empty")
- }
- return s.withFilerClient(filerAddress, func(client filer_pb.SeaweedFilerClient) error {
- request := &filer_pb.DeleteEntryRequest{
- Directory: s.basePath,
- Name: s.getPolicyFileName(name),
- IsDeleteData: true,
- IsRecursive: false,
- IgnoreRecursiveError: false,
- }
- glog.V(3).Infof("Deleting policy %s", name)
- resp, err := client.DeleteEntry(ctx, request)
- if err != nil {
- // Ignore "not found" errors - policy may already be deleted
- if strings.Contains(err.Error(), "not found") {
- return nil
- }
- return fmt.Errorf("failed to delete policy %s: %v", name, err)
- }
- // Check response error
- if resp.Error != "" {
- // Ignore "not found" errors - policy may already be deleted
- if strings.Contains(resp.Error, "not found") {
- return nil
- }
- return fmt.Errorf("failed to delete policy %s: %s", name, resp.Error)
- }
- return nil
- })
- }
- // ListPolicies lists all policy names in filer
- func (s *FilerPolicyStore) ListPolicies(ctx context.Context, filerAddress string) ([]string, error) {
- // Use provider function if filerAddress is not provided
- if filerAddress == "" && s.filerAddressProvider != nil {
- filerAddress = s.filerAddressProvider()
- }
- if filerAddress == "" {
- return nil, fmt.Errorf("filer address is required for FilerPolicyStore")
- }
- var policyNames []string
- err := s.withFilerClient(filerAddress, func(client filer_pb.SeaweedFilerClient) error {
- // List all entries in the policy directory
- request := &filer_pb.ListEntriesRequest{
- Directory: s.basePath,
- Prefix: "policy_",
- StartFromFileName: "",
- InclusiveStartFrom: false,
- Limit: 1000, // Process in batches of 1000
- }
- stream, err := client.ListEntries(ctx, request)
- if err != nil {
- return fmt.Errorf("failed to list policies: %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 policy name from filename
- filename := resp.Entry.Name
- if strings.HasPrefix(filename, "policy_") && strings.HasSuffix(filename, ".json") {
- // Remove "policy_" prefix and ".json" suffix
- policyName := strings.TrimSuffix(strings.TrimPrefix(filename, "policy_"), ".json")
- policyNames = append(policyNames, policyName)
- }
- }
- return nil
- })
- if err != nil {
- return nil, err
- }
- return policyNames, nil
- }
- // Helper methods
- // withFilerClient executes a function with a filer client
- func (s *FilerPolicyStore) withFilerClient(filerAddress string, fn func(client filer_pb.SeaweedFilerClient) error) error {
- if filerAddress == "" {
- return fmt.Errorf("filer address is required for FilerPolicyStore")
- }
- // Use the pb.WithGrpcFilerClient helper similar to existing SeaweedFS code
- return pb.WithGrpcFilerClient(false, 0, pb.ServerAddress(filerAddress), s.grpcDialOption, fn)
- }
- // getPolicyPath returns the full path for a policy
- func (s *FilerPolicyStore) getPolicyPath(policyName string) string {
- return s.basePath + "/" + s.getPolicyFileName(policyName)
- }
- // getPolicyFileName returns the filename for a policy
- func (s *FilerPolicyStore) getPolicyFileName(policyName string) string {
- return "policy_" + policyName + ".json"
- }
|