From f73943110c64a522b17b642433dacd533516292c Mon Sep 17 00:00:00 2001 From: ulvii Date: Wed, 18 Mar 2020 21:10:23 -0700 Subject: [PATCH] Provide an option to configure trusted Azure Key Vault endpoints (#1285) --- ...ColumnEncryptionAzureKeyVaultProvider.java | 78 ++++++++++++++++--- .../sqlserver/testframework/AbstractTest.java | 21 ++++- .../sqlserver/testframework/Constants.java | 2 + 3 files changed, 89 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java index 2e21f69fb..c673728fe 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java @@ -7,6 +7,8 @@ import static java.nio.charset.StandardCharsets.UTF_16LE; +import java.io.FileInputStream; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteBuffer; @@ -14,8 +16,12 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; +import java.util.Properties; import java.util.concurrent.ExecutorService; +import java.util.logging.Level; import com.microsoft.azure.AzureResponseBuilder; import com.microsoft.azure.keyvault.KeyVaultClient; @@ -43,6 +49,8 @@ */ public class SQLServerColumnEncryptionAzureKeyVaultProvider extends SQLServerColumnEncryptionKeyStoreProvider { + private final static java.util.logging.Logger akvLogger = java.util.logging.Logger + .getLogger("com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionAzureKeyVaultProvider"); /** * Column Encryption Key Store Provider string */ @@ -50,15 +58,12 @@ public class SQLServerColumnEncryptionAzureKeyVaultProvider extends SQLServerCol private final String baseUrl = "https://{vaultBaseUrl}"; - /** - * List of Azure trusted endpoints https://docs.microsoft.com/en-us/azure/key-vault/key-vault-secure-your-key-vault - */ - private final String azureTrustedEndpoints[] = {"vault.azure.net", // default - "vault.azure.cn", // Azure China - "vault.usgovcloudapi.net", // US Government - "vault.microsoftazure.de" // Azure Germany - }; - + private static final String MSSQL_JDBC_PROPERTIES = "mssql-jdbc.properties"; + private static final String AKV_TRUSTED_ENDPOINTS_KEYWORD = "AKVTrustedEndpoints"; + private static final List akvTrustedEndpoints; + static { + akvTrustedEndpoints = getTrustedEndpoints(); + } private final String rsaEncryptionAlgorithmWithOAEPForAKV = "RSA-OAEP"; /** @@ -455,7 +460,7 @@ private void ValidateNonEmptyAKVPath(String masterKeyPath) throws SQLServerExcep if (null != host) { host = host.toLowerCase(Locale.ENGLISH); } - for (final String endpoint : azureTrustedEndpoints) { + for (final String endpoint : akvTrustedEndpoints) { if (null != host && host.endsWith(endpoint)) { return; } @@ -628,4 +633,57 @@ public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allow throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); } } + + private static List getTrustedEndpoints() { + Properties mssqlJdbcProperties = getMssqlJdbcProperties(); + List trustedEndpoints = new ArrayList(); + boolean append = true; + if (null != mssqlJdbcProperties) { + String endpoints = mssqlJdbcProperties.getProperty(AKV_TRUSTED_ENDPOINTS_KEYWORD); + if (null != endpoints && !endpoints.isBlank()) { + endpoints = endpoints.trim(); + // Append if the list starts with a semicolon. + if (';' != endpoints.charAt(0)) { + append = false; + } else { + endpoints = endpoints.substring(1); + } + String[] entries = endpoints.split(";"); + for (String entry : entries) { + if (null != entry && !entry.isBlank()) { + trustedEndpoints.add(entry.trim()); + } + } + } + } + /* + * List of Azure trusted endpoints + * https://docs.microsoft.com/en-us/azure/key-vault/key-vault-secure-your-key-vault + */ + if (append) { + trustedEndpoints.add("vault.azure.net"); + trustedEndpoints.add("vault.azure.cn"); + trustedEndpoints.add("vault.usgovcloudapi.net"); + trustedEndpoints.add("vault.microsoftazure.de"); + } + return trustedEndpoints; + } + + /** + * Attempt to read MSSQL_JDBC_PROPERTIES. + * + * @return corresponding Properties object or null if failed to read the file. + */ + private static Properties getMssqlJdbcProperties() { + Properties props = null; + try (FileInputStream in = new FileInputStream(MSSQL_JDBC_PROPERTIES)) { + props = new Properties(); + props.load(in); + } catch (IOException e) { + if (akvLogger.isLoggable(Level.FINER)) { + akvLogger.finer("Unable to load the mssql-jdbc.properties file: " + e); + } + } + return (null != props && !props.isEmpty()) ? props : null; + } } diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java index 246785546..159f382f8 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java @@ -6,9 +6,12 @@ package com.microsoft.sqlserver.testframework; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintStream; import java.sql.Connection; import java.sql.ResultSet; @@ -145,8 +148,22 @@ public static void setup() throws Exception { } if (null == akvProvider) { - akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, applicationKey); - map.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); + File file = null; + try { + file = new File(Constants.MSSQL_JDBC_PROPERTIES); + try (OutputStream os = new FileOutputStream(file);) { + Properties props = new Properties(); + // Append to the list of hardcoded endpoints. + props.setProperty(Constants.AKV_TRUSTED_ENDPOINTS_KEYWORD, ";vault.azure.net"); + props.store(os, ""); + } + akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, applicationKey); + map.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); + } finally { + if (null != file) { + file.delete(); + } + } } if (!isKspRegistered) { diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java index e6f16958e..b7d452bc4 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java @@ -43,6 +43,8 @@ private Constants() {} public static final String JDBC_PREFIX = "jdbc:sqlserver://"; public static final String DEFAULT_DRIVER_LOG = "Driver.log"; public static final String MSSQL_JDBC_PACKAGE = "com.microsoft.sqlserver.jdbc"; + public static final String MSSQL_JDBC_PROPERTIES = "mssql-jdbc.properties"; + public static final String AKV_TRUSTED_ENDPOINTS_KEYWORD = "AKVTrustedEndpoints"; public static final String DEFAULT_WRAP_IDENTIFIER = "\'"; public static final String CREATE_TABLE = "CREATE TABLE";