Create non-root user account during image build process

Extended Nailgun data driver to parse new ks_meta keys.

Extended Ubuntu cloud-init config template to create a non-root
account. Root login is being disabled, however, this setting
will only be effective until osnailyfacter::ssh puppet class
will have been evaluated during deployment as it overrides
sshd_config values. This means, that PermitRootLogin should be
managed by library as well.

Blueprint: fuel-nonroot-openstack-nodes
Depends-On: Ia18305e07d07377886783c3b3e44abe93cef2da5

Change-Id: I69831fe0327ef9ac55bed99301d2c3732b87ed88
This commit is contained in:
Dmitry Nikishov 2015-12-15 18:02:12 -06:00
parent 2e608ccc1b
commit 70f767066e
8 changed files with 124 additions and 12 deletions

View File

@ -4,14 +4,28 @@ growpart:
mode: false
disable_ec2_metadata: true
disable_root: false
user: root
password: r00tme
users:
{% for user in user_accounts %}
- name: {{ user.name }}
plain_text_passwd: {{ user.password }}
lock_passwd: False
homedir: {{ user.homedir }}
shell: {{ user.shell }}
{% if user.ssh_keys|length > 0 %}
ssh_authorized_keys:
{% for key in user.ssh_keys %}
- {{ key }}
{% endfor %}
{% endif %}
{% if user.sudo|length > 0 %}
sudo:
{% for entry in user.sudo %}
- "{{ entry }}"
{% endfor %}
{% endif %}
{% endfor %}
chpasswd: { expire: false }
ssh_pwauth: false
ssh_authorized_keys:
{% for key in common.ssh_auth_keys %}
- {{ key }}
{% endfor %}
# set the locale to a given locale
# default: en_US.UTF-8

View File

@ -285,9 +285,28 @@ class Nailgun(BaseDataDriver):
def parse_operating_system(self):
LOG.debug('--- Preparing operating system data ---')
os_release = self._image_meta.get('os', None)
return self.get_os_by_image_meta(os_release) or \
os = self.get_os_by_image_meta(os_release) or \
self.get_os_by_profile(self.data['profile'].lower())
# FIXME(dnikishov): until fuel-agent-versioning BP
# will have been implemented, we need to deal with the case when
# 9.0 fuel-agent will be managing 6.1 to 8.0 environments, whose
# provisioning serializers on Nailgun side will not have
# user_accounts in the ks_meta dict
try:
user_accounts = self.data['ks_meta']['user_accounts']
except KeyError:
LOG.warn(('This environment does not support non-root accounts '
'on the target nodes. Non-root user accounts will not '
'be created'))
user_accounts = []
for account in user_accounts:
os.add_user_account(**account)
return os
def parse_partition_scheme(self):
LOG.debug('--- Preparing partition scheme ---')
data = self.partition_data()
@ -520,7 +539,9 @@ class Nailgun(BaseDataDriver):
def parse_configdrive_scheme(self):
LOG.debug('--- Preparing configdrive scheme ---')
data = self.data
configdrive_scheme = objects.ConfigDriveScheme()
configdrive_scheme = objects.ConfigDriveScheme(
user_accounts=self.operating_system.user_accounts
)
LOG.debug('Adding common parameters')

View File

@ -58,12 +58,14 @@ class ConfigDriveMcollective(object):
class ConfigDriveScheme(object):
def __init__(self, common=None, puppet=None,
mcollective=None, profile=None, templates=None):
mcollective=None, profile=None, templates=None,
user_accounts=None):
self.common = common
self.puppet = puppet
self.mcollective = mcollective
self._profile = profile or 'ubuntu'
self.templates = templates or {}
self.user_accounts = user_accounts or []
# TODO(kozhukalov) make it possible to validate scheme according to
# chosen profile which means chosen set of cloud-init templates.
@ -87,6 +89,8 @@ class ConfigDriveScheme(object):
template_data.update(puppet=self.puppet)
if self.mcollective is not None:
template_data.update(mcollective=self.mcollective)
if self.user_accounts:
template_data.update(user_accounts=self.user_accounts)
return template_data
def set_profile(self, profile):

View File

@ -12,15 +12,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from fuel_agent.objects import users
class OperatingSystem(object):
def __init__(self, repos, packages, major='unknown', minor='unknown',
proxies=None):
proxies=None, user_accounts=None):
self.repos = repos
self.packages = packages
self.major = major
self.minor = minor
self.proxies = proxies
self.user_accounts = user_accounts or []
def add_user_account(self, **kwargs):
self.user_accounts.append(users.User(**kwargs))
def to_dict(self):
return {'major': self.major,

View File

@ -88,7 +88,29 @@
}
]
},
"timezone": "America/Los_Angeles"
"timezone": "America/Los_Angeles",
"user_accounts": [
{
"name": "fueladmin",
"password": "fueladmin",
"homedir": "/home/fueladmin",
"sudo": [],
"ssh_keys": []
},
{
"name": "fuel",
"password": "fuel",
"homedir": "/var/lib/fuel",
"sudo": ["ALL=(ALL) NOPASSWD: ALL"],
"ssh_keys": []
},
{
"name": "root",
"password": "r00tme",
"homedir": "/root",
"ssh_keys": []
}
]
},
"name": "node-1",
"name_servers": "\"10.20.0.2\"",

View File

@ -17,6 +17,7 @@ import unittest2
from fuel_agent import errors
from fuel_agent.objects import configdrive
from fuel_agent.objects import User
class TestConfigDriveScheme(unittest2.TestCase):
@ -64,10 +65,17 @@ class TestConfigDriveScheme(unittest2.TestCase):
cd_puppet = configdrive.ConfigDrivePuppet('master', 0)
cd_mcollective = configdrive.ConfigDriveMcollective(
'pskey', 'vhost', 'host', 'user', 'password', 'connector', 1, -1)
cd_user_accounts = []
cd_user_accounts.append(User('fuel', 'fuel', '/var/lib/fuel',
['ALL=(ALL) NOPASSWD: ALL']))
cd_user_accounts.append(User('test', 'test', '/home/test',
['SUDO'], ['KEY']))
self.cd_scheme.common = cd_common
self.cd_scheme.puppet = cd_puppet
self.cd_scheme.mcollective = cd_mcollective
self.cd_scheme.user_accounts = cd_user_accounts
template_data = self.cd_scheme.template_data()
self.assertEqual(cd_common, template_data['common'])
self.assertEqual(cd_puppet, template_data['puppet'])
self.assertEqual(cd_mcollective, template_data['mcollective'])
self.assertEqual(cd_user_accounts, template_data['user_accounts'])

View File

@ -326,7 +326,29 @@ PROVISION_SAMPLE_DATA = {
]
},
"mco_connector": "rabbitmq",
"mco_host": "10.20.0.2"
"mco_host": "10.20.0.2",
"user_accounts": [
{
"name": "fueladmin",
"password": "fueladmin",
"homedir": "/home/fueladmin",
"sudo": [],
"ssh_keys": []
},
{
"name": "fuel",
"password": "fuel",
"homedir": "/var/lib/fuel",
"sudo": ["ALL=(ALL) NOPASSWD: ALL"],
"ssh_keys": []
},
{
"name": "root",
"password": "r00tme",
"homedir": "/root",
"ssh_keys": []
}
]
},
"name": "node-1",
"hostname": "node-1.domain.tld",

View File

@ -213,6 +213,21 @@ class TestFullDataRead(unittest2.TestCase):
PROVISION_DATA = base.load_fixture('simple_nailgun_driver.json')
def test_read_61_70_80_with_no_error(self, mock_requests):
PROVISION_DATA_61_70_80 = dict(self.PROVISION_DATA)
del PROVISION_DATA_61_70_80['ks_meta']['user_accounts']
mock_requests.get('http://fake.host.org:123/imgs/fake_image.img.gz',
text='{}')
driver = simple.NailgunSimpleDriver(PROVISION_DATA_61_70_80)
scheme = driver.partition_scheme
assert len(scheme.fss) == 5
assert len(scheme.lvs) == 3
assert len(scheme.mds) == 0
assert len(scheme.parteds) == 2
assert len(scheme.pvs) == 4
assert len(scheme.vgs) == 2
def test_read_with_no_error(self, mock_requests):
mock_requests.get('http://fake.host.org:123/imgs/fake_image.img.gz',
text='{}')