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_access_token: add support for private key fact #8989

Merged
merged 11 commits into from
Oct 21, 2024
2 changes: 2 additions & 0 deletions changelogs/fragments/8989-github-app-token-from-fact.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- github_app_access_token lookup plugin - adds new ``private_key`` parameter (https://github.com/ansible-collections/community.general/pull/8989).
24 changes: 19 additions & 5 deletions plugins/lookup/github_app_access_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
key_path:
description:
- Path to your private key.
lewismiddleton marked this conversation as resolved.
Show resolved Hide resolved
required: true
- Either O(key_path) or O(private_key) must be specified.
type: path
app_id:
description:
Expand All @@ -34,6 +34,12 @@
- Alternatively, you can use PyGithub (U(https://github.com/PyGithub/PyGithub)) to get your installation ID.
required: true
type: str
private_key:
description:
- GitHub App private key in PEM file format as string.
- Either O(key_path) or O(private_key) must be specified.
type: str
lewismiddleton marked this conversation as resolved.
Show resolved Hide resolved
version_added: 10.0.0
token_expiry:
description:
- How long the token should last for in seconds.
Expand Down Expand Up @@ -71,7 +77,7 @@
import json
from ansible.module_utils.urls import open_url
from ansible.module_utils.six.moves.urllib.error import HTTPError
from ansible.errors import AnsibleError
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.plugins.lookup import LookupBase
from ansible.utils.display import Display

Expand All @@ -84,8 +90,10 @@
display = Display()


def read_key(path):
def read_key(path, private_key=None):
try:
if private_key:
return jwk_from_pem(private_key.encode('utf-8'))
with open(path, 'rb') as pem_file:
return jwk_from_pem(pem_file.read())
except Exception as e:
Expand Down Expand Up @@ -132,8 +140,8 @@ def post_request(generated_jwt, installation_id):
return json_data.get('token')


def get_token(key_path, app_id, installation_id, expiry=600):
jwk = read_key(key_path)
def get_token(key_path, app_id, installation_id, private_key, expiry=600):
jwk = read_key(key_path, private_key)
generated_jwt = encode_jwt(app_id, jwk, exp=expiry)
return post_request(generated_jwt, installation_id)

Expand All @@ -146,10 +154,16 @@ def run(self, terms, variables=None, **kwargs):

self.set_options(var_options=variables, direct=kwargs)

if not (self.get_option("key_path") or self.get_option("private_key")):
raise AnsibleOptionsError("One of key_path or private_key is required")
felixfontein marked this conversation as resolved.
Show resolved Hide resolved
if self.get_option("key_path") and self.get_option("private_key"):
raise AnsibleOptionsError("key_path and private_key are mutually exclusive")

t = get_token(
self.get_option('key_path'),
self.get_option('app_id'),
self.get_option('installation_id'),
self.get_option('private_key'),
self.get_option('token_expiry'),
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAr/EjKUujUdliSX79ZlDwq/+RCnOF1JCrekWGOOK4YGqgfJBM
Z/CLHYTW+BQAH172NTEwLlegwJpXtalae9WVhyMs4sFm7nxSZsFjRK7Gof1tuFbD
i4+GGlu4kci7xVcxzoZoVvswX4Xw/9MCg/Je35H8xbugwsWYg+ou79e0wx0fYU4d
dwiUte8K+/d1l5acMQuqcnUfJLRmXvw3w7hyemB51EPTGqkpcA4KmYns1W12ianD
Zo9/d2kLC2mcyxDkHmqWCv9vfUVrKB7yIC8DU5uY/acFtBaVE1yyvI+1lCCkxNWX
5IDbpP/xRJk68B0WXKaU2IdFUVYSD48u3nSZoQIDAQABAoIBAGLL9KOevqIagK+m
qKKItuzOgOKuhisb5b6uRbWx0jkKBv6LhOwkzemQi6oYiQ0UpQqviU+sky80PCZd
Z9r7z5Bn9y+JzMQEeb0LwTNzNUUHa1JFHl9DA9nPQXBTmOUyllxTa0nUmZA6RV9S
XSo8snu2nYtnVdmpXYBNw3eY1/9rb1blXEZHLJbCTaTX3MuWDYuJ4G0K6EArjSwG
DDGhOWIWfkk3zZAHqdsrJxgqXIx2Cv9m40hmC0XMwqh8/H3j4kZZhdglJhNbvnBM
8ZKRzpMOP1hbGATmi9A1HU+o6BpdIl1dyMRiod3WjSS7CKvs8BVR0XMK/SXDV9Wl
Jy6kwYUCgYEA4HwzV/YT+cTb61VL5ICj871m5VMaGJD96dOrnX33QYRNw5aLRc35
HMaJ1t5Bp0d2J5h0mPoQkSvQxuPYfaytTYknSUE/bObYNMEnII3XeTmA8ILlG7kV
8OQah66GMKjyHocie2PxUuu9BWtuPvZJDrOuR9Pmw5aH6+oiXBSM0YcCgYEAyKRW
FHtDGC8ZHgBaytaGvlbVo3RTKboQgYqzf9HdvzWHlSbeZVuCk6MWtSNU+5+26RBK
FCI8FTBxqY/vai9zRgp/1u3jY3N1WIsowBgBV7C84IP6gEr/FAJlx++Eqzfmx1W7
lU3/0IJ/jYS7D6C4aADifo4aGF0mFHFBk7sfpZcCgYBaIyTOnf15XgVcIjy9/LVY
amXFkS+6S4XY/Og87dZ5VTGQZoN3vPPZDRNN1qKQE46q6Xlv74D1eZ10Lwq/s7VG
m9rNfEiGZs7Lp/8ZADtT7rYKXNS35AKeXkkU0AwLv9qwTVyYJRJCVGvqoC99UpEV
OSqyprBTOr9LCBFR3eKJQwKBgHXRqoqUZy3IWmN3qdj6aF1U+Fbnc/5IuHCZVhZ0
0lX5xQgcrvOt7NttJWRwvvKTMwFhA18XS1jV/aioUNp1yqcSe0dmoeRAZGP+M4u5
jPBFZGQim/LCF09UqRfi2nEAfpAHFAP0rYdvWh9sFbxzkFXiTx4pq8Eq0bWnW+64
Lzk5AoGAVeV9KgqJZLbl2Vbii3bJOuvtNeHOkIYPIoU6kgox9qp1derccWuMtwLT
PjhnWuCAX5dN1d7Rve4EovkjvuomDuZy6NCQlmLA6ff2pxtcJAkGy8Blc5VQeWs9
i9DUFz2Sx6olEO7MDykj4B6O2YNlAwb8xq1oivE24laZPufprwI=
-----END RSA PRIVATE KEY-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Copyright (c) Ansible Project
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
SPDX-License-Identifier: GPL-3.0-or-later
30 changes: 30 additions & 0 deletions tests/integration/targets/github_app_access_token/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################

# Test code for the github_app_access_token plugin.
#
# Copyright (c) 2017-2018, Abhijeet Kasurde <akasurde@redhat.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

- name: Install JWT
ansible.builtin.pip:
name:
- jwt

- name: Read file
ansible.builtin.set_fact:
github_app_private_key: "{{ lookup('ansible.builtin.file', 'app-private-key.pem') }}"

- name: Generate Github App Token
register: github_app_access_token
ignore_errors: true
ansible.builtin.set_fact:
github_app_token: "{{ lookup('community.general.github_app_access_token', app_id=github_app_id, installation_id=github_app_installation_id, private_key=github_app_private_key) }}"

- assert:
that:
- github_app_access_token is failed
- '"Github return error" in github_app_access_token.msg'
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

github_app_id: 123456
github_app_installation_id: 123456
20 changes: 19 additions & 1 deletion tests/unit/plugins/lookup/test_github_app_access_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def read(self):

class TestLookupModule(unittest.TestCase):

def test_get_token(self):
def test_get_token_with_file(self):
with patch.multiple("ansible_collections.community.general.plugins.lookup.github_app_access_token",
open=mock_open(read_data="foo_bar"),
open_url=MagicMock(return_value=MockResponse()),
Expand All @@ -50,3 +50,21 @@ def test_get_token(self):
token_expiry=600
)
)

def test_get_token_with_fact(self):
with patch.multiple("ansible_collections.community.general.plugins.lookup.github_app_access_token",
open_url=MagicMock(return_value=MockResponse()),
jwk_from_pem=MagicMock(return_value='private_key'),
jwt_instance=MockJWT(),
HAS_JWT=True):
lookup = lookup_loader.get('community.general.github_app_access_token')
self.assertListEqual(
[MockResponse.response_token],
lookup.run(
[],
app_id="app_id",
installation_id="installation_id",
private_key="foo_bar",
token_expiry=600
)
)