diff options
-rw-r--r-- | mod/jwt.cxx | 10 | ||||
-rw-r--r-- | mod/mod-ci-github.cxx | 124 | ||||
-rw-r--r-- | mod/mod-ci-github.hxx | 10 |
3 files changed, 74 insertions, 70 deletions
diff --git a/mod/jwt.cxx b/mod/jwt.cxx index 6176f27..4e28630 100644 --- a/mod/jwt.cxx +++ b/mod/jwt.cxx @@ -52,11 +52,11 @@ using namespace butl; // base64url($header) + '.' + base64url($payload) + '.' + base64url($signature) // string brep:: -gen_jwt (const options::openssl_options& o, - const path& pk, - const string& iss, - const chrono::seconds& vp, - const chrono::seconds& bd) +generate_jwt (const options::openssl_options& o, + const path& pk, + const string& iss, + const chrono::seconds& vp, + const chrono::seconds& bd) { // Create the header. // diff --git a/mod/mod-ci-github.cxx b/mod/mod-ci-github.cxx index 0d92f8a..6782e71 100644 --- a/mod/mod-ci-github.cxx +++ b/mod/mod-ci-github.cxx @@ -33,51 +33,6 @@ // https://en.wikipedia.org/wiki/HMAC#Definition. A suitable implementation // is provided by OpenSSL. -// @@ Move to function bodies below. - -// @@ Authenticating to use the API -// -// There are three types of authentication: -// -// 1) Authenticating as an app. Used to access parts of the API concerning -// the app itself such as getting the list of installations. (Need to -// authenticate as an app as part of authenticating as an app -// installation.) -// -// 2) Authenticating as an app installation (on a user or organisation -// account). Used to access resources belonging to the user/repository -// or organisation the app is installed in. -// -// 3) Authenticating as a user. Used to perform actions as the user. -// -// We need to authenticate as an app installation (2). -// -// How to authenticate as an app installation -// -// Reference: -// https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation -// -// The final authentication token we need is an installation access token -// (IAT), valid for one hour, which we will pass in the `Authentication` -// header of our Github API requests: -// -// Authorization: Bearer <INSTALLATION_ACCESS_TOKEN> -// -// To generate an IAT: -// -// - Generate a JSON Web Token (JWT) -// -// - Get the installation ID. This will be included in the webhook request -// in our case -// -// - Send a POST to /app/installations/<INSTALLATION_ID>/access_tokens -// which includes the JWT (`Authorization: Bearer <JWT>`). The response -// will include the IAT. Can pass the name of the repository included in -// the webhook request to restrict access, otherwise we get access to all -// repos covered by the installation if installed on an organisation for -// example. -// - using namespace std; using namespace butl; using namespace web; @@ -108,7 +63,7 @@ namespace brep HANDLER_DIAG; - // @@ TODO: diable service is HMAC is not specified in config. + // @@ TODO: disable service if HMAC is not specified in config. // if (false) throw invalid_request (404, "CI request submission disabled"); @@ -116,6 +71,8 @@ namespace brep // Process headers. // string event; + // @@ TMP Shouldn't we also error<< in some of these header problem cases? + // { bool content_type (false); @@ -233,13 +190,13 @@ namespace brep } } - bool - handle_check_suite_request (check_suite_event) const + bool ci_github:: + handle_check_suite_request (check_suite_event cs) const { cout << "<check_suite webhook>" << endl << cs << endl; installation_access_token iat ( - obtain_installation_access_token (generate_jwt ())); + obtain_installation_access_token (cs.installation.id, generate_jwt ())); cout << endl << "<installation_access_token>" << endl << iat << endl; @@ -380,7 +337,7 @@ namespace brep } } - void string + string ci_github:: generate_jwt () const { string jwt; @@ -389,33 +346,76 @@ namespace brep // Set token's "issued at" time 60 seconds in the past to combat clock // drift (as recommended by GitHub). // - jwt = gen_jwt ( - *options_, - options_->ci_github_app_private_key (), - to_string (options_->ci_github_app_id ()), - chrono::seconds (options_->ci_github_jwt_validity_period ()), - chrono::seconds (60)); + jwt = brep::generate_jwt ( + *options_, + options_->ci_github_app_private_key (), + to_string (options_->ci_github_app_id ()), + chrono::seconds (options_->ci_github_jwt_validity_period ()), + chrono::seconds (60)); cout << "JWT: " << jwt << endl; } catch (const system_error& e) { + HANDLER_DIAG; + fail << "unable to generate JWT (errno=" << e.code () << "): " << e; } + + return jwt; } - // Authenticate to GitHub as an app installation. + // There are three types of GitHub API authentication: + // + // 1) Authenticating as an app. Used to access parts of the API concerning + // the app itself such as getting the list of installations. (Need to + // authenticate as an app as part of authenticating as an app + // installation.) + // + // 2) Authenticating as an app installation (on a user or organisation + // account). Used to access resources belonging to the user/repository + // or organisation the app is installed in. + // + // 3) Authenticating as a user. Used to perform actions as the user. + // + // We need to authenticate as an app installation (2). + // + // How to authenticate as an app installation + // + // Reference: + // https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation + // + // The final authentication token we need is an installation access token + // (IAT), valid for one hour, which we will pass in the `Authentication` + // header of our Github API requests: // - gh::installation_access_token - obtain_installation_access_token (string jwt) const + // Authorization: Bearer <INSTALLATION_ACCESS_TOKEN> + // + // To generate an IAT: + // + // - Generate a JSON Web Token (JWT) + // + // - Get the installation ID. This will be included in the webhook request + // in our case + // + // - Send a POST to /app/installations/<INSTALLATION_ID>/access_tokens which + // includes the JWT (`Authorization: Bearer <JWT>`). The response will + // include the IAT. Can pass the name of the repository included in the + // webhook request to restrict access, otherwise we get access to all + // repos covered by the installation if installed on an organisation for + // example. + // + installation_access_token ci_github:: + obtain_installation_access_token (uint64_t iid, string jwt) const { + HANDLER_DIAG; + installation_access_token iat; try { // API endpoint. // - string ep ("app/installations/" + to_string (cs.installation.id) + - "/access_tokens"); + string ep ("app/installations/" + to_string (iid) + "/access_tokens"); int sc (github_post (iat, ep, strings {"Authorization: Bearer " + jwt})); @@ -452,6 +452,8 @@ namespace brep fail << "unable to get installation access token (errno=" << e.code () << "): " << e.what (); } + + return iat; } // The rest is GitHub request/response type parsing and printing. diff --git a/mod/mod-ci-github.hxx b/mod/mod-ci-github.hxx index 4f724af..f9fb7e6 100644 --- a/mod/mod-ci-github.hxx +++ b/mod/mod-ci-github.hxx @@ -29,7 +29,9 @@ namespace brep // namespace gh { - // The "check_suite" object within a check_quite webhook request. + namespace json = butl::json; + + // The "check_suite" object within a check_suite webhook request. // struct check_suite { @@ -131,15 +133,15 @@ namespace brep // Handle the check_suite event `requested` and `rerequested` actions. // bool - handle_check_suite_request (check_suite_event) const; + handle_check_suite_request (gh::check_suite_event) const; - void string + string generate_jwt () const; // Authenticate to GitHub as an app installation. // gh::installation_access_token - obtain_installation_access_token (string jwt) const; + obtain_installation_access_token (uint64_t install_id, string jwt) const; private: shared_ptr<options::ci_github> options_; |