def index(): user = session['user'] start = request.args.get('start') end = request.args.get('end') if not start or not end: timedelta = 90 end = datetime.datetime.utcnow().strftime('%Y-%m-%d') start = (datetime.datetime.utcnow() - datetime.timedelta(days=timedelta)).strftime('%Y-%m-%d') user_kibana_url = False loadbalancer_dns_name = read_secretmanager.get_soca_configuration( )['LoadBalancerDNSName'] elastic_search_endpoint = read_secretmanager.get_soca_configuration( )['ESDomainEndpoint'] job_index = "https://" + elastic_search_endpoint + "/_search?q=type:index-pattern%20AND%20index-pattern.title:" + config.Config.KIBANA_JOB_INDEX get_job_index = get(job_index, verify=False) # nosec index_id = False if get_job_index.status_code == 200: response = json.loads(get_job_index.text) if len(response["hits"]["hits"]) == 0: pass elif len(response["hits"]["hits"]) == 1: index_id = response["hits"]["hits"][0]["_id"].split(":")[-1] else: flash( "More than 1 index has been detected when using the index name {}. Edit config.py and change KIBANA_JOB_INDEX to something more specific " .format(config.Config.KIBANA_JOB_INDEX), "error") elif get_job_index.status_code == 404: flash( "Job index cannot be found on your ElasticSearch. Please create it first: https://awslabs.github.io/scale-out-computing-on-aws/analytics/monitor-cluster-activity/ to setup your index", "error") else: flash( "Unable to query Elastic Search indices. Make sure this host has permission to query: {}" .format(job_index), "error") if index_id is False: flash( "Unable to retrieve index ID for {}. To do the initial setup, follow instructions available on <a href='https://awslabs.github.io/scale-out-computing-on-aws/analytics/monitor-cluster-activity/' target='_blank'>https://awslabs.github.io/scale-out-computing-on-aws/analytics/monitor-cluster-activity/</a>" .format(config.Config.KIBANA_JOB_INDEX)) user_kibana_url = "https://" + loadbalancer_dns_name + "/_plugin/kibana/" else: user_kibana_url = "https://" + loadbalancer_dns_name + "/_plugin/kibana/app/kibana#/discover?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:'" + start + "T00:00:00.000Z',to:'" + end + "T23:59:59.000Z'))&_a=(columns:!(_source),filters:!(),index:'" + index_id + "',interval:auto,query:(language:kuery,query:'user:"******"'),sort:!(!(start_iso,desc)))" return render_template('my_activity.html', user_kibana_url=user_kibana_url, user=user, start=start, client_ip=request.environ.get( 'HTTP_X_FORWARDED_FOR', request.remote_addr), end=end)
def generate_client(): dcv_session = request.args.get("session", None) if dcv_session is None: flash("Invalid graphical sessions", "error") return redirect("/remote_desktop") check_session = LinuxDCVSessions.query.filter_by(user=session["user"], session_number=dcv_session, is_active=True).first() if check_session: session_file = ''' [version] format=1.0 [connect] host=''' + read_secretmanager.get_soca_configuration()['LoadBalancerDNSName'] + ''' port=443 sessionid='''+check_session.session_id+''' user='''+session["user"]+''' authToken='''+check_session.dcv_authentication_token+''' weburlpath=/''' + check_session.session_host_private_dns + ''' ''' return Response( session_file, mimetype='text/txt', headers={'Content-disposition': 'attachment; filename=' + session['user'] + '_soca_' + str(dcv_session) + '.dcv'}) else: flash("Unable to retrieve this session. This session may have been terminated.", "error") return redirect("/remote_desktop")
def home(): scheduler_ip = read_secretmanager.get_soca_configuration( )['SchedulerPublicIP'] return render_template('ssh.html', user=session["user"], scheduler_ip=scheduler_ip, timestamp=datetime.datetime.utcnow().strftime("%s"))
def delete(): dcv_session = request.args.get("session", None) action = request.args.get("action", None) if action not in ["terminate", "stop", "hibernate"]: flash("action must be either terminate, stop or hibernate") return redirect("/remote_desktop") if dcv_session is None: flash("Invalid graphical session ID", "error") return redirect("/remote_desktop") check_session = LinuxDCVSessions.query.filter_by(user=session["user"], session_number=dcv_session, is_active=True).first() if check_session: instance_id = check_session.session_instance_id session_name = check_session.session_name base_os = check_session.session_linux_distribution if action == "hibernate": # Hibernate instance try: client_ec2.stop_instances(InstanceIds=[instance_id], Hibernate=True, DryRun=True) except ClientError as e: if e.response['Error'].get('Code') == 'DryRunOperation': client_ec2.stop_instances(InstanceIds=[instance_id], Hibernate=True) check_session.session_state = "stopped" db.session.commit() else: flash("Unable to hibernate instance ({}) due to {}".format(instance_id, e), "error") elif action == "stop": # Stop Instance try: client_ec2.stop_instances(InstanceIds=[instance_id], DryRun=True) except ClientError as e: if e.response['Error'].get('Code') == 'DryRunOperation': client_ec2.stop_instances(InstanceIds=[instance_id]) check_session.session_state = "stopped" db.session.commit() else: flash("Unable to Stop instance ({}) due to {}".format(instance_id, e), "error") else: # Terminate instance stack_name = str(read_secretmanager.get_soca_configuration()["ClusterId"] + "-" + session_name + "-" + session["user"]) try: client_cfn.delete_stack(StackName=stack_name) flash("Your graphical session is about to be terminated.", "success") check_session.is_active = False check_session.deactivated_on = datetime.utcnow() db.session.commit() return redirect("/remote_desktop") except ClientError as e: flash("Unable to delete associated stack ({}) due to {}".format(stack_name, e), "error") else: flash("Unable to retrieve this session", "error") return redirect("/remote_desktop")
def index(): get_desktops = get(config.Config.FLASK_ENDPOINT + "/api/dcv/desktops", headers={ "X-SOCA-USER": session["user"], "X-SOCA-TOKEN": session["api_key"] }, params={ "user": session["user"], "os": "linux", "is_active": "true" }, verify=False) # nosec if get_desktops.status_code == 200: user_sessions = get_desktops.json()["message"] user_sessions = {int(k): v for k, v in user_sessions.items() } # convert all keys (session number) back to integer else: flash(f"{get_desktops.json()}", "error") user_sessions = {} logger.info(user_sessions) max_number_of_sessions = config.Config.DCV_LINUX_SESSION_COUNT # List of instances not available for DCV. Adjust as needed blocked_instances = config.Config.DCV_RESTRICTED_INSTANCE_TYPE all_instances_available = client_ec2._service_model.shape_for( 'InstanceType').enum all_instances = [ p for p in all_instances_available if not any(substr in p for substr in blocked_instances) ] try: tz = pytz.timezone(config.Config.TIMEZONE) except pytz.exceptions.UnknownTimeZoneError: flash( "Timezone {} configured by the admin does not exist. Defaulting to UTC. Refer to https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a full list of supported timezones" .format(config.Config.TIMEZONE)) tz = pytz.timezone("UTC") server_time = (datetime.now( timezone.utc)).astimezone(tz).strftime("%Y-%m-%d (%A) %H:%M") return render_template( 'remote_desktop.html', user=session["user"], user_sessions=user_sessions, hibernate_idle_session=config.Config.DCV_LINUX_HIBERNATE_IDLE_SESSION, stop_idle_session=config.Config.DCV_LINUX_STOP_IDLE_SESSION, terminate_stopped_session=config.Config. DCV_LINUX_TERMINATE_STOPPED_SESSION, terminate_session=config.Config.DCV_LINUX_TERMINATE_STOPPED_SESSION, allow_instance_change=config.Config.DCV_LINUX_ALLOW_INSTANCE_CHANGE, page='remote_desktop', base_os=read_secretmanager.get_soca_configuration()['BaseOS'], all_instances=all_instances, max_number_of_sessions=max_number_of_sessions, server_time=server_time, server_timezone_human=config.Config.TIMEZONE, ami_list=get_ami_info())
def get_key(): type = request.args.get("type", None) ts = request.args.get("ts", None) if ts is None: return redirect("/ssh") if type is None or type not in ["pem", "ppk"]: return redirect("/ssh") user = session['user'] user_private_key_path = config.Config.USER_HOME + "/" + user + "/.ssh/id_rsa" if type == "pem": return send_file(user_private_key_path, as_attachment=True, attachment_filename=user + '_soca_privatekey.pem') else: user_private_key_path_ppk = '/apps/soca/' + read_secretmanager.get_soca_configuration( )['ClusterId'] + '/cluster_web_ui/' + config.Config.SSH_PRIVATE_KEY_LOCATION + '/' + user + '_soca_privatekey.ppk' generate_ppk = [ '/apps/soca/' + read_secretmanager.get_soca_configuration()['ClusterId'] + '/cluster_web_ui/unix/puttygen', user_private_key_path, '-o', user_private_key_path_ppk ] create_zip = subprocess.call(generate_ppk) if int(create_zip) != 0: flash("Unable to create the download archive, please try again", "error") logger.error("Unable to create zip. " + str(generate_ppk) + " : " + str(create_zip)) return redirect("/ssh") if os.path.exists(user_private_key_path_ppk): return send_file(user_private_key_path_ppk, as_attachment=True, attachment_filename=user + '_soca_privatekey.ppk') else: flash("Unable to locate the download archive, please try again", "error") logger.error("Unable to locate zip. " + str(generate_ppk) + " : " + str(create_zip)) return redirect("/ssh")
def post(self, session_number): """ Create a new DCV desktop session (Linux) --- tags: - DCV parameters: - in: body name: body schema: required: - instance_type - disk_size - session_number - instance_ami - subnet_id - hibernate properties: instance_type: type: string description: Type of EC2 instance to provision disk_size: type: string description: EBS size to provision for root device session_number: type: string description: DCV Session Number session_name: type: string description: DCV Session Name instance_ami: type: string description: Custom AMI to use subnet_id: type: string description: Specify a subnet id to launch the EC2 hibernate: type: string description: True/False. user: type: string description: owner of the session responses: 200: description: Pair of user/token is valid 401: description: Invalid user/token pair """ parser = reqparse.RequestParser() parser.add_argument("instance_type", type=str, location='form') parser.add_argument("disk_size", type=str, location='form') parser.add_argument("session_name", type=str, location='form') parser.add_argument("instance_ami", type=str, location='form') parser.add_argument("subnet_id", type=str, location='form') parser.add_argument("hibernate", type=str, location='form') args = parser.parse_args() logger.info(f"Received parameter for new Linux DCV session: {args}") if not args["subnet_id"]: args["subnet_id"] = False if session_number is None: return errors.all_errors( 'CLIENT_MISSING_PARAMETER', "session_number not found in URL. Endpoint is /api/dcv/desktop/<session_number>/linux" ) else: args["session_number"] = str(session_number) try: user = request.headers.get("X-SOCA-USER") if user is None: return errors.all_errors("X-SOCA-USER_MISSING") if not args["hibernate"]: args["hibernate"] = False elif args["hibernate"].lower() == "false": args["hibernate"] = False elif args["hibernate"].lower() == "true": args["hibernate"] = True else: return errors.all_errors( "DCV_LAUNCH_ERROR", f"hibernate must be either true or false") if args["instance_type"] is None: return errors.all_errors('CLIENT_MISSING_PARAMETER', "instance_type (str) is required.") args["disk_size"] = 30 if args["disk_size"] is None else args[ "disk_size"] try: args["disk_size"] = int(args["disk_size"]) except ValueError: return errors.all_errors("DCV_LAUNCH_ERROR", f"disk_size must be an integer") try: if int(args["session_number"]) > int( config.Config.DCV_LINUX_SESSION_COUNT): return errors.all_errors( "DCV_LAUNCH_ERROR", f"session_number {args['session_number']} is greater than the max number of session allowed ({config.Config.DCV_LINUX_SESSION_COUNT}). Contact admin for increase." ) except Exception as err: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Session Number {args['session_number']} must be a number. Err: {err}" ) session_uuid = str(uuid.uuid4()) region = os.environ["AWS_DEFAULT_REGION"] instance_type = args["instance_type"] soca_configuration = read_secretmanager.get_soca_configuration() instance_profile = soca_configuration[ "ComputeNodeInstanceProfileArn"] security_group_id = soca_configuration["ComputeNodeSecurityGroup"] if session_already_exist(args["session_number"]) is True: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Session Number {args['session_number']} is already used by an active desktop. Terminate it first before being able to use the same number" ) # sanitize session_name, limit to 255 chars if args["session_name"] is None: session_name = 'LinuxDesktop' + str(args["session_number"]) else: session_name = re.sub(r'\W+', '', args["session_name"])[:255] if session_name == "": # handle case when session name specified by user only contains invalid char session_name = 'LinuxDesktop' + str(args["session_number"]) if args["instance_ami"] is None or args["instance_ami"] == "base": image_id = soca_configuration["CustomAMI"] base_os = read_secretmanager.get_soca_configuration()['BaseOS'] else: if len(args["instance_ami"].split(",")) != 2: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Invalid format for instance_ami,base_os : {args['instance_ami']}" ) image_id = args["instance_ami"].split(',')[0] base_os = args["instance_ami"].split(',')[1] if not image_id.startswith("ami-"): return errors.all_errors( "DCV_LAUNCH_ERROR", f"AMI {image_id} does not seems to be valid. Must start with ami-<id>" ) else: if validate_ec2_image(image_id) is False: return errors.all_errors( "DCV_LAUNCH_ERROR", f"AMI {image_id} does not seems to be registered on SOCA. Refer to https://awslabs.github.io/scale-out-computing-on-aws/web-interface/create-virtual-desktops-images/" ) user_data = '''#!/bin/bash -x export PATH=$PATH:/usr/local/bin if [[ "''' + base_os + '''" == "centos7" ]] || [[ "''' + base_os + '''" == "rhel7" ]]; then yum install -y python3-pip PIP=$(which pip3) $PIP install awscli yum install -y nfs-utils # enforce install of nfs-utils else yum install -y python3-pip PIP=$(which pip3) $PIP install awscli fi if [[ "''' + base_os + '''" == "amazonlinux2" ]]; then /usr/sbin/update-motd --disable fi GET_INSTANCE_TYPE=$(curl http://169.254.169.254/latest/meta-data/instance-type) echo export "SOCA_DCV_AUTHENTICATOR="https://''' + soca_configuration[ 'SchedulerPrivateDnsName'] + ''':''' + config.Config.FLASK_PORT + '''/api/dcv/authenticator"" >> /etc/environment echo export "SOCA_DCV_SESSION_ID="''' + str( session_uuid ) + '''"" >> /etc/environment echo export "SOCA_CONFIGURATION="''' + str( soca_configuration['ClusterId'] ) + '''"" >> /etc/environment echo export "SOCA_DCV_OWNER="''' + user + '''"" >> /etc/environment echo export "SOCA_BASE_OS="''' + str( base_os ) + '''"" >> /etc/environment echo export "SOCA_JOB_TYPE="dcv"" >> /etc/environment echo export "SOCA_INSTALL_BUCKET="''' + str( soca_configuration['S3Bucket'] ) + '''"" >> /etc/environment echo export "SOCA_FSX_LUSTRE_BUCKET="false"" >> /etc/environment echo export "SOCA_FSX_LUSTRE_DNS="false"" >> /etc/environment echo export "SOCA_INSTALL_BUCKET_FOLDER="''' + str( soca_configuration['S3InstallFolder'] ) + '''"" >> /etc/environment echo export "SOCA_INSTANCE_TYPE=$GET_INSTANCE_TYPE" >> /etc/environment echo export "SOCA_HOST_SYSTEM_LOG="/apps/soca/''' + str( soca_configuration['ClusterId'] ) + '''/cluster_node_bootstrap/logs/desktop/''' + user + '''/''' + session_name + '''/$(hostname -s)"" >> /etc/environment echo export "AWS_DEFAULT_REGION="''' + region + '''"" >> /etc/environment echo export "SOCA_AUTH_PROVIDER="''' + str( soca_configuration['AuthProvider'] ).lower() + '''"" >> /etc/environment echo export "AWS_STACK_ID=${AWS::StackName}" >> /etc/environment echo export "AWS_DEFAULT_REGION=${AWS::Region}" >> /etc/environment # Required for proper EBS tagging echo export "SOCA_JOB_ID="''' + str( session_name ) + '''"" >> /etc/environment echo export "SOCA_JOB_OWNER="''' + user + '''"" >> /etc/environment echo export "SOCA_JOB_PROJECT="dcv"" >> /etc/environment echo export "SOCA_JOB_QUEUE="dcv"" >> /etc/environment source /etc/environment AWS=$(which aws) # Give yum permission to the user on this specific machine echo "''' + user + ''' ALL=(ALL) /bin/yum" >> /etc/sudoers mkdir -p /apps mkdir -p /data FS_DATA_PROVIDER=''' + soca_configuration[ 'FileSystemDataProvider'] + ''' FS_DATA=''' + soca_configuration['FileSystemData'] + ''' FS_APPS_PROVIDER=''' + soca_configuration[ 'FileSystemAppsProvider'] + ''' FS_APPS=''' + soca_configuration['FileSystemApps'] + ''' if [[ "$FS_DATA_PROVIDER" == "fsx_lustre" ]] || [[ "$FS_APPS_PROVIDER" == "fsx_lustre" ]]; then if [[ -z "$(rpm -qa lustre-client)" ]]; then # Install FSx for Lustre Client if [[ "$SOCA_BASE_OS" == "amazonlinux2" ]]; then amazon-linux-extras install -y lustre2.10 else kernel=$(uname -r) machine=$(uname -m) echo "Found kernel version: $kernel running on: $machine" yum -y install wget if [[ $kernel == *"3.10.0-957"*$machine ]]; then yum -y install https://downloads.whamcloud.com/public/lustre/lustre-2.10.8/el7/client/RPMS/x86_64/kmod-lustre-client-2.10.8-1.el7.x86_64.rpm yum -y install https://downloads.whamcloud.com/public/lustre/lustre-2.10.8/el7/client/RPMS/x86_64/lustre-client-2.10.8-1.el7.x86_64.rpm elif [[ $kernel == *"3.10.0-1062"*$machine ]]; then wget https://fsx-lustre-client-repo-public-keys.s3.amazonaws.com/fsx-rpm-public-key.asc -O /tmp/fsx-rpm-public-key.asc rpm --import /tmp/fsx-rpm-public-key.asc wget https://fsx-lustre-client-repo.s3.amazonaws.com/el/7/fsx-lustre-client.repo -O /etc/yum.repos.d/aws-fsx.repo sed -i 's#7#7.7#' /etc/yum.repos.d/aws-fsx.repo yum clean all yum install -y kmod-lustre-client lustre-client elif [[ $kernel == *"3.10.0-1127"*$machine ]]; then wget https://fsx-lustre-client-repo-public-keys.s3.amazonaws.com/fsx-rpm-public-key.asc -O /tmp/fsx-rpm-public-key.asc rpm --import /tmp/fsx-rpm-public-key.asc wget https://fsx-lustre-client-repo.s3.amazonaws.com/el/7/fsx-lustre-client.repo -O /etc/yum.repos.d/aws-fsx.repo sed -i 's#7#7.8#' /etc/yum.repos.d/aws-fsx.repo yum clean all yum install -y kmod-lustre-client lustre-client elif [[ $kernel == *"3.10.0-1160"*$machine ]]; then wget https://fsx-lustre-client-repo-public-keys.s3.amazonaws.com/fsx-rpm-public-key.asc -O /tmp/fsx-rpm-public-key.asc rpm --import /tmp/fsx-rpm-public-key.asc wget https://fsx-lustre-client-repo.s3.amazonaws.com/el/7/fsx-lustre-client.repo -O /etc/yum.repos.d/aws-fsx.repo yum clean all yum install -y kmod-lustre-client lustre-client elif [[ $kernel == *"4.18.0-193"*$machine ]]; then # FSX for Lustre on aarch64 is supported only on 4.18.0-193 wget https://fsx-lustre-client-repo-public-keys.s3.amazonaws.com/fsx-rpm-public-key.asc -O /tmp/fsx-rpm-public-key.asc rpm --import /tmp/fsx-rpm-public-key.asc wget https://fsx-lustre-client-repo.s3.amazonaws.com/centos/7/fsx-lustre-client.repo -O /etc/yum.repos.d/aws-fsx.repo yum clean all yum install -y kmod-lustre-client lustre-client else echo "ERROR: Can't install FSx for Lustre client as kernel version: $kernel isn't matching expected versions: (x86_64: 3.10.0-957, -1062, -1127, -1160, aarch64: 4.18.0-193)!" fi fi fi fi if [[ "$FS_DATA_PROVIDER" == "efs" ]]; then echo "$FS_DATA:/ /data nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport 0 0" >> /etc/fstab elif [[ "$FS_DATA_PROVIDER" == "fsx_lustre" ]]; then FSX_ID=$(echo $FS_DATA | cut -d. -f1) FSX_DATA_MOUNT_NAME=$($AWS fsx describe-file-systems --file-system-ids $FSX_ID --query FileSystems[].LustreConfiguration.MountName --output text) echo "$FS_DATA@tcp:/$FSX_DATA_MOUNT_NAME /data lustre defaults,noatime,flock,_netdev 0 0" >> /etc/fstab fi if [[ "$FS_APPS_PROVIDER" == "efs" ]]; then echo "$FS_APPS:/ /apps nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport 0 0" >> /etc/fstab elif [[ "$FS_APPS_PROVIDER" == "fsx_lustre" ]]; then FSX_ID=$(echo $FS_APPS | cut -d. -f1) FSX_APPS_MOUNT_NAME=$($AWS fsx describe-file-systems --file-system-ids $FSX_ID --query FileSystems[].LustreConfiguration.MountName --output text) echo "$FS_APPS@tcp:/$FSX_APPS_MOUNT_NAME /apps lustre defaults,noatime,flock,_netdev 0 0" >> /etc/fstab fi FS_MOUNT=0 mount -a while [[ $? -ne 0 ]] && [[ $FS_MOUNT -lt 5 ]] do SLEEP_TIME=$(( RANDOM % 60 )) echo "Failed to mount FS, retrying in $SLEEP_TIME seconds and Loop $FS_MOUNT/5..." sleep $SLEEP_TIME ((FS_MOUNT++)) mount -a done # Configure Chrony yum remove -y ntp yum install -y chrony mv /etc/chrony.conf /etc/chrony.conf.original echo -e """ # use the local instance NTP service, if available server 169.254.169.123 prefer iburst minpoll 4 maxpoll 4 # Use public servers from the pool.ntp.org project. # Please consider joining the pool (http://www.pool.ntp.org/join.html). # !!! [BEGIN] SOCA REQUIREMENT # You will need to open UDP egress traffic on your security group if you want to enable public pool #pool 2.amazon.pool.ntp.org iburst # !!! [END] SOCA REQUIREMENT # Record the rate at which the system clock gains/losses time. driftfile /var/lib/chrony/drift # Allow the system clock to be stepped in the first three updates # if its offset is larger than 1 second. makestep 1.0 3 # Specify file containing keys for NTP authentication. keyfile /etc/chrony.keys # Specify directory for log files. logdir /var/log/chrony # save data between restarts for fast re-load dumponexit dumpdir /var/run/chrony """ > /etc/chrony.conf systemctl enable chronyd # Prepare Log folder mkdir -p $SOCA_HOST_SYSTEM_LOG echo "@reboot /bin/bash /apps/soca/$SOCA_CONFIGURATION/cluster_node_bootstrap/ComputeNodePostReboot.sh >> $SOCA_HOST_SYSTEM_LOG/ComputeNodePostReboot.log 2>&1" | crontab - cp /apps/soca/$SOCA_CONFIGURATION/cluster_node_bootstrap/config.cfg /root/ /bin/bash /apps/soca/$SOCA_CONFIGURATION/cluster_node_bootstrap/ComputeNode.sh ''' + soca_configuration[ 'SchedulerPrivateDnsName'] + ''' >> $SOCA_HOST_SYSTEM_LOG/ComputeNode.sh.log 2>&1''' if args["hibernate"]: try: check_hibernation_support = client_ec2.describe_instance_types( InstanceTypes=[instance_type], Filters=[{ "Name": "hibernation-supported", "Values": ["true"] }]) logger.info( "Checking in {} support Hibernation : {}".format( instance_type, check_hibernation_support)) if len(check_hibernation_support["InstanceTypes"]) == 0: if config.Config.DCV_FORCE_INSTANCE_HIBERNATE_SUPPORT is True: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Sorry your administrator limited <a href='https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Hibernate.html' target='_blank'>DCV to instances that support hibernation mode</a> <br> Please choose a different type of instance." ) else: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Sorry you have selected {instance_type} with hibernation support, but this instance type does not support it. Either disable hibernation support or pick a different instance type" ) except ClientError as e: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Error while checking hibernation support due to {e}") launch_parameters = { "security_group_id": security_group_id, "instance_profile": instance_profile, "instance_type": instance_type, "soca_private_subnets": soca_configuration["PrivateSubnets"], "user_data": user_data, "subnet_id": args["subnet_id"], "image_id": image_id, "session_name": session_name, "session_uuid": session_uuid, "base_os": base_os, "disk_size": args["disk_size"], "cluster_id": soca_configuration["ClusterId"], "hibernate": args["hibernate"], "user": user, "DefaultMetricCollection": True if soca_configuration["DefaultMetricCollection"] == "true" else False, "SolutionMetricsLambda": soca_configuration['SolutionMetricsLambda'], "ComputeNodeInstanceProfileArn": soca_configuration["ComputeNodeInstanceProfileArn"] } dry_run_launch = can_launch_instance(launch_parameters) if dry_run_launch is True: launch_template = dcv_cloudformation_builder.main( **launch_parameters) if launch_template["success"] is True: cfn_stack_name = str(launch_parameters["cluster_id"] + "-" + launch_parameters["session_name"] + "-" + launch_parameters["user"]) cfn_stack_tags = [{ "Key": "soca:JobName", "Value": str(launch_parameters["session_name"]) }, { "Key": "soca:JobOwner", "Value": user }, { "Key": "soca:JobProject", "Value": "desktop" }, { "Key": "soca:ClusterId", "Value": str(launch_parameters["cluster_id"]) }, { "Key": "soca:NodeType", "Value": "dcv" }, { "Key": "soca:DCVSystem", "Value": base_os }] try: client_cfn.create_stack( StackName=cfn_stack_name, TemplateBody=launch_template["output"], Tags=cfn_stack_tags) except Exception as e: logger.error( f"Error while trying to provision {cfn_stack_name} due to {e}" ) return errors.all_errors( "DCV_LAUNCH_ERROR", f"Error while trying to provision {cfn_stack_name} due to {e}" ) else: return errors.all_errors("DCV_LAUNCH_ERROR", f"{launch_template['output']}") else: return errors.all_errors("DCV_LAUNCH_ERROR", f" Dry Run error: {dry_run_launch}") new_session = LinuxDCVSessions( user=user, session_number=args["session_number"], session_name=session_name, session_state="pending", session_host_private_dns=False, session_host_private_ip=False, session_instance_type=instance_type, session_linux_distribution=base_os, dcv_authentication_token=None, session_id=session_uuid, tag_uuid=session_uuid, session_token=str(uuid.uuid4()), is_active=True, support_hibernation=args["hibernate"], created_on=datetime.utcnow(), schedule_monday_start=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekdays"]["start"], schedule_tuesday_start=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekdays"]["start"], schedule_wednesday_start=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekdays"]["start"], schedule_thursday_start=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekdays"]["start"], schedule_friday_start=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekdays"]["start"], schedule_saturday_start=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekend"]["start"], schedule_sunday_start=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekend"]["start"], schedule_monday_stop=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekdays"]["stop"], schedule_tuesday_stop=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekdays"]["stop"], schedule_wednesday_stop=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekdays"]["stop"], schedule_thursday_stop=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekdays"]["stop"], schedule_friday_stop=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekdays"]["stop"], schedule_saturday_stop=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekend"]["stop"], schedule_sunday_stop=config.Config. DCV_LINUX_DEFAULT_SCHEDULE["weekend"]["stop"]) db.session.add(new_session) db.session.commit() return { "success": True, "message": f"Session {session_name} with ID {args['session_number']} started successfully." }, 200 except Exception as err: exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] logger.error(exc_type, fname, exc_tb.tb_lineno) return errors.all_errors(type(err).__name__, err)
def index(): user_sessions = {} for session_info in WindowsDCVSessions.query.filter_by( user=session["user"], is_active=True).all(): session_number = session_info.session_number session_state = session_info.session_state session_local_admin_password = session_info.session_local_admin_password tag_uuid = session_info.tag_uuid session_name = session_info.session_name session_host_private_dns = session_info.session_host_private_dns session_token = session_info.session_token session_instance_type = session_info.session_instance_type session_instance_id = session_info.session_instance_id session_schedule = { "monday": str(session_info.schedule_monday_start) + "-" + str(session_info.schedule_monday_stop), "tuesday": str(session_info.schedule_tuesday_start) + "-" + str(session_info.schedule_tuesday_stop), "wednesday": str(session_info.schedule_wednesday_start) + "-" + str(session_info.schedule_wednesday_stop), "thursday": str(session_info.schedule_thursday_start) + "-" + str(session_info.schedule_thursday_stop), "friday": str(session_info.schedule_friday_start) + "-" + str(session_info.schedule_friday_stop), "saturday": str(session_info.schedule_saturday_start) + "-" + str(session_info.schedule_saturday_stop), "sunday": str(session_info.schedule_sunday_start) + "-" + str(session_info.schedule_sunday_stop) } support_hibernation = session_info.support_hibernation dcv_authentication_token = session_info.dcv_authentication_token session_id = session_info.session_id stack_name = str( read_secretmanager.get_soca_configuration()["ClusterId"] + "-" + session_name + "-" + session["user"]) host_info = get_host_info( tag_uuid, read_secretmanager.get_soca_configuration()["ClusterId"]) logger.info(f"Host Info {host_info}") if not host_info: try: check_stack = client_cfn.describe_stacks(StackName=stack_name) logger.info(f"Host Info check_stack {check_stack}") if check_stack['Stacks'][0]['StackStatus'] in [ 'CREATE_FAILED', 'ROLLBACK_COMPLETE', 'ROLLBACK_FAILED' ]: logger.info(f"Host Info DEACTIVATE") # no host detected, session no longer active session_info.is_active = False session_info.deactivated_on = datetime.utcnow() db.session.commit() except Exception as err: logger.error( f"Error checking CFN stack {stack_name} due to {err}") session_info.is_active = False session_info.deactivated_on = datetime.utcnow() db.session.commit() else: # detected EC2 host for the session if not dcv_authentication_token: session_info.session_host_private_dns = host_info[ "private_dns"] session_info.session_host_private_ip = host_info["private_ip"] session_info.session_instance_id = host_info["instance_id"] authentication_data = json.dumps({ "system": "windows", "session_instance_id": host_info["instance_id"], "session_token": session_token, "session_user": session["user"] }) session_authentication_token = base64.b64encode( encrypt(authentication_data)).decode("utf-8") session_info.dcv_authentication_token = session_authentication_token db.session.commit() if "status" not in host_info.keys(): try: check_stack = client_cfn.describe_stacks(StackName=stack_name) logger.info(f"Host Info check_stack {check_stack}") if check_stack['Stacks'][0]['StackStatus'] in [ 'CREATE_FAILED', 'ROLLBACK_COMPLETE', 'ROLLBACK_FAILED' ]: logger.info(f"Host Info DEACTIVATE") # no host detected, session no longer active session_info.is_active = False session_info.deactivated_on = datetime.utcnow() db.session.commit() except Exception as err: logger.error( f"Error checking CFN stack {stack_name} due to {err}") session_info.is_active = False session_info.deactivated_on = datetime.utcnow() db.session.commit() else: if host_info["status"] in ["stopped", "stopping" ] and session_state != "stopped": session_state = "stopped" session_info.session_state = "stopped" db.session.commit() if session_state == "pending" and session_host_private_dns is not False: check_dcv_state = get( 'https://' + read_secretmanager.get_soca_configuration() ['LoadBalancerDNSName'] + '/' + session_host_private_dns + '/', allow_redirects=False, verify=False) logger.info("Checking {} for {} and received status {} ".format( 'https://' + read_secretmanager.get_soca_configuration() ['LoadBalancerDNSName'] + '/' + session_host_private_dns + '/', session_info, check_dcv_state.status_code)) if check_dcv_state.status_code == 200: session_info.session_state = "running" db.session.commit() user_sessions[session_number] = { "url": 'https://' + read_secretmanager.get_soca_configuration()['LoadBalancerDNSName'] + '/' + session_host_private_dns + '/', "session_local_admin_password": session_local_admin_password, "session_state": session_state, "session_authentication_token": dcv_authentication_token, "session_id": session_id, "session_name": session_name, "session_instance_id": session_instance_id, "session_instance_type": session_instance_type, "tag_uuid": tag_uuid, "support_hibernation": support_hibernation, "session_schedule": session_schedule } max_number_of_sessions = config.Config.DCV_WINDOWS_SESSION_COUNT # List of instances not available for DCV. Adjust as needed blacklist = config.Config.DCV_BLACKLIST_INSTANCE_TYPE all_instances_available = client_ec2._service_model.shape_for( 'InstanceType').enum all_instances = [ p for p in all_instances_available if not any(substr in p for substr in blacklist) ] try: tz = pytz.timezone(config.Config.TIMEZONE) except pytz.exceptions.UnknownTimeZoneError: flash( "Timezone {} configured by the admin does not exist. Defaulting to UTC. Refer to https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a full list of supported timezones" .format(config.Config.TIMEZONE)) tz = pytz.timezone("UTC") server_time = (datetime.now( timezone.utc)).astimezone(tz).strftime("%Y-%m-%d (%A) %H:%M") return render_template( 'remote_desktop_windows.html', user=session["user"], user_sessions=user_sessions, hibernate_idle_session=config.Config. DCV_WINDOWS_HIBERNATE_IDLE_SESSION, stop_idle_session=config.Config.DCV_WINDOWS_STOP_IDLE_SESSION, terminate_stopped_session=config.Config. DCV_WINDOWS_TERMINATE_STOPPED_SESSION, terminate_session=config.Config.DCV_WINDOWS_TERMINATE_STOPPED_SESSION, allow_instance_change=config.Config.DCV_WINDOWS_ALLOW_INSTANCE_CHANGE, page='remote_desktop', server_time=server_time, server_timezone_human=config.Config.TIMEZONE, all_instances=all_instances, max_number_of_sessions=max_number_of_sessions, ami_list=get_ami_info())
def create(): parameters = {} for parameter in [ "instance_type", "disk_size", "session_number", "session_name", "instance_ami", "hibernate", "subnet_id" ]: if not request.form[parameter]: parameters[parameter] = False else: if request.form[parameter].lower() in ["yes", "true"]: parameters[parameter] = True elif request.form[parameter].lower() in ["no", "false"]: parameters[parameter] = False else: parameters[parameter] = request.form[parameter] session_uuid = str(uuid.uuid4()) region = os.environ["AWS_DEFAULT_REGION"] instance_type = parameters["instance_type"] soca_configuration = read_secretmanager.get_soca_configuration() instance_profile = soca_configuration["ComputeNodeInstanceProfileArn"] security_group_id = soca_configuration["ComputeNodeSecurityGroup"] if parameters["subnet_id"] is not False: soca_private_subnets = [parameters["subnet_id"]] else: soca_private_subnets = [ soca_configuration["PrivateSubnet1"], soca_configuration["PrivateSubnet2"], soca_configuration["PrivateSubnet3"] ] # sanitize session_name, limit to 255 chars if parameters["session_name"] is False: session_name = 'WindowsDesktop' + str(parameters["session_number"]) else: session_name = re.sub(r'\W+', '', parameters["session_name"])[:255] if session_name == "": # handle case when session name specified by user only contains invalid char session_name = 'WindowsDesktop' + str(parameters["session_number"]) # Official DCV AMI # https://aws.amazon.com/marketplace/pp/B07TVL513S + https://aws.amazon.com/marketplace/pp/B082HYM34K # Non graphics is everything but g3/g4 if parameters["instance_ami"] == "base": dcv_windows_ami = config.Config.DCV_WINDOWS_AMI if instance_type.startswith("g"): if region not in dcv_windows_ami["graphics"].keys( ) and parameters["instance_ami"] is False: flash( "Sorry, Windows Desktop is not available on your AWS region. Base AMI are only available on {}" .format(dcv_windows_ami["graphics"].keys()), "error") return redirect("/remote_desktop_windows") else: image_id = dcv_windows_ami["graphics"][region] else: if region not in dcv_windows_ami["non-graphics"].keys( ) and parameters["instance_ami"] is False: flash( "Sorry, Windows Desktop is not available on your AWS region. Base AMI are only available on {}" .format(dcv_windows_ami["non-graphics"].keys()), "error") return redirect("/remote_desktop_windows") else: image_id = dcv_windows_ami["non-graphics"][region] else: image_id = parameters["instance_ami"] if not image_id.startswith("ami-"): flash( "AMI selectioned {} does not seems to be valid. Must start with ami-<id>" .format(image_id), "error") return redirect("/remote_desktop_windows") digits = ([ random.choice(''.join(random.choice(string.digits) for _ in range(10))) for _ in range(3) ]) uppercase = ([ random.choice(''.join( random.choice(string.ascii_uppercase) for _ in range(10))) for _ in range(3) ]) lowercase = ([ random.choice(''.join( random.choice(string.ascii_lowercase) for _ in range(10))) for _ in range(3) ]) pw = digits + uppercase + lowercase session_local_admin_password = ''.join(random.sample(pw, len(pw))) user_data_script = open( "/apps/soca/" + soca_configuration["ClusterId"] + "/cluster_node_bootstrap/windows/ComputeNodeInstallDCVWindows.ps", "r") user_data = user_data_script.read() user_data_script.close() user_data = user_data.replace("%SOCA_LOCAL_ADMIN_PASSWORD%", session_local_admin_password) user_data = user_data.replace( "%SOCA_SchedulerPrivateIP%", soca_configuration['SchedulerPrivateIP'] + ":" + str(config.Config.FLASK_PORT)) user_data = user_data.replace("%SOCA_LoadBalancerDNSName%", soca_configuration['LoadBalancerDNSName']) user_data = user_data.replace("%SOCA_LOCAL_USER%", session["user"]) if config.Config.DCV_WINDOWS_AUTOLOGON is True: user_data = user_data.replace("%SOCA_WINDOWS_AUTOLOGON%", "true") else: user_data = user_data.replace("%SOCA_WINDOWS_AUTOLOGON%", "false") check_hibernation_support = client_ec2.describe_instance_types( InstanceTypes=[instance_type], Filters=[{ "Name": "hibernation-supported", "Values": ["true"] }]) logger.info("Checking in {} support Hibernation : {}".format( instance_type, check_hibernation_support)) if len(check_hibernation_support["InstanceTypes"]) == 0: if config.Config.DCV_FORCE_INSTANCE_HIBERNATE_SUPPORT is True: flash( "Sorry your administrator limited <a href='https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/Hibernate.html#hibernating-prerequisites' target='_blank'>DCV to instances that support hibernation mode</a> <br> Please choose a different type of instance." ) return redirect("/remote_desktop_windows") else: hibernate_support = False else: hibernate_support = True if parameters["hibernate"] and not hibernate_support: flash( "Sorry you have selected {} with hibernation support, but this instance type does not support it. Either disable hibernation support or pick a different instance type" .format(instance_type), "error") return redirect("/remote_desktop_windows") launch_parameters = { "security_group_id": security_group_id, "instance_profile": instance_profile, "instance_type": instance_type, "soca_private_subnets": soca_private_subnets, "user_data": user_data, "image_id": image_id, "session_name": session_name, "session_uuid": session_uuid, "base_os": "windows", "disk_size": parameters["disk_size"], "cluster_id": soca_configuration["ClusterId"], "hibernate": parameters["hibernate"], "user": session["user"], "DefaultMetricCollection": True if soca_configuration["DefaultMetricCollection"] == "true" else False, "SolutionMetricLambda": soca_configuration['SolutionMetricLambda'], "ComputeNodeInstanceProfileArn": soca_configuration["ComputeNodeInstanceProfileArn"] } dry_run_launch = can_launch_instance(launch_parameters) if dry_run_launch is True: launch_template = dcv_cloudformation_builder.main(**launch_parameters) if launch_template["success"] is True: cfn_stack_name = str(launch_parameters["cluster_id"] + "-" + launch_parameters["session_name"] + "-" + launch_parameters["user"]) cfn_stack_tags = [{ "Key": "soca:JobName", "Value": str(launch_parameters["session_name"]) }, { "Key": "soca:JobOwner", "Value": str(session["user"]) }, { "Key": "soca:JobProject", "Value": "desktop" }, { "Key": "soca:ClusterId", "Value": str(launch_parameters["cluster_id"]) }, { "Key": "soca:NodeType", "Value": "dcv" }, { "Key": "soca:DCVSystem", "Value": "windows" }] try: client_cfn.create_stack(StackName=cfn_stack_name, TemplateBody=launch_template["output"], Tags=cfn_stack_tags) except Exception as e: logger.error( f"Error while trying to provision {cfn_stack_name} due to {e}" ) flash( f"Error while trying to provision {cfn_stack_name} due to {e}" ) return redirect("/remote_desktop_windows") else: flash(launch_template["output"], "error") return redirect("/remote_desktop_windows") else: flash(dry_run_launch, "error") return redirect("/remote_desktop_windows") flash( "Your session has been initiated. It will be ready within 10 minutes.", "success") new_session = WindowsDCVSessions( user=session["user"], session_number=parameters["session_number"], session_name=session_name, session_state="pending", session_host_private_dns=False, session_host_private_ip=False, session_instance_type=instance_type, dcv_authentication_token=None, session_local_admin_password=session_local_admin_password, session_id="console", tag_uuid=session_uuid, session_token=str(uuid.uuid4()), is_active=True, support_hibernation=parameters["hibernate"], created_on=datetime.utcnow(), schedule_monday_start=config.Config.DCV_WINDOWS_DEFAULT_SCHEDULE_START, schedule_tuesday_start=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE_START, schedule_wednesday_start=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE_START, schedule_thursday_start=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE_START, schedule_friday_start=config.Config.DCV_WINDOWS_DEFAULT_SCHEDULE_START, schedule_saturday_start=0, schedule_sunday_start=0, schedule_monday_stop=config.Config.DCV_WINDOWS_DEFAULT_SCHEDULE_STOP, schedule_tuesday_stop=config.Config.DCV_WINDOWS_DEFAULT_SCHEDULE_STOP, schedule_wednesday_stop=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE_STOP, schedule_thursday_stop=config.Config.DCV_WINDOWS_DEFAULT_SCHEDULE_STOP, schedule_friday_stop=config.Config.DCV_WINDOWS_DEFAULT_SCHEDULE_STOP, schedule_saturday_stop=0, schedule_sunday_stop=0, ) db.session.add(new_session) db.session.commit() return redirect("/remote_desktop_windows")
def index(): loadbalancer_dns_name = read_secretmanager.get_soca_configuration()['LoadBalancerDNSName'] kibana_url = "https://" + loadbalancer_dns_name + "/_plugin/kibana/" return render_template("dashboard.html", kibana_url=kibana_url)
def home(): scheduler_ip = read_secretmanager.get_soca_configuration()['SchedulerIP'] return render_template('sftp.html', scheduler_ip=scheduler_ip, user=session["user"])
def download_all(): path = request.args.get("path", None) if path is None: return redirect("/my_files") allow_download = config.Config.ALLOW_DOWNLOAD_FROM_PORTAL if allow_download is not True: flash( " Download file is disabled. Please contact your SOCA cluster administrator" ) return redirect("/my_files") filesystem = {} try: for entry in os.scandir(path): if not entry.name.startswith("."): if entry.is_dir(): # Ignore folder. We only include files pass else: filesystem[entry.name] = { "path": path + "/" + entry.name, "uid": encrypt(path + "/" + entry.name, entry.stat().st_size)["message"], "type": "file", "st_size": convert_size(entry.stat().st_size), "st_size_default": entry.stat().st_size, "st_mtime": entry.stat().st_mtime } except Exception as err: if err.errno == errno.EPERM: flash( "Sorry we could not access this location due to a permission error. If you recently changed the permissions, please allow up to 10 minutes for sync.", "error") elif err.errno == errno.ENOENT: flash("Could not locate the directory. Did you delete it ?", "error") else: flash("Could not locate the directory: " + str(err), "error") return redirect("/my_files") valid_file_path = [] total_size = 0 total_files = 0 for file_name, file_info in filesystem.items(): if user_has_permission(file_info["path"], "read", "file") is False: flash( "You are not authorized to download some files (double check if your user own ALL files in this directory)." ) return redirect("/my_files") valid_file_path.append(file_info["path"]) total_size = total_size + file_info["st_size_default"] total_files = total_files + 1 if total_size > config.Config.MAX_ARCHIVE_SIZE: flash( "Sorry, the maximum archive size is {:.2f} MB. Your archive was {:.2f} MB. To avoid this issue, you can create a smaller archive, download files individually, use SFTP or edit the maximum archive size authorized." .format(config.Config.MAX_ARCHIVE_SIZE / 1024 / 1024, total_size / 1024 / 1024), "error") return redirect("/my_files") if valid_file_path.__len__() == 0: return redirect("/my_files") ts = datetime.datetime.utcnow().strftime("%s") archive_name = "/apps/soca/" + read_secretmanager.get_soca_configuration( )["ClusterId"] + "/cluster_web_ui/tmp/zip_downloads/SOCA_Download_" + session[ "user"] + "_" + ts + ".zip" zipf = zipfile.ZipFile(archive_name, 'w', zipfile.ZIP_DEFLATED) logger.info("About to create archive: " + str(archive_name) + " with the following files: " + str(valid_file_path)) try: for file_to_zip in valid_file_path: zipf.write(file_to_zip) zipf.close() logger.info("Archive created") except Exception as err: logger("Unable to create archive due to: " + str(err)) flash( "Unable to generate download link. Check the logs for more information", "error") return redirect("/my_files") if os.path.exists(archive_name): return send_file(archive_name, mimetype='zip', attachment_filename=archive_name.split("/")[-1], as_attachment=True) else: flash("Unable to locate the download archive, please try again", "error") logger.error("Unable to locate " + str(archive_name)) return redirect("/my_files")
class Config(object): soca_config = read_secretmanager.get_soca_configuration() # APP DEBUG = False USE_PERMANENT_SESSION = True PERMANENT_SESSION_LIFETIME = timedelta(days=1) SESSION_TYPE = "sqlalchemy" SQLALCHEMY_TRACK_MODIFICATIONS = False SESSION_SQLALCHEMY_TABLE = "flask_sessions" SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(basedir, "db.sqlite") SECRET_KEY = os.environ["SOCA_FLASK_SECRET_KEY"] API_ROOT_KEY = os.environ["SOCA_FLASK_API_ROOT_KEY"] SOCA_DATA_SHARING_SYMMETRIC_KEY = os.environ["SOCA_FLASK_FERNET_KEY"] TIMEZONE = "UTC" # Change to match your local timezone if needed. See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for all TZ # WEB APPS_LOCATION = "/apps/" USER_HOME = "/data/home" CHROOT_USER = False # if True, user can only access their $HOME directory (aka: USER_HOME/<user>) PATH_TO_RESTRICT = [ '/bin', '/boot', '/dev', '/etc', '/home', '/lib', '/lib64', '/local', '/media', '/opt', '/proc', '/root', '/run', '/sbin', '/srv', '/sys', '/tmp', '/usr', # nosec '/var' ] # List of folders not accessible via the web ui DEFAULT_CACHE_TIME = 120 # 2 minutes. Change this value to optimize performance in case you have a large number of concurrent user MAX_UPLOAD_FILE = 5120 # 5 GB MAX_UPLOAD_TIMEOUT = 1800000 # 30 minutes ALLOW_DOWNLOAD_FROM_PORTAL = True # Give user ability to download files from the web portal MAX_SIZE_ONLINE_PREVIEW = 150000000 # in bytes (150mb by default), maximum size of file that can be visualized via the web editor MAX_ARCHIVE_SIZE = 150000000 # in bytes (150mb by default), maximum size of archive generated when downloading multiple files at once DAILY_BACKUP_COUNT = 15 # Keep 15 latest daily backups KIBANA_JOB_INDEX = "soca-jobs*" # Default index to look for /my_activity. Change it something more specific if using more than 1 index with name ~ "job*" # UWSGI SETTINGS FLASK_HOST = "127.0.0.1" FLASK_PROTOCOL = "https://" FLASK_PORT = "8443" FLASK_ENDPOINT = FLASK_PROTOCOL + FLASK_HOST + ":" + FLASK_PORT # COGNITO ENABLE_SSO = False COGNITO_OAUTH_AUTHORIZE_ENDPOINT = "https://<YOUR_COGNITO_DOMAIN_NAME>.auth.<YOUR_REGION>.amazoncognito.com/oauth2/authorize" COGNITO_OAUTH_TOKEN_ENDPOINT = "https://<YOUR_COGNITO_DOMAIN_NAME>.auth.<YOUR_REGION>.amazoncognito.com/oauth2/token" COGNITO_JWS_KEYS_ENDPOINT = "https://cognito-idp.<YOUR_REGION>.amazonaws.com/<YOUR_REGION>_<YOUR_ID>/.well-known/jwks.json" COGNITO_APP_SECRET = "<YOUR_APP_SECRET>" COGNITO_APP_ID = "<YOUR_APP_ID>" COGNITO_ROOT_URL = "<YOUR_WEB_URL>" COGNITO_CALLBACK_URL = "<YOUR_CALLBACK_URL>" # DCV General DCV_AUTH_DIR = "/var/run/dcvsimpleextauth" DCV_SIMPLE_AUTH = "/usr/libexec/dcvsimpleextauth.py" DCV_SESSION_LOCATION = "tmp/dcv_sessions" DCV_FORCE_INSTANCE_HIBERNATE_SUPPORT = False # If True, users can only provision instances that support hibernation DCV_TOKEN_SYMMETRIC_KEY = os.environ[ "SOCA_DCV_TOKEN_SYMMETRIC_KEY"] # used to encrypt/decrypt and validate DCV session auth DCV_RESTRICTED_INSTANCE_TYPE = [ 'metal', 'nano', 'micro', 'p3', 'p2', 'p3dn', 'g2' ] # This instance type won't be visible on the dropdown menu DCV_IDLE_CPU_THRESHOLD = 15 # SOCA will NOT hibernate/stop an instance if current CPU usage % is over this value # DCV Linux DCV_LINUX_SESSION_COUNT = 4 DCV_LINUX_ALLOW_INSTANCE_CHANGE = True # Allow user to change their instance type if their DCV session is stopped DCV_LINUX_HIBERNATE_IDLE_SESSION = 1 # In hours. Linux DCV sessions will be hibernated to save cost if there is no active connection within the time specified. 0 to disable DCV_LINUX_STOP_IDLE_SESSION = 1 # In hours. Linux DCV sessions will be stopped to save cost if there is no active connection within the time specified. 0 to disable DCV_LINUX_TERMINATE_STOPPED_SESSION = 0 # In hours. Stopped Linux DCV will be permanently terminated if user won't restart it within the time specified. 0 to disable DCV_LINUX_DEFAULT_SCHEDULE = { "weekdays": { "start": 480, # Default Schedule - Start 8 AM (8*60) mon-fri "stop": 1140 # Default Schedule - Stop if idle after 7 PM (19*60) mon-fri }, "weekend": { "start": 0, # Default Schedule - Stopped by default sat-sun "stop": 0 # Default Schedule - Stopped by default sat-sun } } # DCV Windows DCV_WINDOWS_SESSION_COUNT = 4 DCV_WINDOWS_ALLOW_INSTANCE_CHANGE = True # Allow user to change their instance type if their DCV session is stopped DCV_WINDOWS_HIBERNATE_IDLE_SESSION = 1 # In hours. Windows DCV sessions will be hibernated to save cost if there is no active connection within the time specified. 0 to disable DCV_WINDOWS_STOP_IDLE_SESSION = 1 # In hours. Windows DCV sessions will be stopped to save cost if there is no active connection within the time specified. 0 to disable DCV_WINDOWS_TERMINATE_STOPPED_SESSION = 0 # In hours. Stopped Windows DCV will be permanently terminated if user won't restart it within the time specified. 0 to disable DCV_WINDOWS_AUTOLOGON = True # enable or disable autologon. If disabled user will have to manually input Windows password DCV_WINDOWS_DEFAULT_SCHEDULE = { "weekdays": { "start": 480, # Default Schedule - Start 8 AM (8*60) mon-fri "stop": 1140 # Default Schedule - Stop if idle after 7 PM (19*60) mon-fri }, "weekend": { "start": 0, # Default Schedule - Stopped by default sat-sun "stop": 0 # Default Schedule - Stopped by default sat-sun } } DCV_WINDOWS_AMI = { "graphics-amd": { 'us-east-1': 'ami-09e9fc6b0563179e0', 'ca-central-1': 'ami-02ce5abc7648ae028', 'us-east-2': 'ami-0e9dffe211d55ea3d', 'us-west-2': 'ami-0ca9facae744b755d', 'eu-west-1': 'ami-0b79b4e3a40bdf60e', 'eu-west-2': 'ami-049344b657f4fb45c', 'eu-central-1': 'ami-01f877801cd06f23f', 'ap-northeast-1': 'ami-0f73f5c42d0a2a659' }, "graphics": { # Nvidia 'us-east-1': 'ami-0feb4b3151fb93b8e', 'ca-central-1': 'ami-01a3418587fbf46cb', 'us-east-2': 'ami-08dbaa5a7a0d66dd8', 'us-west-1': 'ami-044ba431fff097d34', 'us-west-2': 'ami-05f7fcc83babd18eb', 'eu-west-1': 'ami-0d0fcc08444568042', 'eu-west-2': 'ami-0567a305f99abe138', 'eu-west-3': 'ami-0df87cbfbf5e29a6d', 'eu-central-1': 'ami-042ee7fefd2fbce6c', 'eu-north-1': 'ami-095afbb53221a2d16', 'ap-northeast-1': 'ami-04bcb220c319e2d0a', 'ap-northeast-2': 'ami-0af10927eaebde20b', 'ap-southeast-1': 'ami-068263e9c9ca3d9dc', 'ap-southeast-2': 'ami-07ce6711013585e48', 'ap-south-1': 'ami-0d4304f1bb621ae46', 'sa-east-1': 'ami-0c297587e3093aa24' }, "non-graphics": { "us-east-1": "ami-0d9299304a5e3cfea", "ca-central-1": "ami-072dfac76bbba7a11", "us-east-2": "ami-08ad93b85eb93fc5c", "us-west-1": "ami-084e38470f6754074", "us-west-2": "ami-0dab9e46b5aa47961", "eu-west-1": "ami-01f10101bddf8ecb8", "eu-west-2": "ami-0c57e129d10d47c39", "eu-west-3": "ami-0433843d3f015e124", "eu-central-1": "ami-01d59b9c6a0031789", "eu-north-1": "ami-04994358f2a0bd112", "ap-northeast-1": "ami-0cebe66543ec56ce2", "ap-northeast-2": "ami-0825e91c25df5a3a1", "ap-southeast-1": "ami-08e0bf49e1b2bbd6c", "ap-southeast-2": "ami-0058a6d6659ce124f", "ap-south-1": "ami-074192a64ff10aa67", "sa-east-1": "ami-02938726ac94b1cea", } } SOCA_AUTH_PROVIDER = os.environ.get("SOCA_AUTH_PROVIDER") if SOCA_AUTH_PROVIDER == "openldap": # LDAP LDAP_HOST = soca_config["LdapHost"] LDAP_BASE_DN = soca_config["LdapBase"] LDAP_ADMIN_PASSWORD_FILE = "/root/OpenLdapAdminPassword.txt" LDAP_ADMIN_USERNAME_FILE = "/root/OpenLdapAdminUsername.txt" ROOT_DN = 'CN=' + open( LDAP_ADMIN_USERNAME_FILE, 'r').read().rstrip().lstrip() + ',' + LDAP_BASE_DN ROOT_PW = open(LDAP_ADMIN_PASSWORD_FILE, 'r').read().rstrip().lstrip() else: DOMAIN_NAME = soca_config["DSDomainName"] DIRECTORY_SERVICE_ID = soca_config["DSDirectoryId"] ROOT_USER = soca_config["DSDomainAdminUsername"] ROOT_PW = soca_config["DSDomainAdminPassword"] LDAP_BASE = soca_config["DSDomainBase"] NETBIOS = soca_config["DSDomainNetbios"] DIRECTORY_SERVICE_RESET_LAMBDA_ARN = soca_config[ "DSResetLambdaFunctionArn"] SUDOERS_GROUP = "AWS Delegated Administrators" SUDOERS_GROUP_DN = f"CN={SUDOERS_GROUP},OU=AWS Delegated Groups,{LDAP_BASE}" # With AD, user and group share the same OU (Domain Users). # To identify group/user, group associated to "user" will be named "user<GROUP_NAME_SUFFIX>" GROUP_NAME_SUFFIX = "socagroup" # PBS PBS_QSTAT = "/opt/pbs/bin/qstat" PBS_QDEL = "/opt/pbs/bin/qdel" PBS_QSUB = "/opt/pbs/bin/qsub" PBS_QMGR = "/opt/pbs/bin/qmgr" # SSH SSH_PRIVATE_KEY_LOCATION = "tmp/ssh"
def create(): parameters = {} for parameter in [ "instance_type", "disk_size", "session_number", "session_name", "instance_ami", "hibernate" ]: if not request.form[parameter]: parameters[parameter] = False else: if request.form[parameter].lower() in ["yes", "true"]: parameters[parameter] = True elif request.form[parameter].lower() in ["no", "false"]: parameters[parameter] = False else: parameters[parameter] = request.form[parameter] session_uuid = str(uuid.uuid4()) region = os.environ["AWS_DEFAULT_REGION"] instance_type = parameters["instance_type"] soca_configuration = read_secretmanager.get_soca_configuration() instance_profile = soca_configuration["ComputeNodeInstanceProfileArn"] security_group_id = soca_configuration["ComputeNodeSecurityGroup"] soca_private_subnets = [ soca_configuration["PrivateSubnet1"], soca_configuration["PrivateSubnet2"], soca_configuration["PrivateSubnet3"] ] # sanitize session_name, limit to 255 chars if parameters["session_name"] is False: session_name = 'LinuxDesktop' + str(parameters["session_number"]) else: session_name = re.sub(r'\W+', '', parameters["session_name"])[:255] if session_name == "": # handle case when session name specified by user only contains invalid char session_name = 'LinuxDesktop' + str(parameters["session_number"]) if parameters["instance_ami"] == "base": image_id = soca_configuration["CustomAMI"] base_os = read_secretmanager.get_soca_configuration()['BaseOS'] else: if len(parameters["instance_ami"].split(",")) != 2: flash( "Invalid format for instance_ami,base_os : {}".format( parameters["instance_ami"]), "error") return redirect("/remote_desktop") image_id = parameters["instance_ami"].split(',')[0] base_os = parameters["instance_ami"].split(',')[1] if not image_id.startswith("ami-"): flash( "AMI selectioned {} does not seems to be valid. Must start with ami-<id>" .format(image_id), "error") return redirect("/remote_desktop") user_data = '''#!/bin/bash -x export PATH=$PATH:/usr/local/bin if [[ "''' + base_os + '''" == "centos7" ]] || [[ "''' + base_os + '''" == "rhel7" ]]; then yum install -y python3-pip PIP=$(which pip3) $PIP install awscli yum install -y nfs-utils # enforce install of nfs-utils else yum install -y python3-pip PIP=$(which pip3) $PIP install awscli fi if [[ "''' + base_os + '''" == "amazonlinux2" ]]; then /usr/sbin/update-motd --disable fi GET_INSTANCE_TYPE=$(curl http://169.254.169.254/latest/meta-data/instance-type) echo export "SOCA_DCV_AUTHENTICATOR="https://''' + soca_configuration[ 'SchedulerPrivateDnsName'] + ''':''' + config.Config.FLASK_PORT + '''/api/dcv/authenticator"" >> /etc/environment echo export "SOCA_DCV_SESSION_ID="''' + str( session_uuid ) + '''"" >> /etc/environment echo export "SOCA_CONFIGURATION="''' + str( soca_configuration['ClusterId'] ) + '''"" >> /etc/environment echo export "SOCA_DCV_OWNER="''' + str( session["user"] ) + '''"" >> /etc/environment echo export "SOCA_BASE_OS="''' + str(base_os) + '''"" >> /etc/environment echo export "SOCA_JOB_TYPE="dcv"" >> /etc/environment echo export "SOCA_INSTALL_BUCKET="''' + str( soca_configuration['S3Bucket'] ) + '''"" >> /etc/environment echo export "SOCA_FSX_LUSTRE_BUCKET="false"" >> /etc/environment echo export "SOCA_FSX_LUSTRE_DNS="false"" >> /etc/environment echo export "SOCA_INSTALL_BUCKET_FOLDER="''' + str( soca_configuration['S3InstallFolder'] ) + '''"" >> /etc/environment echo export "SOCA_INSTANCE_TYPE=$GET_INSTANCE_TYPE" >> /etc/environment echo export "SOCA_HOST_SYSTEM_LOG="/apps/soca/''' + str( soca_configuration['ClusterId'] ) + '''/cluster_node_bootstrap/logs/desktop/''' + str( session["user"] ) + '''/''' + session_name + '''/$(hostname -s)"" >> /etc/environment echo export "AWS_DEFAULT_REGION="''' + region + '''"" >> /etc/environment # Required for proper EBS tagging echo export "SOCA_JOB_ID="''' + str(session_name) + '''"" >> /etc/environment echo export "SOCA_JOB_OWNER="''' + str( session["user"] ) + '''"" >> /etc/environment echo export "SOCA_JOB_PROJECT="dcv"" >> /etc/environment echo export "SOCA_JOB_QUEUE="dcv"" >> /etc/environment source /etc/environment AWS=$(which aws) # Give yum permission to the user on this specific machine echo "''' + session['user'] + ''' ALL=(ALL) /bin/yum" >> /etc/sudoers mkdir -p /apps mkdir -p /data # Mount EFS echo "''' + soca_configuration[ 'EFSDataDns'] + ''':/ /data nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport 0 0" >> /etc/fstab echo "''' + soca_configuration[ 'EFSAppsDns'] + ''':/ /apps nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport 0 0" >> /etc/fstab EFS_MOUNT=0 mount -a while [[ $? -ne 0 ]] && [[ $EFS_MOUNT -lt 5 ]] do SLEEP_TIME=$(( RANDOM % 60 )) echo "Failed to mount EFS, retrying in $SLEEP_TIME seconds and Loop $EFS_MOUNT/5..." sleep $SLEEP_TIME ((EFS_MOUNT++)) mount -a done # Configure Chrony yum remove -y ntp yum install -y chrony mv /etc/chrony.conf /etc/chrony.conf.original echo -e """ # use the local instance NTP service, if available server 169.254.169.123 prefer iburst minpoll 4 maxpoll 4 # Use public servers from the pool.ntp.org project. # Please consider joining the pool (http://www.pool.ntp.org/join.html). # !!! [BEGIN] SOCA REQUIREMENT # You will need to open UDP egress traffic on your security group if you want to enable public pool #pool 2.amazon.pool.ntp.org iburst # !!! [END] SOCA REQUIREMENT # Record the rate at which the system clock gains/losses time. driftfile /var/lib/chrony/drift # Allow the system clock to be stepped in the first three updates # if its offset is larger than 1 second. makestep 1.0 3 # Specify file containing keys for NTP authentication. keyfile /etc/chrony.keys # Specify directory for log files. logdir /var/log/chrony # save data between restarts for fast re-load dumponexit dumpdir /var/run/chrony """ > /etc/chrony.conf systemctl enable chronyd # Prepare Log folder mkdir -p $SOCA_HOST_SYSTEM_LOG echo "@reboot /bin/bash /apps/soca/$SOCA_CONFIGURATION/cluster_node_bootstrap/ComputeNodePostReboot.sh >> $SOCA_HOST_SYSTEM_LOG/ComputeNodePostReboot.log 2>&1" | crontab - $AWS s3 cp s3://$SOCA_INSTALL_BUCKET/$SOCA_INSTALL_BUCKET_FOLDER/scripts/config.cfg /root/ /bin/bash /apps/soca/$SOCA_CONFIGURATION/cluster_node_bootstrap/ComputeNode.sh ''' + soca_configuration[ 'SchedulerPrivateDnsName'] + ''' >> $SOCA_HOST_SYSTEM_LOG/ComputeNode.sh.log 2>&1''' check_hibernation_support = client_ec2.describe_instance_types( InstanceTypes=[instance_type], Filters=[{ "Name": "hibernation-supported", "Values": ["true"] }]) logger.info("Checking in {} support Hibernation : {}".format( instance_type, check_hibernation_support)) if len(check_hibernation_support["InstanceTypes"]) == 0: if config.Config.DCV_FORCE_INSTANCE_HIBERNATE_SUPPORT is True: flash( "Sorry your administrator limited <a href='https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Hibernate.html' target='_blank'>DCV to instances that support hibernation mode</a> <br> Please choose a different type of instance." ) return redirect("/remote_desktop") else: hibernate_support = False else: hibernate_support = True if parameters["hibernate"] and not hibernate_support: flash( "Sorry you have selected {} with hibernation support, but this instance type does not support it. Either disable hibernation support or pick a different instance type" .format(instance_type), "error") return redirect("/remote_desktop") launch_parameters = { "security_group_id": security_group_id, "instance_profile": instance_profile, "instance_type": instance_type, "soca_private_subnets": soca_private_subnets, "user_data": user_data, "image_id": image_id, "session_name": session_name, "session_uuid": session_uuid, "base_os": base_os, "disk_size": parameters["disk_size"], "cluster_id": soca_configuration["ClusterId"], "hibernate": parameters["hibernate"], "user": session["user"], "DefaultMetricCollection": True if soca_configuration["DefaultMetricCollection"] == "true" else False, "SolutionMetricLambda": soca_configuration['SolutionMetricLambda'], "ComputeNodeInstanceProfileArn": soca_configuration["ComputeNodeInstanceProfileArn"] } dry_run_launch = can_launch_instance(launch_parameters) if dry_run_launch is True: launch_template = dcv_cloudformation_builder.main(**launch_parameters) if launch_template["success"] is True: cfn_stack_name = str(launch_parameters["cluster_id"] + "-" + launch_parameters["session_name"] + "-" + launch_parameters["user"]) cfn_stack_tags = [{ "Key": "soca:JobName", "Value": str(launch_parameters["session_name"]) }, { "Key": "soca:JobOwner", "Value": str(session["user"]) }, { "Key": "soca:JobProject", "Value": "desktop" }, { "Key": "soca:ClusterId", "Value": str(launch_parameters["cluster_id"]) }, { "Key": "soca:NodeType", "Value": "dcv" }, { "Key": "soca:DCVSystem", "Value": base_os }] try: client_cfn.create_stack(StackName=cfn_stack_name, TemplateBody=launch_template["output"], Tags=cfn_stack_tags) except Exception as e: logger.error( f"Error while trying to provision {cfn_stack_name} due to {e}" ) flash( f"Error while trying to provision {cfn_stack_name} due to {e}" ) return redirect("/remote_desktop") else: flash(launch_template["output"], "error") return redirect("/remote_desktop") else: flash(dry_run_launch, "error") return redirect("/remote_desktop") flash( "Your session has been initiated. It will be ready within 10 minutes.", "success") new_session = LinuxDCVSessions( user=session["user"], session_number=parameters["session_number"], session_name=session_name, session_state="pending", session_host_private_dns=False, session_host_private_ip=False, session_instance_type=instance_type, session_linux_distribution=base_os, dcv_authentication_token=None, session_id=session_uuid, tag_uuid=session_uuid, session_token=str(uuid.uuid4()), is_active=True, support_hibernation=parameters["hibernate"], created_on=datetime.utcnow(), schedule_monday_start=config.Config.DCV_LINUX_DEFAULT_SCHEDULE_START, schedule_tuesday_start=config.Config.DCV_LINUX_DEFAULT_SCHEDULE_START, schedule_wednesday_start=config.Config. DCV_LINUX_DEFAULT_SCHEDULE_START, schedule_thursday_start=config.Config.DCV_LINUX_DEFAULT_SCHEDULE_START, schedule_friday_start=config.Config.DCV_LINUX_DEFAULT_SCHEDULE_START, schedule_saturday_start=0, schedule_sunday_start=0, schedule_monday_stop=config.Config.DCV_LINUX_DEFAULT_SCHEDULE_STOP, schedule_tuesday_stop=config.Config.DCV_LINUX_DEFAULT_SCHEDULE_STOP, schedule_wednesday_stop=config.Config.DCV_LINUX_DEFAULT_SCHEDULE_STOP, schedule_thursday_stop=config.Config.DCV_LINUX_DEFAULT_SCHEDULE_STOP, schedule_friday_stop=config.Config.DCV_LINUX_DEFAULT_SCHEDULE_STOP, schedule_saturday_stop=0, schedule_sunday_stop=0, ) db.session.add(new_session) db.session.commit() return redirect("/remote_desktop")
def download(): uid = request.args.get("uid", None) if uid is None: return redirect("/my_files") allow_download = config.Config.ALLOW_DOWNLOAD_FROM_PORTAL if allow_download is not True: flash( " Download file is disabled. Please contact your SOCA cluster administrator" ) return redirect("/my_files") files_to_download = uid.split(",") if len(files_to_download) == 1: file_information = decrypt(files_to_download[0]) if file_information["success"] is True: file_info = json.loads(file_information["message"]) if user_has_permission(file_info["file_path"], "read", "file") is False: flash( " You are not authorized to download this file or this file is no longer available on the filesystem" ) return redirect("/my_files") current_user = session["user"] if current_user == file_info["file_owner"]: try: return send_file( file_info["file_path"], as_attachment=True, attachment_filename=file_info["file_path"].split( "/")[-1]) except Exception as err: flash("Unable to download file. Did you remove it?", "error") return redirect("/my_files") else: flash("You do not have the permission to download this file", "error") return redirect("/my_files") else: flash("Unable to download " + file_information["message"], "error") return redirect("/my_files") else: valid_file_path = [] total_size = 0 total_files = 0 for file_to_download in files_to_download: file_information = decrypt(file_to_download) if file_information["success"] is True: file_info = json.loads(file_information["message"]) if user_has_permission(file_info["file_path"], "read", "file") is False: flash( "You are not authorized to download this file or this file is no longer available on the filesystem" ) return redirect("/my_files") current_user = session["user"] if current_user == file_info["file_owner"]: valid_file_path.append(file_info["file_path"]) total_size = total_size + file_info["file_size"] total_files = total_files + 1 if total_size > config.Config.MAX_ARCHIVE_SIZE: flash( "Sorry, the maximum archive size is {:.2f} MB. Your archive was {:.2f} MB. To avoid this issue, you can create a smaller archive, download files individually, use SFTP or edit the maximum archive size authorized." .format(config.Config.MAX_ARCHIVE_SIZE / 1024 / 1024, total_size / 1024 / 1024), "error") return redirect("/my_files") # Limit HTTP payload size if total_files > 45: flash( "Sorry, you cannot download more than 45 files in a single call. Your archive contained {} files" .format(total_files), "error") return redirect("/my_files") if valid_file_path.__len__() == 0: return redirect("/my_files") ts = datetime.datetime.utcnow().strftime("%s") archive_name = "/apps/soca/" + read_secretmanager.get_soca_configuration( )["ClusterId"] + "/cluster_web_ui/tmp/zip_downloads/SOCA_Download_" + session[ "user"] + "_" + ts + ".zip" zipf = zipfile.ZipFile(archive_name, 'w', zipfile.ZIP_DEFLATED) logger.info("About to create archive: " + str(archive_name) + " with the following files: " + str(valid_file_path)) try: for file_to_zip in valid_file_path: zipf.write(file_to_zip) zipf.close() logger.info("Archive created") except Exception as err: logger.error("Unable to create archive due to: " + str(err)) flash( "Unable to generate download link. Check the logs for more information", "error") return redirect("/my_files") if os.path.exists(archive_name): return send_file(archive_name, mimetype='zip', attachment_filename=archive_name.split("/")[-1], as_attachment=True) else: flash("Unable to locate the download archive, please try again", "error") logger.error("Unable to locate " + str(archive_name)) return redirect("/my_files")
def get(self): """ List DCV desktop sessions for a given user --- tags: - DCV parameters: - in: body name: body schema: required: - os - state properties: session_number: type: string description: Session Number os: type: string description: DCV session type (Windows or Linux) state: type: string description: active or inactive run_state: type: string description: The state of the desktop (running, pending, stopped ..) responses: 200: description: Pair of user/token is valid 401: description: Invalid user/token pair """ parser = reqparse.RequestParser() parser.add_argument("os", type=str, location='args') parser.add_argument("is_active", type=str, location='args') parser.add_argument("session_number", type=str, location='args') parser.add_argument("state", type=str, location='args') args = parser.parse_args() logger.info(f"Received parameter for listing DCV desktop: {args}") user = request.headers.get("X-SOCA-USER") if user is None: return errors.all_errors("X-SOCA-USER_MISSING") if args["os"] is None or args["is_active"] is None: return errors.all_errors( 'CLIENT_MISSING_PARAMETER', "os (str), is_active (str) are required.") if args["os"] not in ["windows", "linux"]: return errors.all_errors("CLIENT_INVALID_PARAMETER", "os (str) must be windows or linux") if args["is_active"].lower() not in ["true", "false"]: return errors.all_errors("CLIENT_INVALID_PARAMETER", "is_active (str) must be true, false") # Retrieve sessions is_active = True if args["is_active"].lower() == "true" else False if args["os"].lower() == "windows": all_dcv_sessions = WindowsDCVSessions.query.filter( WindowsDCVSessions.user == user) if args["state"] is not None: all_dcv_sessions = all_dcv_sessions.filter( WindowsDCVSessions.session_state == args["state"]) if args["session_number"] is not None: all_dcv_sessions = all_dcv_sessions.filter( WindowsDCVSessions.session_number == args["session_number"]) all_dcv_sessions = all_dcv_sessions.filter( WindowsDCVSessions.is_active == is_active) else: all_dcv_sessions = LinuxDCVSessions.query.filter( LinuxDCVSessions.user == user) if args["state"] is not None: all_dcv_sessions = all_dcv_sessions.filter( LinuxDCVSessions.session_state == args["state"]) if args["session_number"] is not None: all_dcv_sessions = all_dcv_sessions.filter( LinuxDCVSessions.session_number == args["session_number"]) all_dcv_sessions = all_dcv_sessions.filter( LinuxDCVSessions.is_active == is_active) logger.info(f"Checking {args['os']} desktops for {user}") user_sessions = {} for session_info in all_dcv_sessions.all(): try: session_number = session_info.session_number session_state = session_info.session_state tag_uuid = session_info.tag_uuid session_name = session_info.session_name session_host_private_dns = session_info.session_host_private_dns session_token = session_info.session_token session_local_admin_password = session_info.session_local_admin_password if args[ "os"] == "windows" else None if args["os"].lower() != "windows": session_linux_distribution = session_info.session_linux_distribution session_instance_type = session_info.session_instance_type session_instance_id = session_info.session_instance_id support_hibernation = session_info.support_hibernation dcv_authentication_token = session_info.dcv_authentication_token session_id = session_info.session_id session_schedule = { "monday": f"{session_info.schedule_monday_start}-{session_info.schedule_monday_stop}", "tuesday": f"{session_info.schedule_tuesday_start}-{session_info.schedule_tuesday_stop}", "wednesday": f"{session_info.schedule_wednesday_start}-{session_info.schedule_wednesday_stop}", "thursday": f"{session_info.schedule_thursday_start}-{session_info.schedule_thursday_stop}", "friday": f"{session_info.schedule_friday_start}-{session_info.schedule_friday_stop}", "saturday": f"{session_info.schedule_saturday_start}-{session_info.schedule_saturday_stop}", "sunday": f"{session_info.schedule_sunday_start}-{session_info.schedule_sunday_stop}" } stack_name = f"{read_secretmanager.get_soca_configuration()['ClusterId']}-{session_name}-{user}" if args["os"].lower() == "windows": host_info = get_host_info( tag_uuid, read_secretmanager.get_soca_configuration() ["ClusterId"], "windows") else: host_info = get_host_info( tag_uuid, read_secretmanager.get_soca_configuration() ["ClusterId"], session_linux_distribution) logger.info(f"Host Info {host_info}") if not host_info: try: check_stack = client_cfn.describe_stacks( StackName=stack_name) logger.info(f"Host Info check_stack {check_stack}") if check_stack['Stacks'][0]['StackStatus'] in [ 'CREATE_FAILED', 'ROLLBACK_COMPLETE', 'ROLLBACK_FAILED' ]: logger.info(f"Host Info DEACTIVATE") # no host detected, session no longer active session_info.is_active = False session_info.deactivated_on = datetime.utcnow() db.session.commit() except Exception as err: logger.error( f"Error checking CFN stack {stack_name} due to {err}" ) session_info.is_active = False session_info.deactivated_on = datetime.utcnow() db.session.commit() else: # detected EC2 host for the session if not dcv_authentication_token: session_info.session_host_private_dns = host_info[ "private_dns"] session_info.session_host_private_ip = host_info[ "private_ip"] session_info.session_instance_id = host_info[ "instance_id"] authentication_data = json.dumps({ "system": "windows" if args["os"].lower() == "windows" else session_linux_distribution, "session_instance_id": host_info["instance_id"], "session_token": session_token, "session_user": user }) session_authentication_token = base64.b64encode( encrypt(authentication_data)).decode("utf-8") session_info.dcv_authentication_token = session_authentication_token db.session.commit() if "status" not in host_info.keys(): try: check_stack = client_cfn.describe_stacks( StackName=stack_name) logger.info(f"Host Info check_stack {check_stack}") if check_stack['Stacks'][0]['StackStatus'] in [ 'CREATE_FAILED', 'ROLLBACK_COMPLETE', 'ROLLBACK_FAILED' ]: logger.info(f"Host Info DEACTIVATE") # no host detected, session no longer active session_info.is_active = False session_info.deactivated_on = datetime.utcnow() db.session.commit() except Exception as err: logger.error( f"Error checking CFN stack {stack_name} due to {err}" ) session_info.is_active = False session_info.deactivated_on = datetime.utcnow() db.session.commit() else: if host_info["status"] in [ "stopped", "stopping" ] and session_state != "stopped": session_state = "stopped" session_info.session_state = "stopped" db.session.commit() if session_state == "pending" and session_host_private_dns is not False: check_dcv_state = get( f"https://{read_secretmanager.get_soca_configuration()['LoadBalancerDNSName']}/{session_host_private_dns}/", allow_redirects=False, verify=False) # nosec logger.info( "Checking {} for {} and received status {} ".format( 'https://' + read_secretmanager.get_soca_configuration() ['LoadBalancerDNSName'] + '/' + session_host_private_dns + '/', session_info, check_dcv_state.status_code)) if check_dcv_state.status_code == 200: session_info.session_state = "running" db.session.commit() user_sessions[session_number] = { "url": f"https://{read_secretmanager.get_soca_configuration()['LoadBalancerDNSName']}/{session_host_private_dns}/", "session_local_admin_password": session_local_admin_password, "session_state": session_state, "session_authentication_token": dcv_authentication_token, "session_id": session_id, "session_name": session_name, "session_instance_id": session_instance_id, "session_instance_type": session_instance_type, "tag_uuid": tag_uuid, "support_hibernation": support_hibernation, "session_schedule": session_schedule, "connection_string": f"https://{read_secretmanager.get_soca_configuration()['LoadBalancerDNSName']}/{session_host_private_dns}/?authToken={dcv_authentication_token}#{session_id}" } #logger.info(user_sessions) except Exception as err: exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] logger.error(exc_type, fname, exc_tb.tb_lineno) return errors.all_errors(type(err).__name__, err) return {"success": True, "message": user_sessions}, 200
def put(self, session_number, action): """ Stop/Hibernate a DCV desktop session --- tags: - DCV parameters: - in: body name: body schema: required: - os properties: session_number: type: string description: Session Number action: type: string description: Stop/Hibernate or Terminate os: type: string description: DCV session type (Windows or Linux) responses: 200: description: Pair of user/token is valid 401: description: Invalid user/token pair """ parser = reqparse.RequestParser() parser.add_argument("os", type=str, location='form') args = parser.parse_args() user = request.headers.get("X-SOCA-USER") if session_number is None: return errors.all_errors('CLIENT_MISSING_PARAMETER', "session_number not found in URL. Endpoint is /api/dcv/desktop/<session_number>/<action>") else: args["session_number"] = str(session_number) if action is None: return errors.all_errors('CLIENT_MISSING_PARAMETER', "action not found in URL. Endpoint is /api/dcv/desktop/<session_number>/<action>") else: args["action"] = action if user is None: return errors.all_errors("X-SOCA-USER_MISSING") if args["os"] is None: return errors.all_errors('CLIENT_MISSING_PARAMETER', "os (str)") if args["os"].lower() not in ["linux", "windows"]: return errors.all_errors('CLIENT_MISSING_PARAMETER', "os must be linux or windows") if args["action"] not in ["terminate", "stop", "hibernate"]: return errors.all_errors('CLIENT_MISSING_PARAMETER', "action must be terminate, stop or hibernate") if args["os"].lower() == "linux": check_session = LinuxDCVSessions.query.filter_by(user=user, session_number=str(args["session_number"]), is_active=True).first() else: check_session = WindowsDCVSessions.query.filter_by(user=user, session_number=str(args["session_number"]), is_active=True).first() if check_session: instance_id = check_session.session_instance_id session_name = check_session.session_name if args["action"] == "hibernate": if check_session.session_state == "stopped": return errors.all_errors('DCV_STOP_ERROR', f"Your instance is already stopped.") else: # Hibernate instance try: client_ec2.stop_instances(InstanceIds=[instance_id], Hibernate=True, DryRun=True) except ClientError as e: if e.response['Error'].get('Code') == 'DryRunOperation': client_ec2.stop_instances(InstanceIds=[instance_id], Hibernate=True) check_session.session_state = "stopped" db.session.commit() else: return errors.all_errors('DCV_STOP_ERROR', f"Unable to hibernate instance ({instance_id}) due to {e}") elif args["action"] == "stop": if check_session.session_state in "stopped": return errors.all_errors('DCV_STOP_ERROR', f"Your desktop is already stopped.") # Stop Instance else: try: client_ec2.stop_instances(InstanceIds=[instance_id], DryRun=True) except ClientError as e: if e.response['Error'].get('Code') == 'DryRunOperation': try: client_ec2.stop_instances(InstanceIds=[instance_id]) except ClientError as err: # case when someone stop an EC2 instance still initializing. This use case is not handle by the DryRun so we need return errors.all_errors('DCV_STOP_ERROR', f"Unable to stop instance, maybe the instance is not running yet. Error {err}") check_session.session_state = "stopped" db.session.commit() else: return errors.all_errors('DCV_STOP_ERROR', f"Unable to stop instance ({instance_id}) due to {e}") else: # Terminate instance stack_name = str(read_secretmanager.get_soca_configuration()["ClusterId"] + "-" + session_name + "-" + user) try: client_cfn.delete_stack(StackName=stack_name) check_session.is_active = False check_session.deactivated_on = datetime.utcnow() db.session.commit() return {"success": True, "message": f"Your graphical session {session_name} is about to be terminated"}, 200 except ClientError as e: return errors.all_errors('DCV_STOP_ERROR', f"Unable to delete cloudformation stack ({stack_name}) due to {e}") except Exception as e: return errors.all_errors('DCV_STOP_ERROR',f"Unable to update db due to {e}") else: return errors.all_errors('DCV_STOP_ERROR', f"This session does not exist or is not active")
def delete(self, session_number): """ Terminate a DCV desktop session --- tags: - DCV parameters: - in: body name: body schema: required: - os properties: session_number: type: string description: Session Number os: type: string description: DCV session type (Windows or Linux) responses: 200: description: Pair of user/token is valid 401: description: Invalid user/token pair """ parser = reqparse.RequestParser() parser.add_argument("os", type=str, location='form') args = parser.parse_args() user = request.headers.get("X-SOCA-USER") if session_number is None: return errors.all_errors('CLIENT_MISSING_PARAMETER', "session_number not found in URL. Endpoint is /api/dcv/desktop/<session_number>/<action>") else: args["session_number"] = str(session_number) if user is None: return errors.all_errors("X-SOCA-USER_MISSING") if args["os"] is None: return errors.all_errors('CLIENT_MISSING_PARAMETER', "os (str)") if args["os"].lower() not in ["linux", "windows"]: return errors.all_errors('CLIENT_MISSING_PARAMETER', "os must be linux or windows") if args["os"].lower() == "linux": check_session = LinuxDCVSessions.query.filter_by(user=user, session_number=str(args["session_number"]), is_active=True).first() else: check_session = WindowsDCVSessions.query.filter_by(user=user, session_number=str(args["session_number"]), is_active=True).first() if check_session: session_name = check_session.session_name # Terminate instance stack_name = str(read_secretmanager.get_soca_configuration()["ClusterId"] + "-" + session_name + "-" + user) try: client_cfn.delete_stack(StackName=stack_name) check_session.is_active = False check_session.deactivated_on = datetime.utcnow() db.session.commit() return {"success": True, "message": f"Your graphical session {session_name} is about to be terminated"}, 200 except ClientError as e: return errors.all_errors('DCV_STOP_ERROR', f"Unable to delete cloudformation stack ({stack_name}) due to {e}") except Exception as e: return errors.all_errors('DCV_STOP_ERROR',f"Unable to update db due to {e}") else: return errors.all_errors('DCV_STOP_ERROR', f"This session does not exist or is not active")
def index(): elastic_search_endpoint = read_secretmanager.get_soca_configuration( )['ESDomainEndpoint'] kibana_url = "https://" + elastic_search_endpoint + "/_plugin/kibana" return render_template("dashboard.html", kibana_url=kibana_url)
def index(): user_sessions = {} for session_info in DCVSessions.query.filter_by(user=session["user"], is_active=True).all(): session_number = session_info.session_number session_state = session_info.session_state session_password = session_info.session_password session_uuid = session_info.session_uuid session_name = session_info.session_name job_id = session_info.job_id get_job_info = get(config.Config.FLASK_ENDPOINT + "/api/scheduler/job", headers={ "X-SOCA-USER": session["user"], "X-SOCA-TOKEN": session["api_key"] }, params={"job_id": job_id}, verify=False) check_session = DCVSessions.query.filter_by(job_id=job_id).all() if len(check_session) > 1: flash( "More than 1 entry on the DB was found for this job (" + job_id + "). Most likely this is because this db was copied from a different cluster. Please remove the entry for the DB first" ) return redirect("/remote_desktop") else: for job_info in check_session: if get_job_info.status_code == 200: # Job in queue, edit only if state is running job_state = get_job_info.json()["message"]["job_state"] if job_state == "R" and job_info.session_state != "running": exec_host = (get_job_info.json()["message"] ["exec_host"]).split("/")[0] job_info.session_host = exec_host job_info.session_state = "running" db.session.commit() elif get_job_info.status_code == 210: # Job is no longer in the queue job_info.is_active = False job_info.deactivated_on = datetime.datetime.utcnow() db.session.commit() else: flash( "Unknown error for session " + str(session_number) + " assigned to job " + str(job_id) + " with error " + str(get_job_info.text), "error") user_sessions[session_number] = { "url": 'https://' + read_secretmanager.get_soca_configuration() ['LoadBalancerDNSName'] + '/' + job_info.session_host + '/?authToken=' + session_password + '#' + session_uuid, "session_state": session_state, "session_name": session_name } max_number_of_sessions = config.Config.DCV_MAX_SESSION_COUNT # List of instances not available for DCV. Adjust as needed blacklist = ['metal', 'nano', 'micro'] all_instances_available = client._service_model.shape_for( 'InstanceType').enum all_instances = [ p for p in all_instances_available if not any(substr in p for substr in blacklist) ] return render_template( 'remote_desktop.html', user=session["user"], user_sessions=user_sessions, terminate_idle_session=config.Config.DCV_TERMINATE_IDLE_SESSION, page='remote_desktop', all_instances=all_instances, max_number_of_sessions=max_number_of_sessions)
def post(self, session_number): """ Create a new DCV desktop session (Windows) --- tags: - DCV parameters: - in: body name: body schema: required: - instance_type - disk_size - session_number - instance_ami - subnet_id - hibernate properties: instance_type: type: string description: Type of EC2 instance to provision disk_size: type: string description: EBS size to provision for root device session_number: type: string description: DCV Session Number session_name: type: string description: DCV Session Name instance_ami: type: string description: Custom AMI to use subnet_id: type: string description: Specify a subnet id to launch the EC2 hibernate: type: string description: True/False. user: type: string description: owner of the session responses: 200: description: Pair of user/token is valid 401: description: Invalid user/token pair """ parser = reqparse.RequestParser() parser.add_argument("instance_type", type=str, location='form') parser.add_argument("disk_size", type=str, location='form') parser.add_argument("session_name", type=str, location='form') parser.add_argument("instance_ami", type=str, location='form') parser.add_argument("subnet_id", type=str, location='form') parser.add_argument("hibernate", type=str, location='form') args = parser.parse_args() logger.info(f"Received parameter for new Windows DCV session: {args}") try: user = request.headers.get("X-SOCA-USER") if user is None: return errors.all_errors("X-SOCA-USER_MISSING") if not args["subnet_id"]: args["subnet_id"] = False if not args["hibernate"]: args["hibernate"] = False elif args["hibernate"].lower() == "false": args["hibernate"] = False elif args["hibernate"].lower() == "true": args["hibernate"] = True else: return errors.all_errors( "DCV_LAUNCH_ERROR", f"hibernate must be either true or false") if session_number is None: return errors.all_errors( 'CLIENT_MISSING_PARAMETER', "session_number not found in URL. Endpoint is /api/dcv/desktop/<session_number>/windows" ) else: args["session_number"] = str(session_number) if args["instance_type"] is None: return errors.all_errors('CLIENT_MISSING_PARAMETER', "instance_type (str) is required.") args["disk_size"] = 30 if args["disk_size"] is None else args[ "disk_size"] try: args["disk_size"] = int(args["disk_size"]) except ValueError: return errors.all_errors("DCV_LAUNCH_ERROR", f"disk_size must be an integer") try: if int(args["session_number"]) > int( config.Config.DCV_WINDOWS_SESSION_COUNT): return errors.all_errors( "DCV_LAUNCH_ERROR", f"session_number {args['session_number']} is greater than the max number of session allowed ({config.Config.DCV_WINDOWS_SESSION_COUNT}). Contact admin for increase." ) except Exception as err: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Session Number {args['session_number']} must be a number. Err: {err}" ) session_uuid = str(uuid.uuid4()) region = os.environ["AWS_DEFAULT_REGION"] instance_type = args["instance_type"] soca_configuration = read_secretmanager.get_soca_configuration() instance_profile = soca_configuration[ "ComputeNodeInstanceProfileArn"] security_group_id = soca_configuration["ComputeNodeSecurityGroup"] if session_already_exist(args["session_number"]) is True: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Session Number {args['session_number']} is already used by an active desktop. Terminate it first before being able to use the same number" ) # sanitize session_name, limit to 255 chars if args["session_name"] is None: session_name = 'WindowsDesktop' + str(args["session_number"]) else: session_name = re.sub(r'\W+', '', args["session_name"])[:255] if session_name == "": # handle case when session name specified by user only contains invalid char session_name = 'WindowsDesktop' + str( args["session_number"]) # Official DCV AMI # https://aws.amazon.com/marketplace/pp/B07TVL513S + https://aws.amazon.com/marketplace/pp/B082HYM34K # Non graphics is everything but g3/g4 if args["instance_ami"] is None or args["instance_ami"] == "base": dcv_windows_ami = config.Config.DCV_WINDOWS_AMI if instance_type.startswith("g"): if instance_type.startswith("g4ad"): if region not in dcv_windows_ami["graphics-amd"].keys( ) and args["instance_ami"] is None: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Sorry, Windows Desktop is not available on your AWS region. Base AMI are only available on { dcv_windows_ami['graphics-amd'].keys()}" ) else: image_id = dcv_windows_ami["graphics-amd"][region] else: if region not in dcv_windows_ami["graphics"].keys( ) and args["instance_ami"] is False: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Sorry, Windows Desktop is not available on your AWS region. Base AMI are only available on {dcv_windows_ami['graphics'].keys()}" ) else: image_id = dcv_windows_ami["graphics"][region] else: if region not in dcv_windows_ami["non-graphics"].keys( ) and args["instance_ami"] is False: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Sorry, Windows Desktop is not available on your AWS region. Base AMI are only available on {dcv_windows_ami['non-graphics'].keys()}" ) else: image_id = dcv_windows_ami["non-graphics"][region] else: if not args["instance_ami"].startswith("ami-"): return errors.all_errors( "DCV_LAUNCH_ERROR", f"AMI {args['instance_ami']} does not seems to be valid. Must start with ami-<id>" ) else: if validate_ec2_image(args["instance_ami"]) is False: return errors.all_errors( "DCV_LAUNCH_ERROR", f"AMI {args['instance_ami']} does not seems to be registered on SOCA. Refer to https://awslabs.github.io/scale-out-computing-on-aws/web-interface/create-virtual-desktops-images/" ) else: image_id = args["instance_ami"] digits = ([ random.choice(''.join( random.choice(string.digits) for _ in range(10))) for _ in range(3) ]) uppercase = ([ random.choice(''.join( random.choice(string.ascii_uppercase) for _ in range(10))) for _ in range(3) ]) lowercase = ([ random.choice(''.join( random.choice(string.ascii_lowercase) for _ in range(10))) for _ in range(3) ]) pw = digits + uppercase + lowercase session_local_admin_password = ''.join(random.sample(pw, len(pw))) user_data_script = open( "/apps/soca/" + soca_configuration["ClusterId"] + "/cluster_node_bootstrap/windows/ComputeNodeInstallDCVWindows.ps", "r") user_data = user_data_script.read() user_data_script.close() user_data = user_data.replace("%SOCA_LOCAL_ADMIN_PASSWORD%", session_local_admin_password) user_data = user_data.replace( "%SOCA_SchedulerPrivateIP%", soca_configuration['SchedulerPrivateIP'] + ":" + str(config.Config.FLASK_PORT)) user_data = user_data.replace( "%SOCA_LoadBalancerDNSName%", soca_configuration['LoadBalancerDNSName']) user_data = user_data.replace("%SOCA_LOCAL_USER%", user) # required for EBS tagging user_data = user_data.replace("%SOCA_JOB_ID%", str(session_name)) user_data = user_data.replace("%SOCA_JOB_OWNER%", user) user_data = user_data.replace("%SOCA_JOB_PROJECT%", "dcv") user_data = user_data.replace("%SOCA_AUTH_PROVIDER%", soca_configuration["AuthProvider"]) user_data = user_data.replace( "%SOCA_DS_JOIN_USERNAME%", "false" if soca_configuration["AuthProvider"] == "openldap" else soca_configuration["DSDomainAdminUsername"]) user_data = user_data.replace( "%SOCA_DS_JOIN_PASSWORD%", "false" if soca_configuration["AuthProvider"] == "openldap" else soca_configuration["DSDomainAdminPassword"]) user_data = user_data.replace( "%SOCA_DS_NETBIOS%", "false" if soca_configuration["AuthProvider"] == "openldap" else soca_configuration["DSDomainNetbios"]) user_data = user_data.replace( "%SOCA_DS_DOMAIN%", "false" if soca_configuration["AuthProvider"] == "openldap" else soca_configuration["DSDomainName"]) if config.Config.DCV_WINDOWS_AUTOLOGON is True: user_data = user_data.replace("%SOCA_WINDOWS_AUTOLOGON%", "true") else: user_data = user_data.replace("%SOCA_WINDOWS_AUTOLOGON%", "false") if args["hibernate"]: try: check_hibernation_support = client_ec2.describe_instance_types( InstanceTypes=[instance_type], Filters=[{ "Name": "hibernation-supported", "Values": ["true"] }]) logger.info( "Checking in {} support Hibernation : {}".format( instance_type, check_hibernation_support)) if len(check_hibernation_support["InstanceTypes"]) == 0: if config.Config.DCV_FORCE_INSTANCE_HIBERNATE_SUPPORT is True: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Sorry your administrator limited <a href='https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Hibernate.html' target='_blank'>DCV to instances that support hibernation mode</a> <br> Please choose a different type of instance." ) else: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Sorry you have selected {instance_type} with hibernation support, but this instance type does not support it. Either disable hibernation support or pick a different instance type" ) except ClientError as e: return errors.all_errors( "DCV_LAUNCH_ERROR", f"Error while checking hibernation support due to {e}") launch_parameters = { "security_group_id": security_group_id, "instance_profile": instance_profile, "instance_type": instance_type, "soca_private_subnets": soca_configuration["PrivateSubnets"], "user_data": user_data, "subnet_id": args["subnet_id"], "image_id": image_id, "session_name": session_name, "session_uuid": session_uuid, "base_os": "windows", "disk_size": args["disk_size"], "cluster_id": soca_configuration["ClusterId"], "hibernate": args["hibernate"], "user": user, "DefaultMetricCollection": True if soca_configuration["DefaultMetricCollection"] == "true" else False, "SolutionMetricsLambda": soca_configuration['SolutionMetricsLambda'], "ComputeNodeInstanceProfileArn": soca_configuration["ComputeNodeInstanceProfileArn"] } dry_run_launch = can_launch_instance(launch_parameters) if dry_run_launch is True: launch_template = dcv_cloudformation_builder.main( **launch_parameters) if launch_template["success"] is True: cfn_stack_name = str(launch_parameters["cluster_id"] + "-" + launch_parameters["session_name"] + "-" + launch_parameters["user"]) cfn_stack_tags = [{ "Key": "soca:JobName", "Value": str(launch_parameters["session_name"]) }, { "Key": "soca:JobOwner", "Value": user }, { "Key": "soca:JobProject", "Value": "desktop" }, { "Key": "soca:ClusterId", "Value": str(launch_parameters["cluster_id"]) }, { "Key": "soca:NodeType", "Value": "dcv" }, { "Key": "soca:DCVSystem", "Value": "windows" }] try: client_cfn.create_stack( StackName=cfn_stack_name, TemplateBody=launch_template["output"], Tags=cfn_stack_tags) except Exception as e: logger.error( f"Error while trying to provision {cfn_stack_name} due to {e}" ) return errors.all_errors( "DCV_LAUNCH_ERROR", f"Error while trying to provision {cfn_stack_name} due to {e}" ) else: return errors.all_errors("DCV_LAUNCH_ERROR", f"{launch_template['output']}") else: return errors.all_errors("DCV_LAUNCH_ERROR", f" Dry Run error: {dry_run_launch}") new_session = WindowsDCVSessions( user=user, session_number=args["session_number"], session_name=session_name, session_state="pending", session_host_private_dns=False, session_host_private_ip=False, session_instance_type=instance_type, dcv_authentication_token=None, session_local_admin_password=session_local_admin_password, session_id="console", tag_uuid=session_uuid, session_token=str(uuid.uuid4()), is_active=True, support_hibernation=args["hibernate"], created_on=datetime.utcnow(), schedule_monday_start=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekdays"]["start"], schedule_tuesday_start=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekdays"]["start"], schedule_wednesday_start=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekdays"]["start"], schedule_thursday_start=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekdays"]["start"], schedule_friday_start=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekdays"]["start"], schedule_saturday_start=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekend"]["start"], schedule_sunday_start=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekend"]["start"], schedule_monday_stop=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekdays"]["stop"], schedule_tuesday_stop=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekdays"]["stop"], schedule_wednesday_stop=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekdays"]["stop"], schedule_thursday_stop=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekdays"]["stop"], schedule_friday_stop=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekdays"]["stop"], schedule_saturday_stop=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekend"]["stop"], schedule_sunday_stop=config.Config. DCV_WINDOWS_DEFAULT_SCHEDULE["weekend"]["stop"]) db.session.add(new_session) db.session.commit() return { "success": True, "message": f"Session {session_name} with ID {args['session_number']} started successfully." }, 200 except Exception as err: exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] logger.error(exc_type, fname, exc_tb.tb_lineno) return errors.all_errors(type(err).__name__, err)
def post(self): """ Authenticate a DCV desktop via authToken --- tags: - DCV parameters: - in: body name: body schema: required: - authenticationToken properties: authenticationToken: type: string description: DCV auth token responses: 200: description: Pair of user/token is valid 401: description: Invalid user/token pair """ logger.info("DCV Authentication") parser = reqparse.RequestParser() parser.add_argument('sessionId', type=str, location='form') parser.add_argument('authenticationToken', type=str, location='form') parser.add_argument('clientAddress', type=str, location='form') args = parser.parse_args() remote_addr = request.remote_addr if args["sessionId"] is None or args[ 'authenticationToken'] is None or args["clientAddress"] is None: return errors.all_errors( 'CLIENT_MISSING_PARAMETER', "sessionId (str), clientAddress (str) and authenticationToken (str) are required." ) session_id = args["sessionId"] authentication_token = args['authenticationToken'] client_address = args["clientAddress"].split(":")[ 0] # keep only ip, remove port error = False user = False required_params = [ "system", "session_user", "session_token", "session_instance_id" ] session_info = {} logger.info("Detected {} and remote_addr {}".format(args, remote_addr)) try: decoded_token = decrypt(base64.b64decode(authentication_token)) if decoded_token is False: logger.error( "Unable to decrypt the authentication token. It was probably generated by a different Fernet key" ) error = True else: decoded_token = ast.literal_eval(decoded_token) except Exception as err: logger.error("Unable to base64 decode the authentication token") error = True if error is False: for param in required_params: if param not in decoded_token.keys(): logger.error("Unable to find {} in {}".format( decoded_token, decoded_token)) error = True else: session_info[param] = decoded_token[param] if error is False: if session_info["system"].lower() == "windows": validate_session = WindowsDCVSessions.query.filter_by( user=session_info["session_user"], session_host_private_ip=remote_addr, session_token=session_info["session_token"], session_instance_id=session_info["session_instance_id"], is_active=True).first() else: validate_session = LinuxDCVSessions.query.filter_by( user=session_info["session_user"], session_host_private_ip=remote_addr, session_token=session_info["session_token"], session_instance_id=session_info["session_instance_id"], is_active=True).first() if validate_session: user = session_info["session_user"] else: error = True if error is False and user is not False: if session_info["system"].lower() == "windows": soca_config = read_secretmanager.get_soca_configuration() if soca_config["AuthProvider"] == "activedirectory": xml_response = f'<auth result="yes"><username>{soca_config["DSDomainNetbios"]}\\{user}</username></auth>' else: xml_response = '<auth result="yes"><username>' + user + '</username></auth>' else: xml_response = '<auth result="yes"><username>' + user + '</username></auth>' status = 200 logger.info("Successfully authenticated session") else: xml_response = '<auth result="no"/>' status = 401 logger.error( "Unable to authenticate this DCV session. Make sure remote_addr point to the private IP address of your DCV manager (verify your proxy settings)." ) return Response(xml_response, status=status, mimetype='text/xml')