SigV4 canonical URI 이중 인코딩 — Bedrock 403

만든 것3분조회

Bedrock 은 여러 업체의 LLM 을 API 하나로 호출하게 해주는 AWS 서비스이고, SigV4 는 AWS API 요청에 서명해 신원을 증명하는 표준 서명 방식입니다. 보통은 AWS SDK 가 이 서명을 대신 만들어 줍니다.

브라우저 확장에서 AWS SDK 없이 SigV4 서명을 직접 구현해 Bedrock 을 불렀는데, 계속 403 이 났습니다. 서명 단계는 다 맞는 것 같은데 서버가 서명이 안 맞는다고 합니다.

canonical request URI의 이중 인코딩

SigV4 서명은 요청을 정해진 형식(canonical request)으로 정규화한 뒤 그걸 해싱·서명합니다. 서버도 같은 방식으로 정규화해서 서명을 다시 만들고, 두 개가 같은지 비교합니다. 한 글자라도 다르게 정규화하면 서명이 안 맞아 403 입니다.

문제는 canonical request 의 URI path 였습니다. S3 를 제외한 모든 서비스는 URI path 를 두 번 URI 인코딩해야 합니다. 한 번이 아니라 두 번입니다.

Bedrock 의 호출 경로에는 모델 ID 가 들어갑니다.

/model/us.amazon.nova-lite-v1:0/converse

모델 ID 의 콜론(:)은 URI 인코딩 대상입니다. 한 번 인코딩하면 %3A 가 되고, canonical request 에서는 그 %3A% 를 다시 인코딩해서 %253A 가 돼야 합니다. 제가 한 번만 인코딩해서 %3A 로 서명했는데, 서버는 %253A 로 서명하니 안 맞았습니다.

내가 서명한 경로:   /model/us.amazon.nova-lite-v1%3A0/converse   (단일 인코딩)
서버가 기대한 경로: /model/us.amazon.nova-lite-v1%253A0/converse (이중 인코딩)
→ 서명 불일치 → 403

이중 인코딩의 이유

이상하게 들리지만, canonical request 는 이미 URI 인코딩된 경로를 받아서 그걸 또 한 번 인코딩한 값으로 정규화하도록 스펙에 정해져 있습니다. 실제 HTTP 요청에 나가는 경로는 한 번 인코딩된 것이고, 서명 계산에만 두 번 인코딩된 값을 씁니다.

S3 만 예외인데, 키에 슬래시 같은 문자가 많아서 두 번 인코딩하면 경로가 깨지기 때문에 한 번만 합니다. 그래서 “S3 는 단일, 나머지는 이중” 이라는 규칙이 생겼습니다.

SDK 를 쓰면 이걸 알아서 처리해주니 평생 모르고 지나갑니다. 직접 구현하면 정확히 여기서 막힙니다.

직접 구현할 때 확인 순서

SigV4 를 손으로 짜다 403 이 나면 순서대로 봅니다.

  • 서명 자체가 틀렸나 — 서명 키 유도(HMAC 체이닝), 서명 스코프(날짜/리전/ 서비스)가 맞는지. 여기가 틀리면 아예 모든 요청이 실패합니다.
  • canonical request 가 서버와 다른가 — URI 이중 인코딩, 쿼리 파라미터 정렬, 헤더 소문자화·정렬, 서명 대상 헤더(SignedHeaders) 목록.

특수문자가 든 경로에서만 403 이 나고 단순 경로는 되면, 십중팔구 URI 인코딩 문제입니다. 콜론·공백·슬래시가 든 경로로 테스트해보면 바로 드러납니다.

Bedrock 모델 ID 처럼 콜론이 든 경로가 정확히 여기서 걸립니다.

참고

  1. 불러오는 중