From 983eb4b40d63427b619f90e7fe05b4717129a927 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 1 Jun 2017 19:52:54 +0300 Subject: Add support for bbot agent authentication --- bbot/agent.cli | 58 +++++++++++++++++++++++++------- bbot/agent.cxx | 86 +++++++++++++++++++++++++++++++++++++++++++----- bbot/bbot-agent@.service | 3 ++ doc/manual.cli | 2 +- 4 files changed, 128 insertions(+), 21 deletions(-) diff --git a/bbot/agent.cli b/bbot/agent.cli index 2f5ad65..a5dbe01 100644 --- a/bbot/agent.cli +++ b/bbot/agent.cli @@ -42,6 +42,52 @@ namespace bbot "Run as a simple systemd daemon." } + path --auth-key + { + "", + "Private key for the public key-based agent authentication. If not + specified, then the agent will not be able to request tasks from + controllers that require authentication. + + The file is expected to contain a single PEM-encoded private key + without a password. A suitable key can be generated using the + following command: + + \ + $ openssl genrsa 4096 >key.pem + \ + " + } + + path --openssl = "openssl" + { + "", + "The openssl program to be used for crypto operations. You can also + specify additional options that should be passed to the openssl program + with \cb{--openssl-option}. If the openssl program is not explicitly + specified, then \cb{bbot-agent} will use \cb{openssl} by default." + } + + strings --openssl-option + { + "", + "Additional option to be passed to the openssl program (see + \cb{--openssl} for details). Repeat this option to specify multiple + openssl options." + } + + size_t --cpu = 1 + { + "", + "Number of CPUs (threads) to use, 1 by default." + } + + size_t --ram (1024 * 1024) // 1G + { + "", + "Amount of RAM (in kB) to use, 1G by default." + } + string --toolchain-name = "default" { "", @@ -75,18 +121,6 @@ namespace bbot "Trust repository certificate with a SHA256 ." } - size_t --cpu = 1 - { - "", - "Number of CPUs (threads) to use, 1 by default." - } - - size_t --ram (1024 * 1024) // 1G - { - "", - "Amount of RAM (in kB) to use, 1G by default." - } - dir_path --machines = "/build/machines/" { "", diff --git a/bbot/agent.cxx b/bbot/agent.cxx index 234763f..117840a 100644 --- a/bbot/agent.cxx +++ b/bbot/agent.cxx @@ -20,6 +20,8 @@ #include #include +#include +#include #include // dir_iterator #include @@ -870,11 +872,46 @@ try fail << "unable to set signal handler: " << system_error (errno, generic_category ()); // Sanitize. + optional fingerprint; + + if (ops.auth_key_specified ()) + try + { + // Note that the process always prints to STDERR, so we redirect it to the + // null device. We also check for the key file existence to print more + // meaningful error message if that's not the case. + // + if (!file_exists (ops.auth_key ())) + throw_generic_error (ENOENT); + + openssl os (trace, + ops.auth_key (), path ("-"), fdnull (), + ops.openssl (), "rsa", + ops.openssl_option (), "-pubout", "-outform", "DER"); + + vector k (os.in.read_binary ()); + os.in.close (); + + if (!os.wait ()) + throw_generic_error (EIO); + + fingerprint = sha256 (k.data (), k.size ()).string (); + } + catch (const system_error& e) + { + fail << "unable to obtain authentication public key: " << e; + } + if (ops.systemd_daemon ()) { diag_record dr; - dr << info << "bbot agent " << BBOT_VERSION_ID << + dr << info << "bbot agent " << BBOT_VERSION_ID; + + if (fingerprint) + dr << info << "auth key fp " << *fingerprint; + + dr << info << "toolchain name " << tc_name << info << "toolchain num " << tc_num << info << "toolchain ver " << tc_ver.string () << @@ -907,13 +944,11 @@ try // Prepare task request. // - // @@ TODO: key fingerprint. - // task_request_manifest tq { hname, tc_name, tc_ver, - nullopt, + fingerprint, machine_header_manifests {} }; @@ -949,7 +984,7 @@ try tr = task_response_manifest { "fake-session", // Dummy session. - string (), // Empty challange. + nullopt, // No challenge. url, // Empty result URL. move (t)}; @@ -1001,6 +1036,12 @@ try continue; } + if (tr.challenge && !fingerprint) // Controller misbehaves. + { + error << "unexpected challenge from " << u << ": " << *tr.challenge; + continue; + } + if (!tr.session.empty ()) // Got a task. { url = u; @@ -1071,11 +1112,40 @@ try return 0; } - // Upload the result. + // Prepare answer to the private key challenge. // - // @@ TODO challange + optional> challenge; + + if (tr.challenge) + try + { + assert (ops.auth_key_specified ()); + + openssl os (trace, + fdstream_mode::text, path ("-"), 2, + ops.openssl (), "rsautl", + ops.openssl_option (), "-sign", "-inkey", ops.auth_key ()); + + os.out << *tr.challenge; + os.out.close (); + + challenge = os.in.read_binary (); + os.in.close (); + + if (!os.wait ()) + throw_generic_error (EIO); + } + catch (const system_error& e) + { + // The task response challenge is valid (verified by manifest parser), + // so there is something wrong with setup, and so the failure is fatal. + // + fail << "unable to sign task response challenge: " << e; + } + + // Upload the result. // - result_request_manifest rq {tr.session, nullopt, move (r)}; + result_request_manifest rq {tr.session, move (challenge), move (r)}; { const string& u (*tr.result_url); diff --git a/bbot/bbot-agent@.service b/bbot/bbot-agent@.service index 7d8c250..41486e2 100644 --- a/bbot/bbot-agent@.service +++ b/bbot/bbot-agent@.service @@ -7,6 +7,8 @@ Type=simple Environment=VERBOSE=3 +Environment=AUTH_KEY= + Environment=CPU=1 Environment=RAM=1048576 @@ -26,6 +28,7 @@ Environment="CONTROLLER_TRUST=" ExecStart=/build/bots/%i/bin/bbot-agent --systemd-daemon \ --verbose ${VERBOSE} \ + --auth-key ${AUTH_KEY} \ --cpu ${CPU} \ --ram ${RAM} \ --bootstrap-timeout ${BOOTSTRAP_TIMEOUT} \ diff --git a/doc/manual.cli b/doc/manual.cli index f1172cc..d581264 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -434,7 +434,7 @@ starts with the task request manifest followed by a list of machine manifests. \li|\n\c{fingerprint: }\n The SHA256 fingerprint of the agent's public key. An agent may be configured - not to use the certificate-based authentication in which case it does not + not to use the public key-based authentication in which case it does not include this value. However, the controller may be configured to require the authentication in which case it will respond with the 401 (unauthorized) HTTP status code.|| -- cgit v1.1