azure-web-app-serviceazure-sql-databaseazure-keyvaulttomcat9

How do I access the Key Vault using CATALINA_OPTS in Azure Web App Configuration


My Azure Web App (running on Tomcat9) successfully connects to the Azure SQL database by having the following in META-INF/context.xml

<Resource name="jdbc/myDB" type="javax.sql.DataSource" auth="Container"
            factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
            driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver" initialSize="30"
            maxActive="100" validationQuery="SELECT 1"
          validationQueryTimeout="1000"
          testOnBorrow="true"
        url="jdbc:sqlserver://myserver.database.windows.net:1433;database=myDatabase;encrypt=true;trustServerCertificate=true;loginTimeout=10;user=myuser@myserver;password=${dbPassword}"
            />

And having the following CATALINA_OPTS application setting in the WebApps Configuration

-Ddbpassword=<my password>

I want to upgrade my security, so I added a secret in the Key Vault called DB-PASSWORD and changed the CATALINA_OPTS application setting in the WebApps Configuration as follows

-Ddbpassword=@Microsoft.KeyVault(SecretUri=https://thekeyvaulttest.vault.azure.net/secrets/DB-PASSWORD/)

I used this guide from Microsoft.

However the server failed to start, saying /usr/local/tomcat/bin/catalina.sh: eval: line 1: syntax error: unexpected "("

The WebApp has permission to read secrets from the Key Vault. This does not seem to be an authentication issue such as in this question

The Java code to read context.xml is as follows:

public final class DBConnection {

    private static DataSource ds = null;
    private static DBConnection instance = null;
    
    private DBConnection() throws NamingException {
        InitialContext ic = new InitialContext();
        ds = (DataSource) ic.lookup("jdbc/mydb");
    }

    public static synchronized DBConnection getInstance() {
        if (instance == null) {
            try {
                instance = new DBConnection();
            } 
            } catch (Exception e2) {
            }
        }
        return instance;
    }

    public Connection getConnection() {
        Connection con = null;
        try {
            con = ds.getConnection();
        } catch (SQLException e) {
            System.out.println(e.toString());
        }

        return con;
    }

    public void returnConnection(Connection con) {
        try {
            con.close();
        } catch (SQLException e) {
            System.out.println(e.toString());
        } finally {
            try {
                con.close();
            } catch (SQLException e) {
                System.out.println(e.toString());
            }
        }
        
        
        return;
    }
    

}

Solution

  • One workaround that works is to use BasicDataSource instead of DataSource and load the parameters in Java. This bypasses the need for defining the connection in context.xml completely.

     String keyVaultUrl = "https://thekeyvaulttest.vault.azure.net/";
     SecretClient secretClient = new SecretClientBuilder()
                 .vaultUrl(keyVaultUrl)
                 .credential(new DefaultAzureCredentialBuilder().build())
                 .buildClient();
     ds=new BasicDataSource();
     String url = "jdbc:sqlserver://myserver.database.windows.net:1433;database=myDatabase;encrypt=true;trustServerCertificate=true;loginTimeout=10;";
     String username = "myuser@myserver";
     String password = secretClient.getSecret("DB-PASSWORD").getValue();
                
     ds.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
     ds.setUrl(url);
     ds.setMaxTotal(100);
     ds.setUsername(username);
     ds.setPassword(password);
     ds.setInitialSize(30);
     ds.setValidationQuery("SELECT 1");
     ds.setTestOnBorrow(true);