#!/usr/bin/python # -*- coding: utf-8 -*- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. import os import sys import uuid from contextlib import closing from optparse import OptionParser try: import MySQLdb except ImportError: print("MySQLdb cannot be imported, please install python-mysqldb(apt) or mysql-python(yum)") sys.exit(1) dryrun = False def runSql(conn, query): if dryrun: print("Running SQL query: " + query) return with closing(conn.cursor()) as cursor: cursor.execute(query) def migrateApiRolePermissions(apis, conn): # All allow for root admin role Admin(id:1) runSql(conn, "INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 1, '*', 'ALLOW', 0);") # Migrate rules based on commands.properties rule for ResourceAdmin(id:2), DomainAdmin(id:3), User(id:4) octetKey = {2:2, 3:4, 4:8} for role in [2, 3, 4]: sortOrder = 0 for api in sorted(apis.keys()): # Ignore auth commands if api in ['login', 'logout', 'samlSso', 'samlSlo', 'listIdps', 'listAndSwitchSamlAccount', 'getSPMetadata']: continue if (octetKey[role] & int(apis[api])) > 0: runSql(conn, "INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), %d, '%s', 'ALLOW', %d);" % (role, api, sortOrder)) sortOrder += 1 def main(): parser = OptionParser() parser.add_option("-b", "--db", action="store", type="string", dest="db", default="cloud", help="The name of the database, default: cloud") parser.add_option("-u", "--user", action="store", type="string", dest="user", default="cloud", help="User name a MySQL user with privileges on cloud database") parser.add_option("-p", "--password", action="store", type="string", dest="password", default="cloud", help="Password of a MySQL user with privileges on cloud database") parser.add_option("-H", "--host", action="store", type="string", dest="host", default="127.0.0.1", help="Host or IP of the MySQL server") parser.add_option("-P", "--port", action="store", type="int", dest="port", default=3306, help="Host or IP of the MySQL server") parser.add_option("-f", "--properties-file", action="store", type="string", dest="commandsfile", default="/etc/cloudstack/management/commands.properties", help="The commands.properties file") parser.add_option("-d", "--dryrun", action="store_true", dest="dryrun", default=False, help="Dry run and debug operations this tool will perform") (options, args) = parser.parse_args() print("Apache CloudStack Role Permission Migration Tool") print("(c) Apache CloudStack Authors and the ASF, under the Apache License, Version 2.0\n") global dryrun if options.dryrun: dryrun = True conn = MySQLdb.connect( host=options.host, user=options.user, passwd=options.password, port=int(options.port), db=options.db) if not os.path.isfile(options.commandsfile): print("Provided commands.properties cannot be accessed or does not exist, please check check permissions") sys.exit(1) while True: choice = raw_input("Running this migration tool will remove any " + "default-role permissions from cloud.role_permissions. " + "Do you want to continue? [y/N]").lower() if choice == 'y': break else: print("Aborting!") sys.exit(1) # Generate API to permission octet map apiMap = {} with open(options.commandsfile) as f: for line in f.readlines(): if not line or line == '' or line == '\n' or line.startswith('#'): continue name, value = line.split('=') apiMap[name.strip()] = value.strip() # Rename and deprecate old commands.properties file if not dryrun: os.rename(options.commandsfile, options.commandsfile + '.deprecated') print("The commands.properties file has been deprecated and moved at: " + options.commandsfile + '.deprecated') # Truncate any rules in cloud.role_permissions table runSql(conn, "DELETE FROM `cloud`.`role_permissions` WHERE `role_id` in (1,2,3,4);") # Migrate rules from commands.properties to cloud.role_permissions migrateApiRolePermissions(apiMap, conn) print("Static role permissions from commands.properties have been migrated into the db") # Enable dynamic role based API checker runSql(conn, "UPDATE `cloud`.`configuration` SET value='true' where name='dynamic.apichecker.enabled'") conn.commit() conn.close() print("Dynamic role based API checker has been enabled!") if __name__ == '__main__': main()