From 08176b0108c550e5206eb4aa6adae0ee63599b2e Mon Sep 17 00:00:00 2001 From: Stephen Coady Date: Jan 19 2021 12:10:56 +0000 Subject: [PATCH 1/7] add ability to interact with new AAA solution this commit adds the ability to interact with the new freeipa backed AAA solution. there will be an accompanying commit to the ansible repository to add the necessary config there Signed-off-by: Stephen Coady --- diff --git a/config/bridge.conf b/config/bridge.conf index 0e96df0..83c1940 100644 --- a/config/bridge.conf +++ b/config/bridge.conf @@ -52,3 +52,7 @@ unix-group: sigul # Minimum and maximum versions of TLS used nss-min-tls: tls1.2 nss-max-tls: tls1.2 + +[aaa] +ipa_backend: False +url: https://admin.fedoraproject.org/accounts diff --git a/src/bridge.py b/src/bridge.py index dae87f5..a63492d 100644 --- a/src/bridge.py +++ b/src/bridge.py @@ -29,7 +29,7 @@ import socket import sys import tempfile import threading - +from fasjson_client import Client try: import fedora.client have_fas = True @@ -77,6 +77,8 @@ class BridgeConfiguration(utils.DaemonIDConfiguration, utils.KojiConfiguration, 'bridge', 'bridge-cert-nickname') self.client_listen_port = parser.getint('bridge', 'client-listen-port') self.required_fas_group = parser.get('bridge', 'required-fas-group') + self.ipa_backend = parser.get('aaa', 'ipa_backend') + self.accounts_url = parser.get('aaa', 'url') if self.required_fas_group == '': self.required_fas_group = None else: @@ -166,6 +168,33 @@ def fas_user_is_in_group(config, user_name, group_name): fedora.client.FedoraServiceError) as e: raise BridgeError('Error communicating with FAS: {0!s}'.format(str(e))) +_ipa_connection = None + +def ipa_user_is_in_group(config, user_name, group_name): + '''Return True if user_name is in group.''' + try: + if _ipa_connection is None: + logging.debug('Logging into AAA') + _ipa_connection = Client(base_url=config.url) + response = _ipa_connection.users.get_user(username=username).response().result + + person = response.get('result') + if person is None: + return False + + group_response = _ipa_connection.groups.get_group_members(name=group_name).response().result + + group_memberships = group_response.get('result') + if group_memberships is None: + return False + + for member in group_memberships: + if str(member['username']) == user_name: + return True + return False + except requests.exceptions.ConnectionError as e: + raise BridgeError('Error communicating with AAA: {0!s}'.format(str(e))) + def urlopen(url): '''Open url. @@ -1161,6 +1190,13 @@ class BridgeConnection(object): raise ForwardingError('No client certificate') user_name = cert.subject_common_name logging.info('Client with CN %s connected', repr(user_name)) + user_in_group = False + if config.ipa_backend: + user_in_group = ipa_user_is_in_group(config, user_name, + config.required_fas_group) + else: + user_in_group = fas_user_is_in_group(config, user_name, + config.required_fas_group) if (config.required_fas_group is not None and not fas_user_is_in_group(config, user_name, config.required_fas_group)): From 789e045ce9aca6828ab1a86747d8f82a626cdb3b Mon Sep 17 00:00:00 2001 From: Stephen Coady Date: Jan 19 2021 12:10:56 +0000 Subject: [PATCH 2/7] feedback changes. moved to new API Signed-off-by: Stephen Coady --- diff --git a/config/bridge.conf b/config/bridge.conf index 83c1940..011c9c8 100644 --- a/config/bridge.conf +++ b/config/bridge.conf @@ -54,5 +54,6 @@ nss-min-tls: tls1.2 nss-max-tls: tls1.2 [aaa] -ipa_backend: False +# fas2, freeipa-fas etc +backend: freeipa-fas url: https://admin.fedoraproject.org/accounts diff --git a/src/bridge.py b/src/bridge.py index a63492d..2fac66b 100644 --- a/src/bridge.py +++ b/src/bridge.py @@ -77,7 +77,7 @@ class BridgeConfiguration(utils.DaemonIDConfiguration, utils.KojiConfiguration, 'bridge', 'bridge-cert-nickname') self.client_listen_port = parser.getint('bridge', 'client-listen-port') self.required_fas_group = parser.get('bridge', 'required-fas-group') - self.ipa_backend = parser.get('aaa', 'ipa_backend') + self.backend = parser.get('aaa', 'backend') self.accounts_url = parser.get('aaa', 'url') if self.required_fas_group == '': self.required_fas_group = None @@ -170,21 +170,16 @@ def fas_user_is_in_group(config, user_name, group_name): _ipa_connection = None -def ipa_user_is_in_group(config, user_name, group_name): +def noggin_user_is_in_group(config, user_name, group_name): '''Return True if user_name is in group.''' try: if _ipa_connection is None: logging.debug('Logging into AAA') - _ipa_connection = Client(base_url=config.url) - response = _ipa_connection.users.get_user(username=username).response().result + _ipa_connection = Client(url=config.url) - person = response.get('result') - if person is None: - return False - - group_response = _ipa_connection.groups.get_group_members(name=group_name).response().result + response = _ipa_connection.get_group_members(groupname=group_name).result - group_memberships = group_response.get('result') + group_memberships = response.get('result') if group_memberships is None: return False @@ -192,8 +187,8 @@ def ipa_user_is_in_group(config, user_name, group_name): if str(member['username']) == user_name: return True return False - except requests.exceptions.ConnectionError as e: - raise BridgeError('Error communicating with AAA: {0!s}'.format(str(e))) + except fasjson_client.errors.ClientError as e: + raise BridgeError('Error communicating with AAA: {0!s}'.format(e)) def urlopen(url): @@ -1191,15 +1186,13 @@ class BridgeConnection(object): user_name = cert.subject_common_name logging.info('Client with CN %s connected', repr(user_name)) user_in_group = False - if config.ipa_backend: - user_in_group = ipa_user_is_in_group(config, user_name, + if config.backend === 'noggin': + user_in_group = noggin_user_is_in_group(config, user_name, config.required_fas_group) else: user_in_group = fas_user_is_in_group(config, user_name, config.required_fas_group) - if (config.required_fas_group is not None - and not fas_user_is_in_group(config, user_name, - config.required_fas_group)): + if (config.required_fas_group is not None and not user_in_group): raise InvalidRequestError( 'User {0!s} not allowed to connect'.format( repr(user_name))) From a8b17725a94a99effb327d16268698f5a1969e36 Mon Sep 17 00:00:00 2001 From: Stephen Coady Date: Jan 19 2021 12:10:56 +0000 Subject: [PATCH 3/7] rename for allignment with other apps Signed-off-by: Stephen Coady --- diff --git a/src/bridge.py b/src/bridge.py index 2fac66b..8c7fbf0 100644 --- a/src/bridge.py +++ b/src/bridge.py @@ -1186,7 +1186,7 @@ class BridgeConnection(object): user_name = cert.subject_common_name logging.info('Client with CN %s connected', repr(user_name)) user_in_group = False - if config.backend === 'noggin': + if config.backend === 'fasjson': user_in_group = noggin_user_is_in_group(config, user_name, config.required_fas_group) else: From cc76f55e7eb55e5eddab1965d37e8884943f242b Mon Sep 17 00:00:00 2001 From: Stephen Coady Date: Jan 19 2021 12:10:56 +0000 Subject: [PATCH 4/7] linting, renaming of variables Signed-off-by: Stephen Coady --- diff --git a/config/bridge.conf b/config/bridge.conf index 011c9c8..48b7eb9 100644 --- a/config/bridge.conf +++ b/config/bridge.conf @@ -54,6 +54,6 @@ nss-min-tls: tls1.2 nss-max-tls: tls1.2 [aaa] -# fas2, freeipa-fas etc -backend: freeipa-fas +# fas2, fasjson etc +backend: fas2 url: https://admin.fedoraproject.org/accounts diff --git a/src/bridge.py b/src/bridge.py index 8c7fbf0..60d80b9 100644 --- a/src/bridge.py +++ b/src/bridge.py @@ -29,7 +29,7 @@ import socket import sys import tempfile import threading -from fasjson_client import Client +import fasjson_client try: import fedora.client have_fas = True @@ -168,23 +168,28 @@ def fas_user_is_in_group(config, user_name, group_name): fedora.client.FedoraServiceError) as e: raise BridgeError('Error communicating with FAS: {0!s}'.format(str(e))) -_ipa_connection = None -def noggin_user_is_in_group(config, user_name, group_name): +_fasjson_connection = None + + +def fasjson_user_is_in_group(config, user_name, group_name): '''Return True if user_name is in group.''' + global _fasjson_connection + try: - if _ipa_connection is None: + if _fasjson_connection is None: logging.debug('Logging into AAA') - _ipa_connection = Client(url=config.url) + _fasjson_connection = fasjson_client.Client(url=config.url) - response = _ipa_connection.get_group_members(groupname=group_name).result + response = _fasjson_connection.get_group_members( + groupname=group_name).result group_memberships = response.get('result') if group_memberships is None: return False for member in group_memberships: - if str(member['username']) == user_name: + if member['username'] == user_name: return True return False except fasjson_client.errors.ClientError as e: @@ -1186,9 +1191,9 @@ class BridgeConnection(object): user_name = cert.subject_common_name logging.info('Client with CN %s connected', repr(user_name)) user_in_group = False - if config.backend === 'fasjson': - user_in_group = noggin_user_is_in_group(config, user_name, - config.required_fas_group) + if config.backend == 'fasjson': + user_in_group = fasjson_user_is_in_group(config, user_name, + config.required_fas_group) else: user_in_group = fas_user_is_in_group(config, user_name, config.required_fas_group) From fb53c6bc8d5598a84a2ac069683eaf7b0a482dc5 Mon Sep 17 00:00:00 2001 From: Stephen Coady Date: Jan 19 2021 12:10:56 +0000 Subject: [PATCH 5/7] reorder logic. add config to test setup Signed-off-by: Stephen Coady --- diff --git a/config/bridge.conf b/config/bridge.conf index 48b7eb9..2b54ee0 100644 --- a/config/bridge.conf +++ b/config/bridge.conf @@ -56,4 +56,4 @@ nss-max-tls: tls1.2 [aaa] # fas2, fasjson etc backend: fas2 -url: https://admin.fedoraproject.org/accounts +url: https://admin.project.org/accounts diff --git a/src/bridge.py b/src/bridge.py index 60d80b9..0c51a36 100644 --- a/src/bridge.py +++ b/src/bridge.py @@ -69,7 +69,7 @@ class BridgeConfiguration(utils.DaemonIDConfiguration, utils.KojiConfiguration, def _add_sections(self, sections): super(BridgeConfiguration, self)._add_sections(sections) - sections.update(('bridge', 'koji')) + sections.update(('bridge', 'koji', 'aaa')) def _read_configuration(self, parser): super(BridgeConfiguration, self)._read_configuration(parser) @@ -180,18 +180,12 @@ def fasjson_user_is_in_group(config, user_name, group_name): if _fasjson_connection is None: logging.debug('Logging into AAA') _fasjson_connection = fasjson_client.Client(url=config.url) - - response = _fasjson_connection.get_group_members( - groupname=group_name).result - - group_memberships = response.get('result') - if group_memberships is None: - return False - - for member in group_memberships: - if member['username'] == user_name: - return True - return False + try: + return _fasjson_connection.check_membership( + groupname=group_name, + username=user_name).result + except fasjson_client.errors.APIError as e: + raise BridgeError('Error checking membership: {0!s}'.format(e)) except fasjson_client.errors.ClientError as e: raise BridgeError('Error communicating with AAA: {0!s}'.format(e)) @@ -1190,17 +1184,20 @@ class BridgeConnection(object): raise ForwardingError('No client certificate') user_name = cert.subject_common_name logging.info('Client with CN %s connected', repr(user_name)) - user_in_group = False if config.backend == 'fasjson': - user_in_group = fasjson_user_is_in_group(config, user_name, - config.required_fas_group) + if (config.required_fas_group is not None + and not fasjson_user_is_in_group(config, user_name, + config.required_fas_group)): + raise InvalidRequestError( + 'User {0!s} not allowed to connect'.format( + repr(user_name))) else: - user_in_group = fas_user_is_in_group(config, user_name, - config.required_fas_group) - if (config.required_fas_group is not None and not user_in_group): - raise InvalidRequestError( - 'User {0!s} not allowed to connect'.format( - repr(user_name))) + if (config.required_fas_group is not None + and not fas_user_is_in_group(config, user_name, + config.required_fas_group)): + raise InvalidRequestError( + 'User {0!s} not allowed to connect'.format( + repr(user_name))) client_buf = double_tls.OuterBuffer(client_sock) server_buf = double_tls.OuterBuffer(server_sock) diff --git a/tests/include_setup.at b/tests/include_setup.at index 09a3977..be4ff0c 100644 --- a/tests/include_setup.at +++ b/tests/include_setup.at @@ -63,6 +63,10 @@ unix-group: [nss] nss-dir: $(pwd)/bridge nss-password: nss-pw +[aaa] +# fas2, fasjson etc +backend: fas2 +url: https://admin.fedoraproject.org/accounts EOF] @@ -95,6 +99,10 @@ unix-group: [nss] nss-dir: $(pwd)/server nss-password: wrong-nss-pw +[aaa] +# fas2, fasjson etc +backend: fas2 +url: https://admin.fedoraproject.org/accounts EOF] mkdir server/keys From 8abcac3ebb2096e367e0c687fbe06a76f293f563 Mon Sep 17 00:00:00 2001 From: Stephen Coady Date: Jan 21 2021 15:22:07 +0000 Subject: [PATCH 6/7] add fasjson-client to Dockerfile so it doesnt break Signed-off-by: Stephen Coady --- diff --git a/tests/Dockerfile b/tests/Dockerfile index 77a77a8..de5f63a 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -3,7 +3,7 @@ FROM fedora:latest RUN mkdir /testcode RUN dnf install -y \ python3-six python3-nss python3-requests python3-koji python3-rpm python3-cryptography \ - python3-gpg python3-sqlalchemy python3-pycodestyle \ + python3-gpg python3-sqlalchemy python3-pycodestyle python3-fasjson-client \ gnupg2 ostree-devel nss-tools rpm-build rpm-sign \ cargo autoconf automake make bandit openssl ostree \ skopeo From d6f8fd638ad124bcef1df073e101c0acc2e05b03 Mon Sep 17 00:00:00 2001 From: Stephen Coady Date: Feb 16 2021 16:33:48 +0000 Subject: [PATCH 7/7] add webserver to fake fasjson endpoints Signed-off-by: Stephen Coady --- diff --git a/config/bridge.conf b/config/bridge.conf index 2b54ee0..f5e1a63 100644 --- a/config/bridge.conf +++ b/config/bridge.conf @@ -57,3 +57,4 @@ nss-max-tls: tls1.2 # fas2, fasjson etc backend: fas2 url: https://admin.project.org/accounts +auth: true diff --git a/src/bridge.py b/src/bridge.py index 0c51a36..d5e2c9c 100644 --- a/src/bridge.py +++ b/src/bridge.py @@ -78,15 +78,18 @@ class BridgeConfiguration(utils.DaemonIDConfiguration, utils.KojiConfiguration, self.client_listen_port = parser.getint('bridge', 'client-listen-port') self.required_fas_group = parser.get('bridge', 'required-fas-group') self.backend = parser.get('aaa', 'backend') - self.accounts_url = parser.get('aaa', 'url') + self.url = parser.get('aaa', 'url') + self.auth = parser.get('aaa', 'auth') if self.required_fas_group == '': self.required_fas_group = None else: - if not have_fas: - raise utils.ConfigurationError('Fedora Account system ' - 'authentication not supported') - self.fas_user_name = parser.get('bridge', 'fas-user-name') - self.fas_password = parser.get('bridge', 'fas-password') + if self.backend == 'fas2': + if not have_fas: + raise utils.ConfigurationError('Fedora Account system ' + 'authentication not ' + 'supported') + self.fas_user_name = parser.get('bridge', 'fas-user-name') + self.fas_password = parser.get('bridge', 'fas-password') self.max_rpms_payloads_size = parser.getint('bridge', 'max-rpms-payloads-size') self.server_listen_port = parser.getint('bridge', 'server-listen-port') @@ -179,7 +182,12 @@ def fasjson_user_is_in_group(config, user_name, group_name): try: if _fasjson_connection is None: logging.debug('Logging into AAA') - _fasjson_connection = fasjson_client.Client(url=config.url) + if config.auth: + _fasjson_connection = fasjson_client.Client(url=config.url) + else: + _fasjson_connection = fasjson_client.Client( + url=config.url, auth=False + ) try: return _fasjson_connection.check_membership( groupname=group_name, diff --git a/tests/auth-backend.at b/tests/auth-backend.at new file mode 100644 index 0000000..991dc1b --- /dev/null +++ b/tests/auth-backend.at @@ -0,0 +1,55 @@ +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# This copyrighted material is made available to anyone wishing to use, modify, +# copy, or redistribute it subject to the terms and conditions of the GNU +# General Public License v.2. This program is distributed in the hope that it +# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the +# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. You should have +# received a copy of the GNU General Public License along with this program; if +# not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth +# Floor, Boston, MA 02110-1301, USA. Any Red Hat trademarks that are +# incorporated in the source code or documentation are not subject to the GNU +# General Public License and may only be used or replicated with the express +# permission of Red Hat, Inc. +# +# Red Hat Author: Stephen Coady + +AT_SETUP([Auth Backend]) + +m4_include([include_setup.at]) + +# Start mock fasjson server +AT_CHECK([python $abs_srcdir/tests/fake_fasjson.py &], ,[ignore]) + +# Configure sigul to use fasjson config flags +[cat > bridge/bridge.conf <