[Spring Cloud AWS + AWS API Gateway] API Gateway 메서드에 대한 IAM 권한 부여 및 요청

mrcocoball·2025년 5월 29일
0

Spring Cloud

목록 보기
10/11

개요

퍼블릭하게 열려 있던 AWS API Gateway의 메서드에 대한 접근 제어를 강화하기 위해 IAM 권한을 부여하고, 서버에서 API Gateway에 요청을 보낼 때 Spring Cloud AWS를 사용하여 SigV4를 적용한 사례를 공유하고자 한다.

AWS API Gateway

AWS API Gateway는 RESTful API, HTTP API, WebSocket API를 손쉽게 만들고 배포하며 관리할 수 있는 완전관리형 서비스로, 서버리스 환경에 적합하며 AWS Lambda 또는 다른 백엔드와의 통합을 통해 API 게이트웨이 역할을 수행한다.

필자가 속해 있는 팀의 서비스에서는 AWS Lambda의 함수를 직접 호출하지 않고 API Gateway와 통합하여 API Gateway의 메서드를 통해 람다 함수를 호출하는 구조가 적용이 되어 있는데, AWS Lambda의 경우 직접 호출도 가능하긴 하지만 보안 기능이나 자동 스케일링, 모니터링 등의 부가적인 기능을 위해 API Gateway와 통합하는 경우가 많다.

API Gateway 메서드에 대한 IAM 권한 부여와 메서드 요청

IAM 권한 부여를 통한 접근 제어 강화

API Gateway 메서드는 퍼블릭하게 열어둘 수도 있지만, 접근 제어를 강화하기 위해 IAM 권한을 부여할 수 있다.
이렇게 되면 API Gateway 메서드를 호출할 수 있는 권한이 있는 IAM에 한해서만 메서드를 호출할 수 있게 된다.

접근 제어를 강화하는 이유는 여러 가지가 있겠지만, 퍼블릭하게 열려 있는 API Gateway 메서드에 대한 트래픽 공격을 막는다던지, 아니면 메서드와 연결되어 있는 람다 함수가 공개되어서는 안 될 정보 등을 노출한다던지 (가령 특정 Private 버킷에 대한 Presigned URL) 하는 경우에는 반드시 엄격한 접근 제어가 필요하다.

IAM 권한을 부여하는 방법은 아주 간단한데, 배포된 메서드를 확인, 메서드 요청을 편집하여 권한 부여를 해주면 된다.

API Gateway 메서드에 접근할 수 있는 권한 부여

위의 상태를 적용하게 되면, 해당 API Gateway 메서드는 접근 권한이 없는 경우(IAM에 대한 정보가 없거나, 권한이 없는 IAM으로 요청하는 경우) 호출할 때 권한이 없다는 응답을 전달한다.

따라서 해당 메서드에 접근할 수 있는 IAM 사용자를 먼저 만들고, 다음과 같이 인라인 정책으로 해당 메서드에 대한 접근 권한을 부여해야 한다.

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": "execute-api:Invoke",
			"Resource": "${API Gateway 메서드의 ARN}"
		}
	]
}

Spring Cloud AWS를 통한 SigV4 적용 및 요청

API Gateway 메서드에 접근할 수 있는 IAM 사용자를 생성했다면, 다음 절차를 진행해야 한다.

  • IAM의 액세스 키 / 시크릿 키를 통해 AWSCredentials 객체를 생성
  • AWSCredentials 객체를 활용하여 SigV4 객체 생성
  • AP Gateway 메서드 호출 시 (RestTemplate, RestClient, WebClient 등) SigV4 적용

이 때 AWS SDK를 사용하거나, Spring Cloud 환경이라면 Spring Cloud AWS를 사용하여 진행할 수 있다.

필자의 팀에서는 이미 Spring Cloud AWS를 사용하고 있었기 때문에 다음과 같이 처리하였다.
[참고 글] https://velog.io/@mrcocoball2/Spring-Cloud-AWS-Spring-Cloud-AWS-Secrets-Manager-Config-Secrets-Manager

1. AWS 설정 클래스 작성

@Configuration
public class AWSConfig {

    @Value("${cloud.aws.credentials.access-key}")
    private String accessKey;
    @Value("${cloud.aws.credentials.secret-key}")
    private String secretKey;
    @Value("${cloud.aws.region.static}")
    private String region;
    
    @Bean
    public BasicAWSCredentials awsCredentials() {
        return new BasicAWSCredentials(accessKey, secretKey);
    }

}

2. AWSCredentials를 사용하여 SigV4 적용 및 요청 전송

@RequireArgsConstructor
@Component
public class AWSAPIGatewayUtils {

    private final BasicAWSCredentials awsCredentials;
    
    @Value("${cloud.aws.region.static}")
    private String region;
    @Value("${cloud.aws.gateway.host}")
    private String gatewayHost; // API Gateway의 호스트, https:// 제외
    @Value("${cloud.aws.gateway.path}")
    private String gatewayPath; // API Gateway 메서드의 호스트를 제외한 경로
    
    
    public void executeAPIGatewayMethod() {
    
    	// 요청 바디 생성
        ...
        
        String payload = new ObjectMapper().writeValueAsString(requestBody);
        String gatewayUri = "https://" + gatewayHost + gatewayPath;

        // AWS 서명을 위한 요청 객체 생성
        DefaultRequest<?> awsRequest = new DefaultRequest<>("execute-api");
        awsRequest.setHttpMethod(HttpMethodName.POST);
        awsRequest.setEndpoint(URI.create("https://" + gatewayHost));
        awsRequest.setResourcePath(gatewayPath);
        awsRequest.setContent(new StringInputStream(payload));

        // 필수 헤더 설정
        awsRequest.addHeader("Content-Type", "application/json");
        awsRequest.addHeader("Host", gatewayHost);

        // AWS SigV4 서명 생성
        AWS4Signer signer = new AWS4Signer();
        signer.setServiceName("execute-api");
        signer.setRegionName(region);
        signer.sign(awsRequest, awsCredentials);

        // 서명된 헤더로 실제 HTTP 요청 생성
        HttpHeaders headers = new HttpHeaders();
        awsRequest.getHeaders().forEach(headers::add);

        // RestTemplate으로 요청 전송
        HttpEntity<String> httpEntity = new HttpEntity<>(payload, headers);
        ResponseEntity<String> response = restTemplate.exchange(
                gatewayUri,
                HttpMethod.POST,
                httpEntity,
                String.class
        );
        
        ...
    
    }
    
}
profile
Backend Developer

0개의 댓글