1From 3540ddcc7448dc784b65c74424c8a25132cb8534 Mon Sep 17 00:00:00 2001 2From: Hongxu Jia <hongxu.jia@windriver.com> 3Date: Tue, 31 Jul 2018 17:24:47 +0800 4Subject: [PATCH] support authentication for kickstart 5 6While download kickstart file from web server, 7we support basic/digest authentication. 8 9Add KickstartAuthError to report authentication failure, 10which the invoker could parse this specific error. 11 12Upstream-Status: inappropriate [oe specific] 13 14Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com> 15 16--- 17 pykickstart/errors.py | 17 +++++++++++++++++ 18 pykickstart/load.py | 34 ++++++++++++++++++++++++++++------ 19 pykickstart/parser.py | 4 ++-- 20 3 files changed, 47 insertions(+), 8 deletions(-) 21 22diff --git a/pykickstart/errors.py b/pykickstart/errors.py 23index 8294f59a..3d20bf82 100644 24--- a/pykickstart/errors.py 25+++ b/pykickstart/errors.py 26@@ -32,6 +32,9 @@ This module exports several exception classes: 27 KickstartVersionError - An exception for errors relating to unsupported 28 syntax versions. 29 30+ KickstartAuthError - An exception for errors relating to authentication 31+ failed while downloading kickstart from web server 32+ 33 And some warning classes: 34 35 KickstartWarning - A generic warning class. 36@@ -125,3 +128,17 @@ class KickstartDeprecationWarning(KickstartParseWarning, DeprecationWarning): 37 """A class for warnings occurring during parsing related to using deprecated 38 commands and options. 39 """ 40+ 41+class KickstartAuthError(KickstartError): 42+ """An exception for errors relating to authentication failed while 43+ downloading kickstart from web server 44+ """ 45+ def __init__(self, msg): 46+ """Create a new KickstartAuthError exception instance with the 47+ descriptive message val. val should be the return value of 48+ formatErrorMsg. 49+ """ 50+ KickstartError.__init__(self, msg) 51+ 52+ def __str__(self): 53+ return self.value 54diff --git a/pykickstart/load.py b/pykickstart/load.py 55index 30e2fcfa..b984876d 100644 56--- a/pykickstart/load.py 57+++ b/pykickstart/load.py 58@@ -18,9 +18,12 @@ 59 # with the express permission of Red Hat, Inc. 60 # 61 import requests 62+from requests.auth import HTTPDigestAuth 63+from requests.auth import HTTPBasicAuth 64+ 65 import shutil 66 67-from pykickstart.errors import KickstartError 68+from pykickstart.errors import KickstartError, KickstartAuthError 69 from pykickstart.i18n import _ 70 from requests.exceptions import SSLError, RequestException 71 72@@ -28,7 +31,7 @@ _is_url = lambda location: '://' in location # RFC 3986 73 74 SSL_VERIFY = True 75 76-def load_to_str(location): 77+def load_to_str(location, user=None, passwd=None): 78 '''Load a destination URL or file into a string. 79 Type of input is inferred automatically. 80 81@@ -39,7 +42,7 @@ def load_to_str(location): 82 Raises: KickstartError on error reading''' 83 84 if _is_url(location): 85- return _load_url(location) 86+ return _load_url(location, user=user, passwd=passwd) 87 else: 88 return _load_file(location) 89 90@@ -69,11 +72,30 @@ def load_to_file(location, destination): 91 _copy_file(location, destination) 92 return destination 93 94-def _load_url(location): 95- '''Load a location (URL or filename) and return contents as string''' 96+def _get_auth(location, user=None, passwd=None): 97+ 98+ auth = None 99+ request = requests.get(location, verify=SSL_VERIFY) 100+ if request.status_code == requests.codes.unauthorized: 101+ if user is None or passwd is None: 102+ log.info("Require Authentication") 103+ raise KickstartAuthError("Require Authentication.\nAppend 'ksuser=<username> kspasswd=<password>' to boot command") 104 105+ reasons = request.headers.get("WWW-Authenticate", "").split() 106+ if reasons: 107+ auth_type = reasons[0] 108+ if auth_type == "Basic": 109+ auth = HTTPBasicAuth(user, passwd) 110+ elif auth_type == "Digest": 111+ auth=HTTPDigestAuth(user, passwd) 112+ 113+ return auth 114+ 115+def _load_url(location, user=None, passwd=None): 116+ '''Load a location (URL or filename) and return contents as string''' 117+ auth = _get_auth(location, user=user, passwd=passwd) 118 try: 119- request = requests.get(location, verify=SSL_VERIFY) 120+ request = requests.get(location, verify=SSL_VERIFY, auth=auth) 121 except SSLError as e: 122 raise KickstartError(_('Error securely accessing URL "%s"') % location + ': {e}'.format(e=str(e))) 123 except RequestException as e: 124diff --git a/pykickstart/parser.py b/pykickstart/parser.py 125index b23e54f1..e10f06b5 100644 126--- a/pykickstart/parser.py 127+++ b/pykickstart/parser.py 128@@ -796,7 +796,7 @@ class KickstartParser(object): 129 i = PutBackIterator(s.splitlines(True) + [""]) 130 self._stateMachine(i) 131 132- def readKickstart(self, f, reset=True): 133+ def readKickstart(self, f, reset=True, username=None, password=None): 134 """Process a kickstart file, given by the filename f.""" 135 if reset: 136 self._reset() 137@@ -817,7 +817,7 @@ class KickstartParser(object): 138 self.currentdir[self._includeDepth] = cd 139 140 try: 141- s = load_to_str(f) 142+ s = load_to_str(f, user=username, passwd=password) 143 except KickstartError as e: 144 raise KickstartError(_("Unable to open input kickstart file: %s") % str(e), lineno=0) 145 146