2

I am trying to connect to the google cloud platform pub/sub from behind a proxy.

Using Spring lib "org.springframework.cloud:spring-cloud-gcp-starter-pubsub" which uses the google pub sub client, which in order to make the pull call for the subscription uses gRPC calls.

In order to set the proxy I can use GRPC_PROXY_EXP environment variable but I also need credentials to go through this proxy.

I've tries several approaches, including configuring the org.springframework.cloud.gcp.pubsub.support.SubscriberFactory similar to here https://medium.com/google-cloud/accessing-google-cloud-apis-though-a-proxy-fe46658b5f2a

@Bean
    fun inboundQuotationsChannelAdapter(
        @Qualifier("inboundQuotationsMessageChannel") quotationsChannel: MessageChannel,
        mpProperties: ConfigurationProperties,
        defaultSubscriberFactory: SubscriberFactory
    ): PubSubInboundChannelAdapter {

        Authenticator.setDefault(ProxyAuthenticator("ala","bala"))

        val proxySubscriberFactory: DefaultSubscriberFactory = defaultSubscriberFactory as DefaultSubscriberFactory
        proxySubscriberFactory.setCredentialsProvider(ProxyCredentialsProvider(getCredentials()))
        val headers = mutableMapOf(Pair("Proxy-Authorization", getBasicAuth()))
        proxySubscriberFactory.setChannelProvider(SubscriberStubSettings.defaultGrpcTransportProviderBuilder()
            .setHeaderProvider(FixedHeaderProvider.create(headers)).build())


        val proxySubscriberTemplate = PubSubSubscriberTemplate(proxySubscriberFactory)

        val adapter = PubSubInboundChannelAdapter(proxySubscriberTemplate, mpProperties.gcp.quotationSubscription)
        adapter.outputChannel = quotationsChannel
        adapter.ackMode = AckMode.MANUAL
        adapter.payloadType = ActivityStateChanged::class.java
        return adapter
    }


    @Throws(IOException::class)
    fun getCredentials(): GoogleCredentials {
        val httpTransportFactory = getHttpTransportFactory(
            "127.0.0.1", 3128, "ala", "bala"
        )
        return GoogleCredentials.getApplicationDefault(httpTransportFactory)
    }

    fun getHttpTransportFactory(
        proxyHost: String?,
        proxyPort: Int,
        proxyUsername: String?,
        proxyPassword: String?
    ): HttpTransportFactory? {
        val proxyHostDetails = HttpHost(proxyHost, proxyPort)
        val httpRoutePlanner: HttpRoutePlanner = DefaultProxyRoutePlanner(proxyHostDetails)
        val credentialsProvider: CredentialsProvider = BasicCredentialsProvider()
        credentialsProvider.setCredentials(
            AuthScope(proxyHostDetails.hostName, proxyHostDetails.port),
            UsernamePasswordCredentials(proxyUsername, proxyPassword)
        )
        val httpClient: HttpClient = ApacheHttpTransport.newDefaultHttpClientBuilder()
            .setRoutePlanner(httpRoutePlanner)
            .setProxyAuthenticationStrategy(ProxyAuthenticationStrategy.INSTANCE)
            .setDefaultCredentialsProvider(credentialsProvider)
            .setDefaultRequestConfig(
                RequestConfig.custom()
                    .setAuthenticationEnabled(true)
                    .setProxy(proxyHostDetails)
                    .build())
            .addInterceptorLast(HttpRequestInterceptor { request, context ->
                request.addHeader(
                    BasicHeader(
                        "Proxy-Authorization",
                        getBasicAuth()
                    )
                )
            })
            .build()
        val httpTransport: HttpTransport = ApacheHttpTransport(httpClient)
        return HttpTransportFactory { httpTransport }
    }

Also tried using @GRpcGlobalInterceptor from LogNet https://github.com/LogNet/grpc-spring-boot-starter

    @Bean
    @GRpcGlobalInterceptor
    fun globalServerInterceptor(): ServerInterceptor {
        return GrpcServerInterceptor(configurationProperties)
    }

    @Bean
    @GRpcGlobalInterceptor
    fun globalClientInterceptor(): ClientInterceptor {
        return GrpcClientInterceptor(configurationProperties)
    }

with

class GrpcClientInterceptor(private val configurationProperties: ConfigurationProperties) :
    ClientInterceptor {

    private val proxyUsername = configurationProperties.http.proxy.username
    private val proxyPassword = configurationProperties.http.proxy.password
    private val proxyHeaderKey = Metadata.Key.of("Proxy-Authorization", Metadata.ASCII_STRING_MARSHALLER)

    private fun getBasicAuth(): String {
        val usernameAndPassword = "$proxyUsername:$proxyPassword"
        val encoded = Base64.getEncoder().encodeToString(usernameAndPassword.toByteArray())
        return "Basic $encoded"
    }

    override fun <ReqT, RespT> interceptCall(
        method: MethodDescriptor<ReqT, RespT>?,
        callOptions: CallOptions?, next: Channel
    ): ClientCall<ReqT, RespT>? {
        return object : SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
            override fun start(responseListener: Listener<RespT>?, headers: Metadata) {
                headers.put(proxyHeaderKey, getBasicAuth())
                super.start(object : SimpleForwardingClientCallListener<RespT>(responseListener) {
                    override fun onHeaders(headers: Metadata) {
                        super.onHeaders(headers)
                    }
                }, headers)
            }
        }
    }
}
class GrpcServerInterceptor(private val configurationProperties: ConfigurationProperties) :
    ServerInterceptor {

    private val proxyUsername = configurationProperties.http.proxy.username
    private val proxyPassword = configurationProperties.http.proxy.password

    override fun <ReqT : Any?, RespT : Any?> interceptCall(
        call: ServerCall<ReqT, RespT>?,
        headers: io.grpc.Metadata?,
        next: ServerCallHandler<ReqT, RespT>?
    ): ServerCall.Listener<ReqT> {
        val proxyHeaderKey = Metadata.Key.of("Proxy-Authorization", Metadata.ASCII_STRING_MARSHALLER)
        if (!headers!!.containsKey(proxyHeaderKey))
            headers!!.put(proxyHeaderKey, getBasicAuth())
        return next!!.startCall(call, headers)
    }

    private fun getBasicAuth(): String {
        val usernameAndPassword = "$proxyUsername:$proxyPassword"
        val encoded = Base64.getEncoder().encodeToString(usernameAndPassword.toByteArray())
        return "Basic $encoded"
    }
}

(also tried the annotation directly on class level - ofc it did not work)

Also tried using @GrpcGlobalServerInterceptor and @GrpcGlobalClientInterceptor from https://github.com/yidongnan/grpc-spring-boot-starter/tree/v2.12.0.RELEASE but this dependency crashed the app entirely

iulishkiri
  • 56
  • 6

2 Answers2

1

Here you can find an example on how to set the proxy credentials from the Java API documentation to configuring-a-proxy ;

public CloudTasksClient getService() throws IOException {
  TransportChannelProvider transportChannelProvider =
      CloudTasksStubSettings.defaultGrpcTransportProviderBuilder()
          .setChannelConfigurator(
              new ApiFunction<ManagedChannelBuilder, ManagedChannelBuilder>() {
                @Override
                public ManagedChannelBuilder apply(ManagedChannelBuilder managedChannelBuilder) {
                  return managedChannelBuilder.proxyDetector(
                      new ProxyDetector() {
                        @Nullable
                        @Override
                        public ProxiedSocketAddress proxyFor(SocketAddress socketAddress)
                            throws IOException {
                          return HttpConnectProxiedSocketAddress.newBuilder()
                              .setUsername(PROXY_USERNAME)
                              .setPassword(PROXY_PASSWORD)
                              .setProxyAddress(new InetSocketAddress(PROXY_HOST, PROXY_PORT))
                              .setTargetAddress((InetSocketAddress) socketAddress)
                              .build();
                        }
                      });
                }
              })
          .build();
  CloudTasksSettings cloudTasksSettings =
      CloudTasksSettings.newBuilder()
          .setTransportChannelProvider(transportChannelProvider)
          .build();
  return CloudTasksClient.create(cloudTasksSettings);
}

 

Take into consideration the note where it says that gRPC proxy is currently experimental.

Dan
  • 121
  • 5
0

There are two clients that communicate with google. One using "http" and one using "gRPC". (https instead of http is also possible) The Solution Dan posted is just the solution for gRPC. Here my Solution for http by using an Apache-Http-Client.

try (InputStream jsonCredentialsFile = json-file as InputStream) {
    GoogleCredentials credentials = GoogleCredentials.fromStream(jsonCredentialsFile, new HttpTransportFactory() {
                public HttpTransport create() {
                    DefaultHttpClient httpClient = new DefaultHttpClient();
                    httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, new HttpHost("myProxyHost", myProxyPort, "http"));

//  DefaultHttpClient is deprecated but this recommended solution does not work in this context.
//     org.apache.http.impl.client.InternalHttpClient.getParams() always throws UnsupportedOperationException
//                                HttpClientBuilder builder = HttpClientBuilder.create();
//                                if (StringUtils.isBlank(proxyServer) || proxyport < 0) {
//                                    builder.setProxy(new HttpHost(proxyServer, proxyport, "http"));
//                                }
//                                CloseableHttpClient httpClient = builder.build();
                    return new ApacheHttpTransport(httpClient);
                }
            })
            .createScoped(Lists.newArrayList("https://www.googleapis.com/auth/cloud-platform"));

    DocumentProcessorServiceSettings.Builder dpssBuilder = DocumentProcessorServiceSettings.newBuilder()
            .setEndpoint(endpoint)
            .setCredentialsProvider(FixedCredentialsProvider.create(credentials));
    dpssBuilder.setTransportChannelProvider(transportProvider);
    DocumentProcessorServiceClient client = DocumentProcessorServiceClient.create(dpssBuilder.build());
   // use the client   
}
Oliver
  • 11
  • 2