diff --git a/README.md b/README.md index 24dd452..2322d49 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ In particular, it is not necessary to generate grpc classes for the service or t * Parses proto files at runtime to discover services. Supports pretty-printing discovered services. * Supports authentication via oauth. * Accepts request protos through stdin and can output responses to stdout to allow chaining. +* Supports plain text connections as well as TLS. ## Usage @@ -106,6 +107,9 @@ The general philosophy is for the configuration to drive Polyglot's behavior and Polyglot uses statically linked [boringssl](https://boringssl.googlesource.com/boringssl/) libraries under the hood and doesn't require the host machine to have any specific libraries. Whether or not the client uses TLS to talk to the server can be controlled using the `--use_tls` flag or the corresponding configuration entry. +Polyglot can also do client certificate authentication with the `--tls_client_cert_path` and `--tls_client_key_path` flags. If the hostname on the server does not match the endpoint (e.g. connecting +to `localhost`, but the server thinks it's `foo.example.com`), `--tls_client_override_authority=foo.example.com` can be used. + ### Authenticating requests using OAuth Polyglot has built-in support for authentication of requests using OAuth tokens in two ways: diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 7869ea8..6b430b8 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -6,6 +6,10 @@ Quick links: ## Upcoming release +## 1.3.0 +* Upgraded to grpc 1.4.0. +* Added support for client certificates to TLS. + ## 1.2.0 * Upgraded to grpc 1.0.0. diff --git a/src/main/java/me/dinowernli/grpc/polyglot/Main.java b/src/main/java/me/dinowernli/grpc/polyglot/Main.java index 9d7b80a..d34bc41 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/Main.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/Main.java @@ -18,7 +18,7 @@ public class Main { private static final Logger logger = LoggerFactory.getLogger(Main.class); - private static final String VERSION = "1.2.0+dev"; + private static final String VERSION = "1.3.0+dev"; public static void main(String[] args) { // Fix the logging setup. diff --git a/src/main/java/me/dinowernli/grpc/polyglot/command/ServiceCall.java b/src/main/java/me/dinowernli/grpc/polyglot/command/ServiceCall.java index 54313b8..7ef76b0 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/command/ServiceCall.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/command/ServiceCall.java @@ -71,6 +71,8 @@ public static void callEndpoint( dynamicClient = DynamicGrpcClient.create(methodDescriptor, hostAndPort, callConfig); } + logger.info("Reading input from stdin"); + ImmutableList requestMessages = MessageReader.forStdin(methodDescriptor.getInputType()).read(); StreamObserver streamObserver = diff --git a/src/main/java/me/dinowernli/grpc/polyglot/config/CommandLineArgs.java b/src/main/java/me/dinowernli/grpc/polyglot/config/CommandLineArgs.java index 986de84..c0c0921 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/config/CommandLineArgs.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/config/CommandLineArgs.java @@ -2,18 +2,20 @@ import java.io.ByteArrayOutputStream; import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; - /** Provides easy access to the arguments passed on the command line. */ public class CommandLineArgs { @Option(name = "--full_method", metaVar = "") @@ -47,6 +49,30 @@ public class CommandLineArgs { @Option(name = "--tls_ca_cert_path", metaVar = "") private String tlsCaCertPath; + @Option(name = "--tls_client_cert_path", metaVar = "") + private String tlsClientCertPath; + + @Option(name = "--tls_client_key_path", metaVar = "") + private String tlsClientKeyPath; + + @Option(name = "--tls_client_override_authority", metaVar = "") + private String tlsClientOverrideAuthority; + + @Option(name = "--oauth_refresh_token_endpoint_url", metaVar = "") + private String oauthRefreshTokenEndpointUrl; + + @Option(name = "--oauth_client_id", metaVar = "") + private String oauthClientId; + + @Option(name = "--oauth_client_secret", metaVar = "") + private String oauthClientSecret; + + @Option(name = "--oauth_refresh_token_path", metaVar = "") + private String oauthRefreshTokenPath; + + @Option(name = "--oauth_access_token_path", metaVar = "") + private String oauthAccessTokenPath; + @Option(name = "--help") private Boolean help; @@ -65,23 +91,23 @@ public class CommandLineArgs { // TODO: Move to a "list_services"-specific flag container @Option( - name = "--service_filter", - metaVar = "service_name", - usage="Filters service names containing this string e.g. --service_filter TestService") + name = "--service_filter", + metaVar = "service_name", + usage="Filters service names containing this string e.g. --service_filter TestService") private String serviceFilterArg; // TODO: Move to a "list_services"-specific flag container @Option( - name = "--method_filter", - metaVar = "method_name", - usage="Filters service methods to those containing this string e.g. --method_name List") + name = "--method_filter", + metaVar = "method_name", + usage="Filters service methods to those containing this string e.g. --method_name List") private String methodFilterArg; //TODO: Move to a "list_services"-specific flag container @Option( - name = "--with_message", - metaVar = "true|false", - usage="If true, then the message specification for the method is rendered") + name = "--with_message", + metaVar = "true|false", + usage="If true, then the message specification for the method is rendered") private String withMessageArg; // ************************************************************************* @@ -153,6 +179,38 @@ public Optional tlsCaCertPath() { return maybePath(tlsCaCertPath); } + public Optional tlsClientCertPath() { + return maybePath(tlsClientCertPath); + } + + public Optional tlsClientKeyPath() { + return maybePath(tlsClientKeyPath); + } + + public Optional tlsClientOverrideAuthority() { + return Optional.ofNullable(tlsClientOverrideAuthority); + } + + public Optional oauthRefreshTokenEndpointUrl() { + return maybeUrl(oauthRefreshTokenEndpointUrl); + } + + public Optional oauthClientId() { + return Optional.ofNullable(oauthClientId); + } + + public Optional oauthClientSecret() { + return Optional.ofNullable(oauthClientSecret); + } + + public Optional oauthRefreshTokenPath() { + return maybePath(oauthRefreshTokenPath); + } + + public Optional oauthAccessTokenPath() { + return maybePath(oauthAccessTokenPath); + } + /** * First stage of a migration towards a "command"-based instantiation of polyglot. * Supported commands: @@ -214,4 +272,17 @@ private static Optional maybePath(String rawPath) { Preconditions.checkArgument(Files.exists(path), "File " + rawPath + " does not exist"); return Optional.of(Paths.get(rawPath)); } + + private static Optional maybeUrl(String rawUrl) { + if (rawUrl == null) { + return Optional.empty(); + } + try { + URL url = new URL(rawUrl); + return Optional.of(url); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("URL " + rawUrl + " is invalid", e); + } + + } } diff --git a/src/main/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoader.java b/src/main/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoader.java index 8dd9f16..ed0d286 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoader.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoader.java @@ -93,9 +93,9 @@ private Configuration getDefaultConfigurationInternal() { private Configuration getNamedConfigurationInternal(String name) { Preconditions.checkState(!isEmptyConfig(), "Cannot load named config with a config set"); return configSet.get().getConfigurationsList().stream() - .filter(config -> config.getName().equals(name)) - .findAny() - .orElseThrow(() -> new IllegalArgumentException("Could not find named config: " + name)); + .filter(config -> config.getName().equals(name)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("Could not find named config: " + name)); } /** Returns the {@link Configuration} with overrides, if any, applied to it. */ @@ -111,7 +111,7 @@ private Configuration applyOverrides(Configuration configuration) { if (overrides.get().outputFilePath().isPresent()) { resultBuilder.getOutputConfigBuilder().setDestination(Destination.FILE); resultBuilder.getOutputConfigBuilder().setFilePath( - overrides.get().outputFilePath().get().toString()); + overrides.get().outputFilePath().get().toString()); } if (!overrides.get().additionalProtocIncludes().isEmpty()) { List additionalIncludes = new ArrayList<>(); @@ -122,14 +122,49 @@ private Configuration applyOverrides(Configuration configuration) { } if (overrides.get().protoDiscoveryRoot().isPresent()) { resultBuilder.getProtoConfigBuilder().setProtoDiscoveryRoot( - overrides.get().protoDiscoveryRoot().get().toString()); + overrides.get().protoDiscoveryRoot().get().toString()); } if (overrides.get().getRpcDeadlineMs().isPresent()) { resultBuilder.getCallConfigBuilder().setDeadlineMs(overrides.get().getRpcDeadlineMs().get()); } if (overrides.get().tlsCaCertPath().isPresent()) { resultBuilder.getCallConfigBuilder().setTlsCaCertPath( - overrides.get().tlsCaCertPath().get().toString()); + overrides.get().tlsCaCertPath().get().toString()); + } + if (overrides.get().tlsClientCertPath().isPresent()) { + resultBuilder.getCallConfigBuilder().setTlsClientCertPath( + overrides.get().tlsClientCertPath().get().toString()); + } + if (overrides.get().tlsClientKeyPath().isPresent()) { + resultBuilder.getCallConfigBuilder().setTlsClientKeyPath( + overrides.get().tlsClientKeyPath().get().toString()); + } + if (overrides.get().tlsClientOverrideAuthority().isPresent()) { + resultBuilder.getCallConfigBuilder().setTlsClientOverrideAuthority( + overrides.get().tlsClientOverrideAuthority().get()); + } + if (overrides.get().oauthRefreshTokenEndpointUrl().isPresent()) { + resultBuilder.getCallConfigBuilder().getOauthConfigBuilder().getRefreshTokenCredentialsBuilder() + .setTokenEndpointUrl(overrides.get().oauthRefreshTokenEndpointUrl().get().toString()); + } + if (overrides.get().oauthClientId().isPresent()) { + resultBuilder.getCallConfigBuilder().getOauthConfigBuilder().getRefreshTokenCredentialsBuilder() + .getClientBuilder().setId(overrides.get().oauthClientId().get()); + } + if (overrides.get().oauthClientSecret().isPresent()) { + resultBuilder.getCallConfigBuilder().getOauthConfigBuilder().getRefreshTokenCredentialsBuilder() + .getClientBuilder().setSecret(overrides.get().oauthClientSecret().get()); + } + if (overrides.get().oauthRefreshTokenPath().isPresent()) { + resultBuilder.getCallConfigBuilder().getOauthConfigBuilder().getRefreshTokenCredentialsBuilder() + .setRefreshTokenPath(overrides.get().oauthRefreshTokenPath().get().toString()); + } + // Note the ordering of setting these fields is important. Oauth configuration has a oneof field, corresponding + // to access or refresh tokens. We want access tokens to take precedence, setting this field last will ensure this + // occurs. See https://developers.google.com/protocol-buffers/docs/proto#oneof + if (overrides.get().oauthAccessTokenPath().isPresent()) { + resultBuilder.getCallConfigBuilder().getOauthConfigBuilder().getAccessTokenCredentialsBuilder() + .setAccessTokenPath(overrides.get().oauthAccessTokenPath().get().toString()); } return resultBuilder.build(); } diff --git a/src/main/java/me/dinowernli/grpc/polyglot/grpc/DynamicGrpcClient.java b/src/main/java/me/dinowernli/grpc/polyglot/grpc/DynamicGrpcClient.java index 6a0c0dd..01a0a4f 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/grpc/DynamicGrpcClient.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/grpc/DynamicGrpcClient.java @@ -7,8 +7,6 @@ import java.util.concurrent.Callable; import java.util.concurrent.Executors; -import javax.net.ssl.SSLException; - import com.google.auth.Credentials; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; @@ -21,6 +19,10 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.protobuf.Descriptors.MethodDescriptor; import com.google.protobuf.DynamicMessage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ClientCall; @@ -34,9 +36,8 @@ import io.grpc.stub.StreamObserver; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; +import javax.net.ssl.SSLException; import me.dinowernli.grpc.polyglot.protobuf.DynamicMessageMarshaller; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import polyglot.ConfigProto.CallConfiguration; /** A grpc client which operates on dynamic messages. */ @@ -232,10 +233,16 @@ private static Channel createChannel(HostAndPort endpoint, CallConfiguration cal if (!callConfiguration.getUseTls()) { return createPlaintextChannel(endpoint); } - return NettyChannelBuilder.forAddress(endpoint.getHostText(), endpoint.getPort()) - .sslContext(createSslContext(callConfiguration)) - .negotiationType(NegotiationType.TLS) - .build(); + NettyChannelBuilder nettyChannelBuilder = + NettyChannelBuilder.forAddress(endpoint.getHostText(), endpoint.getPort()) + .sslContext(createSslContext(callConfiguration)) + .negotiationType(NegotiationType.TLS); + + if (!callConfiguration.getTlsClientOverrideAuthority().isEmpty()) { + nettyChannelBuilder.overrideAuthority(callConfiguration.getTlsClientOverrideAuthority()); + } + + return nettyChannelBuilder.build(); } private static SslContext createSslContext(CallConfiguration callConfiguration) { @@ -243,6 +250,11 @@ private static SslContext createSslContext(CallConfiguration callConfiguration) if (!callConfiguration.getTlsCaCertPath().isEmpty()) { resultBuilder.trustManager(loadFile(callConfiguration.getTlsCaCertPath())); } + if (!callConfiguration.getTlsClientCertPath().isEmpty()) { + resultBuilder.keyManager( + loadFile(callConfiguration.getTlsClientCertPath()), + loadFile(callConfiguration.getTlsClientKeyPath())); + } try { return resultBuilder.build(); } catch (SSLException e) { diff --git a/src/main/java/me/dinowernli/grpc/polyglot/oauth2/RefreshTokenCredentials.java b/src/main/java/me/dinowernli/grpc/polyglot/oauth2/RefreshTokenCredentials.java index b27b4e9..7140504 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/oauth2/RefreshTokenCredentials.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/oauth2/RefreshTokenCredentials.java @@ -71,10 +71,10 @@ public AccessToken refreshAccessToken() throws IOException { logger.info("Refresh successful, got access token"); return new AccessToken( refreshResponse.getAccessToken(), - computeExpirtyDate(refreshResponse.getExpiresInSeconds())); + computeExpiryDate(refreshResponse.getExpiresInSeconds())); } - private Date computeExpirtyDate(long expiresInSeconds) { + private Date computeExpiryDate(long expiresInSeconds) { long expiresInSecondsWithMargin = (long) (expiresInSeconds * ACCESS_TOKEN_EXPIRY_MARGIN); return Date.from(clock.instant().plusSeconds(expiresInSecondsWithMargin)); } diff --git a/src/main/java/me/dinowernli/grpc/polyglot/testing/TestServer.java b/src/main/java/me/dinowernli/grpc/polyglot/testing/TestServer.java index 75e74ca..60ad9ff 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/testing/TestServer.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/testing/TestServer.java @@ -5,10 +5,13 @@ import java.util.Random; import com.google.common.base.Throwables; + import io.grpc.Server; import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyServerBuilder; +import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import polyglot.test.TestProto.TestResponse; import polyglot.test.TestServiceGrpc.TestServiceImplBase; @@ -101,13 +104,22 @@ public void blockingShutdown() { /** An {@link SslContext} for use in unit test servers. Loads our testing certificates. */ public static SslContext serverSslContextForTesting() throws IOException { - return GrpcSslContexts - .forServer(TestUtils.loadServerChainCert(), TestUtils.loadServerKey()) - .trustManager(TestUtils.loadRootCaCert()) - .sslProvider(SslProvider.OPENSSL) + return getSslContextBuilder().build(); + } + + /** An {@link SslContext} for use in unit test servers with client certs. Loads our testing certificates. */ + public static SslContext serverSslContextWithClientCertsForTesting() throws IOException { + return getSslContextBuilder() + .clientAuth(ClientAuth.REQUIRE) .build(); } + private static SslContextBuilder getSslContextBuilder() { + return GrpcSslContexts.forServer(TestUtils.loadServerChainCert(), TestUtils.loadServerKey()) + .trustManager(TestUtils.loadRootCaCert()) + .sslProvider(SslProvider.OPENSSL); + } + /** Starts a grpc server on the given port, throws {@link IOException} on failure. */ private static Server tryStartServer( int port, diff --git a/src/main/java/me/dinowernli/grpc/polyglot/testing/TestUtils.java b/src/main/java/me/dinowernli/grpc/polyglot/testing/TestUtils.java index 558e4c5..ab6e381 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/testing/TestUtils.java +++ b/src/main/java/me/dinowernli/grpc/polyglot/testing/TestUtils.java @@ -60,6 +60,16 @@ public static File loadRootCaCert() { return Paths.get(TESTING_CERTS_DIR.toString(), "ca.pem").toFile(); } + /** Returns a file containing a client certificate for use in tests. */ + public static File loadClientCert() { + return Paths.get(TESTING_CERTS_DIR.toString(), "client.pem").toFile(); + } + + /** Returns a file containing a client key for use in tests. */ + public static File loadClientKey() { + return Paths.get(TESTING_CERTS_DIR.toString(), "client.key").toFile(); + } + /** Returns a file containing a certificate chain from our testing root CA to our server. */ public static File loadServerChainCert() { return Paths.get(TESTING_CERTS_DIR.toString(), "server.pem").toFile(); diff --git a/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/ca.pem b/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/ca.pem index 715ca9c..c697b9f 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/ca.pem +++ b/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/ca.pem @@ -1,15 +1,15 @@ -----BEGIN CERTIFICATE----- -MIICZjCCAc+gAwIBAgIJAOsqHrpa5cF9MA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV +MIICZjCCAc+gAwIBAgIJAJqx8JYIArI0MA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNjA1MTUwOTIz -MDZaFw0yNjA1MTMwOTIzMDZaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l +aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNzA4MzEyMzM3 +MDNaFw0yNzA4MjkyMzM3MDNaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV -BAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA9T39HWNC -0r9NjD7wmFF6luJJ+NuWG0tuuZoGpbNldQpsZXlS0J/OwNAk+55p6it2Yr89jxM9 -Ea83oYTnjLuGQ/tJmUmPNau2Z4Q/M41000lD6Hd0Sxw7St2nLlgTOMRyEJEAaBBC -yKtHiq6cvu3UmNzY+jok5hmRjGlWHnNsWisCAwEAAaM2MDQwCQYDVR0TBAIwADAL +BAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzroPZgt6 +jX0qwmuo5Y04J8eATK2Lq/ohFfI+LoaqXJ7nOrHViPLpQfqErtHCrcuOD++BcTJ4 +0iw6d0uwxTT0L73cfKI+1zJKcI6jAJ1a86kpYJVqNY5mIDqVXSP2/Ig6Q7I3qDOZ +RM/sNIypd56bQy8GmQYcL1ng1zqy1kBn4jsCAwEAAaM2MDQwCQYDVR0TBAIwADAL BgNVHQ8EBAMCBeAwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3 -DQEBCwUAA4GBAJg2NDTZZB9Kl9mFgjIsL/M4dz/wspsGhwuglpOSwarFkKvSYkxD -61Ls4rp4qT5vEt0EJjksTsxNVdzR9DD0k+LENuEzM+VlzPaKoKrrZRZeiLYnfY28 -etxVuVVW78jd03rx+FpVOql+lKT1hnWn40IVLjLdT60shHfVt34Z6t98 +DQEBCwUAA4GBAAPiapcOh3JBfC6f7dkUAP9KDpNHK8JA1My4+CxkRyShC0rKf+K6 +3wRLLb6f9qyvs3FkSF5uTcD12Irj88SzlMPiu/civVv4ldY/5w1XKmh3BoWwe+cH +jaqPi0MX/uarPCgbgkt219INsBi/Sc8V8Yp1qjZp+pvJ0A80/XdD56/x -----END CERTIFICATE----- diff --git a/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/client.key b/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/client.key new file mode 100644 index 0000000..efa74b8 --- /dev/null +++ b/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/client.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALRoMQCMeQfDcMgk +rs1BREc+4EcGenojetkw10HJ5erZBM05PgdtDTkBT+QVrddVDmn0HUn6S5jZGZGe +NXPOwDoVBHTYdCDKRrnaBg8v8hB2pgMVUaw6idg4FlUbNlit7n07thhXEBGhhw7x +g7cjd8IsV8s1f84+7eC3H2ySVk8hAgMBAAECgYBEH0/ZoDGXj+JHgSqMkQeiS4jO +6RBYjIs39ixiSEXMX3RjtijJDxG+I9OyEcmaFSEjOy3QIHZpWhlAllgiycBly6zi +QjIrdIwTrQHDapxRotT6ap+76/ev1e6145wgyUetSCwzuXdjmP24ujG9coCIP5EB +Rcb5DO8QKZm1gOsgAQJBAOR/PHZSdTT3RsWBW2q03GA+sHpkjuiDaOek7rWJf3U6 +ptZCHjt28VFJa9qDqLKzdcvltqpT+IyskAOrNnyf7CECQQDKHyO5YnEjgMAAnFM6 +3wsm6u+B1ru7UUKOhAr/n+nfSd775A1qF6wQ9YGoHFUHt3k4TjzRWNilQC0jsfUn +sgMBAkAfOM+PL2c6jItME4flRb9TG13L64+nb8VW2a+QeLBE2XXQkwpEf9UrkUe+ +fP0BJgpziPjzvzOYLUAkcDGqx8NhAkEAnOGWpOnXzyq73L15jJRa35Yy2KCHjlkA +RAYRU2AX8wwvW2wjTVmaYH9uZ8G17gtmt2Fiq0s+vOUXJEGYe3scAQJBALmnIp1R +JsAPzxaMa4pJkciHxyZyfH7roLwVUxT3Af+bWPyjOffR7lx9nzsbYf0UuTbiMfEp +YHvy7BUwpIerndI= +-----END PRIVATE KEY----- diff --git a/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/client.pem b/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/client.pem new file mode 100644 index 0000000..22580e4 --- /dev/null +++ b/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/client.pem @@ -0,0 +1,64 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=localhost + Validity + Not Before: Aug 31 23:37:41 2017 GMT + Not After : Aug 29 23:37:41 2027 GMT + Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=localhost-client + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:b4:68:31:00:8c:79:07:c3:70:c8:24:ae:cd:41: + 44:47:3e:e0:47:06:7a:7a:23:7a:d9:30:d7:41:c9: + e5:ea:d9:04:cd:39:3e:07:6d:0d:39:01:4f:e4:15: + ad:d7:55:0e:69:f4:1d:49:fa:4b:98:d9:19:91:9e: + 35:73:ce:c0:3a:15:04:74:d8:74:20:ca:46:b9:da: + 06:0f:2f:f2:10:76:a6:03:15:51:ac:3a:89:d8:38: + 16:55:1b:36:58:ad:ee:7d:3b:b6:18:57:10:11:a1: + 87:0e:f1:83:b7:23:77:c2:2c:57:cb:35:7f:ce:3e: + ed:e0:b7:1f:6c:92:56:4f:21 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment + X509v3 Subject Key Identifier: + E2:53:8F:A6:30:9A:43:C6:24:84:5A:FB:4D:8F:9D:5B:C8:24:28:F8 + X509v3 Authority Key Identifier: + DirName:/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=localhost + serial:9A:B1:F0:96:08:02:B2:34 + + X509v3 Issuer Alternative Name: + DNS:localhost, IP Address:127.0.0.1 + Signature Algorithm: sha256WithRSAEncryption + 4f:a3:04:df:c8:bc:7e:b1:76:a1:5f:8a:35:61:78:60:4b:a5: + 7a:7f:78:91:59:6f:d9:cb:4c:34:8f:b7:b7:dc:53:46:a3:4e: + 08:e4:6b:33:ab:1b:ec:b9:f3:68:34:7d:c1:10:21:66:fd:41: + ef:56:98:9b:bb:d7:1d:ce:6e:ac:55:23:78:05:99:0f:f3:8a: + df:67:28:9c:4b:2f:b1:6f:b7:eb:16:b3:0b:e0:81:ea:f6:fe: + 1d:a6:e5:66:fe:ab:6a:e3:e8:a5:a1:3d:4a:c0:47:4c:bb:6d: + 4f:68:96:88:fe:93:90:77:c0:12:5a:b9:ea:2a:fa:9c:4d:85: + 46:33 +-----BEGIN CERTIFICATE----- +MIIC+zCCAmSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJBVTET +MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTcwODMxMjMzNzQxWhcNMjcw +ODI5MjMzNzQxWjBgMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh +MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRkwFwYDVQQDDBBsb2Nh +bGhvc3QtY2xpZW50MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0aDEAjHkH +w3DIJK7NQURHPuBHBnp6I3rZMNdByeXq2QTNOT4HbQ05AU/kFa3XVQ5p9B1J+kuY +2RmRnjVzzsA6FQR02HQgyka52gYPL/IQdqYDFVGsOonYOBZVGzZYre59O7YYVxAR +oYcO8YO3I3fCLFfLNX/OPu3gtx9sklZPIQIDAQABo4HLMIHIMAkGA1UdEwQCMAAw +CwYDVR0PBAQDAgXgMB0GA1UdDgQWBBTiU4+mMJpDxiSEWvtNj51byCQo+DBzBgNV +HSMEbDBqoV2kWzBZMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh +MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2Nh +bGhvc3SCCQCasfCWCAKyNDAaBgNVHRIEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJ +KoZIhvcNAQELBQADgYEAT6ME38i8frF2oV+KNWF4YEulen94kVlv2ctMNI+3t9xT +RqNOCORrM6sb7LnzaDR9wRAhZv1B71aYm7vXHc5urFUjeAWZD/OK32conEsvsW+3 +6xazC+CB6vb+HablZv6rauPopaE9SsBHTLttT2iWiP6TkHfAElq56ir6nE2FRjM= +-----END CERTIFICATE----- diff --git a/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/readme.txt b/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/readme.txt index 00e6738..96bc506 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/readme.txt +++ b/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/readme.txt @@ -2,11 +2,13 @@ The required files for the tests are: * ca.pem * server.pem * server.key +* client.pem +* client.key Generating these files requires only: * openssl.cnf -In order to generate the three files above from "openssl.cnf", do the following: +In order to generate the files above from "openssl.cnf", do the following: 1) openssl req -x509 -new -newkey rsa:1024 -nodes -out ca.pem -config openssl.cnf -days 3650 -extensions v3_req @@ -18,7 +20,14 @@ all default, except common name: "localhost" all default, except common name: "localhost" -5) mv privkey.pem ca.key -6) touch index.txt -7) echo "01" > serial -8) openssl ca -in server.csr -out server.pem -keyfile ca.key -cert ca.pem -verbose -config openssl.cnf -days 3650 -updatedb +5) openssl genrsa -out client.key.rsa 1024 +6) openssl pkcs8 -topk8 -in client.key.rsa -out client.key -nocrypt +7) openssl req -new -key client.key -out client.csr + +all default, except common name: "localhost-client" + +8) mv privkey.pem ca.key +9) touch index.txt +10) echo "01" > serial +11) openssl ca -in server.csr -out server.pem -keyfile ca.key -cert ca.pem -verbose -config openssl.cnf -days 3650 -batch +12) openssl ca -in client.csr -out client.pem -keyfile ca.key -cert ca.pem -verbose -config openssl.cnf -days 3650 -batch diff --git a/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/server.key b/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/server.key index eb3df9e..7343c9d 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/server.key +++ b/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/server.key @@ -1,16 +1,16 @@ -----BEGIN PRIVATE KEY----- -MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANi3NkLfK5HWIWDb -1JH0pCZUm5vwrFtUByBC6mpiShDMkD9/SkzmTPYFWVEgxqs9n3On6VrnUMHb0zIw -TsZTNhSCcSq1aWuQLIaXwxilaWKLq0p3BBH/j8IRmWeKiP00G1yXG+0lPAGPL3qy -SVDEpTP8lpnY8Oq+SRoKmLfrk7ElAgMBAAECgYB17DOlbaRapcbh2pyvkwwjI1TN -JMfUpkN+ZYsCulsNCxLOymfWslZcZIq2X3xbP5vdJhgPc+D4q7IfFDQ5LJw2yhpR -mgsv1lro4BVmsKvjvC10BdbWhhXVdlv345ySxwW30cMZcRFlSusRfu16GAkWosqD -qc85VaNbKZpok7hUvQJBAPgJJpdK/olfSIXfxoM4J3JbGKsgWI25y7KAJgOfBD+o -UgZGBDMWODuXpsJNHiNgkKMy2lKLyHEBKvHRmYS9V8cCQQDfrJxcc/85a66f+zwY -Kqql/GVfldjdzubbXcDhXpwAKy0kZQJHQnLMncpYwjU/Gukc3WcJscmH6z5vqjHG -sCezAkBZzH5TQgx7UarhBQ5KzZbCeSaqNfC5hu2vd2PL5dNU9KyVpt24XTAIqzPt -npvttPVIkI2oWwE7oG+a0wagnyhbAkEAoFHaaTIDV6blLWooMrIySnLUPmTrYmCw -e7+BRohHjJ9l5dY4gdcQ7bSTmnbpL2gcekH/XqV969wjjmoQ73bHnQJAO2tr4zrR -fICKQA+i7ARPMwlLZvHgIY1gTrgk5/1CpkZCBf9pVxeMUqNWZQrKhKOWnBSqXd7X -lzYGTbGfiTd+7g== +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMxSJo9P48u4mDxI +m+D+C/ad3ZClg6IUO0VbzMtuxBVwAk1SMFusPZzwyKMJYar7qRwB5+pLuXRK8vE/ +0S6SoBZvQaKP5364y7qwCbLC8gf8OKvexKIgVybRKHZIkdacaE1Cuq0QgVzu0w/T +shmJwIa/erG7f2C/LKy0ClvADek7AgMBAAECgYAT7MLz4M+PE05NOqtw0nVqNFTi +ATIIAT8ScXRUNlYK3SRsU+KBXheYEWcPdx++I9KG96ydDYtlStXMLvQAPa48tTIU +yI9T2/CFez1a/ZaDPXDqhHTPSUhSENU9yBlyA/Zb4zMRuiCGm5rHwf+aoU7P2UHW +zn5O/RtiDSSiOQn8SQJBAO48yGEuwh3kyxBLzw+uq9xIDpkGxV9cowXhNXoftm+7 +iG6r9pc5PVTdLDw/8bdoA9SbXHcpwz4LqlOqvtU7I78CQQDbjgGIPjpKiKqoFHtl +VVofsXy7VXxf5dTxKMkk/5mb5XRfdIn2m5yIKPJC1RwMcySAfst1uCJvSfsGa2o4 +FWmFAkEAn5RxeL90YcfOyaSuF0gecJiHxrNFZEJOJPMc+ifh8WgB1Hg13kgGMCFS +ryz6AauX3UMQJfYAhUAVIKQf3f8WSwJBAMymcYcteeg/u9MwRFUQWhFwv3NfG6/H +69Vezx9NoUFPgEn5tx/HrQC+KhNh0eNI8J1VkxEHshFKRFKXjUr5qoECQQCyOtfU +VAJBWi31jHil8UXryxUVWYCTzuNAM6acRaBJd3sHei+4PzzGqh7I7Us0kNoGNQFK +ymC8EYUIcFtqLSly -----END PRIVATE KEY----- diff --git a/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/server.pem b/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/server.pem index be425c2..68a686c 100644 --- a/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/server.pem +++ b/src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/server.pem @@ -5,22 +5,22 @@ Certificate: Signature Algorithm: sha256WithRSAEncryption Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=localhost Validity - Not Before: May 15 09:30:44 2016 GMT - Not After : May 13 09:30:44 2026 GMT + Not Before: Aug 31 23:37:36 2017 GMT + Not After : Aug 29 23:37:36 2027 GMT Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=localhost Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (1024 bit) Modulus: - 00:d8:b7:36:42:df:2b:91:d6:21:60:db:d4:91:f4: - a4:26:54:9b:9b:f0:ac:5b:54:07:20:42:ea:6a:62: - 4a:10:cc:90:3f:7f:4a:4c:e6:4c:f6:05:59:51:20: - c6:ab:3d:9f:73:a7:e9:5a:e7:50:c1:db:d3:32:30: - 4e:c6:53:36:14:82:71:2a:b5:69:6b:90:2c:86:97: - c3:18:a5:69:62:8b:ab:4a:77:04:11:ff:8f:c2:11: - 99:67:8a:88:fd:34:1b:5c:97:1b:ed:25:3c:01:8f: - 2f:7a:b2:49:50:c4:a5:33:fc:96:99:d8:f0:ea:be: - 49:1a:0a:98:b7:eb:93:b1:25 + 00:cc:52:26:8f:4f:e3:cb:b8:98:3c:48:9b:e0:fe: + 0b:f6:9d:dd:90:a5:83:a2:14:3b:45:5b:cc:cb:6e: + c4:15:70:02:4d:52:30:5b:ac:3d:9c:f0:c8:a3:09: + 61:aa:fb:a9:1c:01:e7:ea:4b:b9:74:4a:f2:f1:3f: + d1:2e:92:a0:16:6f:41:a2:8f:e7:7e:b8:cb:ba:b0: + 09:b2:c2:f2:07:fc:38:ab:de:c4:a2:20:57:26:d1: + 28:76:48:91:d6:9c:68:4d:42:ba:ad:10:81:5c:ee: + d3:0f:d3:b2:19:89:c0:86:bf:7a:b1:bb:7f:60:bf: + 2c:ac:b4:0a:5b:c0:0d:e9:3b Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: @@ -28,37 +28,37 @@ Certificate: X509v3 Key Usage: Digital Signature, Non Repudiation, Key Encipherment X509v3 Subject Key Identifier: - F0:04:D5:DC:B2:06:D6:C4:30:75:A3:E5:73:0F:99:88:7D:DF:D5:2A + DA:88:D3:69:24:78:C9:79:E6:D4:36:21:2E:0A:B4:AC:C6:11:85:F3 X509v3 Authority Key Identifier: DirName:/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=localhost - serial:EB:2A:1E:BA:5A:E5:C1:7D + serial:9A:B1:F0:96:08:02:B2:34 X509v3 Issuer Alternative Name: DNS:localhost, IP Address:127.0.0.1 Signature Algorithm: sha256WithRSAEncryption - 32:94:72:6c:56:db:26:dc:89:cd:aa:46:c5:e0:8f:53:62:5e: - 65:d4:40:51:d0:5a:3a:a0:49:54:60:d0:0d:60:4b:0c:19:92: - 9c:70:e6:cc:97:3f:ed:0e:12:5d:e9:56:86:6a:09:20:0f:69: - ed:3d:e7:8b:9f:59:3d:24:cd:f2:c4:5e:98:a4:24:66:84:89: - f2:23:0e:45:62:79:ef:0f:a6:93:cd:df:f4:20:53:29:5c:bc: - 7d:8b:6d:71:3d:e0:09:a2:53:47:fe:49:cc:73:e1:51:36:49: - 20:1c:a1:fc:05:de:ff:f7:f2:74:0f:49:86:a5:0d:bf:67:8c: - 54:46 + 60:2d:71:74:f5:03:c8:bd:7e:ed:52:6d:ed:12:35:73:bd:06: + fe:9d:16:8a:cb:56:97:61:1c:f8:b8:42:9a:39:44:33:76:19: + 8f:85:22:c1:ce:18:19:da:e4:08:ff:1c:6f:b4:86:51:b6:07: + 2d:3e:2f:63:9a:f0:c6:1e:19:cd:9d:48:0f:f7:27:1c:03:9c: + c3:d8:d1:20:cb:c0:5d:4f:88:73:72:c6:4b:84:f9:01:2b:a7: + 91:f1:5b:90:e8:ee:ba:e4:41:cd:75:ae:d5:12:ab:a3:89:31: + 4b:0b:bf:f6:12:61:ba:e6:8b:f2:6c:b4:a3:cc:63:63:14:dd: + d9:9e -----BEGIN CERTIFICATE----- MIIC9DCCAl2gAwIBAgIBATANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJBVTET MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ -dHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTYwNTE1MDkzMDQ0WhcNMjYw -NTEzMDkzMDQ0WjBZMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh +dHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTcwODMxMjMzNzM2WhcNMjcw +ODI5MjMzNzM2WjBZMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2Nh -bGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANi3NkLfK5HWIWDb1JH0 -pCZUm5vwrFtUByBC6mpiShDMkD9/SkzmTPYFWVEgxqs9n3On6VrnUMHb0zIwTsZT -NhSCcSq1aWuQLIaXwxilaWKLq0p3BBH/j8IRmWeKiP00G1yXG+0lPAGPL3qySVDE -pTP8lpnY8Oq+SRoKmLfrk7ElAgMBAAGjgcswgcgwCQYDVR0TBAIwADALBgNVHQ8E -BAMCBeAwHQYDVR0OBBYEFPAE1dyyBtbEMHWj5XMPmYh939UqMHMGA1UdIwRsMGqh +bGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMxSJo9P48u4mDxIm+D+ +C/ad3ZClg6IUO0VbzMtuxBVwAk1SMFusPZzwyKMJYar7qRwB5+pLuXRK8vE/0S6S +oBZvQaKP5364y7qwCbLC8gf8OKvexKIgVybRKHZIkdacaE1Cuq0QgVzu0w/TshmJ +wIa/erG7f2C/LKy0ClvADek7AgMBAAGjgcswgcgwCQYDVR0TBAIwADALBgNVHQ8E +BAMCBeAwHQYDVR0OBBYEFNqI02kkeMl55tQ2IS4KtKzGEYXzMHMGA1UdIwRsMGqh XaRbMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdIIJ -AOsqHrpa5cF9MBoGA1UdEgQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0B -AQsFAAOBgQAylHJsVtsm3InNqkbF4I9TYl5l1EBR0Fo6oElUYNANYEsMGZKccObM -lz/tDhJd6VaGagkgD2ntPeeLn1k9JM3yxF6YpCRmhInyIw5FYnnvD6aTzd/0IFMp -XLx9i21xPeAJolNH/knMc+FRNkkgHKH8Bd7/9/J0D0mGpQ2/Z4xURg== +AJqx8JYIArI0MBoGA1UdEgQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0B +AQsFAAOBgQBgLXF09QPIvX7tUm3tEjVzvQb+nRaKy1aXYRz4uEKaOUQzdhmPhSLB +zhgZ2uQI/xxvtIZRtgctPi9jmvDGHhnNnUgP9yccA5zD2NEgy8BdT4hzcsZLhPkB +K6eR8VuQ6O665EHNda7VEqujiTFLC7/2EmG65ovybLSjzGNjFN3Zng== -----END CERTIFICATE----- diff --git a/src/main/proto/config.proto b/src/main/proto/config.proto index 561966b..263ad75 100644 --- a/src/main/proto/config.proto +++ b/src/main/proto/config.proto @@ -27,6 +27,11 @@ message CallConfiguration { // If set, this file will be used as a root certificate for calls using TLS. string tls_ca_cert_path = 4; + + // If set, this will use client certs for authentication + string tls_client_cert_path = 5; + string tls_client_key_path = 6; + string tls_client_override_authority = 7; } // Holds the necessary parameters for adding authentication to requests using @@ -93,4 +98,4 @@ message ProtoConfiguration { // Include paths used to resolve imports of the files being analyzed. repeated string include_paths = 2; -} \ No newline at end of file +} diff --git a/src/test/java/me/dinowernli/grpc/polyglot/config/CommandLineArgsTest.java b/src/test/java/me/dinowernli/grpc/polyglot/config/CommandLineArgsTest.java index e268b27..71169e9 100644 --- a/src/test/java/me/dinowernli/grpc/polyglot/config/CommandLineArgsTest.java +++ b/src/test/java/me/dinowernli/grpc/polyglot/config/CommandLineArgsTest.java @@ -34,24 +34,62 @@ public void tearDown() throws Throwable { @Test public void parsesAdditionalIncludesSingle() { CommandLineArgs params = parseArgs(ImmutableList.of( - String.format("--add_protoc_includes=%s,%s", tempFile1.toString(), tempFile2.toString()))); + String.format("--add_protoc_includes=%s,%s", tempFile1.toString(), tempFile2.toString()))); assertThat(params.additionalProtocIncludes()).hasSize(2); } @Test public void parsesAdditionalIncludesMulti() { CommandLineArgs params = parseArgs(ImmutableList.of( - String.format("--add_protoc_includes=%s", tempFile1.toString()))); + String.format("--add_protoc_includes=%s", tempFile1.toString()))); assertThat(params.additionalProtocIncludes()).hasSize(1); } + @Test + public void parsesOauthRefreshTokenEndpointUrl() { + String url = "https://github.com/grpc-ecosystem/polyglot"; + CommandLineArgs params = parseArgs(ImmutableList.of( + String.format("--oauth_refresh_token_endpoint_url=%s", url))); + assertThat(params.oauthRefreshTokenEndpointUrl().get().toString()).isEqualTo(url); + } + + @Test + public void parsesOauthClientId() { + String client_id = "client_id"; + CommandLineArgs params = parseArgs(ImmutableList.of( + String.format("--oauth_client_id=%s",client_id))); + assertThat(params.oauthClientId().get()).isEqualTo(client_id); + } + + @Test + public void parsesOauthClientSecret() { + String client_secret = "client_secret"; + CommandLineArgs params = parseArgs(ImmutableList.of( + String.format("--oauth_client_secret=%s", client_secret))); + assertThat(params.oauthClientSecret().get()).isEqualTo(client_secret); + } + + @Test + public void parsesOauthRefreshTokenPath() { + CommandLineArgs params = parseArgs(ImmutableList.of( + String.format("--oauth_refresh_token_path=%s", tempFile1.toString()))); + assertThat(params.oauthRefreshTokenPath().get().toString()).isEqualTo(tempFile1.toString()); + } + + @Test + public void parsesOauthAccessTokenPath() { + CommandLineArgs params = parseArgs(ImmutableList.of( + String.format("--oauth_access_token_path=%s", tempFile1.toString()))); + assertThat(params.oauthAccessTokenPath().get().toString()).isEqualTo(tempFile1.toString()); + } + private static CommandLineArgs parseArgs(ImmutableList args) { ImmutableList allArgs = ImmutableList.builder() - .addAll(args) - .add("--endpoint=somehost:1234") - .add("--full_method=some.package/Method") - .add("--proto_discovery_root=.") - .build(); + .addAll(args) + .add("--endpoint=somehost:1234") + .add("--full_method=some.package/Method") + .add("--proto_discovery_root=.") + .build(); return CommandLineArgs.parse(allArgs.toArray(new String[0])); } } diff --git a/src/test/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoaderTest.java b/src/test/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoaderTest.java index 3d8f5eb..cf6d7d2 100644 --- a/src/test/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoaderTest.java +++ b/src/test/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoaderTest.java @@ -1,7 +1,9 @@ package me.dinowernli.grpc.polyglot.config; +import java.net.MalformedURLException; import java.nio.file.Paths; import java.util.Optional; +import java.net.URL; import com.google.common.collect.ImmutableList; import me.dinowernli.junit.TestClass; @@ -13,10 +15,13 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import polyglot.ConfigProto.CallConfiguration; import polyglot.ConfigProto.Configuration; import polyglot.ConfigProto.ConfigurationSet; import polyglot.ConfigProto.OutputConfiguration.Destination; +import javax.swing.text.html.Option; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; @@ -43,7 +48,7 @@ public void tearDown() { @Test public void loadsDefaultConfig() { Configuration defaultConfig = - ConfigurationLoader.forDefaultConfigSet().getDefaultConfiguration(); + ConfigurationLoader.forDefaultConfigSet().getDefaultConfiguration(); assertThat(defaultConfig).isEqualTo(Configuration.getDefaultInstance()); assertThat(defaultConfig.getCallConfig().getUseTls()).isFalse(); @@ -58,41 +63,112 @@ public void throwsIfAskedToLoadNamedFromDefaultSet() { @Test(expected = IllegalArgumentException.class) public void throwsIfNamedConfigMissing() { ConfigurationLoader.forConfigSet(ConfigurationSet.getDefaultInstance()) - .getNamedConfiguration("asfd"); + .getNamedConfiguration("asfd"); } @Test public void loadsNamedConfig() { ConfigurationLoader loader = ConfigurationLoader.forConfigSet(ConfigurationSet.newBuilder() - .addConfigurations(namedConfig("foo")) - .addConfigurations(namedConfig("bar")) - .build()); + .addConfigurations(namedConfig("foo")) + .addConfigurations(namedConfig("bar")) + .build()); assertThat(loader.getNamedConfiguration("foo").getName()).isEqualTo("foo"); } @Test - public void appliesOverrides() { + public void appliesOverridesWithRefreshToken() { + when(mockOverrides.useTls()).thenReturn(Optional.of(true)); + when(mockOverrides.outputFilePath()).thenReturn(Optional.of(Paths.get("asdf"))); + when(mockOverrides.additionalProtocIncludes()).thenReturn(ImmutableList.of(Paths.get("."))); + when(mockOverrides.protoDiscoveryRoot()).thenReturn(Optional.of(Paths.get("."))); + when(mockOverrides.getRpcDeadlineMs()).thenReturn(Optional.of(25)); + when(mockOverrides.tlsCaCertPath()).thenReturn(Optional.of(Paths.get("asdf"))); + when(mockOverrides.oauthRefreshTokenEndpointUrl()).thenReturn(Optional.of(getTestUrl("https://github.com/grpc-ecosystem/polyglot"))); + when(mockOverrides.oauthClientId()).thenReturn(Optional.of("id")); + when(mockOverrides.oauthClientSecret()).thenReturn(Optional.of("secret")); + when(mockOverrides.oauthRefreshTokenPath()).thenReturn(Optional.of(Paths.get("asdf"))); + when(mockOverrides.oauthAccessTokenPath()).thenReturn(Optional.empty()); + when(mockOverrides.tlsClientCertPath()).thenReturn(Optional.of(Paths.get("client_cert"))); + when(mockOverrides.tlsClientKeyPath()).thenReturn(Optional.of(Paths.get("client_key"))); + when(mockOverrides.tlsClientOverrideAuthority()).thenReturn(Optional.of("override_authority")); + when(mockOverrides.oauthRefreshTokenEndpointUrl()).thenReturn(Optional.of(getTestUrl("https://github.com/grpc-ecosystem/polyglot"))); + when(mockOverrides.oauthClientId()).thenReturn(Optional.of("id")); + when(mockOverrides.oauthClientSecret()).thenReturn(Optional.of("secret")); + when(mockOverrides.oauthRefreshTokenPath()).thenReturn(Optional.of(Paths.get("asdf"))); + when(mockOverrides.oauthAccessTokenPath()).thenReturn(Optional.empty()); + + Configuration config = ConfigurationLoader + .forDefaultConfigSet() + .withOverrides(mockOverrides) + .getDefaultConfiguration(); + + assertThat(config.getOutputConfig().getDestination()).isEqualTo(Destination.FILE); + + CallConfiguration callConfig = config.getCallConfig(); + assertThat(callConfig.getUseTls()).isTrue(); + assertThat(callConfig.getDeadlineMs()).isEqualTo(25); + assertThat(callConfig.getTlsCaCertPath()).isNotEmpty(); + assertThat(callConfig.getTlsClientCertPath()).isEqualTo("client_cert"); + assertThat(callConfig.getTlsClientKeyPath()).isEqualTo("client_key"); + assertThat(callConfig.getTlsClientOverrideAuthority()).isEqualTo("override_authority"); + assertThat(callConfig.getDeadlineMs()).isEqualTo(25); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getTokenEndpointUrl()) + .isEqualTo("https://github.com/grpc-ecosystem/polyglot"); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getClient().getId()).isEqualTo("id"); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getClient().getSecret()).isEqualTo("secret"); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getRefreshTokenPath()).isNotEmpty(); + assertThat(callConfig.getOauthConfig().getAccessTokenCredentials().getAccessTokenPath()).isEmpty(); + } + + @Test + public void appliesOverridesWithAccessToken() { when(mockOverrides.useTls()).thenReturn(Optional.of(true)); when(mockOverrides.outputFilePath()).thenReturn(Optional.of(Paths.get("asdf"))); when(mockOverrides.additionalProtocIncludes()).thenReturn(ImmutableList.of(Paths.get("."))); when(mockOverrides.protoDiscoveryRoot()).thenReturn(Optional.of(Paths.get("."))); when(mockOverrides.getRpcDeadlineMs()).thenReturn(Optional.of(25)); when(mockOverrides.tlsCaCertPath()).thenReturn(Optional.of(Paths.get("asdf"))); + when(mockOverrides.tlsClientCertPath()).thenReturn(Optional.of(Paths.get("client_cert"))); + when(mockOverrides.tlsClientKeyPath()).thenReturn(Optional.of(Paths.get("client_key"))); + when(mockOverrides.tlsClientOverrideAuthority()).thenReturn(Optional.of("override_authority")); + when(mockOverrides.oauthRefreshTokenEndpointUrl()).thenReturn(Optional.of(getTestUrl("https://github.com/grpc-ecosystem/polyglot"))); + when(mockOverrides.oauthClientId()).thenReturn(Optional.of("id")); + when(mockOverrides.oauthClientSecret()).thenReturn(Optional.of("secret")); + when(mockOverrides.oauthRefreshTokenPath()).thenReturn(Optional.of(Paths.get("asdf"))); + when(mockOverrides.oauthAccessTokenPath()).thenReturn(Optional.of(Paths.get("asdf"))); Configuration config = ConfigurationLoader - .forDefaultConfigSet() - .withOverrides(mockOverrides) - .getDefaultConfiguration(); + .forDefaultConfigSet() + .withOverrides(mockOverrides) + .getDefaultConfiguration(); - assertThat(config.getCallConfig().getUseTls()).isTrue(); + CallConfiguration callConfig = config.getCallConfig(); + assertThat(callConfig.getUseTls()).isTrue(); assertThat(config.getOutputConfig().getDestination()).isEqualTo(Destination.FILE); - assertThat(config.getCallConfig().getDeadlineMs()).isEqualTo(25); - assertThat(config.getCallConfig().getTlsCaCertPath()).isNotEmpty(); + assertThat(callConfig.getDeadlineMs()).isEqualTo(25); + assertThat(callConfig.getTlsCaCertPath()).isNotEmpty(); + assertThat(callConfig.getTlsClientCertPath()).isEqualTo("client_cert"); + assertThat(callConfig.getTlsClientKeyPath()).isEqualTo("client_key"); + assertThat(callConfig.getTlsClientOverrideAuthority()).isEqualTo("override_authority"); + assertThat(callConfig.getDeadlineMs()).isEqualTo(25); + // Setting the access token path will unset all of the refresh token properties (due to the oneof semantics) + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getTokenEndpointUrl()).isEmpty(); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getClient().getId()).isEmpty(); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getClient().getSecret()).isEmpty(); + assertThat(callConfig.getOauthConfig().getRefreshTokenCredentials().getRefreshTokenPath()).isEmpty(); + assertThat(callConfig.getOauthConfig().getAccessTokenCredentials().getAccessTokenPath()).isNotEmpty(); } private static Configuration namedConfig(String name) { return Configuration.newBuilder() - .setName(name) - .build(); + .setName(name) + .build(); + } + private static URL getTestUrl(String testUrl) { + try { + return new URL(testUrl); + } catch (MalformedURLException mUrlE) { + throw new RuntimeException(); + } } } diff --git a/src/test/java/me/dinowernli/grpc/polyglot/integration/TlsIntegrationTest.java b/src/test/java/me/dinowernli/grpc/polyglot/integration/TlsIntegrationTest.java index 7b871b8..3a13528 100644 --- a/src/test/java/me/dinowernli/grpc/polyglot/integration/TlsIntegrationTest.java +++ b/src/test/java/me/dinowernli/grpc/polyglot/integration/TlsIntegrationTest.java @@ -49,7 +49,6 @@ public void setUp() throws Throwable { responseFilePath = Files.createTempFile("response", "pb.ascii"); storedStdin = System.in; - testServer = TestServer.createAndStart(Optional.of(TestServer.serverSslContextForTesting())); } @After @@ -63,11 +62,40 @@ public void tearDown() throws Throwable { @Test public void makesRoundTripUnary() throws Throwable { - int serverPort = testServer.getGrpcServerPort(); + testServer = TestServer.createAndStart(Optional.of(TestServer.serverSslContextForTesting())); + ImmutableList args = ImmutableList.builder() + .addAll(makeArgs( + testServer.getGrpcServerPort(), + TestUtils.TESTING_PROTO_ROOT.toString(), + TEST_UNARY_METHOD)) + .add(makeArgument("output_file_path", responseFilePath.toString())) + .add(makeArgument("tls_ca_cert_path", TestUtils.loadRootCaCert().getAbsolutePath())) + .add(makeArgument("use_tls", "true")) + .build(); + setStdinContents(MessageWriter.writeJsonStream(ImmutableList.of(REQUEST))); + + // Run the full client. + me.dinowernli.grpc.polyglot.Main.main(args.toArray(new String[0])); + + // Make sure we can parse the response from the file. + ImmutableList responses = TestUtils.readResponseFile(responseFilePath); + assertThat(responses).hasSize(1); + assertThat(responses.get(0)).isEqualTo(TestServer.UNARY_SERVER_RESPONSE); + } + + @Test + public void makesRoundTripWithClientCerts() throws Throwable { + testServer = TestServer.createAndStart( + Optional.of(TestServer.serverSslContextWithClientCertsForTesting())); ImmutableList args = ImmutableList.builder() - .addAll(makeArgs(serverPort, TestUtils.TESTING_PROTO_ROOT.toString(), TEST_UNARY_METHOD)) + .addAll(makeArgs( + testServer.getGrpcServerPort(), + TestUtils.TESTING_PROTO_ROOT.toString(), + TEST_UNARY_METHOD)) .add(makeArgument("output_file_path", responseFilePath.toString())) .add(makeArgument("tls_ca_cert_path", TestUtils.loadRootCaCert().getAbsolutePath())) + .add(makeArgument("tls_client_cert_path", TestUtils.loadClientCert().getAbsolutePath())) + .add(makeArgument("tls_client_key_path", TestUtils.loadClientKey().getAbsolutePath())) .add(makeArgument("use_tls", "true")) .build(); setStdinContents(MessageWriter.writeJsonStream(ImmutableList.of(REQUEST)));