Esempio n. 1
0
def main():
    action_desc = """Action description:

stdout - dump grants to stdout
check - check grants on the instance and ouput errors to stdout
import - import grants on to the instance and then check
nuke_then_import - DANGEROUS! Delete all grants, reimport and then recheck

Note: Grants do *NOT* run through replication. If you need to make a change,
you will need to run it against the entire replica set.
"""

    parser = argparse.ArgumentParser(description='MySQL grant manager',
                                     epilog=action_desc,
                                     formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('-i',
                        '--instance',
                        help='Instance to act on if other than localhost:3306',
                        default=''.join((host_utils.HOSTNAME, ':3306')))
    parser.add_argument('-a',
                        '--action',
                        choices=('stdout',
                                 'check',
                                 'import',
                                 'nuke_then_import'),
                        required=True)

    args = parser.parse_args()
    instance = host_utils.HostAddr(args.instance)

    if args.action == 'stdout':
        grants = mysql_lib.get_all_mysql_grants()
        for grant in grants.iteritems():
            print format_grant(grant[1])
    else:
        problems = manage_mysql_grants(instance, args.action)
        if len(problems) > 0:
            print "Current problems:"
            print '\n'.join(problems)
            sys.exit(1)
Esempio n. 2
0
def main():
    action_desc = """Action description:

stdout - dump grants to stdout
check - check grants on the instance and ouput errors to stdout
import - import grants on to the instance and then check
nuke_then_import - DANGEROUS! Delete all grants, reimport and then recheck

Note: Grants do *NOT* run through replication. If you need to make a change,
you will need to run it against the entire replica set.
"""

    parser = argparse.ArgumentParser(
        description='MySQL grant manager',
        epilog=action_desc,
        formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('-i',
                        '--instance',
                        help='Instance to act on if other than localhost:3306',
                        default=''.join((host_utils.HOSTNAME, ':3306')))
    parser.add_argument('-a',
                        '--action',
                        choices=('stdout', 'check', 'import',
                                 'nuke_then_import'),
                        required=True)

    args = parser.parse_args()
    instance = host_utils.HostAddr(args.instance)

    if args.action == 'stdout':
        grants = mysql_lib.get_all_mysql_grants()
        for grant in grants.iteritems():
            print format_grant(grant[1])
    else:
        problems = manage_mysql_grants(instance, args.action)
        if len(problems) > 0:
            print "Current problems:"
            print '\n'.join(problems)
            sys.exit(1)
Esempio n. 3
0
def manage_mysql_grants(instance, action):
    """ Nuke/import/check MySQL grants

    Args:
    instance - an object identify which host to act upon
    action - available options:
            check - check grants on the instance and ouput errors to stdout
            import - import grants on to the instance and then check
            nuke_then_import - delete all grants, reimport and then recheck

    Returns:
    problems -  a list of problems

    """
    try:
        conn = mysql_lib.connect_mysql(instance)
    except MySQLdb.OperationalError as detail:
        (error_code, msg) = detail.args
        if (error_code != mysql_lib.MYSQL_ERROR_HOST_ACCESS_DENIED and
                error_code != mysql_lib.MYSQL_ERROR_ACCESS_DENIED):
            raise

        if instance.hostname == host_utils.HOSTNAME.split('.')[0]:
            print ('Could not connect to instance, but it looks like '
                   'instance is on localhost. Going to try defaults for '
                   'authentication.')
            conn = mysql_lib.connect_mysql(instance, 'bootstrap')
        else:
            raise

    grants = mysql_lib.get_all_mysql_grants()

    # nuke
    conn.query("SET SQL_LOG_BIN=0")
    if action == 'nuke_then_import':
        conn.query("SET SQL_SAFE_UPDATES = 0")
        conn.query("delete from mysql.user")
        conn.query("delete from mysql.db")
        conn.query("delete from mysql.proxies_priv")
    # import
    if action in ('import', 'nuke_then_import'):
        for grant in grants.iteritems():
            sql = format_grant(grant[1])
            conn.query(sql)
        conn.query('flush privileges')
    # check
    if action in ('check', 'import', 'nuke_then_import'):
        problems = []
        on_server = dict()
        cursor = conn.cursor()

        # PK on (user, host), so this returns all distinct users
        cursor.execute("SELECT user, host FROM mysql.user")
        users = cursor.fetchall()
        for row in users:
            user = "******".format(user=row['user'],
                                              host=row['host'])
            sql = "SHOW GRANTS FOR {user}".format(user=user)
            try:
                cursor.execute(sql)
            except MySQLdb.OperationalError as detail:
                (error_code, msg) = detail.args
                if error_code != mysql_lib.MYSQL_ERROR_NO_DEFINED_GRANT:
                    raise

                problems.append('Grant {user} is not active, probably due to '
                                'skip-name-resolve being on'.format(user=user))
                continue
            returned_grants = cursor.fetchall()

            if len(returned_grants) > 1:
                problems.append('Grant for {user} is too complicated, '
                                'ignoring grant'.format(user=user))
                continue
            unparsed_grant = returned_grants[0][returned_grants[0].keys()[0]]
            on_server[user] = parse_grant(unparsed_grant)

        expected_users = set(grants.keys())
        active_users = set(on_server.keys())

        missing_users = expected_users.difference(active_users)
        for user in missing_users:
            problems.append('Missing user: {user}'.format(user=user))

        unexpected_user = active_users.difference(expected_users)
        for user in unexpected_user:
            problems.append('Unexpected user: {user}'.format(user=user))

        # need hashes from passwords. We could store this in zk, but it just
        # another thing to screw up
        for key in grants.keys():
            password = grants[key]['password']
            sql = "SELECT PASSWORD('{pw}') pw".format(pw=password)
            cursor.execute(sql)
            ret = cursor.fetchone()
            grants[key]['hashed_password'] = ret['pw']
            del grants[key]['password']

        for key in set(grants.keys()).intersection(set(on_server.keys())):
            if grants[key] != on_server[key]:
                diff = difflib.unified_diff(pprint.pformat(on_server[key]).splitlines(),
                                            pprint.pformat(grants[key]).splitlines())
                problems.append('Grant for user "{user}" does not match:'
                                '{problem}'.format(user=key,
                                                   problem='\n'.join(diff)))

        return problems
Esempio n. 4
0
def manage_mysql_grants(instance, action):
    """ Nuke/import/check MySQL grants

    Args:
    instance - an object identify which host to act upon
    action - available options:
            check - check grants on the instance and ouput errors to stdout
            import - import grants on to the instance and then check
            nuke_then_import - delete all grants, reimport and then recheck

    Returns:
    problems -  a list of problems

    """
    try:
        conn = mysql_lib.connect_mysql(instance)
    except MySQLdb.OperationalError as detail:
        (error_code, msg) = detail.args
        if (error_code != mysql_lib.MYSQL_ERROR_HOST_ACCESS_DENIED
                and error_code != mysql_lib.MYSQL_ERROR_ACCESS_DENIED):
            raise

        if instance.hostname == host_utils.HOSTNAME.split('.')[0]:
            print(
                'Could not connect to instance, but it looks like '
                'instance is on localhost. Going to try defaults for '
                'authentication.')
            conn = mysql_lib.connect_mysql(instance, 'bootstrap')
        else:
            raise

    grants = mysql_lib.get_all_mysql_grants()

    # nuke
    conn.query("SET SQL_LOG_BIN=0")
    if action == 'nuke_then_import':
        conn.query("SET SQL_SAFE_UPDATES = 0")
        conn.query("delete from mysql.user")
        conn.query("delete from mysql.db")
        conn.query("delete from mysql.proxies_priv")
    # import
    if action in ('import', 'nuke_then_import'):
        for grant in grants.iteritems():
            sql = format_grant(grant[1])
            conn.query(sql)
        conn.query('flush privileges')
    # check
    if action in ('check', 'import', 'nuke_then_import'):
        problems = []
        on_server = dict()
        cursor = conn.cursor()

        # PK on (user, host), so this returns all distinct users
        cursor.execute("SELECT user, host FROM mysql.user")
        users = cursor.fetchall()
        for row in users:
            user = "******".format(user=row['user'],
                                              host=row['host'])
            sql = "SHOW GRANTS FOR {user}".format(user=user)
            try:
                cursor.execute(sql)
            except MySQLdb.OperationalError as detail:
                (error_code, msg) = detail.args
                if error_code != mysql_lib.MYSQL_ERROR_NO_DEFINED_GRANT:
                    raise

                problems.append('Grant {user} is not active, probably due to '
                                'skip-name-resolve being on'.format(user=user))
                continue
            returned_grants = cursor.fetchall()

            if len(returned_grants) > 1:
                problems.append('Grant for {user} is too complicated, '
                                'ignoring grant'.format(user=user))
                continue
            unparsed_grant = returned_grants[0][returned_grants[0].keys()[0]]
            on_server[user] = parse_grant(unparsed_grant)

        expected_users = set(grants.keys())
        active_users = set(on_server.keys())

        missing_users = expected_users.difference(active_users)
        for user in missing_users:
            problems.append('Missing user: {user}'.format(user=user))

        unexpected_user = active_users.difference(expected_users)
        for user in unexpected_user:
            problems.append('Unexpected user: {user}'.format(user=user))

        # need hashes from passwords. We could store this in zk, but it just
        # another thing to screw up
        for key in grants.keys():
            password = grants[key]['password']
            sql = "SELECT PASSWORD('{pw}') pw".format(pw=password)
            cursor.execute(sql)
            ret = cursor.fetchone()
            grants[key]['hashed_password'] = ret['pw']
            del grants[key]['password']

        for key in set(grants.keys()).intersection(set(on_server.keys())):
            if grants[key] != on_server[key]:
                diff = difflib.unified_diff(
                    pprint.pformat(on_server[key]).splitlines(),
                    pprint.pformat(grants[key]).splitlines())
                problems.append('Grant for user "{user}" does not match:'
                                '{problem}'.format(user=key,
                                                   problem='\n'.join(diff)))

        return problems