Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GitHub app scm project alt #15481

Open
wants to merge 2 commits into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions awx/main/models/credential/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,28 @@ def create(self):
},
)

ManagedCredentialType(
namespace='scm_github_app',
kind='scm',
name=gettext_noop('GitHub App'),
managed=True,
inputs={
'fields': [
{'id': 'github_app_id', 'label': gettext_noop('GitHub App ID'), 'type': 'string'},
{'id': 'github_app_installation_id', 'label': gettext_noop('GitHub App Installation ID'), 'type': 'string'},
{
'id': 'ssh_key_data',
'label': gettext_noop('GitHub App Private Key'),
'type': 'string',
'format': 'ssh_private_key',
'secret': True,
'multiline': True,
},
{'id': 'github_api_url', 'label': gettext_noop('GitHub API URL'), 'type': 'string'},
],
},
)

ManagedCredentialType(
namespace='scm',
kind='scm',
Expand Down
38 changes: 35 additions & 3 deletions awx/main/tasks/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import traceback
import time
import urllib.parse as urlparse
import jwt
import requests

# Django
from django.conf import settings
Expand Down Expand Up @@ -1153,16 +1155,46 @@ def build_private_data(self, project_update, private_data_dir):
private_data['credentials'][credential] = credential.get_input('ssh_key_data', default='')
return private_data

def _get_github_app_installation_access_token(self, credential):
jwt_token = jwt.encode(
{
'iat': int(time.time()), # Issued at time
'exp': int(time.time()) + (10 * 60), # JWT expiration time (10 minute maximum)
'iss': credential.get_input('github_app_id', default=''), # GitHub App's identifier
},
credential.get_input('ssh_key_data', default=''),
algorithm='RS256',
)

headers = {'Authorization': f'Bearer {jwt_token}', 'Accept': 'application/vnd.github.v3+json'}

github_api_url = credential.get_input('github_api_url', default='https://api.github.com')
installation_id = credential.get_input('github_app_installation_id', default='')
url = f'{github_api_url}/app/installations/{installation_id}/access_tokens'
response = requests.post(url, headers=headers)

if response.status_code == 201:
access_token = response.json()['token']
return access_token
else:
raise Exception(f"Failed to get access token: {response.status_code} {response.text}")

def build_passwords(self, project_update, runtime_passwords):
"""
Build a dictionary of passwords for SSH private key unlock and SCM
username/password.
"""
passwords = super(RunProjectUpdate, self).build_passwords(project_update, runtime_passwords)
if project_update.credential:
passwords['scm_key_unlock'] = project_update.credential.get_input('ssh_key_unlock', default='')
passwords['scm_username'] = project_update.credential.get_input('username', default='')
passwords['scm_password'] = project_update.credential.get_input('password', default='')
if project_update.credential.credential_type.namespace == 'github_app':
passwords['scm_username'] = 'x-access-token'
passwords['scm_password'] = self._get_github_app_installation_access_token(project_update.credential)
else:
passwords['scm_key_unlock'] = project_update.credential.get_input('ssh_key_unlock', default='')
passwords['scm_key_data'] = project_update.credential.get_input('ssh_key_data', default='')
passwords['scm_username'] = project_update.credential.get_input('username', default='')
passwords['scm_password'] = project_update.credential.get_input('password', default='')

return passwords

def build_env(self, project_update, private_data_dir, private_data_files=None):
Expand Down
1 change: 1 addition & 0 deletions awx/main/tests/functional/test_credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def test_default_cred_types():
'thycotic_tss',
'vault',
'vmware',
'scm_github_app',
]
)

Expand Down
4 changes: 4 additions & 0 deletions awx/main/tests/unit/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1595,6 +1595,10 @@ def test_ssh_key_auth(self, project_update, scm_type, mock_me):
expect_passwords = task.create_expect_passwords_data_struct(password_prompts, passwords)
assert 'bob' in expect_passwords.values()

def test_github_app_auth(self, project_update, mock_me):
# TODO: Implement me
pass

def test_awx_task_env(self, project_update, settings, private_data_dir, scm_type, execution_environment, mock_me):
project_update.execution_environment = execution_environment
settings.AWX_TASK_ENV = {'FOO': 'BAR'}
Expand Down
Loading