blob: 1956f0c9990adf5f9ad7ed9fca3eef6f3c0c3ba5 [file] [log] [blame]
// Copyright 2024 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/cloud_service_client.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_split.h"
#include "base/strings/stringize_macros.h"
#include "google_apis/google_api_keys.h"
#include "net/http/http_request_headers.h"
#include "remoting/base/protobuf_http_request.h"
#include "remoting/base/protobuf_http_request_config.h"
#include "remoting/base/service_urls.h"
#include "remoting/base/version.h"
#include "remoting/proto/google/internal/remoting/cloud/v1alpha/empty.pb.h"
#include "remoting/proto/google/internal/remoting/cloud/v1alpha/network_traversal_service.pb.h"
#include "remoting/proto/google/internal/remoting/cloud/v1alpha/remote_access_host.pb.h"
#include "remoting/proto/google/internal/remoting/cloud/v1alpha/remote_access_service.pb.h"
#include "remoting/proto/google/internal/remoting/cloud/v1alpha/session_authz_service.pb.h"
#include "remoting/proto/google/remoting/cloud/v1/provisioning_service.pb.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace {
constexpr net::NetworkTrafficAnnotationTag kGenerateHostTokenTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("remoting_cloud_generate_host_token",
R"(
semantics {
sender: "Chrome Remote Desktop"
description:
"Generates a host token which is used to authorize a Chrome Remote "
"Desktop connection between a user and a 'Cloud host' running on "
"GCE (this machine)."
trigger:
"User connects to a remote access host instance running on GCE "
"which has been configured as a 'Cloud' host."
user_data {
type: OTHER
}
data:
"An access token for the device robot account associated with this "
"machine."
destination: GOOGLE_OWNED_SERVICE
internal {
contacts { owners: "//remoting/OWNERS" }
}
last_reviewed: "2024-09-23"
}
policy {
cookies_allowed: NO
setting:
"This request cannot be stopped in settings, but will not be sent "
"if the Chrome Remote Desktop host is not registered as a Cloud "
"host running on GCE."
policy_exception_justification:
"Not implemented."
})");
constexpr net::NetworkTrafficAnnotationTag
kProvisionGceInstanceTrafficAnnotation =
net::DefineNetworkTrafficAnnotation(
"remoting_cloud_provision_gce_instance",
R"(
semantics {
sender: "Chrome Remote Desktop"
description:
"Registers a new Chrome Remote Desktop host for a GCE instance."
trigger:
"User runs the remoting_start_host command by typing it on the "
"terminal. Third party administrators might implement scripts to "
"run this automatically, but the Chrome Remote Desktop product "
"does not come with such scripts."
user_data {
type: EMAIL
type: OTHER
}
data:
"The email address of the account to configure CRD for and the "
"display name of the new remote access host instance which will be "
"shown in the Chrome Remote Desktop client website."
destination: GOOGLE_OWNED_SERVICE
internal {
contacts { owners: "//remoting/OWNERS" }
}
last_reviewed: "2024-03-29"
}
policy {
cookies_allowed: NO
setting:
"This request cannot be stopped in settings, but will not be sent "
"if the start-host utility is not run with the cloud-user flag."
policy_exception_justification:
"Not implemented."
})");
constexpr net::NetworkTrafficAnnotationTag kReauthorizeHostTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("remoting_cloud_reauthorize_host",
R"(
semantics {
sender: "Chrome Remote Desktop"
description:
"Reauthorizes the Chrome Remote Desktop connection between a user "
"and a 'Cloud host' running on GCE (this machine)."
trigger:
"User connects to a remote access host instance running on GCE "
"which has been configured as a 'Cloud' host."
user_data {
type: OTHER
}
data:
"An access token for the device robot account associated with this "
"machine and an opaque session token generated by the backend "
"service."
destination: GOOGLE_OWNED_SERVICE
internal {
contacts { owners: "//remoting/OWNERS" }
}
last_reviewed: "2024-09-23"
}
policy {
cookies_allowed: NO
setting:
"This request cannot be stopped in settings, but will not be sent "
"if the Chrome Remote Desktop host is not registered as a Cloud "
"host running on GCE."
policy_exception_justification:
"Not implemented."
})");
constexpr net::NetworkTrafficAnnotationTag kSendHeartbeatTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("remoting_cloud_send_heartbeat",
R"(
semantics {
sender: "Chrome Remote Desktop"
description:
"Updates the last seen time in the Chrome Remote Desktop Directory "
"service for a given remote access host instance."
trigger:
"Configuring a CRD remote access host on a GCE Instance."
user_data {
type: OTHER
}
data:
"An internal UUID to identify the remote access host instance."
destination: GOOGLE_OWNED_SERVICE
internal {
contacts { owners: "//remoting/OWNERS" }
}
last_reviewed: "2024-09-18"
}
policy {
cookies_allowed: NO
setting:
"This request cannot be stopped in settings, but will not be sent "
"if the CRD host is not configured and run as a Cloud host."
policy_exception_justification:
"Not implemented."
})");
constexpr net::NetworkTrafficAnnotationTag
kUpdateRemoteAccessHostTrafficAnnotation =
net::DefineNetworkTrafficAnnotation(
"remoting_cloud_update_remote_access_host",
R"(
semantics {
sender: "Chrome Remote Desktop"
description:
"Updates the Chrome Remote Desktop Directory service with "
"environment details and signaling information for a given remote "
"access host instance."
trigger:
"Configuring a CRD remote access host on a GCE Instance."
user_data {
type: OTHER
}
data:
"Includes an internal UUID to identify the remote access host "
"instance, the name and version of the operating system, the "
"version of the CRD package installed, and a set of signaling ids "
"which the CRD client can use to send the host messages."
destination: GOOGLE_OWNED_SERVICE
internal {
contacts { owners: "//remoting/OWNERS" }
}
last_reviewed: "2024-09-18"
}
policy {
cookies_allowed: NO
setting:
"This request cannot be stopped in settings, but will not be sent "
"if the CRD host is not configured and run as a Cloud host."
policy_exception_justification:
"Not implemented."
})");
constexpr net::NetworkTrafficAnnotationTag
kVerifySessionTokenTrafficAnnotation = net::DefineNetworkTrafficAnnotation(
"remoting_cloud_verify_session_token",
R"(
semantics {
sender: "Chrome Remote Desktop"
description:
"Verifies a session token returned by the client device and "
"decodes the shared secret from the session token, to be used to "
"authorize a Chrome Remote Desktop connection between a user and a "
"Cloud host running on GCE (this device)."
trigger:
"User connects to a remote access host instance running on GCE "
"which has been configured as a 'Cloud' host."
user_data {
type: OTHER
}
data:
"An access token for the device robot account associated with this "
"machine and an opaque session token generated by the backend "
"service."
destination: GOOGLE_OWNED_SERVICE
internal {
contacts { owners: "//remoting/OWNERS" }
}
last_reviewed: "2024-09-23"
}
policy {
cookies_allowed: NO
setting:
"This request cannot be stopped in settings, but will not be sent "
"if the Chrome Remote Desktop host is not registered as a Cloud "
"host running on GCE."
policy_exception_justification:
"Not implemented."
})");
constexpr net::NetworkTrafficAnnotationTag kGenerateIceConfigTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("remoting_cloud_generate_ice_config",
R"(
semantics {
sender: "Chrome Remote Desktop"
description:
"Request used by Chrome Remote Desktop to fetch an ICE "
"(Interactive Connectivity Establishment) configuration which "
"contains a list of STUN (Session Traversal Utilities for NAT) & "
"TURN (Traversal Using Relay NAT) servers and TURN credentials. "
"Please see https://tools.ietf.org/html/rfc5245 for more details."
trigger:
"When a user connects to a remote access host instance running on "
"GCE which has been configured as a 'Cloud' host."
user_data {
type: OTHER
}
data:
"An access token for the device robot account associated with this "
"machine."
destination: GOOGLE_OWNED_SERVICE
internal {
contacts { owners: "//remoting/OWNERS" }
}
last_reviewed: "2024-10-08"
}
policy {
cookies_allowed: NO
setting:
"This request cannot be stopped in settings, but will not be sent "
"if the Chrome Remote Desktop host is not registered as a Cloud "
"host running on GCE."
policy_exception_justification:
"Not implemented."
})");
// Remoting Cloud API using statements.
using ProvisionGceInstanceRequest =
google::remoting::cloud::v1::ProvisionGceInstanceRequest;
// Remoting Cloud Private API using statements.
using Empty = google::internal::remoting::cloud::v1alpha::Empty;
using GenerateHostTokenRequest =
google::internal::remoting::cloud::v1alpha::GenerateHostTokenRequest;
using GenerateIceConfigRequest =
google::internal::remoting::cloud::v1alpha::GenerateIceConfigRequest;
using ReauthorizeHostRequest =
google::internal::remoting::cloud::v1alpha::ReauthorizeHostRequest;
using RemoteAccessHost =
google::internal::remoting::cloud::v1alpha::RemoteAccessHost;
using SendHeartbeatRequest =
google::internal::remoting::cloud::v1alpha::SendHeartbeatRequest;
using VerifySessionTokenRequest =
google::internal::remoting::cloud::v1alpha::VerifySessionTokenRequest;
constexpr char kFtlResourceSeparator[] = "/chromoting_ftl_";
} // namespace
namespace remoting {
CloudServiceClient::CloudServiceClient(
const std::string& api_key,
OAuthTokenGetter* oauth_token_getter,
const std::string& base_service_url,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: api_key_(api_key),
http_client_(base_service_url, oauth_token_getter, url_loader_factory) {}
CloudServiceClient::~CloudServiceClient() = default;
void CloudServiceClient::ProvisionGceInstance(
const std::string& owner_email,
const std::string& display_name,
const std::string& public_key,
const std::optional<std::string>& existing_directory_id,
const std::optional<std::string>& instance_identity_token,
ProvisionGceInstanceCallback callback) {
constexpr char path[] = "/v1/provisioning:provisionGceInstance";
auto request = std::make_unique<ProvisionGceInstanceRequest>();
request->set_owner_email(owner_email);
request->set_display_name(display_name);
request->set_public_key(public_key);
request->set_version(STRINGIZE(VERSION));
if (existing_directory_id.has_value() && !existing_directory_id->empty()) {
request->set_existing_directory_id(*existing_directory_id);
}
if (instance_identity_token.has_value() &&
!instance_identity_token->empty()) {
request->set_instance_identity_token(*instance_identity_token);
}
ExecuteRequest(kProvisionGceInstanceTrafficAnnotation, path, api_key_,
net::HttpRequestHeaders::kPostMethod, std::move(request),
std::move(callback));
}
void CloudServiceClient::SendHeartbeat(const std::string& directory_id,
std::string_view instance_identity_token,
SendHeartbeatCallback callback) {
constexpr char path[] = "/v1alpha/access:sendHeartbeat";
auto request = std::make_unique<SendHeartbeatRequest>();
request->set_directory_id(directory_id);
request->set_instance_identity_token(instance_identity_token);
ExecuteRequest(kSendHeartbeatTrafficAnnotation, path, /*api_key=*/"",
net::HttpRequestHeaders::kPostMethod, std::move(request),
std::move(callback));
}
void CloudServiceClient::UpdateRemoteAccessHost(
const std::string& directory_id,
std::optional<std::string> host_version,
std::optional<std::string> signaling_id,
std::optional<std::string> offline_reason,
std::optional<std::string> os_name,
std::optional<std::string> os_version,
std::string_view instance_identity_token,
UpdateRemoteAccessHostCallback callback) {
constexpr char path[] = "/v1alpha/access:updateRemoteAccessHost";
auto host = std::make_unique<RemoteAccessHost>();
host->set_directory_id(directory_id);
if (host_version.has_value()) {
host->set_version(*host_version);
}
if (signaling_id.has_value()) {
auto parts = base::SplitStringUsingSubstr(
*signaling_id, kFtlResourceSeparator, base::TRIM_WHITESPACE,
base::SPLIT_WANT_ALL);
if (parts.size() == 2) {
host->mutable_tachyon_account_info()->set_account_id(std::move(parts[0]));
host->mutable_tachyon_account_info()->set_registration_id(
std::move(parts[1]));
} else {
LOG(WARNING) << "Invalid signaling_id provided: " << *signaling_id;
}
}
if (offline_reason.has_value()) {
host->set_offline_reason(*offline_reason);
}
if (os_name.has_value() && os_version.has_value()) {
host->mutable_operating_system_info()->set_name(*os_name);
host->mutable_operating_system_info()->set_version(*os_version);
}
host->set_instance_identity_token(instance_identity_token);
ExecuteRequest(kUpdateRemoteAccessHostTrafficAnnotation, path, /*api_key=*/"",
net::HttpRequestHeaders::kPatchMethod, std::move(host),
std::move(callback));
}
void CloudServiceClient::GenerateHostToken(
std::string_view instance_identity_token,
GenerateHostTokenCallback callback) {
constexpr char path[] = "/v1alpha/sessionAuthz:generateHostToken";
auto request = std::make_unique<GenerateHostTokenRequest>();
request->set_instance_identity_token(instance_identity_token);
ExecuteRequest(kGenerateHostTokenTrafficAnnotation, path, /*api_key=*/"",
net::HttpRequestHeaders::kPostMethod, std::move(request),
std::move(callback));
}
void CloudServiceClient::GenerateIceConfig(
std::string_view instance_identity_token,
GenerateIceConfigCallback callback) {
constexpr char path[] = "/v1alpha/networkTraversal:generateIceConfig";
auto request = std::make_unique<GenerateIceConfigRequest>();
request->set_instance_identity_token(instance_identity_token);
ExecuteRequest(kGenerateIceConfigTrafficAnnotation, path, /*api_key=*/"",
net::HttpRequestHeaders::kPostMethod, std::move(request),
std::move(callback));
}
void CloudServiceClient::VerifySessionToken(
const std::string& session_token,
std::string_view instance_identity_token,
VerifySessionTokenCallback callback) {
constexpr char path[] = "/v1alpha/sessionAuthz:verifySessionToken";
auto request = std::make_unique<VerifySessionTokenRequest>();
request->set_session_token(session_token);
request->set_instance_identity_token(instance_identity_token);
ExecuteRequest(kVerifySessionTokenTrafficAnnotation, path, /*api_key=*/"",
net::HttpRequestHeaders::kPostMethod, std::move(request),
std::move(callback));
}
void CloudServiceClient::ReauthorizeHost(
const std::string& session_reauth_token,
const std::string& session_id,
std::string_view instance_identity_token,
scoped_refptr<const ProtobufHttpRequestConfig::RetryPolicy> retry_policy,
ReauthorizeHostCallback callback) {
constexpr char path[] = "/v1alpha/sessionAuthz:reauthorizeHost";
auto request = std::make_unique<ReauthorizeHostRequest>();
request->set_session_reauth_token(session_reauth_token);
request->set_session_id(session_id);
request->set_instance_identity_token(instance_identity_token);
ExecuteRequest(kReauthorizeHostTrafficAnnotation, path, /*api_key=*/"",
net::HttpRequestHeaders::kPostMethod, std::move(request),
std::move(callback), std::move(retry_policy));
}
void CloudServiceClient::CancelPendingRequests() {
http_client_.CancelPendingRequests();
}
template <typename CallbackType>
void CloudServiceClient::ExecuteRequest(
const net::NetworkTrafficAnnotationTag& traffic_annotation,
const std::string& path,
const std::string& api_key,
const std::string& method,
std::unique_ptr<google::protobuf::MessageLite> request_message,
CallbackType callback,
scoped_refptr<const ProtobufHttpRequestConfig::RetryPolicy> retry_policy) {
auto request_config =
std::make_unique<ProtobufHttpRequestConfig>(traffic_annotation);
request_config->path = path;
if (api_key.empty()) {
request_config->authenticated = true;
} else {
request_config->api_key = api_key;
request_config->authenticated = false;
}
request_config->method = method;
request_config->retry_policy = std::move(retry_policy);
request_config->request_message = std::move(request_message);
auto request =
std::make_unique<ProtobufHttpRequest>(std::move(request_config));
request->SetResponseCallback(std::move(callback));
http_client_.ExecuteRequest(std::move(request));
}
// static
std::unique_ptr<CloudServiceClient>
CloudServiceClient::CreateForChromotingRobotAccount(
OAuthTokenGetter* oauth_token_getter,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
return base::WrapUnique(new CloudServiceClient(
/*api_key=*/std::string(), oauth_token_getter,
ServiceUrls::GetInstance()->remoting_cloud_private_endpoint(),
url_loader_factory));
}
// static
std::unique_ptr<CloudServiceClient> CloudServiceClient::CreateForGcpProject(
const std::string& api_key,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
return base::WrapUnique(new CloudServiceClient(
api_key,
/*oauth_token_getter=*/nullptr,
ServiceUrls::GetInstance()->remoting_cloud_public_endpoint(),
url_loader_factory));
}
// static
std::unique_ptr<CloudServiceClient>
CloudServiceClient::CreateForGceDefaultServiceAccount(
OAuthTokenGetter* oauth_token_getter,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
return base::WrapUnique(new CloudServiceClient(
/*api_key=*/std::string(), oauth_token_getter,
ServiceUrls::GetInstance()->remoting_cloud_public_endpoint(),
url_loader_factory));
}
} // namespace remoting