blob: 9d57214771ee3f0de2072686f316b653d30b7f0d [file] [log] [blame]
Andreea Costinas942284d2020-01-28 16:28:40 +01001// Copyright 2020 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4#include "system-proxy/system_proxy_adaptor.h"
5
6#include <string>
7#include <utility>
8#include <vector>
9
10#include <base/location.h>
Andreea Costinase9c73592020-07-17 15:27:54 +020011#include <base/strings/stringprintf.h>
Andreea Costinas91f75352020-07-08 14:47:47 +020012#include <base/time/time.h>
Andreea Costinas942284d2020-01-28 16:28:40 +010013#include <brillo/dbus/dbus_object.h>
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010014#include <brillo/message_loops/message_loop.h>
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020015#include <chromeos/dbus/service_constants.h>
Jason Jeremy Imanadffbcb2020-08-31 13:21:36 +090016#include <chromeos/patchpanel/dbus/client.h>
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020017#include <dbus/object_proxy.h>
Andreea Costinas942284d2020-01-28 16:28:40 +010018
Andreea Costinas922fbaf2020-05-28 11:55:22 +020019#include "system-proxy/kerberos_client.h"
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010020#include "system-proxy/sandboxed_worker.h"
Andreea Costinas942284d2020-01-28 16:28:40 +010021
22namespace system_proxy {
23namespace {
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010024
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020025constexpr int kProxyPort = 3128;
Andreea Costinas77b180e2020-05-12 15:17:32 +020026constexpr char kFailedToStartWorkerError[] = "Failed to start worker process";
Andreea Costinas91f75352020-07-08 14:47:47 +020027// Time delay for calling patchpanel::ConnectNamespace(). Patchpanel needs to
28// enter the network namespace of the worker process to configure it and fails
29// if it's soon after the process starts. See https://crbug.com/1095170 for
30// details.
31constexpr base::TimeDelta kConnectNamespaceDelay =
32 base::TimeDelta::FromSeconds(1);
33constexpr int kNetworkNamespaceReconnectAttempts = 3;
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020034
Andreea Costinas942284d2020-01-28 16:28:40 +010035// Serializes |proto| to a vector of bytes.
36std::vector<uint8_t> SerializeProto(
37 const google::protobuf::MessageLite& proto) {
38 std::vector<uint8_t> proto_blob(proto.ByteSizeLong());
Andreea Costinasc991e232020-06-08 20:30:58 +020039 bool result = proto.SerializeToArray(proto_blob.data(), proto_blob.size());
40 DCHECK(result);
Andreea Costinas942284d2020-01-28 16:28:40 +010041 return proto_blob;
42}
43
44// Parses a proto from an array of bytes |proto_blob|. Returns
45// ERROR_PARSE_REQUEST_FAILED on error.
46std::string DeserializeProto(const base::Location& from_here,
47 google::protobuf::MessageLite* proto,
48 const std::vector<uint8_t>& proto_blob) {
49 if (!proto->ParseFromArray(proto_blob.data(), proto_blob.size())) {
50 const std::string error_message = "Failed to parse proto message.";
51 LOG(ERROR) << from_here.ToString() << error_message;
52 return error_message;
53 }
54 return "";
55}
56} // namespace
57
58SystemProxyAdaptor::SystemProxyAdaptor(
59 std::unique_ptr<brillo::dbus_utils::DBusObject> dbus_object)
60 : org::chromium::SystemProxyAdaptor(this),
Andreea Costinas91f75352020-07-08 14:47:47 +020061 netns_reconnect_attempts_available_(kNetworkNamespaceReconnectAttempts),
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010062 dbus_object_(std::move(dbus_object)),
Andreea Costinas922fbaf2020-05-28 11:55:22 +020063 weak_ptr_factory_(this) {
64 kerberos_client_ = std::make_unique<KerberosClient>(dbus_object_->GetBus());
65}
Andreea Costinas942284d2020-01-28 16:28:40 +010066
67SystemProxyAdaptor::~SystemProxyAdaptor() = default;
68
69void SystemProxyAdaptor::RegisterAsync(
70 const brillo::dbus_utils::AsyncEventSequencer::CompletionAction&
71 completion_callback) {
72 RegisterWithDBusObject(dbus_object_.get());
73 dbus_object_->RegisterAsync(completion_callback);
74}
75
Andreea Costinas77b180e2020-05-12 15:17:32 +020076std::vector<uint8_t> SystemProxyAdaptor::SetAuthenticationDetails(
77 const std::vector<uint8_t>& request_blob) {
78 LOG(INFO) << "Received set authentication details request.";
79
80 SetAuthenticationDetailsRequest request;
Andreea Costinas350e4aa2020-07-20 20:29:46 +020081 std::string error_message =
Andreea Costinas77b180e2020-05-12 15:17:32 +020082 DeserializeProto(FROM_HERE, &request, request_blob);
83
84 SetAuthenticationDetailsResponse response;
85 if (!error_message.empty()) {
86 response.set_error_message(error_message);
87 return SerializeProto(response);
88 }
89
Andreea Costinas350e4aa2020-07-20 20:29:46 +020090 if (IncludesSystemTraffic(request.traffic_type())) {
91 SetAuthenticationDetails(request, /*user_traffic=*/false, &error_message);
92 }
93 if (IncludesUserTraffic(request.traffic_type())) {
94 SetAuthenticationDetails(request, /*user_traffic=*/true, &error_message);
95 }
96 if (!error_message.empty()) {
97 response.set_error_message(error_message);
98 }
99 return SerializeProto(response);
100}
101
102void SystemProxyAdaptor::SetAuthenticationDetails(
103 SetAuthenticationDetailsRequest auth_details,
104 bool user_traffic,
105 std::string* error_message) {
106 SandboxedWorker* worker = CreateWorkerIfNeeded(user_traffic);
107 if (!worker) {
108 error_message->append(kFailedToStartWorkerError);
109 return;
Andreea Costinas77b180e2020-05-12 15:17:32 +0200110 }
111
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200112 if (auth_details.has_credentials() || auth_details.has_protection_space()) {
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200113 worker::Credentials credentials;
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200114 if (auth_details.has_protection_space()) {
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200115 worker::ProtectionSpace protection_space;
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200116 protection_space.set_origin(auth_details.protection_space().origin());
117 protection_space.set_scheme(auth_details.protection_space().scheme());
118 protection_space.set_realm(auth_details.protection_space().realm());
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200119 *credentials.mutable_protection_space() = protection_space;
120 }
Andreea Costinas77b180e2020-05-12 15:17:32 +0200121
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200122 if (auth_details.has_credentials()) {
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200123 system_proxy::Credentials dbus_cred = auth_details.credentials();
124 if (dbus_cred.has_username() && dbus_cred.has_password()) {
125 credentials.set_username(dbus_cred.username());
126 credentials.set_password(dbus_cred.password());
127 credentials.mutable_policy_credentials_auth_schemes()->Swap(
128 dbus_cred.mutable_policy_credentials_auth_schemes());
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200129 }
130 }
131
132 brillo::MessageLoop::current()->PostTask(
133 FROM_HERE,
134 base::Bind(&SystemProxyAdaptor::SetCredentialsTask,
135 weak_ptr_factory_.GetWeakPtr(), worker, credentials));
136 }
137 if (auth_details.has_kerberos_enabled()) {
138 std::string principal_name = auth_details.has_active_principal_name()
139 ? auth_details.active_principal_name()
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200140 : std::string();
141
142 brillo::MessageLoop::current()->PostTask(
143 FROM_HERE, base::Bind(&SystemProxyAdaptor::SetKerberosEnabledTask,
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200144 weak_ptr_factory_.GetWeakPtr(), worker,
145 auth_details.kerberos_enabled(), principal_name));
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200146 }
Andreea Costinas77b180e2020-05-12 15:17:32 +0200147}
148
Andreea Costinasfc3dc7d2020-07-20 18:54:38 +0200149// TODO(acostinas, crbug.com/1109144): Deprecated in favor of |ShutDownProcess|.
Andreea Costinas942284d2020-01-28 16:28:40 +0100150std::vector<uint8_t> SystemProxyAdaptor::ShutDown() {
151 LOG(INFO) << "Received shutdown request.";
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100152
153 std::string error_message;
Andreea Costinase9c73592020-07-17 15:27:54 +0200154 if (!ResetWorker(/* user_traffic=*/false)) {
155 error_message =
156 "Failure to terminate worker process for system services traffic.";
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100157 }
158
Andreea Costinase9c73592020-07-17 15:27:54 +0200159 if (!ResetWorker(/* user_traffic=*/true)) {
160 error_message += "Failure to terminate worker process for arc traffic.";
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100161 }
162
Andreea Costinas942284d2020-01-28 16:28:40 +0100163 ShutDownResponse response;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100164 if (!error_message.empty())
165 response.set_error_message(error_message);
166
167 brillo::MessageLoop::current()->PostTask(
168 FROM_HERE, base::Bind(&SystemProxyAdaptor::ShutDownTask,
169 weak_ptr_factory_.GetWeakPtr()));
170
Andreea Costinas942284d2020-01-28 16:28:40 +0100171 return SerializeProto(response);
172}
173
Andreea Costinase9c73592020-07-17 15:27:54 +0200174std::vector<uint8_t> SystemProxyAdaptor::ClearUserCredentials(
175 const std::vector<uint8_t>& request_blob) {
176 LOG(INFO) << "Received request to clear user credentials.";
177 std::string error_message;
178 ClearUserCredentials(/*user_traffic=*/false, &error_message);
179 ClearUserCredentials(/*user_traffic=*/true, &error_message);
180
181 ClearUserCredentialsResponse response;
182 if (!error_message.empty())
183 response.set_error_message(error_message);
184 return SerializeProto(response);
185}
186
187void SystemProxyAdaptor::ClearUserCredentials(bool user_traffic,
188 std::string* error_message) {
189 SandboxedWorker* worker = GetWorker(user_traffic);
190 if (!worker) {
191 return;
192 }
193 if (!worker->ClearUserCredentials()) {
194 error_message->append(
195 base::StringPrintf("Failure to clear user credentials for worker with "
196 "pid %s. Restarting worker.",
197 std::to_string(worker->pid()).c_str()));
198 ResetWorker(user_traffic);
199 CreateWorkerIfNeeded(user_traffic);
200 }
201}
202
Andreea Costinasfc3dc7d2020-07-20 18:54:38 +0200203std::vector<uint8_t> SystemProxyAdaptor::ShutDownProcess(
204 const std::vector<uint8_t>& request_blob) {
205 LOG(INFO) << "Received shutdown request.";
206 ShutDownRequest request;
207 std::string error_message =
208 DeserializeProto(FROM_HERE, &request, request_blob);
209
210 if (IncludesSystemTraffic(request.traffic_type()) &&
211 !ResetWorker(/* user_traffic=*/false)) {
212 error_message =
213 "Failure to terminate worker process for system services traffic.";
214 }
215
216 if (IncludesUserTraffic(request.traffic_type()) &&
217 !ResetWorker(/* user_traffic=*/true)) {
218 error_message += "Failure to terminate worker process for arc traffic.";
219 }
220
221 ShutDownResponse response;
222 if (!error_message.empty())
223 response.set_error_message(error_message);
224
225 if (request.traffic_type() == TrafficOrigin::ALL) {
226 brillo::MessageLoop::current()->PostTask(
227 FROM_HERE, base::Bind(&SystemProxyAdaptor::ShutDownTask,
228 weak_ptr_factory_.GetWeakPtr()));
229 }
230 return SerializeProto(response);
231}
232
Andreea Costinas5862b102020-03-19 14:45:36 +0100233void SystemProxyAdaptor::GetChromeProxyServersAsync(
234 const std::string& target_url,
235 const brillo::http::GetChromeProxyServersCallback& callback) {
Andreea Costinasc9defae2020-04-22 10:28:35 +0200236 brillo::http::GetChromeProxyServersAsync(dbus_object_->GetBus(), target_url,
237 move(callback));
Andreea Costinas5862b102020-03-19 14:45:36 +0100238}
239
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200240bool SystemProxyAdaptor::IsLocalProxy(const std::string& proxy) {
241 if (system_services_worker_ &&
242 proxy.find(system_services_worker_->local_proxy_host_and_port()) !=
243 std::string::npos) {
244 return true;
245 }
246 return arc_worker_ && proxy.find(arc_worker_->local_proxy_host_and_port()) !=
247 std::string::npos;
248}
249
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100250std::unique_ptr<SandboxedWorker> SystemProxyAdaptor::CreateWorker() {
Andreea Costinas5862b102020-03-19 14:45:36 +0100251 return std::make_unique<SandboxedWorker>(weak_ptr_factory_.GetWeakPtr());
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100252}
253
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200254SandboxedWorker* SystemProxyAdaptor::CreateWorkerIfNeeded(bool user_traffic) {
255 SandboxedWorker* worker = GetWorker(user_traffic);
256 if (worker) {
257 // A worker for traffic indicated by |user_traffic| already exists.
258 return worker;
Andreea Costinas77b180e2020-05-12 15:17:32 +0200259 }
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200260 SetWorker(user_traffic, CreateWorker());
261 worker = GetWorker(user_traffic);
Andreea Costinas77b180e2020-05-12 15:17:32 +0200262
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200263 if (!worker->Start()) {
264 ResetWorker(user_traffic);
265 return nullptr;
Andreea Costinas77b180e2020-05-12 15:17:32 +0200266 }
267 // patchpanel_proxy is owned by |dbus_object_->bus_|.
268 dbus::ObjectProxy* patchpanel_proxy = dbus_object_->GetBus()->GetObjectProxy(
269 patchpanel::kPatchPanelServiceName,
270 dbus::ObjectPath(patchpanel::kPatchPanelServicePath));
271 patchpanel_proxy->WaitForServiceToBeAvailable(
272 base::Bind(&SystemProxyAdaptor::OnPatchpanelServiceAvailable,
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200273 weak_ptr_factory_.GetWeakPtr(), user_traffic));
274 return worker;
Andreea Costinas77b180e2020-05-12 15:17:32 +0200275}
276
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200277void SystemProxyAdaptor::SetCredentialsTask(
278 SandboxedWorker* worker, const worker::Credentials& credentials) {
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100279 DCHECK(worker);
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200280 worker->SetCredentials(credentials);
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100281}
282
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200283void SystemProxyAdaptor::SetKerberosEnabledTask(
284 SandboxedWorker* worker,
285 bool kerberos_enabled,
286 const std::string& principal_name) {
287 DCHECK(worker);
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200288 worker->SetKerberosEnabled(kerberos_enabled,
289 kerberos_client_->krb5_conf_path(),
290 kerberos_client_->krb5_ccache_path());
291 kerberos_client_->SetKerberosEnabled(kerberos_enabled);
292 if (kerberos_enabled) {
293 kerberos_client_->SetPrincipalName(principal_name);
294 }
295}
296
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100297void SystemProxyAdaptor::ShutDownTask() {
298 brillo::MessageLoop::current()->BreakLoop();
299}
300
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200301void SystemProxyAdaptor::SetWorker(bool user_traffic,
302 std::unique_ptr<SandboxedWorker> worker) {
303 if (user_traffic) {
304 arc_worker_ = std::move(worker);
305 } else {
306 system_services_worker_ = std::move(worker);
307 }
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100308}
309
Andreea Costinase9c73592020-07-17 15:27:54 +0200310bool SystemProxyAdaptor::ResetWorker(bool user_traffic) {
311 SandboxedWorker* worker =
312 user_traffic ? arc_worker_.get() : system_services_worker_.get();
313 if (!worker) {
314 return true;
315 }
316 if (!worker->Stop()) {
317 return false;
318 }
319 if (user_traffic) {
320 arc_worker_.reset();
321 } else {
322 system_services_worker_.reset();
323 }
324 return true;
325}
326
327SandboxedWorker* SystemProxyAdaptor::GetWorker(bool user_traffic) {
328 return user_traffic ? arc_worker_.get() : system_services_worker_.get();
329}
330
Andreea Costinasfc3dc7d2020-07-20 18:54:38 +0200331bool SystemProxyAdaptor::IncludesSystemTraffic(TrafficOrigin traffic_origin) {
332 return traffic_origin != TrafficOrigin::USER;
333}
334
335bool SystemProxyAdaptor::IncludesUserTraffic(TrafficOrigin traffic_origin) {
336 return traffic_origin != TrafficOrigin::SYSTEM;
337}
338
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200339void SystemProxyAdaptor::OnPatchpanelServiceAvailable(bool user_traffic,
340 bool is_available) {
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200341 if (!is_available) {
342 LOG(ERROR) << "Patchpanel service not available";
343 return;
344 }
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200345 ConnectNamespace(user_traffic);
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200346}
347
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200348void SystemProxyAdaptor::ConnectNamespace(bool user_traffic) {
Andreea Costinas91f75352020-07-08 14:47:47 +0200349 DCHECK_GT(netns_reconnect_attempts_available_, 0);
350 --netns_reconnect_attempts_available_;
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200351 SandboxedWorker* worker = GetWorker(user_traffic);
352 DCHECK(worker);
Andreea Costinas91f75352020-07-08 14:47:47 +0200353 // TODO(b/160736881, acostinas): Remove the delay after patchpanel
354 // implements "ip netns" to create the veth pair across network namespaces.
355 brillo::MessageLoop::current()->PostDelayedTask(
356 FROM_HERE,
357 base::Bind(&SystemProxyAdaptor::ConnectNamespaceTask,
Andreea Costinase9c73592020-07-17 15:27:54 +0200358 weak_ptr_factory_.GetWeakPtr(), worker, user_traffic),
Andreea Costinas91f75352020-07-08 14:47:47 +0200359 kConnectNamespaceDelay);
360}
361
362void SystemProxyAdaptor::ConnectNamespaceTask(SandboxedWorker* worker,
363 bool user_traffic) {
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200364 std::unique_ptr<patchpanel::Client> patchpanel_client =
365 patchpanel::Client::New();
366 if (!patchpanel_client) {
367 LOG(ERROR) << "Failed to open networking service client";
Andreea Costinas91f75352020-07-08 14:47:47 +0200368 return;
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200369 }
370
371 std::pair<base::ScopedFD, patchpanel::ConnectNamespaceResponse> result =
372 patchpanel_client->ConnectNamespace(
373 worker->pid(), "" /* outbound_ifname */, user_traffic);
374
375 if (!result.first.is_valid()) {
Andreea Costinas91f75352020-07-08 14:47:47 +0200376 LOG(ERROR) << "Failed to setup network namespace on attempt "
377 << kNetworkNamespaceReconnectAttempts -
378 netns_reconnect_attempts_available_;
379 if (netns_reconnect_attempts_available_ > 0) {
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200380 ConnectNamespace(user_traffic);
Andreea Costinas91f75352020-07-08 14:47:47 +0200381 }
382 return;
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200383 }
384
385 worker->SetNetNamespaceLifelineFd(std::move(result.first));
Andreea Costinasa89309d2020-05-08 15:51:12 +0200386 if (!worker->SetListeningAddress(result.second.host_ipv4_address(),
387 kProxyPort)) {
Andreea Costinas91f75352020-07-08 14:47:47 +0200388 return;
Andreea Costinasa89309d2020-05-08 15:51:12 +0200389 }
390 OnNamespaceConnected(worker, user_traffic);
Andreea Costinasa89309d2020-05-08 15:51:12 +0200391}
392
393void SystemProxyAdaptor::OnNamespaceConnected(SandboxedWorker* worker,
394 bool user_traffic) {
395 WorkerActiveSignalDetails details;
396 details.set_traffic_origin(user_traffic ? TrafficOrigin::USER
397 : TrafficOrigin::SYSTEM);
398 details.set_local_proxy_url(worker->local_proxy_host_and_port());
399 SendWorkerActiveSignal(SerializeProto(details));
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100400}
401
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200402void SystemProxyAdaptor::RequestAuthenticationCredentials(
Andreea Costinased9e6122020-08-12 12:06:19 +0200403 const worker::ProtectionSpace& protection_space,
404 bool bad_cached_credentials) {
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200405 AuthenticationRequiredDetails details;
406 ProtectionSpace proxy_protection_space;
407 proxy_protection_space.set_origin(protection_space.origin());
408 proxy_protection_space.set_realm(protection_space.realm());
409 proxy_protection_space.set_scheme(protection_space.scheme());
410 *details.mutable_proxy_protection_space() = proxy_protection_space;
Andreea Costinased9e6122020-08-12 12:06:19 +0200411 details.set_bad_cached_credentials(bad_cached_credentials);
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200412 SendAuthenticationRequiredSignal(SerializeProto(details));
413}
414
Andreea Costinas942284d2020-01-28 16:28:40 +0100415} // namespace system_proxy