Add support for reading 'random_seed' from data source.

A new field in the metadata has emerged on openstack config drive, one
that provides a way to seed the linux random generator.

This adds a 'random_seed' config module that writes and that it to
/dev/urandom.  Also added is support for reading that data on
azure via the hyper-v acpi table data.

In config drive datasource, it rewrites parts of the on_boot code to use a
little helper class.
This commit is contained in:
Scott Moser
2013-09-09 20:31:30 -04:00
6 changed files with 249 additions and 14 deletions

View File

@@ -15,6 +15,8 @@
which also reads from uptime. uptime is useful as clock may change during
boot due to ntp.
- prefer growpart resizer to 'parted resizepart' (LP: #1212492)
- support random data seed from config drive or azure, and a module
'seed_random' to read that and write it to /dev/urandom.
0.7.2:
- add a debian watch file
- add 'sudo' entry to ubuntu's default user (LP: #1080717)

View File

@@ -0,0 +1,61 @@
# vi: ts=4 expandtab
#
# Copyright (C) 2013 Yahoo! Inc.
#
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
from StringIO import StringIO
from cloudinit.settings import PER_INSTANCE
from cloudinit import util
frequency = PER_INSTANCE
def _decode(data, encoding=None):
if not data:
return ''
if not encoding or encoding.lower() in ['raw']:
return data
elif encoding.lower() in ['base64', 'b64']:
return base64.b64decode(data)
elif encoding.lower() in ['gzip', 'gz']:
return util.decomp_gzip(data, quiet=False)
else:
raise IOError("Unknown random_seed encoding: %s" % (encoding))
def handle(name, cfg, cloud, log, _args):
if not cfg or "random_seed" not in cfg:
log.debug(("Skipping module named %s, "
"no 'random_seed' configuration found"), name)
return
my_cfg = cfg['random_seed']
seed_path = my_cfg.get('file', '/dev/urandom')
seed_buf = StringIO()
seed_buf.write(_decode(my_cfg.get('data', ''),
encoding=my_cfg.get('encoding')))
metadata = cloud.datasource.metadata
if metadata and 'random_seed' in metadata:
seed_buf.write(metadata['random_seed'])
seed_data = seed_buf.getvalue()
if len(seed_data):
log.debug("%s: adding %s bytes of random seed entrophy to %s", name,
len(seed_data), seed_path)
util.append_file(seed_path, seed_data)

View File

@@ -106,6 +106,11 @@ class DataSourceAzureNet(sources.DataSource):
if found == ddir:
LOG.debug("using files cached in %s", ddir)
# azure / hyper-v provides random data here
seed = util.load_file("/sys/firmware/acpi/tables/OEM0", quiet=True)
if seed:
self.metadata['random_seed'] = seed
# now update ds_cfg to reflect contents pass in config
usercfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {})
self.ds_cfg = util.mergemanydict([usercfg, self.ds_cfg])

View File

@@ -18,6 +18,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import json
import os
@@ -41,6 +42,25 @@ DEFAULT_METADATA = {
VALID_DSMODES = ("local", "net", "pass", "disabled")
class ConfigDriveHelper(object):
def __init__(self, distro):
self.distro = distro
def on_first_boot(self, data):
if not data:
data = {}
if 'network_config' in data:
LOG.debug("Updating network interfaces from config drive")
self.distro.apply_network(data['network_config'])
files = data.get('files')
if files:
LOG.debug("Writing %s injected files", len(files))
try:
write_files(files)
except IOError:
util.logexc(LOG, "Failed writing files")
class DataSourceConfigDrive(sources.DataSource):
def __init__(self, sys_cfg, distro, paths):
sources.DataSource.__init__(self, sys_cfg, distro, paths)
@@ -49,6 +69,7 @@ class DataSourceConfigDrive(sources.DataSource):
self.seed_dir = os.path.join(paths.seed_dir, 'config_drive')
self.version = None
self.ec2_metadata = None
self.helper = ConfigDriveHelper(distro)
def __str__(self):
root = sources.DataSource.__str__(self)
@@ -187,20 +208,8 @@ class DataSourceConfigDrive(sources.DataSource):
# instance-id
prev_iid = get_previous_iid(self.paths)
cur_iid = md['instance-id']
if ('network_config' in results and self.dsmode == "local" and
prev_iid != cur_iid):
LOG.debug("Updating network interfaces from config drive (%s)",
dsmode)
self.distro.apply_network(results['network_config'])
# file writing occurs in local mode (to be as early as possible)
if self.dsmode == "local" and prev_iid != cur_iid and results['files']:
LOG.debug("writing injected files")
try:
write_files(results['files'])
except:
util.logexc(LOG, "Failed writing files")
if prev_iid != cur_iid and self.dsmode == "local":
self.helper.on_first_boot(results)
# dsmode != self.dsmode here if:
# * dsmode = "pass", pass means it should only copy files and then
@@ -338,6 +347,13 @@ def read_config_drive_dir_v2(source_dir, version="2012-08-10"):
except KeyError:
raise BrokenConfigDriveDir("No uuid entry in metadata")
if 'random_seed' in results['metadata']:
random_seed = results['metadata']['random_seed']
try:
results['metadata']['random_seed'] = base64.b64decode(random_seed)
except (ValueError, TypeError) as exc:
raise BrokenConfigDriveDir("Badly formatted random_seed: %s" % exc)
def read_content_path(item):
# do not use os.path.join here, as content_path starts with /
cpath = os.path.sep.join((source_dir, "openstack",

View File

@@ -24,6 +24,7 @@ preserve_hostname: false
# The modules that run in the 'init' stage
cloud_init_modules:
- migrator
- seed_random
- bootcmd
- write-files
- growpart

View File

@@ -0,0 +1,150 @@
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
#
# Author: Juerg Haefliger <juerg.haefliger@hp.com>
#
# Based on test_handler_set_hostname.py
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from cloudinit.config import cc_seed_random
import base64
import tempfile
import gzip
from StringIO import StringIO
from cloudinit import cloud
from cloudinit import distros
from cloudinit import helpers
from cloudinit import util
from cloudinit.sources import DataSourceNone
from tests.unittests import helpers as t_help
import logging
LOG = logging.getLogger(__name__)
class TestRandomSeed(t_help.TestCase):
def setUp(self):
super(TestRandomSeed, self).setUp()
self._seed_file = tempfile.mktemp()
def tearDown(self):
util.del_file(self._seed_file)
def _compress(self, text):
contents = StringIO()
gz_fh = gzip.GzipFile(mode='wb', fileobj=contents)
gz_fh.write(text)
gz_fh.close()
return contents.getvalue()
def _get_cloud(self, distro, metadata=None):
paths = helpers.Paths({})
cls = distros.fetch(distro)
ubuntu_distro = cls(distro, {}, paths)
ds = DataSourceNone.DataSourceNone({}, ubuntu_distro, paths)
if metadata:
ds.metadata = metadata
return cloud.Cloud(ds, paths, {}, ubuntu_distro, None)
def test_append_random(self):
cfg = {
'random_seed': {
'file': self._seed_file,
'data': 'tiny-tim-was-here',
}
}
cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, [])
contents = util.load_file(self._seed_file)
self.assertEquals("tiny-tim-was-here", contents)
def test_append_random_unknown_encoding(self):
data = self._compress("tiny-toe")
cfg = {
'random_seed': {
'file': self._seed_file,
'data': data,
'encoding': 'special_encoding',
}
}
self.assertRaises(IOError, cc_seed_random.handle, 'test', cfg,
self._get_cloud('ubuntu'), LOG, [])
def test_append_random_gzip(self):
data = self._compress("tiny-toe")
cfg = {
'random_seed': {
'file': self._seed_file,
'data': data,
'encoding': 'gzip',
}
}
cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, [])
contents = util.load_file(self._seed_file)
self.assertEquals("tiny-toe", contents)
def test_append_random_gz(self):
data = self._compress("big-toe")
cfg = {
'random_seed': {
'file': self._seed_file,
'data': data,
'encoding': 'gz',
}
}
cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, [])
contents = util.load_file(self._seed_file)
self.assertEquals("big-toe", contents)
def test_append_random_base64(self):
data = base64.b64encode('bubbles')
cfg = {
'random_seed': {
'file': self._seed_file,
'data': data,
'encoding': 'base64',
}
}
cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, [])
contents = util.load_file(self._seed_file)
self.assertEquals("bubbles", contents)
def test_append_random_b64(self):
data = base64.b64encode('kit-kat')
cfg = {
'random_seed': {
'file': self._seed_file,
'data': data,
'encoding': 'b64',
}
}
cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, [])
contents = util.load_file(self._seed_file)
self.assertEquals("kit-kat", contents)
def test_append_random_metadata(self):
cfg = {
'random_seed': {
'file': self._seed_file,
'data': 'tiny-tim-was-here',
}
}
c = self._get_cloud('ubuntu', {'random_seed': '-so-was-josh'})
cc_seed_random.handle('test', cfg, c, LOG, [])
contents = util.load_file(self._seed_file)
self.assertEquals('tiny-tim-was-here-so-was-josh', contents)