idtools.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package idtools
  2. import (
  3. "bufio"
  4. "fmt"
  5. "os"
  6. "sort"
  7. "strconv"
  8. "strings"
  9. )
  10. // IDMap contains a single entry for user namespace range remapping. An array
  11. // of IDMap entries represents the structure that will be provided to the Linux
  12. // kernel for creating a user namespace.
  13. type IDMap struct {
  14. ContainerID int `json:"container_id"`
  15. HostID int `json:"host_id"`
  16. Size int `json:"size"`
  17. }
  18. type subIDRange struct {
  19. Start int
  20. Length int
  21. }
  22. type ranges []subIDRange
  23. func (e ranges) Len() int { return len(e) }
  24. func (e ranges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
  25. func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start }
  26. const (
  27. subuidFileName string = "/etc/subuid"
  28. subgidFileName string = "/etc/subgid"
  29. )
  30. // MkdirAllAs creates a directory (include any along the path) and then modifies
  31. // ownership to the requested uid/gid. If the directory already exists, this
  32. // function will still change ownership to the requested uid/gid pair.
  33. func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
  34. return mkdirAs(path, mode, ownerUID, ownerGID, true, true)
  35. }
  36. // MkdirAllNewAs creates a directory (include any along the path) and then modifies
  37. // ownership ONLY of newly created directories to the requested uid/gid. If the
  38. // directories along the path exist, no change of ownership will be performed
  39. func MkdirAllNewAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
  40. return mkdirAs(path, mode, ownerUID, ownerGID, true, false)
  41. }
  42. // MkdirAs creates a directory and then modifies ownership to the requested uid/gid.
  43. // If the directory already exists, this function still changes ownership
  44. func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
  45. return mkdirAs(path, mode, ownerUID, ownerGID, false, true)
  46. }
  47. // GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
  48. // If the maps are empty, then the root uid/gid will default to "real" 0/0
  49. func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
  50. var uid, gid int
  51. if uidMap != nil {
  52. xUID, err := ToHost(0, uidMap)
  53. if err != nil {
  54. return -1, -1, err
  55. }
  56. uid = xUID
  57. }
  58. if gidMap != nil {
  59. xGID, err := ToHost(0, gidMap)
  60. if err != nil {
  61. return -1, -1, err
  62. }
  63. gid = xGID
  64. }
  65. return uid, gid, nil
  66. }
  67. // ToContainer takes an id mapping, and uses it to translate a
  68. // host ID to the remapped ID. If no map is provided, then the translation
  69. // assumes a 1-to-1 mapping and returns the passed in id
  70. func ToContainer(hostID int, idMap []IDMap) (int, error) {
  71. if idMap == nil {
  72. return hostID, nil
  73. }
  74. for _, m := range idMap {
  75. if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
  76. contID := m.ContainerID + (hostID - m.HostID)
  77. return contID, nil
  78. }
  79. }
  80. return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID)
  81. }
  82. // ToHost takes an id mapping and a remapped ID, and translates the
  83. // ID to the mapped host ID. If no map is provided, then the translation
  84. // assumes a 1-to-1 mapping and returns the passed in id #
  85. func ToHost(contID int, idMap []IDMap) (int, error) {
  86. if idMap == nil {
  87. return contID, nil
  88. }
  89. for _, m := range idMap {
  90. if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
  91. hostID := m.HostID + (contID - m.ContainerID)
  92. return hostID, nil
  93. }
  94. }
  95. return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
  96. }
  97. // CreateIDMappings takes a requested user and group name and
  98. // using the data from /etc/sub{uid,gid} ranges, creates the
  99. // proper uid and gid remapping ranges for that user/group pair
  100. func CreateIDMappings(username, groupname string) ([]IDMap, []IDMap, error) {
  101. subuidRanges, err := parseSubuid(username)
  102. if err != nil {
  103. return nil, nil, err
  104. }
  105. subgidRanges, err := parseSubgid(groupname)
  106. if err != nil {
  107. return nil, nil, err
  108. }
  109. if len(subuidRanges) == 0 {
  110. return nil, nil, fmt.Errorf("No subuid ranges found for user %q", username)
  111. }
  112. if len(subgidRanges) == 0 {
  113. return nil, nil, fmt.Errorf("No subgid ranges found for group %q", groupname)
  114. }
  115. return createIDMap(subuidRanges), createIDMap(subgidRanges), nil
  116. }
  117. func createIDMap(subidRanges ranges) []IDMap {
  118. idMap := []IDMap{}
  119. // sort the ranges by lowest ID first
  120. sort.Sort(subidRanges)
  121. containerID := 0
  122. for _, idrange := range subidRanges {
  123. idMap = append(idMap, IDMap{
  124. ContainerID: containerID,
  125. HostID: idrange.Start,
  126. Size: idrange.Length,
  127. })
  128. containerID = containerID + idrange.Length
  129. }
  130. return idMap
  131. }
  132. func parseSubuid(username string) (ranges, error) {
  133. return parseSubidFile(subuidFileName, username)
  134. }
  135. func parseSubgid(username string) (ranges, error) {
  136. return parseSubidFile(subgidFileName, username)
  137. }
  138. // parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid)
  139. // and return all found ranges for a specified username. If the special value
  140. // "ALL" is supplied for username, then all ranges in the file will be returned
  141. func parseSubidFile(path, username string) (ranges, error) {
  142. var rangeList ranges
  143. subidFile, err := os.Open(path)
  144. if err != nil {
  145. return rangeList, err
  146. }
  147. defer subidFile.Close()
  148. s := bufio.NewScanner(subidFile)
  149. for s.Scan() {
  150. if err := s.Err(); err != nil {
  151. return rangeList, err
  152. }
  153. text := strings.TrimSpace(s.Text())
  154. if text == "" || strings.HasPrefix(text, "#") {
  155. continue
  156. }
  157. parts := strings.Split(text, ":")
  158. if len(parts) != 3 {
  159. return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
  160. }
  161. if parts[0] == username || username == "ALL" {
  162. startid, err := strconv.Atoi(parts[1])
  163. if err != nil {
  164. return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
  165. }
  166. length, err := strconv.Atoi(parts[2])
  167. if err != nil {
  168. return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
  169. }
  170. rangeList = append(rangeList, subIDRange{startid, length})
  171. }
  172. }
  173. return rangeList, nil
  174. }