From 393e1b75e92b2f1870943ad06dfed59d9620099c Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Fri, 23 Oct 2020 01:03:32 -0400 Subject: [PATCH] Return more user-friendly errors for assorted manifest failures --- awx/api/views/root.py | 24 ++++++++++++++---------- awx/main/utils/licensing.py | 21 ++++++++++++++++----- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/awx/api/views/root.py b/awx/api/views/root.py index 9f5b142351..8818b06ee1 100644 --- a/awx/api/views/root.py +++ b/awx/api/views/root.py @@ -200,7 +200,7 @@ class ApiV2SubscriptionView(APIView): if pw: settings.SUBSCRIPTIONS_PASSWORD = data['subscriptions_password'] except Exception as exc: - msg = _("Invalid License") + msg = _("Invalid Subscription") if ( isinstance(exc, requests.exceptions.HTTPError) and getattr(getattr(exc, 'response', None), 'status_code', None) == 401 @@ -213,7 +213,7 @@ class ApiV2SubscriptionView(APIView): elif isinstance(exc, (ValueError, OSError)) and exc.args: msg = exc.args[0] else: - logger.exception(smart_text(u"Invalid license submitted."), + logger.exception(smart_text(u"Invalid subscription submitted."), extra=dict(actor=request.user.username)) return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST) @@ -245,7 +245,7 @@ class ApiV2AttachView(APIView): with set_environ(**settings.AWX_TASK_ENV): validated = get_licenser().validate_rh(user, pw) except Exception as exc: - msg = _("Invalid License") + msg = _("Invalid Subscription") if ( isinstance(exc, requests.exceptions.HTTPError) and getattr(getattr(exc, 'response', None), 'status_code', None) == 401 @@ -258,7 +258,7 @@ class ApiV2AttachView(APIView): elif isinstance(exc, (ValueError, OSError)) and exc.args: msg = exc.args[0] else: - logger.exception(smart_text(u"Invalid license submitted."), + logger.exception(smart_text(u"Invalid subscription submitted."), extra=dict(actor=request.user.username)) return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST) for sub in validated: @@ -330,7 +330,7 @@ class ApiV2ConfigView(APIView): def post(self, request): if not isinstance(request.data, dict): - return Response({"error": _("Invalid license data")}, status=status.HTTP_400_BAD_REQUEST) + return Response({"error": _("Invalid subscription data")}, status=status.HTTP_400_BAD_REQUEST) if "eula_accepted" not in request.data: return Response({"error": _("Missing 'eula_accepted' property")}, status=status.HTTP_400_BAD_REQUEST) try: @@ -350,17 +350,21 @@ class ApiV2ConfigView(APIView): from awx.main.utils.common import get_licenser license_data = json.loads(data_actual) + if 'license_key' in license_data: + return Response({"error": _('Legacy license submitted. A subscription manifest is now required.')}, status=status.HTTP_400_BAD_REQUEST) if 'manifest' in license_data: try: license_data = validate_entitlement_manifest(license_data['manifest']) + except ValueError as e: + return Response({"error": e}, status=status.HTTP_400_BAD_REQUEST) except Exception: - logger.exception('Invalid license submitted. {}') - return Response({"error": 'Invalid license submitted.'}, status=status.HTTP_400_BAD_REQUEST) + logger.exception('Invalid manifest submitted. {}') + return Response({"error": _('Invalid manifest submitted.')}, status=status.HTTP_400_BAD_REQUEST) try: license_data_validated = get_licenser().license_from_manifest(license_data) except Exception: - logger.warning(smart_text(u"Invalid license submitted."), + logger.warning(smart_text(u"Invalid subscription submitted."), extra=dict(actor=request.user.username)) return Response({"error": _("Invalid License")}, status=status.HTTP_400_BAD_REQUEST) else: @@ -372,9 +376,9 @@ class ApiV2ConfigView(APIView): settings.TOWER_URL_BASE = "{}://{}".format(request.scheme, request.get_host()) return Response(license_data_validated) - logger.warning(smart_text(u"Invalid license submitted."), + logger.warning(smart_text(u"Invalid subscription submitted."), extra=dict(actor=request.user.username)) - return Response({"error": _("Invalid license")}, status=status.HTTP_400_BAD_REQUEST) + return Response({"error": _("Invalid subscription")}, status=status.HTTP_400_BAD_REQUEST) def delete(self, request): try: diff --git a/awx/main/utils/licensing.py b/awx/main/utils/licensing.py index a83947c7cf..70e62c8101 100644 --- a/awx/main/utils/licensing.py +++ b/awx/main/utils/licensing.py @@ -24,6 +24,7 @@ import zipfile from dateutil.parser import parse as parse_date +from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import padding @@ -31,6 +32,7 @@ from cryptography import x509 # Django from django.conf import settings +from django.utils.translation import ugettext_lazy as _ # AWX from awx.main.models import Host @@ -50,22 +52,31 @@ def rhsm_config(): def validate_entitlement_manifest(data): buff = io.BytesIO() buff.write(base64.b64decode(data)) - z = zipfile.ZipFile(buff) + try: + z = zipfile.ZipFile(buff) + except zipfile.BadZipFile as e: + raise ValueError(_("Invalid manifest: a subscription manifest zip file is required.")) from e buff = io.BytesIO() + files = z.namelist() + if 'consumer_export.zip' not in files or 'signature' not in files: + raise ValueError(_("Invalid manifest: missing required files.")) export = z.open('consumer_export.zip').read() sig = z.open('signature').read() with open('/etc/tower/candlepin-redhat-ca.crt', 'rb') as f: cert = x509.load_pem_x509_certificate(f.read(), backend=default_backend()) key = cert.public_key() - key.verify(sig, export, padding=padding.PKCS1v15(), algorithm=hashes.SHA256()) + try: + key.verify(sig, export, padding=padding.PKCS1v15(), algorithm=hashes.SHA256()) + except InvalidSignature as e: + raise ValueError(_("Invalid manifest: signature verification failed.")) from e buff.write(export) z = zipfile.ZipFile(buff) for f in z.filelist: if f.filename.startswith('export/entitlements') and f.filename.endswith('.json'): return json.loads(z.open(f).read()) - + raise ValueError(_("Invalid manifest: manifest contains no subscriptions.")) class OpenLicense(object): def validate(self): @@ -88,7 +99,7 @@ class Licenser(object): instance_count=0, license_date=0, license_type="UNLICENSED", - product_name="Red Hat Ansible Tower", + product_name="Red Hat Ansible Automation Platform", valid_key=False ) @@ -308,7 +319,7 @@ class Licenser(object): if valid_subs: licenses = [] for sub in valid_subs: - license = self.__class__(subscription_name='Ansible Tower by Red Hat') + license = self.__class__(subscription_name='Red Hat Ansible Automation Platform') license._attrs['instance_count'] = int(sub.quantity) license._attrs['sku'] = sub.sku license._attrs['support_level'] = sub.support_level