5 #include "util/gce/gce.h" 7 #include <boost/asio/connect.hpp> 8 #include <boost/asio/ip/tcp.hpp> 10 #include <boost/beast/core/buffers_to_string.hpp> 11 #include <boost/beast/core/flat_buffer.hpp> 12 #include <boost/beast/http/dynamic_body.hpp> 13 #include <boost/beast/http/empty_body.hpp> 14 #include <boost/beast/http/string_body.hpp> 15 #include <boost/beast/http/write.hpp> 17 #include <rapidjson/document.h> 18 #include <rapidjson/error/en.h> 20 #include "absl/strings/str_cat.h" 21 #include "absl/strings/str_split.h" 22 #include "absl/strings/strip.h" 23 #include "base/logging.h" 24 #include "file/file_util.h" 25 #include "file/filesource.h" 26 #include "util/asio/fiber_socket.h" 27 #include "util/http/https_client.h" 31 using namespace boost;
33 namespace h2 = beast::http;
34 namespace rj = rapidjson;
35 using tcp = asio::ip::tcp;
37 static const char kMetaDataHost[] =
"metadata.google.internal";
39 static Status ToStatus(
const system::error_code& ec) {
40 return Status(StatusCode::IO_ERROR, absl::StrCat(ec.value(),
": ", ec.message()));
43 #define RETURN_ON_ERROR \ 47 static StatusObject<string> GetHttp(
const h2::request<h2::empty_body>& req, tcp::socket* sock) {
48 system::error_code ec;
49 boost::beast::flat_buffer buffer;
50 h2::response<h2::string_body> resp;
52 h2::write(*sock, req, ec);
55 h2::read(*sock, buffer, resp, ec);
58 return std::move(resp).body();
61 const char* GCE::GoogleCert() {
63 -----BEGIN CERTIFICATE----- 64 MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G 65 A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp 66 Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 67 MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG 68 A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI 69 hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL 70 v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 71 eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq 72 tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd 73 C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa 74 zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB 75 mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH 76 V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n 77 bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG 78 3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs 79 J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO 80 291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS 81 ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd 82 AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 83 TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== 84 -----END CERTIFICATE----- 88 const char* GCE::kApiDomain =
"www.googleapis.com";
90 asio::ssl::context GCE::CheckedSslContext() {
91 auto res = http::CreateClientSslContext(GCE::GoogleCert());
92 asio::ssl::context* ssl_cntx = absl::get_if<asio::ssl::context>(&res);
93 CHECK(ssl_cntx) << absl::get<system::error_code>(res);
94 return std::move(*ssl_cntx);
97 Status GCE::ParseDefaultConfig() {
98 string config = file_util::ExpandPath(
"~/.config/gcloud/configurations/config_default");
99 if (!file::Exists(config)) {
100 return Status(
"Could not find config_default");
102 file::LineReader reader(config);
106 while (reader.Next(&line, &scratch)) {
107 vector<StringPiece> vals = absl::StrSplit(line,
"=");
108 if (vals.size() != 2)
110 for (
auto& v : vals) {
111 v = absl::StripAsciiWhitespace(v);
113 if (vals[0] ==
"account") {
114 account_id_ = string(vals[1]);
115 }
else if (vals[0] ==
"project") {
116 project_id_ = string(vals[1]);
120 if (account_id_.empty() || project_id_.empty()) {
121 return Status(
"Could not find required fields in config_default");
127 system::error_code ec;
128 asio::ssl::context ssl_context = CheckedSslContext();
129 ssl_ctx_.reset(
new SslContext{std::move(ssl_context)});
131 string root_path = file_util::ExpandPath(
"~/.config/gcloud/");
132 string gce_file = absl::StrCat(root_path,
"gce");
134 asio::io_context io_context;
135 tcp::socket socket(io_context);
138 tcp::resolver resolver{io_context};
139 auto const results = resolver.resolve(kMetaDataHost,
"80", ec);
141 asio::connect(socket, results.begin(), results.end(), ec);
145 if (file::Exists(gce_file)) {
147 CHECK(file_util::ReadFileToString(gce_file, &tmp_str));
148 is_prod_env_ = (tmp_str ==
"True");
155 if (!socket.is_open()) {
160 h2::request<h2::empty_body> req{
161 h2::verb::get,
"/computeMetadata/v1/instance/service-accounts/default/email", 11};
162 req.set(
"Metadata-Flavor",
"Google");
163 req.set(h2::field::host, kMetaDataHost);
165 VLOG(1) <<
"Req: " << req;
166 auto str_res = GetHttp(req, &socket);
168 return str_res.status;
169 account_id_ = std::move(str_res.obj);
171 req.target(
"/computeMetadata/v1/project/project-id");
172 str_res = GetHttp(req, &socket);
174 return str_res.status;
175 project_id_ = std::move(str_res.obj);
177 return ReadDevCreds(root_path);
182 util::Status GCE::ReadDevCreds(
const std::string& root_path) {
183 RETURN_IF_ERROR(ParseDefaultConfig());
184 LOG(INFO) <<
"Found account " << account_id_ <<
"/" << project_id_;
186 string adc_file = absl::StrCat(root_path,
"legacy_credentials/", account_id_,
"/adc.json");
189 if (!file_util::ReadFileToString(adc_file, &adc)) {
190 return Status(
"Could not find " + adc_file);
193 rj::Document adc_doc;
194 constexpr
unsigned kFlags = rj::kParseTrailingCommasFlag | rj::kParseCommentsFlag;
195 adc_doc.ParseInsitu<kFlags>(&adc.front());
197 if (adc_doc.HasParseError()) {
198 return Status(rj::GetParseError_En(adc_doc.GetParseError()));
200 for (
auto it = adc_doc.MemberBegin(); it != adc_doc.MemberEnd(); ++it) {
201 if (it->name ==
"client_id") {
202 client_id_ = it->value.GetString();
203 }
else if (it->name ==
"client_secret") {
204 client_secret_ = it->value.GetString();
205 }
else if (it->name ==
"refresh_token") {
206 refresh_token_ = it->value.GetString();
209 if (client_id_.empty() || client_secret_.empty() || refresh_token_.empty()) {
210 return Status(
"Did not find secret tokens");
216 std::lock_guard<fibers::mutex> lk(mu_);
217 return access_token_;
221 h2::response<h2::string_body> resp;
225 h2::request<h2::empty_body> req{
226 h2::verb::get,
"/computeMetadata/v1/instance/service-accounts/default/token", 11};
227 req.set(
"Metadata-Flavor",
"Google");
228 req.set(h2::field::host, kMetaDataHost);
230 FiberSyncSocket socket{kMetaDataHost,
"80", context};
231 ec = socket.ClientWaitToConnect(2000);
234 h2::write(socket, req, ec);
237 beast::flat_buffer buffer;
238 h2::read(socket, buffer, resp, ec);
241 constexpr
char kDomain[] =
"oauth2.googleapis.com";
243 http::HttpsClient https_client(kDomain, context, ssl_ctx_.get());
245 ec = https_client.Connect(2000);
248 h2::request<h2::string_body> req{h2::verb::post,
"/token", 11};
249 req.set(h2::field::host, kDomain);
250 req.set(h2::field::content_type,
"application/x-www-form-urlencoded");
252 string& body = req.body();
253 body = absl::StrCat(
"grant_type=refresh_token&client_secret=", client_secret(),
254 "&refresh_token=", refresh_token());
255 absl::StrAppend(&body,
"&client_id=", client_id());
256 req.prepare_payload();
257 VLOG(1) <<
"Req: " << req;
259 ec = https_client.Send(req, &resp);
261 return Status(absl::StrCat(
"Error sending access token request: ", ec.message()));
265 if (resp.result() != h2::status::ok) {
266 return Status(StatusCode::IO_ERROR,
267 absl::StrCat(
"Http error ",
string(resp.reason()),
"Body: ", resp.body()));
269 VLOG(1) <<
"Resp: " << resp;
271 return ParseTokenResponse(std::move(resp.body()));
276 constexpr
unsigned kFlags = rj::kParseTrailingCommasFlag | rj::kParseCommentsFlag;
277 doc.ParseInsitu<kFlags>(&response.front());
279 if (doc.HasParseError()) {
280 return Status(rj::GetParseError_En(doc.GetParseError()));
283 string access_token, token_type;
284 for (
auto it = doc.MemberBegin(); it != doc.MemberEnd(); ++it) {
285 if (it->name ==
"access_token") {
286 access_token = it->value.GetString();
287 }
else if (it->name ==
"token_type") {
288 token_type = it->value.GetString();
291 if (token_type !=
"Bearer" || access_token.empty()) {
292 return Status(absl::StrCat(
"Bad json response: ", doc.GetString()));
295 std::lock_guard<fibers::mutex> lk(mu_);
296 access_token_ = access_token;
301 void GCE::Test_InjectAcessToken(std::string access_token) {
302 std::lock_guard<fibers::mutex> lk(mu_);
303 access_token_.swap(access_token);
std::string access_token() const