Skip to content

Commit affec22

Browse files
committed
Mask password on agent lookup according to policy
We currently mask passwords in driver_info for all API responses, except for agent lookups. This is because driver_vendor_passthru just expects a dict to return as JSON in the response, and doesn't modify it at all. Change lookup to follow the defined policy when returning the node object in the response, the same way we do for other API responses. Co-authored-by: Jim Rollenhagen <[email protected]> Change-Id: Ib19d2f86d3527333e905fdbf1f09fc3dc8b8c5a7 Closes-Bug: #1572796 (cherry picked from commit 426a306)
1 parent f66a437 commit affec22

File tree

4 files changed

+38
-6
lines changed

4 files changed

+38
-6
lines changed

ironic/drivers/modules/agent_base_vendor.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# License for the specific language governing permissions and limitations
1717
# under the License.
1818

19+
import ast
1920
import collections
2021
import time
2122

@@ -535,9 +536,14 @@ def lookup(self, context, **kwargs):
535536
LOG.info(_LI('Initial lookup for node %s succeeded, agent is running '
536537
'and waiting for commands'), node.uuid)
537538

539+
ndict = node.as_dict()
540+
if not context.show_password:
541+
ndict['driver_info'] = ast.literal_eval(
542+
strutils.mask_password(ndict['driver_info'], "******"))
543+
538544
return {
539545
'heartbeat_timeout': CONF.agent.heartbeat_timeout,
540-
'node': node.as_dict()
546+
'node': ndict,
541547
}
542548

543549
def _get_completed_cleaning_command(self, task):

ironic/tests/unit/db/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ def get_test_agent_driver_info():
152152
return {
153153
'deploy_kernel': 'glance://deploy_kernel_uuid',
154154
'deploy_ramdisk': 'glance://deploy_ramdisk_uuid',
155+
'ipmi_password': 'foo',
155156
}
156157

157158

ironic/tests/unit/drivers/modules/test_agent_base_vendor.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# License for the specific language governing permissions and limitations
1616
# under the License.
1717

18+
import copy
1819
import time
1920
import types
2021

@@ -91,7 +92,8 @@ def test_lookup_version_not_found(self):
9192

9293
@mock.patch('ironic.drivers.modules.agent_base_vendor.BaseAgentVendor'
9394
'._find_node_by_macs', autospec=True)
94-
def test_lookup_v2(self, find_mock):
95+
def _test_lookup_v2(self, find_mock, show_password=True):
96+
self.context.show_password = show_password
9597
kwargs = {
9698
'version': '2',
9799
'inventory': {
@@ -108,10 +110,20 @@ def test_lookup_v2(self, find_mock):
108110
]
109111
}
110112
}
113+
# NOTE(jroll) apparently as_dict() returns a dict full of references
114+
expected = copy.deepcopy(self.node.as_dict())
115+
if not show_password:
116+
expected['driver_info']['ipmi_password'] = '******'
111117
find_mock.return_value = self.node
112118
with task_manager.acquire(self.context, self.node.uuid) as task:
113119
node = self.passthru.lookup(task.context, **kwargs)
114-
self.assertEqual(self.node.as_dict(), node['node'])
120+
self.assertEqual(expected, node['node'])
121+
122+
def test_lookup_v2_show_password(self):
123+
self._test_lookup_v2(show_password=True)
124+
125+
def test_lookup_v2_hide_password(self):
126+
self._test_lookup_v2(show_password=False)
115127

116128
def test_lookup_v2_missing_inventory(self):
117129
with task_manager.acquire(self.context, self.node.uuid) as task:
@@ -136,9 +148,11 @@ def test_lookup_v2_empty_interfaces(self):
136148

137149
@mock.patch.object(objects.Node, 'get_by_uuid')
138150
def test_lookup_v2_with_node_uuid(self, mock_get_node):
151+
self.context.show_password = True
152+
expected = copy.deepcopy(self.node.as_dict())
139153
kwargs = {
140154
'version': '2',
141-
'node_uuid': 'fake uuid',
155+
'node_uuid': 'fake-uuid',
142156
'inventory': {
143157
'interfaces': [
144158
{
@@ -156,8 +170,8 @@ def test_lookup_v2_with_node_uuid(self, mock_get_node):
156170
mock_get_node.return_value = self.node
157171
with task_manager.acquire(self.context, self.node.uuid) as task:
158172
node = self.passthru.lookup(task.context, **kwargs)
159-
self.assertEqual(self.node.as_dict(), node['node'])
160-
mock_get_node.assert_called_once_with(mock.ANY, 'fake uuid')
173+
self.assertEqual(expected, node['node'])
174+
mock_get_node.assert_called_once_with(mock.ANY, 'fake-uuid')
161175

162176
@mock.patch.object(objects.port.Port, 'get_by_address',
163177
spec_set=types.FunctionType)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
security:
3+
- A critical security vulnerability (CVE-2016-4985) was fixed in this
4+
release. Previously, a client with network access to the ironic-api service
5+
was able to bypass Keystone authentication and retrieve all information
6+
about any Node registered with Ironic, if they knew (or were able to guess)
7+
the MAC address of a network card belonging to that Node, by sending a
8+
crafted POST request to the /v1/drivers/$DRIVER_NAME/vendor_passthru
9+
resource. Ironic's policy.json configuration is now respected when
10+
responding to this request such that, if passwords should be masked for
11+
other requests, they are also masked for this request.

0 commit comments

Comments
 (0)