mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +01:00 
			
		
		
		
	Automate dynamic roles migration for missing props file - In case commands.properties file is missing, enables dynamic roles. - Adds a new -D or --default flag to migrate-dynamicroles.py script to simply update the global setting and use the default role-rule permissions. - Add warning message, ask admins to move to dynamic roles during upgrade Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
		
			
				
	
	
		
			146 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			146 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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 mysql.connector
 | |
| except ImportError:
 | |
|     print("mysql.connector cannot be imported, please install mysql-connector-python")
 | |
|     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
 | |
|     print("Static role permissions from commands.properties have been migrated into the db")
 | |
| 
 | |
| 
 | |
| def enableDynamicApiChecker(conn):
 | |
|     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!")
 | |
| 
 | |
| 
 | |
| 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", "--default", action="store_true", dest="defaultRules", default=False,
 | |
|                         help="")
 | |
|     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 = mysql.connector.connect(
 | |
|             host=options.host,
 | |
|             user=options.user,
 | |
|             passwd=options.password,
 | |
|             port=int(options.port),
 | |
|             db=options.db)
 | |
| 
 | |
|     if options.defaultRules:
 | |
|         print("Applying the default role permissions, ignoring any provided properties files(s).")
 | |
|         enableDynamicApiChecker(conn)
 | |
|         sys.exit(0)
 | |
| 
 | |
|     if not os.path.isfile(options.commandsfile):
 | |
|         print("Provided commands.properties cannot be accessed or does not exist.")
 | |
|         print("Please check passed options, or run only with --default option to use the default role 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 == '\r\n' or line.startswith('#'):
 | |
|                 continue
 | |
|             name, value = line.split('=')
 | |
|             apiMap[name.strip()] = value.strip().split(';')[-1]
 | |
| 
 | |
|     # 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)
 | |
| 
 | |
|     enableDynamicApiChecker(conn)
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |