IMDSv2 지원

Discourse에서 인스턴스 프로파일을 통한 IMDSv2 지원에 대해 문의드립니다. IMDSv1은 보안에 안전한 옵션이 아니므로, 현재 IMDSv2 사용을 위해 서비스를 마이그레이션하고 있습니다.

Discourse가 현재 인스턴스 프로파일을 통해 IMDSv2를 지원하는지, 그리고 지원하지 않는 경우 가까운 미래에 이를 지원할 계획이 있는지 알고 싶습니다. 또한, Discourse와 함께 IMDSv2를 사용할 수 있게 해주는 우회 방법이나 패치가 있는지 여부도 확인하고 싶습니다.

보안 요구 사항을 충족하는 것이 우리에게 중요하며, IMDSv2를 통한 임시 자격 증명 사용이 그 중 핵심 요소라고 생각합니다.

인스턴스 프로파일을 통해 제공되는 보안 자격 증명에 접근하기 위한 기대되는 동작은 다음과 같습니다.

  • 인스턴스 내 애플리케이션이 인스턴스 메타데이터 항목 iam/security-credentials/ role-name에서 역할에 의해 제공된 보안 자격 증명을 가져옵니다.
  • 애플리케이션은 해당 역할과 연관된 보안 자격 증명을 통해 우리가 정의한 작업 및 리소스에 대한 권한을 부여받습니다. 이러한 보안 자격 증명은 임시 자격 증명이며 자동으로 회전됩니다. 기존 자격 증명의 만료 최소 5분 전에 새 자격 증명을 사용할 수 있게 합니다.

IMDSv1과 IMDSv2 호출 사이에 차이가 있음을 확인했습니다.

IMDSv1 호출:

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/s3access

반면, IMDSv2 호출은 메타데이터 토큰의 사용을 필요로 하며, 다음 명령을 사용하여 수행할 수 있습니다:

TOKEN=`curl -X PUT "<http://169.254.169.254/latest/api/token>" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` \\\\\\\\
&& curl -H "X-aws-ec2-metadata-token: $TOKEN" -v <http://169.254.169.254/latest/meta-data/iam/security-credentials/s3access>

Discourse와 함께 IMDSv2를 사용하는 방법 또는 사용 가능한 우회 방법이나 패치가 있는지에 대한 정보를 제공해 주시면 감사하겠습니다.

참고 자료:

Discourse 자체가 IMDS를 사용하는 방식에 대해 제가 아는 바는 없습니다. 혹시 Discourse를 AWS에 설치하셨나요? 이미 Discourse에서 IMDSv1을 사용하고 계신 건가요?

사용 사례가 어떻게 되시나요?

관련 코드를 하나 찾았습니다.

이것은 테스트 스펙에 포함된 내용입니다.

Discourse는 IMDSv2를 지원하기 위해 최소 요구 사항보다 높은 버전의 AWS SDK(3.130.2)를 사용하고 있으며, AWS 배포 환경의 MetadataNoToken 메트릭을 확인해 본 결과 IMDSv1에 대한 호출이 전혀 없는 것으로 보입니다.

제가 파악한 바로는 이미 모든 곳에서 IMDSv2를 사용하고 있습니다.

1년 전부터 AWS EC2 인스턴스에서 Discourse를 사용하고 있습니다. 이번 주에 인스턴스를 IMDSv2만 사용하도록 업데이트했는데, 이로 인해 AWS S3 업로드가 "unable to sign request without credentials set"라는 오류 메시지와 함께 중단되었습니다. 또한 “s3 use iam profile” 설정도 함께 사용하고 있습니다.

로컬 IMDS 서비스는 Discourse가 기타 AWS 관련 서비스 API 호출에 사용할 자격 증명을 가져오는 데 사용됩니다. 이는 Ruby aws-sdk-s3를 사용하여 수행됩니다.

보안상의 이유로 IMDSv1을 비활성화한 후에도 이 백업 문제를 겪고 있습니다.

MetadataNoToken 메트릭을 통해 IMDSv1(3.3.0.beta1-dev) 사용이 확인되므로, Discourse의 어떤 버전에서 모든 곳에서 v2 사용으로 전환되었는지 궁금합니다.

우리도 오늘 AWS 인스턴스를 IMDSv2 전용으로 변경한 후 이 문제에 걸렸습니다. 사용자들이 더 이상 S3에 이미지를 업로드할 수 없게 되었습니다.

아마도 관련이 있을 수 있는 점: 우리는 또한 s3 use iam profile 옵션을 사용 중입니다.

지금은 "Optional"으로 전환했는데, 이는 기본적으로 IMDSv1이 여전히 활성화되어 있다는 것을 의미합니다. 보안 측면에서는 최선은 아니지만, 이렇게 하자 업로드가 다시 작동했습니다.

IMDSv2와 함께 Discourse를 작동시키는 해결책이나 우회 방법이 있는 분이 계신가요?

이 기능과 중복될 수 있는 .aws/config 파일을 통해 더 많은 구성을 가능하도록 하는 변경 작업을 진행 중입니다.

@supermathie 제가 아무리 생각해봐도 이해가 안 되는 가장 이상한 점은, 제가 직접 dev/prod 두 개의 discourse 인스턴스를 동일하게 구성(파일 업로드 및 백업용 S3, IAM 프로파일 사용)하고 동일한 버전(9436f5e3d4)으로 업데이트했는데, IMDSv1을 비활성화했을 때 dev 환경에서는 모든 것이 정상적으로 작동한 반면 prod 환경에서는 작동하지 않고 “unable to sign request without credentials set” 같은 오류가 계속 발생한다는 것입니다. 정말 난해한 문제입니다.

테스트나 확인해볼 수 있는 방법이 떠오르시면 알려주세요.

@ducks가 SDK에서 IMDS 자격 증명을 가져오는 데 사용되는 타임아웃이 매우 공격적(1초, 재시도 없음)이라는 점을 식별했습니다. 따라서 해당 타임아웃에 걸리고 있을 가능성이 있습니다.

하지만 그것은 그저 추측일 뿐입니다.

프로덕션 환경에 콘솔로 접속할 수 있다면, 다음과 같이 인터랙티브로 실행해 볼 수 있나요:

discourse(prod)> c = Aws::S3::Client.new(region: ENV['DISCOURSE_S3_REGION'])
=> #<Aws::S3::Client>

discourse(prod)> c.list_objects_v2(bucket: ENV['DISCOURSE_S3_BUCKET']).contents.count
=> 1000

문제가 무엇이었는지 파악했고, 결국 내 탓이라고 할 수밖에 없었지만, 문제는 꽤 미묘했습니다.
문제는 "HttpPutResponseHopLimit"이 1로 설정되어 있어 컨테이너 내부에서 IMDSv2를 호출할 수 없었던 것이었습니다.

이 명령어를 실행하면 다음과 같은 응답을 받았습니다:

> aws ec2 describe-instances --instance-ids i-00000000000000000 --query “Reservations[0].Instances[0].MetadataOptions”`
{
“State”: “applied”,
“HttpTokens”: “optional”,
“HttpPutResponseHopLimit”: 1,
“HttpEndpoint”: “enabled”,
“HttpProtocolIpv6”: “disabled”,
“InstanceMetadataTags”: “disabled”
}

설정을 조정하면 올바른 출력은 다음과 같습니다.

> aws ec2 describe-instances --instance-ids i-00000000000000000 --query “Reservations[0].Instances[0].MetadataOptions”`
{
“State”: “applied”,
“HttpTokens”: “required”,
“HttpPutResponseHopLimit”: 2,
“HttpEndpoint”: “enabled”,
“HttpProtocolIpv6”: “disabled”,
“InstanceMetadataTags”: “disabled”
}

…그리고 마침내 미스터리는 풀렸습니다 :sweat_smile:

모두에게 도움을 주셔서 감사합니다

이 정보를 알게 되어 좋습니다!

그리고 이것이 다른 사람들에게도 적용되기를 바랍니다.

하지만 왜 우리에게서는 문제가 되지 않는지 궁금합니다. 우리는 이 값을 1로 설정해 두었는데도 작동하거든요?

discourse(prod)> ENV['AWS_EC2_METADATA_V1_DISABLED'] = 'true'
=> "true"
discourse(prod)> c = Aws::S3::Client.new(region: ENV['DISCOURSE_S3_REGION'])
=> #<Aws::S3::Client>
discourse(prod)> c.config.credentials.disable_imds_v1
=> true
discourse(prod)> c.list_objects_v2(bucket: ENV['DISCOURSE_S3_BUCKET']).contents.count
=> 1000

그리고 해당 인스턴스는 동일한 쿼리 명령에 따라 다음과 같은 메타데이터를 가지고 있습니다:

{
    "State": "applied",
    "HttpTokens": "optional",
    "HttpPutResponseHopLimit": 1,
    "HttpEndpoint": "enabled",
    "HttpProtocolIpv6": "disabled",
    "InstanceMetadataTags": "disabled"
}

우리는 다른 사람들과 마찬가지로 EC2에서 Docker 컨테이너로 Discourse를 실행하고 있으므로… 차이는 무엇일까요?