diff --git a/README.md b/README.md index 9a25cded2a..03a34e5e90 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,11 @@ Many organizations are using WebLogic Server, with or without other Oracle Fusio - [Model Names](#model-names) - [Model Semantics](#model-semantics) - [Administration Server Configuration](site/admin_server.md) - - [Modeling Security Providers](site/security_providers.md) - - [JRF Trust Service Identity Asserter](site/security_providers.md#trust-service-identity-asserter) - - [Custom Security Providers](site/security_providers.md#custom-security-providers) + - [Model Security](site/security.md) + - [Modeling Security Providers](site/security_providers.md) + - [JRF Trust Service Identity Asserter](site/security_providers.md#trust-service-identity-asserter) + - [Custom Security Providers](site/security_providers.md#custom-security-providers) + - [Modeling WebLogic Users, Groups, and Roles](site/security_users_groups_roles.md) - [Variable Injection](site/variable_injection.md) - [Model Filters](site/tool_filters.md) - [Downloading and Installing](#downloading-and-installing-the-software) diff --git a/core/src/main/python/wlsdeploy/aliases/alias_entries.py b/core/src/main/python/wlsdeploy/aliases/alias_entries.py index c214619280..fdf75ecec0 100644 --- a/core/src/main/python/wlsdeploy/aliases/alias_entries.py +++ b/core/src/main/python/wlsdeploy/aliases/alias_entries.py @@ -186,7 +186,8 @@ class AliasEntries(object): # servers in the domain. 'ServerGroupTargetingLimits': 'dict', 'RCUDbInfo': 'dict', - 'OPSSSecrets': 'string' + 'OPSSSecrets': 'password', + 'WLSRoles': 'dict' } __domain_name_token = 'DOMAIN' diff --git a/core/src/main/python/wlsdeploy/aliases/model_constants.py b/core/src/main/python/wlsdeploy/aliases/model_constants.py index b0b287be2a..f89a15dd76 100644 --- a/core/src/main/python/wlsdeploy/aliases/model_constants.py +++ b/core/src/main/python/wlsdeploy/aliases/model_constants.py @@ -17,6 +17,7 @@ ADMIN_USERNAME = 'AdminUserName' APP_DEPLOYMENTS = 'appDeployments' APP_DIR = 'AppDir' +APPEND = 'append' APPLICATION = 'Application' RCU_DB_INFO = 'RCUDbInfo' OPSS_SECRETS = 'OPSSSecrets' @@ -95,6 +96,7 @@ DYNAMIC_SERVERS = 'DynamicServers' ERROR_DESTINATION = 'ErrorDestination' EXECUTE_QUEUE = 'ExecuteQueue' +EXPRESSION = 'Expression' FAIR_SHARE_REQUEST_CLASS = 'FairShareRequestClass' FILE_OPEN = 'FileOpen' FILE_STORE = 'FileStore' @@ -183,11 +185,13 @@ PKI_CREDENTIAL_MAPPER = 'PKICredentialMapper' PLAN_DIR = 'PlanDir' PLAN_PATH = 'PlanPath' +PREPEND = 'prepend' PROPERTIES = 'Properties' QUEUE = 'Queue' QUOTA = 'Quota' REALM = 'Realm' RECOVER_ONLY_ONCE = 'RecoverOnlyOnce' +REPLACE = 'replace' RESOURCE_GROUP = 'ResourceGroup' RESOURCE_GROUP_TEMPLATE = 'ResourceGroupTemplate' RESOURCES = 'resources' @@ -248,6 +252,7 @@ UNIFORM_DISTRIBUTED_QUEUE = 'UniformDistributedQueue' UNIFORM_DISTRIBUTED_TOPIC = 'UniformDistributedTopic' UNIX_MACHINE = 'UnixMachine' +UPDATE_MODE = 'UpdateMode' USER = 'User' VIRTUAL_TARGET = 'VirtualTarget' VIRTUAL_USER_AUTHENTICATOR = 'VirtualUserAuthenticator' @@ -268,6 +273,7 @@ WLDF_INSTRUMENTATION_MONITOR = "WLDFInstrumentationMonitor" WLDF_RESOURCE = "WLDFResource" WLDF_SYSTEM_RESOURCE = "WLDFSystemResource" +WLS_ROLES = "WLSRoles" WS_RELIABLE_DELIVERY_POLICY = 'WSReliableDeliveryPolicy' XACML_AUTHORIZER = 'XACMLAuthorizer' XACML_ROLE_MAPPER = 'XACMLRoleMapper' diff --git a/core/src/main/python/wlsdeploy/tool/create/domain_creator.py b/core/src/main/python/wlsdeploy/tool/create/domain_creator.py index 1d9a763b73..be51f17534 100644 --- a/core/src/main/python/wlsdeploy/tool/create/domain_creator.py +++ b/core/src/main/python/wlsdeploy/tool/create/domain_creator.py @@ -77,6 +77,7 @@ from wlsdeploy.tool.create.creator import Creator from wlsdeploy.tool.create.rcudbinfo_helper import RcuDbInfo from wlsdeploy.tool.create.security_provider_creator import SecurityProviderCreator +from wlsdeploy.tool.create.wlsroles_helper import WLSRoles from wlsdeploy.tool.deploy import deployer_utils from wlsdeploy.tool.deploy import model_deployer from wlsdeploy.tool.util.archive_helper import ArchiveHelper @@ -148,6 +149,9 @@ def __init__(self, model_dictionary, model_context, aliases): self.target_helper = TargetHelper(self.model, self.model_context, self.aliases, ExceptionType.CREATE, self.logger) + self.wlsroles_helper = WLSRoles(self._domain_info, self._domain_home, self.wls_helper, + ExceptionType.CREATE, self.logger) + # # This list gets modified as the domain is being created so do use this list for anything else... # @@ -338,6 +342,7 @@ def __create_domain(self): self.library_helper.install_domain_libraries() self.library_helper.extract_classpath_libraries() + self.wlsroles_helper.process_roles() self.logger.exiting(class_name=self.__class_name, method_name=_method_name) return diff --git a/core/src/main/python/wlsdeploy/tool/create/wlsroles_helper.py b/core/src/main/python/wlsdeploy/tool/create/wlsroles_helper.py new file mode 100644 index 0000000000..a27f53214a --- /dev/null +++ b/core/src/main/python/wlsdeploy/tool/create/wlsroles_helper.py @@ -0,0 +1,189 @@ +""" +Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. +Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. +""" +from java.io import File +from wlsdeploy.aliases.model_constants import APPEND +from wlsdeploy.aliases.model_constants import EXPRESSION +from wlsdeploy.aliases.model_constants import PREPEND +from wlsdeploy.aliases.model_constants import REPLACE +from wlsdeploy.aliases.model_constants import UPDATE_MODE +from wlsdeploy.aliases.model_constants import WLS_ROLES +from wlsdeploy.util import dictionary_utils +from wlsdeploy.util import string_utils +from wlsdeploy.util.weblogic_roles_helper import WebLogicRolesHelper + +WLS_GLOBAL_ROLES = { + 'Admin': '?weblogic.entitlement.rules.AdministrativeGroup(Administrators)', + 'Deployer': '?weblogic.entitlement.rules.AdministrativeGroup(Deployers)', + 'Operator': '?weblogic.entitlement.rules.AdministrativeGroup(Operators)', + 'Monitor': '?weblogic.entitlement.rules.AdministrativeGroup(Monitors)', + 'Anonymous': 'Grp(everyone)', + 'AppTester': '?weblogic.entitlement.rules.OwnerIDDGroup(AppTesters)', + 'CrossDomainConnector': '?weblogic.entitlement.rules.OwnerIDDGroup(CrossDomainConnectors)', + 'AdminChannelUser': '?weblogic.entitlement.rules.OwnerIDDGroup(AdminChannelUsers)' +} +WLS_ROLE_UPDATE_OPERAND = '|' + +class WLSRoles(object): + """ + Handle the WLSRoles section from the model domainInfo + """ + __class_name = 'WLSRoles' + + def __init__(self, domain_info, domain_home, wls_helper, exception_type, logger, validation_roles_map = None): + self.logger = logger + self._wls_helper = wls_helper + self._wls_roles_map = None + self._domain_security_folder = None + self._validation_roles_map = validation_roles_map + + if domain_info is not None: + if not dictionary_utils.is_empty_dictionary_element(domain_info, WLS_ROLES): + self._wls_roles_map = domain_info[WLS_ROLES] + self._domain_security_folder = File(domain_home, 'security').getPath() + self._weblogic_roles_helper = WebLogicRolesHelper(logger, exception_type, self._domain_security_folder) + return + + def process_roles(self): + """ + Process the WebLogic roles contained in the domainInfo section of the model when specified + Support for the WebLogic roles handling is when using WebLogic Server 12.2.1 or greater + """ + _method_name = 'process_roles' + self.logger.entering(self._domain_security_folder, self._wls_roles_map, class_name=self.__class_name, method_name=_method_name) + if self._wls_roles_map is not None: + # Process the WLSRoles section after checking for proper version support + if self._wls_helper is not None and not self._wls_helper.is_weblogic_version_or_above('12.2.1'): + self.logger.warning('WLSDPLY-12504', self._wls_helper.get_actual_weblogic_version(), class_name=self.__class_name, method_name=_method_name) + else: + role_expressions = self._process_roles_map(self._wls_roles_map, None) + if role_expressions is not None and len(role_expressions) > 0: + self.logger.info('WLSDPLY-12500', role_expressions.keys(), class_name=self.__class_name, method_name=_method_name) + self._update_xacml_role_mapper(role_expressions) + self.logger.exiting(class_name=self.__class_name, method_name=_method_name) + return + + def validate_roles(self, validation_result): + """ + Validate WLSRoles section of the domainInfo independent of any domain home location + """ + _method_name = 'validate_roles' + self.logger.entering(self._validation_roles_map, class_name=self.__class_name, method_name=_method_name) + if self._validation_roles_map is not None and len(self._validation_roles_map) > 0: + self._validate_update_mode(self._validation_roles_map, validation_result) + self._process_roles_map(self._validation_roles_map, validation_result) + self.logger.exiting(class_name=self.__class_name, method_name=_method_name) + return validation_result + + def _process_roles_map(self, roles_map, validation_result): + """ + Loop through the WebLogic roles listed in the domainInfo and create a map of the role to the expression + """ + _method_name = '_process_roles_map' + self.logger.entering(class_name=self.__class_name, method_name=_method_name) + result = {} + for role in roles_map.keys(): + # Get the role expression and if the role should be an update to the default set of roles + expression = self._get_role_expression(role, roles_map) + if string_utils.is_empty(expression): + self.logger.finer('WLSDPLY-12501', role, class_name=self.__class_name, method_name=_method_name) + if validation_result is not None: + validation_result.add_warning('WLSDPLY-12501', role) + continue + update_role = self._is_role_update_mode(role, roles_map) + if update_role and role not in WLS_GLOBAL_ROLES: + self.logger.finer('WLSDPLY-12502', role, class_name=self.__class_name, method_name=_method_name) + if validation_result is not None: + validation_result.add_warning('WLSDPLY-12502', role) + update_role = False + # Add the role and expression to the map of roles to be processed + if update_role: + expression = self._update_role_epression(role, expression, roles_map) + result[role] = expression + + self.logger.exiting(class_name=self.__class_name, method_name=_method_name) + return result + + def _update_xacml_role_mapper(self, role_expression_map): + """ + Update the XACML role mapper based on the supplied map of WebLogic roles with expressions + """ + _method_name = '_update_xacml_role_mapper' + self.logger.entering(role_expression_map, class_name=self.__class_name, method_name=_method_name) + self._weblogic_roles_helper.update_xacml_role_mapper(role_expression_map) + self.logger.exiting(class_name=self.__class_name, method_name=_method_name) + return + + def _get_role_expression(self, role_name, roles_map): + """ + Determine if the role has an expression defined in the model + :return: the expression if the model value is present + """ + result = None + role_map = roles_map[role_name] + if EXPRESSION in role_map: + result = role_map[EXPRESSION] + return result + + def _is_role_update_mode(self, role_name, roles_map): + """ + Determine if the role update mode indicates that a role update is specified + :return: True if the update mode value is present and set to append or prepend mode + """ + result = False + role_map = roles_map[role_name] + if UPDATE_MODE in role_map: + mode = role_map[UPDATE_MODE] + if not string_utils.is_empty(mode): + mode = mode.lower() + if APPEND == mode or PREPEND == mode: + result = True + return result + + def _update_role_epression(self, role_name, expression_value, roles_map): + """ + Lookup the default role definition and logically OR the expression + Based on the update mode the expression is appended or prepended + :return: the updated role expression + """ + result = expression_value + role_map = roles_map[role_name] + if UPDATE_MODE in role_map: + mode = role_map[UPDATE_MODE] + if not string_utils.is_empty(mode): + mode = mode.lower() + if APPEND == mode: + result = WLS_GLOBAL_ROLES[role_name] + WLS_ROLE_UPDATE_OPERAND + expression_value + elif PREPEND == mode: + result = expression_value + WLS_ROLE_UPDATE_OPERAND + WLS_GLOBAL_ROLES[role_name] + return result + + def _validate_update_mode(self, roles_map, validation_result): + """ + Check that the UpdateMode value of a role is one of append, prepend or replace + Provide a warning for other values as these will be treated as the default of replace + """ + _method_name = '_validate_update_mode' + self.logger.entering(class_name=self.__class_name, method_name=_method_name) + + for role_name in roles_map.keys(): + role_map = roles_map[role_name] + if UPDATE_MODE in role_map: + mode = role_map[UPDATE_MODE] + if not string_utils.is_empty(mode): + mode = mode.lower() + if APPEND == mode or PREPEND == mode or REPLACE == mode: + continue + self.logger.finer('WLSDPLY-12503', role_name, class_name=self.__class_name, method_name=_method_name) + if validation_result is not None: + validation_result.add_warning('WLSDPLY-12503', role_name) + + self.logger.exiting(class_name=self.__class_name, method_name=_method_name) + return + +def validator(roles_map, logger): + """ + Obtain a WLSRoles helper only for the validation of the WLSRoles section + """ + return WLSRoles(None, None, None, None, logger, validation_roles_map = roles_map) diff --git a/core/src/main/python/wlsdeploy/tool/validate/validator.py b/core/src/main/python/wlsdeploy/tool/validate/validator.py index 695b08e4c1..cf391f0f23 100644 --- a/core/src/main/python/wlsdeploy/tool/validate/validator.py +++ b/core/src/main/python/wlsdeploy/tool/validate/validator.py @@ -16,6 +16,7 @@ from wlsdeploy.exception.expection_types import ExceptionType from wlsdeploy.exception import exception_helper from wlsdeploy.logging.platform_logger import PlatformLogger +from wlsdeploy.tool.create import wlsroles_helper from wlsdeploy.tool.util.alias_helper import AliasHelper from wlsdeploy.tool.util.archive_helper import ArchiveHelper from wlsdeploy.tool.validate import validation_utils @@ -32,6 +33,7 @@ from wlsdeploy.aliases.model_constants import NAME from wlsdeploy.aliases.model_constants import SERVER_GROUP_TARGETING_LIMITS from wlsdeploy.aliases.model_constants import TOPOLOGY +from wlsdeploy.aliases.model_constants import WLS_ROLES _class_name = 'Validator' _logger = PlatformLogger('wlsdeploy.validate') @@ -210,6 +212,17 @@ def print_usage(self, model_path, control_option=None): # #################################################################################### + def __get_attribute_log_value(self, attribute_name, attribute_value, attribute_infos): + """ + Get the log output for an attribute value to protect sensitive data + """ + result = attribute_value + if attribute_name in attribute_infos: + expected_data_type = dictionary_utils.get_element(attribute_infos, attribute_name) + if expected_data_type == 'password': + result = '' + return result + def __validate_model_file(self, model_dict, variables_file_name, archive_file_name): _method_name = '__validate_model_file' @@ -387,7 +400,8 @@ def __validate_domain_info_section(self, model_section_key, model_dict, validati if '${' in section_dict_key: validation_result.add_error('WLSDPLY-05035', model_folder_path, section_dict_key) - self._logger.finer('WLSDPLY-05011', section_dict_key, section_dict_value, + log_value = self.__get_attribute_log_value(section_dict_key, section_dict_value, valid_attr_infos) + self._logger.finer('WLSDPLY-05011', section_dict_key, log_value, class_name=_class_name, method_name=_method_name) if section_dict_key in valid_attr_infos: @@ -405,6 +419,14 @@ def __validate_domain_info_section(self, model_section_key, model_dict, validati model_folder_path, validation_location, validation_result) + elif section_dict_key == WLS_ROLES: + validation_result = self.__validate_wlsroles_section(section_dict_key, + section_dict_value, + valid_attr_infos, + path_tokens_attr_keys, + model_folder_path, + validation_location, + validation_result) else: validation_result = self.__validate_attribute(section_dict_key, section_dict_value, @@ -740,11 +762,7 @@ def __validate_attribute(self, attribute_name, attribute_value, valid_attr_infos model_folder_path, validation_location, validation_result): _method_name = '__validate_attribute' - log_value = attribute_value - expected_data_type = dictionary_utils.get_element(valid_attr_infos, attribute_name) - if expected_data_type == 'password': - log_value = '' - + log_value = self.__get_attribute_log_value(attribute_name, attribute_value, valid_attr_infos) self._logger.entering(attribute_name, log_value, str(valid_attr_infos), str(path_tokens_attr_keys), model_folder_path, str(validation_location), class_name=_class_name, method_name=_method_name) @@ -980,6 +998,27 @@ def __validate_server_group_targeting_limits(self, attribute_name, attribute_val self._logger.exiting(class_name=_class_name, method_name=__method_name) return validation_result + def __validate_wlsroles_section(self, attribute_name, attribute_value, valid_attr_infos, path_tokens_attr_keys, + model_folder_path, validation_location, validation_result): + __method_name = '__validate_wlsroles_section' + self._logger.entering(class_name=_class_name, method_name=__method_name) + + # Validate the basics of the WLSRoles section definition + validation_result = self.__validate_attribute(attribute_name, + attribute_value, + valid_attr_infos, + path_tokens_attr_keys, + model_folder_path, + validation_location, + validation_result) + + # Validate WebLogic role content using WLSRoles helper + wlsroles_validator = wlsroles_helper.validator(attribute_value, self._logger) + validation_result = wlsroles_validator.validate_roles(validation_result) + + self._logger.exiting(class_name=_class_name, method_name=__method_name) + return validation_result + def _validate_single_server_group_target_limits_value(key, value, model_folder_path, validation_result): if type(value) is str: diff --git a/core/src/main/python/wlsdeploy/util/weblogic_roles_helper.py b/core/src/main/python/wlsdeploy/util/weblogic_roles_helper.py new file mode 100644 index 0000000000..6dced8309e --- /dev/null +++ b/core/src/main/python/wlsdeploy/util/weblogic_roles_helper.py @@ -0,0 +1,171 @@ +""" +Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. +Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. +""" +import os +from java.io import File +from java.lang import String +from wlsdeploy.exception import exception_helper + +import com.bea.common.security.utils.encoders.BASE64Encoder as BASE64Encoder +import com.bea.common.security.xacml.DocumentParseException as DocumentParseException +import com.bea.common.security.xacml.URISyntaxException as URISyntaxException +import com.bea.security.providers.xacml.entitlement.EntitlementConverter as EntitlementConverter +import com.bea.security.xacml.cache.resource.ResourcePolicyIdUtil as ResourcePolicyIdUtil + +WLS_XACML_ROLE_MAPPER_LDIFT_FILENAME = 'XACMLRoleMapperInit.ldift' + +class WebLogicRolesHelper(object): + """ + Helper functions for handling WebLogic Roles + """ + __class_name = 'WebLogicRolesHelper' + + def __init__(self, logger, exception_type, domain_security_folder): + self.logger = logger + self._exception_type = exception_type + self._domain_security_folder = domain_security_folder + self._b64encoder = BASE64Encoder() + self._escaper = ResourcePolicyIdUtil.getEscaper() + self._converter = EntitlementConverter(None) + return + + def update_xacml_role_mapper(self, role_expressions_map): + _method_name = 'update_xacml_role_mapper' + self.logger.entering(class_name=self.__class_name, method_name=_method_name) + role_entries_map = self._create_xacml_role_mapper_entries(role_expressions_map) + self._update_xacml_role_mapper_ldift(role_entries_map) + self.logger.exiting(class_name=self.__class_name, method_name=_method_name) + return + + def _create_xacml_role_mapper_entries(self, role_expressions_map): + _method_name = '_create_xacml_role_mapper_entries' + self.logger.entering(class_name=self.__class_name, method_name=_method_name) + + entries = {} + if role_expressions_map is not None: + try: + for role_name in role_expressions_map.keys(): + # Get the role expression + role_expression = role_expressions_map[role_name] + # Convert the role expression + policy = self._converter.convertRoleExpression(None, role_name, role_expression, None) + role = self._escaper.escapeString(role_name) + cn = self._escaper.escapeString(policy.getId().toString()) + xacml = self._b64encoder.encodeBuffer(String(policy.toString()).getBytes("UTF-8")) + # Setup the lines that make up the role entry + entry = [] + entry.append('dn: cn=' + cn + '+xacmlVersion=1.0,ou=Policies,ou=XACMLRole,ou=@realm@,dc=@domain@\n') + entry.append('objectclass: top\n') + entry.append('objectclass: xacmlEntry\n') + entry.append('objectclass: xacmlRoleAssignmentPolicy\n') + entry.append('cn: ' + cn + '\n') + entry.append('xacmlVersion: 1.0\n') + entry.append('xacmlStatus: 3\n') + entry.append('xacmlDocument:: ' + xacml + '\n') + entry.append('xacmlRole: ' + role + '\n') + entry.append('\n') + # Add to the map of role entries + entries[role] = entry + + except DocumentParseException, dpe: + ex = exception_helper.create_exception(self._exception_type, 'WLSDPLY-01804', role_name, role_expression, dpe.getLocalizedMessage(), error=dpe) + self.logger.throwing(ex, class_name=self.__class_name, method_name=_method_name) + raise ex + except URISyntaxException, use: + ex = exception_helper.create_exception(self._exception_type, 'WLSDPLY-01804', role_name, role_expression, use.getLocalizedMessage(), error=use) + self.logger.throwing(ex, class_name=self.__class_name, method_name=_method_name) + raise ex + + self.logger.exiting(class_name=self.__class_name, method_name=_method_name) + return entries + + def _update_xacml_role_mapper_ldift(self, role_entries_map): + _method_name = '_update_xacml_role_mapper_ldift' + self.logger.entering(class_name=self.__class_name, method_name=_method_name) + + try: + # Determine the current XACML ldift file name + ldift_filename = File(self._domain_security_folder, WLS_XACML_ROLE_MAPPER_LDIFT_FILENAME).getPath() + self.logger.finer('WLSDPLY-01800', ldift_filename, class_name=self.__class_name, method_name=_method_name) + + # Update the XACML ldift file + ldift_file = None + update_file = None + update_filename = ldift_filename + '.new' + try: + update_file = open(update_filename, 'w') + ldift_file = open(ldift_filename) + # Look for existing entries to be replaced + for line in ldift_file: + if line.startswith('dn: cn='): + role_entry_lines = self.__read_xacml_role_entry(line, ldift_file) + role_name = self.__get_xacml_role_entry_name(role_entry_lines) + if role_name in role_entries_map: + self.logger.finer('WLSDPLY-01802', role_name, class_name=self.__class_name, method_name=_method_name) + update_file.writelines(role_entries_map[role_name]) + del role_entries_map[role_name] + else: + update_file.writelines(role_entry_lines) + else: + update_file.write(line) + # Append any remaining entries + role_names = role_entries_map.keys() + self.logger.finer('WLSDPLY-01803', role_names, class_name=self.__class_name, method_name=_method_name) + for role_name in role_names: + update_file.write('\n') + update_file.writelines(role_entries_map[role_name]) + finally: + if ldift_file is not None: + ldift_file.close() + if update_file is not None: + update_file.close() + + # Backup or remove the existing ldift file + backup_filename = ldift_filename + '.bak' + if not os.path.exists(backup_filename): + self.logger.finer('WLSDPLY-01801', backup_filename, class_name=self.__class_name, method_name=_method_name) + os.rename(ldift_filename, backup_filename) + else: + os.remove(ldift_filename) + + # Rename updated ldift file to the regular ldift file + os.rename(update_filename, ldift_filename) + + except ValueError, ve: + ex = exception_helper.create_exception(self._exception_type, 'WLSDPLY-01805', 'ValueError', str(ve), error=ve) + self.logger.throwing(ex, class_name=self.__class_name, method_name=_method_name) + raise ex + except IOError, ioe: + ex = exception_helper.create_exception(self._exception_type, 'WLSDPLY-01805', 'IOError', str(ioe), error=ioe) + self.logger.throwing(ex, class_name=self.__class_name, method_name=_method_name) + raise ex + except OSError, ose: + ex = exception_helper.create_exception(self._exception_type, 'WLSDPLY-01805', 'OSError', str(ose), error=ose) + self.logger.throwing(ex, class_name=self.__class_name, method_name=_method_name) + raise ex + + self.logger.exiting(class_name=self.__class_name, method_name=_method_name) + return + + def __read_xacml_role_entry(self, start_line, ldift_file): + count = 1 + lines = [start_line] + for line in ldift_file: + count += 1 + lines.append(line) + if count == 10: + break + + if len(lines) != 10: + raise ValueError('Error reading XACML role entry!') + + if not lines[-2].startswith('xacmlRole: '): + raise ValueError('Unable to find XACML role entry!') + + return lines + + def __get_xacml_role_entry_name(self, role_entry_lines): + role_name_entry = role_entry_lines[-2] + role_name = role_name_entry[10:] + return role_name.strip() diff --git a/core/src/main/resources/oracle/weblogic/deploy/messages/wlsdeploy_rb.properties b/core/src/main/resources/oracle/weblogic/deploy/messages/wlsdeploy_rb.properties index fedb0541f3..d987ad7c7a 100644 --- a/core/src/main/resources/oracle/weblogic/deploy/messages/wlsdeploy_rb.properties +++ b/core/src/main/resources/oracle/weblogic/deploy/messages/wlsdeploy_rb.properties @@ -314,6 +314,14 @@ WLSDPLY-01786=MBean {0} getter {1} is not an attribute on the mbean instance WLSDPLY-01787=The list of attributes to discover that are not in the LSA map {0} WLSDPLY-01788=Attribute {0} from {1} not found in {2} +# wlsdeploy/util/weblogic_roles_helper.py +WLSDPLY-01800=Updating role mapper file: {0} +WLSDPLY-01801=Creating backup file: {0} +WLSDPLY-01802=Updating role: {0} +WLSDPLY-01803=Adding roles: {0} +WLSDPLY-01804=Failed to convert role {0} with expression {1} because {2} +WLSDPLY-01805=Unexpected {0} during role mapper processing: {1} + ############################################################################### # Encrypt Messages (04000 - 04999) # ############################################################################### @@ -1125,6 +1133,13 @@ WLSDPLY-12412=The model file specified ATP database info without oracle.net.tns_ archive specified WLSDPLY-12413=The model file specified RCUDbInfo section but key {0} is None or missing. Required keys are {1} +# wlsroles_helper.py +WLSDPLY-12500=The WebLogic role mapper will be updated with the roles: {0} +WLSDPLY-12501=The role {0} has no specified expression value and will be ignored +WLSDPLY-12502=The role {0} is not a WebLogic global role and will use the expression as specified +WLSDPLY-12503=The role {0} specifies an invalid update mode and will use the default replace mode +WLSDPLY-12504=The processing of WebLogic roles from the model is not support with WebLogic Server version {0} + ############################################################################### # YAML/JSON messages (18000 - 18999) # ############################################################################### diff --git a/core/src/test/python/validation_test.py b/core/src/test/python/validation_test.py index 623a593e45..5e00252f83 100644 --- a/core/src/test/python/validation_test.py +++ b/core/src/test/python/validation_test.py @@ -19,6 +19,9 @@ import oracle.weblogic.deploy.util.TranslateException as TranslateException from oracle.weblogic.deploy.validate import ValidateException +from wlsdeploy.tool.create import wlsroles_helper +from wlsdeploy.tool.validate.validation_results import ValidationResult + class ValidationTestCase(unittest.TestCase): _program_name = 'validation_test' _class_name = 'ValidationTestCase' @@ -232,5 +235,43 @@ def testYamlModelValidation(self): self.assertNotEqual(return_code, Validator.ReturnCode.STOP) + def testWLSRolesValidation(self): + """ + Run the validation portion of the WLSRoles helper and check for expected results. + """ + _method_name = 'testWLSRolesValidation' + + validation_result = ValidationResult('Test WLSRoles Section') + wlsroles_dict = {'Admin': {'UpdateMode': 'append', + 'Expression': 'Grp(AppAdmin)'}, + 'Deployer': {'UpdateMode': 'prepend', + 'Expression': 'Grp(AppDeployer)'}, + 'Tester': {'Expression': 'Grp(AppTester)'}, + 'MyEmpty': { }, + 'MyTester': {'UpdateMode': 'append', + 'Expression': 'Grp(MyTester)'}, + 'MyTester2': {'UpdateMode': 'replace', + 'Expression': 'Grp(MyTester2)'}, + 'MyTester3': {'UpdateMode': 'bad', + 'Expression': 'Grp(MyTester3)'}} + + wlsroles_validator = wlsroles_helper.validator(wlsroles_dict, self._logger) + validation_result = wlsroles_validator.validate_roles(validation_result) + self._logger.info('The WLSRoles validation result is: {0}', validation_result, + class_name=self._class_name, method_name=_method_name) + + # Verify only warnings resulted + self.assertEqual(validation_result.get_errors_count(), 0) + self.assertEqual(validation_result.get_warnings_count(), 3) + self.assertEqual(validation_result.get_infos_count(), 0) + + # Verify each warning message is a different message id + msgid_dict = {} + warn_msg_list = validation_result.get_warnings_messages() + for warn_msg in warn_msg_list: + id = warn_msg['resource_id'] + msgid_dict[id] = 'true' + self.assertEqual(len(msgid_dict), 3) + if __name__ == '__main__': unittest.main() diff --git a/site/security.md b/site/security.md new file mode 100644 index 0000000000..4147fc8f8f --- /dev/null +++ b/site/security.md @@ -0,0 +1,7 @@ +### Model Security +Oracle WebLogic Server Deploy Tooling covers several aspects for the modeling of security including encryption of model data to protect sensitive information as well as the security configuration and handling of WebLogic domains. + +#### Table of Contents +- [Encrypt Model Tool](encrypt.md) +- [Modeling Security Providers](security_providers.md) +- [Modeling WebLogic Users, Groups, and Roles](security_users_groups_roles.md) diff --git a/site/security_users_groups_roles.md b/site/security_users_groups_roles.md new file mode 100644 index 0000000000..37e112c9e1 --- /dev/null +++ b/site/security_users_groups_roles.md @@ -0,0 +1,54 @@ +### Modeling WebLogic Users, Groups, and Roles +WebLogic Server has the ability to establish a set of users, groups, and global roles as part of the WebLogic domain creation. The WebLogic global roles become part of the WebLogic role mapper (i.e. `XACMLRoleMapper`) and are specified under `domainInfo` in the `WLSRoles` section. The users and groups become part of the Embedded LDAP server (i.e. `DefaultAuthenticator`) and are specified under `topology` in the `Security` section. + +#### WebLogic Global Roles +The model allows for the definition of WebLogic roles that can augment the well known WebLogic global roles (e.g. `Admin`, `Deployer`, `Monitor`, ...) in addition to defining new roles. When updating the well known WebLogic roles, an `UpdateMode` can be specified as `{ append | prepend | replace }` with the default being `replace` when not specified. Also, when updating the well known roles, the specified `Expression` will be a logical `OR` with the default expression. The `Expression` value for the role is the same as when using the WebLogic `RoleEditorMBean` for a WebLogic security role mapping provider. + +For example, the `WLSRoles` section below updates the well known `Admin`, `Deployer` and `Monitor` roles while adding a new global role with `Tester` as the role name: + +```yaml +domainInfo: + WLSRoles: + Admin: + UpdateMode: append + Expression: "?weblogic.entitlement.rules.IDCSAppRoleName(AppAdmin,@@PROP:AppName@@)" + Deployer: + UpdateMode: replace + Expression: "?weblogic.entitlement.rules.AdministrativeGroup(@@PROP:Deployers@@)" + Monitor: + UpdateMode: prepend + Expression: "?weblogic.entitlement.rules.AdministrativeGroup(AppMonitors)" + Tester: + Expression: "?weblogic.entitlement.rules.IDCSAppRoleName(AppTester,@@PROP:AppName@@)" +``` + +The `Admin` role will have the expression appended to the default expression, the `Deployer` role expression will replace the default, the `Monitor` role expression will be prepended to the default expression and `Tester` will be a new role with the specified expression. + +In addition, the `Expression` value can use the variable placeholder syntax specified when running the [Create Tool](create.md) as shown in the above example. + +#### WebLogic Users and Groups +The model allows for the definition of a set of users and groups that will be loaded into the WebLogic Embedded LDAP Server (i.e. `DefaultAuthenticator`). New groups can be specified and users can be added as members of the new groups or existing groups such as the `Administrators` group which is defaulted to be in the WebLogic `Admin` global role. Please see [known limitations](#known-limitations) below for additional information on users and groups. + +The user password can be specified with a placeholder or encrypted with the [Encrypt Tool](encrypt.md). An example `Security` section that adds an additional group `AppMonitors`, adds two new users and places the users into groups is as follows: + +```yaml +topology: + Security: + Group: + AppMonitors: + Description: Application Monitors + User: + john: + Password: welcome1 + GroupMemberOf: [ AppMonitors, Administrators ] + joe: + Password: welcome1 + GroupMemberOf: [ AppMonitors ] +``` + +#### Known Limitations + +- The processing of users, groups, and roles will only take place when using the [Create Domain Tool](create.md) +- WebLogic global roles are only supported with WebLogic Server version 12.2.1 or greater +- WebLogic global roles are only updated for the WebLogic security XACML role mapping provider (i.e. `XACMLRoleMapper`) +- The user and group processing is not complete, currently, users cannot be assigned to groups. Users created using the `Security` section are automatically added to the `Administrators` group and are not added to the groups specified. As soon as a patch to correct the user and group processing is available, we will post it here.