Skip to content

Commit b40356f

Browse files
committed
Return 404 when unauthorised to access resources
When a user is not authorised to access a specific resource (e.g. did not create the resource, or not part of the project that owns the resource, or the project that created the resource), they should not be able to enumerate the resource in any way. To prevent such users from inferring the existence of resources they should not have access to, change the policy enforcement for `get resource` such that it returns `404 Not Found` instead of `403 Forbidden` in these cases.
1 parent 33de48a commit b40356f

3 files changed

Lines changed: 111 additions & 5 deletions

File tree

gnocchi/rest/api.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,11 @@ def get_all(self):
801801
self.resource_type, self.resource_id)
802802
if not resource:
803803
abort(404, str(indexer.NoSuchResource(self.resource_id)))
804-
enforce("get resource", resource)
804+
pecan.request.auth_helper.enforce_resource_policy(
805+
pecan.request,
806+
"get resource",
807+
self.resource_id,
808+
resource)
805809
return pecan.request.indexer.list_metrics(
806810
attribute_filter={"=": {"resource_id": self.resource_id}})
807811

@@ -822,7 +826,11 @@ def get(self, **kwargs):
822826
if not resource:
823827
abort(404, str(indexer.NoSuchResource(self.resource_id)))
824828

825-
enforce("get resource", resource)
829+
pecan.request.auth_helper.enforce_resource_policy(
830+
pecan.request,
831+
"get resource",
832+
self.resource_id,
833+
resource)
826834

827835
try:
828836
resources = pecan.request.indexer.list_resources(
@@ -1080,7 +1088,11 @@ def get(self):
10801088
resource = pecan.request.indexer.get_resource(
10811089
self._resource_type, self.id, with_metrics=True)
10821090
if resource:
1083-
enforce("get resource", resource)
1091+
pecan.request.auth_helper.enforce_resource_policy(
1092+
pecan.request,
1093+
"get resource",
1094+
self.id,
1095+
resource)
10841096
etag_precondition_check(resource)
10851097
etag_set_headers(resource)
10861098
return resource

gnocchi/rest/auth_helper.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import webob
2020
import werkzeug.http
2121

22+
from gnocchi import indexer
2223
from gnocchi.rest import api
2324

2425

@@ -45,6 +46,83 @@ def get_auth_info(request):
4546
'roles': request.headers.get("X-Roles", "").split(","),
4647
}
4748

49+
@staticmethod
50+
def enforce_resource_policy(request,
51+
rule,
52+
resource_id,
53+
resource,
54+
prefix=None):
55+
try:
56+
# Check if the policy allows the user to get any resource
57+
api.enforce(rule, {})
58+
except webob.exc.HTTPForbidden:
59+
policy_matched = False
60+
auth_info = KeystoneAuthHelper.get_auth_info(request)
61+
project_id = auth_info["project_id"]
62+
user_id = auth_info["user_id"]
63+
64+
target = {}
65+
if prefix:
66+
target_resource = target[prefix] = {}
67+
else:
68+
target_resource = target
69+
70+
target_resource["project_id"] = project_id
71+
try:
72+
# Check if the policy allows the user to get resources linked
73+
# to their project
74+
api.enforce(rule, target)
75+
except webob.exc.HTTPForbidden:
76+
pass
77+
else:
78+
policy_matched = True
79+
# User is authenticated using the project that owns the
80+
# resource and the policy allows access by project.
81+
if resource.project_id == project_id:
82+
return
83+
84+
resource_creator_user_id, _, resource_creator_project_id = (
85+
resource.creator.partition(":"))
86+
87+
del target_resource["project_id"]
88+
target_resource["created_by_project_id"] = project_id
89+
try:
90+
# Check if the policy allows the user to get resources linked
91+
# to their created_by_project
92+
api.enforce(rule, target)
93+
except webob.exc.HTTPForbidden:
94+
pass
95+
else:
96+
policy_matched = True
97+
# User is authenticated using the project that created the
98+
# resource and the policy allows access by created_by_project.
99+
if resource_creator_project_id == project_id:
100+
return
101+
102+
del target_resource["created_by_project_id"]
103+
target_resource["creator"] = user_id
104+
try:
105+
# Check if the policy allows the user to get resources linked
106+
# to their creator
107+
api.enforce(rule, target)
108+
except webob.exc.HTTPForbidden:
109+
pass
110+
else:
111+
policy_matched = True
112+
# The authenticated user is the user that created the resource
113+
# and the policy allows access by creator.
114+
if resource_creator_user_id == user_id:
115+
return
116+
117+
# If at least one of the policies matched but the user should not
118+
# be allowed access to the resource, return 404 Not Found to
119+
# prevent the user from enumerating the existence of the resource.
120+
if policy_matched:
121+
api.abort(404, str(indexer.NoSuchResource(resource_id)))
122+
123+
# None of the above policies matched, return 403 Forbidden.
124+
api.abort(403)
125+
48126
@staticmethod
49127
def get_resource_policy_filter(request, rule, resource_type, prefix=None):
50128
try:
@@ -177,6 +255,14 @@ def get_auth_info(self, request):
177255
"system": 'all',
178256
}
179257

258+
@staticmethod
259+
def enforce_resource_policy(request,
260+
rule,
261+
resource_id,
262+
resource,
263+
prefix=None):
264+
pass
265+
180266
@staticmethod
181267
def get_resource_policy_filter(request, rule, resource_type, prefix=None):
182268
return None
@@ -210,6 +296,14 @@ def get_auth_info(self, request):
210296
"system": 'all',
211297
}
212298

299+
@staticmethod
300+
def enforce_resource_policy(request,
301+
rule,
302+
resource_id,
303+
resource,
304+
prefix=None):
305+
pass
306+
213307
@staticmethod
214308
def get_resource_policy_filter(request, rule, resource_type, prefix=None):
215309
return None

gnocchi/tests/test_rest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,7 +1005,7 @@ def test_get_resource_unauthorized(self):
10051005
+ self.resource_type
10061006
+ "/"
10071007
+ self.attributes['id'],
1008-
status=403)
1008+
status=404)
10091009

10101010
def test_get_resource_named_metric(self):
10111011
self.attributes['metrics'] = {'foo': {'archive_policy_name': "high"}}
@@ -1026,7 +1026,7 @@ def test_list_resource_metrics_unauthorized(self):
10261026
self.app.get(
10271027
"/v1/resource/" + self.resource_type
10281028
+ "/" + self.attributes['id'] + "/metric",
1029-
status=403)
1029+
status=404)
10301030

10311031
def test_delete_resource_named_metric(self):
10321032
self.attributes['metrics'] = {'foo': {'archive_policy_name': "high"}}

0 commit comments

Comments
 (0)