I am trying the managed database service on DigitalOcean.com, having created a Postgres instance.
Digital Ocean defaults to requiring remote connections be made with SSL (actually TLS) encryption.
How does one configure a JDBC DataSource
implementation for providing connections to such a database server?
This Question is similar to Produce a DataSource
object for Postgres JDBC, programmatically but adds the specific elements of SSL/TLS encryption and using a managed database remotely.
Advertisement
Answer
Obtaining a DataSource
implementation
Usually your JDBC driver provides an implementation of javax.sql.DataSource
.
For more details, see my Answer to a similar Question.
PGSimpleDataSource
If using the JDBC driver from jdbc.postgresql.org, you can use the org.postgresql.ds.PGSimpleDataSource
class as your DataSource
implementation.
Instantiate an object of type PGSimpleDataSource
, then call setter methods to provide all the info needed to connect to your database server. The web page on the DigitalOcean site lists all these pieces of info for your particular database instance.
Basically this:
PGSimpleDataSource dataSource = new PGSimpleDataSource(); dataSource.setServerName( "your-server-address-goes-here" ); dataSource.setPortNumber( your-port-number-goes-here ); dataSource.setDatabaseName( "defaultdb" ); dataSource.setUser( "doadmin" ); dataSource.setPassword( "your-password-goes-here" );
Plural server names and port numbers
Unfortunately, the singular version of the methods setServerName
and setPortNumber
are deprecated. While annoying, we should probably use the plural version of those methods (setServerNames
& setPortNumbers
) that each take an array. We populate a pair of single-element arrays, String[]
and int[]
, with our server’s address and port number using array literals:
- { “your-server-address-goes-here” }
- { your-port-number-goes-here }
PGSimpleDataSource dataSource = new PGSimpleDataSource(); String[] serverAddresses = { "your-server-address-goes-here" }; dataSource.setServerNames( serverAddresses ); int[] serverPortNumbers = { your-port-number-goes-here }; dataSource.setPortNumbers( serverPortNumbers ); dataSource.setDatabaseName( "defaultdb" ); dataSource.setUser( "doadmin" ); dataSource.setPassword( "your-password-goes-here" );
SSL/TLS encryption
Lastly we need to add info for the SSL/TLS encryption addressed in the Question.
Ironically, do not call setSsl( true )
You would think we would call the setSsl
method and pass true
. But, no. While counter-intuitive, setting that to true will cause your connection attempts to fail. I do not know why that is. But trial-and-error led me to avoid that call.
CA certificate
In order for your client-side Java app to initiate the SSL/TLS connection, the JDBC driver must have access to the CA certificate used by Digital Ocean. On your Digital Ocean admin page, click the Download CA certificate
link to download a tiny text file. That file will be named ca-certificate.crt
.
We need to feed the text of that file to our JDBC driver as a string. Do the following to load the file into a text string.
// Get CA certificate used in TLS connections. Path path = Paths.get( "/Users/basilbourque/Downloads/ca-certificate.crt" ); String cert = null; try { cert = Files.readString( path , StandardCharsets.UTF_8 ); System.out.println( "cert = " + cert ); } catch ( IOException ex ) { throw new IllegalStateException( "Unable to load the TLS certificate needed to make database connections." ); } Objects.requireNonNull( cert ); if ( cert.isEmpty() ) {throw new IllegalStateException( "Failed to load TLS cert." ); }
Pass that CA certificate text to your JDBC driver with a call to DataSource::setSslCert
. Note that we are not calling setSslRootCert
. Do not mix up the two similarly-named methods.
dataSource.setSslCert( cert );
Testing the connection
Lastly, we can use the configured DataSource
object to make a connection to the database. That code will look like this:
// Test connection try ( Connection conn = dataSource.getConnection() ; // … ) { System.out.println( "DEBUG - Postgres connection made. " + Instant.now() ); // … } catch ( SQLException e ) { e.printStackTrace(); }
Complete example code
Putting all that together, see something like this.
// Get CA certificate used in TLS connections. Path path = Paths.get( "/Users/basilbourque/Downloads/ca-certificate.crt" ); String cert = null; try { cert = Files.readString( path , StandardCharsets.UTF_8 ); System.out.println( "cert = " + cert ); } catch ( IOException ex ) { throw new IllegalStateException( "Unable to load the TLS certificate needed to make database connections." ); } Objects.requireNonNull( cert ); if ( cert.isEmpty() ) {throw new IllegalStateException( "Failed to load TLS cert." ); } // PGSimpleDataSource configuration PGSimpleDataSource dataSource = new PGSimpleDataSource(); String[] serverAddresses = { "your-server-address-goes-here" }; dataSource.setServerNames( serverAddresses ); int[] serverPortNumbers = { your-port-number-goes-here }; dataSource.setPortNumbers( serverPortNumbers ); dataSource.setSslCert( cert ); dataSource.setDatabaseName( "defaultdb" ); dataSource.setUser( "doadmin" ); dataSource.setPassword( "your-password-goes-here" ); // Test connection try ( Connection conn = dataSource.getConnection() ; // … ) { System.out.println( "DEBUG - Postgres connection made. " + Instant.now() ); // … } catch ( SQLException e ) { e.printStackTrace(); }
Trusted source
When creating your Postgres instance on DigitalOcean.com, you will be offered the ability to restrict incoming connection requests. You can specify a single IP address to be expected by the Postgres server, a “trusted source” in Digital Ocean’s lingo. Any hackers attempting to connect to your Postgres server will be ignored as they originate from other IP addresses.
Tip: Click inside the data-entry field for this IP address number to have the web page automatically detect and offer the IP address being currently used by your web browser. If you are running your Java code from the same computer, use that same IP number as your single trusted source.
Otherwise, type in the IP address of the computer running your Java JDBC code.
Other issues
I have not addressed elaborations, such as proper security protocols to use to manage your CA certificate file, or such as externalizing your connection info by getting a DataSource
object from a JNDI server rather than hard-coding. But hopefully the examples above help you get started.