From d6a34b68d4667d4b99c1e76d63604a7bc1c9c3dd Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 25 May 2017 21:12:03 +0300 Subject: Add support for bbot agent authentication --- mod/mod-build-result.cxx | 123 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 103 insertions(+), 20 deletions(-) (limited to 'mod/mod-build-result.cxx') diff --git a/mod/mod-build-result.cxx b/mod/mod-build-result.cxx index ae6c5b1..41bfb2b 100644 --- a/mod/mod-build-result.cxx +++ b/mod/mod-build-result.cxx @@ -7,7 +7,9 @@ #include #include +#include #include +#include #include #include #include @@ -226,6 +228,11 @@ handle (request& rq, response&) return true; } + auto print_args = [&trace, this] (const char* args[], size_t n) + { + l2 ([&]{trace << process_args {args, n};}); + }; + // Load and update the package build configuration (if present). // shared_ptr b; @@ -245,28 +252,109 @@ handle (request& rq, response&) warn_expired ("non-matching timestamp"); else { - unforced = b->force == force_state::unforced; + // Check the challenge. + // + // If the challenge doesn't match expectations (probably due to the + // authentication settings change), then we log this case with the + // warning severity and respond with the 200 HTTP code as if the + // challenge is valid. The thinking is that we shouldn't alarm a + // law-abaiding agent and shouldn't provide any information to a + // malicious one. + // + auto warn_auth = [&rqm, &warn] (const string& d) + { + warn << "session '" << rqm.session << "' authentication failed: " << d; + }; + + bool auth (false); - // Don's send email for the success-to-success status change, unless the - // build was forced. + // Must both be present or absent. // - notify = !(rqm.result.status == result_status::success && - b->status && *b->status == rqm.result.status && unforced); + if (!b->agent_challenge != !rqm.challenge) + warn_auth (rqm.challenge + ? "unexpected challenge" + : "challenge is expected"); + else if (bot_agent_keys_ == nullptr) // Authentication is disabled. + auth = true; + else if (!b->agent_challenge) // Authentication is recently enabled. + warn_auth ("challenge is required now"); + else + { + assert (b->agent_fingerprint && rqm.challenge); + auto i (bot_agent_keys_->find (*b->agent_fingerprint)); + + // The agent's key is recently replaced. + // + if (i == bot_agent_keys_->end ()) + warn_auth ("agent's public key not found"); + else + { + try + { + openssl os (print_args, + path ("-"), fdstream_mode::text, 2, + options_->openssl (), "rsautl", + options_->openssl_option (), + "-verify", "-pubin", "-inkey", i->second); + + for (const auto& c: *rqm.challenge) + os.out.put (c); // Sets badbit on failure. + + os.out.close (); + + string s; + getline (os.in, s); + + bool v (os.in.eof ()); + os.in.close (); + + if (os.wait () && v) + { + auth = s == *b->agent_challenge; + + if (!auth) + warn_auth ("challenge mismatched"); + } + else // The signature is presumably meaningless. + warn_auth ("unable to verify challenge"); + } + catch (const system_error& e) + { + fail << "unable to verify challenge: " << e; + } + } + } - prev_status = move (b->status); + if (auth) + { + unforced = b->force == force_state::unforced; - b->state = build_state::built; - b->status = rqm.result.status; - b->force = force_state::unforced; + // Don's send email for the success-to-success status change, unless + // the build was forced. + // + notify = !(rqm.result.status == result_status::success && + b->status && *b->status == rqm.result.status && unforced); - // Mark the section as loaded, so results are updated. - // - b->results_section.load (); - b->results = move (rqm.result.results); + prev_status = move (b->status); + + b->state = build_state::built; + b->status = rqm.result.status; + b->force = force_state::unforced; + + // Cleanup the authentication data. + // + b->agent_fingerprint = nullopt; + b->agent_challenge = nullopt; + + // Mark the section as loaded, so results are updated. + // + b->results_section.load (); + b->results = move (rqm.result.results); - b->timestamp = timestamp::clock::now (); + b->timestamp = timestamp::clock::now (); - build_db_->update (b); + build_db_->update (b); + } } t.commit (); @@ -299,11 +387,6 @@ handle (request& rq, response&) ? *p->package_email : p->email); - auto print_args = [&trace, this] (const char* args[], size_t n) - { - l2 ([&]{trace << process_args {args, n};}); - }; - // Redirect the diagnostics to webserver error log. // // Note: if using this somewhere else, then need to factor out all this -- cgit v1.1