OpenVPN 3 Core Library
Loading...
Searching...
No Matches
awsroute.hpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12// Query/Set VPC routes, requires this policy:
13//
14// {
15// "Version": "2012-10-17",
16// "Statement": [
17// {
18// "Sid": "Stmt1478633458000",
19// "Effect": "Allow",
20// "Action": [
21// "ec2:CreateRoute",
22// "ec2:DescribeNetworkInterfaceAttribute",
23// "ec2:DescribeNetworkInterfaces",
24// "ec2:DescribeRouteTables",
25// "ec2:ModifyNetworkInterfaceAttribute",
26// "ec2:ReplaceRoute",
27// "ec2:DeleteRoute"
28// ],
29// "Resource": [
30// "*"
31// ]
32// }
33// ]
34// }
35//
36// Creating and tagging route tables also require these policy actions:
37//
38// ec2:CreateRouteTable
39// ec2:CreateTags
40//
41
42#pragma once
43
46#include <openvpn/aws/awspc.hpp>
48
49namespace openvpn::AWS {
50class Route
51{
52 public:
53 OPENVPN_EXCEPTION(aws_route_error);
54
55 enum class RouteTargetType
56 {
59 };
60
61 class Context
62 {
63 public:
64 Context(PCQuery::Info instance_info_arg,
65 Creds creds_arg,
67 Stop *async_stop_arg,
68 const int debug_level)
69 : instance_info(std::move(instance_info_arg)),
70 http_context(std::move(rng), debug_level),
71 ts(http_context.transaction_set(ec2_host(instance_info))),
72 creds(std::move(creds_arg)),
73 async_stop(async_stop_arg)
74 {
75 }
76
77 void reset()
78 {
79 if (ts)
80 ts->hsc.reset();
81 }
82
83 std::string instance_id() const
84 {
86 }
87
88 private:
89 friend class Route;
95 };
96
97 // Query network_interface_id and route_table_id
98 // from EC2 API.
99 class Info
100 {
101 public:
102 Info(std::string network_interface_id_arg,
103 std::string route_table_id_arg)
104 : network_interface_id(std::move(network_interface_id_arg)),
105 route_table_id(std::move(route_table_id_arg))
106 {
107 }
108
110 {
111 // AWS IDs local to this constructor
112 std::string subnet_id;
113
114 // first request -- get the AWS network interface
115 {
116 // create API query
117 {
118 REST::Query q;
119 q.emplace_back("Action", "DescribeNetworkInterfaces");
120 q.emplace_back("Filter.1.Name", "attachment.instance-id");
121 q.emplace_back("Filter.1.Value.1", ctx.instance_info.instanceId);
122 q.emplace_back("Filter.2.Name", "addresses.private-ip-address");
123 q.emplace_back("Filter.2.Value.1", ctx.instance_info.privateIp);
124 add_transaction(ctx, std::move(q));
125 }
126
127 // do transaction
129
130 // process reply
131 {
132 // get the transaction
133 WS::ClientSet::Transaction &t = ctx.ts->first_transaction();
134
135 // get reply
136 const std::string reply = t.content_in_string();
137
138 // check the reply status
139 if (!t.http_status_success())
140 OPENVPN_THROW(aws_route_error, "DescribeNetworkInterfaces: " << t.format_status(*ctx.ts));
141
142 // parse XML reply
143 const Xml::Document doc(reply, "DescribeNetworkInterfaces");
144 const tinyxml2::XMLElement *item = Xml::find(&doc,
145 "DescribeNetworkInterfacesResponse",
146 "networkInterfaceSet",
147 "item");
148 if (!item)
149 OPENVPN_THROW(aws_route_error, "DescribeNetworkInterfaces: cannot locate <item> tag in returned XML:\n"
150 << reply);
151 network_interface_id = Xml::find_text(item, "networkInterfaceId");
152 vpc_id = Xml::find_text(item, "vpcId");
153 subnet_id = Xml::find_text(item, "subnetId");
154 if (network_interface_id.empty() || vpc_id.empty() || subnet_id.empty())
155 OPENVPN_THROW(aws_route_error, "DescribeNetworkInterfaces: cannot locate one of networkInterfaceId, vpcId, or subnetId in returned XML:\n"
156 << reply);
157 }
158 }
159
160 // second request -- get the VPC routing table
161 {
162 // create API query
163 {
164 REST::Query q;
165 q.emplace_back("Action", "DescribeRouteTables");
166 q.emplace_back("Filter.1.Name", "vpc-id");
167 q.emplace_back("Filter.1.Value.1", vpc_id);
168 q.emplace_back("Filter.2.Name", "association.subnet-id");
169 q.emplace_back("Filter.2.Value.1", subnet_id);
170 add_transaction(ctx, std::move(q));
171 }
172
173 // do transaction
175
176 // process reply
177 {
178 // get the transaction
179 WS::ClientSet::Transaction &t = ctx.ts->first_transaction();
180
181 // get reply
182 const std::string reply = t.content_in_string();
183
184 // check the reply status
185 if (!t.http_status_success())
186 OPENVPN_THROW(aws_route_error, "DescribeRouteTables: " << t.format_status(*ctx.ts) << '\n'
187 << reply);
188
189 // parse XML reply
190 const Xml::Document doc(reply, "DescribeRouteTables");
192 "DescribeRouteTablesResponse",
193 "routeTableSet",
194 "item",
195 "routeTableId");
196 if (route_table_id.empty())
197 OPENVPN_THROW(aws_route_error, "DescribeRouteTables: cannot locate routeTableId in returned XML:\n"
198 << reply);
199 }
200 }
201 }
202
203 std::string to_string() const
204 {
205 return '[' + network_interface_id + '/' + route_table_id + ']';
206 }
207
209 std::string route_table_id;
210 std::string vpc_id;
211 };
212
213 // Set sourceDestCheck flag on AWS network interface.
215 const std::string &network_interface_id,
216 const bool source_dest_check)
217 {
218 const std::string sdc = source_dest_check ? "true" : "false";
219
220 // first get the attribute and see if it is already set
221 // the way we want it
222 {
223 REST::Query q;
224 q.emplace_back("Action", "DescribeNetworkInterfaceAttribute");
225 q.emplace_back("NetworkInterfaceId", network_interface_id);
226 q.emplace_back("Attribute", "sourceDestCheck");
227 add_transaction(ctx, std::move(q));
228 }
229
230 // do transaction
232
233 // process reply
234 {
235 // get the transaction
236 WS::ClientSet::Transaction &t = ctx.ts->first_transaction();
237
238 // get reply
239 const std::string reply = t.content_in_string();
240
241 // check the reply status
242 if (!t.http_status_success())
243 OPENVPN_THROW(aws_route_error, "DescribeNetworkInterfaceAttribute: " << t.format_status(*ctx.ts) << '\n'
244 << reply);
245
246 // parse XML reply
247 const Xml::Document doc(reply, "DescribeNetworkInterfaceAttribute");
248 const std::string retval = Xml::find_text(&doc,
249 "DescribeNetworkInterfaceAttributeResponse",
250 "sourceDestCheck",
251 "value");
252 // already set to desired value?
253 if (retval == sdc)
254 return;
255 }
256
257 // create API query
258 {
259 REST::Query q;
260 q.emplace_back("Action", "ModifyNetworkInterfaceAttribute");
261 q.emplace_back("NetworkInterfaceId", network_interface_id);
262 q.emplace_back("SourceDestCheck.Value", sdc);
263 add_transaction(ctx, std::move(q));
264 }
265
266 // do transaction
268
269 // process reply
270 {
271 // get the transaction
272 WS::ClientSet::Transaction &t = ctx.ts->first_transaction();
273
274 // get reply
275 const std::string reply = t.content_in_string();
276
277 // check the reply status
278 if (!t.http_status_success())
279 OPENVPN_THROW(aws_route_error, "ModifyNetworkInterfaceAttribute: " << t.format_status(*ctx.ts) << '\n'
280 << reply);
281
282 // parse XML reply
283 const Xml::Document doc(reply, "ModifyNetworkInterfaceAttribute");
284 const std::string retval = Xml::find_text(&doc,
285 "ModifyNetworkInterfaceAttributeResponse",
286 "return");
287 if (retval != "true")
288 OPENVPN_THROW(aws_route_error, "ModifyNetworkInterfaceAttribute: returned failure status: " << '\n'
289 << reply);
290
291 OPENVPN_LOG("AWS EC2 ModifyNetworkInterfaceAttribute " << network_interface_id << " SourceDestCheck.Value=" << sdc);
292 }
293 }
294
295 static void delete_route(Context &ctx,
296 const std::string &route_table_id,
297 const std::string &cidr,
298 bool ipv6)
299 {
300 {
301 REST::Query q;
302 q.emplace_back("Action", "DeleteRoute");
303 q.emplace_back(ipv6 ? "DestinationIpv6CidrBlock" : "DestinationCidrBlock", cidr);
304 q.emplace_back("RouteTableId", route_table_id);
305 add_transaction(ctx, std::move(q));
306 }
307
308 // do transaction
310
311 // process reply
312 {
313 // get the transaction
314 WS::ClientSet::Transaction &t = ctx.ts->first_transaction();
315
316 // get reply
317 const std::string reply = t.content_in_string();
318
319 // check the reply status
320 if (!t.http_status_success())
321 OPENVPN_THROW(aws_route_error, "DeleteRoute: " << t.format_status(*ctx.ts) << '\n'
322 << reply);
323
324 // parse XML reply
325 const Xml::Document doc(reply, "DeleteRoute");
326 const std::string retval = Xml::find_text(&doc,
327 "DeleteRouteResponse",
328 "return");
329 if (retval != "true")
330 OPENVPN_THROW(aws_route_error, "DeleteRoute: returned failure status: " << '\n'
331 << reply);
332
333 OPENVPN_LOG("AWS EC2 DeleteRoute " << cidr << " -> table " << route_table_id);
334 }
335 }
336
337 static std::string create_route_table(Context &ctx,
338 const std::string &vpc_id,
339 const std::string &name)
340 {
341 std::string route_table_id;
342
343 // create route table
344 {
345 REST::Query q;
346 q.emplace_back("Action", "CreateRouteTable");
347 q.emplace_back("VpcId", vpc_id);
348 add_transaction(ctx, std::move(q));
349 }
350
352
353 // process reply
354 {
355 // get the transaction
356 WS::ClientSet::Transaction &t = ctx.ts->first_transaction();
357
358 auto reply = t.content_in_string();
359
360 // check the reply status
361 if (!t.http_status_success())
362 OPENVPN_THROW(aws_route_error, "CreateRouteTable: " << t.format_status(*ctx.ts) << '\n'
363 << reply);
364
365 const Xml::Document doc(reply, "CreateRouteTable");
366 route_table_id = Xml::find_text(&doc,
367 "CreateRouteTableResponse",
368 "routeTable",
369 "routeTableId");
370
371 OPENVPN_LOG("AWS EC2 CreateRouteTable -> RouteTableId " << route_table_id);
372 }
373
374 // tag route table with provided name
375 {
376 REST::Query q;
377 q.emplace_back("Action", "CreateTags");
378 q.emplace_back("ResourceId.1", route_table_id);
379 q.emplace_back("Tag.1.Key", "Name");
380 q.emplace_back("Tag.1.Value", name);
381 add_transaction(ctx, std::move(q));
382 }
383
385
386 // process reply
387 {
388 // get the transaction
389 WS::ClientSet::Transaction &t = ctx.ts->first_transaction();
390
391 // get reply
392 const std::string reply = t.content_in_string();
393
394 if (!t.request_status_success())
395 OPENVPN_THROW(aws_route_error, "CreateTags: " << t.format_status(*ctx.ts) << '\n'
396 << reply);
397 }
398
399 return route_table_id;
400 }
401
402 // Create/replace a VPC route
404 const std::string &route_table_id,
405 const std::string &route,
406 RouteTargetType target_type,
407 const std::string &target_value,
408 bool ipv6)
409 {
410 std::string target_type_str;
411
412 switch (target_type)
413 {
415 target_type_str = "InstanceId";
416 break;
417
419 target_type_str = "NetworkInterfaceId";
420 break;
421
422 default:
423 OPENVPN_THROW(aws_route_error,
424 "replace_create_route: unknown RouteTargetType " << (int)target_type);
425 }
426
427 const std::string dest_cidr_block_name = ipv6 ? "DestinationCidrIpv6Block" : "DestinationCidrBlock";
428
429 // create API query
430 {
431 REST::Query q;
432 q.emplace_back("Action", "ReplaceRoute");
433 q.emplace_back(dest_cidr_block_name, route);
434 q.emplace_back(target_type_str, target_value);
435 q.emplace_back("RouteTableId", route_table_id);
436 add_transaction(ctx, std::move(q));
437 }
438
439 // we expect to get 400 if route doesn't exist, so no need to retry
440 ctx.ts->retry_on_http_4xx = false;
441
442 // do transaction
444
445 // process reply
446 {
447 // get the transaction
448 WS::ClientSet::Transaction &t = ctx.ts->first_transaction();
449
450 // get reply
451 const std::string reply = t.content_in_string();
452
453 // Check the reply status. We only throw on communication failure,
454 // since ReplaceRoute will legitimately fail if the route doesn't
455 // exist yet.
456 if (!t.comm_status_success())
457 OPENVPN_THROW(aws_route_error, "ReplaceRoute: " << t.format_status(*ctx.ts) << '\n'
458 << reply);
459
460 // ReplaceRoute succeeded?
462 {
463 // parse XML reply
464 const Xml::Document doc(reply, "ReplaceRoute");
465 const std::string retval = Xml::find_text(&doc,
466 "ReplaceRouteResponse",
467 "return");
468 if (retval == "true")
469 {
470 OPENVPN_LOG("AWS EC2 ReplaceRoute " << route << " -> table " << route_table_id);
471 return;
472 }
473 }
474 }
475
476 // Now try CreateRoute
477 {
478 REST::Query q;
479 q.emplace_back("Action", "CreateRoute");
480 q.emplace_back(dest_cidr_block_name, route);
481 q.emplace_back(target_type_str, target_value);
482 q.emplace_back("RouteTableId", route_table_id);
483 add_transaction(ctx, std::move(q));
484 }
485
486 ctx.ts->retry_on_http_4xx = true;
487
488 // do transaction
490
491 // process reply
492 {
493 // get the transaction
494 WS::ClientSet::Transaction &t = ctx.ts->first_transaction();
495
496 // get reply
497 const std::string reply = t.content_in_string();
498
499 // check the reply status
500 if (!t.http_status_success())
501 OPENVPN_THROW(aws_route_error, "CreateRoute: " << t.format_status(*ctx.ts) << '\n'
502 << reply);
503
504 // parse XML reply
505 const Xml::Document doc(reply, "CreateRoute");
506 const std::string retval = Xml::find_text(&doc,
507 "CreateRouteResponse",
508 "return");
509 if (retval != "true")
510 OPENVPN_THROW(aws_route_error, "CreateRoute: returned failure status: " << '\n'
511 << reply);
512
513 OPENVPN_LOG("AWS EC2 CreateRoute " << route << " -> table " << route_table_id);
514 }
515 }
516
517 static std::string get_route_table_by_name(Context &ctx, std::string &name)
518 {
519 REST::Query q;
520 q.emplace_back("Action", "DescribeRouteTables");
521 q.emplace_back("Filter.1.Name", "tag:Name");
522 q.emplace_back("Filter.1.Value.1", name);
523 add_transaction(ctx, std::move(q));
524
525 // do transaction
527
528 // process reply
529
530 // get the transaction
531 WS::ClientSet::Transaction &t = ctx.ts->first_transaction();
532
533 // get reply
534 const std::string reply = t.content_in_string();
535
536 // check the reply status
537 if (!t.http_status_success())
538 OPENVPN_THROW(aws_route_error, "DescribeRouteTables: " << t.format_status(*ctx.ts) << '\n'
539 << reply);
540
541 // parse XML reply
542 const Xml::Document doc(reply, "DescribeRouteTables");
543 auto route_table_id = Xml::find_text(&doc,
544 "DescribeRouteTablesResponse",
545 "routeTableSet",
546 "item",
547 "routeTableId");
548
549 OPENVPN_LOG("AWS EC2 DescribeRouteTables " << name << " -> routeTableId " << (route_table_id.empty() ? "<none>" : route_table_id));
550
551 return route_table_id;
552 }
553
554 private:
555 static void execute_transaction(Context &ctx)
556 {
558 }
559
560 static void add_transaction(const Context &ctx, REST::Query &&q)
561 {
562 std::unique_ptr<WS::ClientSet::Transaction> t(new WS::ClientSet::Transaction);
563 t->req.uri = ec2_uri(ctx, std::move(q));
564 t->req.method = "GET";
565 t->ci.keepalive = true;
566 ctx.ts->transactions.clear();
567 ctx.ts->transactions.push_back(std::move(t));
568 }
569
570 static std::string ec2_uri(const Context &ctx, REST::Query &&q)
571 {
573 qb.date = REST::amz_date();
574 qb.expires = 300;
575 qb.region = ctx.instance_info.region;
576 qb.service = "ec2";
577 qb.method = "GET";
578 qb.host = ec2_host(ctx.instance_info);
579 qb.uri = "/";
580 qb.parms = std::move(q);
581 qb.parms.emplace_back("Version", "2015-10-01");
582 qb.add_amz_parms(ctx.creds);
583 qb.sort_parms();
585 return qb.uri_query();
586 }
587
588 static std::string ec2_host(const PCQuery::Info &instance_info)
589 {
590 return "ec2." + instance_info.region + ".amazonaws.com";
591 }
592};
593} // namespace openvpn::AWS
StrongRandomAPI * rng() const
Definition awshttp.hpp:76
DigestFactory & digest_factory() const
Definition awshttp.hpp:71
static std::string amz_date()
Definition awsrest.hpp:36
std::string instance_id() const
Definition awsroute.hpp:83
WS::ClientSet::TransactionSet::Ptr ts
Definition awsroute.hpp:92
Context(PCQuery::Info instance_info_arg, Creds creds_arg, StrongRandomAPI::Ptr rng, Stop *async_stop_arg, const int debug_level)
Definition awsroute.hpp:64
Info(std::string network_interface_id_arg, std::string route_table_id_arg)
Definition awsroute.hpp:102
std::string network_interface_id
Definition awsroute.hpp:208
std::string to_string() const
Definition awsroute.hpp:203
static void delete_route(Context &ctx, const std::string &route_table_id, const std::string &cidr, bool ipv6)
Definition awsroute.hpp:295
static std::string ec2_uri(const Context &ctx, REST::Query &&q)
Definition awsroute.hpp:570
static std::string create_route_table(Context &ctx, const std::string &vpc_id, const std::string &name)
Definition awsroute.hpp:337
static void replace_create_route(Context &ctx, const std::string &route_table_id, const std::string &route, RouteTargetType target_type, const std::string &target_value, bool ipv6)
Definition awsroute.hpp:403
static void execute_transaction(Context &ctx)
Definition awsroute.hpp:555
static void set_source_dest_check(Context &ctx, const std::string &network_interface_id, const bool source_dest_check)
Definition awsroute.hpp:214
static void add_transaction(const Context &ctx, REST::Query &&q)
Definition awsroute.hpp:560
static std::string get_route_table_by_name(Context &ctx, std::string &name)
Definition awsroute.hpp:517
OPENVPN_EXCEPTION(aws_route_error)
static std::string ec2_host(const PCQuery::Info &instance_info)
Definition awsroute.hpp:588
void reset() noexcept
Points this RCPtr<T> to nullptr safely.
Definition rc.hpp:290
static void new_request_synchronous(const TransactionSet::Ptr ts, Stop *stop=nullptr, RandomAPI *prng=nullptr, const bool sps=false)
static const tinyxml2::XMLElement * find(const tinyxml2::XMLNode *node, const T &first, Args... args)
Definition xmlhelper.hpp:87
static std::string find_text(const tinyxml2::XMLNode *node, const T &first, Args... args)
Definition xmlhelper.hpp:75
#define OPENVPN_THROW(exc, stuff)
#define OPENVPN_LOG(args)
void add_amz_parms(const Creds &creds)
Definition awsrest.hpp:163
std::string uri_query() const
Definition awsrest.hpp:153
void add_amz_signature(DigestFactory &digest_factory, const Creds &creds)
Definition awsrest.hpp:180
std::string format_status(const TransactionSet &ts) const
std::string content_in_string() const
remote_address ipv6