OpenVPN 3 Core Library
Loading...
Searching...
No Matches
awspc.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// Get AWS info such as instanceId, region, and privateIp.
13
14#pragma once
15
16#include <string>
17#include <utility>
18
26
27namespace openvpn::AWS {
28
29class PCQuery : public RC<thread_unsafe_refcount>
30{
31 public:
33
34 OPENVPN_EXCEPTION(awspc_query_error);
35
36 struct Info
37 {
38 std::string instanceId;
39 std::string region;
40 std::string az;
41 std::string privateIp;
42
44
45 std::string error;
46
47 bool is_error() const
48 {
49 return !error.empty();
50 }
51
53 {
54 return !instanceId.empty() && !region.empty() && !privateIp.empty();
55 }
56
57 // example: [instanceId=i-ae91d23e region=us-east-1 privateIp=10.0.0.218]
58 std::string to_string() const
59 {
60 std::string ret = "[instanceId=" + instanceId + " region=" + region;
61 if (!privateIp.empty())
62 ret += " privateIp=" + privateIp;
63 if (!error.empty())
64 ret += " error='" + error + '\'';
65 ret += ']';
66 return ret;
67 }
68 };
69
71 const int debug_level_arg)
72 : cs(std::move(cs_arg)),
74 debug_level(debug_level_arg)
75 {
76 }
77
79 const std::string &role_for_credentials_arg,
80 const std::string &certs_dir_arg)
81 : cs(std::move(cs_arg)),
83 debug_level(0),
84 role_for_credentials(role_for_credentials_arg),
85 certs_dir(certs_dir_arg)
86 {
87 }
88
90 {
91 // make HTTP context
93 http_config->frame = frame;
94 http_config->connect_timeout = 15;
95 http_config->general_timeout = 30;
96
97 // make transation set
99 ts->host.host = "169.254.169.254";
100 ts->host.port = "80";
101 ts->http_config = http_config;
102 ts->max_retries = 3;
103 ts->debug_level = debug_level;
104
105 return ts;
106 }
107
108 void start(std::function<void(Info info)> completion_arg)
109 {
110 // make sure we are not in a pending state
111 if (pending)
112 throw awspc_query_error("request pending");
113 pending = true;
114
115 // save completion method
116 completion = std::move(completion_arg);
117
118 // init return object
119 info = Info();
120
121 try
122 {
123 auto ts = prepare_transaction_set();
124
125 {
126 std::unique_ptr<WS::ClientSet::Transaction> t(new WS::ClientSet::Transaction);
127 t->req.method = "PUT";
128 t->req.uri = "/latest/api/token";
129 t->ci.extra_headers.emplace_back("X-aws-ec2-metadata-token-ttl-seconds: 60");
130 ts->transactions.push_back(std::move(t));
131 }
132
133 // completion handler
134 ts->completion = [self = Ptr(this)](WS::ClientSet::TransactionSet &ts)
135 {
136 self->token_query_complete(ts);
137 };
138
139 // do the request
140 cs->new_request(ts);
141 }
142 catch (const std::exception &e)
143 {
144 done(e.what());
145 }
146 }
147
148 void stop()
149 {
150 if (cs)
151 cs->stop();
152 }
153
154 private:
155 void done(std::string error)
156 {
157 pending = false;
158 info.error = std::move(error);
159 if (completion)
160 completion(std::move(info));
161 }
162
164 {
165 try
166 {
167 // get transactions and check that they succeeded
168 WS::ClientSet::Transaction &ident_trans = *lts.transactions.at(0);
169 if (!ident_trans.request_status_success())
170 {
171 done("could not fetch AWS identity document: " + ident_trans.format_status(lts));
172 return;
173 }
174
175 WS::ClientSet::Transaction &sig_trans = *lts.transactions.at(1);
176 if (!sig_trans.request_status_success())
177 {
178 done("could not fetch AWS identity document signature: " + sig_trans.format_status(lts));
179 return;
180 }
181
182 // get identity document and signature
183 const std::string ident = ident_trans.content_in.to_string();
184 const std::string sig = "-----BEGIN PKCS7-----\n"
185 + sig_trans.content_in.to_string()
186 + "\n-----END PKCS7-----\n";
187
188 if (debug_level >= 3)
189 {
190 OPENVPN_LOG("IDENT\n"
191 << ident);
192 OPENVPN_LOG("SIG\n"
193 << sig);
194 }
195
196 // verify signature on identity document
197 {
198 std::list<OpenSSLPKI::X509> certs;
199 if (certs_dir.empty())
200 certs.emplace_back(awscert(), "AWS Cert");
201 else
202 {
203 enum_dir(certs_dir, [&certs, certs_dir = certs_dir](const std::string &file)
204 { certs.emplace_back(read_text(certs_dir + "/" + file), "AWS Cert"); });
205 }
206 OpenSSLSign::verify_pkcs7(certs, sig, ident);
207 }
208
209 // parse the identity document (JSON)
210 {
211 const std::string title = "identity-document";
212 const Json::Value root = json::parse(ident, title);
213 info.region = json::get_string(root, "region", title);
214 info.az = json::get_string(root, "availabilityZone", title);
215 info.instanceId = json::get_string(root, "instanceId", title);
216 info.privateIp = json::get_string(root, "privateIp", title);
217 }
218
219 if (!role_for_credentials.empty())
220 {
221 WS::ClientSet::Transaction &cred_trans = *lts.transactions.at(2);
222 if (cred_trans.request_status_success())
223 {
224 const std::string creds = cred_trans.content_in.to_string();
225 const Json::Value root = json::parse(creds);
226 info.creds.access_key = json::get_string(root, "AccessKeyId");
227 info.creds.secret_key = json::get_string(root, "SecretAccessKey");
228 info.creds.token = json::get_string(root, "Token");
229 done("");
230 }
231 else
232 done("could not fetch role credentials: " + cred_trans.format_status(lts));
233 }
234 else
235 done("");
236 }
237 catch (const std::exception &e)
238 {
239 done(e.what());
240 }
241 }
242
244 {
245 try
246 {
247 // get transaction and check that they succeeded
248 WS::ClientSet::Transaction &token_trans = *lts.transactions.at(0);
249 if (!token_trans.request_status_success())
250 {
251 done("could not fetch AWS session token: " + token_trans.format_status(lts));
252 return;
253 }
254 const std::string token = token_trans.content_in.to_string();
255
256 auto ts = prepare_transaction_set();
257
258 // transaction #1
259 {
260 std::unique_ptr<WS::ClientSet::Transaction> t(new WS::ClientSet::Transaction);
261 t->req.method = "GET";
262 t->req.uri = "/latest/dynamic/instance-identity/document";
263 t->ci.extra_headers.emplace_back("X-aws-ec2-metadata-token: " + token);
264 ts->transactions.push_back(std::move(t));
265 }
266
267 // transaction #2
268 {
269 std::unique_ptr<WS::ClientSet::Transaction> t(new WS::ClientSet::Transaction);
270 t->req.method = "GET";
271 t->req.uri = "/latest/dynamic/instance-identity/pkcs7";
272 t->ci.extra_headers.emplace_back("X-aws-ec2-metadata-token: " + token);
273 ts->transactions.push_back(std::move(t));
274 }
275
276 // transaction #3
277 if (!role_for_credentials.empty())
278 {
279 std::unique_ptr<WS::ClientSet::Transaction> t(new WS::ClientSet::Transaction);
280 t->req.method = "GET";
281 t->req.uri = "/latest/meta-data/iam/security-credentials/" + role_for_credentials;
282 t->ci.extra_headers.emplace_back("X-aws-ec2-metadata-token: " + token);
283 ts->transactions.push_back(std::move(t));
284 }
285
286 // completion handler
287 ts->completion = [self = Ptr(this)](WS::ClientSet::TransactionSet &ts)
288 {
289 self->local_query_complete(ts);
290 };
291
292 // do the request
293 cs->new_request(ts);
294 }
295 catch (const std::exception &e)
296 {
297 done(e.what());
298 }
299 }
300
301 // The AWS cert for PKCS#7 validation of AWS identity document
302 static std::string awscert()
303 {
304 return std::string(
305 "-----BEGIN CERTIFICATE-----\n"
306 "MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw\n"
307 "FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD\n"
308 "VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z\n"
309 "ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u\n"
310 "IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl\n"
311 "cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e\n"
312 "ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3\n"
313 "VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P\n"
314 "hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j\n"
315 "k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U\n"
316 "hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF\n"
317 "lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf\n"
318 "MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW\n"
319 "MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw\n"
320 "vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw\n"
321 "7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K\n"
322 "-----END CERTIFICATE-----\n");
323 }
324
327 const int debug_level;
329 std::string certs_dir;
330
331 std::function<void(Info info)> completion;
333 bool pending = false;
334};
335} // namespace openvpn::AWS
PCQuery(WS::ClientSet::Ptr cs_arg, const std::string &role_for_credentials_arg, const std::string &certs_dir_arg)
Definition awspc.hpp:78
OPENVPN_EXCEPTION(awspc_query_error)
WS::ClientSet::Ptr cs
Definition awspc.hpp:325
void token_query_complete(WS::ClientSet::TransactionSet &lts)
Definition awspc.hpp:243
const int debug_level
Definition awspc.hpp:327
static std::string awscert()
Definition awspc.hpp:302
void done(std::string error)
Definition awspc.hpp:155
void local_query_complete(WS::ClientSet::TransactionSet &lts)
Definition awspc.hpp:163
RCPtr< PCQuery > Ptr
Definition awspc.hpp:32
std::function< void(Info info)> completion
Definition awspc.hpp:331
PCQuery(WS::ClientSet::Ptr cs_arg, const int debug_level_arg)
Definition awspc.hpp:70
std::string certs_dir
Definition awspc.hpp:329
void start(std::function< void(Info info)> completion_arg)
Definition awspc.hpp:108
std::string role_for_credentials
Definition awspc.hpp:328
WS::ClientSet::TransactionSet::Ptr prepare_transaction_set()
Definition awspc.hpp:89
The smart pointer class.
Definition rc.hpp:119
Reference count base class for objects tracked by RCPtr. Disallows copying and assignment.
Definition rc.hpp:908
#define OPENVPN_LOG(args)
void verify_pkcs7(const std::list< OpenSSLPKI::X509 > &certs, const std::string &sig, const std::string &data)
Json::Value parse(const std::string &str, const TITLE &title)
std::string get_string(const Json::Value &root, const NAME &name, const TITLE &title)
Frame::Ptr frame_init_simple(const size_t payload)
std::string read_text(const std::string &filename, const std::uint64_t max_size=0)
Definition file.hpp:127
bool enum_dir(const std::string &dirname, F func)
Definition enumdir.hpp:33
std::string access_key
Definition awscreds.hpp:50
std::string token
Definition awscreds.hpp:52
std::string secret_key
Definition awscreds.hpp:51
std::string to_string() const
Definition awspc.hpp:58
bool instance_data_defined() const
Definition awspc.hpp:52
std::string to_string() const
Definition buflist.hpp:72
std::string format_status(const TransactionSet &ts) const