mirror of
https://github.com/OpenVPN/openvpn3.git
synced 2024-09-20 12:12:15 +02:00
c60f27cc3b
Signed-off-by: James Yonan <james@openvpn.net>
394 lines
11 KiB
C++
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
|