summaryrefslogtreecommitdiffstats
path: root/meta/classes/useradd-staticids.bbclass
diff options
context:
space:
mode:
authorMark Hatle <mark.hatle@windriver.com>2014-02-06 17:37:24 -0600
committerRichard Purdie <richard.purdie@linuxfoundation.org>2014-02-09 09:40:41 +0000
commit4bc6982ea60929226bcd9d206b4879dea2a3157c (patch)
tree75a92e0de8521fcca84235bcfdc760e83b07c8b9 /meta/classes/useradd-staticids.bbclass
parent72288dd499d236071a5216c9a9e3b6bd66c3888a (diff)
downloadpoky-4bc6982ea60929226bcd9d206b4879dea2a3157c.tar.gz
useradd.bbclass: Add ability to select a static uid/gid automatically
[YOCTO #5436] Automatic selection of static uid/gid is needed for a dynamically generated passwd and group file to have a deterministic outcome. When a package is installed and instructs the system to add a new user or group, unless it selects a static uid/gid value, the next available uid/gid will be used. The order in which packages are installed is dynamically computed, and may change from one installation to the next. This results in a non-deterministic set of uid/gid values. Enabling this code by adding USERADDEXTENSION = "useradd-staticids", and adding a preconfigured passwd/group file will allow the continued dynamic generation of the rootfs passwd/group files, but will ensure a deterministic outcome. (Dynamic generation is desired so that users and groups that have no corresponding functionality are not present within the final system image.) The rewrite params function will override each of the fields in the useradd and groupadd calls with the values specified. Note, the password field is ignored as is the member groups field in the group file. If the field is empty, the value will not be overridden. (Note, there is no way to 'blank' a field, as this would only generally affect the 'comment' field and there really is no reason to blank it.) Enabling USERADD_ERROR_DYNAMIC will cause packages without static uid/gid to generate an error and be skipped for the purpose of building. This is used to prevent non-deterministic behavior. USERADD_UID_TABLES and USERADD_GID_TABLES may be used to specify the name of the passwd and group files. By default they are assumed to be 'files/passwd' and 'files/group'. Layers are searched in BBPATH order. (From OE-Core rev: 18c99dac52b746b88cd084eb4c2a2ef0329a6ff3) Signed-off-by: Mark Hatle <mark.hatle@windriver.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/classes/useradd-staticids.bbclass')
-rw-r--r--meta/classes/useradd-staticids.bbclass259
1 files changed, 259 insertions, 0 deletions
diff --git a/meta/classes/useradd-staticids.bbclass b/meta/classes/useradd-staticids.bbclass
new file mode 100644
index 0000000000..689c29c53f
--- /dev/null
+++ b/meta/classes/useradd-staticids.bbclass
@@ -0,0 +1,259 @@
1# In order to support a deterministic set of 'dynamic' users/groups,
2# we need a function to reformat the params based on a static file
3def update_useradd_static_config(d):
4 import argparse
5 import re
6
7 class myArgumentParser( argparse.ArgumentParser ):
8 def _print_message(self, message, file=None):
9 bb.warn("%s - %s: %s" % (d.getVar('PN', True), pkg, message))
10
11 # This should never be called...
12 def exit(self, status=0, message=None):
13 message = message or ("%s - %s: useradd.bbclass: Argument parsing exited" % (d.getVar('PN', True), pkg))
14 error(message)
15
16 def error(self, message):
17 raise bb.build.FuncFailed(message)
18
19 # We parse and rewrite the useradd components
20 def rewrite_useradd(params):
21 # The following comes from --help on useradd from shadow
22 parser = myArgumentParser(prog='useradd')
23 parser.add_argument("-b", "--base-dir", metavar="BASE_DIR", help="base directory for the home directory of the new account")
24 parser.add_argument("-c", "--comment", metavar="COMMENT", help="GECOS field of the new account")
25 parser.add_argument("-d", "--home-dir", metavar="HOME_DIR", help="home directory of the new account")
26 parser.add_argument("-D", "--defaults", help="print or change default useradd configuration", action="store_true")
27 parser.add_argument("-e", "--expiredate", metavar="EXPIRE_DATE", help="expiration date of the new account")
28 parser.add_argument("-f", "--inactive", metavar="INACTIVE", help="password inactivity period of the new account")
29 parser.add_argument("-g", "--gid", metavar="GROUP", help="name or ID of the primary group of the new account")
30 parser.add_argument("-G", "--groups", metavar="GROUPS", help="list of supplementary groups of the new account")
31 parser.add_argument("-k", "--skel", metavar="SKEL_DIR", help="use this alternative skeleton directory")
32 parser.add_argument("-K", "--key", metavar="KEY=VALUE", help="override /etc/login.defs defaults")
33 parser.add_argument("-l", "--no-log-init", help="do not add the user to the lastlog and faillog databases", action="store_true")
34 parser.add_argument("-m", "--create-home", help="create the user's home directory", action="store_true")
35 parser.add_argument("-M", "--no-create-home", help="do not create the user's home directory", action="store_true")
36 parser.add_argument("-N", "--no-user-group", help="do not create a group with the same name as the user", action="store_true")
37 parser.add_argument("-o", "--non-unique", help="allow to create users with duplicate (non-unique UID)", action="store_true")
38 parser.add_argument("-p", "--password", metavar="PASSWORD", help="encrypted password of the new account")
39 parser.add_argument("-R", "--root", metavar="CHROOT_DIR", help="directory to chroot into")
40 parser.add_argument("-r", "--system", help="create a system account", action="store_true")
41 parser.add_argument("-s", "--shell", metavar="SHELL", help="login shell of the new account")
42 parser.add_argument("-u", "--uid", metavar="UID", help="user ID of the new account")
43 parser.add_argument("-U", "--user-group", help="create a group with the same name as the user", action="store_true")
44 parser.add_argument("LOGIN", help="Login name of the new user")
45
46 # Return a list of configuration files based on either the default
47 # files/passwd or the contents of USERADD_UID_TABLES
48 # paths are resulved via BBPATH
49 def get_passwd_list(d):
50 str = ""
51 bbpath = d.getVar('BBPATH', True)
52 passwd_tables = d.getVar('USERADD_UID_TABLES', True)
53 if not passwd_tables:
54 passwd_tables = 'files/passwd'
55 for conf_file in passwd_tables.split():
56 str += " %s" % bb.utils.which(bbpath, conf_file)
57 return str
58
59 newparams = []
60 for param in re.split('''[ \t]*;[ \t]*(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', params):
61 param=param.strip()
62 try:
63 uaargs = parser.parse_args(re.split('''[ \t]*(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', param))
64 except:
65 raise bb.build.FuncFailed("%s: Unable to parse arguments for USERADD_PARAM_%s: '%s'" % (d.getVar('PN', True), pkg, param))
66
67 # files/passwd or the contents of USERADD_UID_TABLES
68 # Use the standard passwd layout:
69 # username:password:user_id:group_id:comment:home_directory:login_shell
70 # (we want to process in reverse order, as 'last found' in the list wins)
71 #
72 # If a field is left blank, the original value will be used. The 'username'
73 # field is required.
74 #
75 # Note: we ignore the password field, as including even the hashed password
76 # in the useradd command may introduce a security hole. It's assumed that
77 # all new users get the default ('*' which prevents login) until the user is
78 # specifically configured by the system admin.
79 for conf in get_passwd_list(d).split()[::-1]:
80 if os.path.exists(conf):
81 f = open(conf, "r")
82 for line in f:
83 if line.startswith('#'):
84 continue
85 field = line.rstrip().split(":")
86 if field[0] == uaargs.LOGIN:
87 if uaargs.uid and field[2] and (uaargs.uid != field[2]):
88 bb.warn("%s: Changing username %s's uid from (%s) to (%s), verify configuration files!" % (d.getVar('PN', True), uaargs.LOGIN, uaargs.uid, field[2]))
89 uaargs.uid = [field[2], uaargs.uid][not field[2]]
90
91 # Determine the possible groupname
92 # Unless the group name (or gid) is specified, we assume that the LOGIN is the groupname
93 #
94 # By default the system has creation of the matching groups enabled
95 # So if the implicit username-group creation is on, then the implicit groupname (LOGIN)
96 # is used, and we disable the user_group option.
97 #
98 uaargs.groupname = [uaargs.gid, uaargs.LOGIN][not uaargs.gid or uaargs.user_group]
99 uaargs.user_group = False
100
101 uaargs.gid = [uaargs.gid, uaargs.groupname][not uaargs.gid]
102 uaargs.gid = [field[3], uaargs.gid][not field[3]]
103
104 if uaargs.groupname == uaargs.gid:
105 # Nothing to do...
106 pass
107 elif (uaargs.groupname and uaargs.groupname.isdigit()) and (uaargs.gid and uaargs.gid.isdigit()) and (uaargs.groupname != uaargs.gid):
108 # We want to add a group, but we don't know it's name... so we can't add the group...
109 # We have to assume the group has previously been added or we'll fail on the adduser...
110 # Note: specifying the actual gid is very rare in OE, usually the group name is specified.
111 bb.warn("%s: Changing gid for login %s from (%s) to (%s), verify configuration files!" % (d.getVar('PN', True), uaargs.LOGIN, uaargs.groupname, uaargs.gid))
112 elif uaargs.groupname and (uaargs.gid and uaargs.gid.isdigit()):
113 bb.debug(1, "Adding group %s gid (%s)!" % (uaargs.groupname, uaargs.gid))
114 groupadd = d.getVar("GROUPADD_PARAM_%s" % pkg, True)
115 newgroup = "-g %s %s" % (uaargs.gid, uaargs.groupname)
116 if groupadd:
117 d.setVar("GROUPADD_PARAM_%s" % pkg, "%s ; %s" % (groupadd, newgroup))
118 else:
119 d.setVar("GROUPADD_PARAM_%s" % pkg, newgroup)
120
121 uaargs.comment = ["'%s'" % field[4], uaargs.comment][not field[4]]
122 uaargs.home_dir = [field[5], uaargs.home_dir][not field[5]]
123 uaargs.shell = [field[6], uaargs.shell][not field[6]]
124 break
125
126 # Should be an error if a specific option is set...
127 if d.getVar('USERADD_ERROR_DYNAMIC', True) == '1' and (not uaargs.uid or not uaargs.gid):
128 raise bb.build.FuncFailed("%s - %s: Username %s does not have a static uid/gid defined." % (d.getVar('PN', True), pkg, uaargs.LOGIN))
129
130 # Reconstruct the args...
131 newparam = ['', ' --defaults'][uaargs.defaults]
132 newparam += ['', ' --base-dir %s' % uaargs.base_dir][uaargs.base_dir != None]
133 newparam += ['', ' --comment %s' % uaargs.comment][uaargs.comment != None]
134 newparam += ['', ' --home-dir %s' % uaargs.home_dir][uaargs.home_dir != None]
135 newparam += ['', ' --expiredata %s' % uaargs.expiredate][uaargs.expiredate != None]
136 newparam += ['', ' --inactive %s' % uaargs.inactive][uaargs.inactive != None]
137 newparam += ['', ' --gid %s' % uaargs.gid][uaargs.gid != None]
138 newparam += ['', ' --groups %s' % uaargs.groups][uaargs.groups != None]
139 newparam += ['', ' --skel %s' % uaargs.skel][uaargs.skel != None]
140 newparam += ['', ' --key %s' % uaargs.key][uaargs.key != None]
141 newparam += ['', ' --no-log-init'][uaargs.no_log_init]
142 newparam += ['', ' --create-home'][uaargs.create_home]
143 newparam += ['', ' --no-create-home'][uaargs.no_create_home]
144 newparam += ['', ' --no-user-group'][uaargs.no_user_group]
145 newparam += ['', ' --non-unique'][uaargs.non_unique]
146 newparam += ['', ' --password %s' % uaargs.password][uaargs.password != None]
147 newparam += ['', ' --root %s' % uaargs.root][uaargs.root != None]
148 newparam += ['', ' --system'][uaargs.system]
149 newparam += ['', ' --shell %s' % uaargs.shell][uaargs.shell != None]
150 newparam += ['', ' --uid %s' % uaargs.uid][uaargs.uid != None]
151 newparam += ['', ' --user-group'][uaargs.user_group]
152 newparam += ' %s' % uaargs.LOGIN
153
154 newparams.append(newparam)
155
156 return " ;".join(newparams).strip()
157
158 # We parse and rewrite the groupadd components
159 def rewrite_groupadd(params):
160 # The following comes from --help on groupadd from shadow
161 parser = myArgumentParser(prog='groupadd')
162 parser.add_argument("-f", "--force", help="exit successfully if the group already exists, and cancel -g if the GID is already used", action="store_true")
163 parser.add_argument("-g", "--gid", metavar="GID", help="use GID for the new group")
164 parser.add_argument("-K", "--key", metavar="KEY=VALUE", help="override /etc/login.defs defaults")
165 parser.add_argument("-o", "--non-unique", help="allow to create groups with duplicate (non-unique) GID", action="store_true")
166 parser.add_argument("-p", "--password", metavar="PASSWORD", help="use this encrypted password for the new group")
167 parser.add_argument("-R", "--root", metavar="CHROOT_DIR", help="directory to chroot into")
168 parser.add_argument("-r", "--system", help="create a system account", action="store_true")
169 parser.add_argument("GROUP", help="Group name of the new group")
170
171 # Return a list of configuration files based on either the default
172 # files/group or the contents of USERADD_GID_TABLES
173 # paths are resulved via BBPATH
174 def get_group_list(d):
175 str = ""
176 bbpath = d.getVar('BBPATH', True)
177 group_tables = d.getVar('USERADD_GID_TABLES', True)
178 if not group_tables:
179 group_tables = 'files/group'
180 for conf_file in group_tables.split():
181 str += " %s" % bb.utils.which(bbpath, conf_file)
182 return str
183
184 newparams = []
185 for param in re.split('''[ \t]*;[ \t]*(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', params):
186 param=param.strip()
187 try:
188 # If we're processing multiple lines, we could have left over values here...
189 gaargs = parser.parse_args(re.split('''[ \t]*(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', param))
190 except:
191 raise bb.build.FuncFailed("%s: Unable to parse arguments for GROUPADD_PARAM_%s: '%s'" % (d.getVar('PN', True), pkg, param))
192
193 # Need to iterate over layers and open the right file(s)
194 # Use the standard group layout:
195 # groupname:password:group_id:group_members
196 #
197 # If a field is left blank, the original value will be used. The 'groupname' field
198 # is required.
199 #
200 # Note: similar to the passwd file, the 'password' filed is ignored
201 # Note: group_members is ignored, group members must be configured with the GROUPMEMS_PARAM
202 for conf in get_group_list(d).split()[::-1]:
203 if os.path.exists(conf):
204 f = open(conf, "r")
205 for line in f:
206 if line.startswith('#'):
207 continue
208 field = line.rstrip().split(":")
209 if field[0] == gaargs.GROUP and field[2]:
210 if gaargs.gid and (gaargs.gid != field[2]):
211 bb.warn("%s: Changing groupname %s's gid from (%s) to (%s), verify configuration files!" % (d.getVar('PN', True), gaargs.GROUP, gaargs.gid, field[2]))
212 gaargs.gid = field[2]
213 break
214
215 if d.getVar('USERADD_ERROR_DYNAMIC', True) == '1' and not gaargs.gid:
216 raise bb.build.FuncFailed("%s - %s: Groupname %s does not have a static gid defined." % (d.getVar('PN', True), pkg, gaargs.GROUP))
217
218 # Reconstruct the args...
219 newparam = ['', ' --force'][gaargs.force]
220 newparam += ['', ' --gid %s' % gaargs.gid][gaargs.gid != None]
221 newparam += ['', ' --key %s' % gaargs.key][gaargs.key != None]
222 newparam += ['', ' --non-unique'][gaargs.non_unique]
223 newparam += ['', ' --password %s' % gaargs.password][gaargs.password != None]
224 newparam += ['', ' --root %s' % gaargs.root][gaargs.root != None]
225 newparam += ['', ' --system'][gaargs.system]
226 newparam += ' %s' % gaargs.GROUP
227
228 newparams.append(newparam)
229
230 return " ;".join(newparams).strip()
231
232 # Load and process the users and groups, rewriting the adduser/addgroup params
233 useradd_packages = d.getVar('USERADD_PACKAGES', True)
234
235 for pkg in useradd_packages.split():
236 # Groupmems doesn't have anything we might want to change, so simply validating
237 # is a bit of a waste -- only process useradd/groupadd
238 useradd_param = d.getVar('USERADD_PARAM_%s' % pkg, True)
239 if useradd_param:
240 #bb.warn("Before: 'USERADD_PARAM_%s' - '%s'" % (pkg, useradd_param))
241 d.setVar('USERADD_PARAM_%s' % pkg, rewrite_useradd(useradd_param))
242 #bb.warn("After: 'USERADD_PARAM_%s' - '%s'" % (pkg, d.getVar('USERADD_PARAM_%s' % pkg, True)))
243
244 groupadd_param = d.getVar('GROUPADD_PARAM_%s' % pkg, True)
245 if groupadd_param:
246 #bb.warn("Before: 'GROUPADD_PARAM_%s' - '%s'" % (pkg, groupadd_param))
247 d.setVar('GROUPADD_PARAM_%s' % pkg, rewrite_groupadd(groupadd_param))
248 #bb.warn("After: 'GROUPADD_PARAM_%s' - '%s'" % (pkg, d.getVar('GROUPADD_PARAM_%s' % pkg, True)))
249
250
251
252python __anonymous() {
253 if not bb.data.inherits_class('nativesdk', d):
254 try:
255 update_useradd_static_config(d)
256 except bb.build.FuncFailed as f:
257 bb.debug(1, "Skipping recipe %s: %s" % (d.getVar('PN', True), f))
258 raise bb.parse.SkipPackage(f)
259}