mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 10:00:01 -03:30
* feat: 38589 GitHub App Authentication (#15807) * feat: 38589 GitHub App Authentication Allows both git@<personal-token> and x-access-token@<github-access-token> when authenticating using git. This allows GitHub App tokens to work without interfering with existing authentication types. --------- Co-authored-by: Jake Jackson <thedoubl3j@Jakes-MacBook-Pro.local> * revert change made to allow UI to accept x-access-token, just use htt… (#15851) revert change made to allow UI to accept x-access-token, just use https:// instead * Add Github dep for new cred support if used (#15850) * Add pygithub for new app token support * fixed git requirements file with new * added new github dep and relevant deps it needs * add required licenses * Add artifacts to satisfy license check * Remove duplicated license --------- Co-authored-by: Andrea Restle-Lay <arestlel@redhat.com> Co-authored-by: Alan Rominger <arominge@redhat.com> * Remove deps update it came with the cherry-pick and is not needed in this version Remove unneeded deps updates from requirements.in Remove point to awx-plugins as it is not needed in tower * Add a credential plugin that uses GitHub Apps to get tokens * Add github app tests * Ran requirements updater script Ran black on github_app_test to fix formatting issue Add scm_github_app to managed credentials Ran updater script to reflect new deps Added github app info to def build_passwords in jobs.py, cred now appears in credential types Update ManagedCredentialType for GitHub App to match what we have in awx-plugins Update inputs in maManagedCredentialType to github_app_inputs to communicate with awx/main/credential_plugins/github_app.py Revert incorrect change in ManagedCredentialType, change github_app_lookup to call inputs instead of github_app_inputs Updated namespace to github_app_lookup to agree with nomenclature used in the rest of the implementation and to resolve failing API test Remove import pointing to awx plugins and update to point to credential_plugins Remove references to gh_app_plugin_mod and change to github_app Remove from awx_plugins.interfaces._temporary_private_django_api import ( # noqa: WPS436 to resolve failing test Remove flake8 typing & typing references that do not exist in this version of Tower Remove references in jobs.py and __init__.py since this is an external cred type and registered it in setup.cfg instead Remove blank line REvise name in cfg from github_app_lookup to github_app to see if that ifxes module not found error Revise first declaration of github_app to agree with file name to see if that resolves issue Rename line 174 to agree with what's in config Fix reference to github_app_lookup to github_app Linters compliaining about the github_app in __all__ not being defined, renamed to see if that aligns them Fix naming in test to correspond to naming of cred type Update naming to be more specific and add blank line to setup.cfg Remove __all__ from githubapp.py to satisfy linters Revert formatting change since it is not needed in this repository * Add blank line at the end of requirements.in --------- Co-authored-by: Andrea Restle-Lay <andrearestlelay@gmail.com> Co-authored-by: Jake Jackson <thedoubl3j@Jakes-MacBook-Pro.local> Co-authored-by: Jake Jackson <jljacks93@gmail.com> Co-authored-by: Andrea Restle-Lay <arestlel@redhat.com> Co-authored-by: Alan Rominger <arominge@redhat.com>
177 lines
6.2 KiB
Python
177 lines
6.2 KiB
Python
"""GitHub App Installation Access Token Credential Plugin.
|
|
|
|
This module defines a credential plugin for making use of the
|
|
GitHub Apps mechanism, allowing authentication via GitHub App
|
|
installation-scoped access tokens.
|
|
|
|
Functions:
|
|
|
|
- :func:`extract_github_app_install_token`: Generates a GitHub App
|
|
Installation token.
|
|
- ``github_app_lookup``: Defines the credential plugin interface.
|
|
"""
|
|
|
|
from github import Auth as Auth, Github
|
|
from github.Consts import DEFAULT_BASE_URL as PUBLIC_GH_API_URL
|
|
from github.GithubException import (
|
|
BadAttributeException,
|
|
GithubException,
|
|
UnknownObjectException,
|
|
)
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from .plugin import CredentialPlugin
|
|
|
|
github_app_inputs = {
|
|
'fields': [
|
|
{
|
|
'id': 'github_api_url',
|
|
'label': _('GitHub API endpoint URL'),
|
|
'type': 'string',
|
|
'help_text': _(
|
|
'Specify the GitHub API URL here. In the case of an Enterprise: '
|
|
'https://gh.your.org/api/v3 (self-hosted) '
|
|
'or https://api.SUBDOMAIN.ghe.com (cloud)',
|
|
),
|
|
'default': 'https://api.github.com',
|
|
},
|
|
{
|
|
'id': 'app_or_client_id',
|
|
'label': _('GitHub App ID'),
|
|
'type': 'string',
|
|
'help_text': _(
|
|
'The GitHub App ID created by the GitHub Admin. '
|
|
'Example App ID: 1121547 '
|
|
'found on https://github.com/settings/apps/ '
|
|
'required for creating a JWT token for authentication.',
|
|
),
|
|
},
|
|
{
|
|
'id': 'install_id',
|
|
'label': _('GitHub App Installation ID'),
|
|
'type': 'string',
|
|
'help_text': _(
|
|
'The Installation ID from the GitHub App installation '
|
|
'generated by the GitHub Admin. '
|
|
'Example: 59980338 extracted from the installation link '
|
|
'https://github.com/settings/installations/59980338 '
|
|
'required for creating a limited GitHub app token.',
|
|
),
|
|
},
|
|
{
|
|
'id': 'private_rsa_key',
|
|
'label': _('RSA Private Key'),
|
|
'type': 'string',
|
|
'format': 'ssh_private_key',
|
|
'secret': True,
|
|
'multiline': True,
|
|
'help_text': _(
|
|
'Paste the contents of the PEM file that the GitHub Admin provided to you with the app and installation IDs.',
|
|
),
|
|
},
|
|
],
|
|
'metadata': [
|
|
{
|
|
'id': 'description',
|
|
'label': _('Description (Optional)'),
|
|
'type': 'string',
|
|
'help_text': _('To be removed after UI is updated'),
|
|
},
|
|
],
|
|
'required': ['app_or_client_id', 'install_id', 'private_rsa_key'],
|
|
}
|
|
|
|
GH_CLIENT_ID_TRAILER_LENGTH = 16
|
|
HEXADECIMAL_BASE = 16
|
|
|
|
|
|
def _is_intish(app_id_candidate):
|
|
return isinstance(app_id_candidate, int) or app_id_candidate.isdigit()
|
|
|
|
|
|
def _is_client_id(client_id_candidate):
|
|
client_id_prefix = 'Iv1.'
|
|
if not client_id_candidate.startswith(client_id_prefix):
|
|
return False
|
|
|
|
client_id_trailer = client_id_candidate[len(client_id_prefix) :]
|
|
|
|
if len(client_id_trailer) != GH_CLIENT_ID_TRAILER_LENGTH:
|
|
return False
|
|
|
|
try:
|
|
int(client_id_trailer, base=HEXADECIMAL_BASE)
|
|
except ValueError:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def _is_app_or_client_id(app_or_client_id_candidate):
|
|
if _is_intish(app_or_client_id_candidate):
|
|
return True
|
|
return _is_client_id(app_or_client_id_candidate)
|
|
|
|
|
|
def _assert_ids_look_acceptable(app_or_client_id, install_id):
|
|
if not _is_app_or_client_id(app_or_client_id):
|
|
raise ValueError(
|
|
'Expected GitHub App or Client ID to be an integer or a string '
|
|
f'starting with `Iv1.` followed by 16 hexadecimal digits, '
|
|
f'but got {app_or_client_id !r}',
|
|
)
|
|
if isinstance(app_or_client_id, str) and _is_client_id(app_or_client_id):
|
|
raise ValueError(
|
|
'Expected GitHub App ID must be an integer or a string '
|
|
f'with an all-digit value, but got {app_or_client_id !r}. '
|
|
'Client IDs are currently unsupported.',
|
|
)
|
|
if not _is_intish(install_id):
|
|
raise ValueError(
|
|
'Expected GitHub App Installation ID to be an integer' f' but got {install_id !r}',
|
|
)
|
|
|
|
|
|
def extract_github_app_install_token(github_api_url, app_or_client_id, private_rsa_key, install_id, **_discarded_kwargs):
|
|
"""Generate a GH App Installation access token."""
|
|
_assert_ids_look_acceptable(app_or_client_id, install_id)
|
|
|
|
auth = Auth.AppAuth(
|
|
app_id=str(app_or_client_id),
|
|
private_key=private_rsa_key,
|
|
).get_installation_auth(installation_id=int(install_id))
|
|
|
|
Github(
|
|
auth=auth,
|
|
base_url=github_api_url if github_api_url else PUBLIC_GH_API_URL,
|
|
)
|
|
|
|
doc_url = 'See https://docs.github.com/rest/reference/apps#create-an-installation-access-token-for-an-app'
|
|
app_install_context = f'app_or_client_id: {app_or_client_id}, install_id: {install_id}'
|
|
|
|
try:
|
|
return auth.token
|
|
except UnknownObjectException as github_install_not_found_exc:
|
|
raise ValueError(
|
|
f'Failed to retrieve a GitHub installation token from {github_api_url} using {app_install_context}. Is the app installed? {doc_url}.'
|
|
f'\n\n{github_install_not_found_exc}',
|
|
) from github_install_not_found_exc
|
|
except GithubException as pygithub_catchall_exc:
|
|
raise RuntimeError(
|
|
f'An unexpected error happened while talking to GitHub API @ {github_api_url} ({app_install_context}). '
|
|
f'Is the app or client ID correct? And the private RSA key? {doc_url}.'
|
|
f'\n\n{pygithub_catchall_exc}',
|
|
) from pygithub_catchall_exc
|
|
except BadAttributeException as github_broken_exc:
|
|
raise RuntimeError(
|
|
f'Broken GitHub @ {github_api_url} with {app_install_context}. It is a bug, please report it to the developers.\n\n{github_broken_exc}',
|
|
) from github_broken_exc
|
|
|
|
|
|
github_app_lookup_plugin = CredentialPlugin(
|
|
'GitHub App Installation Access Token Lookup',
|
|
inputs=github_app_inputs,
|
|
backend=extract_github_app_install_token,
|
|
)
|