Skip to content

Commit b73f17a

Browse files
authored
Added support for cross-domain credential mapper (#710)
* Create default credential mapper init file during create * Revised model structure; revised template format; corrected hash values
1 parent f075a7c commit b73f17a

File tree

8 files changed

+367
-5
lines changed

8 files changed

+367
-5
lines changed

core/pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
3-
Copyright (c) 2017, 2019, Oracle Corporation and/or its affiliates. All rights reserved.
3+
Copyright (c) 2017, 2020, Oracle Corporation and/or its affiliates.
44
The Universal Permissive License (UPL), Version 1.0
55
-->
66
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
@@ -84,6 +84,7 @@
8484
<directory>src/main/resources</directory>
8585
<includes>
8686
<include>oracle/**/*.json</include>
87+
<include>oracle/**/*.ldift</include>
8788
<include>oracle/**/*.properties</include>
8889
<include>oracle/**/*.yaml</include>
8990
</includes>

core/src/main/python/wlsdeploy/aliases/alias_entries.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@
5151
from wlsdeploy.aliases.model_constants import APPLICATION
5252
from wlsdeploy.aliases.model_constants import DOMAIN_INFO
5353
from wlsdeploy.aliases.model_constants import DOMAIN_INFO_ALIAS
54-
from wlsdeploy.aliases.model_constants import KUBERNETES_ALIAS
5554
from wlsdeploy.aliases.model_constants import JOLT_CONNECTION_POOL
5655
from wlsdeploy.aliases.model_constants import JPA
5756
from wlsdeploy.aliases.model_constants import KUBERNETES
57+
from wlsdeploy.aliases.model_constants import KUBERNETES_ALIAS
5858
from wlsdeploy.aliases.model_constants import ODL_CONFIGURATION
5959
from wlsdeploy.aliases.model_constants import OHS
6060
from wlsdeploy.aliases.model_constants import RCU_DB_INFO
@@ -64,6 +64,7 @@
6464
from wlsdeploy.aliases.model_constants import SYSTEM_COMPONENT
6565
from wlsdeploy.aliases.model_constants import TOPOLOGY
6666
from wlsdeploy.aliases.model_constants import WLS_ROLES
67+
from wlsdeploy.aliases.model_constants import WLS_USER_PASSWORD_CREDENTIAL_MAPPINGS
6768
from wlsdeploy.aliases.model_constants import WTC_SERVER
6869
from wlsdeploy.aliases.validation_codes import ValidationCodes
6970
from wlsdeploy.aliases.wlst_modes import WlstModes
@@ -154,7 +155,8 @@ class AliasEntries(object):
154155

155156
__domain_info_top_level_folders = [
156157
RCU_DB_INFO,
157-
WLS_ROLES
158+
WLS_ROLES,
159+
WLS_USER_PASSWORD_CREDENTIAL_MAPPINGS
158160
]
159161

160162
__kubernetes_top_level_folders = [

core/src/main/python/wlsdeploy/aliases/model_constants.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
CREDENTIAL = 'Credential'
8181
CREDENTIAL_ENCRYPTED = 'CredentialEncrypted'
8282
CREDENTIAL_MAPPER = 'CredentialMapper'
83+
CROSS_DOMAIN = 'CrossDomain'
8384
CUSTOM_DBMS_AUTHENTICATOR = 'CustomDBMSAuthenticator'
8485
DATA_SOURCE = 'DataSource'
8586
DEFAULT_ADJUDICATOR = 'DefaultAdjudicator'
@@ -180,6 +181,7 @@
180181
MIN_THREADS_CONSTRAINT = 'MinThreadsConstraint'
181182
MESSAGE_LOGGING_PARAMS = 'MessageLoggingParams'
182183
MESSAGING_BRIDGE = 'MessagingBridge'
184+
METHOD = 'Method'
183185
MULTICAST = 'Multicast'
184186
MULTICAST_ADDRESS = 'MulticastAddress'
185187
MULTICAST_PORT = 'MulticastPort'
@@ -203,6 +205,7 @@
203205
PARTITION_USER_FILE = 'UserFileSystem'
204206
PARTITION_WORK_MANAGER = 'PartitionWorkManager'
205207
PASSWORD_VALIDATOR = 'PasswordValidator'
208+
PATH = 'Path'
206209
PATH_SERVICE = 'PathService'
207210
PASSWORD_ENCRYPTED = 'PasswordEncrypted'
208211
PERSISTENT_STORE = 'PersistentStore'
@@ -212,10 +215,16 @@
212215
PREPEND = 'prepend'
213216
PROPERTIES = 'Properties'
214217
PRODUCTION_MODE_ENABLED = 'ProductionModeEnabled'
218+
PROTOCOL = 'Protocol'
215219
QUEUE = 'Queue'
216220
QUOTA = 'Quota'
217221
REALM = 'Realm'
218222
RECOVER_ONLY_ONCE = 'RecoverOnlyOnce'
223+
REMOTE_DOMAIN = 'RemoteDomain'
224+
REMOTE_HOST = 'RemoteHost'
225+
REMOTE_PASSWORD = 'RemotePassword'
226+
REMOTE_PORT = 'RemotePort'
227+
REMOTE_USER = 'RemoteUser'
219228
REPLACE = 'replace'
220229
RESOURCE_GROUP = 'ResourceGroup'
221230
RESOURCE_GROUP_TEMPLATE = 'ResourceGroupTemplate'
@@ -303,6 +312,7 @@
303312
WLDF_RESOURCE = "WLDFResource"
304313
WLDF_SYSTEM_RESOURCE = "WLDFSystemResource"
305314
WLS_ROLES = "WLSRoles"
315+
WLS_USER_PASSWORD_CREDENTIAL_MAPPINGS = 'WLSUserPasswordCredentialMappings'
306316
WS_RELIABLE_DELIVERY_POLICY = 'WSReliableDeliveryPolicy'
307317
WTC_SERVER = 'WTCServer'
308318
XACML_AUTHORIZER = 'XACMLAuthorizer'

core/src/main/python/wlsdeploy/tool/create/domain_creator.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
from java.lang import IllegalArgumentException
1010
from java.util import Properties
1111
from oracle.weblogic.deploy.create import RCURunner
12-
from oracle.weblogic.deploy.util import WLSDeployArchive, FileUtils
13-
from wlsdeploy.util import string_utils
12+
from oracle.weblogic.deploy.util import FileUtils
13+
1414
from wlsdeploy.aliases.location_context import LocationContext
1515
from wlsdeploy.aliases.model_constants import ADMIN_PASSWORD
1616
from wlsdeploy.aliases.model_constants import ADMIN_SERVER_NAME
@@ -71,6 +71,7 @@
7171
from wlsdeploy.aliases.model_constants import URL
7272
from wlsdeploy.aliases.model_constants import USER
7373
from wlsdeploy.aliases.model_constants import VIRTUAL_TARGET
74+
from wlsdeploy.aliases.model_constants import WLS_USER_PASSWORD_CREDENTIAL_MAPPINGS
7475
from wlsdeploy.aliases.model_constants import WS_RELIABLE_DELIVERY_POLICY
7576
from wlsdeploy.aliases.model_constants import XML_ENTITY_CACHE
7677
from wlsdeploy.aliases.model_constants import XML_REGISTRY
@@ -84,13 +85,15 @@
8485
from wlsdeploy.tool.deploy import deployer_utils
8586
from wlsdeploy.tool.deploy import model_deployer
8687
from wlsdeploy.tool.util.archive_helper import ArchiveHelper
88+
from wlsdeploy.tool.util.credential_map_helper import CredentialMapHelper
8789
from wlsdeploy.tool.util.library_helper import LibraryHelper
8890
from wlsdeploy.tool.util.rcu_helper import RCUHelper
8991
from wlsdeploy.tool.util.target_helper import TargetHelper
9092
from wlsdeploy.tool.util.targeting_types import TargetingType
9193
from wlsdeploy.tool.util.topology_helper import TopologyHelper
9294
from wlsdeploy.util import dictionary_utils
9395
from wlsdeploy.util import model as model_helper
96+
from wlsdeploy.util import string_utils
9497

9598

9699
class DomainCreator(Creator):
@@ -177,6 +180,7 @@ def create(self):
177180
self.__deploy()
178181
self.__deploy_after_update()
179182
self.__create_boot_dot_properties()
183+
self.__create_credential_mappings()
180184

181185
self.logger.exiting(class_name=self.__class_name, method_name=_method_name)
182186
return
@@ -1242,6 +1246,15 @@ def __create_boot_dot_properties(self):
12421246
self.logger.exiting(class_name=self.__class_name, method_name=_method_name)
12431247
return
12441248

1249+
def __create_credential_mappings(self):
1250+
"""
1251+
Create credential mappings from model elements.
1252+
"""
1253+
default_nodes = dictionary_utils.get_dictionary_element(self._domain_info, WLS_USER_PASSWORD_CREDENTIAL_MAPPINGS)
1254+
if default_nodes:
1255+
credential_map_helper = CredentialMapHelper(self.model_context, ExceptionType.CREATE)
1256+
credential_map_helper.create_default_init_file(default_nodes)
1257+
12451258
def __configure_opss_secrets(self):
12461259
_method_name = '__configure_opss_secrets'
12471260

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
"""
2+
Copyright (c) 2020, Oracle Corporation and/or its affiliates.
3+
Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
4+
"""
5+
import com.bea.common.security.utils.encoders.BASE64Encoder as BASE64Encoder
6+
import com.bea.security.xacml.cache.resource.ResourcePolicyIdUtil as ResourcePolicyIdUtil
7+
from java.io import File
8+
from java.lang import String
9+
from oracle.weblogic.deploy.aliases import TypeUtils
10+
11+
from wlsdeploy.aliases.model_constants import CROSS_DOMAIN
12+
from wlsdeploy.aliases.model_constants import METHOD
13+
from wlsdeploy.aliases.model_constants import PATH
14+
from wlsdeploy.aliases.model_constants import PROTOCOL
15+
from wlsdeploy.aliases.model_constants import REMOTE_DOMAIN
16+
from wlsdeploy.aliases.model_constants import REMOTE_HOST
17+
from wlsdeploy.aliases.model_constants import REMOTE_PASSWORD
18+
from wlsdeploy.aliases.model_constants import REMOTE_PORT
19+
from wlsdeploy.aliases.model_constants import REMOTE_USER
20+
from wlsdeploy.aliases.model_constants import USER
21+
from wlsdeploy.exception import exception_helper
22+
from wlsdeploy.logging.platform_logger import PlatformLogger
23+
from wlsdeploy.tool.util.targets import file_template_helper
24+
from wlsdeploy.util import dictionary_utils
25+
from wlsdeploy.util.weblogic_helper import WebLogicHelper
26+
27+
DEFAULT_MAPPER_INIT_FILE = 'DefaultCredentialMapperInit.ldift'
28+
SECURITY_SUBDIR = 'security'
29+
TEMPLATE_PATH = 'oracle/weblogic/deploy/security'
30+
31+
CREDENTIAL_MAPPINGS = 'credentialMappings'
32+
RESOURCE_MAPPINGS = 'resourceMappings'
33+
34+
CROSS_DOMAIN_PROTOCOL = 'cross-domain-protocol'
35+
CROSS_DOMAIN_USER = 'cross-domain'
36+
NULL = 'null'
37+
38+
# template hash constants
39+
HASH_CREDENTIAL_CN = 'credentialCn'
40+
HASH_LOCAL_USER = 'localUser'
41+
HASH_PASSWORD_ENCODED = 'passwordEncoded'
42+
HASH_REMOTE_USER = 'remoteUser'
43+
HASH_RESOURCE_CN = 'resourceCn'
44+
HASH_RESOURCE_NAME = 'resourceName'
45+
46+
# keys in resource name
47+
ID_METHOD = 'method'
48+
ID_PATH = 'path'
49+
ID_PROTOCOL = 'protocol'
50+
ID_REMOTE_HOST = 'remoteHost'
51+
ID_REMOTE_PORT = 'remotePort'
52+
53+
# ordered to match resource name output
54+
RESOURCE_FIELDS = [
55+
ID_PROTOCOL,
56+
ID_REMOTE_HOST,
57+
ID_REMOTE_PORT,
58+
ID_PATH,
59+
ID_METHOD
60+
]
61+
62+
63+
class CredentialMapHelper(object):
64+
"""
65+
Creates .ldift initialization file for user/password credential mappings
66+
"""
67+
_class_name = 'CredentialMapHelper'
68+
69+
def __init__(self, model_context, exception_type):
70+
"""
71+
Initialize an instance of CredentialMapHelper.
72+
:param model_context: used to find domain home
73+
:param exception_type: the type of exception to be thrown
74+
"""
75+
self._model_context = model_context
76+
self._exception_type = exception_type
77+
self._logger = PlatformLogger('wlsdeploy.tool.util')
78+
self._weblogic_helper = WebLogicHelper(self._logger)
79+
self._resource_escaper = ResourcePolicyIdUtil.getEscaper()
80+
self._b64_encoder = BASE64Encoder()
81+
82+
def create_default_init_file(self, default_mapping_nodes):
83+
"""
84+
Create a .ldift file to initialize default credential mappers.
85+
Build a hash map for use with a template file resource.
86+
Write the file to a known location in the domain.
87+
:param default_mapping_nodes: the credential mapping elements from the model
88+
"""
89+
_method_name = 'create_default_init_file'
90+
91+
template_hash = self._build_default_template_hash(default_mapping_nodes)
92+
template_path = TEMPLATE_PATH + '/' + DEFAULT_MAPPER_INIT_FILE
93+
94+
output_dir = File(self._model_context.get_domain_home(), SECURITY_SUBDIR)
95+
output_file = File(output_dir, DEFAULT_MAPPER_INIT_FILE)
96+
97+
self._logger.info('WLSDPLY-01790', output_file, class_name=self._class_name, method_name=_method_name)
98+
99+
file_template_helper.create_file(template_path, template_hash, output_file, self._exception_type)
100+
101+
def _build_default_template_hash(self, mapping_section_nodes):
102+
"""
103+
Create a dictionary of substitution values to apply to the default credential mappers template.
104+
:param mapping_section_nodes: the credential mapping elements from the model
105+
:return: the template hash dictionary
106+
"""
107+
template_hash = dict()
108+
109+
credential_mappings = []
110+
resource_mappings = []
111+
112+
for mapping_type in mapping_section_nodes.keys():
113+
mapping_name_nodes = mapping_section_nodes[mapping_type]
114+
for mapping_name in mapping_name_nodes.keys():
115+
mapping = mapping_name_nodes[mapping_name]
116+
mapping_hash = self._build_mapping_hash(mapping_type, mapping_name, mapping)
117+
118+
# add a hash with remote target details to create a single passwordCredentialMap element
119+
credential_mappings.append(mapping_hash)
120+
121+
# add a modified hash for each local user to create resourceMap elements
122+
resource_name = mapping_hash[HASH_RESOURCE_NAME]
123+
local_users = self._get_local_users(mapping_type, mapping_name, mapping)
124+
for local_user in local_users:
125+
resource_hash = dict(mapping_hash)
126+
resource_hash[HASH_LOCAL_USER] = local_user
127+
resource_hash[HASH_RESOURCE_CN] = self._create_cn(resource_name, local_user)
128+
resource_mappings.append(resource_hash)
129+
130+
template_hash[CREDENTIAL_MAPPINGS] = credential_mappings
131+
template_hash[RESOURCE_MAPPINGS] = resource_mappings
132+
return template_hash
133+
134+
def _build_mapping_hash(self, mapping_type, mapping_name, mapping):
135+
"""
136+
Build a template hash for the specified mapping element from the model.
137+
:param mapping_type: the type of the mapping, such as 'CrossDomain'
138+
:param mapping_name: the mapping name from the model, such as 'map1'
139+
:param mapping: the mapping element from the model
140+
:return: the template hash
141+
"""
142+
resource_name = self._build_resource_name(mapping_type, mapping_name, mapping)
143+
144+
remote_user = self._get_required_attribute(mapping, REMOTE_USER, mapping_type, mapping_name)
145+
credential_cn = self._create_cn(resource_name, remote_user)
146+
147+
# the password needs to be encrypted, then base64 encoded
148+
password = self._get_required_attribute(mapping, REMOTE_PASSWORD, mapping_type, mapping_name)
149+
encrypted = self._weblogic_helper.encrypt(password, self._model_context.get_domain_home())
150+
password_encoded = self._b64_encoder.encodeBuffer(String(encrypted).getBytes("UTF-8"))
151+
152+
# the local user and resource CN will be updated later for each user
153+
return {
154+
HASH_CREDENTIAL_CN: credential_cn,
155+
HASH_LOCAL_USER: NULL,
156+
HASH_PASSWORD_ENCODED: password_encoded,
157+
HASH_REMOTE_USER: remote_user,
158+
HASH_RESOURCE_CN: NULL,
159+
HASH_RESOURCE_NAME: resource_name
160+
}
161+
162+
def _build_resource_name(self, mapping_type, mapping_name, mapping):
163+
"""
164+
Build the resource name based on elements in the mapping element from the model.
165+
Example: type=<remote>, protocol=http, remoteHost=my.host, remotePort=7020, path=/myapp, method=POST
166+
:param mapping_type: the type of the mapping, such as 'CrossDomain'
167+
:param mapping_name: the mapping name from the model, such as 'map1'
168+
:param mapping: the mapping element from the model
169+
:return: the resource name
170+
"""
171+
172+
# for cross-domain mapping, use domain for remote host, and set cross-domain protocol
173+
if mapping_type == CROSS_DOMAIN:
174+
remote_host = self._get_required_attribute(mapping, REMOTE_DOMAIN, mapping_type, mapping_name)
175+
protocol = CROSS_DOMAIN_PROTOCOL
176+
else:
177+
remote_host = self._get_required_attribute(mapping, REMOTE_HOST, mapping_type, mapping_name)
178+
protocol = dictionary_utils.get_element(mapping, PROTOCOL)
179+
180+
# build a map of available values, some may be None
181+
resource_name_values = {
182+
ID_METHOD: dictionary_utils.get_element(mapping, METHOD),
183+
ID_PATH: dictionary_utils.get_element(mapping, PATH),
184+
ID_PROTOCOL: protocol,
185+
ID_REMOTE_HOST: remote_host,
186+
ID_REMOTE_PORT: dictionary_utils.get_element(mapping, REMOTE_PORT)
187+
}
188+
189+
# build the resource name string
190+
resource_name = 'type=<remote>'
191+
for field in RESOURCE_FIELDS:
192+
value = dictionary_utils.get_element(resource_name_values, field)
193+
if value is not None:
194+
resource_name += ', %s=%s' % (field, value)
195+
196+
return resource_name
197+
198+
def _get_local_users(self, mapping_type, mapping_name, mapping):
199+
"""
200+
Get the local users list, based on the mapping element from the model.
201+
:param mapping_type: the type of the mapping, such as 'CrossDomain'
202+
:param mapping_name: the mapping name from the model, such as 'map1'
203+
:param mapping: the mapping element from the model
204+
:return: a list of local users
205+
"""
206+
if mapping_type == CROSS_DOMAIN:
207+
return [CROSS_DOMAIN_USER]
208+
209+
local_user_value = self._get_required_attribute(mapping, USER, mapping_type, mapping_name)
210+
return TypeUtils.convertToType(list, local_user_value)
211+
212+
def _get_required_attribute(self, dictionary, name, mapping_type, mapping_name):
213+
"""
214+
Return the value of the specified attribute from the specified dictionary.
215+
Log and throw an exception if the attribute is not found.
216+
:param dictionary: the dictionary to be checked
217+
:param name: the name of the attribute to find
218+
:param mapping_type: the type of the mapping, such as 'CrossDomain'
219+
:param mapping_name: the mapping name from the model, such as 'map1'
220+
:return: the value of the attribute
221+
:raises: Tool type exception: if an the attribute is not found
222+
"""
223+
_method_name = '_get_required_attribute'
224+
225+
result = dictionary_utils.get_element(dictionary, name)
226+
if result is None:
227+
pwe = exception_helper.create_exception(self._exception_type, 'WLSDPLY-01791', name, mapping_type,
228+
mapping_name)
229+
self._logger.throwing(class_name=self._class_name, method_name=_method_name, error=pwe)
230+
raise pwe
231+
return result
232+
233+
def _create_cn(self, resource_name, user):
234+
"""
235+
Create a CN string from the specified resource name and user name.
236+
The result should be escaped for use as a CN.
237+
:param resource_name: the name of the resource
238+
:param user: the user name
239+
:return: the CN string
240+
"""
241+
name = resource_name + "." + user
242+
return self._resource_escaper.escapeString(name)

0 commit comments

Comments
 (0)