Поддержка IMDSv2

Я пишу, чтобы уточнить информацию о поддержке IMDSv2 через профили экземпляров в Discourse. Мы в процессе миграции нашего сервиса на использование IMDSv2, поскольку IMDSv1 не является безопасным вариантом.

Мы хотели бы понять, поддерживает ли Discourse в настоящее время IMDSv2 через профили экземпляров, и если нет, то каковы планы по поддержке этой функции в ближайшем будущем. Кроме того, существуют ли какие-либо обходные пути или патчи, которые позволили бы нам использовать IMDSv2 с Discourse?

Для нас важно обеспечить соответствие нашим требованиям безопасности, и мы считаем, что использование временных учетных данных через IMDSv2 является критически важным аспектом этого.

Желаемое поведение при доступе к учетным данным безопасности, предоставляемым через профиль экземпляра, следующее:

  • Приложение на экземпляре извлекает учетные данные безопасности, предоставленные ролью, из элемента метаданных экземпляра iam/security-credentials/ имя-роли.
  • Приложению предоставляются разрешения на действия и ресурсы, которые мы определили для роли через учетные данные безопасности, связанные с ролью. Эти учетные данные являются временными и автоматически обновляются. Мы предоставляем новые учетные данные как минимум за пять минут до истечения срока действия старых учетных данных.

Мы отметили, что существуют различия между вызовами 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>

Мы будем признательны за любую информацию, которую вы сможете предоставить о том, как мы можем использовать IMDSv2 с Discourse, или о наличии каких-либо обходных путей или патчей.

Ссылка:

Мне неизвестно о том, чтобы сам Discourse использовал IMDS. Вы установили Discourse на AWS каким-либо образом? Вы уже каким-то образом используете IMDSv1 с Discourse?

Какой у вас сценарий использования?

Я нашёл соответствующий код.

Это находится в спецификациях тестирования.

Discourse использует версию AWS SDK (3.130.2), превышающую минимально необходимую для поддержки IMDSv2. Судя по метрике MetadataNoToken в наших развёртываниях AWS, у нас нет вызовов IMDSv1.

По моим данным, мы уже повсеместно используем IMDSv2.

Мы начали использовать Discourse на экземпляре AWS EC2 год назад. На этой неделе мы обновили наш экземпляр так, чтобы он использовал только IMDSv2, что привело к сбою загрузки в AWS S3 с сообщением об ошибке «не удалось подписать запрос без установленных учетных данных». Мы также используем настройку «s3 use iam profile».

Локальный сервис IMDS используется Discourse для получения учетных данных для выполнения других вызовов API связанных сервисов AWS. Это осуществляется с помощью Ruby aws-sdk-s3.

Мы также наблюдаем эту проблему с резервным копированием после отключения IMDSv1 по соображениям безопасности.

Мы видим использование IMDSv1 (в версии 3.3.0.beta1-dev) через метрику MetadataNoToken, поэтому хотим узнать, в какой версии Discourse был полностью переведён на использование v2?

Сегодня мы тоже столкнулись с этой проблемой, когда перевели наш экземпляр AWS на использование только IMDSv2: наши пользователи больше не могли загружать изображения в S3.

Вероятно, это имеет отношение к делу: мы также используем опцию s3 use iam profile.

На данный момент мы переключили её на «Опционально», что по сути означает, что IMDSv1 всё ещё включён. Это не лучший вариант с точки зрения безопасности, но загрузка файлов снова заработала.

У кого-нибудь есть решение или обходной путь для работы Discourse с IMDSv2?

Мы вносим изменения, чтобы сделать возможным (или ожидаемым) выполнение большей части конфигурации через файл .aws/config, что может пересекаться с этим и сделать его возможным.

@supermathie самое странное, что я не могу понять, это то, что я лично настроил два экземпляра Discourse (dev/prod), которые идентично сконфигурированы (загрузка файлов в 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"
}

Мы запускаем Discourse в контейнере Docker на EC2, как и другие, так что… в чём разница?