diff --git a/build.gradle b/build.gradle index 8f94d3c6..0ab76095 100644 --- a/build.gradle +++ b/build.gradle @@ -49,7 +49,7 @@ dependencies { compile "io.grpc:grpc-protobuf:${grpcVersion}" compile "io.grpc:grpc-stub:${grpcVersion}" compile "io.grpc:grpc-netty:${grpcVersion}" - compile "io.netty:netty-tcnative-boringssl-static:2.0.22.Final" + compile "io.netty:netty-tcnative-boringssl-static:2.0.25.Final" compile "io.netty:netty-all:4.1.36.Final" compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ConfiguredUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ConfiguredUpstreams.kt index 8aed8237..f07ae0ac 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ConfiguredUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ConfiguredUpstreams.kt @@ -122,7 +122,8 @@ open class ConfiguredUpstreams( endpoint.host!!, endpoint.port ?: 443, objectMapper, - options + options, + up.auth ) log.info("Using ALL CHAINS (gRPC) upstream, at ${endpoint.host}:${endpoint.port}") ds.start() diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/GrpcUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/GrpcUpstreams.kt index fb4862e2..978a5d90 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/GrpcUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/GrpcUpstreams.kt @@ -6,7 +6,12 @@ import io.emeraldpay.api.proto.ReactorBlockchainGrpc import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.grpc.Chain import io.grpc.ManagedChannelBuilder +import io.grpc.netty.NettyChannelBuilder +import io.netty.handler.ssl.* +import org.apache.commons.lang3.StringUtils +import org.slf4j.LoggerFactory import reactor.core.publisher.Mono +import java.io.File import java.util.* import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock @@ -15,17 +20,26 @@ class GrpcUpstreams( private val host: String, private val port: Int, private val objectMapper: ObjectMapper, - private val options: UpstreamsConfig.Options + private val options: UpstreamsConfig.Options, + private val auth: UpstreamsConfig.TlsAuth? = null ) { + private val log = LoggerFactory.getLogger(GrpcUpstreams::class.java) private var client: ReactorBlockchainGrpc.ReactorBlockchainStub? = null private var known = HashMap() private val lock = ReentrantLock() fun start(): Mono> { - val channel = ManagedChannelBuilder.forAddress(host, port) - .enableRetry() - channel.usePlaintext() + val channel: ManagedChannelBuilder<*> = if (auth != null && StringUtils.isNotEmpty(auth.ca)) { + NettyChannelBuilder.forAddress(host, port) + .useTransportSecurity() + .sslContext(withTls(auth)) + } else { + log.warn("Using insecure connection for $host:$port") + ManagedChannelBuilder.forAddress(host, port) + .usePlaintext() + } + val client = ReactorBlockchainGrpc.newReactorStub(channel.build()) this.client = client val loaded = client.describe(BlockchainOuterClass.DescribeRequest.newBuilder().build()) @@ -52,6 +66,24 @@ class GrpcUpstreams( return loaded } + internal fun withTls(auth: UpstreamsConfig.TlsAuth): SslContext { + val sslContext = SslContextBuilder.forClient() + .clientAuth(ClientAuth.REQUIRE) + sslContext.trustManager(File(auth.ca!!).inputStream()) + if (StringUtils.isNotEmpty(auth.key) && StringUtils.isNoneEmpty(auth.certificate)) { + sslContext.keyManager(File(auth.certificate!!).inputStream(), File(auth.key!!).inputStream()) + } else { + log.warn("Connect to remote using only CA certificate") + } + val alpn = ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + "grpc-exp", "h2") + sslContext.applicationProtocolConfig(alpn) + return sslContext.build() + } + fun getOrCreate(chain: Chain): GrpcUpstream { lock.withLock { val current = known[chain]