From 623c5f289f74f353a079b4898b95cf38255d3b78 Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 13:18:17 +0530 Subject: [PATCH 01/17] DEVOPS-265 update python image from 3.10 to 3.11 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 8fc96b9..3abe31c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Container image that runs your code -FROM python:3.10-slim-bullseye +FROM python:3.11-slim-bullseye WORKDIR /app # Copies your code file from your action repository to the filesystem path `/` of the container From ca8b88ce79c6060fa57d99b8a56d7690cd2eaa4d Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 13:19:47 +0530 Subject: [PATCH 02/17] DEVOPS-265 incorporated new variables to action workfow updated --- action.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/action.yml b/action.yml index 4598991..8af6b7a 100644 --- a/action.yml +++ b/action.yml @@ -10,12 +10,19 @@ inputs: required: true github_app_private_key: description: "Github App private key" - github_account_type: - description: "Github account user or organization" + required: true + owner: + description: "The owner of the GitHub App installation. If empty, defaults to the current repository owner" + required: false + repositories: + description: "Comma-separated list of repositories to grant access to" + required: false + runs: using: 'docker' image: 'Dockerfile' args: - ${{ inputs.github_app_id }} - ${{ inputs.github_app_private_key }} - - ${{ inputs.github_account_type }} + - ${{ inputs.owner }} + - ${{ inputs.repositories }} From 90adc63ae831c3381d70fb71100c9e9b14f2ce9c Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 13:21:03 +0530 Subject: [PATCH 03/17] DEVOPS-265 Updated entrypoint script to pass new vars to python --- entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entrypoint.sh b/entrypoint.sh index a352387..48d18f6 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -4,6 +4,6 @@ cd /app && pipenv install --skip-lock # run python program to generate token -pipenv run python3 /app/generate_jwt.py --github_app_id "$1" --github_app_private_key "$2" --github_account_type "$3" +pipenv run python3 /app/generate_jwt.py --github_app_id "$1" --github_app_private_key "$2" --owner "$3" --repositories "$4" From 5f17819639208cdfe5914eba1a814fa56d304e9e Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 13:21:52 +0530 Subject: [PATCH 04/17] DEVOPS-265 updated readme --- README.md | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 4bcc67a..751f6a9 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,26 @@ Once your GitHub App is installed on an account, you can make it authenticate as This allows the app to access resources owned by that installation, as long as the app was granted the necessary repository access and permissions. API requests made by an app installation are attributed to the app. -:pushpin: This action will help in creating github app installation token for both **user accounts** and **Github organizations** +:pushpin: This action will help in creating GitHub app installation token for both **user accounts** and **Github organizations** + +> [!IMPORTANT] +> An installation access token expires after 1 hour. Please find suitable alternative approaches if you have long-running processes.. # Parameters of action -| Parameter name | Description | Required | -|----------------|-------------|--------------------| -| github_app_private_key | Github App Private key | :heavy_check_mark: | -| github_app_id | Your GitHub App ID | :heavy_check_mark: | -| github_account_type | Github account whether `user` account or `organization` | :heavy_check_mark: | +| Parameter name | Description | Required | +|----------------|----------------------------------------------------------------------------------------------------------------|-------------------| +| github_app_private_key | Github App Private key | :heavy_check_mark: | +| github_app_id | Your GitHub App ID | :heavy_check_mark: | +| owner | Github account owner name. if not specified takes owner of current repository where action is ran | ❌ | +| repositories | List of github repositores to generte token for. if not specified takes current repository where action is ran. | ❌ | + +* Store your `Github App Id` and `Github App Private key` as github secret and pass the secret names as inputs for action. + +* ❌ 👉 Means optional values -* Store your `Github App Id` and `Github App Private key` as github secret and pass the secret names as inuts for action. +> [!NOTE] +> If the owner is set but repositories are empty, access will include all repositories for that owner. +> If both the owner and repositories are empty, access will be limited to the current repository. # What's New @@ -27,11 +37,14 @@ Please refer to the [release](https://github.com/githubofkrishnadhas/github-acce # Your GitHub App ID - interger value github_app_id: 1234567 - # Github App Private key + # GitHub App Private key github_app_private_key : '' - # Gituhb account type `user` or `organization` only - github_account_type: '' + # GitHub account Owner name - Optional + owner: '' + + # GitHub repositories names seperated by comma if more than 1 - optional + repositories: '' ``` # output From 6ab7dbba50446c296ef13ed804b901d041bbd885 Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 13:35:22 +0530 Subject: [PATCH 05/17] DEVOPS-265 generate jwt and access token updated py filr --- generate_jwt.py | 112 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 83 insertions(+), 29 deletions(-) diff --git a/generate_jwt.py b/generate_jwt.py index 3bdfbe5..a7984d4 100644 --- a/generate_jwt.py +++ b/generate_jwt.py @@ -2,6 +2,7 @@ import time import argparse import os +import sys import requests from dotenv import load_dotenv @@ -36,54 +37,84 @@ def create_jwt(private_key, app_id): # print(f"JWT token created successfully") return encoded_jwt -def get_app_installation_id(jwt:str, github_account_type:str): +def get_app_installation_id(jwt:str, owner:str, repositories:str): """ returns github app installation id on user and org accounts + github app can be installed on scope of org providing permission to all repos or hand picked ones. + in either case github app is installed on organization has a unique installation id. same for user accounts as well. :param jwt: :return: """ - GITHUB_REPOSITORY = os.getenv('GITHUB_REPOSITORY') - GITHUB_REPOSITORY_OWNER = os.getenv('GITHUB_REPOSITORY_OWNER') - org_url = f'https://api.github.com/repos/{GITHUB_REPOSITORY}/installation' - user_url = f'https://api.github.com/users/{GITHUB_REPOSITORY_OWNER}/installation' - if github_account_type == 'user': - url = user_url - else: - url = org_url + results = [] + # Pagination query params + per_page = 50 + page = 1 + # Api call url + url = f'https://api.github.com/app/installations' + # header headers = { "Accept": "application/vnd.github+json", "Authorization": f"Bearer {jwt}", "X-GitHub-Api-Version": "2022-11-28" - } - response = requests.get(url= url, headers=headers) + } + while True: + params = {'per_page': per_page, 'page': page} + response = requests.get(url= url, headers=headers, params=params) - if response.status_code == 200: - print(f'Okay. Received proper response.Got installation id') - response_json = response.json() - elif response.status_code == 301: - print(f'Moved permanently. Cant get a response') - else: - print(f'Resource Not Found!') + if response.status_code == 200: + response_json = response.json() + elif response.status_code == 301: + print(f'Moved permanently. Cant get a response') + else: + print(f'Resource Not Found!') + + # Add the current page of results to the results list + results.extend(response_json) - # Installation id of github app - installation_id = response_json['id'] + # Check if there are more results + if len(response_json) < per_page: + break + page += 1 + + # Iterating through all installations + for result in results: + result_owner = result['account']['login'] + if owner == result_owner: + installation_id = result['id'] + print(f'Installation id is {installation_id} - {owner} {result["target_type"]} ') + print(f'Okay. Received proper response.Got installation id') return installation_id -def generate_token_by_post_call(installation_id:int, jwt:str): + +def generate_token_by_post_call(installation_id:int, jwt:str, repositories: str): """ create a app installation token by doing a rest api post call with permissions for application :return: """ + input_repositories = [item.strip() for item in repositories.split(',')] url = f'https://api.github.com/app/installations/{installation_id}/access_tokens' headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {jwt}", + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {jwt}", "X-GitHub-Api-Version": "2022-11-28" } - response = requests.post(url=url, headers=headers) + data = { + "repositories": input_repositories + } + response = requests.post(url=url, headers=headers, json=data) response_json = response.json() if response.status_code == 201: - print(f'Github app installation token generate succcessfully, expires at {response_json["expires_at"]}') + print(f'Github app installation token generated successfully for scope {repositories} - expires at {response_json["expires_at"]}') + elif response.status_code == 401: + print(f'Authentication is required') + elif response.status_code == 403: + print(f'Forbidden action') + elif response.status_code == 404: + print(f'Resource not found') + else: + print(f"Validation failed, {response_json['message']} or the endpoint has been spammed. Provided input repositories are {repositories}") + print(f'Aborting GitHub App installation token generation') + sys.exit(1) # Exiting as rest api cant process the req 422 error code os.environ['GH_APP_TOKEN'] = response_json['token'] # Write the token to GITHUB_ENV to be available in subsequent steps with open(os.environ['GITHUB_ENV'], 'a') as fh: @@ -97,18 +128,41 @@ def main(): load_dotenv() parser = argparse.ArgumentParser(description="Create JWT for GitHub App authentication") parser.add_argument("--github_app_private_key",required=True, type=str, help="Github App Private key") - parser.add_argument("--github_account_type",required=True, choices=['user','organization'], help="Github account whether user account ot github org") parser.add_argument("--github_app_id",required=True, type=str, help="Your GitHub App ID") + parser.add_argument("--owner", required=False, type=str, help="Target github owner") + parser.add_argument("--repositories", required=False, type=str, help="Repos to which token will be generated, for multiple seperate by comma") args = parser.parse_args() private_key = args.github_app_private_key app_id = args.github_app_id - github_account_type = args.github_account_type + + # Handle --owner argument + if args.owner is None: + print("No owner specified. Considering current repository owner as Owner") + owner = os.getenv('GITHUB_REPOSITORY_OWNER') # Assign a default or dynamically determined value + print(f"Taking owner as {owner}") + else: + owner = args.owner + print(f"Owner: {owner}") + + # Handle --repositories argument + if args.repositories is None: + if args.owner is not None: + # If owner is provided but repositories are not + print(f"No repositories specified for the provided owner: {owner}.") + repositories = 'all' # You can decide on a default behavior here, e.g., an empty list or a specific action + print(f"Selecting all repos under owner {owner}") + else: + print("No repositories specified. Considering current repository as repositories") + repositories = os.getenv('GITHUB_REPOSITORY') # Assign a default or dynamically determined value + else: + repositories = args.repositories + print(f"Will generate tokens for {repositories}") # function call jwt = create_jwt(private_key=private_key, app_id=app_id) - installation_id = get_app_installation_id(jwt=jwt, github_account_type=github_account_type) - generate_token_by_post_call(installation_id=installation_id, jwt=jwt) + installation_id = get_app_installation_id(jwt=jwt, owner=owner, repositories=repositories) + generate_token_by_post_call(installation_id=installation_id, jwt=jwt, repositories=repositories) From a4093240322d2a5186c0a2f7a5974388f1bc9668 Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 13:39:05 +0530 Subject: [PATCH 06/17] DEVOPS-265 added codeql and dependabot for action repo --- .github/dependabot.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ca4f0e3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,25 @@ +version: 2 +updates: +- package-ecosystem: "pip" + directory: / + schedule: + interval: weekly + # Assignees to set on pull requests + assignees: + - "githubofkrishnadhas" + # prefix specifies a prefix for all commit messages. When you specify a prefix for commit messages, + # GitHub will automatically add a colon between the defined prefix and the commit message provided the + # defined prefix ends with a letter, number, closing parenthesis, or closing bracket. + commit-message: + prefix: "dependabot python package" + # Raise pull requests for version updates to pip against the `main` branch + target-branch: "main" + # Labels on pull requests for version updates only + labels: + - "pip dependencies" + # Increase the version requirements for Composer only when required + versioning-strategy: increase-if-necessary + # Dependabot opens a maximum of five pull requests for version updates. Once there are five open pull requests from Dependabot, + # Dependabot will not open any new requests until some of those open requests are merged or closed. + # Use open-pull-requests-limit to change this limit. + open-pull-requests-limit: 10 \ No newline at end of file From bc1287eb49fe3d948e8261e75659d6110dcdcd8b Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 14:42:27 +0530 Subject: [PATCH 07/17] DEVOPS-265 fix python env issue --- README.md | 6 +++--- generate_jwt.py | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 751f6a9..4d3dee2 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ The token generated will be available as a Environment variable `GH_APP_TOKEN` w # References -[generating-an-installation-access-token](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app#generating-an-installation-access-token) -[get-a-user-installation-for-the-authenticated-app](https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-a-user-installation-for-the-authenticated-app) -[get-a-repository-installation-for-the-authenticated-app](https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app) +* [generating-an-installation-access-token](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app#generating-an-installation-access-token) +* [get-a-user-installation-for-the-authenticated-app](https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-a-user-installation-for-the-authenticated-app) +* [get-a-repository-installation-for-the-authenticated-app](https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app) All the above API's uses [JWT](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app#authenticating-as-a-github-app) as access token. diff --git a/generate_jwt.py b/generate_jwt.py index a7984d4..d57e491 100644 --- a/generate_jwt.py +++ b/generate_jwt.py @@ -15,7 +15,7 @@ def create_jwt(private_key, app_id): """ # Open PEM # with open(private_key, 'rb') as pem_file: - # signing_key = jwk_from_pem(pem_file.read()) + # signing_key = jwk_from_pem(pem_file.read()) signing_key = jwk_from_pem(private_key.encode('utf-8')) payload = { @@ -153,8 +153,10 @@ def main(): repositories = 'all' # You can decide on a default behavior here, e.g., an empty list or a specific action print(f"Selecting all repos under owner {owner}") else: - print("No repositories specified. Considering current repository as repositories") - repositories = os.getenv('GITHUB_REPOSITORY') # Assign a default or dynamically determined value + print("No repositories & owner specified. Considering current repository as repositories") + os_repositories = os.getenv('GITHUB_REPOSITORY') # Assign a default or dynamically determined value + parts = os_repositories.split('/') + repositories = parts[1] else: repositories = args.repositories print(f"Will generate tokens for {repositories}") From 67d26e16a6cea2f4fe9dbfa372a4a77e7388ac61 Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 15:02:21 +0530 Subject: [PATCH 08/17] generate token fix --- generate_jwt.py | 124 +++++++++++++++++++++++------------------------- 1 file changed, 60 insertions(+), 64 deletions(-) diff --git a/generate_jwt.py b/generate_jwt.py index d57e491..8159548 100644 --- a/generate_jwt.py +++ b/generate_jwt.py @@ -8,94 +8,86 @@ def create_jwt(private_key, app_id): """ - function to create JWT from GitHub app id and pvt key - :param private_key: - :param app_id: - :return: + Function to create JWT from GitHub app id and private key. + :param private_key: Path to the PEM file containing the private key. + :param app_id: GitHub App ID. + :return: Encoded JWT. """ # Open PEM - # with open(private_key, 'rb') as pem_file: - # signing_key = jwk_from_pem(pem_file.read()) - signing_key = jwk_from_pem(private_key.encode('utf-8')) + with open(private_key, 'rb') as pem_file: + signing_key = jwk_from_pem(pem_file.read()) payload = { - # Issued at time - 'iat': int(time.time()), - # JWT expiration time (10 minutes maximum) - 'exp': int(time.time()) + 600, - # GitHub App's identifier - 'iss': app_id + 'iat': int(time.time()), # Issued at time + 'exp': int(time.time()) + 600, # JWT expiration time (10 minutes maximum) + 'iss': app_id # GitHub App's identifier } # Create JWT jwt_instance = JWT() encoded_jwt = jwt_instance.encode(payload, signing_key, alg='RS256') - # Set JWT as environment variable - # os.environ["GITHUB_JWT"] = encoded_jwt - - # print(f"JWT token created successfully") return encoded_jwt -def get_app_installation_id(jwt:str, owner:str, repositories:str): +def get_app_installation_id(jwt: str, owner: str): """ - returns github app installation id on user and org accounts - github app can be installed on scope of org providing permission to all repos or hand picked ones. - in either case github app is installed on organization has a unique installation id. same for user accounts as well. - :param jwt: - :return: + Get GitHub app installation ID on user and org accounts. + :param jwt: JWT token. + :param owner: GitHub owner (user or organization). + :return: Installation ID. """ results = [] - # Pagination query params per_page = 50 page = 1 - # Api call url - url = f'https://api.github.com/app/installations' - # header + url = 'https://api.github.com/app/installations' headers = { "Accept": "application/vnd.github+json", "Authorization": f"Bearer {jwt}", "X-GitHub-Api-Version": "2022-11-28" - } + } + while True: params = {'per_page': per_page, 'page': page} - response = requests.get(url= url, headers=headers, params=params) + response = requests.get(url=url, headers=headers, params=params) if response.status_code == 200: response_json = response.json() elif response.status_code == 301: - print(f'Moved permanently. Cant get a response') + print('Moved permanently. Cannot get a response.') + sys.exit(1) else: - print(f'Resource Not Found!') + print('Resource not found!') + sys.exit(1) - # Add the current page of results to the results list results.extend(response_json) - # Check if there are more results if len(response_json) < per_page: break page += 1 - # Iterating through all installations for result in results: result_owner = result['account']['login'] if owner == result_owner: installation_id = result['id'] - print(f'Installation id is {installation_id} - {owner} {result["target_type"]} ') - print(f'Okay. Received proper response.Got installation id') - return installation_id + print(f'Installation ID is {installation_id} - {owner} {result["target_type"]}') + return installation_id + print(f'Installation ID for owner {owner} not found.') + sys.exit(1) -def generate_token_by_post_call(installation_id:int, jwt:str, repositories: str): +def generate_token_by_post_call(installation_id: int, jwt: str, repositories: str): """ - create a app installation token by doing a rest api post call with permissions for application - :return: + Create an app installation token by making a POST request with permissions for the application. + :param installation_id: Installation ID of the GitHub App. + :param jwt: JWT token. + :param repositories: Comma-separated list of repositories. """ input_repositories = [item.strip() for item in repositories.split(',')] + print(f'Input repos - {input_repositories}') url = f'https://api.github.com/app/installations/{installation_id}/access_tokens' headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {jwt}", + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {jwt}", "X-GitHub-Api-Version": "2022-11-28" } data = { @@ -103,34 +95,34 @@ def generate_token_by_post_call(installation_id:int, jwt:str, repositories: str) } response = requests.post(url=url, headers=headers, json=data) response_json = response.json() + if response.status_code == 201: - print(f'Github app installation token generated successfully for scope {repositories} - expires at {response_json["expires_at"]}') + print(f'GitHub app installation token generated successfully for scope {repositories} - expires at {response_json["expires_at"]}') elif response.status_code == 401: - print(f'Authentication is required') + print('Authentication is required') elif response.status_code == 403: - print(f'Forbidden action') + print('Forbidden action') elif response.status_code == 404: - print(f'Resource not found') + print('Resource not found') else: - print(f"Validation failed, {response_json['message']} or the endpoint has been spammed. Provided input repositories are {repositories}") + print(f"Validation failed, {response_json.get('message', 'Unknown error')} or the endpoint has been spammed.") print(f'Aborting GitHub App installation token generation') - sys.exit(1) # Exiting as rest api cant process the req 422 error code + sys.exit(1) + os.environ['GH_APP_TOKEN'] = response_json['token'] - # Write the token to GITHUB_ENV to be available in subsequent steps with open(os.environ['GITHUB_ENV'], 'a') as fh: fh.write(f"GH_APP_TOKEN={response_json['token']}\n") def main(): """ - to test the code - :return: + Main function to run the script. """ load_dotenv() parser = argparse.ArgumentParser(description="Create JWT for GitHub App authentication") - parser.add_argument("--github_app_private_key",required=True, type=str, help="Github App Private key") - parser.add_argument("--github_app_id",required=True, type=str, help="Your GitHub App ID") - parser.add_argument("--owner", required=False, type=str, help="Target github owner") - parser.add_argument("--repositories", required=False, type=str, help="Repos to which token will be generated, for multiple seperate by comma") + parser.add_argument("--github_app_private_key", required=True, type=str, help="GitHub App Private key") + parser.add_argument("--github_app_id", required=True, type=str, help="Your GitHub App ID") + parser.add_argument("--owner", required=False, type=str, help="Target GitHub owner") + parser.add_argument("--repositories", required=False, type=str, help="Repos to which token will be generated, for multiple separate by comma") args = parser.parse_args() private_key = args.github_app_private_key @@ -138,7 +130,7 @@ def main(): # Handle --owner argument if args.owner is None: - print("No owner specified. Considering current repository owner as Owner") + print("No owner specified. Considering current repository owner as Owner.") owner = os.getenv('GITHUB_REPOSITORY_OWNER') # Assign a default or dynamically determined value print(f"Taking owner as {owner}") else: @@ -153,20 +145,24 @@ def main(): repositories = 'all' # You can decide on a default behavior here, e.g., an empty list or a specific action print(f"Selecting all repos under owner {owner}") else: - print("No repositories & owner specified. Considering current repository as repositories") - os_repositories = os.getenv('GITHUB_REPOSITORY') # Assign a default or dynamically determined value - parts = os_repositories.split('/') - repositories = parts[1] + # If neither owner nor repositories are specified + print("No repositories & owner specified. Considering current repository as repositories.") + os_repositories = os.getenv('GITHUB_REPOSITORY') # Get the current repository + if os_repositories: + parts = os_repositories.split('/') + repositories = parts[1] + print(f"Taking repositories as {repositories}") + else: + print("Current repository information is not available.") + sys.exit(1) else: repositories = args.repositories print(f"Will generate tokens for {repositories}") - # function call + # Function calls jwt = create_jwt(private_key=private_key, app_id=app_id) - installation_id = get_app_installation_id(jwt=jwt, owner=owner, repositories=repositories) + installation_id = get_app_installation_id(jwt=jwt, owner=owner) generate_token_by_post_call(installation_id=installation_id, jwt=jwt, repositories=repositories) - - if __name__ == "__main__": main() From df9eb90e25e168dcd9df250523afeee8c3185f96 Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 15:04:00 +0530 Subject: [PATCH 09/17] generate token fix --- generate_jwt.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/generate_jwt.py b/generate_jwt.py index 8159548..8ba9f95 100644 --- a/generate_jwt.py +++ b/generate_jwt.py @@ -14,8 +14,10 @@ def create_jwt(private_key, app_id): :return: Encoded JWT. """ # Open PEM - with open(private_key, 'rb') as pem_file: - signing_key = jwk_from_pem(pem_file.read()) + # with open(private_key, 'rb') as pem_file: + # signing_key = jwk_from_pem(pem_file.read()) + signing_key = jwk_from_pem(private_key.encode('utf-8')) + payload = { 'iat': int(time.time()), # Issued at time From f6de3201031b1973e6c99756151915fdfa1b6975 Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 15:09:05 +0530 Subject: [PATCH 10/17] generate token fix --- entrypoint.sh | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 48d18f6..552553c 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -3,7 +3,28 @@ # installng pipenv and creating pipenv venv cd /app && pipenv install --skip-lock -# run python program to generate token -pipenv run python3 /app/generate_jwt.py --github_app_id "$1" --github_app_private_key "$2" --owner "$3" --repositories "$4" +# Capture arguments +GITHUB_APP_ID="$1" +GITHUB_APP_PRIVATE_KEY="$2" +OWNER="$3" +REPOSITORIES="$4" + +# Build the command based on available parameters +CMD="pipenv run python3 /app/generate_jwt.py --github_app_id \"$GITHUB_APP_ID\" --github_app_private_key \"$GITHUB_APP_PRIVATE_KEY\"" + +if [ -n "$OWNER" ]; then + CMD="$CMD --owner \"$OWNER\"" +fi + +if [ -n "$REPOSITORIES" ]; then + CMD="$CMD --repositories \"$REPOSITORIES\"" +fi + +# Print and execute the command +echo "Executing command: $CMD" +eval "$CMD" + +## run python program to generate token +#pipenv run python3 /app/generate_jwt.py --github_app_id "$1" --github_app_private_key "$2" --owner "$3" --repositories "$4" From 4a36cc15789e1d567da6f90754854094e84c51c9 Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 15:25:56 +0530 Subject: [PATCH 11/17] generate token fix and entry point eval --- entrypoint.sh | 2 -- generate_jwt.py | 13 +++++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 552553c..4c69514 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -24,7 +24,5 @@ fi echo "Executing command: $CMD" eval "$CMD" -## run python program to generate token -#pipenv run python3 /app/generate_jwt.py --github_app_id "$1" --github_app_private_key "$2" --owner "$3" --repositories "$4" diff --git a/generate_jwt.py b/generate_jwt.py index 8ba9f95..d94f3bd 100644 --- a/generate_jwt.py +++ b/generate_jwt.py @@ -92,10 +92,15 @@ def generate_token_by_post_call(installation_id: int, jwt: str, repositories: st "Authorization": f"Bearer {jwt}", "X-GitHub-Api-Version": "2022-11-28" } - data = { - "repositories": input_repositories - } - response = requests.post(url=url, headers=headers, json=data) + + if input_repositories == ['all']: + response = requests.post(url=url, headers=headers) + else: + data = { + "repositories": input_repositories + } + response = requests.post(url=url, headers=headers, json=data) + # response = requests.post(url=url, headers=headers, json=data) response_json = response.json() if response.status_code == 201: From 88bdbc87366fbc3f29c345c81a7b8616c0469c57 Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 15:54:36 +0530 Subject: [PATCH 12/17] remove commented items --- generate_jwt.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/generate_jwt.py b/generate_jwt.py index d94f3bd..4057da6 100644 --- a/generate_jwt.py +++ b/generate_jwt.py @@ -85,7 +85,6 @@ def generate_token_by_post_call(installation_id: int, jwt: str, repositories: st :param repositories: Comma-separated list of repositories. """ input_repositories = [item.strip() for item in repositories.split(',')] - print(f'Input repos - {input_repositories}') url = f'https://api.github.com/app/installations/{installation_id}/access_tokens' headers = { "Accept": "application/vnd.github+json", @@ -100,7 +99,7 @@ def generate_token_by_post_call(installation_id: int, jwt: str, repositories: st "repositories": input_repositories } response = requests.post(url=url, headers=headers, json=data) - # response = requests.post(url=url, headers=headers, json=data) + response_json = response.json() if response.status_code == 201: From f982fc621796a5a4f479599bc8bf30358a645869 Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 17:16:01 +0530 Subject: [PATCH 13/17] adding token to github output to be used as steps.app-token.outputs.token --- generate_jwt.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/generate_jwt.py b/generate_jwt.py index 4057da6..2b4b2c2 100644 --- a/generate_jwt.py +++ b/generate_jwt.py @@ -115,9 +115,13 @@ def generate_token_by_post_call(installation_id: int, jwt: str, repositories: st print(f'Aborting GitHub App installation token generation') sys.exit(1) - os.environ['GH_APP_TOKEN'] = response_json['token'] - with open(os.environ['GITHUB_ENV'], 'a') as fh: - fh.write(f"GH_APP_TOKEN={response_json['token']}\n") + token = response_json['token'] + print(f"Token is {token}") + # with open(os.environ['GITHUB_ENV'], 'a') as fh: + # fh.write(f"GH_APP_TOKEN={response_json['token']}\n") + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: + fh.write(f'{token}={response_json["token"]}\n') + def main(): """ From d2a8d6ee9ef5be55a9e0890d5c24e712b5a88c65 Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 17:38:09 +0530 Subject: [PATCH 14/17] slight modification on output token --- generate_jwt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generate_jwt.py b/generate_jwt.py index 2b4b2c2..0739de3 100644 --- a/generate_jwt.py +++ b/generate_jwt.py @@ -116,11 +116,11 @@ def generate_token_by_post_call(installation_id: int, jwt: str, repositories: st sys.exit(1) token = response_json['token'] - print(f"Token is {token}") + # print(f"Token is {token}") # with open(os.environ['GITHUB_ENV'], 'a') as fh: # fh.write(f"GH_APP_TOKEN={response_json['token']}\n") with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: - fh.write(f'{token}={response_json["token"]}\n') + fh.write(f'{token}={token}\n') def main(): From b56741d5eb0be64ca6adba2dd6185af84e95c0bc Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 20:43:56 +0530 Subject: [PATCH 15/17] token name issue fixed --- generate_jwt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate_jwt.py b/generate_jwt.py index 0739de3..1108c39 100644 --- a/generate_jwt.py +++ b/generate_jwt.py @@ -120,7 +120,7 @@ def generate_token_by_post_call(installation_id: int, jwt: str, repositories: st # with open(os.environ['GITHUB_ENV'], 'a') as fh: # fh.write(f"GH_APP_TOKEN={response_json['token']}\n") with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: - fh.write(f'{token}={token}\n') + fh.write(f'token={token}\n') def main(): From 0c7854c811619f57a954b10ec7021514d9b751cd Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 21:18:43 +0530 Subject: [PATCH 16/17] githubapp installation token set to GITHUB_OUTPUT instead of GITHUB_ENV allowing multiple token generations --- generate_jwt.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/generate_jwt.py b/generate_jwt.py index 1108c39..ad83c1b 100644 --- a/generate_jwt.py +++ b/generate_jwt.py @@ -115,10 +115,7 @@ def generate_token_by_post_call(installation_id: int, jwt: str, repositories: st print(f'Aborting GitHub App installation token generation') sys.exit(1) - token = response_json['token'] - # print(f"Token is {token}") - # with open(os.environ['GITHUB_ENV'], 'a') as fh: - # fh.write(f"GH_APP_TOKEN={response_json['token']}\n") + token = response_json['token'] # setting the output token as token inside github output with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: fh.write(f'token={token}\n') From 5ec4ec06b5b89d62e2703d1f5e1027e750bbc0fe Mon Sep 17 00:00:00 2001 From: githubofkrishnadhas Date: Sat, 27 Jul 2024 21:19:04 +0530 Subject: [PATCH 17/17] Updated README.md DEVOPS-265 --- README.md | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4d3dee2..ae5d732 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # github-access-using-githubapp -github-access-using-githubapp Once your GitHub App is installed on an account, you can make it authenticate as an app installation for API requests. This allows the app to access resources owned by that installation, as long as the app was granted the necessary repository access and permissions. @@ -30,9 +29,10 @@ API requests made by an app installation are attributed to the app. Please refer to the [release](https://github.com/githubofkrishnadhas/github-access-using-githubapp/releases) page for the latest release notes. -# Usage +# Usage ```commandline -- uses: githubofkrishnadhas/github-access-using-githubapp@v1 +- uses: githubofkrishnadhas/github-access-using-githubapp@v2 + id: token-generation with: # Your GitHub App ID - interger value github_app_id: 1234567 @@ -49,7 +49,91 @@ Please refer to the [release](https://github.com/githubofkrishnadhas/github-acce # output -The token generated will be available as a Environment variable `GH_APP_TOKEN` which can be used while running api calls +* The token generated will be available as a ${{ steps.token-generation.outputs.token }} which can be used in later stages as required + +# Example usages + +## Create a token for the current repository + +```commandline +uses: githubofkrishnadhas/github-access-using-githubapp@v2 + id: token-generation + with: + github_app_id: ${{ secrets.APP_ID }} + github_app_private_key : ${{ secrets.PRIVATE_KEY }} +``` +* To create a Token in the scope of current repository where action is run, you do not need to specify `owner` or `repositores` +* Assuming both GitHub App ID and Private key are present as github secrets with names `APP_ID` and `PRIVATE_KEY` +* You can substitute your secrets names with above +* The token generated will be available as a ${{ steps.token-generation.outputs.token }} which can be used in later stages as required + + +## Create a token for the current user or organization level + +```commandline +uses: githubofkrishnadhas/github-access-using-githubapp@v2 + id: token-generation + with: + github_app_id: ${{ secrets.APP_ID }} + github_app_private_key : ${{ secrets.PRIVATE_KEY }} + owner: 'github' +``` +* To create a Token in the scope of current user or organization where your Github app has access, you need only to specify `owner` +* Assuming both GitHub App ID and Private key are present as github secrets with names `APP_ID` and `PRIVATE_KEY` +* You can substitute your secrets names with above +* The token generated will be available as a ${{ steps.token-generation.outputs.token }} which can be used in later stages as required + + +## Create a token for a differnt user or organization scoped to specific repos + +```commandline +uses: githubofkrishnadhas/github-access-using-githubapp@v2 + id: token-generation + with: + github_app_id: ${{ secrets.APP_ID }} + github_app_private_key : ${{ secrets.PRIVATE_KEY }} + owner: 'github' + repositories: 'test1,test2,test3' +``` +* To create a Token in the scope of provided repositories and owner where your Github app has access you need only to specify `owner` and `repositories` +* The above will generate token which are scoped to repositores named `test1, test2, test3` on `github` org +* Assuming both GitHub App ID and Private key are present as github secrets with names `APP_ID` and `PRIVATE_KEY` +* You can substitute your secrets names with above +* The token generated will be available as a ${{ steps.token-generation.outputs.token }} which can be used in later stages as required + + +## Using the token generated with other actions + +```commandline +name: Clone Repository + +on: + workflow_dispatch: + +jobs: + clone: + runs-on: ubuntu-latest + + steps: + + - name: Token generator + uses: githubofkrishnadhas/github-access-using-githubapp@v2 + id: token-generation + with: + github_app_id: ${{ secrets.APP_ID }} + github_app_private_key : ${{ secrets.PRIVATE_KEY }} + + - name: Checkout Repository + uses: actions/checkout@v4 + with: + repository: 'devwithkrishna/azure-terraform-modules' + token: ${{ steps.token-generation.outputs.token }} + fetch-depth: 1 +``` +* The above workflow generates a github app installation access token using the action - `githubofkrishnadhas/github-access-using-githubapp@v2` +* The token generated will be available as a ${{ steps.token-generation.outputs.token }} which can be used in later stages as shown above +* The workflow is to clone a repository named `azure-terraform-modules` inside `devwithkrishna` organization + # References