Skip to content

Commit

Permalink
Adding GitHub api url (#14)
Browse files Browse the repository at this point in the history
* Adding GitHub base API URL support

* Updating the ability to inject GitHub API url and expanding tests

* Fixing JSON struture for test data
  • Loading branch information
peter-murray authored Apr 30, 2022
1 parent a4618a9 commit e8782d6
Show file tree
Hide file tree
Showing 11 changed files with 1,593 additions and 137 deletions.
9 changes: 9 additions & 0 deletions .github_application
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,14 @@
"repo": "your-repo-name"
},
"org": "octodemo"
},
"test-ghes": {
"applicationId": 123456,
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\nk3y_g03s_her3\n-----END RSA PRIVATE KEY-----\n",
"repo": {
"owner": "your-org-name",
"repo": "your-repo-name"
},
"org": "octodemo"
}
}
20 changes: 20 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"configurations": [
{
"args": [
"--timeout",
"999999",
"--colors",
"${workspaceFolder}/lib/**.test.js"
],
"internalConsoleOptions": "openOnSessionStart",
"name": "Mocha Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
}
]
}
37 changes: 19 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ Why would you want to do this? Well the `GITHUB_TOKEN` whilst having an expiry,
events that prevent downstream GitHub Actions workflow from triggering. This prevents recursive loops from workflows, but
there are a number of valid types of workflows that may require or desire triggering downstream GitHub Actions Workflows.

The existing way to work around this today is to use a Personal Access Token, but these tokens are tied to a user and
generally are over priviledged for the tasks at hand, increasing the risk if they get exposed and are not time limited
The existing way to work around this today is to use a Personal Access Token, but these tokens are tied to a user and
generally are over priviledged for the tasks at hand, increasing the risk if they get exposed and are not time limited
like the `GITHUB_TOKEN`.

This is where a GitHub Application access token can really help out. The benefits of GitHub Applications is that you can
restrict/scope the access of the token considerably more than what can be achieved using a Personal Access Token. The
This is where a GitHub Application access token can really help out. The benefits of GitHub Applications is that you can
restrict/scope the access of the token considerably more than what can be achieved using a Personal Access Token. The
access token from the GitHub Application is also time limited, expiring after an hour from being issued, providing some
more protection against any leaking of credentials from a Workflow.
more protection against any leaking of credentials from a Workflow.


## Usage
To use this action you first need a GitHub Application created so that you can request temporary credentials on behalf
To use this action you first need a GitHub Application created so that you can request temporary credentials on behalf
of the application inside your workflows.

__Requirements:__
Expand All @@ -28,7 +28,7 @@ __Requirements:__


### Creating a GitHub Application
You will need to have a GitHub Application that is scoped with the necessary permissions for the token that you want to
You will need to have a GitHub Application that is scoped with the necessary permissions for the token that you want to
retrieve at runtime.

To create a GitHub Application you can follow the steps available at https://docs.github.com/en/developers/apps/creating-a-github-app
Expand All @@ -43,39 +43,40 @@ The important configuration details for the application are:
* `Where can this GitHub App be installed?` should be scoped to your desired audience (the current account, or any account)

Once the application has been created you will be taken to the `General` settings page for the new application.
The GitHub Application will be issued an `App ID` which you can see in the `About` section, take note of this for later
The GitHub Application will be issued an `App ID` which you can see in the `About` section, take note of this for later
use in the Actions workflow.

On the `General` settings page for the application, at the bottom there is a `Private keys` section that you can use to
On the `General` settings page for the application, at the bottom there is a `Private keys` section that you can use to
generate a private key that can be utilized to authenticate as the application.
Generate a new private key and store the information for later use.

_Note: the private keys can and should be rotated periodically to limit the risks of them being exposed in use._


### Install the GitHub Application
Once you have the GitHub Application defined, you will need to install the application on the target organization or repository/
repositories that you want it to have access to. These will be any repositories that you want to gather information
Once you have the GitHub Application defined, you will need to install the application on the target organization or repository/
repositories that you want it to have access to. These will be any repositories that you want to gather information
from or want the application to modify as per the scopes that were defined when the application was installed.

_Note: The GitHub Application will need to be installed on the organization and or repository that you are executing
_Note: The GitHub Application will need to be installed on the organization and or repository that you are executing
the GitHub Actions workflow from, as the implementation requires this to be able to generate the access tokens_.


### Using the GitHub Action in a Workflow

To use the action in a worklow, it is recommended that you store the GitHub Application Private key in GitHub Secrets.
To use the action in a worklow, it is recommended that you store the GitHub Application Private key in GitHub Secrets.
This can be done at a repository or organization level (provided that the actions workflow has access to the secret).

When storing the Private key, you can store the raw PEM encoded certificate contents that the GitHub Application
generates for you or Base64 encode it in the secret.
When storing the Private key, you can store the raw PEM encoded certificate contents that the GitHub Application
generates for you or Base64 encode it in the secret.

#### Parameters

* `application_id`: The GitHub Application ID that you wil be getting the access token for
* `application_private_key`: A private key generated for the GitHub Application so that you can authenticate (PEM format or base64 encoded)
* `permissions`: The optional limited permissions to request, specifying this allows you to request a subset of the permissions for the underlying GitHub Application. Defaults to all permissions available to the GitHub Application when not specified. Must be provided in a comma separated list of token permissions e.g. `issues:read, secrets:write, packages:read`
* `organization`: An optional organization name if the GitHub Application is installed at the Organization level (instead of the repository).
* `github_api_base_url`: An optional URl to the GitHub API, this will be read and loaded from the runner environment by default, but you might be bridging access to a secondary GHES instance or from GHES to GHEC, you can utilize this to make sure the Octokit library is talking to the right GitHub instance.

#### Examples
Get a token with all the permissions of the GitHub Application:
Expand All @@ -92,7 +93,7 @@ jobs:
with:
application_id: ${{ secrets.APPLICATION_ID }}
application_private_key: ${{ secrets.APPLICATION_PRIVATE_KEY }}

- name: Use Application Token to create a release
uses: actions/create-release@v1
env:
Expand All @@ -116,7 +117,7 @@ jobs:
application_id: ${{ secrets.APPLICATION_ID }}
application_private_key: ${{ secrets.APPLICATION_PRIVATE_KEY }}
permissions: "actions:write"
- name: Use Application Token to create a release
uses: actions/create-release@v1
env:
Expand All @@ -140,7 +141,7 @@ jobs:
application_id: ${{ secrets.APPLICATION_ID }}
application_private_key: ${{ secrets.APPLICATION_PRIVATE_KEY }}
organization: octodemo
- name: Use Application Token to create a release
uses: actions/create-release@v1
env:
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ inputs:
description: The GitHub Organization to get the application installation for, if not specified will use the current repository instead
required: false

github_api_base_url:
description: The GitHub API base URL to use, no needed it working within the same GitHub instance as the workflow as it will get picked up from the environment
required: false

outputs:
token:
description: A valid token representing the Application that can be used to access what the Application has been scoped to access.
Expand Down
6 changes: 3 additions & 3 deletions dist/index.js

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ async function run() {
try {
const privateKey = getRequiredInputValue('application_private_key')
, applicationId = getRequiredInputValue('application_id')
, githubApiBaseUrl = core.getInput('github_api_base_url') || process.env['GITHUB_API_URL'] || 'https://api.github.com'
;
app = await githubApplication.create(privateKey, applicationId);
app = await githubApplication.create(privateKey, applicationId, githubApiBaseUrl);
} catch(err) {
fail(err, 'Failed to initialize GitHub Application connection using provided id and private key');
}
Expand Down Expand Up @@ -46,7 +47,7 @@ async function run() {
fail(null, `GitHub Application is not installed on repository: ${repository}`);
}
}

if (installationId) {
const permissions = {};
// Build up the list of requested permissions
Expand All @@ -67,7 +68,7 @@ async function run() {
core.info(JSON.stringify(accessToken));
core.info(`Successfully generated an access token for application.`)
} else {
fail('No installation of the specified GitHub application was abel to be retrieved');
fail('No installation of the specified GitHub application was able to be retrieved.');
}
} catch (err) {
fail(err);
Expand All @@ -78,7 +79,7 @@ run();

function fail(err, message) {
core.error(err);

if (message) {
core.setFailed(message);
} else {
Expand Down
22 changes: 15 additions & 7 deletions lib/github-application.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ const jwt = require('jsonwebtoken')
, PrivateKey = require('./private-key')
;

module.exports.create = (privateKey, applicationId, timeout) => {
const app = new GitHubApplication(privateKey, applicationId);
module.exports.create = (privateKey, applicationId, baseApiUrl, timeout) => {
const app = new GitHubApplication(privateKey, applicationId, baseApiUrl);

return app.connect(timeout)
.then(() => {
Expand All @@ -14,12 +14,13 @@ module.exports.create = (privateKey, applicationId, timeout) => {

class GitHubApplication {

constructor(privateKey, applicationId) {
constructor(privateKey, applicationId, baseApiUrl) {
this._config = {
privateKey: new PrivateKey(_validateVariableValue('privateKey', privateKey)),
id: _validateVariableValue('applicationId', applicationId),
};

this._githubApiUrl = baseApiUrl;
this._client = null;
}

Expand All @@ -36,14 +37,19 @@ class GitHubApplication {
};

const token = jwt.sign(payload, this.privateKey, {algorithm: 'RS256'});
this._client = new github.getOctokit(token);

const octokitOptions = {};
if (this.githubApiBaseUrl) {
octokitOptions.baseUrl = this.githubApiBaseUrl;
}
this._client = new github.getOctokit(token, octokitOptions);

return this.client.request('GET /app', {
mediaType: {
previews: ['machine-man']
}
}).catch(err => {
throw new Error(`Failed to connect as application; ${err.message}`);
throw new Error(`Failed to connect as application; status code: ${err.status}\n${err.message}`);
}).then(resp => {
if (resp.status === 200) {
// Store the metadata for debug purposes
Expand All @@ -56,6 +62,10 @@ class GitHubApplication {
});
}

get githubApiBaseUrl() {
return this._githubApiUrl;
}

get metadata() {
return this._metadata;
}
Expand Down Expand Up @@ -95,8 +105,6 @@ class GitHubApplication {
});
}

//TODO can get other types of app installations too at org and enterprise

getRepositoryInstallation(owner, repo) {
return this.client.rest.apps.getRepoInstallation({
owner: owner,
Expand Down
Loading

0 comments on commit e8782d6

Please sign in to comment.