S3에 저장된 이미지나 동영상 파일을 웹 애플리케이션에서 보여줄 때 모든 파일을 퍼블릭으로 공개하는 것은 보안상 위험이 따르거든요. 이럴 때 유용하게 사용하는 방법이 바로 Presigned URL을 생성하여 임시로 접근 권한을 부여하는 방식이에요. 특정 사용자에게만 일정 시간 동안 파일에 접근할 수 있는 고유한 URL을 만들어 전달하면, 버킷 자체를 공개하지 않고도 안전하게 콘텐츠를 제공할 수 있답니다. 특히 대용량 파일이나 개인정보가 포함된 문서를 처리할 때 이 방식이 실무에서 아주 유용하게 쓰여요.

import boto3
from botocore.exceptions import ClientError

# 버킷 이름과 객체 키를 인자로 받아 presigned URL을 생성합니다.

def generate_presigned_url(bucket_name, object_key):
    s3_client = boto3.client('s3')
    try:
        response = s3_client.generate_presigned_url(
            'get_object',
            Params={'Bucket': bucket_name, 'Key': object_key},
            ExpiresIn=3600  # 1시간 유효
        )
    except ClientError as e:
        return None
    return response

Presigned URL은 AWS Signature Version 4 방식을 기반으로 생성되며, 요청 시 포함된 만료 시간을 체크하여 접근을 허용하는 원리예요. 이 기능이 제대로 작동하려면 몇 가지 전제 조건이 필요한데, 우선 S3 버킷에 대한 적절한 IAM 권한이 부여되어 있어야 해요. 단순히 URL을 생성한다고 해서 바로 접근이 가능한 것이 아니라, 해당 링크를 생성하는 주체인 IAM 역할이나 사용자 계정이 실제 객체에 접근할 수 있는 권한을 가지고 있어야 하거든요. 또한 리전 정보가 명확하게 포함되어야 다른 지역의 요청을 처리할 때 오류가 발생하지 않더라고요.

실무에서 이 기능을 구현할 때 주의해야 할 중요한 포인트 중 하나는 서버의 시스템 시간이 매우 정확해야 한다는 점이에요. 인증 정보를 생성하는 과정에서 현재 시간을 기반으로 서명(Signature)을 만들기 때문에, 서버 시계와 실제 시간 사이에 차이가 발생하면 접근 거부 오류가 나타날 수 있거든요. 또한 보안을 위해 만료 시간을 최소한으로 설정하고, 사용자가 요청할 때마다 동적으로 URL을 생성하여 제공하는 방식이 권장되더라고요. 이렇게 하면 링크가 유출되더라도 일정 시간이 지나면 무효화되기 때문에 훨씬 안전하게 관리할 수 있어요. 추가로 S3 버킷 정책이나 ACL이 복잡하게 얽혀 있는 경우, 의도치 않은 권한 거부가 발생할 수 있으니 최소 권한 원칙을 적용하여 접근 범위를 제한하는 것이 좋답니다.

Lambda 함수를 VPC에 연결하면 내부 서비스에는 접근이 되지만 외부 인터넷 통신이 막히거나 DNS가 해결되지 않아 막히는 경우가 많아요. 보통 서브넷 라우팅 설정이나 VPC 기본 속성을 놓치면서 발생하거든요. 이럴 때는 프라이빗 서브넷 배치 여부부터 NAT 게이트웨이 라우팅, 그리고 VPC의 DNS 관련 속성까지 한 번에 검토해야 해요. 특히 서브넷 배치와 라우팅 설정을 잘못하면 통신이 완전히 차단되니까 흐름을 먼저 그려 보는 것이 같아요.

Lambda가 외부로 나가기 위해서는 반드시 프라이빗 서브넷에 배치해야 합니다. 해당 서브넷이 연결된 경로 테이블에 0.0.0.0/0 경로를 NAT 게이트웨이로 설정하면 인터넷 통신이 가능해지죠. 인터넷 게이트웨이는 Lambda에서 직접 사용할 수 없으므로 이 부분을 꼭 구분해야 해요. DNS 해결을 위해서는 VPC 설정에서 enableDnsHostnames와 enableDnsSupport를 모두 true로 활성화해야 합니다. 또한 Lambda 서브넷과 NAT 게이트웨이가 반드시 같은 가용 영역에 배치되어야 하는 것은 아니에요. 다른 가용 영역에 있더라도 라우팅 테이블 설정만 올바르다면 정상적으로 통신이 가능하거든요. 동일 가용 영역 배치는 비용 최적화와 고가용성을 위한 권장 사항일 뿐 기술적 필수 조건은 아닙니다. 보안 그룹에서 아웃바운드 규칙이 0.0.0.0/0로 열려 있는지 함께 확인해야 하네요. 경로 테이블의 대상 서브넷 링크가 누락되지 않도록 주의해야 해요.

aws ec2 modify-vpc-attribute --vpc-id <VPC_ID> --enable-dns-hostnames Value=true
aws ec2 modify-vpc-attribute --vpc-id <VPC_ID> --enable-dns-support Value=true
aws ec2 create-route --route-table-id <RT_ID> --destination-cidr-block 0.0.0.0/0 --nat-gateway-id <NAT_GW_ID>

이렇게 라우팅과 DNS 속성을 함께 맞추면 Lambda 내부에서도 외부 API 호출이나 패키지 다운로드가 자연스럽게 잘 되네요. 설정을 검토할 때 서브넷 매칭과 게이트웨이 유형을 한 번 더 확인해 보는 것이 같아요. NAT 게이트웨이가 공인 IP를 통해 트래픽을 중계하는 방식임을 기억하면 네트워크 구성이 훨씬 수월해지더라고요. 실수 없이 적용하려면 AWS 콘솔에서 가시적으로 라우팅 테이블을 점검해 보는 것을 추천해요. 이때 경로의 우선순위나 중복 설정도 함께 확인하면 안정적인 통신 환경을 구축할 수 있어요.

클라우드 인프라를 구성할 때 IAM 역할은 자주 막히는 지점 중 하나거든요. 특히 ECS에서 컨테이너가 외부 서비스를 접근할 때 어떤 역할이 적용되는지 헷갈리기 쉬워요. 태스크 역할과 실행 역할을 명확히 구분하지 않으면 권한 오류가 발생하거나 보안 설정이 왜곡될 수 있네요. 두 역할을 분리하면 최소 권한 원칙을 지키기 편하더라고요.

실행 역할은 ecs-tasks.amazonaws.com을 신뢰 기반으로 하고요. 컨테이너 시작, 이미지 풀링, 로그 전송에 사용되지요. 애플리케이션 로직이 외부 AWS 서비스에 접근할 때는 태스크 역할이 필요해요. Terraform이나 CloudFormation으로 정의할 때는 taskDefinition의 executionRoleArn과 taskRoleArn 필드에 각각 ARN을 연결하면 돼요.

resource "aws_ecs_task_definition" "app" {
  family                   = "my-app"
  execution_role_arn       = aws_iam_role.ecs_execution.arn
  task_role_arn            = aws_iam_role.ecs_task.arn
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  container_definitions    = jsonencode([{ name = "app", image = "public.ecr.aws/docker/library/httpd:latest" }])
}

태스크 역할에 정책을 첨부할 때는 실제 코드에서 사용하는 API 액션만 명시하는 게 좋아요. 예를 들어 Secrets Manager에서 시크릿을 가져온다면 secretsmanager:GetSecretValue만 허용하면 충분하네요. 역할 분리만 잘 적용해도 권한 오류를 줄이고 보안 검증도 수월해져요. 각 역할이 담당하는 시나리오를 먼저 매핑한 후 IAM 정책을 작성하면 실수를 피할 수 있네요. 유지보수가 훨씬 깔끔해지더라고요.

+ Recent posts