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")
예제 #3
0
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())
예제 #6
0
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")
예제 #10
0
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)
예제 #11
0
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")
예제 #13
0
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
예제 #17
0
    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")
예제 #18
0
    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")
예제 #19
0
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)
예제 #20
0
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')