Route 53에서 구매한 도메인을 API gateway에 연결 후 서브 도메인(api.도메인)으로 요청을 보내면 403 Forbidden 에러가 나타났다.
권한부여자(Lambda 함수) 등록, 통합(Lambda 통합 유형)을 경로에 연결하고 사용자 지정 도메인도 매핑 해줬는데 왜 그럴까?
어디서 문제가 발생한 것인지 하나씩 확인해보자.
403 Forbidden공식문서 👉 API Gateway의 HTTP 403 오류 문제 해결
HTTP 403 응답 코드는 클라이언트가 유효한 URL에 액세스하는 것이 금지되었음을 의미한다. 서버는 요청을 이해하지만 클라이언트 측 문제로 인해 요청을 이행할 수 없다는 뜻이다.
API Gateway API는 다음과 같은 이유로 403 응답을 반환할 수 있다.
| 문제 | 응답 헤더 | 오류 메시지 |
|---|---|---|
| 액세스 거부됨 | "x-amzn-errortype" = "AccessDeniedException" | "User is not authorized to access this resource with an explicit deny" |
| 액세스 거부됨 | "x-amzn-errortype" = "AccessDeniedException" | "User: is not authorized to perform: execute-api:Invoke on resource: with an explicit deny" |
| 액세스 거부됨 | "x-amzn-errortype" = "AccessDeniedException" | "User: anonymous is not authorized to perform: execute-api:Invoke on resource:" |
| 액세스 거부됨 | "x-amzn-errortype" = "AccessDeniedException" | "The security token included in the request is invalid." |
| Missing authentication token | "x-amzn-errortype" = "MissingAuthenticationTokenException" | "Missing Authentication Token" |
| 인증 토큰이 만료되었습니다 | "x-amzn-errortype" = "InvalidSignatureException" | "Signature expired" |
| API 키가 유효하지 않음 | "x-amzn-errortype" = "ForbiddenException" | "Invalid API Key identifier specified" |
| 서명이 유효하지 않습니다. | "x-amzn-errortype" = "InvalidSignatureException" | "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method." |
| AWS WAF 필터링 | "x-amzn-errortype" = "ForbiddenException" | "Forbidden" |
| 리소스 경로가 존재하지 않음 | "x-amzn-errortype" = "MissingAuthenticationTokenException" | "Missing Authentication Token" |
| 리소스 경로가 존재하지 않음 | "x-amzn-errortype" = "IncompleteSignatureException" | "Authorization header requires 'Credential' parameter. Authorization header requires 'Signature' parameter. Authorization header requires 'SignedHeaders' parameter. Authorization header requires existence of either a 'X-Amz-Date' or a 'Date' header. Authorization=allow" |
| 퍼블릭 DNS 이름을 잘못 사용하여 프라이빗 API를 호출 | "x-amzn-errortype" = "ForbiddenException" | "Forbidden" |
| 기본 execute-api 엔드포인트를 사용하여 사용자 지정 도메인 이름을 가진 REST API 호출 | "x-amzn-errortype" = "ForbiddenException" | "Forbidden" |
| 유효하지 않은 클라이언트 인증서를 사용하여 상호 전송 계층 보안(TLS)이 필요한 API Gateway 사용자 지정 도메인 이름을 호출합니다. | "x-amzn-errortype" = "ForbiddenException" | "Forbidden" |
| 기본 경로 매핑 없이 사용자 지정 도메인 이름 호출 | "x-amzn-errortype" = "ForbiddenException" | "Forbidden" |
| 도메인 URL에 단계가 포함된 경우 사용자 지정 도메인을 활성화하여 API 호출 | "x-amzn-errortype" = "MissingAuthenticationTokenException" | "Missing Authentication Token" |
| 요청 URL의 단계가 유효하지 않음 | "x-amzn-errortype" = "ForbiddenException" | "Forbidden" |
Postman 헤더를 확인해보니 x-amzn-ErrorType 에 ForbiddenException 을 확인할 수 있었다. 그리고 에러 메시지는 Forbidden이 반환되었다.
위의 항목 중 해당하는 것들을 추려보면 다음과 같다.
| 문제 | 근본 원인 |
|---|---|
| 퍼블릭 DNS 이름을 잘못 사용하여 프라이빗 API를 호출 | 퍼블릭 DNS 이름을 잘못 사용하여 Amazon Virtual Private Cloud(VPC) 내에서 프라이빗 API를 호출합니다. 예: "Host" 또는 "x-apigw-api-id" 헤더가 요청에 없습니다. 자세한 내용을 보려면 https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-private-api-test-invoke-url.html#apigateway-private-api-public-dns을 참조하세요. |
| 기본 execute-api 엔드포인트를 사용하여 사용자 지정 도메인 이름을 가진 REST API 호출 | 호출자는 기본 엔드포인트를 비활성화한 후 기본 execute-api 엔드포인트를 사용하여 REST API를 호출합니다. 자세한 내용을 보려면 https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-disable-default-endpoint.html를 참조하세요. |
| 유효하지 않은 클라이언트 인증서를 사용하여 상호 전송 계층 보안(TLS)이 필요한 API Gateway 사용자 지정 도메인 이름을 호출합니다. | API 요청에 제공된 클라이언트 인증서가 사용자 지정 도메인 이름의 truststore에서 발급되지 않았거나 유효하지 않습니다. 자세한 내용을 보려면 https://aws.amazon.com/ko/premiumsupport/knowledge-center/api-gateway-mutual-tls-403-errors/를 참조하세요. |
| 기본 경로 매핑 없이 사용자 지정 도메인 이름 호출 | 호출자가 기본 경로가 API에 매핑되지 않은 사용자 지정 도메인을 호출합니다. 자세한 내용을 보려면 https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html을 참조하세요. |
| 요청 URL의 단계가 유효하지 않음 | 호출자의 요청 URL에 존재하지 않는 단계가 포함되어 있습니다. 단계가 존재하고 요청 URL의 철자가 맞는지 확인하세요. 자세한 내용을 보려면 https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-call-api.html를 참조하세요. |
우선 API에 대해 Amazon CloudWatch 액세스 로깅을 설정하고 CloudWatch에서 API의 실행 로그를 확인하여 요청이 API에 도달하는지 확인해보자.
참고: HTTP API는 실행 로깅을 지원하지 않는다. 상호 TLS가 필요하고 HTTP API를 호출하는 사용자 지정 도메인 이름에서 반환되는
403오류 문제를 해결하려면 다음을 수행해야 한다.
사용자 지정 도메인 이름에 대해 테스트용으로만 REST API를 호출하는 새 API 매핑을 생성
CloudWatch에서 REST API의 실행 로그를 확인하여 오류의 원인을 파악한다.
오류가 확인되고 해결되면 사용자 지정 도메인 이름에 대한 API 매핑을 HTTP API로 다시 라우팅한다.
확인해보니 api gateway endpoint로 요청을 보내면
{
"errorType": "string",
"errorMessage": "Error: Invalid token",
"trace": []
}
이렇게 로그에 기록이 되지만,
Postman에서 https://api.pqsoft.net/api-gateway-user-auth-test로 요청을 보내면 로그에 아예 뜨지 않는다.
인증서는 모두 정상적으로 등록되어 있었다.
서브 도메인에 대한 레코드도 등록되어 있었기에 굳이 와일드카드를 사용한 인증서를 발급받을 필요도 없었다.(하지만 발급받아서 테스트는 해보았다..😅)
애초에 매핑쪽에 문제가 있을까 싶어 사용자 정의 도메인에 api.pqsoft.net을 따로 매핑해주니 500 Internal server Error로 변경되었다.
500 Internal sever Error)공식문서 👉 HTTP API Lambda 통합 관련 문제 해결 - Amazon API Gateway
58.230.79.137 - - [18/Apr/2024:15:22:31 +0000] "GET ANY /api-gateway-user-auth-test HTTP/1.1" 500 35 WbYcyh5xIE0EPLQ= -
이렇게 로그가 나오는걸 볼 수 있었다.
이 로그는 API Gateway에서 발생한 HTTP 요청에 대한 정보를 담고 있는데 각 부분이 무엇을 의미하는지 살펴보자(GPT 도움받음):
IP 주소: 58.230.79.137 - 이 요청을 보낸 클라이언트의 IP 주소
사용자 식별자: 두 개의 - -문자는 원격 사용자 이름과 로컬 사용자 이름이 로깅되지 않았음을 나타낸다.
요청 시간: [18/Apr/2024:15:22:31 +0000] - 요청이 받아진 시간과 날짜. GMT 시간으로 표시된다.
요청 라인: "GET ANY /api-gateway-user-auth-test HTTP/1.1" - 이 부분은 요청된 메서드(GET), 요청된 리소스(/api-gateway-user-auth-test), 프로토콜 버전(HTTP/1.1)을 보여준다.
상태 코드: 500 - 서버가 요청을 처리하지 못하고 내부 서버 오류를 나타내는 상태 코드.
응답 크기: 35 - 응답의 바이트 크기. 여기서는 35바이트의 데이터가 반환됐다.
요청 ID: WbYcyh5xIE0EPLQ= - 이 요청을 추적하는 데 사용되는 고유 식별자.
인티그레이션 에러 메시지: -**** - 이 경우 추가 에러 메시지는 없는 것으로 보여진다.
에러에 대한 명확한 설명이 없다.. 그래서 직접 찾아보기로 결정
스택오버플로우를 뒤져보다
Here is the common issues which might be able to help you diagnose the issue.
1. It doesn't have a right permission to allow API Gateway invoke your Lambda function.
2. It is set as AWS Service Proxy to your Lambda function, the response from your Lambda function doesn't return the response in the proper format.
라는 댓글을 발견했다. 해석하면 다음과 같다.
다음은 문제를 진단하는 데 도움이 될 수 있는 일반적인 문제입니다.
1. API Gateway가 Lambda 함수를 호출하도록 허용할 수 있는 올바른 권한이 없습니다.
2. Lambda 함수에 대한 AWS 서비스 프록시로 설정되어 있으며 Lambda 함수의 응답이 적절한 형식으로 응답을 반환하지 않습니다.
그리고 람다 트리거 쪽을 확인해보니
The API with ID 8fxaaypxth doesn’t include a route with path /api-gateway-auth-test-2 having an integration arn:aws:lambda:ap-northeast-2:730335473359:function:api-gateway-auth-test-2.
라는 에러가 나타나고 있었고 스택오버플로우에 동일한 증상(HTTP 500 error + 트리거 error)이 나타나는 경우를 확인해보았다.
해당 에러가 나타는 원인을 보니 다음과 같이 예측이 가능했다.
애초에 생성할 때 람다 함수에 대한 권한 자동 부여를 활성화했기때문에 IAM 역할의 권한부분을 확인해보니 이미 권한이 부여되어 있었다.
하지만 혹시 작동을 안하는 건가 싶어 새로 권한 추가해주었고
[AWS] IAM - Lambda - API Gateway
해당 글을 참고해 AmazonAPIGatewayPushToCloudWatchLogs 정책도 연결해주었다.
하지만 해결안됨..
인증키 활성화가 안되어있기에 바로 다음으로 넘어갔다.
위과 같은 스택오버플로우 글을 참고해서 따라해보았지만 소용없었다.(원래대로 롤백함)
Lambda 함수의 응답이 올바르지 않은 상황에 왜 500 에러가 나타나는지 찾아보니 아래의 글을 발견했다. 참고해보자.
def lambda_handler(event, context):
return "hello world"
하지만, 이런 Lambda 함수를 API Gateway와 연결하면
500 Internal Server error가 납니다.
왜 일까요?
어떻게 보면 당연합니다.
AWS Lambda는 API Gateway와 연결할 것을 전제로 설계된 것이 아닙니다.따라서, API Gateway가 이해할 수 있는 return value를 작성해야 합니다.
출처:https://bablabs.tistory.com/39
그리고 람다 함수가 올바른 값을 반환할 수 있도록 수정해주었다.
callback(null, {statusCode: 200, body: JSON.stringify(message), headers: {'Content-Type': 'application/json'}, });
하지만 여전히 500 에러는 유지되고 있다.
그럼 이제 다음 상황을 확인해보자.
It transpires that it was not related to the above issues but was due to the payload format version in API Gateway. I had elected two optional options (without properly knowing what they were doing or their consequences). These were PayloadFormatVersion and EnableSimpleResponses: true.
PayloadFormatVersion can be either 1.0 or 2.0. The chief difference is the switch from methodARN in 1.0 to routeArn in 2.0.
Following this EnableSimpleResponses: true means that you shouldn't return a policy document from the handler, but instead must return this
{
"isAuthorized": true/false,
"context": {
"exampleKey": "exampleValue"
}
}
Lesson --> don't update payload versions without research!
해석해보면

위의 문제와는 관련이 없고 API Gateway의 페이로드 형식 버전 때문인 것으로 확인되었습니다 . 나는 두 가지 선택적 옵션을 선택했습니다(그들이 무엇을 하고 있는지, 그 결과가 무엇인지 제대로 알지 못한 채). 이것들은 PayloadFormatVersion과 이었습니다 EnableSimpleResponses: true.
PayloadFormatVersion1.0 또는 2.0일 수 있습니다. 가장 큰 차이점은 methodARN1.0에서 routeArn2.0 으로 의 전환입니다 .
이는 EnableSimpleResponses: true핸들러에서 정책 문서를 반환하지 않고 대신 다음을 반환해야 함을 의미합니다.
{
"isAuthorized": true/false,
"context": {
"exampleKey": "exampleValue"
}
}
교훈 --> 조사 없이 페이로드 버전을 업데이트하지 마세요!
페이로드 버전을 확인해보니 2.0으로 되어있었다.
반환값은 1.0과 호환되는데 설정은 2.0으로 되어있으니 충돌이 일어난 것으로 예상된다.
1.0으로 바꿔주니


위의 이미지와 같이 페이로드는 Body에 포함되어서 전송되어짐.
메시지 프로토콜에서의 프로토콜 오버헤드와 원하는 데이터를 구별 할 때 사용 되어짐.
웹 서비스의 응답 데이터가 JSON 데이터 형식이라면,
{
"status":"OK",
"data": {
"message":"Hello, world!"
}
}
json내에서의 'data'가 페이로드 데이터가 되어지고,
나머지 데이터는 통신을 하는데 용이하게 해주는 부차적인 정보를 가진 프로토콜 오버헤드라고 보면된다.
풀스택 개발자가 되어가고 계시군요 🤓 📚