diff --git a/web/pgacloud/providers/rds.py b/web/pgacloud/providers/rds.py index cdae8ea7a..86dd4977b 100644 --- a/web/pgacloud/providers/rds.py +++ b/web/pgacloud/providers/rds.py @@ -137,17 +137,18 @@ class RdsProvider(AbsProvider): """ Create a new security group for the instance """ ec2 = self._get_aws_client('ec2', args) ip = args.public_ip if args.public_ip else get_my_ip() + ip = ip.split(',') # Deploy the security group try: name = 'pgacloud_{}_{}_{}'.format(args.name, - ip.replace('.', '-'), + ip[0].replace('.', '-'), get_random_id()) debug(args, 'Creating security group: {}...'.format(name)) output({'Creating': 'Creating security group: {}...'.format(name)}) response = ec2.create_security_group( Description='Inbound access for {} to RDS instance {}'.format( - ip, args.name), + ip[0], args.name), GroupName=name ) except Exception as e: @@ -158,9 +159,17 @@ class RdsProvider(AbsProvider): def _add_ingress_rule(self, args, security_group): """ Add a local -> PostgreSQL ingress rule to a security group """ ec2 = self._get_aws_client('ec2', args) - ip = args.public_ip if args.public_ip else '{}/32'.format(get_my_ip()) + ip = args.public_ip if args.public_ip else\ + '{}/32'.format(get_my_ip()) port = args.db_port or 5432 + IpRanges = [] + ip = ip.split(',') + for i in ip: + IpRanges.append({ + 'CidrIp': i, + 'Description': 'pgcloud client {}'.format(i) + }) try: output({'Adding': 'Adding ingress rule for: {}...'.format(ip)}) debug(args, @@ -172,12 +181,7 @@ class RdsProvider(AbsProvider): 'FromPort': port, 'ToPort': port, 'IpProtocol': 'tcp', - 'IpRanges': [ - { - 'CidrIp': ip, - 'Description': 'pgcloud client {}'.format(ip) - }, - ] + 'IpRanges': IpRanges }, ] ) diff --git a/web/pgadmin/misc/cloud/__init__.py b/web/pgadmin/misc/cloud/__init__.py index 985869e95..b94fd018e 100644 --- a/web/pgadmin/misc/cloud/__init__.py +++ b/web/pgadmin/misc/cloud/__init__.py @@ -25,6 +25,7 @@ from pgadmin.model import db, Server, Process from pgadmin.misc.cloud.utils.rds import RDS, verify_aws_credentials,\ get_aws_db_instances, get_aws_db_versions, clear_aws_session,\ get_aws_regions +from pgadmin.misc.cloud.utils import get_my_ip from config import root @@ -81,7 +82,8 @@ class CloudModule(PgAdminModule): 'cloud.get_aws_db_instances', 'cloud.update_cloud_server', 'cloud.update_cloud_process', - 'cloud.get_aws_regions'] + 'cloud.get_aws_regions', + 'cloud.get_host_ip'] # Create blueprint for CloudModule class @@ -108,6 +110,13 @@ def script(): return res +@blueprint.route('/get_host_ip/', + methods=['GET'], endpoint='get_host_ip') +@login_required +def get_host_ip(): + return make_json_response(data=get_my_ip()) + + @blueprint.route('/verify_credentials/', methods=['POST'], endpoint='verify_credentials') @login_required @@ -285,7 +294,8 @@ def _create_server(data): maintenance_db=data.get('db'), username=data.get('username'), ssl_mode='prefer', - cloud_status=data.get('cloud_status') + cloud_status=data.get('cloud_status'), + connect_timeout=30, ) db.session.add(server) diff --git a/web/pgadmin/misc/cloud/static/js/CloudWizard.jsx b/web/pgadmin/misc/cloud/static/js/CloudWizard.jsx index 2db6ec549..d176ba1aa 100644 --- a/web/pgadmin/misc/cloud/static/js/CloudWizard.jsx +++ b/web/pgadmin/misc/cloud/static/js/CloudWizard.jsx @@ -68,8 +68,22 @@ export default function CloudWizard({ nodeInfo, nodeData }) { const [cloudDBCred, setCloudDBCred] = React.useState({}); const [cloudDBDetails, setCloudDBDetails] = React.useState({}); const [callRDSAPI, setCallRDSAPI] = React.useState({}); + const [hostIP, setHostIP] = React.useState('127.0.0.1/32'); const axiosApi = getApiInstance(); + React.useEffect(() => { + let _url = url_for('cloud.get_host_ip') ; + axiosApi.get(_url) + .then((res) => { + if (res.data.data) { + setHostIP(res.data.data); + } + }) + .catch((error) => { + Alertify.error(gettext(`Error while getting the host ip: ${error.response.data.errormsg}`)); + }); + }, []); + React.useEffect(() => { if (callRDSAPI == 2) { const cloudDBInstanceSchema = new CloudInstanceDetailsSchema({ @@ -111,6 +125,7 @@ export default function CloudWizard({ nodeInfo, nodeData }) { server_groups: ()=>getNodeListById(pgAdmin.Browser.Nodes['server_group'], nodeInfo, nodeData), }, { gid: nodeInfo['server_group']._id, + hostIP: hostIP, }); setCloudInstanceDetailsInstance(cloudDBInstanceSchema); } @@ -151,7 +166,7 @@ export default function CloudWizard({ nodeInfo, nodeData }) { return isError; }; - const validateCloudStep2 = (cloudInstanceDetails) => { + const validateCloudStep2 = (cloudInstanceDetails, host_ip) => { let isError = false; if (isEmptyString(cloudInstanceDetails.aws_name) || isEmptyString(cloudInstanceDetails.aws_db_version) || isEmptyString(cloudInstanceDetails.aws_instance_type) || @@ -162,7 +177,7 @@ export default function CloudWizard({ nodeInfo, nodeData }) { if(cloudInstanceDetails.aws_storage_type == 'io1' && isEmptyString(cloudInstanceDetails.aws_storage_IOPS)) { isError = true; } - if (isEmptyString(cloudInstanceDetails.aws_public_ip)) cloudInstanceDetails.aws_public_ip = '127.0.0.1/32'; + if (isEmptyString(cloudInstanceDetails.aws_public_ip)) cloudInstanceDetails.aws_public_ip = host_ip; return isError; }; @@ -222,7 +237,7 @@ export default function CloudWizard({ nodeInfo, nodeData }) { isError = validateCloudStep1(cloudDBCred); break; case 2: - isError = validateCloudStep2(cloudInstanceDetails); + isError = validateCloudStep2(cloudInstanceDetails, hostIP); break; case 3: isError = validateCloudStep3(cloudDBDetails); diff --git a/web/pgadmin/misc/cloud/static/js/cloud.js b/web/pgadmin/misc/cloud/static/js/cloud.js index 9519fd6b8..4200f1db9 100644 --- a/web/pgadmin/misc/cloud/static/js/cloud.js +++ b/web/pgadmin/misc/cloud/static/js/cloud.js @@ -27,6 +27,7 @@ define('pgadmin.misc.cloud', [ return pgBrowser.Cloud; } + // Create an Object Cloud of pgBrowser class pgBrowser.Cloud = { init: function() { diff --git a/web/pgadmin/misc/cloud/static/js/cloud_db_details_schema.ui.js b/web/pgadmin/misc/cloud/static/js/cloud_db_details_schema.ui.js index 08487d88a..1a6b50973 100644 --- a/web/pgadmin/misc/cloud/static/js/cloud_db_details_schema.ui.js +++ b/web/pgadmin/misc/cloud/static/js/cloud_db_details_schema.ui.js @@ -17,7 +17,7 @@ class CloudInstanceDetailsSchema extends BaseUISchema { super({ oid: undefined, aws_name: '', - aws_public_ip: '127.0.0.1/32', + aws_public_ip: initValues.hostIP, ...initValues }); @@ -39,7 +39,7 @@ class CloudInstanceDetailsSchema extends BaseUISchema { }, { id: 'aws_public_ip', label: gettext('Public IP range'), type: 'text', mode: ['create'], - helpMessage: gettext('IP Address range for permitting the inbound traffic. Ex: 127.0.0.1/32'), + helpMessage: gettext('IP Address range for permitting the inbound traffic. Ex: 127.0.0.1/32, add multiple ip addresses/ranges by comma separated.'), }, { type: 'nested-fieldset', label: gettext('Version & Instance'), mode: ['create'], @@ -127,9 +127,18 @@ class DatabaseSchema extends BaseUISchema { && data.aws_db_password != data.aws_db_confirm_password) { setErrMsg('aws_db_confirm_password', gettext('Passwords do not match.')); return true; - } else { - return false; } + if (!isEmptyString(data.aws_db_confirm_password) && data.aws_db_confirm_password.length < 8) { + setErrMsg('aws_db_confirm_password', gettext('Password must be 8 characters or more.')); + return true; + } + if (data.aws_db_confirm_password.includes('\'') || data.aws_db_confirm_password.includes('"') || + data.aws_db_confirm_password.includes('@') || data.aws_db_confirm_password.includes('/')) { + setErrMsg('aws_db_confirm_password', gettext('Invalid passowrd.')); + return true; + } + + return false; } get idAttribute() { @@ -152,6 +161,7 @@ class DatabaseSchema extends BaseUISchema { }, { id: 'aws_db_password', label: gettext('Password'), type: 'password', mode: ['create'], noEmpty: true, + helpMessage: gettext('At least 8 printable ASCII characters. Can not contain any of the following: / \(slash\), \'\(single quote\), "\(double quote\) and @ \(at sign\).') }, { id: 'aws_db_confirm_password', label: gettext('Confirm password'), type: 'password', @@ -202,6 +212,7 @@ export class InstanceSchema extends BaseUISchema { if (source[0] == 'aws_db_instance_class') { return {reload_instances: false}; } else { + state.instanceData = []; return {reload_instances: true}; } }, diff --git a/web/pgadmin/misc/cloud/utils/__init__.py b/web/pgadmin/misc/cloud/utils/__init__.py new file mode 100644 index 000000000..00ac8fce8 --- /dev/null +++ b/web/pgadmin/misc/cloud/utils/__init__.py @@ -0,0 +1,26 @@ +# ########################################################################## +# # +# # pgAdmin 4 - PostgreSQL Tools +# # +# # Copyright (C) 2013 - 2022, The pgAdmin Development Team +# # This software is released under the PostgreSQL Licence +# # +# ########################################################################## + +import urllib3 + + +def get_my_ip(): + """ Return the public IP of this host """ + http = urllib3.PoolManager() + try: + external_ip = http.request.urlopen( + 'https://ident.me').read().decode('utf8') + except Exception: + try: + external_ip = http.request.urlopen( + 'https://ifconfig.me/ip').read().decode('utf8') + except Exception: + external_ip = '127.0.0.1' + + return external_ip diff --git a/web/pgadmin/misc/cloud/utils/aws_regions.py b/web/pgadmin/misc/cloud/utils/aws_regions.py index 785ae9c21..ea3901831 100644 --- a/web/pgadmin/misc/cloud/utils/aws_regions.py +++ b/web/pgadmin/misc/cloud/utils/aws_regions.py @@ -13,24 +13,28 @@ AWS_REGIONS = { 'us-gov-east-1': 'AWS GovCloud (US-East)', 'us-gov-west-1': 'AWS GovCloud (US-West)', + 'af-south-1': 'Africa (Cape Town)', 'ap-east-1': 'Asia Pacific (Hong Kong)', - 'ap-south-': 'Asia Pacific (Mumbai)', - 'ap-northeast-3': 'Asia Pacific (Osaka-Local)', + 'ap-south-1': 'Asia Pacific (Mumbai)', + 'ap-northeast-3': 'Asia Pacific (Osaka)', 'ap-northeast-2': 'Asia Pacific (Seoul)', 'ap-southeast-1': 'Asia Pacific (Singapore)', 'ap-southeast-2': 'Asia Pacific (Sydney)', + 'ap-southeast-3': 'Asia Pacific (Jakarta)', 'ap-northeast-1': 'Asia Pacific (Tokyo)', 'ca-central-1': 'Canada (Central)', 'cn-north-1': 'China (Beijing)', 'cn-northwest-1': 'China (Ningxia)', - 'eu-central-1': 'EU (Frankfurt)', - 'eu-west-1': 'EU (Ireland)', - 'eu-west-2': 'EU (London)', - 'eu-west-3': 'EU (Paris)', - 'eu-north-1': 'EU (Stockholm)', + 'eu-central-1': 'Europe (Frankfurt)', + 'eu-west-1': 'Europe (Ireland)', + 'eu-west-2': 'Europe (London)', + 'eu-west-3': 'Europe (Paris)', + 'eu-north-1': 'Europe (Stockholm)', + 'eu-south-1': 'Europe (Milan)', 'sa-east-1': 'South America (Sao Paulo)', 'us-east-1': 'US East (N. Virginia)', 'us-east-2': 'US East (Ohio)', 'us-west-1': 'US West (N. California)', 'us-west-2': 'US West (Oregon)', + 'me-south-1': 'Middle East (Bahrain)', }