summaryrefslogtreecommitdiff
path: root/pkg/lookup/lookup.go
blob: a249dd753c73d6fb68132e4e42704edb4d452c9c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package lookup

import (
	"os"
	"strconv"

	"github.com/cyphar/filepath-securejoin"
	"github.com/opencontainers/runc/libcontainer/user"
	"github.com/sirupsen/logrus"
)

const (
	etcpasswd = "/etc/passwd"
	etcgroup  = "/etc/group"
)

// Overrides allows you to override defaults in GetUserGroupInfo
type Overrides struct {
	DefaultUser            *user.ExecUser
	ContainerEtcPasswdPath string
	ContainerEtcGroupPath  string
}

// GetUserGroupInfo takes string forms of the the container's mount path and the container user and
// returns a ExecUser with uid, gid, sgids, and home.  And override can be provided for defaults.
func GetUserGroupInfo(containerMount, containerUser string, override *Overrides) (*user.ExecUser, error) {
	var (
		passwdDest, groupDest string
		defaultExecUser       *user.ExecUser
		err                   error
	)

	if override != nil {
		// Check for an override /etc/passwd path
		if override.ContainerEtcPasswdPath != "" {
			passwdDest = override.ContainerEtcPasswdPath
		}
		// Check for an override for /etc/group path
		if override.ContainerEtcGroupPath != "" {
			groupDest = override.ContainerEtcGroupPath
		}
	}

	if passwdDest == "" {
		// Make sure the /etc/passwd destination is not a symlink to something naughty
		if passwdDest, err = securejoin.SecureJoin(containerMount, etcpasswd); err != nil {
			logrus.Debug(err)
			return nil, err
		}
	}
	if groupDest == "" {
		// Make sure the /etc/group destination is not a symlink to something naughty
		if groupDest, err = securejoin.SecureJoin(containerMount, etcgroup); err != nil {
			logrus.Debug(err)
			return nil, err
		}
	}

	// Check for an override default user
	if override != nil && override.DefaultUser != nil {
		defaultExecUser = override.DefaultUser
	} else {
		// Define a default container user
		//defaultExecUser = &user.ExecUser{
		//	Uid:  0,
		//	Gid:  0,
		//	Home: "/",
		defaultExecUser = nil

	}

	return user.GetExecUserPath(containerUser, defaultExecUser, passwdDest, groupDest)
}

// GetContainerGroups uses securejoin to get a list of numerical groupids from a container. Per the runc
// function it calls: If a group name cannot be found, an error will be returned. If a group id cannot be found,
// or the given group data is nil, the id will be returned as-is  provided it is in the legal range.
func GetContainerGroups(groups []string, containerMount string, override *Overrides) ([]uint32, error) {
	var (
		groupDest string
		err       error
		uintgids  []uint32
	)

	groupPath := etcgroup
	if override != nil && override.ContainerEtcGroupPath != "" {
		groupPath = override.ContainerEtcGroupPath
	}

	if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil {
		logrus.Debug(err)
		return nil, err
	}

	gids, err := user.GetAdditionalGroupsPath(groups, groupDest)
	if err != nil {
		return nil, err
	}
	// For libpod, we want []uint32s
	for _, gid := range gids {
		uintgids = append(uintgids, uint32(gid))
	}
	return uintgids, nil
}

// GetUser takes a containermount path and user name or ID and returns
// a matching User structure from /etc/passwd.  If it cannot locate a user
// with the provided information, an ErrNoPasswdEntries is returned.
// When the provided user name was an ID, a User structure with Uid
// set is returned along with ErrNoPasswdEntries.
func GetUser(containerMount, userIDorName string) (*user.User, error) {
	var inputIsName bool
	uid, err := strconv.Atoi(userIDorName)
	if err != nil {
		inputIsName = true
	}
	passwdDest, err := securejoin.SecureJoin(containerMount, etcpasswd)
	if err != nil {
		return nil, err
	}
	users, err := user.ParsePasswdFileFilter(passwdDest, func(u user.User) bool {
		if inputIsName {
			return u.Name == userIDorName
		}
		return u.Uid == uid
	})
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}
	if len(users) > 0 {
		return &users[0], nil
	}
	if !inputIsName {
		return &user.User{Uid: uid}, user.ErrNoPasswdEntries
	}
	return nil, user.ErrNoPasswdEntries
}

// GetGroup takes a containermount path and a group name or ID and returns
// a match Group struct from /etc/group.  If it cannot locate a group,
// an ErrNoGroupEntries error is returned.  When the provided group name
// was an ID, a Group structure with Gid set is returned along with
// ErrNoGroupEntries.
func GetGroup(containerMount, groupIDorName string) (*user.Group, error) {
	var inputIsName bool
	gid, err := strconv.Atoi(groupIDorName)
	if err != nil {
		inputIsName = true
	}

	groupDest, err := securejoin.SecureJoin(containerMount, etcgroup)
	if err != nil {
		return nil, err
	}

	groups, err := user.ParseGroupFileFilter(groupDest, func(g user.Group) bool {
		if inputIsName {
			return g.Name == groupIDorName
		}
		return g.Gid == gid
	})
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}
	if len(groups) > 0 {
		return &groups[0], nil
	}
	if !inputIsName {
		return &user.Group{Gid: gid}, user.ErrNoGroupEntries
	}
	return nil, user.ErrNoGroupEntries
}