Skip to content

Commit 8012b88

Browse files
authored
Design doc: secure access to APIs from builders (#10289)
- Ref readthedocs/meta#21 - Ref #7928 (not directly, but it opens the door for the future)
1 parent de1c1fa commit 8012b88

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
Secure API access from builders
2+
===============================
3+
4+
Goals
5+
-----
6+
7+
- Provide a secure way for builders to access the API.
8+
- Limit the access of the tokens to the minimum required.
9+
10+
Non-goals
11+
---------
12+
13+
- Migrate builds to use API V3
14+
- Implement this mechanism in API V3
15+
- Expose it to users
16+
17+
All these changes can be made in the future, if needed.
18+
19+
Current state
20+
-------------
21+
22+
Currently, we access the API V2 from the builders using the credentials of the "builder" user.
23+
This user is a superuser, it has access to all projects,
24+
write access to the API, access to restricted endpoints, and restricted fields.
25+
26+
The credentials are hardcoded in our settings file,
27+
so if there is a vulnerability that allows users to have access to the settings file,
28+
the attacker will have access to the credentials of the "builder" user,
29+
giving them full access to the API and all projects.
30+
31+
Proposed solution
32+
-----------------
33+
34+
Instead of using the credential of a super user to access the API,
35+
we will create a temporal token attached to a project, and one of the owners of the project.
36+
This way this token will have access to the given project only for a limited period of time.
37+
38+
This token will be generated from the webs,
39+
and passed to the builders via the celery task,
40+
where it can be used to access the API.
41+
Once the build has finished, this token will be revoked.
42+
43+
Technical implementation
44+
------------------------
45+
46+
We will use the rest-knox_ package,
47+
this package is recommended by the DRF documentation,
48+
since the default token implementation of DRF is very basic,
49+
some relevant features of knox are:
50+
51+
- Support for several tokens per user.
52+
- Tokens are stored in a hashed format in the database.
53+
We don't have access the tokens after they are created.
54+
- Tokens can have an expiration date.
55+
- Tokens can be created with a prefix (rtd_xxx) (unreleased)
56+
- Support for custom token model (unreleased)
57+
58+
We won't expose the token creation view directly,
59+
since we can create the tokens from the webs,
60+
and this isn't exposed to users.
61+
62+
The view to revoke the token will be exposed,
63+
since we need it to revoke the token once the build has finished.
64+
65+
From the API, we just need to add the proper permission and authentication classes
66+
to the views we want to support.
67+
68+
To differentiate from a normal user and a token authed user,
69+
we will have access to the token via the ``request.auth`` attribute in the API views,
70+
this will also be used to get the attached projects to filter the querysets.
71+
72+
The knox package allows us to provide our own token model,
73+
this will be useful to add our own fields to the token model.
74+
Fields like the projects attached to the token,
75+
or access to all projects the user has access to, etc.
76+
77+
.. _rest-knox: https://james1345.github.io/django-rest-knox/
78+
79+
Flow
80+
----
81+
82+
The flow of creation and usage of the token will be:
83+
84+
- Create a token from the webs when a build is triggered.
85+
The triggered project will be attached to the token,
86+
if the build was triggered by a user, that user will be attached to the token,
87+
otherwise the token will be attached to one of the owners of the project.
88+
- The token will be created with an expiration date
89+
of 3 hours, this should be enough for the build to finish.
90+
We could also make this dynamic depending of the project.
91+
- Pass the token to the builder via the celery task.
92+
- Pass the token to all places where the API is used.
93+
- Revoke the token when the build has finished.
94+
This is done by hitting the revoke endpoint.
95+
- In case the revoke endpoint fails, the token will expire in 3 hours.
96+
97+
Why attach tokens to users?
98+
---------------------------
99+
100+
Attaching tokens to users will ease the implementation,
101+
since we can re-use the code from knox package.
102+
103+
Attaching tokens to projects only is possible,
104+
but it will require to manage the authentication manually.
105+
This is since Knox requires a user to be attached to the token,
106+
and this user is used in their ``TokenAuthentication`` class.
107+
An alternative is to use the DRF API key package, which doesn't require a user,
108+
but then if we wanted to extend this functionality to our normal APIs, we will have
109+
to implement the authentication manually.
110+
111+
Kepping backwards compatibility
112+
-------------------------------
113+
114+
Access to write API V2 is restricted to superusers,
115+
and was used only from the builders.
116+
So we don't need to keep backwards compatibility for authed requests,
117+
but we need to keep the old implementation working while we deploy the new one.
118+
119+
Possible issues
120+
---------------
121+
122+
Some of the features that we may need are not released yet,
123+
we need the custom token model feature, specially.
124+
125+
There is a race condition when using the token,
126+
and the user that is attached to that token is removed from the project.
127+
This is, if the user is removed while the build is running,
128+
the builders won't be able to access the API.
129+
We could avoid this by not relying on the user attached to the token,
130+
only on the projects attached to it (this would be for our build APIs only).
131+
132+
Alternative implementation with Django REST Framework API Key
133+
-------------------------------------------------------------
134+
135+
Instead of using knox, we can use `DRF API key`_,
136+
it has the same features as knox, with the exception of:
137+
138+
- It is only used for authorization,
139+
it can't be used for authentication (or it can't be out of the box).
140+
- It doesn't expose views to revoke the tokens (but this should be easy to manually implement)
141+
- Changing the behaviour of some things require sub-classing instead of defining settings.
142+
- It supports several token models (not just one like knox).
143+
- All features that we need are already released.
144+
145+
The implementation will be very similar to the one described for knox,
146+
with the exception that tokens won't be attached to users,
147+
but just a project. And we won't be needing to handle authentication,
148+
since the token itself will grant access to the projects.
149+
150+
To avoid breaking builders,
151+
we need to be able to make the old and the new implementation work together,
152+
this is, allow authentication and handle tokens at the same time.
153+
This means passing valid user credentials together with the token,
154+
this "feature" can be removed in the next deploy
155+
(with knox we also need to handle both implementations,
156+
but it doesn't require passing credentials with the token,
157+
since it also handles authentication).
158+
159+
.. _DRF API key: https://florimondmanca.github.io/djangorestframework-api-key/
160+
161+
Decision
162+
--------
163+
164+
Due to the fact that the required featues from knox are not released yet,
165+
we have decided to use DRF API key instead.
166+
167+
Future work
168+
-----------
169+
170+
This work can be extended to API V3, and be exposed to users in the future.
171+
We only need to take into consideration that the token model will be shared by both,
172+
API V2 and API V3 if using knox, if we use API key, we can have different token models
173+
for each use case.

0 commit comments

Comments
 (0)