|
15 | 15 | */ |
16 | 16 | package com.google.cloud.sql.core; |
17 | 17 |
|
18 | | -import com.google.api.client.http.HttpStatusCodes; |
19 | | -import com.google.api.client.http.HttpTransport; |
20 | | -import com.google.api.client.http.LowLevelHttpRequest; |
21 | | -import com.google.api.client.http.LowLevelHttpResponse; |
22 | | -import com.google.api.client.json.Json; |
23 | | -import com.google.api.client.json.JsonFactory; |
24 | | -import com.google.api.client.json.gson.GsonFactory; |
25 | | -import com.google.api.client.testing.http.MockHttpTransport; |
26 | | -import com.google.api.client.testing.http.MockLowLevelHttpRequest; |
27 | | -import com.google.api.client.testing.http.MockLowLevelHttpResponse; |
28 | | -import com.google.api.services.sqladmin.model.ConnectSettings; |
29 | | -import com.google.api.services.sqladmin.model.GenerateEphemeralCertResponse; |
30 | | -import com.google.api.services.sqladmin.model.IpMapping; |
31 | | -import com.google.api.services.sqladmin.model.SslCert; |
32 | | -import com.google.common.collect.ImmutableList; |
33 | | -import com.google.common.util.concurrent.Futures; |
34 | | -import com.google.common.util.concurrent.ListenableFuture; |
35 | | -import com.google.common.util.concurrent.ListeningScheduledExecutorService; |
36 | | -import java.io.IOException; |
37 | | -import java.math.BigInteger; |
38 | | -import java.security.GeneralSecurityException; |
39 | | -import java.security.KeyFactory; |
40 | | -import java.security.KeyPair; |
41 | | -import java.security.PrivateKey; |
42 | | -import java.security.PublicKey; |
43 | | -import java.security.cert.Certificate; |
44 | | -import java.security.spec.PKCS8EncodedKeySpec; |
45 | | -import java.security.spec.X509EncodedKeySpec; |
46 | | -import java.time.Duration; |
47 | | -import java.time.ZoneId; |
48 | | -import java.time.ZonedDateTime; |
49 | | -import java.util.Base64; |
50 | | -import java.util.Date; |
51 | | -import java.util.concurrent.ExecutionException; |
52 | | -import javax.security.auth.x500.X500Principal; |
53 | | -import org.bouncycastle.cert.X509CertificateHolder; |
54 | | -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; |
55 | | -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; |
56 | | -import org.bouncycastle.operator.ContentSigner; |
57 | | -import org.bouncycastle.operator.OperatorCreationException; |
58 | | -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; |
59 | | -import org.junit.Before; |
| 18 | +import static com.google.common.truth.Truth.assertThat; |
| 19 | + |
| 20 | +import com.google.cloud.sql.AuthType; |
| 21 | +import com.google.cloud.sql.IpType; |
| 22 | +import com.google.cloud.sql.RefreshStrategy; |
| 23 | +import io.netty.handler.ssl.SslContextBuilder; |
| 24 | +import io.r2dbc.spi.Connection; |
| 25 | +import io.r2dbc.spi.ConnectionFactory; |
| 26 | +import io.r2dbc.spi.ConnectionFactoryMetadata; |
| 27 | +import io.r2dbc.spi.ConnectionFactoryOptions; |
| 28 | +import java.util.Collections; |
| 29 | +import java.util.function.Function; |
| 30 | +import org.junit.Test; |
| 31 | +import org.reactivestreams.Publisher; |
60 | 32 |
|
61 | 33 | public class GcpConnectionFactoryProviderTest { |
| 34 | + private final ConnectionFactory unixFactory = new StubConnectionFactory(null); |
62 | 35 |
|
63 | | - static final String PUBLIC_IP = "127.0.0.1"; |
64 | | - static final String PRIVATE_IP = "10.0.0.1"; |
65 | | - final CredentialFactoryProvider stubCredentialFactoryProvider = |
66 | | - new CredentialFactoryProvider(new StubCredentialFactory()); |
67 | | - ListeningScheduledExecutorService defaultExecutor; |
68 | | - ListenableFuture<KeyPair> clientKeyPair; |
69 | | - InternalConnectorRegistry internalConnectorRegistryStub; |
| 36 | + @Test |
| 37 | + public void testCreateWithInstanceName() { |
70 | 38 |
|
71 | | - String fakeInstanceName = "myProject:myRegion:myInstance"; |
| 39 | + ConnectionFactoryOptions.Builder options = ConnectionFactoryOptions.builder(); |
| 40 | + options.option(ConnectionFactoryOptions.PROTOCOL, "cloudsql"); |
| 41 | + options.option(ConnectionFactoryOptions.HOST, "project:region:instance"); |
72 | 42 |
|
73 | | - private static byte[] decodeBase64StripWhitespace(String b64) { |
74 | | - return Base64.getDecoder().decode(b64.replaceAll("\\s", "")); |
| 43 | + StubConnectionFactory factory = configureConnection(options.build()); |
| 44 | + ConnectionConfig config = factory.config; |
| 45 | + assertThat(config.getCloudSqlInstance()).isEqualTo("project:region:instance"); |
75 | 46 | } |
76 | 47 |
|
77 | | - private String createEphemeralCert(Duration shiftIntoPast) |
78 | | - throws GeneralSecurityException, ExecutionException, OperatorCreationException { |
79 | | - Duration validFor = Duration.ofHours(1); |
80 | | - ZonedDateTime notBefore = ZonedDateTime.now(ZoneId.of("UTC")).minus(shiftIntoPast); |
81 | | - ZonedDateTime notAfter = notBefore.plus(validFor); |
| 48 | + @Test |
| 49 | + public void testCreateWithUnixSocket() { |
82 | 50 |
|
83 | | - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); |
84 | | - PKCS8EncodedKeySpec keySpec = |
85 | | - new PKCS8EncodedKeySpec(decodeBase64StripWhitespace(R2dbcTestKeys.SIGNING_CA_PRIVATE_KEY)); |
86 | | - PrivateKey signingKey = keyFactory.generatePrivate(keySpec); |
| 51 | + ConnectionFactoryOptions.Builder options = ConnectionFactoryOptions.builder(); |
| 52 | + options.option(ConnectionFactoryOptions.PROTOCOL, "cloudsql"); |
| 53 | + options.option(ConnectionFactoryOptions.HOST, "project:region:instance"); |
| 54 | + options.option(GcpConnectionFactoryProvider.UNIX_SOCKET, "/socket/path"); |
87 | 55 |
|
88 | | - final ContentSigner signer = new JcaContentSignerBuilder("SHA1withRSA").build(signingKey); |
| 56 | + StubConnectionFactory factory = configureConnection(options.build()); |
| 57 | + assertThat(factory).isSameInstanceAs(unixFactory); |
| 58 | + } |
89 | 59 |
|
90 | | - X500Principal issuer = |
91 | | - new X500Principal("C = US, O = Google\\, Inc, CN=Google Cloud SQL Signing CA foo:baz"); |
92 | | - X500Principal subject = new X500Principal("C = US, O = Google\\, Inc, CN=temporary-subject"); |
| 60 | + @Test |
| 61 | + public void testCreateWithAllOptions() { |
| 62 | + |
| 63 | + ConnectionFactoryOptions.Builder options = ConnectionFactoryOptions.builder(); |
| 64 | + options.option(ConnectionFactoryOptions.PROTOCOL, "cloudsql"); |
| 65 | + options.option(ConnectionFactoryOptions.HOST, "project:region:instance"); |
| 66 | + options.option(GcpConnectionFactoryProvider.NAMED_CONNECTOR, "resolver-test"); |
| 67 | + options.option(GcpConnectionFactoryProvider.IP_TYPES, "private"); |
| 68 | + options.option(GcpConnectionFactoryProvider.ENABLE_IAM_AUTH, true); |
| 69 | + options. option( GcpConnectionFactoryProvider. DELEGATES, "[email protected]"); |
| 70 | + options. option( GcpConnectionFactoryProvider. TARGET_PRINCIPAL, "[email protected]"); |
| 71 | + options.option(GcpConnectionFactoryProvider.ADMIN_QUOTA_PROJECT, "quota-project"); |
| 72 | + options.option(GcpConnectionFactoryProvider.GOOGLE_CREDENTIALS_PATH, "/credentials/path"); |
| 73 | + options.option(GcpConnectionFactoryProvider.UNIVERSE_DOMAIN, "domain.google.com"); |
| 74 | + options.option(GcpConnectionFactoryProvider.REFRESH_STRATEGY, "lazy"); |
| 75 | + |
| 76 | + StubConnectionFactory factory = configureConnection(options.build()); |
| 77 | + ConnectionConfig config = factory.config; |
| 78 | + |
| 79 | + assertThat(config.getCloudSqlInstance()).isEqualTo("project:region:instance"); |
| 80 | + assertThat(config.getNamedConnector()).isEqualTo("resolver-test"); |
| 81 | + assertThat(config.getIpTypes()).isEqualTo(Collections.singletonList(IpType.PRIVATE)); |
| 82 | + assertThat(config.getAuthType()).isEqualTo(AuthType.IAM); |
| 83 | + assertThat(config.getConnectorConfig().getDelegates()) |
| 84 | + . isEqualTo( Collections. singletonList( "[email protected]")); |
| 85 | + assertThat( config. getConnectorConfig(). getTargetPrincipal()). isEqualTo( "[email protected]"); |
| 86 | + assertThat(config.getConnectorConfig().getAdminQuotaProject()).isEqualTo("quota-project"); |
| 87 | + assertThat(config.getConnectorConfig().getGoogleCredentialsPath()) |
| 88 | + .isEqualTo("/credentials/path"); |
| 89 | + assertThat(config.getConnectorConfig().getUniverseDomain()).isEqualTo("domain.google.com"); |
| 90 | + assertThat(config.getConnectorConfig().getRefreshStrategy()).isEqualTo(RefreshStrategy.LAZY); |
| 91 | + } |
93 | 92 |
|
94 | | - JcaX509v3CertificateBuilder certificateBuilder = |
95 | | - new JcaX509v3CertificateBuilder( |
96 | | - issuer, |
97 | | - BigInteger.ONE, |
98 | | - Date.from(notBefore.toInstant()), |
99 | | - Date.from(notAfter.toInstant()), |
100 | | - subject, |
101 | | - Futures.getDone(clientKeyPair).getPublic()); |
| 93 | + @Test |
| 94 | + public void testCreateWithAdminApiOptions() { |
102 | 95 |
|
103 | | - X509CertificateHolder certificateHolder = certificateBuilder.build(signer); |
| 96 | + ConnectionFactoryOptions.Builder options = ConnectionFactoryOptions.builder(); |
| 97 | + options.option(ConnectionFactoryOptions.PROTOCOL, "cloudsql"); |
| 98 | + options.option(ConnectionFactoryOptions.HOST, "project:region:instance"); |
| 99 | + options.option(GcpConnectionFactoryProvider.ADMIN_ROOT_URL, "http://example.com/root"); |
| 100 | + options.option(GcpConnectionFactoryProvider.ADMIN_SERVICE_PATH, "/service"); |
104 | 101 |
|
105 | | - Certificate cert = new JcaX509CertificateConverter().getCertificate(certificateHolder); |
| 102 | + StubConnectionFactory factory = configureConnection(options.build()); |
| 103 | + ConnectionConfig config = factory.config; |
106 | 104 |
|
107 | | - return "-----BEGIN CERTIFICATE-----\n" |
108 | | - + Base64.getEncoder().encodeToString(cert.getEncoded()).replaceAll("(.{64})", "$1\n") |
109 | | - + "\n" |
110 | | - + "-----END CERTIFICATE-----\n"; |
| 105 | + assertThat(config.getCloudSqlInstance()).isEqualTo("project:region:instance"); |
| 106 | + assertThat(config.getConnectorConfig().getAdminRootUrl()).isEqualTo("http://example.com/root"); |
| 107 | + assertThat(config.getConnectorConfig().getAdminServicePath()).isEqualTo("/service"); |
111 | 108 | } |
112 | 109 |
|
113 | | - private HttpTransport fakeSuccessHttpTransport(Duration certDuration) { |
114 | | - final JsonFactory jsonFactory = new GsonFactory(); |
115 | | - return new MockHttpTransport() { |
116 | | - @Override |
117 | | - public LowLevelHttpRequest buildRequest(String method, String url) { |
118 | | - return new MockLowLevelHttpRequest() { |
119 | | - @Override |
120 | | - public LowLevelHttpResponse execute() throws IOException { |
121 | | - MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); |
122 | | - if (method.equals("GET") && url.contains("connectSettings")) { |
123 | | - ConnectSettings settings = |
124 | | - new ConnectSettings() |
125 | | - .setBackendType("SECOND_GEN") |
126 | | - .setIpAddresses( |
127 | | - ImmutableList.of( |
128 | | - new IpMapping().setIpAddress(PUBLIC_IP).setType("PRIMARY"), |
129 | | - new IpMapping().setIpAddress(PRIVATE_IP).setType("PRIVATE"))) |
130 | | - .setServerCaCert(new SslCert().setCert(R2dbcTestKeys.SERVER_CA_CERT)) |
131 | | - .setDatabaseVersion("POSTGRES14") |
132 | | - .setRegion("myRegion"); |
133 | | - settings.setFactory(jsonFactory); |
134 | | - response |
135 | | - .setContent(settings.toPrettyString()) |
136 | | - .setContentType(Json.MEDIA_TYPE) |
137 | | - .setStatusCode(HttpStatusCodes.STATUS_CODE_OK); |
138 | | - } else if (method.equals("POST") && url.contains("generateEphemeralCert")) { |
139 | | - GenerateEphemeralCertResponse certResponse = new GenerateEphemeralCertResponse(); |
140 | | - try { |
141 | | - certResponse.setEphemeralCert( |
142 | | - new SslCert().setCert(createEphemeralCert(certDuration))); |
143 | | - certResponse.setFactory(jsonFactory); |
144 | | - } catch (GeneralSecurityException |
145 | | - | ExecutionException |
146 | | - | OperatorCreationException e) { |
147 | | - throw new RuntimeException(e); |
148 | | - } |
149 | | - response |
150 | | - .setContent(certResponse.toPrettyString()) |
151 | | - .setContentType(Json.MEDIA_TYPE) |
152 | | - .setStatusCode(HttpStatusCodes.STATUS_CODE_OK); |
153 | | - } |
154 | | - return response; |
155 | | - } |
156 | | - }; |
157 | | - } |
158 | | - }; |
159 | | - } |
| 110 | + private static class StubConnectionFactory implements ConnectionFactory { |
| 111 | + |
| 112 | + final ConnectionConfig config; |
160 | 113 |
|
161 | | - @Before |
162 | | - public void setup() throws GeneralSecurityException { |
| 114 | + private StubConnectionFactory(ConnectionConfig config) { |
| 115 | + this.config = config; |
| 116 | + } |
163 | 117 |
|
164 | | - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); |
165 | | - PKCS8EncodedKeySpec privateKeySpec = |
166 | | - new PKCS8EncodedKeySpec(decodeBase64StripWhitespace(R2dbcTestKeys.CLIENT_PRIVATE_KEY)); |
167 | | - PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); |
| 118 | + @Override |
| 119 | + public Publisher<? extends Connection> create() { |
| 120 | + return null; |
| 121 | + } |
| 122 | + |
| 123 | + @Override |
| 124 | + public ConnectionFactoryMetadata getMetadata() { |
| 125 | + return null; |
| 126 | + } |
| 127 | + } |
168 | 128 |
|
169 | | - X509EncodedKeySpec publicKeySpec = |
170 | | - new X509EncodedKeySpec(decodeBase64StripWhitespace(R2dbcTestKeys.CLIENT_PUBLIC_KEY)); |
171 | | - PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); |
| 129 | + private StubConnectionFactory configureConnection(ConnectionFactoryOptions options) { |
| 130 | + GcpConnectionFactoryProvider p = |
| 131 | + new GcpConnectionFactoryProvider() { |
| 132 | + @Override |
| 133 | + ConnectionFactory tcpSocketConnectionFactory( |
| 134 | + ConnectionConfig config, |
| 135 | + ConnectionFactoryOptions.Builder optionBuilder, |
| 136 | + Function<SslContextBuilder, SslContextBuilder> customizer) { |
| 137 | + return new StubConnectionFactory(config); |
| 138 | + } |
172 | 139 |
|
173 | | - clientKeyPair = Futures.immediateFuture(new KeyPair(publicKey, privateKey)); |
| 140 | + @Override |
| 141 | + ConnectionFactory unixSocketConnectionFactory( |
| 142 | + ConnectionFactoryOptions.Builder optionBuilder, String socket) { |
| 143 | + return unixFactory; |
| 144 | + } |
174 | 145 |
|
175 | | - defaultExecutor = InternalConnectorRegistry.getDefaultExecutor(); |
| 146 | + @Override |
| 147 | + ConnectionFactoryOptions.Builder createBuilder( |
| 148 | + ConnectionFactoryOptions connectionFactoryOptions) { |
| 149 | + return ConnectionFactoryOptions.builder(); |
| 150 | + } |
176 | 151 |
|
177 | | - ConnectionInfoRepositoryFactory repo = |
178 | | - new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0))); |
| 152 | + @Override |
| 153 | + boolean supportedProtocol(String protocol) { |
| 154 | + return "cloudsql".equals(protocol); |
| 155 | + } |
| 156 | + }; |
179 | 157 |
|
180 | | - internalConnectorRegistryStub = |
181 | | - new InternalConnectorRegistry( |
182 | | - clientKeyPair, |
183 | | - repo, |
184 | | - stubCredentialFactoryProvider, |
185 | | - 3307, |
186 | | - InternalConnectorRegistry.DEFAULT_CONNECT_TIMEOUT_MS, |
187 | | - defaultExecutor); |
| 158 | + return (StubConnectionFactory) p.create(options); |
188 | 159 | } |
189 | 160 | } |
0 commit comments