From bdf0003580c61e04b96e6395ea4b310e30c38aaf Mon Sep 17 00:00:00 2001 From: subhamkrai Date: Wed, 17 Nov 2021 10:09:47 +0530 Subject: [PATCH] security: add dry run mode for external cluster script Adding dry run mode for external cluster script. This will add cli argument `--dry-run`. By default `dry-run` option will be `False` which means it will only print something like below. ``` sh-4.4$ python3 create-external-cluster-resources.py --dry-run=True The script is checking conflicting options The script is running command `ceph fs ls` to fetch cephFS data pool details The script is getting fsid The script is running command `ceph quorum_status` to fetch ceph mon external data The script is running command `ceph auth get-or-create ...` to fetch/create to key for rook external user The script is running command `ceph mgr services` to fetch mgr services The script is runnning command `ceph auth get-or-create ...`to fetch/create Ceph CSI keyring for RBD provisioner The script is running command `ceph auth get-or-create client.csi-cephfs-node ...` to fetch/create Ceph CSI keyring for RBD provisioner The script is running command `ceph status` to get mon endpoint and mon endpoint port None The script is checks if connected then shutdown ``` Signed-off-by: subhamkrai --- .../create-external-cluster-resources.py | 73 +++++++++++++++---- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/deploy/examples/create-external-cluster-resources.py b/deploy/examples/create-external-cluster-resources.py index 465ccab2218bd..4a1a3738bb94d 100644 --- a/deploy/examples/create-external-cluster-resources.py +++ b/deploy/examples/create-external-cluster-resources.py @@ -193,6 +193,8 @@ def gen_arg_parser(cls, args_to_parse=None): help="Ceph Manager prometheus exporter endpoints (comma separated list of entries of active and standby mgrs)") output_group.add_argument("--monitoring-endpoint-port", default="", required=False, help="Ceph Manager prometheus exporter port") + output_group.add_argument("--dry-run", default=False, required=False, + help="Dry run the python script") upgrade_group = argP.add_argument_group('upgrade') upgrade_group.add_argument("--upgrade", action='store_true', default=False, @@ -205,14 +207,22 @@ def gen_arg_parser(cls, args_to_parse=None): args_to_parse = sys.argv[1:] return argP.parse_args(args_to_parse) + def dry_run(self, msg): + if self._arg_parser.dry_run: + print("The script is " + msg) + def validate_rgw_endpoint_tls_cert(self): + if self._arg_parser.dry_run: + return self.dry_run("validating rados gateWay endpoint TLS certificate") if self._arg_parser.rgw_tls_cert_path: with open(self._arg_parser.rgw_tls_cert_path, encoding='utf8') as f: contents = f.read() return contents.rstrip() def _check_conflicting_options(self): - if not self._arg_parser.upgrade and not self._arg_parser.rbd_data_pool_name: + if self._arg_parser.dry_run: + return self.dry_run("checking conflicting options") + if not self._arg_parser.upgrade and not self._arg_parser.rbd_data_pool_name and not self._arg_parser.dry_run: raise ExecutionFailureException( "Either '--upgrade' or '--rbd-data-pool-name ' should be specified") if self._arg_parser.upgrade and self._arg_parser.rbd_data_pool_name: @@ -224,6 +234,8 @@ def _check_conflicting_options(self): "Please provide an existing user-name through '--run-as-user' (or '-u') flag while upgrading") def _invalid_endpoint(self, endpoint_str): + if self._arg_parser.dry_run: + return self.dry_run("if checking if endpoint is vaild or not") try: ipv4, port = endpoint_str.split(':') except ValueError: @@ -250,6 +262,8 @@ def _invalid_endpoint(self, endpoint_str): return False def endpoint_dial(self, endpoint_str, timeout=3, cert=None): + if self._arg_parser.dry_run: + return self.dry_run("trying to connect endpoints") # if the 'cluster' instance is a dummy one, # don't try to reach out to the endpoint if isinstance(self.cluster, DummyRados): @@ -303,10 +317,14 @@ def __init__(self, arg_list=None): self.cluster.connect() def shutdown(self): + if self._arg_parser.dry_run: + return self.dry_run("checks if connected then shutdown") if self.cluster.state == "connected": self.cluster.shutdown() def get_fsid(self): + if self._arg_parser.dry_run: + return self.dry_run("getting fsid") return str(self.cluster.get_fsid()) def _common_cmd_json_gen(self, cmd_json): @@ -324,6 +342,8 @@ def _common_cmd_json_gen(self, cmd_json): return ret_val, json_out, err_msg def get_ceph_external_mon_data(self): + if self._arg_parser.dry_run: + return self.dry_run("running command `ceph quorum_status` to fetch ceph mon external data") cmd_json = {"prefix": "quorum_status", "format": "json"} ret_val, json_out, err_msg = self._common_cmd_json_gen(cmd_json) # if there is an unsuccessful attempt, @@ -375,6 +395,8 @@ def _convert_hostname_to_ip(self, host_name): return ip def get_active_and_standby_mgrs(self): + if self._arg_parser.dry_run: + return "", self.dry_run("running command `ceph status` to get mon endpoint and mon endpoint port") monitoring_endpoint_port = self._arg_parser.monitoring_endpoint_port monitoring_endpoint_ip_list = self._arg_parser.monitoring_endpoint standby_mgrs = [] @@ -446,6 +468,8 @@ def get_active_and_standby_mgrs(self): return all_mgr_ips_str, monitoring_endpoint_port def create_cephCSIKeyring_cephFSProvisioner(self): + if self._arg_parser.dry_run: + return self.dry_run("running command `ceph auth get-or-create client.csi-cephfs-provisioner` to fetch/create Ceph CSI keyring for cephFS provisioner") ''' command: ceph auth get-or-create client.csi-cephfs-provisioner mon 'allow r' mgr 'allow rw' osd 'allow rw tag cephfs metadata=*' ''' @@ -458,7 +482,7 @@ def create_cephCSIKeyring_cephFSProvisioner(self): if self._arg_parser.restricted_auth_permission: if metadata_pool == "": raise ExecutionFailureException( - "'cephfs_metadata_pool_name' not found, please set the '--cephfs-metadata-pool-name' flag") + "'cephfs_metadata_pool_name' not found, please set the '--cephfs-metadata-pool-name' flag") cmd_json = {"prefix": "auth get-or-create", "entity": entity, "caps": ["mon", "allow r", "mgr", "allow rw", @@ -479,6 +503,8 @@ def create_cephCSIKeyring_cephFSProvisioner(self): return str(json_out[0]['key']) def create_cephCSIKeyring_cephFSNode(self): + if self._arg_parser.dry_run: + return self.dry_run("running command `ceph auth get-or-create client.csi-cephfs-node ...` to fetch/create Ceph CSI keyring for cephFS node") data_pool = self._arg_parser.cephfs_data_pool_name cluster_name = self._arg_parser.cluster_name entity = "client.csi-cephfs-node" @@ -492,9 +518,10 @@ def create_cephCSIKeyring_cephFSNode(self): cmd_json = {"prefix": "auth get-or-create", "entity": entity, "caps": ["mon", "allow r", - "mgr", "allow rw", - "osd", "allow rw tag cephfs data={}".format(data_pool), - "mds", "allow rw"], + "mgr", "allow rw", + "osd", "allow rw tag cephfs data={}".format( + data_pool), + "mds", "allow rw"], "format": "json"} else: cmd_json = {"prefix": "auth get-or-create", @@ -513,12 +540,14 @@ def create_cephCSIKeyring_cephFSNode(self): return str(json_out[0]['key']) def create_cephCSIKeyring_RBDProvisioner(self): + if self._arg_parser.dry_run: + return self.dry_run("running command `ceph auth get-or-create client.csi-cephfs-node ...` to fetch/create Ceph CSI keyring for RBD provisioner") rbd_pool_name = self._arg_parser.rbd_data_pool_name cluster_name = self._arg_parser.cluster_name entity = "client.csi-rbd-provisioner" if cluster_name: entity = "client.csi-rbd-provisioner-{}".format(cluster_name) - cmd_json={} + cmd_json = {} if self._arg_parser.restricted_auth_permission: if rbd_pool_name == "": raise ExecutionFailureException( @@ -545,6 +574,8 @@ def create_cephCSIKeyring_RBDProvisioner(self): return str(json_out[0]['key']) def get_cephfs_data_pool_details(self): + if self._arg_parser.dry_run: + return self.dry_run("running command `ceph fs ls` to fetch cephFS data pool details") cmd_json = {"prefix": "fs ls", "format": "json"} ret_val, json_out, err_msg = self._common_cmd_json_gen(cmd_json) # if there is an unsuccessful attempt, report an error @@ -597,8 +628,10 @@ def get_cephfs_data_pool_details(self): return if matching_json_out: - self._arg_parser.cephfs_filesystem_name = str(matching_json_out['name']) - self._arg_parser.cephfs_metadata_pool_name = str(matching_json_out['metadata_pool']) + self._arg_parser.cephfs_filesystem_name = str( + matching_json_out['name']) + self._arg_parser.cephfs_metadata_pool_name = str( + matching_json_out['metadata_pool']) if type(matching_json_out['data_pools']) == list: # if the user has already provided data-pool-name, @@ -630,12 +663,14 @@ def get_cephfs_data_pool_details(self): self._arg_parser.cephfs_data_pool_name)) def create_cephCSIKeyring_RBDNode(self): + if self._arg_parser.dry_run: + return self.dry_run("runnning command `ceph auth get-or-create ...`to fetch/create Ceph CSI keyring for RBD provisioner") rbd_pool_name = self._arg_parser.rbd_data_pool_name cluster_name = self._arg_parser.cluster_name entity = "client.csi-rbd-node" if cluster_name: entity = "client.csi-rbd-node-{}".format(cluster_name) - cmd_json={} + cmd_json = {} if self._arg_parser.restricted_auth_permission: if rbd_pool_name == "": raise ExecutionFailureException( @@ -660,6 +695,8 @@ def create_cephCSIKeyring_RBDNode(self): return str(json_out[0]['key']) def create_checkerKey(self): + if self._arg_parser.dry_run: + return self.dry_run("running command `ceph auth get-or-create ...` to fetch/create to key for rook external user") cmd_json = {"prefix": "auth get-or-create", "entity": self.run_as_user, "caps": ["mon", self.MIN_USER_CAP_PERMISSIONS['mon'], @@ -675,6 +712,8 @@ def create_checkerKey(self): return str(json_out[0]['key']) def get_ceph_dashboard_link(self): + if self._arg_parser.dry_run: + return self.dry_run("running command `ceph mgr services` to fetch mgr services") cmd_json = {"prefix": "mgr services", "format": "json"} ret_val, json_out, _ = self._common_cmd_json_gen(cmd_json) # if there is an unsuccessful attempt, @@ -685,6 +724,8 @@ def get_ceph_dashboard_link(self): return json_out['dashboard'] def create_rgw_admin_ops_user(self): + if self._arg_parser.dry_run: + return self.dry_run("running command `radosgw-admin user create ...` to fetch/create rgw admin ops user for access and secret key") cmd = ['radosgw-admin', 'user', 'create', '--uid', self.EXTERNAL_RGW_ADMIN_OPS_USER_NAME, '--display-name', 'Rook RGW Admin Ops user', '--caps', 'buckets=*;users=*;usage=read;metadata=read;zone=read'] try: @@ -729,7 +770,7 @@ def _gen_output_map(self): pools_to_validate.extend(rgw_pool_to_validate) for pool in pools_to_validate: - if not self.cluster.pool_exists(pool): + if not self.cluster.pool_exists(pool) and not self._arg_parser.dry_run: raise ExecutionFailureException( "The provided pool, '{}', does not exist".format(pool)) self._excluded_keys.add('CLUSTER_NAME') @@ -751,7 +792,8 @@ def _gen_output_map(self): self.out_map['CSI_CEPHFS_PROVISIONER_SECRET'] = '' # create CephFS node and provisioner keyring only when MDS exists if self.out_map['CEPHFS_FS_NAME'] and self.out_map['CEPHFS_POOL_NAME']: - self.out_map['CSI_CEPHFS_NODE_SECRET'] = self.create_cephCSIKeyring_cephFSNode() + self.out_map['CSI_CEPHFS_NODE_SECRET'] = self.create_cephCSIKeyring_cephFSNode( + ) self.out_map['CSI_CEPHFS_PROVISIONER_SECRET'] = self.create_cephCSIKeyring_cephFSProvisioner() self.out_map['RGW_ENDPOINT'] = self._arg_parser.rgw_endpoint self.out_map['RGW_TLS_CERT'] = '' @@ -775,6 +817,9 @@ def gen_shell_out(self): return shOut def gen_json_out(self): + if self._arg_parser.dry_run: + return self._gen_output_map() + self._gen_output_map() json_out = [ { @@ -1035,14 +1080,16 @@ def test_method_main_output(self): def test_method_create_cephCSIKeyring_cephFSProvisioner(self): csiKeyring = self.rjObj.create_cephCSIKeyring_cephFSProvisioner() - print("cephCSIKeyring without restricting it to a metadata pool. {}".format(csiKeyring)) + print("cephCSIKeyring without restricting it to a metadata pool. {}".format( + csiKeyring)) self.rjObj._arg_parser.restricted_auth_permission = True self.rjObj._arg_parser.cephfs_metadata_pool_name = "myfs-metadata" csiKeyring = self.rjObj.create_cephCSIKeyring_cephFSProvisioner() print("cephCSIKeyring for a specific metadata pool. {}".format(csiKeyring)) self.rjObj._arg_parser.cluster_name = "openshift-storage" csiKeyring = self.rjObj.create_cephCSIKeyring_cephFSProvisioner() - print("cephCSIKeyring for a specific metadata pool and cluster. {}".format(csiKeyring)) + print("cephCSIKeyring for a specific metadata pool and cluster. {}".format( + csiKeyring)) def test_non_zero_return_and_error(self): self.rjObj.cluster.return_val = 1