diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/toolkit/intellij/connector/mysql/JdbcUrl.java b/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/toolkit/intellij/connector/mysql/JdbcUrl.java index 51c6b1ceae2..0d15025d07a 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/toolkit/intellij/connector/mysql/JdbcUrl.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/toolkit/intellij/connector/mysql/JdbcUrl.java @@ -33,6 +33,10 @@ public static JdbcUrl mysql(String serverHost, String database) { return new JdbcUrl(String.format("jdbc:mysql://%s:3306/%s?serverTimezone=UTC&useSSL=true&requireSSL=false", serverHost, database)); } + public static JdbcUrl mysql(String serverHost) { + return new JdbcUrl(String.format("jdbc:mysql://%s:3306?serverTimezone=UTC&useSSL=true&requireSSL=false", serverHost)); + } + public int getPort() { if (this.uri.getScheme().toLowerCase().startsWith("mysql")) { return this.uri.getPort() < 1 ? 3306 : this.uri.getPort(); diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/toolkit/intellij/mysql/MySQLPropertyView.java b/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/toolkit/intellij/mysql/MySQLPropertyView.java index 1e2ccec8dbd..cd01e090bf0 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/toolkit/intellij/mysql/MySQLPropertyView.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/toolkit/intellij/mysql/MySQLPropertyView.java @@ -14,6 +14,7 @@ import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; import com.microsoft.azure.toolkit.lib.common.task.AzureTask; import com.microsoft.azure.toolkit.lib.common.task.AzureTaskManager; +import com.microsoft.azure.toolkit.lib.mysql.AzureMySQLService; import com.microsoft.azuretools.ActionConstants; import com.microsoft.azuretools.azurecommons.util.Utils; import com.microsoft.azuretools.core.mvp.model.AzureMvpModel; @@ -148,14 +149,19 @@ private void onSaveButtonClicked(ActionEvent e) { refreshProperty(subscriptionId, property.getServer().resourceGroupName(), property.getServer().name()); boolean allowAccessToAzureServices = connectionSecurity.getAllowAccessFromAzureServicesCheckBox().getModel().isSelected(); if (!originalAllowAccessToAzureServices.equals(allowAccessToAzureServices)) { - MySQLMvpModel.FirewallRuleMvpModel + boolean result = MySQLMvpModel.FirewallRuleMvpModel .updateAllowAccessFromAzureServices(subscriptionId, property.getServer(), allowAccessToAzureServices); - originalAllowAccessToAzureServices = allowAccessToAzureServices; + if (result) { + originalAllowAccessToAzureServices = allowAccessToAzureServices; + } } boolean allowAccessToLocal = connectionSecurity.getAllowAccessFromLocalMachineCheckBox().getModel().isSelected(); if (!originalAllowAccessToLocal.equals(allowAccessToLocal)) { - MySQLMvpModel.FirewallRuleMvpModel.updateAllowAccessToLocalMachine(subscriptionId, property.getServer(), allowAccessToLocal); - originalAllowAccessToLocal = allowAccessToLocal; + boolean result = AzureMySQLService.FirewallRuleService.getInstance() + .updateAllowAccessToLocalMachine(subscriptionId, property.getServer(), allowAccessToLocal); + if (result) { + originalAllowAccessToLocal = allowAccessToLocal; + } } MySQLPropertyView.this.propertyActionPanel.getSaveButton().setText(originalText); Boolean changed = MySQLPropertyView.this.changed(); @@ -270,7 +276,7 @@ public void showProperty(MySQLProperty property) { if (ServerState.READY.equals(server.userVisibleState())) { originalAllowAccessToAzureServices = MySQLMvpModel.FirewallRuleMvpModel.isAllowAccessFromAzureServices(property.getFirewallRules()); connectionSecurity.getAllowAccessFromAzureServicesCheckBox().setSelected(originalAllowAccessToAzureServices); - originalAllowAccessToLocal = MySQLMvpModel.FirewallRuleMvpModel.isAllowAccessFromLocalMachine(property.getFirewallRules()); + originalAllowAccessToLocal = AzureMySQLService.FirewallRuleService.getInstance().isAllowAccessFromLocalMachine(property.getFirewallRules()); connectionSecurity.getAllowAccessFromLocalMachineCheckBox().setSelected(originalAllowAccessToLocal); } else { connectionSecuritySeparator.collapse(); diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/toolkit/lib/mysql/AzureMySQLService.java b/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/toolkit/lib/mysql/AzureMySQLService.java index 4b302ba35df..ea8a3473598 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/toolkit/lib/mysql/AzureMySQLService.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/toolkit/lib/mysql/AzureMySQLService.java @@ -8,17 +8,34 @@ import com.microsoft.azure.management.Azure; import com.microsoft.azure.management.mysql.v2020_01_01.Server; import com.microsoft.azure.management.mysql.v2020_01_01.ServerPropertiesForDefaultCreate; +import com.microsoft.azure.management.mysql.v2020_01_01.implementation.FirewallRuleInner; +import com.microsoft.azure.management.mysql.v2020_01_01.implementation.MySQLManager; import com.microsoft.azure.management.resources.ResourceGroup; import com.microsoft.azure.toolkit.intellij.common.Draft; +import com.microsoft.azure.toolkit.intellij.connector.mysql.JdbcUrl; +import com.microsoft.azure.toolkit.intellij.connector.mysql.MySQLConnectionUtils; import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; import com.microsoft.azuretools.ActionConstants; import com.microsoft.azuretools.authmanage.AuthMethodManager; +import com.microsoft.azuretools.azurecommons.util.GetHashMac; import com.microsoft.azuretools.core.mvp.model.mysql.MySQLMvpModel; import com.microsoft.azuretools.telemetry.TelemetryConstants; import com.microsoft.azuretools.telemetrywrapper.*; +import org.apache.commons.lang3.StringUtils; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.net.URL; +import java.net.HttpURLConnection; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class AzureMySQLService { private static final AzureMySQLService instance = new AzureMySQLService(); @@ -56,7 +73,7 @@ public Server createMySQL(final AzureMySQLConfig config) { // update access from azure services MySQLMvpModel.FirewallRuleMvpModel.updateAllowAccessFromAzureServices(subscriptionId, server, config.isAllowAccessFromAzureServices()); // update access from local machine - MySQLMvpModel.FirewallRuleMvpModel.updateAllowAccessToLocalMachine(subscriptionId, server, config.isAllowAccessFromLocalMachine()); + AzureMySQLService.FirewallRuleService.getInstance().updateAllowAccessToLocalMachine(subscriptionId, server, config.isAllowAccessFromLocalMachine()); return server; } catch (final RuntimeException e) { EventUtil.logError(operation, ErrorType.systemError, e, null, null); @@ -66,4 +83,99 @@ public Server createMySQL(final AzureMySQLConfig config) { } } + public static class FirewallRuleService { + + private static final String NAME_PREFIX_ALLOW_ACCESS_TO_LOCAL = "ClientIPAddress_"; + private static final Pattern IPADDRESS_PATTERN = Pattern.compile("\\d{1,3}.\\d{1,3}.\\d{1,3}.\\d{1,3}"); + + private static final FirewallRuleService instance = new FirewallRuleService(); + + public static FirewallRuleService getInstance() { + return FirewallRuleService.instance; + } + + public boolean isAllowAccessFromLocalMachine(final String subscriptionId, final Server server) { + final List firewallRules = MySQLMvpModel.FirewallRuleMvpModel.listFirewallRules(subscriptionId, server); + return isAllowAccessFromLocalMachine(firewallRules); + } + + public boolean isAllowAccessFromLocalMachine(final List firewallRules) { + final String ruleName = getAccessFromLocalRuleName(); + return firewallRules.stream().filter(e -> StringUtils.equals(e.name(), ruleName)).count() > 0L; + } + + public boolean updateAllowAccessToLocalMachine(final String subscriptionId, final Server server, final boolean enable) { + if (enable) { + return enableAllowAccessFromLocalMachine(subscriptionId, server); + } else { + return disableAllowAccessFromLocalMachine(subscriptionId, server); + } + } + + public boolean enableAllowAccessFromLocalMachine(final String subscriptionId, final Server server) { + if (isAllowAccessFromLocalMachine(subscriptionId, server)) { + return true; + } + final String publicIp = getPublicIp(server); + if (StringUtils.isNotBlank(publicIp)) { + final String ruleName = getAccessFromLocalRuleName(); + final FirewallRuleInner firewallRule = new FirewallRuleInner(); + firewallRule.withStartIpAddress(publicIp); + firewallRule.withEndIpAddress(publicIp); + final MySQLManager mySQLManager = AuthMethodManager.getInstance().getMySQLManager(subscriptionId); + mySQLManager.firewallRules().inner().createOrUpdate(server.resourceGroupName(), server.name(), ruleName, firewallRule); + return true; + } + return false; + } + + private String getPublicIp(final Server server) { + // try to get public IP by ping Azure MySQL server + MySQLConnectionUtils.ConnectResult connectResult = MySQLConnectionUtils.connectWithPing(JdbcUrl.mysql(server.fullyQualifiedDomainName()), + server.administratorLogin() + "@" + server.name(), StringUtils.EMPTY); + if (StringUtils.isNotBlank(connectResult.getMessage())) { + Matcher matcher = IPADDRESS_PATTERN.matcher(connectResult.getMessage()); + if (matcher.find()) { + return matcher.group(); + } + } + // Alternatively, get public IP by ping public URL + String ip = StringUtils.EMPTY; + try { + final URL url = new URL("https://ipecho.net/plain"); + final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StandardCharsets.UTF_8)); + while ((ip = in.readLine()) != null) { + if (StringUtils.isNotBlank(ip)) { + break; + } + } + } catch (IOException e) { + } + return ip; + } + + public boolean disableAllowAccessFromLocalMachine(final String subscriptionId, final Server server) { + if (!isAllowAccessFromLocalMachine(subscriptionId, server)) { + return true; + } + final String ruleName = getAccessFromLocalRuleName(); + final MySQLManager mySQLManager = AuthMethodManager.getInstance().getMySQLManager(subscriptionId); + mySQLManager.firewallRules().inner().delete(server.resourceGroupName(), server.name(), ruleName); + return true; + } + + private String getAccessFromLocalRuleName() { + String hostname = "UNKNOWN_HOST"; + try { + hostname = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + } + final String macAddress = GetHashMac.getMac(); + final String ruleName = NAME_PREFIX_ALLOW_ACCESS_TO_LOCAL + hostname + "_" + macAddress; + return ruleName; + } + + } + } diff --git a/Utils/azuretools-core/src/com/microsoft/azuretools/azurecommons/util/GetHashMac.java b/Utils/azuretools-core/src/com/microsoft/azuretools/azurecommons/util/GetHashMac.java index 7d26627d60f..9bc95092787 100644 --- a/Utils/azuretools-core/src/com/microsoft/azuretools/azurecommons/util/GetHashMac.java +++ b/Utils/azuretools-core/src/com/microsoft/azuretools/azurecommons/util/GetHashMac.java @@ -12,8 +12,10 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; +import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; +import java.net.UnknownHostException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -59,6 +61,21 @@ public static String getHashMac() { return ret; } + public static String getMac() { + try { + InetAddress ip = InetAddress.getLocalHost(); + NetworkInterface network = NetworkInterface.getByInetAddress(ip); + byte[] mac = network.getHardwareAddress(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mac.length; i++) { + sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? "-" : "")); + } + return sb.toString(); + } catch (UnknownHostException | SocketException e) { + return null; + } + } + private static boolean isValidMac(String mac) { if (StringUtils.isEmpty(mac)) { return false; diff --git a/Utils/azuretools-core/src/com/microsoft/azuretools/core/mvp/model/mysql/MySQLMvpModel.java b/Utils/azuretools-core/src/com/microsoft/azuretools/core/mvp/model/mysql/MySQLMvpModel.java index 7b7e732d6e5..b4a8f86912d 100644 --- a/Utils/azuretools-core/src/com/microsoft/azuretools/core/mvp/model/mysql/MySQLMvpModel.java +++ b/Utils/azuretools-core/src/com/microsoft/azuretools/core/mvp/model/mysql/MySQLMvpModel.java @@ -21,16 +21,10 @@ import com.microsoft.azure.management.resources.fluentcore.arm.Region; import com.microsoft.azuretools.authmanage.AuthMethodManager; import com.microsoft.azuretools.core.mvp.model.AzureMvpModel; -import lombok.Lombok; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -42,7 +36,6 @@ public class MySQLMvpModel { private static final String NAME_AVAILABILITY_CHECK_TYPE = "Microsoft.DBforMySQL/servers"; private static final String NAME_ALLOW_ACCESS_TO_AZURE_SERVICES = "AllowAllWindowsAzureIps"; private static final String IP_ALLOW_ACCESS_TO_AZURE_SERVICES = "0.0.0.0"; - private static final String NAME_PREFIX_ALLOW_ACCESS_TO_LOCAL = "ClientIPAddress_"; private static final List MYSQL_SUPPORTED_REGIONS = Arrays.asList( "australiacentral", "australiacentral2", "australiaeast", "australiasoutheast", "brazilsouth", "canadacentral", "canadaeast", "centralindia", "centralus", "eastasia", "eastus2", "eastus", "francecentral", "francesouth", "germanywestcentral", "japaneast", "japanwest", "koreacentral", @@ -207,82 +200,6 @@ public static boolean disableAllowAccessFromAzureServices(final String subscript return true; } - public static boolean isAllowAccessFromLocalMachine(final String subscriptionId, final Server server) { - final List firewallRules = MySQLMvpModel.FirewallRuleMvpModel.listFirewallRules(subscriptionId, server); - return MySQLMvpModel.FirewallRuleMvpModel.isAllowAccessFromLocalMachine(firewallRules); - } - - public static boolean isAllowAccessFromLocalMachine(final List firewallRules) { - final List localFirewallRules = MySQLMvpModel.FirewallRuleMvpModel.getLocalFirewallRules(firewallRules); - return CollectionUtils.isNotEmpty(localFirewallRules); - } - - public static boolean updateAllowAccessToLocalMachine(final String subscriptionId, final Server server, final boolean enable) { - if (enable) { - return MySQLMvpModel.FirewallRuleMvpModel.enableAllowAccessFromLocalMachine(subscriptionId, server); - } else { - return MySQLMvpModel.FirewallRuleMvpModel.disableAllowAccessFromLocalMachine(subscriptionId, server); - } - } - - public static boolean enableAllowAccessFromLocalMachine(final String subscriptionId, final Server server) { - if (MySQLMvpModel.FirewallRuleMvpModel.isAllowAccessFromLocalMachine(subscriptionId, server)) { - return true; - } - try { - final String publicIp = MySQLMvpModel.FirewallRuleMvpModel.getPublicIp(); - final String ruleName = NAME_PREFIX_ALLOW_ACCESS_TO_LOCAL + publicIp.replaceAll("\\.", "-"); - final FirewallRuleInner firewallRule = new FirewallRuleInner(); - firewallRule.withStartIpAddress(publicIp); - firewallRule.withEndIpAddress(publicIp); - final MySQLManager mySQLManager = AuthMethodManager.getInstance().getMySQLManager(subscriptionId); - mySQLManager.firewallRules().inner().createOrUpdate(server.resourceGroupName(), server.name(), ruleName, firewallRule); - } catch (IOException e) { - Lombok.sneakyThrow(e); - } - return true; - } - - public static boolean disableAllowAccessFromLocalMachine(final String subscriptionId, final Server server) { - if (!MySQLMvpModel.FirewallRuleMvpModel.isAllowAccessFromLocalMachine(subscriptionId, server)) { - return true; - } - final MySQLManager mySQLManager = AuthMethodManager.getInstance().getMySQLManager(subscriptionId); - final List firewallRules = MySQLMvpModel.FirewallRuleMvpModel.listFirewallRules(subscriptionId, server); - final List localFirewallRules = MySQLMvpModel.FirewallRuleMvpModel.getLocalFirewallRules(firewallRules); - localFirewallRules.stream().forEach(e -> { - mySQLManager.firewallRules().inner().delete(server.resourceGroupName(), server.name(), e.name()); - }); - return true; - } - - private static List getLocalFirewallRules(final List firewallRules) { - try { - final String publicIp = getPublicIp(); - if (StringUtils.isBlank(publicIp)) { - return new ArrayList<>(); - } - return firewallRules.stream().filter(e -> StringUtils.equals(publicIp, e.startIpAddress()) && StringUtils.equals(publicIp, e.endIpAddress())) - .collect(Collectors.toList()); - } catch (IOException e) { - Lombok.sneakyThrow(e); - } - return new ArrayList<>(); - } - - private static String getPublicIp() throws IOException { - final URL url = new URL("https://ipecho.net/plain"); - final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - String ip; - try (BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StandardCharsets.UTF_8))) { - while ((ip = in.readLine()) != null) { - if (StringUtils.isNotBlank(ip)) { - break; - } - } - } - return ip; - } } private static List listMySQLServersBySubscriptionId(final String subscriptionId) throws IOException {