Get root password for build image from settings

Image building process reads from settings hashed version of root
password and apply it instead of default.

Conflicts:

	bareon/drivers/data/nailgun.py
	bareon/drivers/deploy/nailgun.py
	bareon/utils/utils.py
	fuel_agent/tests/test_manager.py

Change-Id: Ibb614ddd1973c8fae25dae8217d207ffc92f1b15
Partial-Bug: #1537496
Depends-On: I2092bfca78fb721a8df3c8c6e4e6fd18e64ba353
This commit is contained in:
Nikita Zubkov 2016-02-08 14:47:06 +03:00 committed by Alexander Gordeev
parent b6f7ee6935
commit 10687832f2
12 changed files with 113 additions and 15 deletions

View File

@ -42,6 +42,7 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF CONF = cfg.CONF
CONF.import_opt('prepare_configdrive', 'bareon.drivers.deploy.nailgun') CONF.import_opt('prepare_configdrive', 'bareon.drivers.deploy.nailgun')
CONF.import_opt('config_drive_path', 'bareon.drivers.deploy.nailgun') CONF.import_opt('config_drive_path', 'bareon.drivers.deploy.nailgun')
CONF.import_opt('default_root_password', 'bareon.drivers.deploy.nailgun')
def match_device(hu_disk, ks_disk): def match_device(hu_disk, ks_disk):
@ -827,6 +828,21 @@ class NailgunBuildImage(BaseDataDriver,
os = objects.Ubuntu(repos=repos, packages=packages, major=14, minor=4, os = objects.Ubuntu(repos=repos, packages=packages, major=14, minor=4,
proxies=proxies) proxies=proxies)
# add root account
root_password = self.data.get('root_password')
hashed_root_password = self.data.get('hashed_root_password')
# for backward compatibily set default password is no password provided
if root_password is None and hashed_root_password is None:
root_password = CONF.default_root_password
os.add_user_account(
name='root',
password=root_password,
homedir='/root',
hashed_password=hashed_root_password,
)
return os return os
def parse_schemes(self): def parse_schemes(self):

View File

@ -108,6 +108,11 @@ opts = [
default=True, default=True,
help='Add udev rules for NIC remapping' help='Add udev rules for NIC remapping'
), ),
cfg.StrOpt(
'default_root_password',
default='r00tme',
help='Default password for root user',
)
] ]
cli_opts = [ cli_opts = [
@ -492,7 +497,10 @@ class Manager(BaseDeployDriver):
'etc/nailgun-agent/config.yaml')) 'etc/nailgun-agent/config.yaml'))
bu.append_lvm_devices_filter(chroot, CONF.multipath_lvm_filter, bu.append_lvm_devices_filter(chroot, CONF.multipath_lvm_filter,
CONF.lvm_conf_path) CONF.lvm_conf_path)
root = driver_os.get_user_by_name('root')
bu.do_post_inst(chroot, bu.do_post_inst(chroot,
hashed_root_password=root.hashed_password,
allow_unsigned_file=CONF.allow_unsigned_file, allow_unsigned_file=CONF.allow_unsigned_file,
force_ipv4_file=CONF.force_ipv4_file) force_ipv4_file=CONF.force_ipv4_file)
# restore disabled hosts/resolv files # restore disabled hosts/resolv files
@ -594,7 +602,9 @@ class Manager(BaseDeployDriver):
attempts=CONF.fetch_packages_attempts) attempts=CONF.fetch_packages_attempts)
LOG.debug('Post-install OS configuration') LOG.debug('Post-install OS configuration')
root = driver_os.get_user_by_name('root')
bu.do_post_inst(chroot, bu.do_post_inst(chroot,
hashed_root_password=root.hashed_password,
allow_unsigned_file=CONF.allow_unsigned_file, allow_unsigned_file=CONF.allow_unsigned_file,
force_ipv4_file=CONF.force_ipv4_file) force_ipv4_file=CONF.force_ipv4_file)

View File

@ -28,6 +28,15 @@ class OperatingSystem(object):
def add_user_account(self, **kwargs): def add_user_account(self, **kwargs):
self.user_accounts.append(users.User(**kwargs)) self.user_accounts.append(users.User(**kwargs))
def get_user_by_name(self, name):
"""Get User object by name from user_accounts.
If there is no users with such name return None
"""
for user in self.user_accounts:
if user.name == name:
return user
def to_dict(self): def to_dict(self):
return {'major': self.major, return {'major': self.major,
'minor': self.minor, 'minor': self.minor,

View File

@ -12,13 +12,28 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import crypt
from bareon.utils import utils
class User(object): class User(object):
def __init__(self, name, password, homedir, sudo=None, ssh_keys=None, def __init__(self, name, password, homedir, sudo=None, ssh_keys=None,
shell="/bin/bash"): shell="/bin/bash", hashed_password=None):
self.name = name self.name = name
self.password = password self.password = password
self.homedir = homedir self.homedir = homedir
self.sudo = sudo or [] self.sudo = sudo or []
self.ssh_keys = ssh_keys or [] self.ssh_keys = ssh_keys or []
self.shell = shell self.shell = shell
self._hashed_password = hashed_password
@property
def hashed_password(self):
if self.password is None:
return self._hashed_password
if self._hashed_password is None:
self._hashed_password = crypt.crypt(self.password, utils.gensalt())
return self._hashed_password

View File

@ -181,14 +181,22 @@ class BuildUtilsTestCase(unittest2.TestCase):
mock_open): mock_open):
mock_path.join.return_value = 'fake_path' mock_path.join.return_value = 'fake_path'
mock_path.exists.return_value = True mock_path.exists.return_value = True
bu.do_post_inst('chroot', allow_unsigned_file='fake_unsigned',
# crypt.crypt('qwerty')
password = ('$6$KyOsgFgf9cLbGNST$Ej0Usihfy7W/WT2H0z0mC1DapC/IUpA0jF'
'.Fs83mFIdkGYHL9IOYykRCjfssH.YL4lHbmrvOd/6TIfiyh1hDY1')
bu.do_post_inst('chroot',
hashed_root_password=password,
allow_unsigned_file='fake_unsigned',
force_ipv4_file='fake_force_ipv4') force_ipv4_file='fake_force_ipv4')
file_handle_mock = mock_open.return_value.__enter__.return_value file_handle_mock = mock_open.return_value.__enter__.return_value
file_handle_mock.write.assert_called_once_with('manual\n') file_handle_mock.write.assert_called_once_with('manual\n')
mock_exec_expected_calls = [ mock_exec_expected_calls = [
mock.call('sed', '-i', 's%root:[\*,\!]%root:$6$IInX3Cqo$5xytL1VZb' mock.call('sed',
'ZTusOewFnG6couuF0Ia61yS3rbC6P5YbZP2TYclwHqMq9e3Tg8rvQx' '-i',
'hxSlBXP1DZhdUamxdOBXK0.%', 'fake_path'), 's%root:[\*,\!]%root:{}%'.format(password),
'fake_path'),
mock.call('chroot', 'chroot', 'update-rc.d', 'puppet', 'disable'), mock.call('chroot', 'chroot', 'update-rc.d', 'puppet', 'disable'),
mock.call('chroot', 'chroot', 'dpkg-divert', '--local', '--add', mock.call('chroot', 'chroot', 'dpkg-divert', '--local', '--add',
'fake_path'), 'fake_path'),

View File

@ -40,6 +40,12 @@ class TestImageBuild(unittest2.TestCase):
def setUp(self, mock_driver, mock_http, mock_yaml): def setUp(self, mock_driver, mock_http, mock_yaml):
super(self.__class__, self).setUp() super(self.__class__, self).setUp()
mock_driver.return_value = nailgun_data.NailgunBuildImage mock_driver.return_value = nailgun_data.NailgunBuildImage
# TEST_ROOT_PASSWORD = crypt.crypt('qwerty')
self.TEST_ROOT_PASSWORD = ('$6$KyOsgFgf9cLbGNST$Ej0Usihfy7W/WT2H0z0mC'
'1DapC/IUpA0jF.Fs83mFIdkGYHL9IOYykRCjfssH.'
'YL4lHbmrvOd/6TIfiyh1hDY1')
image_conf = { image_conf = {
"image_data": { "image_data": {
"/": { "/": {
@ -59,7 +65,8 @@ class TestImageBuild(unittest2.TestCase):
'priority': 1001 'priority': 1001
} }
], ],
"codename": "trusty" "codename": "trusty",
"hashed_root_password": self.TEST_ROOT_PASSWORD,
} }
self.mgr = nailgun_deploy.Manager( self.mgr = nailgun_deploy.Manager(
nailgun_data.NailgunBuildImage(image_conf)) nailgun_data.NailgunBuildImage(image_conf))
@ -99,7 +106,10 @@ class TestImageBuild(unittest2.TestCase):
'trusty', 'fakesection', priority=None), 'trusty', 'fakesection', priority=None),
objects.DEBRepo('mos', 'http://fakemos', objects.DEBRepo('mos', 'http://fakemos',
'mosX.Y', 'fakesection', priority=1000)], 'mosX.Y', 'fakesection', priority=1000)],
packages=['fakepackage1', 'fakepackage2']) packages=['fakepackage1', 'fakepackage2'],
user_accounts=[
objects.User(name='root', password=None, homedir='/root',
hashed_password=self.TEST_ROOT_PASSWORD)])
self.mgr.driver.operating_system.proxies = objects.RepoProxies( self.mgr.driver.operating_system.proxies = objects.RepoProxies(
proxies={'fake': 'fake'}, proxies={'fake': 'fake'},
direct_repo_addr_list='fake_addr') direct_repo_addr_list='fake_addr')
@ -187,7 +197,9 @@ class TestImageBuild(unittest2.TestCase):
'/tmp/imgdir', packages=['fakepackage1', 'fakepackage2'], '/tmp/imgdir', packages=['fakepackage1', 'fakepackage2'],
attempts=CONF.fetch_packages_attempts) attempts=CONF.fetch_packages_attempts)
mock_bu.do_post_inst.assert_called_once_with( mock_bu.do_post_inst.assert_called_once_with(
'/tmp/imgdir', allow_unsigned_file=CONF.allow_unsigned_file, '/tmp/imgdir',
hashed_root_password=self.TEST_ROOT_PASSWORD,
allow_unsigned_file=CONF.allow_unsigned_file,
force_ipv4_file=CONF.force_ipv4_file) force_ipv4_file=CONF.force_ipv4_file)
signal_calls = mock_bu.stop_chrooted_processes.call_args_list signal_calls = mock_bu.stop_chrooted_processes.call_args_list

View File

@ -56,10 +56,6 @@ PROXY_PROTOCOLS = {
ADDITIONAL_DEBOOTSTRAP_PACKAGES = ['ca-certificates', ADDITIONAL_DEBOOTSTRAP_PACKAGES = ['ca-certificates',
'apt-transport-https'] 'apt-transport-https']
# NOTE(agordeev): hardcoded to r00tme
ROOT_PASSWORD = '$6$IInX3Cqo$5xytL1VZbZTusOewFnG6couuF0Ia61yS3rbC6P5YbZP2TYcl'\
'wHqMq9e3Tg8rvQxhxSlBXP1DZhdUamxdOBXK0.'
def run_debootstrap(uri, suite, chroot, arch='amd64', eatmydata=False, def run_debootstrap(uri, suite, chroot, arch='amd64', eatmydata=False,
attempts=10, proxies=None, direct_repo_addr=None): attempts=10, proxies=None, direct_repo_addr=None):
@ -180,11 +176,12 @@ def clean_apt_settings(chroot, allow_unsigned_file='allow_unsigned_packages',
clean_dirs(chroot, dirs) clean_dirs(chroot, dirs)
def do_post_inst(chroot, allow_unsigned_file='allow_unsigned_packages', def do_post_inst(chroot, hashed_root_password,
allow_unsigned_file='allow_unsigned_packages',
force_ipv4_file='force_ipv4'): force_ipv4_file='force_ipv4'):
# NOTE(agordeev): set up password for root # NOTE(agordeev): set up password for root
utils.execute('sed', '-i', utils.execute('sed', '-i',
's%root:[\*,\!]%root:' + ROOT_PASSWORD + '%', 's%root:[\*,\!]%root:' + hashed_root_password + '%',
os.path.join(chroot, 'etc/shadow')) os.path.join(chroot, 'etc/shadow'))
# NOTE(agordeev): backport from bash-script: # NOTE(agordeev): backport from bash-script:
# in order to prevent the later puppet workflow outage, puppet service # in order to prevent the later puppet workflow outage, puppet service

View File

@ -18,9 +18,11 @@ import json
import locale import locale
import math import math
import os import os
import random as _random
import re import re
import shlex import shlex
import socket import socket
import string
import subprocess import subprocess
import time import time
@ -36,6 +38,7 @@ import urllib3
from bareon import errors from bareon import errors
from bareon.openstack.common import log as logging from bareon.openstack.common import log as logging
random = _random.SystemRandom()
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -419,6 +422,14 @@ def udevadm_settle():
execute('udevadm', 'settle', check_exit_code=[0]) execute('udevadm', 'settle', check_exit_code=[0])
def gensalt():
"""Generate SHA-512 salt for crypt.crypt function."""
letters = string.ascii_letters + string.digits + './'
sha512prefix = "$6$"
random_letters = ''.join(random.choice(letters) for _ in range(16))
return sha512prefix + random_letters
def dict_diff(dict1, dict2, sfrom="from", sto="to"): def dict_diff(dict1, dict2, sfrom="from", sto="to"):
j1 = json.dumps(dict1, indent=2) j1 = json.dumps(dict1, indent=2)
j2 = json.dumps(dict2, indent=2) j2 = json.dumps(dict2, indent=2)

View File

@ -189,6 +189,13 @@ class BuildCommand(command.Command):
" files", " files",
action='append' action='append'
) )
parser.add_argument(
'--root-password',
type=str,
help=("Root password for bootstrap image. PasswordAuthentication"
" by ssh still rejected by default! This password actual"
" only for tty login!"),
)
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):

View File

@ -116,3 +116,5 @@ active_bootstrap_symlink: "/var/www/nailgun/bootstraps/active_bootstrap"
# "user": "admin" # "user": "admin"
# "password": "admin" # "password": "admin"
# User can provide default hashed root password for bootstrap image
# hashed_root_password: "$6$IInX3Cqo$5xytL1VZbZTusOewFnG6couuF0Ia61yS3rbC6P5YbZP2TYclwHqMq9e3Tg8rvQxhxSlBXP1DZhdUamxdOBXK0."

View File

@ -72,6 +72,12 @@ class BootstrapDataBuilder(object):
self.certs = data.get('certs') self.certs = data.get('certs')
self.root_password = data.get('root_password')
self.hashed_root_password = None
if self.root_password is None:
self.hashed_root_password = CONF.hashed_root_password
def build(self): def build(self):
return { return {
'bootstrap': { 'bootstrap': {
@ -93,7 +99,9 @@ class BootstrapDataBuilder(object):
'codename': self.ubuntu_release, 'codename': self.ubuntu_release,
'output': self.output, 'output': self.output,
'packages': self._get_packages(), 'packages': self._get_packages(),
'image_data': self._prepare_image_data() 'image_data': self._prepare_image_data(),
'hashed_root_password': self.hashed_root_password,
'root_password': self.root_password,
} }
def _get_extra_dirs(self): def _get_extra_dirs(self):

View File

@ -209,3 +209,6 @@ log_file=/var/log/bareon.log
#execute_retry_delay=2.0 #execute_retry_delay=2.0
# Default password for root user
# (string value)
default_root_password=r00tme