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.import_opt('prepare_configdrive', '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):
@ -827,6 +828,21 @@ class NailgunBuildImage(BaseDataDriver,
os = objects.Ubuntu(repos=repos, packages=packages, major=14, minor=4,
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
def parse_schemes(self):

View File

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

View File

@ -28,6 +28,15 @@ class OperatingSystem(object):
def add_user_account(self, **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):
return {'major': self.major,
'minor': self.minor,

View File

@ -12,13 +12,28 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import crypt
from bareon.utils import utils
class User(object):
def __init__(self, name, password, homedir, sudo=None, ssh_keys=None,
shell="/bin/bash"):
shell="/bin/bash", hashed_password=None):
self.name = name
self.password = password
self.homedir = homedir
self.sudo = sudo or []
self.ssh_keys = ssh_keys or []
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_path.join.return_value = 'fake_path'
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')
file_handle_mock = mock_open.return_value.__enter__.return_value
file_handle_mock.write.assert_called_once_with('manual\n')
mock_exec_expected_calls = [
mock.call('sed', '-i', 's%root:[\*,\!]%root:$6$IInX3Cqo$5xytL1VZb'
'ZTusOewFnG6couuF0Ia61yS3rbC6P5YbZP2TYclwHqMq9e3Tg8rvQx'
'hxSlBXP1DZhdUamxdOBXK0.%', 'fake_path'),
mock.call('sed',
'-i',
's%root:[\*,\!]%root:{}%'.format(password),
'fake_path'),
mock.call('chroot', 'chroot', 'update-rc.d', 'puppet', 'disable'),
mock.call('chroot', 'chroot', 'dpkg-divert', '--local', '--add',
'fake_path'),

View File

@ -40,6 +40,12 @@ class TestImageBuild(unittest2.TestCase):
def setUp(self, mock_driver, mock_http, mock_yaml):
super(self.__class__, self).setUp()
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_data": {
"/": {
@ -59,7 +65,8 @@ class TestImageBuild(unittest2.TestCase):
'priority': 1001
}
],
"codename": "trusty"
"codename": "trusty",
"hashed_root_password": self.TEST_ROOT_PASSWORD,
}
self.mgr = nailgun_deploy.Manager(
nailgun_data.NailgunBuildImage(image_conf))
@ -99,7 +106,10 @@ class TestImageBuild(unittest2.TestCase):
'trusty', 'fakesection', priority=None),
objects.DEBRepo('mos', 'http://fakemos',
'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(
proxies={'fake': 'fake'},
direct_repo_addr_list='fake_addr')
@ -187,7 +197,9 @@ class TestImageBuild(unittest2.TestCase):
'/tmp/imgdir', packages=['fakepackage1', 'fakepackage2'],
attempts=CONF.fetch_packages_attempts)
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)
signal_calls = mock_bu.stop_chrooted_processes.call_args_list

View File

@ -56,10 +56,6 @@ PROXY_PROTOCOLS = {
ADDITIONAL_DEBOOTSTRAP_PACKAGES = ['ca-certificates',
'apt-transport-https']
# NOTE(agordeev): hardcoded to r00tme
ROOT_PASSWORD = '$6$IInX3Cqo$5xytL1VZbZTusOewFnG6couuF0Ia61yS3rbC6P5YbZP2TYcl'\
'wHqMq9e3Tg8rvQxhxSlBXP1DZhdUamxdOBXK0.'
def run_debootstrap(uri, suite, chroot, arch='amd64', eatmydata=False,
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)
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'):
# NOTE(agordeev): set up password for root
utils.execute('sed', '-i',
's%root:[\*,\!]%root:' + ROOT_PASSWORD + '%',
's%root:[\*,\!]%root:' + hashed_root_password + '%',
os.path.join(chroot, 'etc/shadow'))
# NOTE(agordeev): backport from bash-script:
# in order to prevent the later puppet workflow outage, puppet service

View File

@ -18,9 +18,11 @@ import json
import locale
import math
import os
import random as _random
import re
import shlex
import socket
import string
import subprocess
import time
@ -36,6 +38,7 @@ import urllib3
from bareon import errors
from bareon.openstack.common import log as logging
random = _random.SystemRandom()
LOG = logging.getLogger(__name__)
@ -419,6 +422,14 @@ def udevadm_settle():
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"):
j1 = json.dumps(dict1, indent=2)
j2 = json.dumps(dict2, indent=2)

View File

@ -189,6 +189,13 @@ class BuildCommand(command.Command):
" files",
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
def take_action(self, parsed_args):

View File

@ -116,3 +116,5 @@ active_bootstrap_symlink: "/var/www/nailgun/bootstraps/active_bootstrap"
# "user": "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.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):
return {
'bootstrap': {
@ -93,7 +99,9 @@ class BootstrapDataBuilder(object):
'codename': self.ubuntu_release,
'output': self.output,
'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):

View File

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