스키마 튜토리얼레슨 21: 서비스 연결 시 자격 증명 유출 방지
레슨 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}"
}
})
}아래는 이 쿼리의 작동 방식에 대한 설명입니다.
자격 증명이 유출될 수 있는 상황
외부 서비스에 연결할 때 자격 증명을 제공해야 하는 경우가 자주 있습니다. 예를 들어, 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 쿼리 내: 자격 증명을 소스 코드에 직접 삽입해서는 안 됩니다. 평문으로 기록되어 보안상 위험이 발생합니다
- GraphQL 응답 내: 서비스에 연결하는 필드에서 오류가 발생하면 GraphQL 응답의
errors항목에 오류 메시지가 추가됩니다. 이 메시지에는 실패한 필드 이름과 인수가 포함될 수 있으므로 자격 증명이 출력될 가능성이 있습니다 - 서버 로그 내: 자격 증명이 변수를 통해 액세스되고 해당 변수가 URL 파라미터로 제공되면 웹 서버의 로그에 기록될 수 있습니다
자격 증명 유출을 방지하는 GraphQL 쿼리
이 GraphQL 쿼리는 자격 증명을 유출하지 않고 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"이며 그 값이 아닙니다
마지막 항목을 설명하기 위해, 존재하지 않는 저장소 "leoloso/NonExisting"의 URL을 GitHub API에 전달하면 오류가 발생하고 다음과 같은 응답이 반환됩니다(오류 메시지의 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
}
}