def post(self): """ Change password for a given user --- tags: - User Management parameters: - in: body name: body schema: required: - user - password properties: user: type: string description: SOCA user password: type: string description: New password to configure responses: 200: description: Pair of username/token is valid 203: description: Invalid username/token pair 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument("user", type=str, location="form") parser.add_argument("password", type=str, location="form") args = parser.parse_args() user = args["user"] password = args["password"] logger.info(f"Received password reset request for {user}") if user.lower() in password.lower(): return errors.all_errors("DS_PASSWORD_USERNAME_IN_PW") ds_password_complexity = r"(?=^.{8,64}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9\s])(?=.*[a-z])|(?=.*[^A-Za-z0-9\s])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9\s]))^.*" if not re.search(ds_password_complexity, password): return errors.all_errors("DS_PASSWORD_COMPLEXITY_ERROR") lambda_client = boto3.client("lambda", config=config.boto_extra_config()) if user is None or password is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "user (str) and password (str) parameters are required") ds_password_reset_lambda_arn = config.Config.DIRECTORY_SERVICE_RESET_LAMBDA_ARN if not ds_password_reset_lambda_arn: return errors.all_errors("MISSING_DS_RESET_LAMBDA") try: response = lambda_client.invoke(FunctionName=ds_password_reset_lambda_arn, Payload=json.dumps({"Username": user, "Password": password, "DirectoryServiceId": config.Config.DIRECTORY_SERVICE_ID}, indent=2).encode("utf-8")) logger.info(str(response['Payload'].read())) return {"success": True, "message": "Password updated correctly."}, 200 except Exception as err: return errors.all_errors(type(err).__name__, err)
def get(self): """ Retrieve information for a specific group --- tags: - Group Management parameters: - in: body name: body schema: required: - group properties: group: type: string description: user of the SOCA user responses: 200: description: Return user information 203: description: Unknown user 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument('group', type=str, location='args') args = parser.parse_args() group = args["group"] if group is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "group (str) parameter is required") group_search_base = "ou=Group," + config.Config.LDAP_BASE_DN group_search_scope = ldap.SCOPE_SUBTREE group_filter = 'cn=' + group try: conn = ldap.initialize('ldap://' + config.Config.LDAP_HOST) groups = conn.search_s(group_search_base, group_search_scope, group_filter, ["cn", "memberUid"]) if groups.__len__() == 0: return errors.all_errors("GROUP_DO_NOT_EXIST") for group in groups: group_base = group[0] members = [] if "memberUid" in group[1].keys(): for member in group[1]["memberUid"]: user = re.match("uid=(\w+),", member.decode("utf-8")) if user: members.append(user.group(1)) else: members.append(member.decode("utf-8")) # return {"success": False, "message": "Unable to retrieve memberUid for this group: " + str(group_base) + "members: "+str(group[1]["memberUid"])}, 500 return {"success": True, "message": {"group_dn": group_base, "members": members}} except Exception as err: return errors.all_errors(type(err).__name__, err)
def delete(self): """ Delete a LDAP group --- tags: - Group Management parameters: - in: body name: body schema: required: - user properties: user: type: string description: user of the SOCA user responses: 200: description: Deleted user 203: description: Unknown user 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument('group', type=str, location='form') args = parser.parse_args() group = args["group"] request_user = request.headers.get("X-SOCA-USER") if request_user is None: return errors.all_errors("X-SOCA-USER_MISSING") if request_user == group: return errors.all_errors("CLIENT_OWN_RESOURCE") if group is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "group (str) parameter is required") else: if group.endswith(config.Config.GROUP_NAME_SUFFIX): pass else: group = f"{group}{config.Config.GROUP_NAME_SUFFIX}" try: conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}") conn.simple_bind_s( f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}", config.Config.ROOT_PW) conn.protocol_version = 3 conn.set_option(ldap.OPT_REFERRALS, 0) group_dn = f"cn={group},ou=Users,ou={config.Config.NETBIOS},{config.Config.LDAP_BASE}" conn.delete_s(group_dn) return {"success": True, "message": "Deleted Resource."}, 200 except Exception as err: return errors.all_errors(type(err).__name__, err)
def delete(self): """ Delete a job from the queue --- tags: - Scheduler parameters: - in: body name: body schema: required: - job_id properties: job_id: type: string description: ID of the job to remove responses: 200: description: Job submitted correctly 500: description: Backend error """ parser = reqparse.RequestParser() parser.add_argument('job_id', type=str, location='args') args = parser.parse_args() job_id = args['job_id'] if job_id is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "job_id (str) parameter is required") get_job_info = get(config.Config.FLASK_ENDPOINT + "/api/scheduler/job", headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY}, params={"job_id": job_id}, verify=False) if get_job_info.status_code != 200: return {"success": False, "message": "Unable to retrieve this job. Job may have terminated"}, 500 else: job_info = get_job_info.json()["message"] job_owner = job_info["Job_Owner"].split("@")[0] request_user = request.headers.get("X-SOCA-USER") if request_user is None: return errors.all_errors("X-SOCA-USER_MISSING") if request_user != job_owner: return errors.all_errors("CLIENT_NOT_OWNER") try: qdel_command = config.Config.PBS_QDEL + " " + job_id try: delete_job = subprocess.check_output(shlex.split(qdel_command)) return {"success": True, "message": "Job deleted"} except Exception as err: return {"succes": False, "message": "Unable to execute qsub command: " + str(err)}, 500 except Exception as err: return {"success": False, "message": "Unknown error: " + str(err)}, 500
def post(self): """ Validate a LDAP user/password --- tags: - LDAP management parameters: - in: body name: body schema: required: - user - password properties: user: type: string description: SOCA user token: type: string description: Token associated to the user responses: 200: description: Pair of user/token is valid 210: description: Invalid user/token pair 400: description: Client error 401: description: Un-authorized 500: description: Backend error """ parser = reqparse.RequestParser() parser.add_argument("user", type=str, location='form') parser.add_argument("password", type=str, location='form') args = parser.parse_args() user = args["user"] password = args["password"] if user is None or password is None: return errors.all_errors( 'CLIENT_MISSING_PARAMETER', "user (str) and password (str) are required.") try: logger.info(f"Received authentication request for {user}") conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}") conn.simple_bind_s(f"{user}@{config.Config.DOMAIN_NAME}", password) logger.info(f"Auth success") return {'success': True, 'message': 'User is valid'}, 200 except Exception as err: logger.info(f"Auth failed") return errors.all_errors(type(err).__name__, err)
def post(self): """ Validate a LDAP user/password --- tags: - LDAP management parameters: - in: body name: body schema: required: - user - password properties: user: type: string description: SOCA user token: type: string description: Token associated to the user responses: 200: description: Pair of user/token is valid 210: description: Invalid user/token pair 400: description: Client error 401: description: Un-authorized 500: description: Backend error """ parser = reqparse.RequestParser() parser.add_argument('user', type=str, location='form') parser.add_argument('password', type=str, location='form') args = parser.parse_args() user = args["user"] password = args["password"] if user is None or password is None: return errors.all_errors( 'CLIENT_MISSING_PARAMETER', "user (str) and password (str) are required.") ldap_host = config.Config.LDAP_HOST base_dn = config.Config.LDAP_BASE_DN user_dn = 'uid={},ou=people,{}'.format(user, base_dn) try: conn = ldap.initialize('ldap://{}'.format(ldap_host)) conn.bind_s(user_dn, password, ldap.AUTH_SIMPLE) return {'success': True, 'message': 'User is valid'}, 200 except Exception as err: return errors.all_errors(type(err).__name__, err)
def delete(self): """ Delete API key(s) associated to a user --- tags: - User Management parameters: - in: body name: body schema: required: - user properties: user: type: string description: user of the SOCA user responses: 200: description: Key(s) has been deleted successfully. 203: description: Unable to find a token. 400: description: Client error. """ parser = reqparse.RequestParser() parser.add_argument('user', type=str, location='form') args = parser.parse_args() user = args["user"] if user is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "user (str) parameter is required") try: check_existing_keys = ApiKeys.query.filter_by( user=user, is_active=True).all() if check_existing_keys: for key in check_existing_keys: key.is_active = False key.deactivated_on = datetime.datetime.utcnow() db.session.commit() return { "success": True, "message": "Successfully deactivated" }, 200 else: return errors.all_errors("NO_ACTIVE_TOKEN") except Exception as err: return errors.all_errors(type(err).__name__, err)
def delete(self): """ Delete a LDAP group --- tags: - Group Management parameters: - in: body name: body schema: required: - user properties: user: type: string description: user of the SOCA user responses: 200: description: Deleted user 203: description: Unknown user 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument('group', type=str, location='form') args = parser.parse_args() group = args["group"] request_user = request.headers.get("X-SOCA-USER") if request_user is None: return errors.all_errors("X-SOCA-USER_MISSING") if request_user == group: return errors.all_errors("CLIENT_OWN_RESOURCE") if group is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "group (str) parameter is required") ldap_base = config.Config.LDAP_BASE_DN try: conn = ldap.initialize('ldap://' + config.Config.LDAP_HOST) conn.simple_bind_s(config.Config.ROOT_DN, config.Config.ROOT_PW) conn.delete_s("cn=" + group + ",ou=Group," + ldap_base) return {"success": True, "message": "Deleted user."}, 200 except Exception as err: return errors.all_errors(type(err).__name__, err)
def get(self): """ List all LDAP users --- tags: - User Management responses: 200: description: Pair of username/token is valid 203: description: Invalid username/token pair 400: description: Malformed client input """ ldap_host = config.Config.LDAP_HOST base_dn = config.Config.LDAP_BASE_DN all_ldap_users = {} user_search_base = "ou=People," + base_dn user_search_scope = ldap.SCOPE_SUBTREE user_filter = 'uid=*' try: con = ldap.initialize('ldap://{}'.format(ldap_host)) users = con.search_s(user_search_base, user_search_scope, user_filter) for user in users: user_base = user[0] username = user[1]['uid'][0].decode('utf-8') all_ldap_users[username] = user_base return {"success": True, "message": all_ldap_users}, 200 except Exception as err: return all_errors(type(err).__name__, err)
def get(self): """ Return available Linux UID/GID numbers --- tags: - LDAP management responses: 200: description: Return list of UID/GID 500: description: Unable to contact LDAP server 501: description: Unknown error (followed by trace) """ uid_in_use = [] gid_in_use = [] UID = 5000 GID = 5000 MAX_IDS = 65533 # 65534 is for "nobody" and 65535 is reserved try: conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}") conn.simple_bind_s( f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}", config.Config.ROOT_PW) user_res = conn.search_s( f"ou=Users,ou={config.Config.NETBIOS},{config.Config.LDAP_BASE}", ldap.SCOPE_SUBTREE, 'objectClass=person', ["uidNumber"]) group_res = conn.search_s( f"ou=Users,ou={config.Config.NETBIOS},{config.Config.LDAP_BASE}", ldap.SCOPE_SUBTREE, 'objectClass=group', ["gidNumber"]) for a in user_res: if a[1]: uid_temp = int(a[1].get('uidNumber')[0]) uid_in_use.append(uid_temp) for a in group_res: if a[1]: gid_temp = int(a[1].get('gidNumber')[0]) gid_in_use.append(gid_temp) return { "success": True, "message": { "proposed_uid": choice([ i for i in range(UID, MAX_IDS) if i not in uid_in_use ]), "proposed_gid": choice([ i for i in range(GID, MAX_IDS) if i not in gid_in_use ]), "uid_in_use": uid_in_use, "gid_in_use": gid_in_use } }, 200 except Exception as err: return errors.all_errors(type(err).__name__, err)
def post(self): """ Add SUDO permission for a user --- tags: - User Management parameters: - in: body name: body schema: required: - user properties: user: type: string description: user of the SOCA user token: type: string description: token associated to the user responses: 200: description: Pair of user/token is valid 203: description: Invalid user/token pair 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument('user', type=str, location='form') args = parser.parse_args() user = args["user"] if user is None: return {"success": False, "message": "user can not be empty"}, 400 # check if user exist ldap_host = config.Config.LDAP_HOST base_dn = config.Config.LDAP_BASE_DN try: conn = ldap.initialize('ldap://{}'.format(ldap_host)) conn.simple_bind_s(config.Config.ROOT_DN, config.Config.ROOT_PW) dn_user = "******" + user + ",ou=Sudoers," + base_dn attrs = [ ('objectClass', ['top'.encode('utf-8'), 'sudoRole'.encode('utf-8')]), ('sudoHost', ['ALL'.encode('utf-8')]), ('sudoUser', [str(user).encode('utf-8')]), ('sudoCommand', ['ALL'.encode('utf-8')]) ] conn.add_s(dn_user, attrs) change_user_key_scope = ApiKeys.query.filter_by(user=user, is_active=True).all() if change_user_key_scope: for key in change_user_key_scope: key.scope = "sudo" db.session.commit() return {"success": True, "message": user + " now has admin permission"}, 200 except Exception as err: return errors.all_errors(type(err).__name__, err)
def delete(self): """ Delete an EC2 AMI registered as DCV image on SOCA --- tags: - DCV parameters: - in: body name: body schema: required: - ami_label properties: ami_label: type: string description: Friendly name for your image responses: 200: description: Pair of user/token is valid 401: description: Invalid user/token pair """ parser = reqparse.RequestParser() parser.add_argument('ami_label', type=str, location='form') args = parser.parse_args() if args["ami_label"] is None: return errors.all_errors('CLIENT_MISSING_PARAMETER', "ami_label (str) is required.") check_session = AmiList.query.filter_by(ami_label=args["ami_label"], is_active=True).first() if check_session: check_session.is_active = False check_session.deactivated_on = datetime.datetime.utcnow() try: db.session.commit() logger.info(f"AMI Label {args['ami_label']} deleted from SOCA") return {"success": True, "message": f"{args['ami_label']} deleted from SOCA successfully"}, 200 except exc.SQLAlchemyError as e: db.session.rollback() logger.error(f"AMI Label {args['ami_label']} delete failed {e}") return errors.all_errors('IMAGE_DELETE_ERROR', f"{args['ami_label']} could not have been deleted because of {e}") else: return errors.all_errors('IMAGE_DELETE_ERROR', f"{args['ami_label']} could not be found")
def get(self): """ Check SUDO permissions for a user --- tags: - User Management parameters: - in: body name: body schema: required: - user properties: user: type: string description: user of the SOCA user responses: 200: description: Pair of user/token is valid 203: description: Invalid user/token pair 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument('user', type=str, location='args') args = parser.parse_args() user = args["user"] if user is None: return {"success": False, "message": "user can not be empty"}, 400 try: logger.info(f"Checking SUDO permission for {user}") conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}") conn.protocol_version = 3 conn.set_option(ldap.OPT_REFERRALS, 0) conn.simple_bind_s(f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}", config.Config.ROOT_PW) user_search_base = f"CN={user},OU=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}" sudoers_group = config.Config.SUDOERS_GROUP filter_criteria = f"(&(objectClass=group)(member={user_search_base}))" for dn, entry in conn.search_s(config.Config.LDAP_BASE, ldap.SCOPE_SUBTREE, filter_criteria, ["cn", "member"]): if isinstance(entry, dict): logger.info(f"Checking {sudoers_group}: {dn}, {entry}") if "cn" in entry.keys(): if entry["cn"][0].decode("utf-8") == sudoers_group: logger.info("Logger SUDOERS group detected, checking members") if "member" in entry.keys(): for users in entry["member"]: logger.info(f"Detected sudo permission for {users}") if user_search_base.lower() == users.lower().decode("utf-8"): return {'success': True, 'message': "User has SUDO permissions."}, 200 return {'success': False, 'message': "User does not have SUDO permissions."}, 222 except Exception as err: return errors.all_errors(type(err).__name__, err)
def get(self): """ Retrieve information for a specific user --- tags: - User Management parameters: - in: body name: body schema: required: - user properties: user: type: string description: user of the SOCA user responses: 200: description: Return user information 203: description: Unknown user 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument('user', type=str, location='args') args = parser.parse_args() user = args["user"] if user is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "user (str) parameter is required") try: conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}") conn.simple_bind_s( f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}", config.Config.ROOT_PW) conn.protocol_version = 3 conn.set_option(ldap.OPT_REFERRALS, 0) user_search_base = f"OU=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}" user_search_scope = ldap.SCOPE_SUBTREE user_filter = f"(&(objectClass=user)(sAMAccountName={user}))" check_user = conn.search_s(user_search_base, user_search_scope, user_filter) if check_user.__len__() == 0: return {"success": False, "message": "Unknown user"}, 203 else: return {"success": True, "message": str(check_user)}, 200 except Exception as err: return { "success": False, "message": "Unknown error: " + str(err) }, 500
def get(self): """ Check SUDO permissions for a user --- tags: - User Management parameters: - in: body name: body schema: required: - user properties: user: type: string description: user of the SOCA user responses: 200: description: Pair of user/token is valid 203: description: Invalid user/token pair 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument('user', type=str, location='args') args = parser.parse_args() user = args["user"] if user is None: return {"success": False, "message": "user can not be empty"}, 400 ldap_host = config.Config.LDAP_HOST base_dn = config.Config.LDAP_BASE_DN try: con = ldap.initialize('ldap://{}'.format(ldap_host)) sudoers_search_base = "ou=Sudoers," + base_dn sudoers_search_scope = ldap.SCOPE_SUBTREE sudoers_filter = 'cn=' + user is_sudo = con.search_s(sudoers_search_base, sudoers_search_scope, sudoers_filter) if is_sudo.__len__() > 0: return { 'success': True, 'message': "User has SUDO permissions." }, 200 else: return { 'success': False, 'message': "User does not have SUDO permissions." }, 222 except Exception as err: return errors.all_errors(type(err).__name__, err)
def get(self): """ Return available Linux UID/GID numbers --- tags: - LDAP management responses: 200: description: Return list of UID/GID 500: description: Unable to contact LDAP server 501: description: Unknown error (followed by trace) """ uid_in_use = [] gid_in_use = [] try: conn = ldap.initialize("ldap://" + config.Config.LDAP_HOST) user_res = conn.search_s(config.Config.LDAP_BASE_DN, ldap.SCOPE_SUBTREE, 'objectClass=Group', ["uidNumber"]) group_res = conn.search_s(config.Config.LDAP_BASE_DN, ldap.SCOPE_SUBTREE, 'objectClass=posixGroup', ["gidNumber"]) except Exception as err: return errors.all_errors(type(err).__name__, err) UID = 5000 GID = 5000 MAX_IDS = 65533 # 65534 is for "nobody" and 65535 is reserved for uid in user_res: uid_temp = int(uid[1].get('uidNumber')[0]) uid_in_use.append(uid_temp) for gid in group_res: gid_temp = int(gid[1].get('gidNumber')[0]) gid_in_use.append(gid_temp) return { "success": True, "message": { "proposed_uid": choice([i for i in range(UID, MAX_IDS) if i not in uid_in_use]), "proposed_gid": choice([i for i in range(GID, MAX_IDS) if i not in gid_in_use]), "uid_in_use": uid_in_use, "gid_in_use": gid_in_use } }, 200
def delete(self): """ Remove SUDO permission for a user --- tags: - User Management parameters: - in: body name: body schema: required: - user properties: user: type: string description: user of the SOCA user responses: 200: description: Pair of user/token is valid 203: description: Invalid user/token pair 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument('user', type=str, location='form') args = parser.parse_args() user = args["user"] if user is None: return {"success": False, "message": "user can not be empty"}, 400 ldap_host = config.Config.LDAP_HOST base_dn = config.Config.LDAP_BASE_DN try: conn = ldap.initialize('ldap://{}'.format(ldap_host)) conn.simple_bind_s(config.Config.ROOT_DN, config.Config.ROOT_PW) dn_user = "******" + user + ",ou=Sudoers," + base_dn conn.delete_s(dn_user) change_user_key_scope = ApiKeys.query.filter_by( user=user, is_active=True).all() if change_user_key_scope: for key in change_user_key_scope: key.scope = "user" db.session.commit() return { "success": True, "message": user + " does not have admin permission anymore" }, 200 except Exception as err: return errors.all_errors(type(err).__name__, err)
def get(self): """ List all LDAP groups --- tags: - Group Management responses: 200: description: Group info 400: description: Malformed client input 500: description: Backend issue """ try: all_ldap_groups = {} conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}") conn.protocol_version = 3 conn.set_option(ldap.OPT_REFERRALS, 0) conn.simple_bind_s( f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}", config.Config.ROOT_PW) group_search_base = f"OU=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}" filter_criteria = f"(objectClass=group)" groups = conn.search_s(group_search_base, ldap.SCOPE_SUBTREE, filter_criteria, ["cn", "member"]) logger.info( f"Checking all AD groups with search filter {filter_criteria} and base {group_search_base}" ) for group in groups: logger.info(f"Detected {group}") group_base = group[0] group_name = group[1]['cn'][0].decode('utf-8') members = [] if "member" in group[1].keys(): for member in group[1]["member"]: user = re.match("cn=(\w+),", member.decode("utf-8")) if user: members.append(user.group(1)) else: # handle case where lDAP ownership was done outside of SOCA members.append(member.decode("utf-8")) # return {"success": False, "message": "Unable to retrieve memberUid for this group: " + str(group_base) + "members: "+str(group[1]["memberUid"])}, 500 all_ldap_groups[group_name] = { "group_dn": group_base, "members": members } logger.info(f"Groups detected {all_ldap_groups}") return {"success": True, "message": all_ldap_groups}, 200 except Exception as err: return errors.all_errors(type(err).__name__, err)
def post(self): """ Add SUDO permission for a user --- tags: - User Management parameters: - in: body name: body schema: required: - user properties: user: type: string description: user of the SOCA user token: type: string description: token associated to the user responses: 200: description: Pair of user/token is valid 203: description: Invalid user/token pair 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument('user', type=str, location='form') args = parser.parse_args() user = args["user"] if user is None: return {"success": False, "message": "user can not be empty"}, 400 conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}") conn.simple_bind_s(f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}", config.Config.ROOT_PW) sudoers_group = config.Config.SUDOERS_GROUP_DN dn_user = f"cn={user},ou=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}" logger.info(f"Adding SUDO permission for {dn_user}") mod_attrs = [(ldap.MOD_ADD, 'member', [dn_user.encode("utf-8")])] try: conn.modify_s(sudoers_group, mod_attrs) change_user_key_scope = ApiKeys.query.filter_by(user=user, is_active=True).all() if change_user_key_scope: for key in change_user_key_scope: key.scope = "sudo" db.session.commit() logger.info(f"Permission granted for {user}") return {"success": True, "message": f"{user} now has admin permission"}, 200 except Exception as e: return errors.all_errors(type(e).__name__, e)
def get(self): """ Return information for a given job --- tags: - Scheduler parameters: - in: body name: body schema: optional: - job_id properties: job_id: type: string description: ID of the job responses: 200: description: List of all jobs 500: description: Backend error """ parser = reqparse.RequestParser() parser.add_argument('job_id', type=str, location='args') args = parser.parse_args() job_id = args['job_id'] if job_id is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "job_id (str) parameter is required") try: qstat_command = config.Config.PBS_QSTAT + " -f " + job_id + " -Fjson" try: get_job_info = subprocess.check_output(shlex.split(qstat_command)) try: sanitize_input = get_job_info.decode("utf-8") for match in re.findall('"project":(\d+),', sanitize_input, re.MULTILINE): # Clear case where project starts with digits to prevent leading zero errors print(f'Detected "project":{match}, > Will be replaced to prevent int leading zero error') sanitize_input = sanitize_input.replace(f'"project":{match},', f'"project":"{match}",') job_info = ast.literal_eval(sanitize_input) except Exception as err: return {"success": False, "message": "Unable to retrieve this job. Job may have terminated. Error: " + str(job_info)}, 210 job_key = list(job_info["Jobs"].keys())[0] return {"success": True, "message": job_info["Jobs"][job_key]}, 200 except Exception as err: return {"success": False, "message": "Unable to retrieve Job ID (job may have terminated and is no longer in the queue)"}, 210 except Exception as err: return {"success": False, "message": "Unknown error: " + str(err)}, 500
def get(self): """ Retrieve information for a specific user --- tags: - User Management parameters: - in: body name: body schema: required: - user properties: user: type: string description: user of the SOCA user responses: 200: description: Return user information 203: description: Unknown user 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument('user', type=str, location='args') args = parser.parse_args() user = args["user"] if user is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "user (str) parameter is required") user_filter = 'cn=' + user user_search_base = "ou=People," + config.Config.LDAP_BASE_DN user_search_scope = ldap.SCOPE_SUBTREE try: conn = ldap.initialize('ldap://' + config.Config.LDAP_HOST) check_user = conn.search_s(user_search_base, user_search_scope, user_filter) if check_user.__len__() == 0: return {"success": False, "message": "Unknown user"}, 203 else: return {"success": True, "message": str(check_user)}, 200 except Exception as err: return { "success": False, "message": "Unknown error: " + str(err) }, 500
def get(self): """ List all LDAP groups --- tags: - Group Management responses: 200: description: Group info 400: description: Malformed client input 500: description: Backend issue """ # List all LDAP users ldap_host = config.Config.LDAP_HOST base_dn = config.Config.LDAP_BASE_DN all_ldap_groups = {} group_search_base = "ou=Group," + base_dn group_search_scope = ldap.SCOPE_SUBTREE group_filter = 'cn=*' try: con = ldap.initialize('ldap://{}'.format(ldap_host)) groups = con.search_s(group_search_base, group_search_scope, group_filter, ["cn", "memberUid"]) for group in groups: group_base = group[0] group_name = group[1]['cn'][0].decode('utf-8') members = [] if "memberUid" in group[1].keys(): for member in group[1]["memberUid"]: user = re.match("uid=(\w+),", member.decode("utf-8")) if user: members.append(user.group(1)) else: # handle case where lDAP ownership was done outside of SOCA members.append(member.decode("utf-8")) # return {"success": False, "message": "Unable to retrieve memberUid for this group: " + str(group_base) + "members: "+str(group[1]["memberUid"])}, 500 all_ldap_groups[group_name] = { "group_dn": group_base, "members": members } return {"success": True, "message": all_ldap_groups}, 200 except Exception as err: return errors.all_errors(type(err).__name__, err)
def get(self): """ Return information for a given job --- tags: - Scheduler parameters: - in: body name: body schema: optional: - job_id properties: job_id: type: string description: ID of the job responses: 200: description: List of all jobs 500: description: Backend error """ parser = reqparse.RequestParser() parser.add_argument('job_id', type=str, location='args') args = parser.parse_args() job_id = args['job_id'] if job_id is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "job_id (str) parameter is required") try: qstat_command = config.Config.PBS_QSTAT + " -f " + job_id + " -Fjson" try: get_job_info = subprocess.check_output(shlex.split(qstat_command)) try: job_info = json.loads(((get_job_info.decode('utf-8')).rstrip().lstrip())) except Exception as err: return {"success": False, "message": "Unable to retrieve this job. Job may have terminated. Error: " + str(job_info)}, 210 job_key = list(job_info["Jobs"].keys())[0] return {"success": True, "message": job_info["Jobs"][job_key]}, 200 except Exception as err: return {"succes": False, "message": "Unable to retrieve Job ID (job may have terminated and is no longer in the queue)"}, 210 except Exception as err: return {"success": False, "message": "Unknown error: " + str(err)}, 500
def get(self): """ List all LDAP users --- tags: - User Management responses: 200: description: Pair of username/token is valid 203: description: Invalid username/token pair 400: description: Malformed client input """ all_ldap_users = {} try: conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}") conn.protocol_version = 3 conn.set_option(ldap.OPT_REFERRALS, 0) conn.simple_bind_s( f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}", config.Config.ROOT_PW) user_search_base = f"OU=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}" filter_criteria = f"(objectClass=person)" logger.info( f"Checking all AD users with search filter {filter_criteria} and base {user_search_base}" ) for dn, entry in conn.search_s(user_search_base, ldap.SCOPE_SUBTREE, filter_criteria, ["cn"]): all_ldap_users[entry["cn"][0].decode("utf-8")] = str(dn) logger.info(f"all ldap users {all_ldap_users}") return {"success": True, "message": all_ldap_users}, 200 except Exception as err: return all_errors(type(err).__name__, err)
def put(self): """ Add/Remove user to/from a LDAP group --- tags: - Group Management parameters: - in: body name: body schema: required: - user - attribute - value properties: group: type: string description: user of the SOCA user user: type: string description: Attribute to change action: type: string description: New attribute value responses: 200: description: LDAP attribute modified successfully 203: description: User already belongs to the group 204: description: User does not belong to the group 400: description: Malformed client input 401: description: Unable to bind LDAP (invalid credentials) 500: description: Backend issue (see trace) """ parser = reqparse.RequestParser() parser.add_argument('group', type=str, location='form') parser.add_argument('user', type=str, location='form') parser.add_argument('action', type=str, location='form') args = parser.parse_args() group = args["group"] user = args["user"] action = args["action"] ALLOWED_ACTIONS = ["add", "remove"] logger.info( f"Received LDAP group modification: {user} will be {action} from {group}" ) if user is None or group is None or action is None: return errors.all_errors( "CLIENT_MISSING_PARAMETER", "user (str), group (str) and action (str) parameters are required" ) if group.endswith(config.Config.GROUP_NAME_SUFFIX): pass else: group = f"{group}{config.Config.GROUP_NAME_SUFFIX}" if action not in ALLOWED_ACTIONS: return { "success": False, "message": "This action is not supported" }, 400 try: logger.info(f"About to {action} {user} to {group}") conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}") conn.simple_bind_s( f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}", config.Config.ROOT_PW) conn.protocol_version = 3 conn.set_option(ldap.OPT_REFERRALS, 0) group_dn = f"cn={group},ou=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}" if "ou=users" in user.lower(): user_dn = user else: user_dn = f"cn={user},ou=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}" get_all_users = get( config.Config.FLASK_ENDPOINT + "/api/ldap/users", headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY}, verify=False) # nosec if get_all_users.status_code == 200: all_users = get_all_users.json()["message"] all_users = dict( (k.lower(), v.lower()) for k, v in all_users.items()) # force lowercase logger.info(f"all users: {all_users}") if user_dn.lower() not in all_users.values(): return { "success": False, "message": "User do not exist." }, 212 else: return { "success": False, "message": "Unable to retrieve list of LDAP users. " + str(get_all_users._content) }, 500 if action == "add": mod_attrs = [(ldap.MOD_ADD, 'member', [user_dn.encode("utf-8")])] else: mod_attrs = [(ldap.MOD_DELETE, 'member', [user_dn.encode("utf-8")])] logger.info( f"About to modify LDAP group for {user_dn}. Action {action} to group {group_dn}" ) conn.modify_s(group_dn, mod_attrs) return { "success": True, "message": "LDAP attribute has been modified correctly" }, 200 except Exception as err: return errors.all_errors(type(err).__name__, err)
def get(self): """ Retrieve API key of the user --- tags: - User Management parameters: - in: body name: body schema: required: - user properties: user: type: string description: user of the SOCA user responses: 200: description: Return the token associated to the user 203: description: No token detected 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument("user", type=str, location='args') args = parser.parse_args() user = args["user"] if user is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "user (str) parameter is required") try: check_existing_key = ApiKeys.query.filter_by(user=user, is_active=True).first() if check_existing_key: return {"success": True, "message": check_existing_key.token}, 200 else: try: # Create an API key for the user if needed user_exist = get(config.Config.FLASK_ENDPOINT + "/api/ldap/user", headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY}, params={"user": user}, verify=False) # nosec if user_exist.status_code == 200: permissions = get(config.Config.FLASK_ENDPOINT + "/api/ldap/sudo", headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY}, params={"user": user}, verify=False) # nosec if permissions.status_code == 200: scope = "sudo" else: scope = "user" api_token = secrets.token_hex(16) new_key = ApiKeys(user=user, token=api_token, is_active=True, scope=scope, created_on=datetime.datetime.utcnow()) db.session.add(new_key) db.session.commit() return {"success": True, "message": api_token}, 200 else: return {"success": False, "message": "Not authorized"}, 401 except Exception as err: return errors.all_errors(type(err).__name__, err) except Exception as err: return errors.all_errors(type(err).__name__, err)
def get(self): """ Retrieve information for a specific group --- tags: - Group Management parameters: - in: body name: body schema: required: - group properties: group: type: string description: user of the SOCA user responses: 200: description: Return user information 203: description: Unknown user 400: description: Malformed client input """ parser = reqparse.RequestParser() parser.add_argument('group', type=str, location='args') args = parser.parse_args() group = args["group"] if group is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "group (str) parameter is required") else: if group.endswith(config.Config.GROUP_NAME_SUFFIX): pass else: group = f"{group}{config.Config.GROUP_NAME_SUFFIX}" try: logger.info(f"Received group request for {group}") conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}") conn.simple_bind_s( f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}", config.Config.ROOT_PW) conn.protocol_version = 3 conn.set_option(ldap.OPT_REFERRALS, 0) group_search_base = f"ou=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}" group_search_scope = ldap.SCOPE_SUBTREE filter_criteria = f"(&(objectClass=group)(cn={group}))" groups = conn.search_s(group_search_base, group_search_scope, filter_criteria) if groups.__len__() == 0: logger.info(f"{group} does not exist") return errors.all_errors("GROUP_DO_NOT_EXIST") for group in groups: logger.info(f"Detected {group}") group_base = group[0] members = [] if "member" in group[1].keys(): for member in group[1]["member"]: logger.info(f"Detected group member {member}") members.append(member.decode("utf-8")) # return {"success": False, "message": "Unable to retrieve memberUid for this group: " + str(group_base) + "members: "+str(group[1]["memberUid"])}, 500 return { "success": True, "message": { "group_dn": group_base, "members": members } } except Exception as err: return errors.all_errors(type(err).__name__, err)
def post(self): """ Create a new LDAP group --- tags: - Group Management parameters: - in: body name: body schema: required: - group optional: - gid - users properties: group: type: string description: Name of the group gid: type: integer description: Linux GID to be associated to the group users: type: list description: List of user(s) to add to the group responses: 200: description: Group created 203: description: Group already exist 204: description: User does not exist and can't be added to the group 400: description: Malformed client input 500: description: Backend issue """ parser = reqparse.RequestParser() parser.add_argument('group', type=str, location='form') parser.add_argument('gid', type=int, location='form') parser.add_argument('members', type=str, location='form') # comma separated list of users args = parser.parse_args() gid = args["gid"] group = args["group"] if args["members"] is None: members = [] else: members = args["members"].split(",") get_gid = get(config.Config.FLASK_ENDPOINT + '/api/ldap/ids', headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY}, verify=False) # nosec if get_gid.status_code == 200: current_ldap_gids = get_gid.json() else: return errors.all_errors("UNABLE_RETRIEVE_IDS", str(get_gid.text)) if gid is None: group_id = current_ldap_gids["message"]["proposed_gid"] else: if gid in current_ldap_gids["message"]["gid_in_use"]: return errors.all_errors("GID_ALREADY_IN_USE") group_id = gid if group is None: return errors.all_errors("CLIENT_MISSING_PARAMETER", "group (str) parameter is required") else: if group.endswith(config.Config.GROUP_NAME_SUFFIX): pass else: group = f"{group}{config.Config.GROUP_NAME_SUFFIX}" try: logger.info( f"About to create new group {group} with members {members}") conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}") conn.simple_bind_s( f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}", config.Config.ROOT_PW) conn.protocol_version = 3 conn.set_option(ldap.OPT_REFERRALS, 0) group_members = [] group_dn = f"cn={group},ou=Users,ou={config.Config.NETBIOS},{config.Config.LDAP_BASE}" if members is not None: if not isinstance(members, list): return { "success": False, "message": "users must be a valid list" }, 400 if len(members) > 0: get_all_users = get( config.Config.FLASK_ENDPOINT + "/api/ldap/users", headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY}, verify=False) # nosec if get_all_users.status_code == 200: all_users = get_all_users.json()["message"] all_users = dict( (k.lower(), v) for k, v in all_users.items()) # force lowercase else: return { "success": False, "message": "Unable to retrieve the list of SOCA users " + str(get_all_users.json()) }, 212 for member in members: #if "ou=users" in member.lower(): # dn_user = member #else: # dn_user = f"cn={member},ou=Users,ou={config.Config.NETBIOS},{config.Config.LDAP_BASE}" logger.info( f"Checking if {member} exist in {all_users}") if member.lower() not in all_users.keys(): return { "success": False, "message": "Unable to create group because user (" + member + ") does not exist." }, 211 else: group_members.append(member) attrs = [('objectClass', ['top'.encode('utf-8'), 'group'.encode('utf-8')]), ('gidNumber', [str(group_id).encode('utf-8')]), ('sAMAccountName', [f"{group}".encode('utf-8')])] conn.add_s(group_dn, attrs) users_not_added = [] for member in group_members: add_member_to_group = put( config.Config.FLASK_ENDPOINT + "/api/ldap/group", headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY}, data={ "group": group, "user": member, "action": "add" }, verify=False) # nosec if add_member_to_group.status_code != 200: users_not_added.append(member) if users_not_added.__len__() == 0: return { "success": True, "message": "Group created successfully" }, 200 else: return { "success": True, "message": "Group created successfully but unable to add some users: " + str(users_not_added) }, 214 except Exception as err: return errors.all_errors(type(err).__name__, err)
def get(self): """ List DCV desktop sessions for a given user --- tags: - DCV parameters: - in: body name: body schema: required: - os - state properties: session_number: type: string description: Session Number os: type: string description: DCV session type (Windows or Linux) state: type: string description: active or inactive run_state: type: string description: The state of the desktop (running, pending, stopped ..) responses: 200: description: Pair of user/token is valid 401: description: Invalid user/token pair """ parser = reqparse.RequestParser() parser.add_argument("os", type=str, location='args') parser.add_argument("is_active", type=str, location='args') parser.add_argument("session_number", type=str, location='args') parser.add_argument("state", type=str, location='args') args = parser.parse_args() logger.info(f"Received parameter for listing DCV desktop: {args}") user = request.headers.get("X-SOCA-USER") if user is None: return errors.all_errors("X-SOCA-USER_MISSING") if args["os"] is None or args["is_active"] is None: return errors.all_errors( 'CLIENT_MISSING_PARAMETER', "os (str), is_active (str) are required.") if args["os"] not in ["windows", "linux"]: return errors.all_errors("CLIENT_INVALID_PARAMETER", "os (str) must be windows or linux") if args["is_active"].lower() not in ["true", "false"]: return errors.all_errors("CLIENT_INVALID_PARAMETER", "is_active (str) must be true, false") # Retrieve sessions is_active = True if args["is_active"].lower() == "true" else False if args["os"].lower() == "windows": all_dcv_sessions = WindowsDCVSessions.query.filter( WindowsDCVSessions.user == user) if args["state"] is not None: all_dcv_sessions = all_dcv_sessions.filter( WindowsDCVSessions.session_state == args["state"]) if args["session_number"] is not None: all_dcv_sessions = all_dcv_sessions.filter( WindowsDCVSessions.session_number == args["session_number"]) all_dcv_sessions = all_dcv_sessions.filter( WindowsDCVSessions.is_active == is_active) else: all_dcv_sessions = LinuxDCVSessions.query.filter( LinuxDCVSessions.user == user) if args["state"] is not None: all_dcv_sessions = all_dcv_sessions.filter( LinuxDCVSessions.session_state == args["state"]) if args["session_number"] is not None: all_dcv_sessions = all_dcv_sessions.filter( LinuxDCVSessions.session_number == args["session_number"]) all_dcv_sessions = all_dcv_sessions.filter( LinuxDCVSessions.is_active == is_active) logger.info(f"Checking {args['os']} desktops for {user}") user_sessions = {} for session_info in all_dcv_sessions.all(): try: session_number = session_info.session_number session_state = session_info.session_state tag_uuid = session_info.tag_uuid session_name = session_info.session_name session_host_private_dns = session_info.session_host_private_dns session_token = session_info.session_token session_local_admin_password = session_info.session_local_admin_password if args[ "os"] == "windows" else None if args["os"].lower() != "windows": session_linux_distribution = session_info.session_linux_distribution session_instance_type = session_info.session_instance_type session_instance_id = session_info.session_instance_id support_hibernation = session_info.support_hibernation dcv_authentication_token = session_info.dcv_authentication_token session_id = session_info.session_id session_schedule = { "monday": f"{session_info.schedule_monday_start}-{session_info.schedule_monday_stop}", "tuesday": f"{session_info.schedule_tuesday_start}-{session_info.schedule_tuesday_stop}", "wednesday": f"{session_info.schedule_wednesday_start}-{session_info.schedule_wednesday_stop}", "thursday": f"{session_info.schedule_thursday_start}-{session_info.schedule_thursday_stop}", "friday": f"{session_info.schedule_friday_start}-{session_info.schedule_friday_stop}", "saturday": f"{session_info.schedule_saturday_start}-{session_info.schedule_saturday_stop}", "sunday": f"{session_info.schedule_sunday_start}-{session_info.schedule_sunday_stop}" } stack_name = f"{read_secretmanager.get_soca_configuration()['ClusterId']}-{session_name}-{user}" if args["os"].lower() == "windows": host_info = get_host_info( tag_uuid, read_secretmanager.get_soca_configuration() ["ClusterId"], "windows") else: host_info = get_host_info( tag_uuid, read_secretmanager.get_soca_configuration() ["ClusterId"], session_linux_distribution) logger.info(f"Host Info {host_info}") if not host_info: try: check_stack = client_cfn.describe_stacks( StackName=stack_name) logger.info(f"Host Info check_stack {check_stack}") if check_stack['Stacks'][0]['StackStatus'] in [ 'CREATE_FAILED', 'ROLLBACK_COMPLETE', 'ROLLBACK_FAILED' ]: logger.info(f"Host Info DEACTIVATE") # no host detected, session no longer active session_info.is_active = False session_info.deactivated_on = datetime.utcnow() db.session.commit() except Exception as err: logger.error( f"Error checking CFN stack {stack_name} due to {err}" ) session_info.is_active = False session_info.deactivated_on = datetime.utcnow() db.session.commit() else: # detected EC2 host for the session if not dcv_authentication_token: session_info.session_host_private_dns = host_info[ "private_dns"] session_info.session_host_private_ip = host_info[ "private_ip"] session_info.session_instance_id = host_info[ "instance_id"] authentication_data = json.dumps({ "system": "windows" if args["os"].lower() == "windows" else session_linux_distribution, "session_instance_id": host_info["instance_id"], "session_token": session_token, "session_user": user }) session_authentication_token = base64.b64encode( encrypt(authentication_data)).decode("utf-8") session_info.dcv_authentication_token = session_authentication_token db.session.commit() if "status" not in host_info.keys(): try: check_stack = client_cfn.describe_stacks( StackName=stack_name) logger.info(f"Host Info check_stack {check_stack}") if check_stack['Stacks'][0]['StackStatus'] in [ 'CREATE_FAILED', 'ROLLBACK_COMPLETE', 'ROLLBACK_FAILED' ]: logger.info(f"Host Info DEACTIVATE") # no host detected, session no longer active session_info.is_active = False session_info.deactivated_on = datetime.utcnow() db.session.commit() except Exception as err: logger.error( f"Error checking CFN stack {stack_name} due to {err}" ) session_info.is_active = False session_info.deactivated_on = datetime.utcnow() db.session.commit() else: if host_info["status"] in [ "stopped", "stopping" ] and session_state != "stopped": session_state = "stopped" session_info.session_state = "stopped" db.session.commit() if session_state == "pending" and session_host_private_dns is not False: check_dcv_state = get( f"https://{read_secretmanager.get_soca_configuration()['LoadBalancerDNSName']}/{session_host_private_dns}/", allow_redirects=False, verify=False) # nosec logger.info( "Checking {} for {} and received status {} ".format( 'https://' + read_secretmanager.get_soca_configuration() ['LoadBalancerDNSName'] + '/' + session_host_private_dns + '/', session_info, check_dcv_state.status_code)) if check_dcv_state.status_code == 200: session_info.session_state = "running" db.session.commit() user_sessions[session_number] = { "url": f"https://{read_secretmanager.get_soca_configuration()['LoadBalancerDNSName']}/{session_host_private_dns}/", "session_local_admin_password": session_local_admin_password, "session_state": session_state, "session_authentication_token": dcv_authentication_token, "session_id": session_id, "session_name": session_name, "session_instance_id": session_instance_id, "session_instance_type": session_instance_type, "tag_uuid": tag_uuid, "support_hibernation": support_hibernation, "session_schedule": session_schedule, "connection_string": f"https://{read_secretmanager.get_soca_configuration()['LoadBalancerDNSName']}/{session_host_private_dns}/?authToken={dcv_authentication_token}#{session_id}" } #logger.info(user_sessions) except Exception as err: exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] logger.error(exc_type, fname, exc_tb.tb_lineno) return errors.all_errors(type(err).__name__, err) return {"success": True, "message": user_sessions}, 200
def post(self): """ Register a new EC2 AMI as DCV image on SOCA --- tags: - DCV parameters: - in: body name: body schema: required: - os - ami_id - ami_label - root_size properties: ami_id: type: string description: EC2 ID of the AMI os: type: string description: Windows or Linux ami_label: type: string description: Friendly name for your image root_size: type: string description: Minimum size of your EC2 AMI responses: 200: description: Pair of user/token is valid 401: description: Invalid user/token pair """ parser = reqparse.RequestParser() parser.add_argument('ami_id', type=str, location='form') parser.add_argument('os', type=str, location='form') parser.add_argument('ami_label', type=str, location='form') parser.add_argument('root_size', type=str, location='form') args = parser.parse_args() ami_id = args["ami_id"] ami_label = str(args["ami_label"]) os = args["os"] if args["os"] is None or args["ami_label"] is None or args["ami_id"] is None or args["root_size"] is None: return errors.all_errors('CLIENT_MISSING_PARAMETER', "os (str), ami_id (str), ami_label (str) and root_size (str) are required.") if args["os"].lower() not in ["centos7", "rhel7", "amazonlinux2", "windows"]: return errors.all_errors('CLIENT_MISSING_PARAMETER', "os must be centos7, rhel7, amazonlinux2, or windows") try: root_size = int(args["root_size"]) except ValueError: return errors.all_errors('IMAGE_REGISTER_ERROR', f"{root_size} must be a valid integer") soca_labels = get_ami_info() # Register AMI to SOCA if ami_label not in soca_labels.keys(): try: ec2_response = ec2_client.describe_images(ImageIds=[ami_id], Filters=[{'Name': 'state', 'Values': ['available']}]) if (len(ec2_response["Images"]) != 0): new_ami = AmiList(ami_id=ami_id, ami_type=os.lower(), ami_label=ami_label, is_active=True, ami_root_disk_size=root_size, created_on=datetime.datetime.utcnow()) try: db.session.add(new_ami) db.session.commit() return {"success": True, "message": f"{ami_id} registered successfully in SOCA as {ami_label}"}, 200 except SQLAlchemyError as e: db.session.rollback() logger.error(f"Failed Creating AMI {ami_label} {ami_id} {e}") return errors.all_errors('IMAGE_REGISTER_ERROR', f"{ami_id} registration not successful") else: logger.error(f"{ami_id} is not available in AWS account") return errors.all_errors('IMAGE_REGISTER_ERROR', f"{ami_id} is not available in AWS account. If you just created it, make sure the state of the image is 'available' on the AWS console") except botocore.exceptions.ClientError as error: logger.error(f"Failed Creating AMI {ami_label} {ami_id} {error}") return errors.all_errors('IMAGE_REGISTER_ERROR', f"{ami_id} Couldn't locate {ami_id} in AWS account. Make sure you do have permission to view it") else: logger.error(f"Label already in use {ami_label}") return errors.all_errors('IMAGE_REGISTER_ERROR', f"Label {ami_label} already in use. Please enter a unique label")