blob: 2e0323fdfaa99b5486d7c2f3ebff984424be7926 [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>
16#include <chromeos/patchpanel/client.h>
17#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 Costinas942284d2020-01-28 16:28:40 +010020#include "system_proxy/proto_bindings/system_proxy_service.pb.h"
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010021#include "system-proxy/sandboxed_worker.h"
Andreea Costinas942284d2020-01-28 16:28:40 +010022
23namespace system_proxy {
24namespace {
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010025
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020026constexpr int kProxyPort = 3128;
Andreea Costinas77b180e2020-05-12 15:17:32 +020027constexpr char kNoCredentialsSpecifiedError[] =
28 "No authentication credentials specified";
29constexpr char kOnlySystemTrafficSupportedError[] =
30 "Only system services traffic is currenly supported";
31constexpr char kFailedToStartWorkerError[] = "Failed to start worker process";
Andreea Costinas91f75352020-07-08 14:47:47 +020032// Time delay for calling patchpanel::ConnectNamespace(). Patchpanel needs to
33// enter the network namespace of the worker process to configure it and fails
34// if it's soon after the process starts. See https://crbug.com/1095170 for
35// details.
36constexpr base::TimeDelta kConnectNamespaceDelay =
37 base::TimeDelta::FromSeconds(1);
38constexpr int kNetworkNamespaceReconnectAttempts = 3;
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020039
Andreea Costinas942284d2020-01-28 16:28:40 +010040// Serializes |proto| to a vector of bytes.
41std::vector<uint8_t> SerializeProto(
42 const google::protobuf::MessageLite& proto) {
43 std::vector<uint8_t> proto_blob(proto.ByteSizeLong());
Andreea Costinasc991e232020-06-08 20:30:58 +020044 bool result = proto.SerializeToArray(proto_blob.data(), proto_blob.size());
45 DCHECK(result);
Andreea Costinas942284d2020-01-28 16:28:40 +010046 return proto_blob;
47}
48
49// Parses a proto from an array of bytes |proto_blob|. Returns
50// ERROR_PARSE_REQUEST_FAILED on error.
51std::string DeserializeProto(const base::Location& from_here,
52 google::protobuf::MessageLite* proto,
53 const std::vector<uint8_t>& proto_blob) {
54 if (!proto->ParseFromArray(proto_blob.data(), proto_blob.size())) {
55 const std::string error_message = "Failed to parse proto message.";
56 LOG(ERROR) << from_here.ToString() << error_message;
57 return error_message;
58 }
59 return "";
60}
61} // namespace
62
63SystemProxyAdaptor::SystemProxyAdaptor(
64 std::unique_ptr<brillo::dbus_utils::DBusObject> dbus_object)
65 : org::chromium::SystemProxyAdaptor(this),
Andreea Costinas91f75352020-07-08 14:47:47 +020066 netns_reconnect_attempts_available_(kNetworkNamespaceReconnectAttempts),
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010067 dbus_object_(std::move(dbus_object)),
Andreea Costinas922fbaf2020-05-28 11:55:22 +020068 weak_ptr_factory_(this) {
69 kerberos_client_ = std::make_unique<KerberosClient>(dbus_object_->GetBus());
70}
Andreea Costinas942284d2020-01-28 16:28:40 +010071
72SystemProxyAdaptor::~SystemProxyAdaptor() = default;
73
74void SystemProxyAdaptor::RegisterAsync(
75 const brillo::dbus_utils::AsyncEventSequencer::CompletionAction&
76 completion_callback) {
77 RegisterWithDBusObject(dbus_object_.get());
78 dbus_object_->RegisterAsync(completion_callback);
79}
80
Andreea Costinas77b180e2020-05-12 15:17:32 +020081std::vector<uint8_t> SystemProxyAdaptor::SetAuthenticationDetails(
82 const std::vector<uint8_t>& request_blob) {
83 LOG(INFO) << "Received set authentication details request.";
84
85 SetAuthenticationDetailsRequest request;
86 const std::string error_message =
87 DeserializeProto(FROM_HERE, &request, request_blob);
88
89 SetAuthenticationDetailsResponse response;
90 if (!error_message.empty()) {
91 response.set_error_message(error_message);
92 return SerializeProto(response);
93 }
94
Andreea Costinas77b180e2020-05-12 15:17:32 +020095 if (request.traffic_type() != TrafficOrigin::SYSTEM) {
96 response.set_error_message(kOnlySystemTrafficSupportedError);
97 return SerializeProto(response);
98 }
99
100 if (!CreateWorkerIfNeeded(/* user_traffic */ false)) {
101 response.set_error_message(kFailedToStartWorkerError);
102 return SerializeProto(response);
103 }
104
105 if (request.has_credentials()) {
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200106 if (!((request.credentials().has_username() &&
107 request.credentials().has_password()) ||
108 request.has_protection_space())) {
Andreea Costinas77b180e2020-05-12 15:17:32 +0200109 response.set_error_message(kNoCredentialsSpecifiedError);
110 return SerializeProto(response);
111 }
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200112 worker::Credentials credentials;
113 if (request.has_protection_space()) {
114 worker::ProtectionSpace protection_space;
115 protection_space.set_origin(request.protection_space().origin());
116 protection_space.set_scheme(request.protection_space().scheme());
117 protection_space.set_realm(request.protection_space().realm());
118 *credentials.mutable_protection_space() = protection_space;
119 }
120 if (request.credentials().has_username()) {
121 credentials.set_username(request.credentials().username());
122 credentials.set_password(request.credentials().password());
123 }
Andreea Costinas77b180e2020-05-12 15:17:32 +0200124 brillo::MessageLoop::current()->PostTask(
125 FROM_HERE, base::Bind(&SystemProxyAdaptor::SetCredentialsTask,
126 weak_ptr_factory_.GetWeakPtr(),
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200127 system_services_worker_.get(), credentials));
Andreea Costinas77b180e2020-05-12 15:17:32 +0200128 }
129
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200130 if (request.has_kerberos_enabled()) {
131 std::string principal_name = request.has_active_principal_name()
132 ? request.active_principal_name()
133 : std::string();
134
135 brillo::MessageLoop::current()->PostTask(
136 FROM_HERE, base::Bind(&SystemProxyAdaptor::SetKerberosEnabledTask,
137 weak_ptr_factory_.GetWeakPtr(),
138 system_services_worker_.get(),
139 request.kerberos_enabled(), principal_name));
140 }
141
Andreea Costinas77b180e2020-05-12 15:17:32 +0200142 return SerializeProto(response);
143}
144
Andreea Costinas942284d2020-01-28 16:28:40 +0100145std::vector<uint8_t> SystemProxyAdaptor::ShutDown() {
146 LOG(INFO) << "Received shutdown request.";
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100147
148 std::string error_message;
Andreea Costinase9c73592020-07-17 15:27:54 +0200149 if (!ResetWorker(/* user_traffic=*/false)) {
150 error_message =
151 "Failure to terminate worker process for system services traffic.";
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100152 }
153
Andreea Costinase9c73592020-07-17 15:27:54 +0200154 if (!ResetWorker(/* user_traffic=*/true)) {
155 error_message += "Failure to terminate worker process for arc traffic.";
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100156 }
157
Andreea Costinas942284d2020-01-28 16:28:40 +0100158 ShutDownResponse response;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100159 if (!error_message.empty())
160 response.set_error_message(error_message);
161
162 brillo::MessageLoop::current()->PostTask(
163 FROM_HERE, base::Bind(&SystemProxyAdaptor::ShutDownTask,
164 weak_ptr_factory_.GetWeakPtr()));
165
Andreea Costinas942284d2020-01-28 16:28:40 +0100166 return SerializeProto(response);
167}
168
Andreea Costinase9c73592020-07-17 15:27:54 +0200169std::vector<uint8_t> SystemProxyAdaptor::ClearUserCredentials(
170 const std::vector<uint8_t>& request_blob) {
171 LOG(INFO) << "Received request to clear user credentials.";
172 std::string error_message;
173 ClearUserCredentials(/*user_traffic=*/false, &error_message);
174 ClearUserCredentials(/*user_traffic=*/true, &error_message);
175
176 ClearUserCredentialsResponse response;
177 if (!error_message.empty())
178 response.set_error_message(error_message);
179 return SerializeProto(response);
180}
181
182void SystemProxyAdaptor::ClearUserCredentials(bool user_traffic,
183 std::string* error_message) {
184 SandboxedWorker* worker = GetWorker(user_traffic);
185 if (!worker) {
186 return;
187 }
188 if (!worker->ClearUserCredentials()) {
189 error_message->append(
190 base::StringPrintf("Failure to clear user credentials for worker with "
191 "pid %s. Restarting worker.",
192 std::to_string(worker->pid()).c_str()));
193 ResetWorker(user_traffic);
194 CreateWorkerIfNeeded(user_traffic);
195 }
196}
197
Andreea Costinas5862b102020-03-19 14:45:36 +0100198void SystemProxyAdaptor::GetChromeProxyServersAsync(
199 const std::string& target_url,
200 const brillo::http::GetChromeProxyServersCallback& callback) {
Andreea Costinasc9defae2020-04-22 10:28:35 +0200201 brillo::http::GetChromeProxyServersAsync(dbus_object_->GetBus(), target_url,
202 move(callback));
Andreea Costinas5862b102020-03-19 14:45:36 +0100203}
204
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100205std::unique_ptr<SandboxedWorker> SystemProxyAdaptor::CreateWorker() {
Andreea Costinas5862b102020-03-19 14:45:36 +0100206 return std::make_unique<SandboxedWorker>(weak_ptr_factory_.GetWeakPtr());
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100207}
208
Andreea Costinas77b180e2020-05-12 15:17:32 +0200209bool SystemProxyAdaptor::CreateWorkerIfNeeded(bool user_traffic) {
210 if (user_traffic) {
211 // Not supported at the moment.
212 return false;
213 }
214 if (system_services_worker_) {
215 return true;
216 }
217
218 system_services_worker_ = CreateWorker();
219 if (!StartWorker(system_services_worker_.get(),
220 /* user_traffic= */ false)) {
221 system_services_worker_.reset();
222 return false;
223 }
224 // patchpanel_proxy is owned by |dbus_object_->bus_|.
225 dbus::ObjectProxy* patchpanel_proxy = dbus_object_->GetBus()->GetObjectProxy(
226 patchpanel::kPatchPanelServiceName,
227 dbus::ObjectPath(patchpanel::kPatchPanelServicePath));
228 patchpanel_proxy->WaitForServiceToBeAvailable(
229 base::Bind(&SystemProxyAdaptor::OnPatchpanelServiceAvailable,
230 weak_ptr_factory_.GetWeakPtr()));
231 return true;
232}
233
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200234void SystemProxyAdaptor::SetCredentialsTask(
235 SandboxedWorker* worker, const worker::Credentials& credentials) {
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100236 DCHECK(worker);
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200237 worker->SetCredentials(credentials);
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100238}
239
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200240void SystemProxyAdaptor::SetKerberosEnabledTask(
241 SandboxedWorker* worker,
242 bool kerberos_enabled,
243 const std::string& principal_name) {
244 DCHECK(worker);
245
246 worker->SetKerberosEnabled(kerberos_enabled,
247 kerberos_client_->krb5_conf_path(),
248 kerberos_client_->krb5_ccache_path());
249 kerberos_client_->SetKerberosEnabled(kerberos_enabled);
250 if (kerberos_enabled) {
251 kerberos_client_->SetPrincipalName(principal_name);
252 }
253}
254
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100255void SystemProxyAdaptor::ShutDownTask() {
256 brillo::MessageLoop::current()->BreakLoop();
257}
258
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200259bool SystemProxyAdaptor::StartWorker(SandboxedWorker* worker,
260 bool user_traffic) {
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100261 DCHECK(worker);
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200262 return worker->Start();
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100263}
264
Andreea Costinase9c73592020-07-17 15:27:54 +0200265bool SystemProxyAdaptor::ResetWorker(bool user_traffic) {
266 SandboxedWorker* worker =
267 user_traffic ? arc_worker_.get() : system_services_worker_.get();
268 if (!worker) {
269 return true;
270 }
271 if (!worker->Stop()) {
272 return false;
273 }
274 if (user_traffic) {
275 arc_worker_.reset();
276 } else {
277 system_services_worker_.reset();
278 }
279 return true;
280}
281
282SandboxedWorker* SystemProxyAdaptor::GetWorker(bool user_traffic) {
283 return user_traffic ? arc_worker_.get() : system_services_worker_.get();
284}
285
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200286// Called when the patchpanel D-Bus service becomes available.
287void SystemProxyAdaptor::OnPatchpanelServiceAvailable(bool is_available) {
288 if (!is_available) {
289 LOG(ERROR) << "Patchpanel service not available";
290 return;
291 }
292 if (system_services_worker_) {
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200293 ConnectNamespace(system_services_worker_.get(), /* user_traffic= */ false);
294 }
295}
296
Andreea Costinas91f75352020-07-08 14:47:47 +0200297void SystemProxyAdaptor::ConnectNamespace(SandboxedWorker* worker,
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200298 bool user_traffic) {
Andreea Costinase9c73592020-07-17 15:27:54 +0200299 DCHECK(worker->IsRunning());
Andreea Costinas91f75352020-07-08 14:47:47 +0200300 DCHECK_GT(netns_reconnect_attempts_available_, 0);
301 --netns_reconnect_attempts_available_;
302 // TODO(b/160736881, acostinas): Remove the delay after patchpanel
303 // implements "ip netns" to create the veth pair across network namespaces.
304 brillo::MessageLoop::current()->PostDelayedTask(
305 FROM_HERE,
306 base::Bind(&SystemProxyAdaptor::ConnectNamespaceTask,
Andreea Costinase9c73592020-07-17 15:27:54 +0200307 weak_ptr_factory_.GetWeakPtr(), worker, user_traffic),
Andreea Costinas91f75352020-07-08 14:47:47 +0200308 kConnectNamespaceDelay);
309}
310
311void SystemProxyAdaptor::ConnectNamespaceTask(SandboxedWorker* worker,
312 bool user_traffic) {
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200313 std::unique_ptr<patchpanel::Client> patchpanel_client =
314 patchpanel::Client::New();
315 if (!patchpanel_client) {
316 LOG(ERROR) << "Failed to open networking service client";
Andreea Costinas91f75352020-07-08 14:47:47 +0200317 return;
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200318 }
319
320 std::pair<base::ScopedFD, patchpanel::ConnectNamespaceResponse> result =
321 patchpanel_client->ConnectNamespace(
322 worker->pid(), "" /* outbound_ifname */, user_traffic);
323
324 if (!result.first.is_valid()) {
Andreea Costinas91f75352020-07-08 14:47:47 +0200325 LOG(ERROR) << "Failed to setup network namespace on attempt "
326 << kNetworkNamespaceReconnectAttempts -
327 netns_reconnect_attempts_available_;
328 if (netns_reconnect_attempts_available_ > 0) {
329 ConnectNamespace(worker, user_traffic);
330 }
331 return;
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200332 }
333
334 worker->SetNetNamespaceLifelineFd(std::move(result.first));
Andreea Costinasa89309d2020-05-08 15:51:12 +0200335 if (!worker->SetListeningAddress(result.second.host_ipv4_address(),
336 kProxyPort)) {
Andreea Costinas91f75352020-07-08 14:47:47 +0200337 return;
Andreea Costinasa89309d2020-05-08 15:51:12 +0200338 }
339 OnNamespaceConnected(worker, user_traffic);
Andreea Costinasa89309d2020-05-08 15:51:12 +0200340}
341
342void SystemProxyAdaptor::OnNamespaceConnected(SandboxedWorker* worker,
343 bool user_traffic) {
344 WorkerActiveSignalDetails details;
345 details.set_traffic_origin(user_traffic ? TrafficOrigin::USER
346 : TrafficOrigin::SYSTEM);
347 details.set_local_proxy_url(worker->local_proxy_host_and_port());
348 SendWorkerActiveSignal(SerializeProto(details));
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100349}
350
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200351void SystemProxyAdaptor::RequestAuthenticationCredentials(
352 const worker::ProtectionSpace& protection_space) {
353 AuthenticationRequiredDetails details;
354 ProtectionSpace proxy_protection_space;
355 proxy_protection_space.set_origin(protection_space.origin());
356 proxy_protection_space.set_realm(protection_space.realm());
357 proxy_protection_space.set_scheme(protection_space.scheme());
358 *details.mutable_proxy_protection_space() = proxy_protection_space;
359 SendAuthenticationRequiredSignal(SerializeProto(details));
360}
361
Andreea Costinas942284d2020-01-28 16:28:40 +0100362} // namespace system_proxy