diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0bdb8c5e..5c178777 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -246,13 +246,17 @@ jobs: secrets: |- ALLOYDB_INSTANCE_NAME:${{ secrets.GOOGLE_CLOUD_PROJECT }}/ALLOYDB_INSTANCE_URI ALLOYDB_CLUSTER_PASS:${{ secrets.GOOGLE_CLOUD_PROJECT }}/ALLOYDB_CLUSTER_PASS + ALLOYDB_IAM_USER:${{ secrets.GOOGLE_CLOUD_PROJECT }}/ALLOYDB_JAVA_IAM_USER + ALLOYDB_INSTANCE_IP:${{ secrets.GOOGLE_CLOUD_PROJECT }}/ALLOYDB_INSTANCE_IP - name: Run tests env: ALLOYDB_DB: 'postgres' ALLOYDB_USER: 'postgres' + ALLOYDB_IAM_USER: '${{ steps.secrets.outputs.ALLOYDB_IAM_USER }}' ALLOYDB_PASS: '${{ steps.secrets.outputs.ALLOYDB_CLUSTER_PASS }}' ALLOYDB_INSTANCE_NAME: '${{ steps.secrets.outputs.ALLOYDB_INSTANCE_NAME }}' + ALLOYDB_INSTANCE_IP: '${{ steps.secrets.outputs.ALLOYDB_INSTANCE_IP }}' JOB_TYPE: integration run: .kokoro/build.sh shell: bash diff --git a/alloydb-jdbc-connector/pom.xml b/alloydb-jdbc-connector/pom.xml index 6e9a44cb..40ae710d 100644 --- a/alloydb-jdbc-connector/pom.xml +++ b/alloydb-jdbc-connector/pom.xml @@ -160,5 +160,11 @@ test + + com.google.auth + google-auth-library-oauth2-http + test + + diff --git a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/AlloyDBDirectPathDataSource.java b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/AlloyDBDirectPathDataSource.java new file mode 100644 index 00000000..9d77d610 --- /dev/null +++ b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/AlloyDBDirectPathDataSource.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.alloydb; + +import com.google.auth.oauth2.AccessToken; +import com.google.auth.oauth2.GoogleCredentials; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import java.io.IOException; + +/** + * The AlloyDB direct path data source demonstrates how to enable Auto IAM AuthN without using the + * AlloyDB Java Connector. To do that, the code here overrides the HikariDataSource's getPassword + * method to dynamically retrieve an OAuth2 token. This means every new connection made by the + * connection pool will get a fresh OAuth2 token without relying on the AlloyDB Java Connector. For + * details on how this is used, see + * AlloyDbJdbcDirectPathDataSourceFactory + */ +public class AlloyDBDirectPathDataSource extends HikariDataSource { + + public AlloyDBDirectPathDataSource(HikariConfig configuration) { + super(configuration); + } + + @Override + public String getPassword() { + try { + GoogleCredentials credentials = GoogleCredentials.getApplicationDefault(); + AccessToken accessToken = credentials.getAccessToken(); + return accessToken.getTokenValue(); + } catch (IOException e) { + throw new RuntimeException("failed to retrieve OAuth2 access token", e); + } + } +} diff --git a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/AlloyDbJdbcDirectPathDataSourceFactory.java b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/AlloyDbJdbcDirectPathDataSourceFactory.java new file mode 100644 index 00000000..6ffb291f --- /dev/null +++ b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/AlloyDbJdbcDirectPathDataSourceFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.alloydb; + +// [START alloydb_hikaricp_connect_iam_authn_direct] +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +public class AlloyDbJdbcDirectPathDataSourceFactory { + + public static final String ALLOYDB_USER = System.getenv("ALLOYDB_IAM_USER"); + public static final String ALLOYDB_INSTANCE_IP = System.getenv("ALLOYDB_INSTANCE_IP"); + + static HikariDataSource createDataSource() { + HikariConfig config = new HikariConfig(); + + config.setJdbcUrl(String.format("jdbc:postgresql://%s/postgres", ALLOYDB_INSTANCE_IP)); + config.setUsername(ALLOYDB_USER); // e.g., "postgres" + // No need to set password, as that's dynamically configured in the + // AlloyDBDirectPathDataSource with a refreshed OAuth2 token. + + return new AlloyDBDirectPathDataSource(config); + } +} +// [END alloydb_hikaricp_connect_iam_authn_direct] diff --git a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITDirectPathTest.java b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITDirectPathTest.java new file mode 100644 index 00000000..03204744 --- /dev/null +++ b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITDirectPathTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.alloydb; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.BoundType; +import com.google.common.collect.Range; +import com.zaxxer.hikari.HikariDataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ITDirectPathTest { + + private HikariDataSource dataSource; + + @Before + public void setUp() { + this.dataSource = AlloyDbJdbcDirectPathDataSourceFactory.createDataSource(); + } + + @After + public void tearDown() { + if (this.dataSource != null) { + dataSource.close(); + } + } + + @Test + public void testConnect() throws SQLException { + try (Connection connection = dataSource.getConnection()) { + try (PreparedStatement statement = connection.prepareStatement("SELECT NOW()")) { + ResultSet resultSet = statement.executeQuery(); + resultSet.next(); + Timestamp timestamp = resultSet.getTimestamp(1); + Instant databaseInstant = timestamp.toInstant(); + + Instant now = Instant.now(); + assertThat(databaseInstant) + .isIn( + Range.range( + now.minus(1, ChronoUnit.MINUTES), + BoundType.CLOSED, + now.plus(1, ChronoUnit.MINUTES), + BoundType.CLOSED)); + } + } + } +}