CVE-2017-12440
Description
Aodh as packaged in Openstack Ocata and Newton before change-ID I8fd11a7f9fe3c0ea5f9843a89686ac06713b7851 and before Pike-rc1 does not verify that trust IDs belong to the user when creating alarm action with the scheme trust+http, which allows remote authenticated users with knowledge of trust IDs where Aodh is the trustee to obtain a Keystone token and perform unspecified authenticated actions by adding an alarm action with the scheme trust+http, and providing a trust id where Aodh is the trustee.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
aodhPyPI | < 6.0.1 | 6.0.1 |
Affected products
1Patches
2149d3ad2193bDon't include trust IDs in Alarm action output
2 files changed · +40 −5
aodh/api/controllers/v2/alarms.py+28 −4 modified@@ -269,6 +269,19 @@ def __init__(self, rule=None, time_constraints=None, **kwargs): self.time_constraints = [AlarmTimeConstraint(**tc) for tc in time_constraints] + @classmethod + def from_db_model_scrubbed(cls, m): + # Return an Alarm from a DB model with trust IDs scrubbed from actions + data = m.as_dict() + + for field in ('ok_actions', 'alarm_actions', + 'insufficient_data_actions'): + if data.get(field) is not None: + data[field] = [cls._scrub_action_url(action) + for action in data[field]] + + return cls(**data) + @staticmethod def validate(alarm): Alarm.check_rule(alarm) @@ -380,6 +393,17 @@ def as_dict(self, db_model): def _is_trust_url(url): return url.scheme.startswith('trust+') + @staticmethod + def _scrub_action_url(action): + """Remove trust ID from a URL.""" + url = netutils.urlsplit(action) + if Alarm._is_trust_url(url): + netloc = url.netloc.rsplit('@', 1)[-1] + url = urlparse.SplitResult(url.scheme, netloc, + url.path, url.query, + url.fragment) + return url.geturl() + def _get_existing_trust_ids(self): for action in itertools.chain(self.ok_actions or [], self.alarm_actions or [], @@ -590,7 +614,7 @@ def _record_delete(self, alarm): @wsme_pecan.wsexpose(Alarm) def get(self): """Return this alarm.""" - return Alarm.from_db_model(self._enforce_rbac('get_alarm')) + return Alarm.from_db_model_scrubbed(self._enforce_rbac('get_alarm')) @wsme_pecan.wsexpose(Alarm, body=Alarm) def put(self, data): @@ -642,7 +666,7 @@ def put(self, data): if v != old_alarm[k] and k not in ['timestamp', 'state_timestamp']) self._record_change(change, now, on_behalf_of=alarm.project_id) - return Alarm.from_db_model(alarm) + return Alarm.from_db_model_scrubbed(alarm) @wsme_pecan.wsexpose(None, status_code=204) def delete(self): @@ -805,7 +829,7 @@ def _set_ownership(aspect, owner_limitation, header): alarm = conn.create_alarm(alarm_in) self._record_creation(conn, change, alarm.alarm_id, now) v2_utils.set_resp_location_hdr("/alarms/" + alarm.alarm_id) - return Alarm.from_db_model(alarm) + return Alarm.from_db_model_scrubbed(alarm) @wsme_pecan.wsexpose([Alarm], [base.Query], [str], int, str) def get_all(self, q=None, sort=None, limit=None, marker=None): @@ -829,5 +853,5 @@ def get_all(self, q=None, sort=None, limit=None, marker=None): if sort or limit or marker: kwargs['pagination'] = v2_utils.get_pagination_options( sort, limit, marker, models.Alarm) - return [Alarm.from_db_model(m) + return [Alarm.from_db_model_scrubbed(m) for m in pecan.request.storage.get_alarms(**kwargs)]
aodh/tests/functional/api/v2/test_alarm_scenarios.py+12 −1 modified@@ -1207,6 +1207,10 @@ def test_post_alarm_trust(self): else: self.fail("Alarm not found") + data = self._get_alarm(alarm.alarm_id) + self.assertEqual( + ['trust+http://my.server:1234/foo'], data['ok_actions']) + with mock.patch('aodh.keystone_client.get_client') as client: client.return_value = mock.Mock( auth_ref=mock.Mock(user_id='my_user')) @@ -1419,9 +1423,16 @@ def test_put_alarm_trust(self): self.put_json('/alarms/%s' % data['alarm_id'], params=data, headers=self.auth_headers) + + for alarm in list(self.alarm_conn.get_alarms()): + if alarm.alarm_id == data['alarm_id']: + self.assertEqual( + ['trust+http://5678:delete@something/ok'], + alarm.ok_actions) + break data = self._get_alarm('a') self.assertEqual( - ['trust+http://5678:delete@something/ok'], data['ok_actions']) + ['trust+http://something/ok'], data['ok_actions']) data.update({'ok_actions': ['http://no-trust-something/ok']})
92182de328d1Don't include trust IDs in Alarm action output
2 files changed · +40 −5
aodh/api/controllers/v2/alarms.py+28 −4 modified@@ -269,6 +269,19 @@ def __init__(self, rule=None, time_constraints=None, **kwargs): self.time_constraints = [AlarmTimeConstraint(**tc) for tc in time_constraints] + @classmethod + def from_db_model_scrubbed(cls, m): + # Return an Alarm from a DB model with trust IDs scrubbed from actions + data = m.as_dict() + + for field in ('ok_actions', 'alarm_actions', + 'insufficient_data_actions'): + if data.get(field) is not None: + data[field] = [cls._scrub_action_url(action) + for action in data[field]] + + return cls(**data) + @staticmethod def validate(alarm): Alarm.check_rule(alarm) @@ -380,6 +393,17 @@ def as_dict(self, db_model): def _is_trust_url(url): return url.scheme.startswith('trust+') + @staticmethod + def _scrub_action_url(action): + """Remove trust ID from a URL.""" + url = netutils.urlsplit(action) + if Alarm._is_trust_url(url): + netloc = url.netloc.rsplit('@', 1)[-1] + url = urlparse.SplitResult(url.scheme, netloc, + url.path, url.query, + url.fragment) + return url.geturl() + def _get_existing_trust_ids(self): for action in itertools.chain(self.ok_actions or [], self.alarm_actions or [], @@ -590,7 +614,7 @@ def _record_delete(self, alarm): @wsme_pecan.wsexpose(Alarm) def get(self): """Return this alarm.""" - return Alarm.from_db_model(self._enforce_rbac('get_alarm')) + return Alarm.from_db_model_scrubbed(self._enforce_rbac('get_alarm')) @wsme_pecan.wsexpose(Alarm, body=Alarm) def put(self, data): @@ -642,7 +666,7 @@ def put(self, data): if v != old_alarm[k] and k not in ['timestamp', 'state_timestamp']) self._record_change(change, now, on_behalf_of=alarm.project_id) - return Alarm.from_db_model(alarm) + return Alarm.from_db_model_scrubbed(alarm) @wsme_pecan.wsexpose(None, status_code=204) def delete(self): @@ -805,7 +829,7 @@ def _set_ownership(aspect, owner_limitation, header): alarm = conn.create_alarm(alarm_in) self._record_creation(conn, change, alarm.alarm_id, now) v2_utils.set_resp_location_hdr("/alarms/" + alarm.alarm_id) - return Alarm.from_db_model(alarm) + return Alarm.from_db_model_scrubbed(alarm) @wsme_pecan.wsexpose([Alarm], [base.Query], [str], int, str) def get_all(self, q=None, sort=None, limit=None, marker=None): @@ -829,5 +853,5 @@ def get_all(self, q=None, sort=None, limit=None, marker=None): if sort or limit or marker: kwargs['pagination'] = v2_utils.get_pagination_options( sort, limit, marker, models.Alarm) - return [Alarm.from_db_model(m) + return [Alarm.from_db_model_scrubbed(m) for m in pecan.request.storage.get_alarms(**kwargs)]
aodh/tests/functional/api/v2/test_alarm_scenarios.py+12 −1 modified@@ -1207,6 +1207,10 @@ def test_post_alarm_trust(self): else: self.fail("Alarm not found") + data = self._get_alarm(alarm.alarm_id) + self.assertEqual( + ['trust+http://my.server:1234/foo'], data['ok_actions']) + with mock.patch('aodh.keystone_client.get_client') as client: client.return_value = mock.Mock( auth_ref=mock.Mock(user_id='my_user')) @@ -1419,9 +1423,16 @@ def test_put_alarm_trust(self): self.put_json('/alarms/%s' % data['alarm_id'], params=data, headers=self.auth_headers) + + for alarm in list(self.alarm_conn.get_alarms()): + if alarm.alarm_id == data['alarm_id']: + self.assertEqual( + ['trust+http://5678:delete@something/ok'], + alarm.ok_actions) + break data = self._get_alarm('a') self.assertEqual( - ['trust+http://5678:delete@something/ok'], data['ok_actions']) + ['trust+http://something/ok'], data['ok_actions']) data.update({'ok_actions': ['http://no-trust-something/ok']})
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
15- bugs.launchpad.net/ossn/+bug/1649333nvdIssue TrackingPatchThird Party AdvisoryWEB
- review.openstack.orgnvdIssue TrackingPatchVendor Advisory
- review.openstack.orgnvdIssue TrackingPatchVendor Advisory
- review.openstack.orgnvdIssue TrackingPatchVendor Advisory
- www.securityfocus.com/bid/100455nvdThird Party AdvisoryVDB EntryWEB
- github.com/advisories/GHSA-86cv-9gpx-6hwjghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2017-12440ghsaADVISORY
- www.debian.org/security/2017/dsa-3953nvdWEB
- access.redhat.com/errata/RHSA-2017:3227nvdWEB
- access.redhat.com/errata/RHSA-2018:0315nvdWEB
- github.com/openstack/aodh/commit/149d3ad2193b4d17df801f82a0a6be62dba564dbghsaWEB
- github.com/openstack/aodh/commit/92182de328d1f088c5f5a68326d2b207b21e06eaghsaWEB
- review.openstack.orgghsaWEB
- review.openstack.orgghsaWEB
- review.openstack.orgghsaWEB
News mentions
0No linked articles in our index yet.