Example #1
0
def grant_access(user):
    """Grant the user access to reports.rc.nectar.org.au.
    """
    ksclient = keystone.client()
    user = keystone.get_user(ksclient, user)
    keystone.add_project_roles(user.default_project_id, user.id, [ROLE])
    keystone.print_members(ksclient, user.default_project_id)
def _populate_project_dict(instances):
    session = keystone.get_session()
    ksclient = keystone.client(session=session)
    project = collections.defaultdict(dict)
    for instance in instances:
        if instance['project_name'] in project.keys():
            project[instance['project_name']]['instances'].append(instance)
        else:
            cclist = []
            for role in ['TenantManager', 'Member']:
                members = keystone.list_members(instance['project'], role)
                if not members:
                    continue
                for uid in members.keys():
                    user = keystone.get_user(ksclient, uid, use_cache=True)
                    if getattr(user, 'enabled', None) and \
                       getattr(user, 'email', None):
                        cclist.append(user.email)
            # rule out the projects where has no valid recipients(tempest. etc)
            if cclist:
                project[instance['project_name']] = {'instances': [instance]}
                project[instance['project_name']].update(
                    {'recipients': cclist})

    return project
Example #3
0
def revoke_access(user):
    """Revoke access to reports.rc.nectar.org.au for the user.
    """
    ksclient = keystone.client()
    user = keystone.get_user(ksclient, user)
    keystone.remove_project_roles(user.default_project_id, user.id, [ROLE])
    keystone.print_members(ksclient, user.default_project_id)
Example #4
0
def generate_instance_info(instance_id, style=None):
    nc = nova.client()
    kc = keystone.client()
    gc = glance.get_glance_client(kc)

    try:
        instance = nc.servers.get(instance_id)
    except n_exc.NotFound:
        error("Instance {} not found".format(instance_id))

    info = instance._info.copy()
    for network_label, address_list in instance.networks.items():
        info["%s network" % network_label] = ", ".join(address_list)

    flavor = info.get("flavor", {})
    flavor_id = flavor.get("id", "")

    try:
        info["flavor"] = "%s (%s)" % (nova.get_flavor(nc, flavor_id).name, flavor_id)
    except Exception:
        info["flavor"] = "%s (%s)" % ("Flavor not found", flavor_id)

    # Image
    image = info.get("image", {})
    if image:
        image_id = image.get("id", "")
        try:
            img = gc.images.get(image_id)
            nectar_build = img.properties.get("nectar_build", "N/A")
            info["image"] = "%s (%s, NeCTAR Build %s)" % (img.name, img.id, nectar_build)
        except Exception:
            info["image"] = "Image not found (%s)" % image_id
    else:  # Booted from volume
        info["image"] = "Attempt to boot from volume - no image supplied"

    # Tenant
    tenant_id = info.get("tenant_id")
    if tenant_id:
        try:
            tenant = keystone.get_tenant(kc, tenant_id)
            info["tenant_id"] = "%s (%s)" % (tenant.name, tenant.id)
        except Exception:
            pass

    # User
    user_id = info.get("user_id")
    if user_id:
        try:
            user = keystone.get_user(kc, user_id)
            info["user_id"] = "%s (%s)" % (user.name, user.id)
        except Exception:
            pass

    # Remove stuff
    info.pop("links", None)
    info.pop("addresses", None)
    info.pop("hostId", None)
    info.pop("security_groups", None)

    return _format_instance(info, style=style)
Example #5
0
def get_tenant_managers_emails(kc, instance):
    """Build a list of email addresses"""
    email_addresses = []
    project = keystone.get_project(kc, instance.tenant_id)
    role = kc.roles.find(name='TenantManager')
    ras = kc.role_assignments.list(project=project, role=role,
                                   include_names=True)
    for ra in ras:
        u = keystone.get_user(kc, ra.user['id'])
        email_addresses.append(u.email)
    return email_addresses
Example #6
0
def extract_server_info(server, ksclient):
    server_info = collections.defaultdict(dict)
    try:
        server_info['id'] = server.id
        server_info['name'] = server.name
        server_info['status'] = server.status

        server_info['flavor'] = server.flavor['id']
        server_info['host'] = getattr(server, "OS-EXT-SRV-ATTR:host")
        server_info['zone'] = getattr(server, "OS-EXT-AZ:availability_zone")

        # handle vms which are not booted from glance images
        server_image = getattr(server, "image", None)
        if server_image:
            server_info['image'] = server_image.get("id", None)
        else:
            server_info['image'] = None

        # handle some tier2 services which using "global" service user/project
        if server.metadata and 'user_id' in server.metadata.keys()\
            and 'project_id' in server.metadata.keys():
            server_info['user'] = server.metadata['user_id']
            server_info['project'] = server.metadata['project_id']
        else:
            server_info['user'] = server.user_id
            server_info['project'] = server.tenant_id

        server_info['addresses'] = _extract_ip(server)

        server_info['project_name'] = keystone.get_project(
            ksclient, server_info['project'], use_cache=True).name
        user = keystone.get_user(
            ksclient, server_info['user'], use_cache=True)

        # handle instaces created by jenkins/tempest and users without fullname
        # set disabled user's email/fullname as None as it should be ruled out
        if not user.enabled:
            server_info['email'], server_info['fullname'] = None, None
        elif getattr(user, 'email', None):
            server_info['email'], server_info['fullname']\
                    = user.email, getattr(user, 'full_name', None)
        else:
            server_info['email'], server_info['fullname']\
                    = user.name, None
    except KeyError as e:
        raise type(e)(e.message + ' missing in context: %s' % server.to_dict())

    return server_info
Example #7
0
def get_ticket_recipients(instance):
    """Build a list of email addresses"""
    email_addresses = []

    kc = keystone.client()
    user = keystone.get_user(kc, instance.user_id)
    if user.email:
        email_addresses.append(user.email)

    # Add tenant members to ticket recipient list
    tenant = keystone.get_tenant(kc, instance.tenant_id)
    for user in tenant.list_users():
        roles = [r.name for r in user.list_roles(tenant)]
        if 'TenantManager' in roles:
            email_addresses.append(user.email)
    return email_addresses
Example #8
0
def purge_project(tenant_name, dry_run=True):
    """Purge resources and disable a given project """
    if not spawn.find_executable('ospurge'):
        error('ospurge not found in path. Please ensure it is installed.')
    username, password = get_creds()

    if dry_run:
        print("Running in dry-run mode")

    ks_client = keystone.client()
    tenant = keystone.get_tenant(ks_client, tenant_name)
    tenant_member_role = ks_client.roles.find(name='Member')
    ospurge_user = keystone.get_user(ks_client, username)

    if not tenant.enabled:
        print("Enabling project for purge")
        tenant.update(enabled=True)

    if not is_in_project(tenant, ospurge_user):
        print("Adding ospurge user to project")
        tenant.add_user(ospurge_user, tenant_member_role)

    if dry_run:
        run_opt = '--dry-run'
    else:
        run_opt = '--verbose'

    cmd = ("ospurge --dont-delete-project --own-project --username {username} "
           "--password {password} --admin-project {tenant} {run_opt}"
           "".format(username=username, password=password,
                     tenant=tenant.name, run_opt=run_opt))
    print("Running: {}".format(cmd.replace(password, 'xxxx')))
    run(cmd)

    if is_in_project(tenant, ospurge_user):
        print("Removing ospurge user from project")
        tenant.remove_user(ospurge_user, tenant_member_role)

    if tenant.enabled:
        if dry_run and tenant.enabled:
            print("Not disabling project due to dry-run")
        else:
            print("Disabling project")
            tenant.update(enabled=False)
            keystone.set_project_metadata(tenant_name,
                                          'ospurge_date',
                                          str(datetime.datetime.now()))
Example #9
0
def lock_instance(instance_id, dry_run=True):
    """pause and lock an instance"""
    if dry_run:
        print('Running in dry-run mode (use --no-dry-run for realsies)')

    fd = get_freshdesk_client()
    nc = nova.client()
    kc = keystone.client()
    try:
        instance = nc.servers.get(instance_id)
    except n_exc.NotFound:
        error('Instance {} not found'.format(instance_id))

    ticket_id = None
    ticket_url = instance.metadata.get('security_ticket')
    if ticket_url:
        print('Found existing ticket: {}'.format(ticket_url))
        ticket_id = int(ticket_url.split('/')[-1])

        if dry_run:
            print('Would set ticket #{} status to open/urgent'
                  .format(ticket_id))
        else:
            # Set ticket status=waiting for customer, priority=urgent
            print('Setting ticket #{} status to open/urgent'.format(ticket_id))
            fd.tickets.update_ticket(ticket_id, status=6, priority=4)
    else:
        tenant = keystone.get_tenant(kc, instance.tenant_id)
        user = keystone.get_user(kc, instance.user_id)
        email_addresses = get_ticket_recipients(instance)

        # Create ticket if none exist, and add instance info
        subject = 'Security incident for instance {}'.format(instance_id)
        body = '<br />\n'.join([
            'Dear NeCTAR Research Cloud User, ',
            '',
            '',
            'We have reason to believe that cloud instance: '
            '<b>{} ({})</b>'.format(instance.name, instance.id),
            'in the project <b>{}</b>'.format(tenant.name),
            'created by <b>{}</b>'.format(user.email),
            'has been involved in a security incident.',
            '',
            'We have opened this helpdesk ticket to track the details and ',
            'the progress of the resolution of this issue.',
            '',
            'Please reply to this email if you have any questions or ',
            'concerns.',
            '',
            'Thanks, ',
            'NeCTAR Research Cloud Team'
        ])

        if dry_run:
            print('Would create ticket with details:')
            print('  To:      {}'.format(email_addresses))
            print('  Subject: {}'.format(subject))

            print('Would add instance details to ticket:')
            print(generate_instance_info(instance_id))
            print(generate_instance_sg_rules_info(instance_id))
        else:
            print('Creating new Freshdesk ticket')
            ticket = fd.tickets.create_ticket(
                description=body,
                subject=subject,
                email='*****@*****.**',
                cc_emails=email_addresses,
                priority=4,
                status=6,
                tags=['security'])
            ticket_id = ticket.id
            ticket_url = 'https://{}/helpdesk/tickets/{}'\
                         .format(fd.domain, ticket_id)
            nc.servers.set_meta(instance_id, {'security_ticket': ticket_url})
            print('Ticket #{} has been created: {}'
                  .format(ticket_id, ticket_url))

            # Add a private note with instance details
            print('Adding instance information to ticket')
            instance_info = generate_instance_info(instance_id, style='html')
            sg_info = generate_instance_sg_rules_info(instance_id,
                                                      style='html')
            body = '<br/><br/>'.join([instance_info, sg_info])
            fd.comments.create_note(ticket_id, body)

    if dry_run:
        if instance.status != 'ACTIVE':
            print('Instance state {}, will not pause'.format(instance.status))
        else:
            print('Would pause and lock instance {}'.format(instance_id))

        print('Would update ticket with action')
    else:
        # Pause and lock
        if instance.status != 'ACTIVE':
            print('Instance not in ACTIVE state ({}), skipping'
                  .format(instance.status))
        else:
            print('Pausing instance {}'.format(instance_id))
            instance.pause()

        print('Locking instance {}'.format(instance_id))
        instance.lock()

        # Add reply to user
        email_addresses = get_ticket_recipients(instance)
        print('Replying to ticket with action details')
        action = 'Instance <b>{} ({})</b> has been <b>paused and locked</b> '\
                 'pending further investigation'\
                 .format(instance.name, instance_id)
        fd.comments.create_reply(ticket_id, action, cc_emails=email_addresses)
Example #10
0
def lock_instance(instance_id, dry_run=True):
    """pause and lock an instance"""
    if dry_run:
        print('Running in dry-run mode (use --no-dry-run for realsies)')

    fd_config = get_freshdesk_config()
    fd = get_freshdesk_client(fd_config['domain'], fd_config['api_key'])
    nc = nova.client()
    kc = keystone.client()
    try:
        instance = nc.servers.get(instance_id)
    except n_exc.NotFound:
        error('Instance {} not found'.format(instance_id))

    # Pause and lock instance
    if dry_run:
        if instance.status != 'ACTIVE':
            print('Instance state {}, will not pause'.format(instance.status))
        else:
            print('Would pause and lock instance {}'.format(instance_id))
    else:
        if instance.status != 'ACTIVE':
            print('Instance not in ACTIVE state ({}), skipping'
                  .format(instance.status))
        else:
            print('Pausing instance {}'.format(instance_id))
            instance.pause()

        print('Locking instance {}'.format(instance_id))
        instance.lock()

    # Process ticket
    ticket_id = None
    ticket_url = instance.metadata.get('security_ticket')
    if ticket_url:
        print('Found existing ticket: {}'.format(ticket_url))
        ticket_id = int(ticket_url.split('/')[-1])

        if dry_run:
            print('Would set ticket #{} status to open/urgent'
                  .format(ticket_id))
        else:
            # Set ticket status=waiting for customer, priority=urgent
            print('Setting ticket #{} status to open/urgent'.format(ticket_id))
            fd.tickets.update_ticket(ticket_id, status=6, priority=4)
    else:
        project = keystone.get_project(kc, instance.tenant_id)
        user = keystone.get_user(kc, instance.user_id)
        email = user.email or '*****@*****.**'
        name = getattr(user, 'full_name', email)
        cc_emails = get_tenant_managers_emails(kc, instance)

        # Create ticket if none exist, and add instance info
        subject = 'Security incident for instance {} ({})'.format(
            instance.name, instance_id)
        body = '<br />\n'.join([
            'Dear Nectar Research Cloud User, ',
            '',
            '',
            'We have reason to believe that cloud instance: '
            '<b>{} ({})</b>'.format(instance.name, instance_id),
            'in the project <b>{}</b>'.format(project.name),
            'created by <b>{}</b>'.format(email),
            'has been involved in a security incident, and has been locked.',
            '',
            'We have opened this helpdesk ticket to track the details and ',
            'the progress of the resolution of this issue.',
            '',
            'Please reply to this email if you have any questions or ',
            'concerns.',
            '',
            'Thanks, ',
            'Nectar Research Cloud Team'
        ])

        if dry_run:
            print('Would create ticket with details:')
            print('  To:      {} <{}>'.format(name, email))
            print('  CC:      {}'.format(', '.join(cc_emails)))
            print('  Subject: {}'.format(subject))

            print('Would add instance details to ticket:')
            print(generate_instance_info(instance_id))
            print(generate_instance_sg_rules_info(instance_id))
        else:
            print('Creating new Freshdesk ticket')
            ticket = fd.tickets.create_outbound_email(
                name=name,
                description=body,
                subject=subject,
                email=email,
                cc_emails=cc_emails,
                email_config_id=int(fd_config['email_config_id']),
                group_id=int(fd_config['group_id']),
                priority=4,
                status=2,
                tags=['security'])
            ticket_id = ticket.id

            # Use friendly domain name if using prod
            if fd.domain == 'dhdnectar.freshdesk.com':
                domain = 'support.ehelp.edu.au'
            else:
                domain = fd.domain

            ticket_url = 'https://{}/helpdesk/tickets/{}'\
                         .format(domain, ticket_id)
            nc.servers.set_meta(instance_id, {'security_ticket': ticket_url})
            print('Ticket #{} has been created: {}'
                  .format(ticket_id, ticket_url))

            # Add a private note with instance details
            print('Adding instance information to ticket')
            instance_info = generate_instance_info(instance_id, style='html')
            sg_info = generate_instance_sg_rules_info(instance_id,
                                                      style='html')
            body = '<br/><br/>'.join([instance_info, sg_info])
            fd.comments.create_note(ticket_id, body)
Example #11
0
def generate_instance_info(instance_id, style=None):
    nc = nova.client()
    kc = keystone.client()
    gc = glance.client()

    try:
        instance = nc.servers.get(instance_id)
    except n_exc.NotFound:
        error("Instance {} not found".format(instance_id))

    info = instance._info.copy()
    for network_label, address_list in instance.networks.items():
        info['%s network' % network_label] = ', '.join(address_list)

    flavor = info.get('flavor', {})
    flavor_id = flavor.get('id', '')

    try:
        info['flavor'] = '%s (%s)' % (nova.get_flavor(nc, flavor_id).name,
                                      flavor_id)
    except Exception:
        info['flavor'] = '%s (%s)' % ("Flavor not found", flavor_id)

    # Image
    image = info.get('image', {})
    if image:
        image_id = image.get('id', '')
        try:
            img = gc.images.get(image_id)
            nectar_build = img.get('nectar_build', 'N/A')
            info['image'] = ('%s (%s, NeCTAR Build %s)'
                             % (img.name, img.id, nectar_build))
        except Exception:
            info['image'] = 'Image not found (%s)' % image_id

    else:  # Booted from volume
        info['image'] = "Attempt to boot from volume - no image supplied"

    # Tenant
    project_id = info.get('tenant_id')
    if project_id:
        try:
            project = keystone.get_project(kc, project_id)
            info['project_id'] = '%s (%s)' % (project.name, project.id)
        except Exception:
            pass

    # User
    user_id = info.get('user_id')
    if user_id:
        try:
            user = keystone.get_user(kc, user_id)
            info['user_id'] = '%s (%s)' % (user.name, user.id)
        except Exception:
            pass

    # Remove stuff
    info.pop('links', None)
    info.pop('addresses', None)
    info.pop('hostId', None)
    info.pop('security_groups', None)

    return _format_instance(info, style=style)
Example #12
0
def all_servers(client, zone=None, host=None, status=None, ip=None,
                image=None, project=None, user=None, limit=None,
                changes_since=None):
    print("\nListing the instances... ", end="")
    marker = None
    opts = {}
    opts["all_tenants"] = True
    if status:
        opts['status'] = status
    if limit:
        opts['limit'] = limit
    if image:
        opts['image'] = image
    if changes_since:
        opts['changes-since'] = changes_since
    if project:
        try:
            opts['tenant_id'] = keystone.get_project(
                keystone.client(), project, use_cache=True).id
        except Exception:
            sys.exit(1)
    if user:
        try:
            opts['user_id'] = keystone.get_user(
                keystone.client(), user, use_cache=True).id
        except Exception:
            sys.exit(1)

    host_list = parse_nodes(host) if host else None
    az_list = parse_nodes(zone) if zone else None
    ip_list = parse_nodes(ip) if ip else None

    # When using all the searching opts other than project or user,
    # trove instances will be returned by default via nova list api.
    # But they will not when search_opts contain project or user.
    # In order to include them, searching all the instances under
    # project "trove" and filtering them by the instance metadata.
    if project or user:
        inst = _search_trove_instances(client, opts)
    else:
        inst = []

    if host_list:
        for host in host_list:
            opts['host'] = host
            instances = client.servers.list(search_opts=opts)
            if not instances:
                continue
            instances = filter(lambda x: _match_availability_zone(x, az_list),
                               instances)
            instances = filter(lambda x: _match_ip_address(x, ip_list),
                               instances)
            inst.extend(instances)
            if limit and len(inst) >= int(limit):
                break
        return inst
    else:
        while True:
            if marker:
                opts['marker'] = marker
            instances = client.servers.list(search_opts=opts)
            if not instances:
                return inst
            # for some instances stuck in build phase, servers.list api
            # will always return the marker instance. Add old marker and
            # new marker comparision to avoid the dead loop
            marker_new = instances[-1].id
            if marker == marker_new:
                return inst
            marker = marker_new
            instances = filter(lambda x: _match_availability_zone(x, az_list),
                               instances)
            instances = filter(lambda x: _match_ip_address(x, ip_list),
                               instances)
            if not instances:
                continue
            inst.extend(instances)
            if limit and len(inst) >= int(limit):
                return inst