blob: 9bc3e8d0169edde4883c2467817cb9fc4b8c488f [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/base/compute_engine_service_client.h"
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include "base/strings/stringprintf.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "remoting/base/http_status.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace remoting {
namespace {
// Compute Engine VM Instances always have an HTTP metadata server endpoint.
// Shielded Instances also provide an HTTPS endpoint. For compatibility, we
// use the HTTP endpoint for fail-fast decisions or to provide an identity token
// but rely on our backend services to validate the token and metadata.
// TODO: joedow - Add support for the HTTPS endpoint:
// https://cloud.google.com/compute/docs/metadata/querying-metadata#query-https-mds
constexpr char kHttpMetadataBaseUrl[] =
"http://metadata.google.internal/computeMetadata/v1/instance/"
"service-accounts/default";
constexpr size_t kMaxResponseSize = 4096;
constexpr net::NetworkTrafficAnnotationTag kInstanceIdentityTrafficAnnotation =
net::DefineNetworkTrafficAnnotation(
"remoting_compute_engine_instance_identity_token",
R"(
semantics {
sender: "Chrome Remote Desktop"
description:
"Retrieves a Compute Engine VM Instance Identity token for use by "
"Chrome Remote Desktop."
trigger:
"The request is made when a Compute Engine VM Instance is "
"configured for remote acces via Chrome Remote Desktop. It is also "
"called when the host instance makes a backend service request as "
"the identity token is used to verify the origin of the request."
data: "Arbitrary payload data used to prevent replay attacks."
destination: GOOGLE_OWNED_SERVICE
internal {
contacts {
email: "chromoting-team@google.com"
}
}
user_data {
type: ARBITRARY_DATA
}
last_reviewed: "2025-02-08"
}
policy {
cookies_allowed: NO
setting:
"This request will not be sent if Chrome Remote Desktop is not "
"used within a Compute Engine VM Instance."
policy_exception_justification:
"Not implemented."
})");
constexpr net::NetworkTrafficAnnotationTag kAccessTokenTrafficAnnotation =
net::DefineNetworkTrafficAnnotation(
"remoting_compute_engine_instance_access_token",
R"(
semantics {
sender: "Chrome Remote Desktop"
description:
"Retrieves an OAuth access token for the default service account "
"associated with the Compute Engine VM Instance."
trigger:
"The request is made when Chrome Remote Desktop is being run in a "
"Compute Engine Instance and needs to send a request to our API "
"using the default service account for the Compute Engine Instance."
data: "None"
destination: GOOGLE_OWNED_SERVICE
internal {
contacts {
email: "chromoting-team@google.com"
}
}
user_data {
type: NONE
}
last_reviewed: "2025-02-08"
}
policy {
cookies_allowed: NO
setting:
"This request will not be sent if Chrome Remote Desktop is not "
"used within a Compute Engine VM Instance."
policy_exception_justification:
"Not implemented."
})");
constexpr net::NetworkTrafficAnnotationTag kAccessTokenScopesTrafficAnnotation =
net::DefineNetworkTrafficAnnotation(
"remoting_compute_engine_instance_access_token_scopes",
R"(
semantics {
sender: "Chrome Remote Desktop"
description:
"Retrieves the set of OAuth scopes included in access tokens which "
"are generated for the default service account associated with the "
"Compute Engine VM Instance."
trigger:
"The request is made when Chrome Remote Desktop is being run in a "
"Compute Engine Instance and needs to determine if the default "
"service account has been configured properly to access our API."
data: "None"
destination: GOOGLE_OWNED_SERVICE
internal {
contacts {
email: "chromoting-team@google.com"
}
}
user_data {
type: NONE
}
last_reviewed: "2025-02-08"
}
policy {
cookies_allowed: NO
setting:
"This request will not be sent if Chrome Remote Desktop is not "
"used within a Compute Engine VM Instance."
policy_exception_justification:
"Not implemented."
})");
} // namespace
ComputeEngineServiceClient::ComputeEngineServiceClient(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: url_loader_factory_(url_loader_factory) {}
ComputeEngineServiceClient::~ComputeEngineServiceClient() = default;
void ComputeEngineServiceClient::GetInstanceIdentityToken(
std::string_view audience,
ResponseCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Use 'format=full' to include project and instance details in the token.
ExecuteRequest(base::StringPrintf("%s/identity?audience=%s&format=full",
kHttpMetadataBaseUrl, audience),
kInstanceIdentityTrafficAnnotation, std::move(callback));
}
void ComputeEngineServiceClient::GetServiceAccountAccessToken(
ResponseCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
ExecuteRequest(base::StringPrintf("%s/token", kHttpMetadataBaseUrl),
kAccessTokenTrafficAnnotation, std::move(callback));
}
void ComputeEngineServiceClient::GetServiceAccountScopes(
ResponseCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
ExecuteRequest(base::StringPrintf("%s/scopes", kHttpMetadataBaseUrl),
kAccessTokenScopesTrafficAnnotation, std::move(callback));
}
void ComputeEngineServiceClient::CancelPendingRequests() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
weak_ptr_factory_.InvalidateWeakPtrs();
url_loader_.reset();
}
void ComputeEngineServiceClient::ExecuteRequest(
std::string_view url,
const net::NetworkTrafficAnnotationTag& network_annotation,
ResponseCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// TODO: joedow - Update to handle concurrent requests when needed.
CHECK(!url_loader_);
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = GURL(url);
resource_request->load_flags =
net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->method = net::HttpRequestHeaders::kGetMethod;
// All Compute Engine Metadata requests must set this header.
resource_request->headers.SetHeader("Metadata-Flavor", "Google");
url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
network_annotation);
url_loader_->SetTimeoutDuration(base::Seconds(60));
url_loader_->SetAllowHttpErrorResults(false);
url_loader_->SetRetryOptions(
3, network::SimpleURLLoader::RETRY_ON_5XX |
network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
url_loader_->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&ComputeEngineServiceClient::OnRequestComplete,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
kMaxResponseSize);
}
void ComputeEngineServiceClient::OnRequestComplete(
ResponseCallback callback,
std::optional<std::string> response_body) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
net::Error net_error = static_cast<net::Error>(url_loader_->NetError());
HttpStatus http_status = HttpStatus::OK();
if (net_error != net::Error::OK &&
net_error != net::Error::ERR_HTTP_RESPONSE_CODE_FAILURE) {
http_status = HttpStatus(net_error);
} else if (!url_loader_->ResponseInfo() ||
!url_loader_->ResponseInfo()->headers ||
url_loader_->ResponseInfo()->headers->response_code() <= 0) {
http_status =
HttpStatus(HttpStatus::Code::INTERNAL,
"Failed to get HTTP status from the response header.");
} else {
http_status =
HttpStatus(static_cast<net::HttpStatusCode>(
url_loader_->ResponseInfo()->headers->response_code()),
response_body.value_or(std::string()));
}
if (!http_status.ok()) {
LOG(ERROR) << "Compute Engine API request failed. Code: "
<< static_cast<int32_t>(http_status.error_code())
<< ", Message: " << http_status.error_message();
}
// Reset |url_loader_| since we've extracted the info we need from it.
// This will allow the caller to reuse this instance to make another request.
url_loader_.reset();
std::move(callback).Run(http_status);
}
} // namespace remoting