6
6
import shlex
7
7
import shutil
8
8
import stat
9
+ import subprocess
9
10
import wbclient
10
11
from pathlib import Path
11
12
from collections import defaultdict
46
47
UserShellChoicesResult ,
47
48
UserUpdateArgs ,
48
49
UserUpdateResult ,
50
+ UserMigrateRootArgs ,
51
+ UserMigrateRootResult
49
52
)
50
53
from middlewared .service import CallError , CRUDService , ValidationErrors , pass_app , private , job
51
54
from middlewared .service_exception import MatchNotFound
@@ -1149,6 +1152,132 @@ async def has_local_administrator_set_up(self):
1149
1152
"""
1150
1153
return len (await self .middleware .call ('privilege.local_administrators' )) > 0
1151
1154
1155
+ @api_method (
1156
+ UserMigrateRootArgs , UserMigrateRootResult ,
1157
+ roles = ['ACCOUNT_WRITE' ], audit = 'Migrate root account'
1158
+ )
1159
+ @job (lock = 'migrate_root' )
1160
+ def migrate_root (self , job , data ):
1161
+ """
1162
+ Migrate from root user account to new one with UID 950 and the specified
1163
+ `username`. If this account already exists then we consider migration to
1164
+ have already happened and will fail with CallError and errno set to EEXIST.
1165
+ """
1166
+ username = data ['username' ]
1167
+ verrors = ValidationErrors ()
1168
+ pw_checkname (verrors , 'account_migrate_root.username' , username )
1169
+ verrors .check ()
1170
+
1171
+ root_user = self .middleware .call_sync ('user.query' , [['uid' , '=' , 0 ]], {'get' : True })
1172
+ homedir = f'/home/{ username } '
1173
+
1174
+ if data ['password' ] is not None :
1175
+ password_hash = crypted_password (data ['password' ])
1176
+ else :
1177
+ password_hash = root_user ['unixhash' ]
1178
+
1179
+ try :
1180
+ pwd_obj = self .middleware .call_sync ('user.get_user_obj' , {'uid' : ADMIN_UID })
1181
+ raise CallError (
1182
+ f'A { pwd_obj ["source" ].lower ()} user with uid={ ADMIN_UID } already exists, '
1183
+ 'setting up local administrator is not possible' ,
1184
+ errno .EEXIST ,
1185
+ )
1186
+ except KeyError :
1187
+ pass
1188
+
1189
+ try :
1190
+ pwd_obj = self .middleware .call_sync ('user.get_user_obj' , {'username' : username })
1191
+ raise CallError (f'{ username !r} { pwd_obj ["source" ].lower ()} user already exists, '
1192
+ 'setting up local administrator is not possible' ,
1193
+ errno .EEXIST )
1194
+ except KeyError :
1195
+ pass
1196
+
1197
+ try :
1198
+ grp_obj = self .middleware .call_sync ('group.get_group_obj' , {'gid' : ADMIN_GID })
1199
+ raise CallError (
1200
+ f'A { grp_obj ["source" ].lower ()} group with gid={ ADMIN_GID } already exists, '
1201
+ 'setting up local administrator is not possible' ,
1202
+ errno .EEXIST ,
1203
+ )
1204
+ except KeyError :
1205
+ pass
1206
+
1207
+ try :
1208
+ grp_obj = self .middleware .call_sync ('group.get_group_obj' , {'groupname' : username })
1209
+ raise CallError (f'{ username !r} { grp_obj ["source" ].lower ()} group already exists, '
1210
+ 'setting up local administrator is not possible' ,
1211
+ errno .EEXIST )
1212
+ except KeyError :
1213
+ pass
1214
+
1215
+ # double-check our database in case we have for some reason failed to write to passwd
1216
+ local_users = self .middleware .call_sync ('user.query' , [['local' , '=' , True ]])
1217
+ local_groups = self .middleware .call_sync ('group.query' , [['local' , '=' , True ]])
1218
+
1219
+ if filter_list (local_users , [['uid' , '=' , ADMIN_UID ]]):
1220
+ raise CallError (
1221
+ f'A user with uid={ ADMIN_UID } already exists, setting up local administrator is not possible' ,
1222
+ errno .EEXIST ,
1223
+ )
1224
+
1225
+ if filter_list (local_users , [['username' , '=' , username ]]):
1226
+ raise CallError (f'{ username !r} user already exists, setting up local administrator is not possible' ,
1227
+ errno .EEXIST )
1228
+
1229
+ if filter_list (local_groups , [['gid' , '=' , ADMIN_GID ]]):
1230
+ raise CallError (
1231
+ f'A group with gid={ ADMIN_GID } already exists, setting up local administrator is not possible' ,
1232
+ errno .EEXIST ,
1233
+ )
1234
+
1235
+ if filter_list (local_groups , [['group' , '=' , username ]]):
1236
+ raise CallError (f'{ username !r} group already exists, setting up local administrator is not possible' ,
1237
+ errno .EEXIST )
1238
+
1239
+ subprocess .run (
1240
+ ['truenas-set-authentication-method.py' ],
1241
+ check = True , encoding = 'utf-8' , errors = 'ignore' ,
1242
+ input = json .dumps ({'username' : username , 'password' : password_hash })
1243
+ )
1244
+ new_user = self .middleware .call_sync ('user.query' , [['uid' , '=' , ADMIN_UID ]], {'get' : True })
1245
+
1246
+ self .middleware .call_sync ('failover.datastore.force_send' )
1247
+ self .middleware .call_sync ('etc.generate' , 'user' )
1248
+
1249
+ # Set up homedir for new admin user
1250
+ try :
1251
+ os .mkdir (homedir , 0o700 )
1252
+ except FileExistsError :
1253
+ pass
1254
+
1255
+ os .chown (homedir , ADMIN_UID , ADMIN_GID )
1256
+ os .chmod (homedir , 0o700 )
1257
+ home_copy_job = self .middleware .call_sync ('user.do_home_copy' , '/root' , homedir , '700' , ADMIN_UID )
1258
+ home_copy_job .wait_sync ()
1259
+
1260
+ # Update new user account with settings from root
1261
+ self .middleware .call_sync ('user.update' , new_user ['id' ], {
1262
+ 'ssh_password_enabled' : root_user ['ssh_password_enabled' ],
1263
+ 'sshpubkey' : root_user ['sshpubkey' ],
1264
+ 'email' : root_user ['email' ],
1265
+ 'shell' : root_user ['shell' ],
1266
+ })
1267
+
1268
+ # Preserve root twofactor settings
1269
+ if root_user ['twofactor_auth_configured' ]:
1270
+ # get twofactor config for UID 0 and copy it over to 950
1271
+ twofactor_data = self .middleware .call_sync ('datastore.query' , 'account.twofactor_user_auth' )
1272
+ root_twofactor = filter_list (twofactor_data , [['user.bsdusr_uid' , '=' , 0 ]], {'get' : True })
1273
+ target = filter_list (twofactor_data , [['user.bsdusr_uid' , '=' , ADMIN_UID ]], {'get' : True })['id' ]
1274
+
1275
+ self .middleware .call_sync ('datastore.update' , 'account.twofactor_user_auth' , target , {
1276
+ 'secret' : root_twofactor ['secret' ],
1277
+ 'otp_digits' : root_twofactor ['otp_digits' ],
1278
+ 'interval' : root_twofactor ['interval' ],
1279
+ })
1280
+
1152
1281
@api_method (
1153
1282
UserSetupLocalAdministratorArgs , UserSetupLocalAdministratorResult ,
1154
1283
audit = 'Set up local administrator' ,
@@ -1164,69 +1293,14 @@ async def setup_local_administrator(self, app, username, password, options):
1164
1293
raise CallError ('Local administrator is already set up' , errno .EEXIST )
1165
1294
1166
1295
if username == 'truenas_admin' :
1167
- # first check based on NSS to catch collisions with AD / LDAP users
1168
- try :
1169
- pwd_obj = await self .middleware .call ('user.get_user_obj' , {'uid' : ADMIN_UID })
1170
- raise CallError (
1171
- f'A { pwd_obj ["source" ].lower ()} user with uid={ ADMIN_UID } already exists, '
1172
- 'setting up local administrator is not possible' ,
1173
- errno .EEXIST ,
1174
- )
1175
- except KeyError :
1176
- pass
1177
-
1178
- try :
1179
- pwd_obj = await self .middleware .call ('user.get_user_obj' , {'username' : username })
1180
- raise CallError (f'{ username !r} { pwd_obj ["source" ].lower ()} user already exists, '
1181
- 'setting up local administrator is not possible' ,
1182
- errno .EEXIST )
1183
- except KeyError :
1184
- pass
1185
-
1186
- try :
1187
- grp_obj = await self .middleware .call ('group.get_group_obj' , {'gid' : ADMIN_GID })
1188
- raise CallError (
1189
- f'A { grp_obj ["source" ].lower ()} group with gid={ ADMIN_GID } already exists, '
1190
- 'setting up local administrator is not possible' ,
1191
- errno .EEXIST ,
1192
- )
1193
- except KeyError :
1194
- pass
1195
-
1196
- try :
1197
- grp_obj = await self .middleware .call ('group.get_group_obj' , {'groupname' : username })
1198
- raise CallError (f'{ username !r} { grp_obj ["source" ].lower ()} group already exists, '
1199
- 'setting up local administrator is not possible' ,
1200
- errno .EEXIST )
1201
- except KeyError :
1202
- pass
1203
-
1204
- # double-check our database in case we have for some reason failed to write to passwd
1205
- local_users = await self .middleware .call ('user.query' , [['local' , '=' , True ]])
1206
- local_groups = await self .middleware .call ('group.query' , [['local' , '=' , True ]])
1207
-
1208
- if filter_list (local_users , [['uid' , '=' , ADMIN_UID ]]):
1209
- raise CallError (
1210
- f'A user with uid={ ADMIN_UID } already exists, setting up local administrator is not possible' ,
1211
- errno .EEXIST ,
1212
- )
1213
-
1214
- if filter_list (local_users , [['username' , '=' , username ]]):
1215
- raise CallError (f'{ username !r} user already exists, setting up local administrator is not possible' ,
1216
- errno .EEXIST )
1217
-
1218
- if filter_list (local_groups , [['gid' , '=' , ADMIN_GID ]]):
1219
- raise CallError (
1220
- f'A group with gid={ ADMIN_GID } already exists, setting up local administrator is not possible' ,
1221
- errno .EEXIST ,
1222
- )
1223
-
1224
- if filter_list (local_groups , [['group' , '=' , username ]]):
1225
- raise CallError (f'{ username !r} group already exists, setting up local administrator is not possible' ,
1226
- errno .EEXIST )
1296
+ # This should be relatively invexpensive even though it's a job since we
1297
+ # don't expect /root to have much in the way of contents.
1298
+ migrate_job = await self .middleware .call ('user.migrate_root' , {'username' : username , 'password' : password })
1299
+ await migrate_job .wait (raise_error = True )
1300
+ return
1227
1301
1228
1302
await run ('truenas-set-authentication-method.py' , check = True , encoding = 'utf-8' , errors = 'ignore' ,
1229
- input = json .dumps ({'username' : username , 'password' : password }))
1303
+ input = json .dumps ({'username' : username , 'password' : crypted_password ( password ) }))
1230
1304
await self .middleware .call ('failover.datastore.force_send' )
1231
1305
await self .middleware .call ('etc.generate' , 'user' )
1232
1306
0 commit comments