Return more user-friendly errors for assorted manifest failures

This commit is contained in:
Bill Nottingham 2020-10-23 01:03:32 -04:00 committed by Ryan Petrello
parent c139a998b8
commit 393e1b75e9
No known key found for this signature in database
GPG Key ID: F2AA5F2122351777
2 changed files with 30 additions and 15 deletions

View File

@ -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:

View File

@ -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