idtools.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. package idtools // import "github.com/docker/docker/pkg/idtools"
  2. import (
  3. "bufio"
  4. "fmt"
  5. "os"
  6. "strconv"
  7. "strings"
  8. )
  9. // IDMap contains a single entry for user namespace range remapping. An array
  10. // of IDMap entries represents the structure that will be provided to the Linux
  11. // kernel for creating a user namespace.
  12. type IDMap struct {
  13. ContainerID int `json:"container_id"`
  14. HostID int `json:"host_id"`
  15. Size int `json:"size"`
  16. }
  17. type subIDRange struct {
  18. Start int
  19. Length int
  20. }
  21. type ranges []subIDRange
  22. func (e ranges) Len() int { return len(e) }
  23. func (e ranges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
  24. func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start }
  25. const (
  26. subuidFileName = "/etc/subuid"
  27. subgidFileName = "/etc/subgid"
  28. )
  29. // MkdirAllAndChown creates a directory (include any along the path) and then modifies
  30. // ownership to the requested uid/gid. If the directory already exists, this
  31. // function will still change ownership to the requested uid/gid pair.
  32. func MkdirAllAndChown(path string, mode os.FileMode, owner Identity) error {
  33. return mkdirAs(path, mode, owner, true, true)
  34. }
  35. // MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
  36. // If the directory already exists, this function still changes ownership.
  37. // Note that unlike os.Mkdir(), this function does not return IsExist error
  38. // in case path already exists.
  39. func MkdirAndChown(path string, mode os.FileMode, owner Identity) error {
  40. return mkdirAs(path, mode, owner, false, true)
  41. }
  42. // MkdirAllAndChownNew creates a directory (include any along the path) and then modifies
  43. // ownership ONLY of newly created directories to the requested uid/gid. If the
  44. // directories along the path exist, no change of ownership will be performed
  45. func MkdirAllAndChownNew(path string, mode os.FileMode, owner Identity) error {
  46. return mkdirAs(path, mode, owner, true, false)
  47. }
  48. // GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
  49. // If the maps are empty, then the root uid/gid will default to "real" 0/0
  50. func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
  51. uid, err := toHost(0, uidMap)
  52. if err != nil {
  53. return -1, -1, err
  54. }
  55. gid, err := toHost(0, gidMap)
  56. if err != nil {
  57. return -1, -1, err
  58. }
  59. return uid, gid, nil
  60. }
  61. // toContainer takes an id mapping, and uses it to translate a
  62. // host ID to the remapped ID. If no map is provided, then the translation
  63. // assumes a 1-to-1 mapping and returns the passed in id
  64. func toContainer(hostID int, idMap []IDMap) (int, error) {
  65. if idMap == nil {
  66. return hostID, nil
  67. }
  68. for _, m := range idMap {
  69. if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
  70. contID := m.ContainerID + (hostID - m.HostID)
  71. return contID, nil
  72. }
  73. }
  74. return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID)
  75. }
  76. // toHost takes an id mapping and a remapped ID, and translates the
  77. // ID to the mapped host ID. If no map is provided, then the translation
  78. // assumes a 1-to-1 mapping and returns the passed in id #
  79. func toHost(contID int, idMap []IDMap) (int, error) {
  80. if idMap == nil {
  81. return contID, nil
  82. }
  83. for _, m := range idMap {
  84. if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
  85. hostID := m.HostID + (contID - m.ContainerID)
  86. return hostID, nil
  87. }
  88. }
  89. return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
  90. }
  91. // Identity is either a UID and GID pair or a SID (but not both)
  92. type Identity struct {
  93. UID int
  94. GID int
  95. SID string
  96. }
  97. // IdentityMapping contains a mappings of UIDs and GIDs
  98. type IdentityMapping struct {
  99. uids []IDMap
  100. gids []IDMap
  101. }
  102. // NewIdentityMapping takes a requested user and group name and
  103. // using the data from /etc/sub{uid,gid} ranges, creates the
  104. // proper uid and gid remapping ranges for that user/group pair
  105. func NewIdentityMapping(username, groupname string) (*IdentityMapping, error) {
  106. subuidRanges, err := parseSubuid(username)
  107. if err != nil {
  108. return nil, err
  109. }
  110. subgidRanges, err := parseSubgid(groupname)
  111. if err != nil {
  112. return nil, err
  113. }
  114. if len(subuidRanges) == 0 {
  115. return nil, fmt.Errorf("No subuid ranges found for user %q", username)
  116. }
  117. if len(subgidRanges) == 0 {
  118. return nil, fmt.Errorf("No subgid ranges found for group %q", groupname)
  119. }
  120. return &IdentityMapping{
  121. uids: createIDMap(subuidRanges),
  122. gids: createIDMap(subgidRanges),
  123. }, nil
  124. }
  125. // NewIDMappingsFromMaps creates a new mapping from two slices
  126. // Deprecated: this is a temporary shim while transitioning to IDMapping
  127. func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IdentityMapping {
  128. return &IdentityMapping{uids: uids, gids: gids}
  129. }
  130. // RootPair returns a uid and gid pair for the root user. The error is ignored
  131. // because a root user always exists, and the defaults are correct when the uid
  132. // and gid maps are empty.
  133. func (i *IdentityMapping) RootPair() Identity {
  134. uid, gid, _ := GetRootUIDGID(i.uids, i.gids)
  135. return Identity{UID: uid, GID: gid}
  136. }
  137. // ToHost returns the host UID and GID for the container uid, gid.
  138. // Remapping is only performed if the ids aren't already the remapped root ids
  139. func (i *IdentityMapping) ToHost(pair Identity) (Identity, error) {
  140. var err error
  141. target := i.RootPair()
  142. if pair.UID != target.UID {
  143. target.UID, err = toHost(pair.UID, i.uids)
  144. if err != nil {
  145. return target, err
  146. }
  147. }
  148. if pair.GID != target.GID {
  149. target.GID, err = toHost(pair.GID, i.gids)
  150. }
  151. return target, err
  152. }
  153. // ToContainer returns the container UID and GID for the host uid and gid
  154. func (i *IdentityMapping) ToContainer(pair Identity) (int, int, error) {
  155. uid, err := toContainer(pair.UID, i.uids)
  156. if err != nil {
  157. return -1, -1, err
  158. }
  159. gid, err := toContainer(pair.GID, i.gids)
  160. return uid, gid, err
  161. }
  162. // Empty returns true if there are no id mappings
  163. func (i *IdentityMapping) Empty() bool {
  164. return len(i.uids) == 0 && len(i.gids) == 0
  165. }
  166. // UIDs return the UID mapping
  167. // TODO: remove this once everything has been refactored to use pairs
  168. func (i *IdentityMapping) UIDs() []IDMap {
  169. return i.uids
  170. }
  171. // GIDs return the UID mapping
  172. // TODO: remove this once everything has been refactored to use pairs
  173. func (i *IdentityMapping) GIDs() []IDMap {
  174. return i.gids
  175. }
  176. func createIDMap(subidRanges ranges) []IDMap {
  177. idMap := []IDMap{}
  178. containerID := 0
  179. for _, idrange := range subidRanges {
  180. idMap = append(idMap, IDMap{
  181. ContainerID: containerID,
  182. HostID: idrange.Start,
  183. Size: idrange.Length,
  184. })
  185. containerID = containerID + idrange.Length
  186. }
  187. return idMap
  188. }
  189. func parseSubuid(username string) (ranges, error) {
  190. return parseSubidFile(subuidFileName, username)
  191. }
  192. func parseSubgid(username string) (ranges, error) {
  193. return parseSubidFile(subgidFileName, username)
  194. }
  195. // parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid)
  196. // and return all found ranges for a specified username. If the special value
  197. // "ALL" is supplied for username, then all ranges in the file will be returned
  198. func parseSubidFile(path, username string) (ranges, error) {
  199. var rangeList ranges
  200. subidFile, err := os.Open(path)
  201. if err != nil {
  202. return rangeList, err
  203. }
  204. defer subidFile.Close()
  205. s := bufio.NewScanner(subidFile)
  206. for s.Scan() {
  207. if err := s.Err(); err != nil {
  208. return rangeList, err
  209. }
  210. text := strings.TrimSpace(s.Text())
  211. if text == "" || strings.HasPrefix(text, "#") {
  212. continue
  213. }
  214. parts := strings.Split(text, ":")
  215. if len(parts) != 3 {
  216. return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
  217. }
  218. if parts[0] == username || username == "ALL" {
  219. startid, err := strconv.Atoi(parts[1])
  220. if err != nil {
  221. return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
  222. }
  223. length, err := strconv.Atoi(parts[2])
  224. if err != nil {
  225. return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
  226. }
  227. rangeList = append(rangeList, subIDRange{startid, length})
  228. }
  229. }
  230. return rangeList, nil
  231. }