github.com/apptainer/singularity@v3.1.1+incompatible/pkg/util/capabilities/config.go (about)

     1  // Copyright (c) 2018, Sylabs Inc. All rights reserved.
     2  // This software is licensed under a 3-clause BSD license. Please consult the
     3  // LICENSE.md file distributed with the sources of this project regarding your
     4  // rights to use or distribute this software.
     5  
     6  package capabilities
     7  
     8  import (
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  )
    14  
    15  // Caplist defines a map of users/groups with associated list of capabilities
    16  type Caplist map[string][]string
    17  
    18  // Config is the in memory representation of the user/group capability
    19  // authorizations as set by an admin
    20  type Config struct {
    21  	Users  Caplist `json:"users,omitempty"`
    22  	Groups Caplist `json:"groups,omitempty"`
    23  }
    24  
    25  // ReadFrom reads a capability configuration from an io.Reader and returns a capability
    26  // config with the set of authorized user/group capabilities
    27  func ReadFrom(r io.Reader) (*Config, error) {
    28  	c := &Config{
    29  		Users:  make(Caplist, 0),
    30  		Groups: make(Caplist, 0),
    31  	}
    32  
    33  	// read all data from r into b
    34  	b, err := ioutil.ReadAll(r)
    35  	if err != nil {
    36  		return nil, fmt.Errorf("failed to read from io.Reader: %s", err)
    37  	}
    38  
    39  	if len(b) > 0 {
    40  		// if we had data to read in io.Reader, attempt to unmarshal as JSON
    41  		if err := json.Unmarshal(b, c); err != nil {
    42  			return nil, fmt.Errorf("failed to decode JSON data from io.Reader: %s", err)
    43  		}
    44  	} else {
    45  		// if no data in io.Reader, populate c with empty data
    46  		data, err := json.Marshal(c)
    47  		if err != nil {
    48  			return nil, fmt.Errorf("failed to initialize data")
    49  		}
    50  		json.Unmarshal(data, c)
    51  	}
    52  
    53  	return c, nil
    54  }
    55  
    56  // WriteTo writes the capability config into the provided io.Writer. If writing to the
    57  // same file as passed to ReadFrom(io.Reader), the file should be truncated should seek to 0
    58  // before passing the file as the io.Writer
    59  func (c *Config) WriteTo(w io.Writer) (int64, error) {
    60  	json, err := json.MarshalIndent(c, "", "\t")
    61  	if err != nil {
    62  		return 0, fmt.Errorf("failed to marshall capability data to json: %v", err)
    63  	}
    64  
    65  	n, err := w.Write(json)
    66  	if err != nil {
    67  		return int64(n), fmt.Errorf("failed to write capability json to io.Writer: %v", err)
    68  	}
    69  
    70  	return int64(n), err
    71  }
    72  
    73  func (c *Config) checkCaps(caps []string) error {
    74  	for _, c := range caps {
    75  		if _, ok := Map[c]; !ok {
    76  			return fmt.Errorf("unknown capability %s", c)
    77  		}
    78  	}
    79  	return nil
    80  }
    81  
    82  // AddUserCaps adds an authorized capability set to user
    83  func (c *Config) AddUserCaps(user string, caps []string) error {
    84  	if err := c.checkCaps(caps); err != nil {
    85  		return err
    86  	}
    87  	for _, cap := range caps {
    88  		present := false
    89  		for _, c := range c.Users[user] {
    90  			if c == cap {
    91  				present = true
    92  			}
    93  		}
    94  		if !present {
    95  			c.Users[user] = append(c.Users[user], cap)
    96  		}
    97  	}
    98  	return nil
    99  }
   100  
   101  // AddGroupCaps adds an authorized capability set to group
   102  func (c *Config) AddGroupCaps(group string, caps []string) error {
   103  	if err := c.checkCaps(caps); err != nil {
   104  		return err
   105  	}
   106  	for _, cap := range caps {
   107  		present := false
   108  		for _, c := range c.Groups[group] {
   109  			if c == cap {
   110  				present = true
   111  			}
   112  		}
   113  		if !present {
   114  			c.Groups[group] = append(c.Groups[group], cap)
   115  		}
   116  	}
   117  	return nil
   118  }
   119  
   120  // DropUserCaps drops a set of capabilities for user
   121  func (c *Config) DropUserCaps(user string, caps []string) error {
   122  	if err := c.checkCaps(caps); err != nil {
   123  		return err
   124  	}
   125  	if _, ok := c.Users[user]; !ok {
   126  		return fmt.Errorf("user '%s' not found", user)
   127  	}
   128  	for _, cap := range caps {
   129  		for i := len(c.Users[user]) - 1; i >= 0; i-- {
   130  			if c.Users[user][i] == cap {
   131  				c.Users[user] = append(c.Users[user][:i], c.Users[user][i+1:]...)
   132  			}
   133  		}
   134  	}
   135  	return nil
   136  }
   137  
   138  // DropGroupCaps drops a set of capabilities for group
   139  func (c *Config) DropGroupCaps(group string, caps []string) error {
   140  	if err := c.checkCaps(caps); err != nil {
   141  		return err
   142  	}
   143  	if _, ok := c.Groups[group]; !ok {
   144  		return fmt.Errorf("group '%s' not found", group)
   145  	}
   146  	for _, cap := range caps {
   147  		for i := len(c.Groups[group]) - 1; i >= 0; i-- {
   148  			if c.Groups[group][i] == cap {
   149  				c.Groups[group] = append(c.Groups[group][:i], c.Groups[group][i+1:]...)
   150  			}
   151  		}
   152  	}
   153  	return nil
   154  }
   155  
   156  // ListUserCaps returns a capability list authorized for user
   157  func (c *Config) ListUserCaps(user string) []string {
   158  	return c.Users[user]
   159  }
   160  
   161  // ListGroupCaps returns a capability list authorized for group
   162  func (c *Config) ListGroupCaps(group string) []string {
   163  	return c.Groups[group]
   164  }
   165  
   166  // ListAllCaps returns capability list for both authorized users and groups
   167  func (c *Config) ListAllCaps() (Caplist, Caplist) {
   168  	return c.Users, c.Groups
   169  }
   170  
   171  // CheckUserCaps checks if provided capability list for user are whether
   172  // or not authorized by returning two lists, the first one containing
   173  // authorized capabilities and the second one containing unauthorized
   174  // capabilities
   175  func (c *Config) CheckUserCaps(user string, caps []string) (authorized []string, unauthorized []string) {
   176  	for _, ca := range caps {
   177  		present := false
   178  		for _, userCap := range c.ListUserCaps(user) {
   179  			if userCap == ca {
   180  				authorized = append(authorized, ca)
   181  				present = true
   182  				break
   183  			}
   184  		}
   185  		if !present {
   186  			unauthorized = append(unauthorized, ca)
   187  		}
   188  	}
   189  	return authorized, unauthorized
   190  }
   191  
   192  // CheckGroupCaps checks if provided capability list for group are whether
   193  // or not authorized by returning two lists, the first one containing
   194  // authorized capabilities and the second one containing unauthorized
   195  // capabilities
   196  func (c *Config) CheckGroupCaps(group string, caps []string) (authorized []string, unauthorized []string) {
   197  	for _, ca := range caps {
   198  		present := false
   199  		for _, groupCap := range c.ListGroupCaps(group) {
   200  			if groupCap == ca {
   201  				authorized = append(authorized, ca)
   202  				present = true
   203  				break
   204  			}
   205  		}
   206  		if !present {
   207  			unauthorized = append(unauthorized, ca)
   208  		}
   209  	}
   210  	return authorized, unauthorized
   211  }