After I setup the keystone federation feature, it’s nice to go with the examples in official docs. However, I want to see more from it. :) First thing is that how do the rules to map federation protocol attributes to Identity API objects and how does SP manage the mapping users.
What’s Mapping
A mapping is a set of rules to map federation protocol attributes to Identity API objects. An Identity Provider can have a single mapping specified per protocol. A mapping is simply a list of rules.
As a simple example, if keystone is your IdP, you can map a few known remote users to the group you already created:
Value Setting in Mapping rules
A rule hierarchy looks as follows:
- rules: top-level list of rules.
- local: a rule containing information on what local attributes will be mapped.
- remote: a rule containing information on what remote attributes will be mapped.
- condition: contains information on conditions that allow a rule, can only be set in a remote rule.
Note: You can not set value arbitrary in remote rule. All the value must follow federation protocol attributes and the key should be type.
What’s Federation Protocol Attributes
Federation protocol attributes is the assertion sent by IdP. Normally You can see it in SP logs, and you can find openstack_user inside. It’s why we have to have “type”: “openstack_user” in the rule. Please refer to this if you want to have other values, such as SERVER_NAME, SERVER_PORT, etc.
How does keystone process mappings?
The main entry is from ‘keystone/federation/core.py’ and ‘keystone/federation/utils.py’ finishes the jobs. Take a look at the process function of RuleProcessor class, _verify_all_requirements function and _update_local_mapping function in utils.py.
why we have to use ‘type’? keystone will use ‘type’ as the key to get the value from assertion. If it’s None, it will cause a final failed in _transform function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
def _verify_all_requirements(self, requirements, assertion):
"""Compare remote requirements of a rule against the assertion.
If a value of ``None`` is returned, the rule with this assertion
doesn't apply.
If an array of zero length is returned, then there are no direct
mappings to be performed, but the rule is valid.
Otherwise, then it will first attempt to filter the values according
to blacklist or whitelist rules and finally return the values in
order, to be directly mapped.
:param requirements: list of remote requirements from rules
:type requirements: list
Example requirements::
[
{
"type": "UserName"
},
{
"type": "orgPersonType",
"any_one_of": [
"Customer"
]
},
{
"type": "ADFS_GROUPS",
"whitelist": [
"g1", "g2", "g3", "g4"
]
}
]
:param assertion: dict of attributes from an IdP
:type assertion: dict
Example assertion::
{
'UserName': ['testacct'],
'LastName': ['Account'],
'orgPersonType': ['Tester'],
'Email': ['testacct@example.com'],
'FirstName': ['Test'],
'ADFS_GROUPS': ['g1', 'g2']
}
:returns: identity values used to update local
:rtype: keystone.federation.utils.DirectMaps or None
"""
direct_maps = DirectMaps()
for requirement in requirements:
requirement_type = requirement['type']
direct_map_values = assertion.get(requirement_type)
regex = requirement.get('regex', False)
if not direct_map_values:
return None
any_one_values = requirement.get(self._EvalType.ANY_ONE_OF)
if any_one_values is not None:
if self._evaluate_requirement(any_one_values,
direct_map_values,
self._EvalType.ANY_ONE_OF,
regex):
continue
else:
return None
not_any_values = requirement.get(self._EvalType.NOT_ANY_OF)
if not_any_values is not None:
if self._evaluate_requirement(not_any_values,
direct_map_values,
self._EvalType.NOT_ANY_OF,
regex):
continue
else:
return None
# If 'any_one_of' or 'not_any_of' are not found, then values are
# within 'type'. Attempt to find that 'type' within the assertion,
# and filter these values if 'whitelist' or 'blacklist' is set.
blacklisted_values = requirement.get(self._EvalType.BLACKLIST)
whitelisted_values = requirement.get(self._EvalType.WHITELIST)
# If a blacklist or whitelist is used, we want to map to the
# whole list instead of just its values separately.
if blacklisted_values is not None:
direct_map_values = [v for v in direct_map_values
if v not in blacklisted_values]
elif whitelisted_values is not None:
direct_map_values = [v for v in direct_map_values
if v in whitelisted_values]
direct_maps.add(direct_map_values)
LOG.debug('updating a direct mapping: %s', direct_map_values)
return direct_maps