0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-20 12:12:15 +02:00
openvpn3/openvpn/aws/awsroute.hpp
James Yonan c60f27cc3b
copyright: updated to 2017
Signed-off-by: James Yonan <james@openvpn.net>
2020-02-05 20:08:35 +02:00

394 lines
11 KiB
C++

//
// OpenVPN
//
// Copyright (C) 2012-2017 OpenVPN Technologies, Inc. All rights reserved.
//
// Query/Set VPC routes, requires this policy:
//
// {
// "Version": "2012-10-17",
// "Statement": [
// {
// "Sid": "Stmt1478633458000",
// "Effect": "Allow",
// "Action": [
// "ec2:CreateRoute",
// "ec2:DescribeNetworkInterfaceAttribute",
// "ec2:DescribeNetworkInterfaces",
// "ec2:DescribeRouteTables",
// "ec2:ModifyNetworkInterfaceAttribute",
// "ec2:ReplaceRoute"
// ],
// "Resource": [
// "*"
// ]
// }
// ]
// }
#ifndef OPENVPN_AWS_AWSROUTE_H
#define OPENVPN_AWS_AWSROUTE_H
#include <openvpn/common/xmlhelper.hpp>
#include <openvpn/aws/awshttp.hpp>
#include <openvpn/aws/awspc.hpp>
#include <openvpn/aws/awsrest.hpp>
namespace openvpn {
namespace AWS {
class Route
{
public:
OPENVPN_EXCEPTION(aws_route_error);
class Context
{
public:
Context(PCQuery::Info instance_info_arg,
Creds creds_arg,
RandomAPI::Ptr rng,
Stop* async_stop_arg,
const int debug_level)
: instance_info(std::move(instance_info_arg)),
http_context(std::move(rng), debug_level),
ts(http_context.transaction_set(ec2_host(instance_info))),
creds(std::move(creds_arg)),
async_stop(async_stop_arg)
{
}
void reset()
{
if (ts)
ts->hsc.reset();
}
private:
friend class Route;
PCQuery::Info instance_info;
HTTPContext http_context;
WS::ClientSet::TransactionSet::Ptr ts;
Creds creds;
Stop* async_stop;
};
// Query network_interface_id and route_table_id
// from EC2 API.
class Info
{
public:
Info(std::string network_interface_id_arg,
std::string route_table_id_arg)
: network_interface_id(std::move(network_interface_id_arg)),
route_table_id(std::move(route_table_id_arg))
{
}
Info(Context& ctx)
{
// AWS IDs local to this constructor
std::string vpc_id;
std::string subnet_id;
// first request -- get the AWS network interface
{
// create API query
{
REST::Query q;
q.emplace_back("Action", "DescribeNetworkInterfaces");
q.emplace_back("Filter.1.Name", "attachment.instance-id");
q.emplace_back("Filter.1.Value.1", ctx.instance_info.instanceId);
q.emplace_back("Filter.2.Name", "addresses.private-ip-address");
q.emplace_back("Filter.2.Value.1", ctx.instance_info.privateIp);
add_transaction(ctx, std::move(q));
}
// do transaction
execute_transaction(ctx);
// process reply
{
// get the transaction
WS::ClientSet::Transaction& t = ctx.ts->first_transaction();
// get reply
const std::string reply = t.content_in_string();
// check the reply status
if (!t.http_status_success())
OPENVPN_THROW(aws_route_error, "DescribeNetworkInterfaces: " << t.format_status(*ctx.ts));
// parse XML reply
const Xml::Document doc(reply, "DescribeNetworkInterfaces");
const tinyxml2::XMLElement* item = Xml::find(&doc,
"DescribeNetworkInterfacesResponse",
"networkInterfaceSet",
"item");
if (!item)
OPENVPN_THROW(aws_route_error, "DescribeNetworkInterfaces: cannot locate <item> tag in returned XML:\n" << reply);
network_interface_id = Xml::find_text(item, "networkInterfaceId");
vpc_id = Xml::find_text(item, "vpcId");
subnet_id = Xml::find_text(item, "subnetId");
if (network_interface_id.empty() || vpc_id.empty() || subnet_id.empty())
OPENVPN_THROW(aws_route_error, "DescribeNetworkInterfaces: cannot locate one of networkInterfaceId, vpcId, or subnetId in returned XML:\n" << reply);
}
}
// second request -- get the VPC routing table
{
// create API query
{
REST::Query q;
q.emplace_back("Action", "DescribeRouteTables");
q.emplace_back("Filter.1.Name", "vpc-id");
q.emplace_back("Filter.1.Value.1", vpc_id);
q.emplace_back("Filter.2.Name", "association.subnet-id");
q.emplace_back("Filter.2.Value.1", subnet_id);
add_transaction(ctx, std::move(q));
}
// do transaction
execute_transaction(ctx);
// process reply
{
// get the transaction
WS::ClientSet::Transaction& t = ctx.ts->first_transaction();
// get reply
const std::string reply = t.content_in_string();
// check the reply status
if (!t.http_status_success())
OPENVPN_THROW(aws_route_error, "DescribeRouteTables: " << t.format_status(*ctx.ts) << '\n' << reply);
// parse XML reply
const Xml::Document doc(reply, "DescribeRouteTables");
route_table_id = Xml::find_text(&doc,
"DescribeRouteTablesResponse",
"routeTableSet",
"item",
"routeTableId");
if (route_table_id.empty())
OPENVPN_THROW(aws_route_error, "DescribeRouteTables: cannot locate routeTableId in returned XML:\n" << reply);
}
}
}
std::string to_string() const
{
return '[' + network_interface_id + '/' + route_table_id + ']';
}
std::string network_interface_id;
std::string route_table_id;
};
// Set sourceDestCheck flag on AWS network interface.
static void set_source_dest_check(Context& ctx,
const Info& info,
const bool source_dest_check)
{
const std::string sdc = source_dest_check ? "true" : "false";
// first get the attribute and see if it is already set
// the way we want it
{
REST::Query q;
q.emplace_back("Action", "DescribeNetworkInterfaceAttribute");
q.emplace_back("NetworkInterfaceId", info.network_interface_id);
q.emplace_back("Attribute", "sourceDestCheck");
add_transaction(ctx, std::move(q));
}
// do transaction
execute_transaction(ctx);
// process reply
{
// get the transaction
WS::ClientSet::Transaction& t = ctx.ts->first_transaction();
// get reply
const std::string reply = t.content_in_string();
// check the reply status
if (!t.http_status_success())
OPENVPN_THROW(aws_route_error, "DescribeNetworkInterfaceAttribute: " << t.format_status(*ctx.ts) << '\n' << reply);
// parse XML reply
const Xml::Document doc(reply, "DescribeNetworkInterfaceAttribute");
const std::string retval = Xml::find_text(&doc,
"DescribeNetworkInterfaceAttributeResponse",
"sourceDestCheck",
"value");
// already set to desired value?
if (retval == sdc)
return;
}
// create API query
{
REST::Query q;
q.emplace_back("Action", "ModifyNetworkInterfaceAttribute");
q.emplace_back("NetworkInterfaceId", info.network_interface_id);
q.emplace_back("SourceDestCheck.Value", sdc);
add_transaction(ctx, std::move(q));
}
// do transaction
execute_transaction(ctx);
// process reply
{
// get the transaction
WS::ClientSet::Transaction& t = ctx.ts->first_transaction();
// get reply
const std::string reply = t.content_in_string();
// check the reply status
if (!t.http_status_success())
OPENVPN_THROW(aws_route_error, "ModifyNetworkInterfaceAttribute: " << t.format_status(*ctx.ts) << '\n' << reply);
// parse XML reply
const Xml::Document doc(reply, "ModifyNetworkInterfaceAttribute");
const std::string retval = Xml::find_text(&doc,
"ModifyNetworkInterfaceAttributeResponse",
"return");
if (retval != "true")
OPENVPN_THROW(aws_route_error, "ModifyNetworkInterfaceAttribute: returned failure status: " << '\n' << reply);
OPENVPN_LOG("AWS EC2 ModifyNetworkInterfaceAttribute " << info.network_interface_id << " SourceDestCheck.Value=" << sdc);
}
}
// Create/replace a VPC route
static void replace_create_route(Context& ctx,
const Info& info,
const std::string& route)
{
// create API query
{
REST::Query q;
q.emplace_back("Action", "ReplaceRoute");
q.emplace_back("DestinationCidrBlock", route);
q.emplace_back("NetworkInterfaceId", info.network_interface_id);
q.emplace_back("RouteTableId", info.route_table_id);
add_transaction(ctx, std::move(q));
}
// do transaction
execute_transaction(ctx);
// process reply
{
// get the transaction
WS::ClientSet::Transaction& t = ctx.ts->first_transaction();
// get reply
const std::string reply = t.content_in_string();
// Check the reply status. We only throw on communication failure,
// since ReplaceRoute will legitimately fail if the route doesn't
// exist yet.
if (!t.comm_status_success())
OPENVPN_THROW(aws_route_error, "ReplaceRoute: " << t.format_status(*ctx.ts) << '\n' << reply);
// ReplaceRoute succeeded?
if (t.request_status_success())
{
// parse XML reply
const Xml::Document doc(reply, "ReplaceRoute");
const std::string retval = Xml::find_text(&doc,
"ReplaceRouteResponse",
"return");
if (retval == "true")
{
OPENVPN_LOG("AWS EC2 ReplaceRoute " << route << " -> " << info.to_string());
return;
}
}
}
// Now try CreateRoute
{
REST::Query q;
q.emplace_back("Action", "CreateRoute");
q.emplace_back("DestinationCidrBlock", route);
q.emplace_back("NetworkInterfaceId", info.network_interface_id);
q.emplace_back("RouteTableId", info.route_table_id);
add_transaction(ctx, std::move(q));
}
// do transaction
execute_transaction(ctx);
// process reply
{
// get the transaction
WS::ClientSet::Transaction& t = ctx.ts->first_transaction();
// get reply
const std::string reply = t.content_in_string();
// check the reply status
if (!t.http_status_success())
OPENVPN_THROW(aws_route_error, "CreateRoute: " << t.format_status(*ctx.ts) << '\n' << reply);
// parse XML reply
const Xml::Document doc(reply, "CreateRoute");
const std::string retval = Xml::find_text(&doc,
"CreateRouteResponse",
"return");
if (retval != "true")
OPENVPN_THROW(aws_route_error, "CreateRoute: returned failure status: " << '\n' << reply);
OPENVPN_LOG("AWS EC2 CreateRoute " << route << " -> " << info.to_string());
}
}
private:
static void execute_transaction(Context& ctx)
{
WS::ClientSet::new_request_synchronous(ctx.ts, ctx.async_stop, ctx.http_context.rng(), true);
}
static void add_transaction(const Context& ctx, REST::Query&& q)
{
std::unique_ptr<WS::ClientSet::Transaction> t(new WS::ClientSet::Transaction);
t->req.uri = ec2_uri(ctx, std::move(q));
t->req.method = "GET";
t->ci.keepalive = true;
ctx.ts->reset_push_back(std::move(t));
}
static std::string ec2_uri(const Context& ctx, REST::Query&& q)
{
REST::QueryBuilder qb;
qb.date = REST::amz_date();
qb.expires = 300;
qb.region = ctx.instance_info.region;
qb.service = "ec2";
qb.method = "GET";
qb.host = ec2_host(ctx.instance_info);
qb.uri = "/";
qb.parms = std::move(q);
qb.parms.emplace_back("Version", "2015-10-01");
qb.add_amz_parms(ctx.creds);
qb.sort_parms();
qb.add_amz_signature(ctx.http_context.digest_factory(), ctx.creds);
return qb.uri_query();
}
static std::string ec2_host(const PCQuery::Info& instance_info)
{
return "ec2." + instance_info.region + ".amazonaws.com";
}
};
}
}
#endif