From af4e7721375aa1e0ee3dc142b3de5be2645db734 Mon Sep 17 00:00:00 2001 From: Alessandro Pilotti Date: Thu, 2 May 2013 02:59:03 +0300 Subject: [PATCH] Adds SetUserPassword plugin Password management needs to be separated from the CreateUserPlugin. --- cloudbaseinit/metadata/services/base.py | 11 ++- cloudbaseinit/osutils/base.py | 8 ++ cloudbaseinit/osutils/windows.py | 18 +++-- cloudbaseinit/plugins/factory.py | 1 + cloudbaseinit/plugins/windows/createuser.py | 83 ++------------------- 5 files changed, 36 insertions(+), 85 deletions(-) diff --git a/cloudbaseinit/metadata/services/base.py b/cloudbaseinit/metadata/services/base.py index 338b0687..cccbf8e3 100644 --- a/cloudbaseinit/metadata/services/base.py +++ b/cloudbaseinit/metadata/services/base.py @@ -102,10 +102,17 @@ class BaseMetadataService(object): def _post_data(self, path, data): raise NotExistingMetadataException() - def post_password(self, enc_password_b64, version='latest'): - path = posixpath.normpath(posixpath.join('openstack', + def _get_password_path(self, version='latest'): + return posixpath.normpath(posixpath.join('openstack', version, 'password')) + + def is_password_set(self, version='latest'): + path = self._get_password_path(version) + return len(self._get_data(path)) > 0 + + def post_password(self, enc_password_b64, version='latest'): + path = self._get_password_path(version) action = lambda: self._post_data(path, enc_password_b64) return self._exec_with_retry(action) diff --git a/cloudbaseinit/osutils/base.py b/cloudbaseinit/osutils/base.py index 1bf3d7d7..f4217f98 100644 --- a/cloudbaseinit/osutils/base.py +++ b/cloudbaseinit/osutils/base.py @@ -14,6 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. +import base64 +import os import subprocess @@ -24,6 +26,12 @@ class BaseOSUtils(object): def user_exists(self, username): pass + def generate_random_password(self, length): + # On Windows os.urandom() uses CryptGenRandom, which is a + # cryptographically secure pseudorandom number generator + b64_password = base64.b64encode(os.urandom(256)) + return b64_password.replace('/', '').replace('+', '')[:length] + def execute_process(self, args, shell=True): p = subprocess.Popen(args, stdout=subprocess.PIPE, diff --git a/cloudbaseinit/osutils/windows.py b/cloudbaseinit/osutils/windows.py index d96c523b..c30cf29c 100644 --- a/cloudbaseinit/osutils/windows.py +++ b/cloudbaseinit/osutils/windows.py @@ -107,8 +107,12 @@ class WindowsUtils(base.BaseOSUtils): (out, err, ret_val) = self.execute_process(args) if not ret_val: self._set_user_password_expiration(username, password_expires) - - return ret_val == 0 + else: + if create: + msg = "Create user failed: %(err)s" + else: + msg = "Set user password failed: %(err)s" + raise Exception(msg % locals()) def _sanitize_wmi_input(self, value): return value.replace('\'', '\'\'') @@ -122,14 +126,12 @@ class WindowsUtils(base.BaseOSUtils): return True def create_user(self, username, password, password_expires=False): - if not self._create_or_change_user(username, password, True, - password_expires): - raise Exception("Create user failed") + self._create_or_change_user(username, password, True, + password_expires) def set_user_password(self, username, password, password_expires=False): - if not self._create_or_change_user(username, password, False, - password_expires): - raise Exception("Set user password failed") + self._create_or_change_user(username, password, False, + password_expires) def _get_user_sid_and_domain(self, username): sid = ctypes.create_string_buffer(1024) diff --git a/cloudbaseinit/plugins/factory.py b/cloudbaseinit/plugins/factory.py index 9abb94b8..e7898014 100644 --- a/cloudbaseinit/plugins/factory.py +++ b/cloudbaseinit/plugins/factory.py @@ -28,6 +28,7 @@ opts = [ 'SetUserSSHPublicKeysPlugin', 'cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin', 'cloudbaseinit.plugins.windows.userdata.UserDataPlugin', + 'cloudbaseinit.plugins.windows.setuserpassword.SetUserPasswordPlugin', ], help='List of enabled plugin classes, ' 'to executed in the provided order'), diff --git a/cloudbaseinit/plugins/windows/createuser.py b/cloudbaseinit/plugins/windows/createuser.py index 2b0bce2c..49a1ab50 100644 --- a/cloudbaseinit/plugins/windows/createuser.py +++ b/cloudbaseinit/plugins/windows/createuser.py @@ -14,15 +14,10 @@ # License for the specific language governing permissions and limitations # under the License. -import base64 -import os - -from cloudbaseinit.metadata.services import base as services_base from cloudbaseinit.openstack.common import cfg from cloudbaseinit.openstack.common import log as logging from cloudbaseinit.osutils import factory as osutils_factory from cloudbaseinit.plugins import base -from cloudbaseinit.utils import crypt opts = [ cfg.StrOpt('username', default='Admin', help='User to be added to the ' @@ -42,81 +37,25 @@ LOG = logging.getLogger(__name__) class CreateUserPlugin(base.BasePlugin): - _post_password_md_ver = '2013-04-04' - - def _generate_random_password(self, length): - # On Windows os.urandom() uses CryptGenRandom, which is a - # cryptographically secure pseudorandom number generator - b64_password = base64.b64encode(os.urandom(256)) - return b64_password.replace('/', '').replace('+', '')[:length] - - def _encrypt_password(self, ssh_pub_key, password): - cm = crypt.CryptManager() - with cm.load_ssh_rsa_public_key(ssh_pub_key) as rsa: - enc_password = rsa.public_encrypt(password) - return base64.b64encode(enc_password) - - def _get_ssh_public_key(self, service): - meta_data = service.get_meta_data('openstack', - self._post_password_md_ver) - if not 'public_keys' in meta_data: - return False - - public_keys = meta_data['public_keys'] - ssh_pub_key = None - for k in public_keys: - # Get the first key - ssh_pub_key = public_keys[k] - break - return ssh_pub_key - - def _get_password(self, service): + def _get_password(self, service, osutils): meta_data = service.get_meta_data('openstack') if 'admin_pass' in meta_data and CONF.inject_user_password: + LOG.warn('Using admin_pass metadata user password. Consider ' + 'changing it as soon as possible') password = meta_data['admin_pass'] else: - LOG.debug('Generating random password') - # Generate a random password - # Limit to 14 chars for compatibility with NT - password = self._generate_random_password(14) + # Generate a temporary random password to be replaced + # by SetUserPasswordPlugin (starting from Grizzly) + password = osutils.generate_random_password(14) return password - def _set_metadata_password(self, password, service): - try: - ssh_pub_key = self._get_ssh_public_key(service) - if ssh_pub_key: - enc_password_b64 = self._encrypt_password(ssh_pub_key, - password) - return service.post_password(enc_password_b64, - self._post_password_md_ver) - else: - LOG.info('No SSH public key available for password encryption') - return True - except services_base.NotExistingMetadataException: - # Requested version not available or password feature - # not implemented - LOG.info('Cannot set the password in the metadata as it is not ' - 'supported by this metadata version') - return True - def execute(self, service): user_name = CONF.username - password = self._get_password(service) - - if service.can_post_password: - md_pwd_already_set = not self._set_metadata_password(password, - service) - else: - md_pwd_already_set = False - LOG.info('Cannot set the password in the metadata as it is not ' - 'supported by this service') - osutils = osutils_factory.OSUtilsFactory().get_os_utils() if not osutils.user_exists(user_name): - if md_pwd_already_set: - LOG.warning('Creating user, but the password was not set in ' - 'the metadata as it was previously set') + password = self._get_password(service, osutils) + osutils.create_user(user_name, password) # Create a user profile in order for other plugins # to access the user home, etc @@ -124,12 +63,6 @@ class CreateUserPlugin(base.BasePlugin): password, True) osutils.close_user_logon_session(token) - else: - if not md_pwd_already_set: - osutils.set_user_password(user_name, password) - else: - LOG.warning('Cannot change the user\'s password as it is ' - 'already set in the metadata') for group_name in CONF.groups: try: