Skip to content

Commit 49fb4b8

Browse files
committed
HPCC-35979 feat(eclccserver) Add github app support to eclccserver
Signed-off-by: Gavin Halliday <gavin.halliday@lexisnexis.com>
1 parent 8673f4d commit 49fb4b8

6 files changed

Lines changed: 144 additions & 38 deletions

File tree

common/remote/hooks/git/gitfile.cpp

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -250,20 +250,9 @@ class GitRepositoryFileIO : implements CSimpleInterfaceOf<IFileIO>
250250
addPathSepChar(scriptPath).append("bin/hpccaskpass.sh");
251251
env.emplace_back("GIT_ASKPASS", scriptPath);
252252

253-
Owned<const IPropertyTree> secret = getSecret("git", gitUser);
254-
if (secret)
255-
{
256-
MemoryBuffer gitKey;
257-
if (getSecretKeyValue(gitKey, secret, "password"))
258-
{
259-
extractedKey.setown(writeToProtectedTempFile("eclcc", "git", gitKey.length(), gitKey.toByteArray()));
260-
env.emplace_back("HPCC_GIT_PASSPATH", extractedKey->queryFilename());
261-
}
262-
else
263-
OWARNLOG("Secret doesn't contain password for git user %s", gitUser);
264-
}
265-
else
266-
OWARNLOG("No secret found for git user %s", gitUser);
253+
extractedKey.setown(getFileWithGitAccessToken(gitUser));
254+
if (extractedKey)
255+
env.emplace_back("HPCC_GIT_PASSPATH", extractedKey->queryFilename());
267256
}
268257
Owned<IPipeProcess> pipe = createPipeProcess();
269258
for (const auto & cur : env)

devdoc/githubapps.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Using GitHub apps for authenticating from eclccserver
2+
3+
## Creating and Configuring a GitHub App for eclccserver
4+
5+
Here is a step-by-step guide to creating and configuring a GitHub App to use with the authentication mechanism.
6+
7+
### Step 1: Create the GitHub App
8+
1. Go to your GitHub account (or Organization) **Settings**.
9+
2. On the left sidebar, scroll down and click **Developer settings**.
10+
3. Select **GitHub Apps** in the left sidebar, then click the **New GitHub App** button.
11+
4. Fill out the **Register new GitHub App** form:
12+
* **GitHub App name**: Give it a recognizable name (e.g., `HPCC ECL Fetcher`).
13+
* **Homepage URL**: You can use your organization's URL or the HPCC platform repository URL (this is just required by the form).
14+
* **Webhook**: Uncheck the **Active** checkbox. (HPCC only needs to pull code; it doesn't need to listen for GitHub events).
15+
5. Set the **Repository permissions**:
16+
* Under **Repository permissions**, find **Contents** and set the access to **Read-only**. (This gives the app permission to perform `git fetch` / `git clone`).
17+
6. Scroll to the bottom (**Where can this GitHub App be installed?**):
18+
* Select **Any account** if you plan to fetch repositories owned by different organizations/users. Select **Only on this account** if you exclusively fetch repositories within your current organization.
19+
7. Click **Create GitHub App**.
20+
21+
### Step 2: Gather Your Secrets
22+
Immediately after creating the app, you will land on its settings page. You need to collect three items to populate your HPCC secrets configuration:
23+
24+
1. **The App ID** (`appid`):
25+
* Look at the "About" section at the top of the page. Copy the **App ID**.
26+
2. **The Private Key** (`appkey`):
27+
* Scroll down to the **Private keys** section.
28+
* Click **Generate a private key**.
29+
* This will download a `.pem` file to your computer. The entire contents of this file (including the `-----BEGIN RSA PRIVATE KEY-----` and end tags) will be your `appkey` secret.
30+
31+
### Step 3: Install the App & Get the Installation ID
32+
The App ID and Private Key alone are not enough; the app must be *installed* on the target account/repository to generate the `installationid`.
33+
34+
1. While still on the App's settings page, click **Install App** on the left sidebar.
35+
2. Click **Install** next to the user or organization account where the target ECL repositories live.
36+
3. Choose whether to install it on **All repositories** or **Only select repositories**, then click **Install**.
37+
4. You will be redirected to the installation settings page. Look at the URL in your browser's address bar. It will look like this:
38+
`https://github.com/settings/installations/12345678`
39+
or
40+
`https://github.com/organizations/YOUR_ORG/settings/installations/12345678`
41+
5. Copy the numeric value at the end of the URL (e.g., `12345678`). This is your **Installation ID** (`installationid`).
42+
43+
## Configuring the secrets for eclccserver
44+
45+
1. configure the git user name to be x-access-token
46+
47+
2. Create a secret category "git", secret "x-access-token"
48+
49+
3. Place these three values highlighted above (`appid`, `appkey`, and `installationid`) into your HPCC secret.

ecl/hql/hqlrepository.cpp

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,33 +1008,12 @@ unsigned EclRepositoryManager::runGitCommand(StringBuffer * output, const char *
10081008
// If gituser is specified never prompt for credentials, otherwise the server can hang.
10091009
env.emplace_back("GIT_TERMINAL_PROMPT", "0");
10101010

1011-
if (!options.gitPasswordPath.isEmpty())
1011+
extractedKey.setown(getFileWithGitAccessToken(options.gitUser.str()));
1012+
if (extractedKey)
10121013
{
1013-
//Convert to an absolute path, and check the file exists, because git will be run in a different directory
1014-
StringBuffer absolutePath;
1015-
makeAbsolutePath(options.gitPasswordPath.str(), absolutePath, true);
1016-
1017-
env.emplace_back("HPCC_GIT_PASSPATH", absolutePath);
1014+
env.emplace_back("HPCC_GIT_PASSPATH", extractedKey->queryFilename());
10181015
useScript = true;
10191016
}
1020-
else
1021-
{
1022-
Owned<const IPropertyTree> secret = getSecret("git", options.gitUser.str());
1023-
if (secret)
1024-
{
1025-
MemoryBuffer gitKey;
1026-
if (!getSecretKeyValue(gitKey, secret, "password"))
1027-
DBGLOG("Secret doesn't contain password for git user %s", options.gitUser.str());
1028-
else
1029-
{
1030-
extractedKey.setown(writeToProtectedTempFile("eclcc", "git", gitKey.length(), gitKey.toByteArray()));
1031-
env.emplace_back("HPCC_GIT_PASSPATH", extractedKey->queryFilename());
1032-
useScript = true;
1033-
}
1034-
}
1035-
else
1036-
DBGLOG("No secret found for git user %s", options.gitUser.str());
1037-
}
10381017
}
10391018

10401019
if (useScript)

system/jlib/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ if (NOT WIN32 AND NOT WIN64 AND NOT APPLE AND NOT EMSCRIPTEN)
5656
pkg_check_modules(liburing REQUIRED IMPORTED_TARGET liburing>=2.0)
5757
endif ()
5858

59+
find_package(CURL REQUIRED)
60+
find_path(JWT_CPP_INCLUDE_DIRS "jwt-cpp/base.h" HINTS "${VCPKG_INSTALLED_DIR}")
61+
5962
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STRICT_CXX_FLAGS}")
6063

6164
set ( SRCS
@@ -212,6 +215,7 @@ set ( INCLUDES
212215
${HPCC_SOURCE_DIR}/system/security/cryptohelper/digisign.cpp
213216
${HPCC_SOURCE_DIR}/system/security/cryptohelper/pke.cpp
214217
${HPCC_SOURCE_DIR}/system/security/cryptohelper/ske.cpp
218+
${JWT_CPP_INCLUDE_DIRS}/jwt-cpp/jwt.h
215219
)
216220

217221
set_source_files_properties(jmd5.cpp jsort.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON)

system/jlib/jsecrets.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,20 @@
4949
#pragma GCC diagnostic pop
5050
#endif
5151

52+
#ifdef verify
53+
#define JWT_HAS_VERIFY_MACRO
54+
#define OLD_VERIFY verify
55+
#undef verify
56+
#endif
57+
58+
#include "jwt-cpp/jwt.h"
59+
#include "nlohmann/json.hpp"
60+
61+
#ifdef JWT_HAS_VERIFY_MACRO
62+
//Force an error if verify macro is used in any of the following code
63+
#undef verify
64+
#endif
65+
5266
#ifdef _USE_OPENSSL
5367
#include <opensslcommon.hpp>
5468
#include <openssl/x509v3.h>
@@ -2427,3 +2441,70 @@ void maskSecret(StringBuffer & maskedSecret, const char * originalSecret, unsign
24272441
maskedSecret.append(originalSecret[i]);
24282442
}
24292443
}
2444+
2445+
std::string generateGithubIAT(const char * appId, const char * appKey, const char * installationId)
2446+
{
2447+
try
2448+
{
2449+
auto now = std::chrono::system_clock::now();
2450+
auto token = jwt::create()
2451+
.set_issuer(appId)
2452+
.set_issued_at(now)
2453+
.set_expires_at(now + std::chrono::minutes(10))
2454+
.sign(jwt::algorithm::rs256("", appKey));
2455+
2456+
httplib::SSLClient cli("api.github.com");
2457+
cli.set_bearer_token_auth(token.c_str());
2458+
cli.set_default_headers({
2459+
{"User-Agent", "ECL-Compiler/1.0"},
2460+
{"Accept", "application/vnd.github.v3+json"}
2461+
});
2462+
2463+
// Generate Access Token
2464+
std::string tokenPath = "/app/installations/";
2465+
tokenPath += installationId;
2466+
tokenPath += "/access_tokens";
2467+
auto postRes = cli.Post(tokenPath.c_str());
2468+
if (!postRes || postRes->status != 201)
2469+
{
2470+
DBGLOG("Failed to generate GitHub IAT. HTTP status: %d", postRes ? postRes->status : -1);
2471+
return "";
2472+
}
2473+
2474+
auto tokenJson = nlohmann::json::parse(postRes->body);
2475+
return tokenJson["token"].get<std::string>();
2476+
}
2477+
catch (const std::exception& e)
2478+
{
2479+
DBGLOG("Exception generating GitHub IAT: %s", e.what());
2480+
}
2481+
2482+
return "";
2483+
}
2484+
2485+
IFile * getFileWithGitAccessToken(const char * gitUser)
2486+
{
2487+
Owned<const IPropertyTree> secret = getSecret("git", gitUser);
2488+
if (!secret)
2489+
{
2490+
DBGLOG("No secret found for git user %s", gitUser);
2491+
return nullptr;
2492+
}
2493+
2494+
MemoryBuffer gitKey;
2495+
if (getSecretKeyValue(gitKey, secret, "password"))
2496+
return writeToProtectedTempFile("eclcc", "git", gitKey.length(), gitKey.toByteArray());
2497+
2498+
StringBuffer appId, appKey, installationId;
2499+
if (getSecretKeyValue(appId, secret, "appid") && getSecretKeyValue(appKey, secret, "appkey") && getSecretKeyValue(installationId, secret, "installationid"))
2500+
{
2501+
// Generate an Installation Access Token (IAT) from the App ID, Private Key, and Installation ID
2502+
std::string iat = generateGithubIAT(appId.str(), appKey.str(), installationId.str());
2503+
if (!iat.empty())
2504+
return writeToProtectedTempFile("eclcc", "git", iat.length(), iat.c_str());
2505+
return nullptr;
2506+
}
2507+
2508+
DBGLOG("Secret doesn't contain password or github app credentials for git user %s", gitUser);
2509+
return nullptr;
2510+
}

system/jlib/jsecrets.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include "jlib.hpp"
2323
#include "jstring.hpp"
24+
#include <string>
2425

2526
interface ISyncedPropertyTree;
2627

@@ -71,6 +72,9 @@ constexpr static unsigned defaultMaskingPercentage = 90;
7172
//creates a masked version of a secret
7273
extern jlib_decl void maskSecret(StringBuffer & maskedSecret, const char * originalSecret, unsigned maskPercentage = defaultMaskingPercentage, bool maskRightSide = true, char maskChar = defaultMaskChar);
7374

75+
extern jlib_decl std::string generateGithubIAT(const char * appId, const char * appKey, const char * installationId);
76+
extern jlib_decl IFile * getFileWithGitAccessToken(const char * gitUser);
77+
7478
#ifdef _USE_CPPUNIT
7579
extern jlib_decl std::string testBuildSecretKey(const char * category, const char * name, const char * optVaultId, const char * optVersion);
7680
extern jlib_decl void testExpandSecretKey(std::string & category, std::string & name, std::string & optVaultId, std::string & optVersion, const char * key);

0 commit comments

Comments
 (0)