Schema 教程第21课:连接服务时避免泄露凭据
第21课:连接服务时避免泄露凭据
此 GraphQL query 从环境变量中获取凭据,并避免其被打印到响应或日志中,从而规避安全风险:
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}"
}
})
}以下是对此 query 工作原理的说明。
凭据可能泄露的情形
连接外部服务时,我们通常需要提供凭据。例如,GitHub 的 REST API 在数据私有或需要修改的端点上要求提供访问令牌:
query {
_sendJSONObjectItemHTTPRequest(input:{
url: "https://api.github.com/repos/GatoGraphQL/GatoGraphQL",
method: PATCH,
options: {
auth: {
password: "{ GITHUB_ACCESS_TOKEN }"
},
body: "{\"has_wiki\":false}"
}
})
}我们需要谨慎,避免暴露凭据:
- 在 GraphQL query 中: 凭据绝不能嵌入源代码,因为其会以明文形式存在,造成安全隐患
- 在 GraphQL 响应中: 如果连接服务的字段产生错误,错误消息将以
errors条目的形式添加到 GraphQL 响应中;该消息可能会打印失败字段的名称及其参数,从而将凭据暴露出去 - 在服务器日志中: 如果凭据通过变量访问,且该变量作为 URL 参数传递,则可能被记录在 Web 服务器的日志中
避免泄露凭据的 GraphQL query
此 GraphQL query 在将凭据传递给 GitHub API 的同时,避免了凭据泄露:
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",而非其实际值
为验证最后一点,向 GitHub API 提供不存在的仓库 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
}
}Next