From 32d93e931c0892f0ee68ed2468ff7dcbad502342 Mon Sep 17 00:00:00 2001 From: Lev Stipakov Date: Thu, 29 Jun 2023 07:58:30 +0000 Subject: [PATCH] aws: support for IMDSv2 IMDSv2 requires to obtain token for instance metadata requests https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-metadata-v2-how-it-works.html IMDSv2 works on all reasonably modern instances and can be optionally forced to be used instead of v1. Fixes https://github.com/OpenVPN/openvpn3-linux/issues/192 Signed-off-by: Ubuntu --- openvpn/aws/awspc.hpp | 135 ++++++++++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 43 deletions(-) diff --git a/openvpn/aws/awspc.hpp b/openvpn/aws/awspc.hpp index 56801e6b6..572d0daa5 100644 --- a/openvpn/aws/awspc.hpp +++ b/openvpn/aws/awspc.hpp @@ -111,6 +111,25 @@ class PCQuery : public RC { } + WS::ClientSet::TransactionSet::Ptr prepare_transaction_set() + { + // make HTTP context + WS::Client::Config::Ptr http_config(new WS::Client::Config()); + http_config->frame = frame; + http_config->connect_timeout = 15; + http_config->general_timeout = 30; + + // make transation set + WS::ClientSet::TransactionSet::Ptr ts = new WS::ClientSet::TransactionSet; + ts->host.host = "169.254.169.254"; + ts->host.port = "80"; + ts->http_config = http_config; + ts->max_retries = 3; + ts->debug_level = debug_level; + + return ts; + } + void start(std::function completion_arg) { // make sure we are not in a pending state @@ -126,58 +145,20 @@ class PCQuery : public RC try { - // make HTTP context - WS::Client::Config::Ptr http_config(new WS::Client::Config()); - http_config->frame = frame; - http_config->connect_timeout = 15; - http_config->general_timeout = 30; - - // make transaction set for initial local query - WS::ClientSet::TransactionSet::Ptr ts = new WS::ClientSet::TransactionSet; - ts->host.host = "169.254.169.254"; - ts->host.port = "80"; - ts->http_config = http_config; - ts->max_retries = 3; - ts->debug_level = debug_level; - - // transaction #1 - { - std::unique_ptr t(new WS::ClientSet::Transaction); - t->req.method = "GET"; - t->req.uri = "/latest/dynamic/instance-identity/document"; - ts->transactions.push_back(std::move(t)); - } - - // transaction #2 - { - std::unique_ptr t(new WS::ClientSet::Transaction); - t->req.method = "GET"; - t->req.uri = "/latest/dynamic/instance-identity/pkcs7"; - ts->transactions.push_back(std::move(t)); - } + auto ts = prepare_transaction_set(); - // transaction #3 - if (lookup_product_code) - { - std::unique_ptr t(new WS::ClientSet::Transaction); - t->req.method = "GET"; - t->req.uri = "/latest/meta-data/product-codes"; - ts->transactions.push_back(std::move(t)); - } - - // transaction #4 - if (!role_for_credentials.empty()) { std::unique_ptr t(new WS::ClientSet::Transaction); - t->req.method = "GET"; - t->req.uri = "/latest/meta-data/iam/security-credentials/" + role_for_credentials; + t->req.method = "PUT"; + t->req.uri = "/latest/api/token"; + t->ci.extra_headers.emplace_back("X-aws-ec2-metadata-token-ttl-seconds: 60"); ts->transactions.push_back(std::move(t)); } // completion handler ts->completion = [self = Ptr(this)](WS::ClientSet::TransactionSet &ts) { - self->local_query_complete(ts); + self->token_query_complete(ts); }; // do the request @@ -296,6 +277,74 @@ class PCQuery : public RC } } + void token_query_complete(WS::ClientSet::TransactionSet <s) + { + try + { + // get transaction and check that they succeeded + WS::ClientSet::Transaction &token_trans = *lts.transactions.at(0); + if (!token_trans.request_status_success()) + { + done("could not fetch AWS session token: " + token_trans.format_status(lts)); + return; + } + const std::string token = token_trans.content_in.to_string(); + + auto ts = prepare_transaction_set(); + + // transaction #1 + { + std::unique_ptr t(new WS::ClientSet::Transaction); + t->req.method = "GET"; + t->req.uri = "/latest/dynamic/instance-identity/document"; + t->ci.extra_headers.emplace_back("X-aws-ec2-metadata-token: " + token); + ts->transactions.push_back(std::move(t)); + } + + // transaction #2 + { + std::unique_ptr t(new WS::ClientSet::Transaction); + t->req.method = "GET"; + t->req.uri = "/latest/dynamic/instance-identity/pkcs7"; + t->ci.extra_headers.emplace_back("X-aws-ec2-metadata-token: " + token); + ts->transactions.push_back(std::move(t)); + } + + // transaction #3 + if (lookup_product_code) + { + std::unique_ptr t(new WS::ClientSet::Transaction); + t->req.method = "GET"; + t->req.uri = "/latest/meta-data/product-codes"; + t->ci.extra_headers.emplace_back("X-aws-ec2-metadata-token: " + token); + ts->transactions.push_back(std::move(t)); + } + + // transaction #4 + if (!role_for_credentials.empty()) + { + std::unique_ptr t(new WS::ClientSet::Transaction); + t->req.method = "GET"; + t->req.uri = "/latest/meta-data/iam/security-credentials/" + role_for_credentials; + t->ci.extra_headers.emplace_back("X-aws-ec2-metadata-token: " + token); + ts->transactions.push_back(std::move(t)); + } + + // completion handler + ts->completion = [self = Ptr(this)](WS::ClientSet::TransactionSet &ts) + { + self->local_query_complete(ts); + }; + + // do the request + cs->new_request(ts); + } + catch (const std::exception &e) + { + done(e.what()); + } + } + void queue_pc_validation(const std::string &pc) { if (debug_level >= 3)