Package backend :: Package cdn_tools :: Module manifest
[hide private]
[frames] | no frames]

Source Code for Module backend.cdn_tools.manifest

  1  # Copyright (c) 2016--2017 Red Hat, Inc. 
  2  # 
  3  # This software is licensed to you under the GNU General Public License, 
  4  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  5  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  6  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  7  # along with this software; if not, see 
  8  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
  9  # 
 10  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 11  # granted to use or replicate Red Hat trademarks that are incorporated 
 12  # in this software or its documentation. 
 13  # 
 14   
 15  import sys 
 16   
 17  import cStringIO 
 18  import json 
 19  import zipfile 
 20  import os 
 21  from M2Crypto import X509 
 22   
 23  from spacewalk.satellite_tools.syncLib import log2 
 24  from spacewalk.server.rhnServer.satellite_cert import SatelliteCert 
 25   
 26  import constants 
 27   
 28   
29 -class Manifest(object):
30 """Class containing relevant data from RHSM manifest.""" 31 32 SIGNATURE_NAME = "signature" 33 INNER_ZIP_NAME = "consumer_export.zip" 34 ENTITLEMENTS_PATH = "export/entitlements" 35 CERTIFICATE_PATH = "export/extensions" 36 PRODUCTS_PATH = "export/products" 37 CONSUMER_INFO = "export/consumer.json" 38 META_INFO = "export/meta.json" 39 UPSTREAM_CONSUMER_PATH = "export/upstream_consumer" 40
41 - def __init__(self, zip_path):
42 self.all_entitlements = [] 43 self.manifest_repos = {} 44 self.sat5_certificate = None 45 self.satellite_version = None 46 self.consumer_credentials = None 47 self.uuid = None 48 self.name = None 49 self.ownerid = None 50 self.api_url = None 51 self.web_url = None 52 self.created = None 53 # Signature and signed data 54 self.signature = None 55 self.data = None 56 # Open manifest from path 57 top_zip = None 58 inner_zip = None 59 inner_file = None 60 61 # normalize path 62 zip_path = os.path.abspath(os.path.expanduser(zip_path)) 63 try: 64 top_zip = zipfile.ZipFile(zip_path, 'r') 65 # Fetch inner zip file into memory 66 try: 67 # inner_file = top_zip.open(zip_path.split('.zip')[0] + '/' + self.INNER_ZIP_NAME) 68 inner_file = top_zip.open(self.INNER_ZIP_NAME) 69 self.data = inner_file.read() 70 inner_file_data = cStringIO.StringIO(self.data) 71 signature_file = top_zip.open(self.SIGNATURE_NAME) 72 self.signature = signature_file.read() 73 # Open the inner zip file 74 try: 75 inner_zip = zipfile.ZipFile(inner_file_data) 76 self._extract_consumer_info(inner_zip) 77 self._load_entitlements(inner_zip) 78 self._extract_certificate(inner_zip) 79 self._extract_meta_info(inner_zip) 80 self._extract_consumer_credentials(inner_zip) 81 finally: 82 if inner_zip is not None: 83 inner_zip.close() 84 finally: 85 if inner_file is not None: 86 inner_file.close() 87 finally: 88 if top_zip is not None: 89 top_zip.close()
90
91 - def _extract_certificate(self, zip_file):
92 files = zip_file.namelist() 93 certificates_names = [] 94 for f in files: 95 if f.startswith(self.CERTIFICATE_PATH) and f.endswith(".xml"): 96 certificates_names.append(f) 97 if len(certificates_names) >= 1: 98 # take only first file 99 cert_file = zip_file.open(certificates_names[0]) # take only first file 100 self.sat5_certificate = cert_file.read().strip() 101 cert_file.close() 102 # Save version too 103 sat5_cert = SatelliteCert() 104 sat5_cert.load(self.sat5_certificate) 105 self.satellite_version = getattr(sat5_cert, 'satellite-version') 106 else: 107 raise MissingSatelliteCertificateError("Satellite Certificate was not found in manifest.")
108
109 - def _fill_product_repositories(self, zip_file, product):
110 product_file = zip_file.open(self.PRODUCTS_PATH + '/' + str(product.get_id()) + '.json') 111 product_data = json.load(product_file) 112 product_file.close() 113 try: 114 for content in product_data['productContent']: 115 content = content['content'] 116 product.add_repository(content['label'], content['contentUrl']) 117 except KeyError: 118 log2(0, 0, "ERROR: Cannot access required field in product '%s'" % product.get_id(), stream=sys.stderr) 119 raise
120
121 - def _load_entitlements(self, zip_file):
122 files = zip_file.namelist() 123 entitlements_files = [] 124 for f in files: 125 if f.startswith(self.ENTITLEMENTS_PATH) and f.endswith(".json"): 126 entitlements_files.append(f) 127 128 if len(entitlements_files) >= 1: 129 self.all_entitlements = [] 130 for entitlement_file in entitlements_files: 131 entitlements = zip_file.open(entitlement_file) 132 # try block in try block - this is hack for python 2.4 compatibility 133 # to support finally 134 try: 135 try: 136 data = json.load(entitlements) 137 138 # Extract credentials 139 certs = data['certificates'] 140 if len(certs) != 1: 141 raise IncorrectEntitlementsFileFormatError( 142 "Single certificate in entitlements file '%s' is expected, found: %d" 143 % (entitlement_file, len(certs))) 144 cert = certs[0] 145 credentials = Credentials(data['id'], cert['cert'], cert['key']) 146 147 # Extract product IDs 148 products = [] 149 provided_products = data['pool']['providedProducts'] or [] 150 derived_provided_products = data['pool']['derivedProvidedProducts'] or [] 151 product_ids = [provided_product['productId'] for provided_product 152 in provided_products + derived_provided_products] 153 for product_id in set(product_ids): 154 product = Product(product_id) 155 self._fill_product_repositories(zip_file, product) 156 products.append(product) 157 158 # Skip entitlements not providing any products 159 if products: 160 entitlement = Entitlement(products, credentials) 161 self.all_entitlements.append(entitlement) 162 except KeyError: 163 log2(0, 0, "ERROR: Cannot access required field in file '%s'" % entitlement_file, 164 stream=sys.stderr) 165 raise 166 finally: 167 entitlements.close() 168 else: 169 refer_url = "%s%s" % (self.web_url, self.uuid) 170 if not refer_url.startswith("http"): 171 refer_url = "https://" + refer_url 172 raise IncorrectEntitlementsFileFormatError( 173 "No subscriptions were found in manifest.\n\nPlease refer to %s for setting up subscriptions." 174 % refer_url)
175
176 - def _extract_consumer_info(self, zip_file):
177 files = zip_file.namelist() 178 found = False 179 for f in files: 180 if f == self.CONSUMER_INFO: 181 found = True 182 break 183 if found: 184 consumer_info = zip_file.open(self.CONSUMER_INFO) 185 try: 186 try: 187 data = json.load(consumer_info) 188 self.uuid = data['uuid'] 189 self.name = data['name'] 190 self.ownerid = data['owner']['key'] 191 self.api_url = data['urlApi'] 192 self.web_url = data['urlWeb'] 193 except KeyError: 194 log2(0, 0, "ERROR: Cannot access required field in file '%s'" % self.CONSUMER_INFO, 195 stream=sys.stderr) 196 raise 197 finally: 198 consumer_info.close() 199 else: 200 raise MissingConsumerInfoError()
201
202 - def _extract_meta_info(self, zip_file):
203 files = zip_file.namelist() 204 found = False 205 for f in files: 206 if f == self.META_INFO: 207 found = True 208 break 209 if found: 210 meta_info = zip_file.open(self.META_INFO) 211 try: 212 try: 213 data = json.load(meta_info) 214 self.created = data['created'] 215 except KeyError: 216 log2(0, 0, "ERROR: Cannot access required field in file '%s'" % self.META_INFO, stream=sys.stderr) 217 raise 218 finally: 219 meta_info.close() 220 else: 221 raise MissingMetaInfoError()
222
223 - def _extract_consumer_credentials(self, zip_file):
224 files = zip_file.namelist() 225 consumer_credentials = [] 226 for f in files: 227 if f.startswith(self.UPSTREAM_CONSUMER_PATH) and f.endswith(".json"): 228 consumer_credentials.append(f) 229 230 if len(consumer_credentials) == 1: 231 upstream_consumer = zip_file.open(consumer_credentials[0]) 232 try: 233 try: 234 data = json.load(upstream_consumer) 235 self.consumer_credentials = Credentials(data['id'], data['cert'], data['key']) 236 except KeyError: 237 log2(0, 0, "ERROR: Cannot access required field in file '%s'" % consumer_credentials[0], 238 stream=sys.stderr) 239 raise 240 finally: 241 upstream_consumer.close() 242 else: 243 raise IncorrectCredentialsError( 244 "ERROR: Single upstream consumer certificate expected, found %d." % len(consumer_credentials))
245
246 - def get_all_entitlements(self):
247 return self.all_entitlements
248
250 return self.sat5_certificate
251
252 - def get_satellite_version(self):
253 return self.satellite_version
254
255 - def get_consumer_credentials(self):
256 return self.consumer_credentials
257
258 - def get_name(self):
259 return self.name
260
261 - def get_uuid(self):
262 return self.uuid
263
264 - def get_ownerid(self):
265 return self.ownerid
266
267 - def get_api_url(self):
268 return self.api_url
269
270 - def get_created(self):
271 return self.created
272
273 - def check_signature(self):
274 if self.signature and self.data: 275 certs = os.listdir(constants.CANDLEPIN_CA_CERT_DIR) 276 # At least one certificate has to match 277 for cert_name in certs: 278 cert_file = None 279 try: 280 try: 281 cert_file = open(constants.CANDLEPIN_CA_CERT_DIR + '/' + cert_name, 'r') 282 cert = X509.load_cert_string(cert_file.read()) 283 except (IOError, X509.X509Error): 284 continue 285 finally: 286 if cert_file is not None: 287 cert_file.close() 288 pubkey = cert.get_pubkey() 289 pubkey.reset_context(md='sha256') 290 pubkey.verify_init() 291 292 pubkey.verify_update(self.data) 293 if pubkey.verify_final(self.signature): 294 return True 295 return False
296 297
298 -class Entitlement(object):
299 - def __init__(self, products, credentials):
300 if products and credentials: 301 self.products = products 302 self.credentials = credentials 303 else: 304 raise IncorrectEntitlementError()
305
306 - def get_products(self):
307 return self.products
308
309 - def get_credentials(self):
310 return self.credentials
311 312
313 -class Credentials(object):
314 - def __init__(self, identifier, cert, key):
315 if identifier: 316 self.id = identifier 317 else: 318 raise IncorrectCredentialsError( 319 "ERROR: ID of credentials has to be defined" 320 ) 321 322 if cert and key: 323 self.cert = cert 324 self.key = key 325 else: 326 raise IncorrectCredentialsError( 327 "ERROR: Trying to create object with cert = %s and key = %s" 328 % (cert, key) 329 )
330
331 - def get_id(self):
332 return self.id
333
334 - def get_cert(self):
335 return self.cert
336
337 - def get_key(self):
338 return self.key
339 340
341 -class Product(object):
342 - def __init__(self, identifier):
343 try: 344 self.id = int(identifier) 345 except ValueError: 346 raise IncorrectProductError( 347 "ERROR: Invalid product id: %s" % identifier 348 ) 349 self.repositories = {}
350
351 - def get_id(self):
352 return self.id
353
354 - def get_repositories(self):
355 return self.repositories
356
357 - def add_repository(self, label, url):
358 self.repositories[label] = url
359 360
361 -class IncorrectProductError(Exception):
362 pass
363 364
365 -class IncorrectEntitlementError(Exception):
366 pass
367 368
369 -class IncorrectCredentialsError(Exception):
370 pass
371 372
373 -class IncorrectEntitlementsFileFormatError(Exception):
374 pass
375 376
377 -class MissingSatelliteCertificateError(Exception):
378 pass
379 380
381 -class ManifestValidationError(Exception):
382 pass
383 384
385 -class MissingConsumerInfoError(Exception):
386 pass
387 388
389 -class MissingMetaInfoError(Exception):
390 pass
391