|
| 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