Урок 21: Как не допустить утечки учётных данных при подключении к сервисам
Этот GraphQL-запрос получает учётные данные из переменной окружения и не допускает их попадания в ответ или логи, тем самым исключая риски безопасности:
query {
githubAccessToken: _env(name: "GITHUB_ACCESS_TOKEN")
@remove
_sendJSONObjectItemHTTPRequest(input:{
url: "https://api.github.com/repos/GatoGraphQL/GatoGraphQL",
method: PATCH,
options: {
auth: {
password: $__githubAccessToken
},
body: "{\"has_wiki\":false}"
}
})
}Ниже приводится объяснение того, как работает этот запрос.
Как могут произойти утечки учётных данных
При подключении к внешним сервисам нередко требуется передавать учётные данные. Например, REST API GitHub требует токен доступа для конечных точек, где данные являются приватными или изменяются:
query {
_sendJSONObjectItemHTTPRequest(input:{
url: "https://api.github.com/repos/GatoGraphQL/GatoGraphQL",
method: PATCH,
options: {
auth: {
password: "{ GITHUB_ACCESS_TOKEN }"
},
body: "{\"has_wiki\":false}"
}
})
}Необходимо проявлять осторожность и не допускать раскрытия учётных данных:
- В GraphQL-запросе: учётные данные никогда не должны быть встроены в исходный код, поскольку они будут представлены в открытом виде, что создаёт угрозу безопасности
- В GraphQL-ответе: если поле, обращающееся к сервису, вызывает ошибку, в GraphQL-ответе в секции
errorsпоявится сообщение об ошибке; это сообщение может содержать название упавшего поля вместе с его аргументами, а значит — и учётные данные - В логах сервера: если учётные данные передаются через переменную, а эта переменная указывается как параметр URL, она может быть записана в логах веб-сервера
GraphQL-запрос, исключающий утечку учётных данных
Этот GraphQL-запрос передаёт учётные данные в API GitHub, не допуская их утечки:
query {
githubAccessToken: _env(name: "GITHUB_ACCESS_TOKEN")
@remove
_sendJSONObjectItemHTTPRequest(input:{
url: "https://api.github.com/repos/GatoGraphQL/GatoGraphQL",
method: PATCH,
options: {
auth: {
password: $__githubAccessToken
},
body: "{\"has_wiki\":false}"
}
})
}Это достигается по следующим причинам:
- Учётные данные получаются из переменной окружения
GITHUB_ACCESS_TOKEN, поэтому их не нужно встраивать в исходный код - Поле
githubAccessTokenудаляется с помощью@remove, поэтому оно не попадает в ответ - Входной параметр
_sendJSONObjectItemHTTPRequest(auth:)ссылается на динамическую переменную$__githubAccessToken, поэтому если поле вызовет ошибку, в сообщении об ошибке будет напечатана литеральная строка"$__githubAccessToken"(а не её значение)
Чтобы продемонстрировать последний пункт: если передать в API GitHub URL несуществующего репозитория "leoloso/NonExisting", возникнет ошибка, и мы получим следующий ответ (обратите внимание на auth: {password: $__githubAccessToken} в сообщении об ошибке):
{
"errors": [
{
"message": "Client error: `PATCH https://api.github.com/repos/leoloso/NonExisting` resulted in a `404 Not Found` response:\n{\"message\":\"Not Found\",\"documentation_url\":\"https://docs.github.com/rest/repos/repos#update-a-repository\"}\n",
"locations": [
{
"line": 21,
"column": 3
}
],
"extensions": {
"path": [
"_sendJSONObjectItemHTTPRequest(input: {url: \"https://api.github.com/repos/leoloso/NonExisting\", method: PATCH, options: {auth: {password: $__githubAccessToken}, body: \"{\"has_wiki\":false}\"}})",
"query { ... }"
],
"type": "QueryRoot",
"field": "_sendJSONObjectItemHTTPRequest(input: {url: \"https://api.github.com/repos/leoloso/NonExisting\", method: PATCH, options: {auth: {password: $__githubAccessToken}, body: \"{\"has_wiki\":false}\"}})",
"id": "root",
"code": "PoP/ComponentModel@e1"
}
}
],
"data": {
"_sendJSONObjectItemHTTPRequest": null
}
}