def _prompt_unencrypt_context(request, ctx, callback_url, decode_data=True, decode_render=False): """ Takes care of prompting user for a password and returning an unencrypted version of a given context "data" section and "rendered" representation. Decoded data is returned as strings. No "unpickling" is performed """ resp = {} title = "Context encrypted" body = "The context information you are trying to use are encrypted with " \ "a private key. Please enter such key below to decrypt:" if "password" in request.POST: # POST already contains "unicode" data! pwd = request.POST["password"].encode("ascii", "ignore") if salt_context_key(ctx.id, pwd) == ctx.key: # Password is OK: decrypt if decode_data: resp["data"] = crypt.decrypt(base64.b64decode(str(ctx.data)), pwd) if decode_render: render = ContextStorage.objects.get(id=ctx.id) m = re.search(r"^ENCRYPTED:(.*)$", render.data) if m: resp["render"] = crypt.decrypt( base64.b64decode(str(m.group(1))), pwd) # Response empty in case of problems else: # Password is wrong resp["httpresp"] = render_password_prompt( request, title, body, callback_url, {"msg_error": "Wrong password"}) else: # Prompt for password resp["httpresp"] = render_password_prompt(request, title, body, callback_url) return resp
def _prompt_unencrypt_context(request, ctx, callback_url, decode_data=True, decode_render=False): """ Takes care of prompting user for a password and returning an unencrypted version of a given context "data" section and "rendered" representation. Decoded data is returned as strings. No "unpickling" is performed """ resp = {} title = "Context encrypted" body = "The context information you are trying to use are encrypted with " \ "a private key. Please enter such key below to decrypt:" if "password" in request.POST: # POST already contains "unicode" data! pwd = request.POST["password"].encode("ascii", "ignore") if salt_context_key(ctx.id, pwd) == ctx.key: # Password is OK: decrypt if decode_data: resp["data"] = crypt.decrypt( base64.b64decode(str(ctx.data)), pwd) if decode_render: render = ContextStorage.objects.get(id=ctx.id) m = re.search(r"^ENCRYPTED:(.*)$", render.data) if m: resp["render"] = crypt.decrypt( base64.b64decode(str(m.group(1))), pwd) # Response empty in case of problems else: # Password is wrong resp["httpresp"] = render_password_prompt( request, title, body, callback_url, {"msg_error": "Wrong password"} ) else: # Prompt for password resp["httpresp"] = render_password_prompt(request, title, body, callback_url) return resp
def _render_elastiq_plugin(resp): """ Given the clean data from the cluster form it returns a string, the elastiq-setup amiconfig plugin settings. """ plg = "" # Elastiq: Jobs per VM v = resp["elastiq"].get("n_jobs_per_vm", None) if v: plg += "elastiq_n_jobs_per_vm=%d\n" % v # Elastiq: VM deployment time v = resp["elastiq"].get("estimated_vm_deploy_time_s", None) if v: plg += "elastiq_estimated_vm_deploy_time_s=%d\n" % v # Elastiq: Queue / VM checking times v = resp["elastiq"].get("check_queue_every_s", None) if v: plg += "elastiq_check_queue_every_s=%d\n" % v v = resp["elastiq"].get("check_vms_every_s", None) if v: plg += "elastiq_check_vms_every_s=%d\n" % v # Elastiq: Idle time before killing v = resp["elastiq"].get("idle_for_time_s", None) if v: plg += "elastiq_idle_for_time_s=%d\n" % v # Elastiq: Minimum time a job is waiting v = resp["elastiq"].get("waiting_jobs_time_s", None) if v: plg += "elastiq_waiting_jobs_time_s=%d\n" % v # Elastiq: Batch sysrem v = resp["elastiq"].get("batch_plugin", None) if v: plg += "elastiq_batch_plugin=%s\n" % v # Quota plg += "quota_min_vms=%d\n" \ % resp["quota"].get("min_vms", 2) v = resp["quota"].get("max_vms", None) if v: plg += "quota_max_vms=%d\n" % v # EC2: API plg += "ec2_api_url=%s\n" \ % resp["ec2"]["api_url"] v = resp["ec2"].get("api_version", None) if v: plg += "ec2_api_version=%s\n" % v # EC2: Access key plg += "ec2_aws_access_key_id=%s\n" \ % resp["ec2"]["aws_access_key_id"] plg += "ec2_aws_secret_access_key=%s\n" \ % resp["ec2"]["aws_secret_access_key"] # EC2: Image plg += "ec2_image_id=%s\n" \ % resp["ec2"]["image_id"] # EC2: Flavor plg += "ec2_flavour=%s\n" % resp["ec2"]["flavour"] # EC2: Keypair v = resp["ec2"].get("key_name", None) if v: plg += "ec2_key_name=%s\n" % v # user-data of the Worker Context wc = ContextStorage.objects.get(id=resp["cluster"]["worker_context_id"]) if wc.is_encrypted: # You *must* handle exceptions in the caller wpwd = resp['cluster']['worker_context_pwd'] wcdef = ContextDefinition.objects.get(id=resp["cluster"]["worker_context_id"]) # Verify password before decrypting if salt_context_key(wcdef.id, wpwd) == wcdef.key: wc.decrypt(wpwd) else: raise cvmo_crypt.DecryptionError('Wrong password supplied') plg += "ec2_user_data_b64=%s\n" % base64.b64encode(wc.ec2_user_data) return ("elastiq-setup", plg)
def save(request): # For debug # post_dict = parser.parse( unicode(request.POST.urlencode()).encode("utf-8") ) # return uncache_response(HttpResponse(json.dumps(post_dict, indent=2), content_type="text/plain")) l = logging.getLogger("cvmo") # Validate request resp = _validate_for_save(request) if isinstance(resp, HttpResponse): return resp # For debug: output validated form # return uncache_response(HttpResponse(json.dumps(resp, indent=2), content_type="text/plain")) # # Preparing the full Master Context. # # This is a full new context available in ContextStorage, but with no corresponding # ContextDefinition. This context is the original Master Context plus some sections # appended. # # Note that the full Master Context is generated unencrypted, unless an appropriate # Cluster Password is provided, even if the original Master Context was secure. # try: (plg_name, plg_cont) = _render_elastiq_plugin(resp) except Exception: messages.error(request, 'Wrong Worker Context password supplied!') return _show_cluster_def(request, resp) # For debug: output generated extra section (with worker user-data embedded) # return uncache_response(HttpResponse(json.dumps({'plg_name':plg_name, 'plg_cont':plg_cont}, indent=2), content_type="text/plain")) master_ctx = ContextStorage.objects.get( id=resp["cluster"]["master_context_id"] ) if master_ctx.is_encrypted: try: mpwd = resp['cluster']['master_context_pwd'] mdef = ContextDefinition.objects.get( id=resp['cluster']['master_context_id'] ) # Verify password before decrypting if salt_context_key(mdef.id, mpwd) == mdef.key: master_ctx.decrypt(mpwd) else: raise Exception() except Exception: messages.error(request, 'Wrong Head Context password supplied!') return _show_cluster_def(request, resp) new_ud = _append_plugin_in_ud(master_ctx.ec2_user_data, plg_name, plg_cont) if not new_ud: messages.error( request, "Failed to append `elastiq-setup` plugin in master context!" ) l.log( logging.ERROR, "Failed to append `elastiq-setup` plugin in master context %s!" % resp["cluster"]["master_context_id"] ) return _show_cluster_def(request, resp) # Do we have a passphrase for the cluster? if 'passphrase' in resp['cluster']: passphrase = resp['cluster']['passphrase'] else: # Never None passphrase = '' # # Store the deployable context (rendered) # context_id = ContextDefinition.generate_new_id() cs = ContextStorage.create( context_id, "Cluster %s head node" % resp["cluster"]["name"], new_ud, master_ctx.root_ssh_key ) if passphrase != '': cs.encrypt( passphrase ) cs.save() # # Create the Cluster Definition # # First create a string representing the input data data = {} for k in [ 'ec2', 'quota', 'elastiq' ]: if k in resp: data[k] = resp[k] else: data[k] = {} # Let's store some cluster variables here as well data['passphrase'] = passphrase data_json_str = json.dumps(data, indent=2) #return uncache_response( HttpResponse( data_json_str, content_type='text/plain' ) ) # Encrypt the Cluster Definition with the given passphrase if passphrase != '': # To verify, we calculate the SHA1SUM of the unencrypted JSON blob and store it checksum = hashlib.sha1( data_json_str ).hexdigest() # Now, we encrypt data_json_str = base64.b64encode( cvmo_crypt.encrypt(data_json_str, passphrase) ) else: # Non-encrypted, plain text context checksum = '' cd = ClusterDefinition( name=resp["cluster"]["name"], description=resp["cluster"].get("description", None), owner=request.user, master_context=ContextDefinition.objects.get( id=resp["cluster"]["master_context_id"] ), worker_context=ContextDefinition.objects.get( id=resp["cluster"]["worker_context_id"] ), deployable_context=cs, data=data_json_str, encryption_checksum=checksum, ) cd.save() messages.success( request, "Cluster '%s' was successfully stored!" % cd.name ) return redirect("dashboard")
def webstart_req(request): """ Request a webstart of a context/config pair """ return HttpResponse(reverse("webapi_webstart_run")) # Get a logger log = logging.getLogger("cvmo.webapi") # Validate request if not "context" in request.GET: log.log(logging.ERROR, "`context` is required") raise SuspiciousOperation("`context` is required") if not "config" in request.GET: log.log(logging.ERROR, "`config` is required") raise SuspiciousOperation("`config` is required") ############################ # Render user data ############################ # Fetch context try: ctx = ContextDefinition.objects.get(id=request.GET['context']) except ContextDefinition.DoesNotExist: raise SuspiciousOperation("`context` is required") # Load the rendered context try: ctx_storage = ContextStorage.objects.get(id=request.GET['context']) ctx_storage_data = ctx_storage.data except ContextStorage.DoesNotExist: return HttpResponse("not-found-rendered", content_type="text/plain") # If the context is encrypted, prompt the user # for the password if ctx.key: # If we have password, continue if "password" in request.POST: # POST already contains "unicode" data! pwd = request.POST["password"].encode("ascii", "ignore") if salt_context_key(ctx.id, pwd) == ctx.key: # Descript and un-base64 m = re.search(r"^ENCRYPTED:(.*)$", ctx_storage_data) if m is None: return HttpResponse("render-format-error", content_type="text/plain") try: user_data = crypt.decrypt( base64.b64decode(str(m.group(1))), pwd) except: return HttpResponse("render-encoding-error", content_type="text/plain") else: # Render password prompt with error return render( request, "webapi/password_prompt.html", { "context": request.GET['context'], "config": request.GET['config'], "error": "Wrong password. Please try again" }) else: # Render password prompt return render( request, "webapi/password_prompt.html", { "context": request.GET['context'], "config": request.GET['config'], "error": "" }) else: # Un-base64 m = re.search(r"^\s*EC2_USER_DATA\s*=\s*([^\s]*)$", ctx_storage_data, re.M) if m is None: return HttpResponse("render-format-error", content_type="text/plain") try: user_data = base64.b64decode(str(m.group(1))) except: return HttpResponse("render-encoding-error", content_type="text/plain") ############################ # Fetch WebAPI configuration ############################ vm_config_id = int(request.GET['config']) try: vm_config = settings.WEBAPI_CONFIGURATIONS[vm_config_id] except IndexError: return HttpResponse("not-found-config", content_type="text/plain") ############################ # Prepare VMCP One-Time Tag ############################ # Create VMCP settings vmcp_settings = { 'name': '%s-%s' % (UNSAFE_CHARS.sub("_", ctx.name), "".join([ random.choice(string.digits + string.letters) for x in range(0, 10) ])), 'userData': user_data, 'memory': vm_config['memory'], 'cpus': vm_config['cpus'], 'disk': vm_config['disk_size'], 'cernvmVersion': settings.WEBAPI_UCERNVM_VERSION, 'flags': 0x31 } # Store json config on tag tag = WebAPIOneTimeTag(payload=json.dumps(vmcp_settings), uuid=uuid.uuid4().hex) tag.save() # Redirect to the HTTP version of webstart_run return redirect("%s?tag=%s" % (reverse("webapi_webstart_run"), tag.uuid))
def create(request): post_dict = parser.parse(unicode(request.POST.urlencode()).encode("utf-8")) # Just for debug # return uncache_response( HttpResponse( json.dumps(post_dict, indent=2), content_type='text/plain' ) ) # The values of all the plugins and the enabled plugins values = post_dict.get("values") enabled = post_dict.get("enabled") abstract = post_dict.get("abstract") # Generate a UUID for this context c_uuid = gen_context_key() # We need to generate hashes for passwords here: only on uCernVM for # compatibility reasons if "cvm_version" in values["general"] \ and values["general"]["cvm_version"] == "uCernVM": if "users" in values["general"]: for k, v in values["general"]["users"].iteritems(): # Don"t re-hash (useful when cloning) if not str(v["password"]).startswith("$6$"): # rounds=5000 is used to avoid the $round=xxx$ placed into # out string, see: # http://pythonhosted.org/passlib/lib/passlib.hash.sha256_crypt.html h = sha512_crypt.encrypt(str(v["password"]), salt_size=8, rounds=5000) values["general"]["users"][k]["password"] = h # Collect data to save. Non-indexed data is pickled raw_values = {"values": values, "enabled": enabled} if abstract is not None: raw_values["abstract"] = abstract from_abstract = True else: from_abstract = False # Prepare pickled data for easy reconstruction # (in case somebody wants to clone a template) c_values = pickle.dumps(raw_values) c_config = ContextPlugins().renderContext(c_uuid, values, enabled) # Generate checksum of the configuration c_checksum = hashlib.sha1(c_config).hexdigest() # Get the possible secret key c_key = "" if ("protect" in values) and (values["protect"]): c_key = str(values["secret"]) # If the content is encrypted process the data now if (c_key != ""): c_values = base64.b64encode(crypt.encrypt(c_values, c_key)) c_config = "ENCRYPTED:" + \ base64.b64encode(crypt.encrypt(c_config, c_key)) c_key = salt_context_key(c_uuid, c_key) # Check if this is public c_public = False if ("public" in values) and (values["public"]): c_public = True # Save context definition ContextDefinition.objects.create( id=c_uuid, name=tou(values["name"]), description=tou(values["description"]), owner=request.user, key=c_key, public=c_public, data=c_values, checksum=c_checksum, inherited=False, abstract=False, # only True for pure abstract contexts from_abstract=from_abstract) # Save context data (Should go to key/value store for speed-up) ContextStorage.objects.create(id=c_uuid, data=c_config) # Go to dashboard return redirect("dashboard")
def webstart_req(request): """ Request a webstart of a context/config pair """ return HttpResponse(reverse("webapi_webstart_run")) # Get a logger log = logging.getLogger("cvmo.webapi") # Validate request if not "context" in request.GET: log.log(logging.ERROR, "`context` is required") raise SuspiciousOperation("`context` is required") if not "config" in request.GET: log.log(logging.ERROR, "`config` is required") raise SuspiciousOperation("`config` is required") ############################ # Render user data ############################ # Fetch context try: ctx = ContextDefinition.objects.get(id=request.GET['context']) except ContextDefinition.DoesNotExist: raise SuspiciousOperation("`context` is required") # Load the rendered context try: ctx_storage = ContextStorage.objects.get(id=request.GET['context']) ctx_storage_data = ctx_storage.data except ContextStorage.DoesNotExist: return HttpResponse("not-found-rendered", content_type="text/plain") # If the context is encrypted, prompt the user # for the password if ctx.key: # If we have password, continue if "password" in request.POST: # POST already contains "unicode" data! pwd = request.POST["password"].encode("ascii", "ignore") if salt_context_key(ctx.id, pwd) == ctx.key: # Descript and un-base64 m = re.search(r"^ENCRYPTED:(.*)$", ctx_storage_data) if m is None: return HttpResponse("render-format-error", content_type="text/plain") try: user_data = crypt.decrypt( base64.b64decode(str(m.group(1))), pwd) except: return HttpResponse("render-encoding-error", content_type="text/plain") else: # Render password prompt with error return render( request, "webapi/password_prompt.html", { "context": request.GET['context'], "config": request.GET['config'], "error": "Wrong password. Please try again" } ) else: # Render password prompt return render( request, "webapi/password_prompt.html", { "context": request.GET['context'], "config": request.GET['config'], "error": "" } ) else: # Un-base64 m = re.search(r"^\s*EC2_USER_DATA\s*=\s*([^\s]*)$", ctx_storage_data, re.M) if m is None: return HttpResponse("render-format-error", content_type="text/plain") try: user_data = base64.b64decode(str(m.group(1))) except: return HttpResponse("render-encoding-error", content_type="text/plain") ############################ # Fetch WebAPI configuration ############################ vm_config_id = int(request.GET['config']) try: vm_config = settings.WEBAPI_CONFIGURATIONS[vm_config_id] except IndexError: return HttpResponse("not-found-config", content_type="text/plain") ############################ # Prepare VMCP One-Time Tag ############################ # Create VMCP settings vmcp_settings = { 'name' : '%s-%s' % ( UNSAFE_CHARS.sub("_", ctx.name), "".join([random.choice(string.digits + string.letters) for x in range(0,10)]) ), 'userData' : user_data, 'memory' : vm_config['memory'], 'cpus' : vm_config['cpus'], 'disk' : vm_config['disk_size'], 'cernvmVersion' : settings.WEBAPI_UCERNVM_VERSION, 'flags' : 0x31 } # Store json config on tag tag = WebAPIOneTimeTag( payload=json.dumps( vmcp_settings ), uuid=uuid.uuid4().hex ) tag.save() # Redirect to the HTTP version of webstart_run return redirect( "%s?tag=%s" % (reverse("webapi_webstart_run"), tag.uuid ) )
def create(request): post_dict = parser.parse( unicode(request.POST.urlencode()).encode("utf-8")) # Just for debug # return uncache_response( HttpResponse( json.dumps(post_dict, indent=2), content_type='text/plain' ) ) # The values of all the plugins and the enabled plugins values = post_dict.get("values") enabled = post_dict.get("enabled") abstract = post_dict.get("abstract") # Generate a UUID for this context c_uuid = gen_context_key() # We need to generate hashes for passwords here: only on uCernVM for # compatibility reasons if "cvm_version" in values["general"] \ and values["general"]["cvm_version"] == "uCernVM": if "users" in values["general"]: for k, v in values["general"]["users"].iteritems(): # Don"t re-hash (useful when cloning) if not str(v["password"]).startswith("$6$"): # rounds=5000 is used to avoid the $round=xxx$ placed into # out string, see: # http://pythonhosted.org/passlib/lib/passlib.hash.sha256_crypt.html h = sha512_crypt.encrypt( str(v["password"]), salt_size=8, rounds=5000 ) values["general"]["users"][k]["password"] = h # Collect data to save. Non-indexed data is pickled raw_values = {"values": values, "enabled": enabled} if abstract is not None: raw_values["abstract"] = abstract from_abstract = True else: from_abstract = False # Prepare pickled data for easy reconstruction # (in case somebody wants to clone a template) c_values = pickle.dumps(raw_values) c_config = ContextPlugins().renderContext(c_uuid, values, enabled) # Generate checksum of the configuration c_checksum = hashlib.sha1(c_config).hexdigest() # Get the possible secret key c_key = "" if ("protect" in values) and (values["protect"]): c_key = str(values["secret"]) # If the content is encrypted process the data now if (c_key != ""): c_values = base64.b64encode(crypt.encrypt(c_values, c_key)) c_config = "ENCRYPTED:" + \ base64.b64encode(crypt.encrypt(c_config, c_key)) c_key = salt_context_key(c_uuid, c_key) # Check if this is public c_public = False if ("public" in values) and (values["public"]): c_public = True # Save context definition ContextDefinition.objects.create( id=c_uuid, name=tou(values["name"]), description=tou(values["description"]), owner=request.user, key=c_key, public=c_public, data=c_values, checksum=c_checksum, inherited=False, abstract=False, # only True for pure abstract contexts from_abstract=from_abstract ) # Save context data (Should go to key/value store for speed-up) ContextStorage.objects.create( id=c_uuid, data=c_config ) # Go to dashboard return redirect("dashboard")