|
@@ -72,6 +72,9 @@ class CustomServiceOrchestrator():
|
|
|
DEFAULT_CREDENTIAL_CONF_DIR = '/var/lib/ambari-agent/cred/conf'
|
|
|
DEFAULT_CREDENTIAL_SHELL_CMD = 'org.apache.hadoop.security.alias.CredentialShell'
|
|
|
|
|
|
+ # The property name used by the hadoop credential provider
|
|
|
+ CREDENTIAL_PROVIDER_PROPERTY_NAME = 'hadoop.security.credential.provider.path'
|
|
|
+
|
|
|
def __init__(self, config, controller):
|
|
|
self.config = config
|
|
|
self.tmp_dir = config.get('agent', 'prefix')
|
|
@@ -147,21 +150,97 @@ class CustomServiceOrchestrator():
|
|
|
|
|
|
return conf_dir
|
|
|
|
|
|
- def getAffectedConfigTypes(self, commandJson):
|
|
|
+ def getConfigTypeCredentials(self, commandJson):
|
|
|
"""
|
|
|
Gets the affected config types for the service in this command
|
|
|
+ with the password aliases and values.
|
|
|
+
|
|
|
+ Input:
|
|
|
+ {
|
|
|
+ "config-type1" : {
|
|
|
+ "password_key_name1":"password_value_name1",
|
|
|
+ "password_key_name2":"password_value_name2",
|
|
|
+ :
|
|
|
+ },
|
|
|
+ "config-type2" : {
|
|
|
+ "password_key_name1":"password_value_name1",
|
|
|
+ "password_key_name2":"password_value_name2",
|
|
|
+ :
|
|
|
+ },
|
|
|
+ :
|
|
|
+ }
|
|
|
+
|
|
|
+ Output:
|
|
|
+ {
|
|
|
+ "config-type1" : {
|
|
|
+ "alias1":"password1",
|
|
|
+ "alias2":"password2",
|
|
|
+ :
|
|
|
+ },
|
|
|
+ "config-type2" : {
|
|
|
+ "alias1":"password1",
|
|
|
+ "alias2":"password2",
|
|
|
+ :
|
|
|
+ },
|
|
|
+ :
|
|
|
+ }
|
|
|
+
|
|
|
+ If password_key_name is the same as password_value_name, then password_key_name is the password alias itself.
|
|
|
+ The value it points to is the password value.
|
|
|
+
|
|
|
+ If password_key_name is not the same as the password_value_name, then password_key_name points to the alias.
|
|
|
+ The value is pointed to by password_value_name.
|
|
|
+
|
|
|
+ For example:
|
|
|
+ Input:
|
|
|
+ {
|
|
|
+ "oozie-site" : {"oozie.service.JPAService.jdbc.password" : "oozie.service.JPAService.jdbc.password"},
|
|
|
+ "admin-properties" {"db_user":"db_password", "ranger.jpa.jdbc.credential.alias:ranger-admin-site" : "db_password"}
|
|
|
+ }
|
|
|
+
|
|
|
+ Output:
|
|
|
+ {
|
|
|
+ "oozie-site" : {"oozie.service.JPAService.jdbc.password" : "MyOozieJdbcPassword"},
|
|
|
+ "admin-properties" {"rangerdba" : "MyRangerDbaPassword", "rangeradmin":"MyRangerDbaPassword"},
|
|
|
+ }
|
|
|
|
|
|
:param commandJson:
|
|
|
:return:
|
|
|
"""
|
|
|
- return commandJson.get('configuration_attributes')
|
|
|
-
|
|
|
- def getCredentialProviderPropertyName(self):
|
|
|
- """
|
|
|
- Gets the property name used by the hadoop credential provider
|
|
|
- :return:
|
|
|
- """
|
|
|
- return 'hadoop.security.credential.provider.path'
|
|
|
+ configtype_credentials = {}
|
|
|
+ if 'configuration_credentials' in commandJson:
|
|
|
+ for config_type, password_properties in commandJson['configuration_credentials'].items():
|
|
|
+ if config_type in commandJson['configurations']:
|
|
|
+ value_names = []
|
|
|
+ config = commandJson['configurations'][config_type]
|
|
|
+ credentials = {}
|
|
|
+ for key_name, value_name in password_properties.items():
|
|
|
+ if key_name == value_name:
|
|
|
+ if value_name in config:
|
|
|
+ # password name is the alias
|
|
|
+ credentials[key_name] = config[value_name]
|
|
|
+ value_names.append(value_name) # Gather the value_name for deletion
|
|
|
+ else:
|
|
|
+ keyname_keyconfig = key_name.split(':')
|
|
|
+ key_name = keyname_keyconfig[0]
|
|
|
+ # if the key is in another configuration (cross reference),
|
|
|
+ # get the value of the key from that configuration
|
|
|
+ if (len(keyname_keyconfig) > 1):
|
|
|
+ if keyname_keyconfig[1] not in commandJson['configurations']:
|
|
|
+ continue
|
|
|
+ key_config = commandJson['configurations'][keyname_keyconfig[1]]
|
|
|
+ else:
|
|
|
+ key_config = config
|
|
|
+ if key_name in key_config and value_name in config:
|
|
|
+ # password name points to the alias
|
|
|
+ credentials[key_config[key_name]] = config[value_name]
|
|
|
+ value_names.append(value_name) # Gather the value_name for deletion
|
|
|
+ if len(credentials) > 0:
|
|
|
+ configtype_credentials[config_type] = credentials
|
|
|
+ for value_name in value_names:
|
|
|
+ # Remove the clear text password
|
|
|
+ config.pop(value_name, None)
|
|
|
+ return configtype_credentials
|
|
|
|
|
|
def generateJceks(self, commandJson):
|
|
|
"""
|
|
@@ -178,16 +257,6 @@ class CustomServiceOrchestrator():
|
|
|
|
|
|
logger.info('generateJceks: roleCommand={0}'.format(roleCommand))
|
|
|
|
|
|
- # Password properties for a config type, if present,
|
|
|
- # are under configuration_attributes:config_type:hidden:{prop1:attributes1, prop2, attributes2}
|
|
|
- passwordProperties = {}
|
|
|
- config_types = self.getAffectedConfigTypes(commandJson)
|
|
|
- for config_type in config_types:
|
|
|
- elem = config_types.get(config_type)
|
|
|
- hidden = elem.get('hidden')
|
|
|
- if hidden is not None:
|
|
|
- passwordProperties[config_type] = hidden
|
|
|
-
|
|
|
# Set up the variables for the external command to generate a JCEKS file
|
|
|
java_home = commandJson['hostLevelParams']['java_home']
|
|
|
java_bin = '{java_home}/bin/java'.format(java_home=java_home)
|
|
@@ -196,31 +265,30 @@ class CustomServiceOrchestrator():
|
|
|
serviceName = commandJson['serviceName']
|
|
|
|
|
|
# Gather the password values and remove them from the configuration
|
|
|
- configs = commandJson.get('configurations')
|
|
|
- for key, value in passwordProperties.items():
|
|
|
- config = configs.get(key)
|
|
|
- if config is not None:
|
|
|
- file_path = os.path.join(self.getProviderDirectory(serviceName), "{0}.jceks".format(key))
|
|
|
- if os.path.exists(file_path):
|
|
|
- os.remove(file_path)
|
|
|
- provider_path = 'jceks://file{file_path}'.format(file_path=file_path)
|
|
|
- logger.info('provider_path={0}'.format(provider_path))
|
|
|
- for alias in value:
|
|
|
- pwd = config.get(alias)
|
|
|
- if pwd is not None:
|
|
|
- # Remove the clear text password
|
|
|
- config.pop(alias, None)
|
|
|
- # Add JCEKS provider path instead
|
|
|
- config[self.getCredentialProviderPropertyName()] = provider_path
|
|
|
- logger.debug("config={0}".format(config))
|
|
|
- protected_pwd = PasswordString(pwd)
|
|
|
- # Generate the JCEKS file
|
|
|
- cmd = (java_bin, '-cp', cs_lib_path, self.credential_shell_cmd, 'create',
|
|
|
- alias, '-value', protected_pwd, '-provider', provider_path)
|
|
|
- logger.info(cmd)
|
|
|
- cmd_result = subprocess.call(cmd)
|
|
|
- logger.info('cmd_result = {0}'.format(cmd_result))
|
|
|
- os.chmod(file_path, 0644) # group and others should have read access so that the service user can read
|
|
|
+ provider_paths = [] # A service may depend on multiple configs
|
|
|
+ configtype_credentials = self.getConfigTypeCredentials(commandJson)
|
|
|
+ for config_type, credentials in configtype_credentials.items():
|
|
|
+ config = commandJson['configurations'][config_type]
|
|
|
+ file_path = os.path.join(self.getProviderDirectory(serviceName), "{0}.jceks".format(config_type))
|
|
|
+ if os.path.exists(file_path):
|
|
|
+ os.remove(file_path)
|
|
|
+ provider_path = 'jceks://file{file_path}'.format(file_path=file_path)
|
|
|
+ provider_paths.append(provider_path)
|
|
|
+ logger.info('provider_path={0}'.format(provider_path))
|
|
|
+ for alias, pwd in credentials.items():
|
|
|
+ logger.debug("config={0}".format(config))
|
|
|
+ protected_pwd = PasswordString(pwd)
|
|
|
+ # Generate the JCEKS file
|
|
|
+ cmd = (java_bin, '-cp', cs_lib_path, self.credential_shell_cmd, 'create',
|
|
|
+ alias, '-value', protected_pwd, '-provider', provider_path)
|
|
|
+ logger.info(cmd)
|
|
|
+ cmd_result = subprocess.call(cmd)
|
|
|
+ logger.info('cmd_result = {0}'.format(cmd_result))
|
|
|
+ os.chmod(file_path, 0644) # group and others should have read access so that the service user can read
|
|
|
+
|
|
|
+ if provider_paths:
|
|
|
+ # Add JCEKS provider paths instead
|
|
|
+ config[self.CREDENTIAL_PROVIDER_PROPERTY_NAME] = ','.join(provider_paths)
|
|
|
|
|
|
return cmd_result
|
|
|
|