บทช่วยสอน Schema
บทช่วยสอน Schemaบทเรียนที่ 21: ไม่ทำให้ข้อมูลรับรองรั่วไหลเมื่อเชื่อมต่อกับบริการ

บทเรียนที่ 21: ไม่ทำให้ข้อมูลรับรองรั่วไหลเมื่อเชื่อมต่อกับบริการ

GraphQL query นี้ดึงข้อมูลรับรองจากค่า environment และหลีกเลี่ยงไม่ให้ข้อมูลเหล่านั้นถูกพิมพ์ออกมาในการตอบกลับหรือในล็อก จึงช่วยหลีกเลี่ยงความเสี่ยงด้านความปลอดภัย:

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 นี้ทำงานอย่างไร

ข้อมูลรับรองอาจรั่วไหลได้อย่างไร

เรามักจำเป็นต้องให้ข้อมูลรับรองเมื่อเชื่อมต่อกับบริการภายนอก ตัวอย่างเช่น REST API ของ GitHub กำหนดให้ต้องมี access token สำหรับ endpoint ที่ข้อมูลเป็นแบบส่วนตัวหรือมีการแก้ไขข้อมูล:

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: หากฟิลด์ที่เชื่อมต่อกับบริการเกิดข้อผิดพลาด ข้อความแสดงข้อผิดพลาดจะถูกเพิ่มเข้าไปในการตอบกลับของ GraphQL ภายใต้เอนทรี errors ข้อความนี้อาจพิมพ์ชื่อของฟิลด์ที่ล้มเหลวพร้อมกับอาร์กิวเมนต์ของมัน จึงพิมพ์ข้อมูลรับรองออกมา
  • ในล็อกของเซิร์ฟเวอร์: หากเข้าถึงข้อมูลรับรองผ่านตัวแปร และตัวแปรนี้ถูกส่งเป็นพารามิเตอร์ของ URL ก็อาจถูกบันทึกไว้ในล็อกของเว็บเซิร์ฟเวอร์

GraphQL query ที่หลีกเลี่ยงการทำให้ข้อมูลรับรองรั่วไหล

GraphQL query นี้ส่งข้อมูลรับรองไปยัง 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}"
    }
  })
}

เป็นเพราะว่า:

  • ข้อมูลรับรองถูกดึงมาจากตัวแปร environment GITHUB_ACCESS_TOKEN จึงไม่จำเป็นต้องฝังไว้ในซอร์สโค้ด
  • ฟิลด์ githubAccessToken ถูก @remove จึงไม่ถูกพิมพ์ออกมาในการตอบกลับ
  • อินพุต _sendJSONObjectItemHTTPRequest(auth:) อ้างอิงถึงตัวแปรไดนามิก $__githubAccessToken ดังนั้นหากฟิลด์เกิดข้อผิดพลาด สิ่งที่จะถูกพิมพ์ในข้อความแสดงข้อผิดพลาดคือสตริงตามตัวอักษร "$__githubAccessToken" (ไม่ใช่ค่าของมัน)

เพื่อสาธิตประเด็นสุดท้ายนี้ การให้ URL ของรีโพซิทอรีที่ไม่มีอยู่จริง "leoloso/NonExisting" แก่ API ของ GitHub จะทำให้เกิดข้อผิดพลาด และเราจะได้รับการตอบกลับนี้ (สังเกต 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
  }
}