solution: allow TLS configuration for proxy

This commit is contained in:
Igor Artamonov
2020-03-20 20:22:01 -04:00
parent 26f8dd294e
commit d4bd180e23
25 changed files with 882 additions and 72 deletions

View File

@@ -34,7 +34,8 @@ class ProxyStarter(
@Autowired private val env: Environment,
@Autowired private val readRpcJson: ReadRpcJson,
@Autowired private val writeRpcJson: WriteRpcJson,
@Autowired private val nativeCall: NativeCall
@Autowired private val nativeCall: NativeCall,
@Autowired private val tlsSetup: TlsSetup
) {
companion object {
@@ -48,7 +49,7 @@ class ProxyStarter(
log.debug("Proxy server is not configured")
return
}
val server = ProxyServer(config, readRpcJson, writeRpcJson, nativeCall)
val server = ProxyServer(config, readRpcJson, writeRpcJson, nativeCall, tlsSetup)
server.start()
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2020 ETCDEV GmbH
*
* 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
*
* http://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 io.emeraldpay.dshackle
import io.emeraldpay.dshackle.config.AuthConfig
import io.grpc.netty.GrpcSslContexts
import io.netty.handler.ssl.ClientAuth
import io.netty.handler.ssl.SslContext
import io.netty.handler.ssl.SslContextBuilder
import org.apache.commons.lang3.StringUtils
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
@Service
class TlsSetup(
@Autowired val fileResolver: FileResolver
) {
companion object {
private val log = LoggerFactory.getLogger(TlsSetup::class.java)
}
fun setupServer(category: String, config: AuthConfig.ServerTlsAuth): SslContext? {
val mustBeSecure = config.enabled != null && config.enabled!!
val tlsDisabled = config.enabled != null && !config.enabled!!
var hasServerCertificate = true
if (!tlsDisabled) {
if (StringUtils.isEmpty(config.certificate)) {
if (mustBeSecure) {
log.error("tls.server.certificate property for $category is not set (path to server TLS certificate) but TLS is enabled")
throw IllegalArgumentException("Certificate not set")
}
hasServerCertificate = false
}
if (StringUtils.isEmpty(config.key)) {
if (mustBeSecure) {
log.error("tls.server.key property for $category is not set (path to server TLS certificate key) but TLS is enabled")
throw IllegalArgumentException("Certificate Key not set")
}
hasServerCertificate = false
}
}
if (mustBeSecure || (!tlsDisabled && hasServerCertificate)) {
log.info("Using TLS for $category")
val sslContextBuilder = SslContextBuilder.forServer(
fileResolver.resolve(config.certificate!!),
fileResolver.resolve(config.key!!)
)
if (StringUtils.isNotEmpty(config.clientCa)) {
log.info("Using TLS for client authentication for $category")
sslContextBuilder.trustManager(
fileResolver.resolve(config.clientCa!!)
)
if (config.clientRequire != null && config.clientRequire!!) {
sslContextBuilder.clientAuth(ClientAuth.REQUIRE)
}
} else if (config.clientRequire != null && config.clientRequire!!) {
throw IllegalArgumentException("Client Certificate not set")
} else {
log.warn("Trust all clients for $category")
}
return sslContextBuilder.build()
} else {
log.warn("Using insecure transport for $category")
}
return null
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2020 ETCDEV GmbH
*
* 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
*
* http://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 io.emeraldpay.dshackle.config
import org.slf4j.LoggerFactory
class AuthConfig {
companion object {
private val log = LoggerFactory.getLogger(AuthConfig::class.java)
}
open class ClientAuth {
var type: String? = null
}
class ClientBasicAuth(
val username: String,
val password: String
) : ClientAuth()
class ClientTlsAuth(
var ca: String? = null,
var certificate: String? = null,
var key: String? = null
) : ClientAuth()
/**
* Example config:
* ```
* enabled: false
* server:
* certificate: "127.0.0.1.crt"
* key: "127.0.0.1.p8.key"
* client:
* require: false
* ca: "ca.dshackle.test.crt"
* ```
*/
open class ServerTlsAuth {
var enabled: Boolean? = null
var certificate: String? = null
var key: String? = null
var clientRequire: Boolean? = null
var clientCa: String? = null
}
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2020 ETCDEV GmbH
*
* 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
*
* http://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 io.emeraldpay.dshackle.config
import org.slf4j.LoggerFactory
import org.yaml.snakeyaml.nodes.MappingNode
class AuthConfigReader : YamlConfigReader() {
companion object {
private val log = LoggerFactory.getLogger(AuthConfigReader::class.java)
}
fun readClientBasicAuth(node: MappingNode?): AuthConfig.ClientBasicAuth? {
return getMapping(node, "basic-auth")?.let { authNode ->
val username = getValueAsString(authNode, "username")
val password = getValueAsString(authNode, "password")
if (username != null && password != null) {
AuthConfig.ClientBasicAuth(username, password)
} else {
log.warn("Basic auth is not fully configured")
null
}
}
}
fun readClientTls(node: MappingNode?): AuthConfig.ClientTlsAuth? {
return getMapping(node, "tls")?.let { authNode ->
val auth = AuthConfig.ClientTlsAuth()
auth.ca = getValueAsString(authNode, "ca")
auth.certificate = getValueAsString(authNode, "certificate")
auth.key = getValueAsString(authNode, "key")
auth
}
}
/**
* Example config:
* ```
* enabled: false
* server:
* certificate: "127.0.0.1.crt"
* key: "127.0.0.1.p8.key"
* client:
* require: false
* ca: "ca.dshackle.test.crt"
* ```
*/
fun readServerTls(node: MappingNode?): AuthConfig.ServerTlsAuth? {
return getMapping(node, "tls")?.let { node ->
val auth = AuthConfig.ServerTlsAuth()
getValueAsBool(node, "enabled")?.let {
auth.enabled = it
}
getMapping(node, "server")?.let { node ->
auth.certificate = getValueAsString(node, "certificate")
auth.key = getValueAsString(node, "key")
}
getMapping(node, "client")?.let { node ->
getValueAsBool(node, "require")?.let {
auth.clientRequire = it
}
auth.clientCa = getValueAsString(node, "ca")
}
auth
}
}
}

View File

@@ -38,6 +38,11 @@ class ProxyConfig {
*/
var port: Int = 8080
/**
* TLS Auth required from clients.
*/
var tls: AuthConfig.ServerTlsAuth? = null
/**
* List of available routes
*/

View File

@@ -33,10 +33,10 @@ class ProxyConfigReader : YamlConfigReader() {
}
private var filename = "dshackle.yaml"
private val authConfigReader = AuthConfigReader()
fun read(input: InputStream): ProxyConfig? {
val yaml = Yaml()
val configNode = asMappingNode(yaml.compose(InputStreamReader(input)))
val configNode = readNode(input)
return read(getMapping(configNode, "proxy"))
}
@@ -73,8 +73,10 @@ class ProxyConfigReader : YamlConfigReader() {
}
}
if (config.routes.isEmpty()) {
log.warn("Proxy config has no routes")
return null
}
config.tls = authConfigReader.readServerTls(input)
return config
}

View File

@@ -17,7 +17,6 @@ package io.emeraldpay.dshackle.config
import io.emeraldpay.dshackle.Defaults
import java.net.URI
import java.time.Duration
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
@@ -81,7 +80,7 @@ class UpstreamsConfig {
class GrpcConnection : UpstreamConnection() {
var host: String? = null
var port: Int = 0
var auth: TlsAuth? = null
var auth: AuthConfig.ClientTlsAuth? = null
}
class EthereumConnection : UpstreamConnection() {
@@ -90,29 +89,16 @@ class UpstreamsConfig {
}
class HttpEndpoint(val url: URI) {
var basicAuth: BasicAuth? = null
var tls: TlsAuth? = null
var basicAuth: AuthConfig.ClientBasicAuth? = null
var tls: AuthConfig.ClientTlsAuth? = null
}
class WsEndpoint(val url: URI) {
var origin: URI? = null
var basicAuth: BasicAuth? = null
var basicAuth: AuthConfig.ClientBasicAuth? = null
}
open class Auth {
var type: String? = null
}
class BasicAuth(
val username: String,
val password: String
) : Auth()
class TlsAuth(
var ca: String? = null,
var certificate: String? = null,
var key: String? = null
) : Auth()
//TODO make it unmodifiable after initial load
class Labels: HashMap<String, String>() {

View File

@@ -18,24 +18,21 @@ package io.emeraldpay.dshackle.config
import org.apache.commons.lang3.StringUtils
import org.slf4j.LoggerFactory
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.nodes.CollectionNode
import org.yaml.snakeyaml.nodes.MappingNode
import org.yaml.snakeyaml.nodes.Node
import org.yaml.snakeyaml.nodes.ScalarNode
import reactor.util.function.Tuples
import java.io.InputStream
import java.io.InputStreamReader
import java.lang.IllegalArgumentException
import java.net.URI
import java.time.Duration
class UpstreamsConfigReader : YamlConfigReader() {
private val log = LoggerFactory.getLogger(UpstreamsConfigReader::class.java)
private val authConfigReader = AuthConfigReader()
fun read(input: InputStream): UpstreamsConfig {
val yaml = Yaml()
val configNode = asMappingNode(yaml.compose(InputStreamReader(input)))
val configNode = readNode(input)
val config = UpstreamsConfig()
config.version = getValueAsString(configNode, "version")
@@ -65,8 +62,8 @@ class UpstreamsConfigReader : YamlConfigReader() {
getValueAsString(node, "url")?.let { url ->
val http = UpstreamsConfig.HttpEndpoint(URI(url))
connection.rpc = http
http.basicAuth = readBasicAuth(node)
http.tls = readTls(node)
http.basicAuth = authConfigReader.readClientBasicAuth(node)
http.tls = authConfigReader.readClientTls(node)
}
}
getMapping(connConfigNode, "ws")?.let { node ->
@@ -76,7 +73,7 @@ class UpstreamsConfigReader : YamlConfigReader() {
getValueAsString(node, "origin")?.let { origin ->
ws.origin = URI(origin)
}
ws.basicAuth = readBasicAuth(node)
ws.basicAuth = authConfigReader.readClientBasicAuth(node)
}
}
} else {
@@ -97,7 +94,7 @@ class UpstreamsConfigReader : YamlConfigReader() {
getValueAsInt(connConfigNode, "port")?.let {
connection.port = it
}
connection.auth = readTls(connConfigNode)
connection.auth = authConfigReader.readClientTls(connConfigNode)
} else {
log.error("Upstream at #0 has invalid configuration")
}
@@ -194,27 +191,4 @@ class UpstreamsConfigReader : YamlConfigReader() {
return options
}
private fun readBasicAuth(node: MappingNode?): UpstreamsConfig.BasicAuth? {
return getMapping(node, "basic-auth")?.let { authNode ->
val username = getValueAsString(authNode, "username")
val password = getValueAsString(authNode, "password")
if (username != null && password != null) {
UpstreamsConfig.BasicAuth(username, password)
} else {
log.warn("Basic auth is not fully configured")
null
}
}
}
private fun readTls(node: MappingNode?): UpstreamsConfig.TlsAuth? {
return getMapping(node, "tls")?.let { authNode ->
val auth = UpstreamsConfig.TlsAuth()
auth.ca = getValueAsString(authNode, "ca")
auth.certificate = getValueAsString(authNode, "certificate")
auth.key = getValueAsString(authNode, "key")
auth
}
}
}

View File

@@ -16,14 +16,26 @@
package io.emeraldpay.dshackle.config
import io.emeraldpay.grpc.Chain
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.nodes.CollectionNode
import org.yaml.snakeyaml.nodes.MappingNode
import org.yaml.snakeyaml.nodes.Node
import org.yaml.snakeyaml.nodes.ScalarNode
import java.io.InputStream
import java.io.InputStreamReader
open class YamlConfigReader {
private val envVariables = EnvVariables()
fun readNode(input: String): MappingNode {
return readNode(input.byteInputStream())
}
fun readNode(input: InputStream): MappingNode {
val yaml = Yaml()
return asMappingNode(yaml.compose(InputStreamReader(input)))
}
protected fun hasAny(mappingNode: MappingNode?, key: String): Boolean {
if (mappingNode == null) {
return false

View File

@@ -17,9 +17,11 @@ package io.emeraldpay.dshackle.proxy
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.Common
import io.emeraldpay.dshackle.TlsSetup
import io.emeraldpay.dshackle.config.ProxyConfig
import io.emeraldpay.dshackle.rpc.NativeCall
import io.netty.buffer.Unpooled
import io.netty.handler.ssl.SslContextBuilder
import org.reactivestreams.Publisher
import org.slf4j.LoggerFactory
import org.springframework.http.HttpHeaders
@@ -29,6 +31,7 @@ import reactor.netty.http.server.HttpServer
import reactor.netty.http.server.HttpServerRequest
import reactor.netty.http.server.HttpServerResponse
import reactor.netty.http.server.HttpServerRoutes
import java.io.File
import java.util.function.BiFunction
/**
@@ -38,7 +41,8 @@ class ProxyServer(
private var config: ProxyConfig,
private val readRpcJson: ReadRpcJson,
private val writeRpcJson: WriteRpcJson,
private val nativeCall: NativeCall
private val nativeCall: NativeCall,
private val tlsSetup: TlsSetup
) {
companion object {
@@ -51,9 +55,17 @@ class ProxyServer(
return
}
log.info("Listening Proxy on ${config.host}:${config.port}")
val server: DisposableServer = HttpServer.create()
var serverBuilder = HttpServer.create()
.host(config.host)
.port(config.port)
config.tls?.let { tls ->
tlsSetup.setupServer("proxy", tls)?.let { sslContext ->
serverBuilder = serverBuilder.secure { secure -> secure.sslContext(sslContext) }
}
}
val server: DisposableServer = serverBuilder
.route(this::setupRoutes)
.bindNow()
}

View File

@@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.upstream.ethereum
import io.emeraldpay.dshackle.Defaults
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CachesEnabled
import io.emeraldpay.dshackle.config.AuthConfig
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.reader.EmptyReader
import io.emeraldpay.dshackle.reader.Reader
@@ -45,7 +46,7 @@ class EthereumWs(
.builder<BlockJson<TransactionRefJson>>()
.name("new-blocks")
.build()
var basicAuth: UpstreamsConfig.BasicAuth? = null
var basicAuth: AuthConfig.ClientBasicAuth? = null
private var blockCache: Reader<BlockHash, BlockJson<TransactionRefJson>> = EmptyReader()

View File

@@ -20,6 +20,7 @@ import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.ReactorBlockchainGrpc
import io.emeraldpay.dshackle.Defaults
import io.emeraldpay.dshackle.FileResolver
import io.emeraldpay.dshackle.config.AuthConfig
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
import io.emeraldpay.dshackle.startup.UpstreamChange
@@ -45,7 +46,7 @@ class GrpcUpstreams(
private val host: String,
private val port: Int,
private val objectMapper: ObjectMapper,
private val auth: UpstreamsConfig.TlsAuth? = null,
private val auth: AuthConfig.ClientTlsAuth? = null,
private val fileResolver: FileResolver
) {
private val log = LoggerFactory.getLogger(GrpcUpstreams::class.java)
@@ -133,7 +134,7 @@ class GrpcUpstreams(
return Flux.fromIterable(removed + added)
}
internal fun withTls(auth: UpstreamsConfig.TlsAuth): SslContext {
internal fun withTls(auth: AuthConfig.ClientTlsAuth): SslContext {
val sslContext = SslContextBuilder.forClient()
.clientAuth(ClientAuth.REQUIRE)
sslContext.trustManager(fileResolver.resolve(auth.ca!!).inputStream())

View File

@@ -0,0 +1,191 @@
/**
* Copyright (c) 2020 ETCDEV GmbH
*
* 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
*
* http://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 io.emeraldpay.dshackle
import io.emeraldpay.dshackle.config.AuthConfig
import io.netty.handler.ssl.ClientAuth
import io.netty.handler.ssl.OpenSslServerContext
import spock.lang.Specification
import sun.security.x509.X509CertImpl
class TlsSetupSpec extends Specification {
TlsSetup tlsSetup = new TlsSetup(new FileResolver(new File("src/test/resources/tls-local")))
def "TLS disabled"() {
setup:
def config = new AuthConfig.ServerTlsAuth(
enabled: false
)
when:
def act = tlsSetup.setupServer("test", config)
then:
act == null
}
def "TLS enabled"() {
setup:
def config = new AuthConfig.ServerTlsAuth(
enabled: true,
certificate: "127.0.0.1.crt",
key: "127.0.0.1.p8.key"
)
when:
def act = tlsSetup.setupServer("test", config)
then:
act != null
act.server
!act.client
with((OpenSslServerContext) act) {
act.clientAuth == ClientAuth.NONE
with((X509CertImpl) keyCertChain[0]) {
getIssuerDN().name == "CN=ca.myhost.dev, OU=Blockchain CA, O=My Company"
}
}
}
def "TLS enabled and required from client"() {
setup:
def config = new AuthConfig.ServerTlsAuth(
enabled: true,
certificate: "127.0.0.1.crt",
key: "127.0.0.1.p8.key",
clientRequire: true,
clientCa: "ca.myhost.dev.crt"
)
when:
def act = tlsSetup.setupServer("test", config)
then:
act != null
act.server
!act.client
with((OpenSslServerContext) act) {
act.clientAuth == ClientAuth.REQUIRE
with((X509CertImpl) keyCertChain[0]) {
getIssuerDN().name == "CN=ca.myhost.dev, OU=Blockchain CA, O=My Company"
}
}
}
def "Fail if certificate not set"() {
setup:
def config = new AuthConfig.ServerTlsAuth(
enabled: true,
key: "127.0.0.1.p8.key",
)
when:
tlsSetup.setupServer("test", config)
then:
def t = thrown(IllegalArgumentException)
t.message == "Certificate not set"
}
def "Fail if certificate key not set"() {
setup:
def config = new AuthConfig.ServerTlsAuth(
enabled: true,
certificate: "127.0.0.1.crt"
)
when:
tlsSetup.setupServer("test", config)
then:
def t = thrown(IllegalArgumentException)
t.message == "Certificate Key not set"
}
def "Fail if certificate not exists"() {
setup:
def config = new AuthConfig.ServerTlsAuth(
enabled: true,
certificate: "none.crt",
key: "127.0.0.1.p8.key",
)
when:
tlsSetup.setupServer("test", config)
then:
def t = thrown(IllegalArgumentException)
}
def "Fail if certificate key not exists"() {
setup:
def config = new AuthConfig.ServerTlsAuth(
enabled: true,
certificate: "127.0.0.1.crt",
key: "none.p8.key",
)
when:
tlsSetup.setupServer("test", config)
then:
def t = thrown(IllegalArgumentException)
}
def "Fail if certificate key is invalid"() {
setup:
def config = new AuthConfig.ServerTlsAuth(
enabled: true,
certificate: "127.0.0.1.crt",
key: "127.0.0.1.key",
)
when:
tlsSetup.setupServer("test", config)
then:
def t = thrown(IllegalArgumentException)
}
def "Fail if client certificate not set but required"() {
setup:
def config = new AuthConfig.ServerTlsAuth(
enabled: true,
certificate: "127.0.0.1.crt",
key: "127.0.0.1.p8.key",
clientRequire: true
)
when:
tlsSetup.setupServer("test", config)
then:
def t = thrown(IllegalArgumentException)
}
def "Fail if client certificate not exists"() {
setup:
def config = new AuthConfig.ServerTlsAuth(
enabled: true,
certificate: "127.0.0.1.crt",
key: "127.0.0.1.p8.key",
clientRequire: true,
clientCa: "none.crt"
)
when:
tlsSetup.setupServer("test", config)
then:
def t = thrown(IllegalArgumentException)
}
def "Fail if client certificate is invalid"() {
setup:
def config = new AuthConfig.ServerTlsAuth(
enabled: true,
certificate: "127.0.0.1.crt",
key: "127.0.0.1.p8.key",
clientRequire: true,
clientCa: "ca.myhost.dev.key"
)
when:
tlsSetup.setupServer("test", config)
then:
def t = thrown(IllegalArgumentException)
}
}

View File

@@ -0,0 +1,62 @@
package io.emeraldpay.dshackle.config
import spock.lang.Specification
class AuthConfigReaderSpec extends Specification {
AuthConfigReader reader = new AuthConfigReader()
def "Read basic-auth for client"() {
setup:
def yaml =
"basic-auth:\n" +
" username: 9c199ad8f281f20154fc258fe41a6814\n" +
" password: 258fe4149c199ad8f2811a68f20154fc"
when:
def act = reader.readClientBasicAuth(reader.readNode(yaml))
then:
act != null
act.username == "9c199ad8f281f20154fc258fe41a6814"
act.password == "258fe4149c199ad8f2811a68f20154fc"
}
def "Read tls for client"() {
setup:
def yaml =
"tls:\n" +
" ca: /etc/ca.myservice.com.crt\n" +
" certificate: /etc/client1.myservice.com.crt\n" +
" key: /etc/client1.myservice.com.key"
when:
def act = reader.readClientTls(reader.readNode(yaml))
then:
act != null
act.ca == "/etc/ca.myservice.com.crt"
act.certificate == "/etc/client1.myservice.com.crt"
act.key == "/etc/client1.myservice.com.key"
}
def "Read tls for server"() {
setup:
def yaml =
"tls:\n" +
" enabled: true\n" +
" server:\n" +
" certificate: \"/etc/client1.myservice.com.crt\"\n" +
" key: \"/etc/client1.myservice.com.key\"\n" +
" client:\n" +
" require: false\n" +
" ca: /etc/ca.myservice.com.crt"
when:
def act = reader.readServerTls(reader.readNode(yaml))
then:
act != null
act.enabled != null
act.enabled
act.certificate == "/etc/client1.myservice.com.crt"
act.key == "/etc/client1.myservice.com.key"
act.clientRequire != null
!act.clientRequire
act.clientCa == "/etc/ca.myservice.com.crt"
}
}

View File

@@ -64,7 +64,7 @@ class UpstreamsConfigReaderSpec extends Specification {
with((UpstreamsConfig.EthereumConnection)connection) {
rpc.url == new URI("https://mainnet.infura.io/v3/fa28c968191849c1aff541ad1d8511f2")
rpc.basicAuth != null
with((UpstreamsConfig.BasicAuth)rpc.basicAuth) {
with((AuthConfig.ClientBasicAuth) rpc.basicAuth) {
username == "4fc258fe41a68149c199ad8f281f2015"
password == "1a68f20154fc258fe4149c199ad8f281"
}

View File

@@ -17,6 +17,7 @@ package io.emeraldpay.dshackle.proxy
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.Common
import io.emeraldpay.dshackle.TlsSetup
import io.emeraldpay.dshackle.config.ProxyConfig
import io.emeraldpay.dshackle.rpc.NativeCall
import io.emeraldpay.dshackle.test.TestingCommons
@@ -42,7 +43,8 @@ class ProxyServerSpec extends Specification {
new ProxyConfig(),
new ReadRpcJson(TestingCommons.objectMapper()),
writeRpcJson,
nativeCall
nativeCall,
new TlsSetup(TestingCommons.fileResolver())
)
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)

View File

@@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.Version
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import io.emeraldpay.dshackle.FileResolver
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CachesFactory
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
@@ -79,4 +80,8 @@ class TestingCommons {
static CachesFactory emptyCaches() {
return new CachesFactory(objectMapper(), new StandardEnvironment())
}
static FileResolver fileResolver() {
return new FileResolver(new File("src/test/resources"))
}
}

View File

@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEmDCCAoCgAwIBAgIQDTubkUXjTKQ5k1GApHel5jANBgkqhkiG9w0BAQsFADBF
MRMwEQYDVQQKEwpNeSBDb21wYW55MRYwFAYDVQQLEw1CbG9ja2NoYWluIENBMRYw
FAYDVQQDEw1jYS5teWhvc3QuZGV2MB4XDTIwMDMyMDIzMTEyN1oXDTIxMDkyMDIz
MTAzNFowRTETMBEGA1UEChMKTXkgQ29tcGFueTEaMBgGA1UECxMRQmxvY2tjaGFp
biBTZXJ2ZXIxEjAQBgNVBAMTCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANMdYDhfCvUhcEPcKIQXvxFrIy2HUOM9soplutOAUv6dbTOs
TeCcpUVgt62sJZcPnE2Iy1nYHeVl+N7vuovUxtzYtfW4rGWprjth4rVcRHkWPwz5
QcyH6GwxsUi/0dEtboOxyqqf3W5C+V0+AJYGZ/bI94pGFOMC4+2HTkF8gCDORWKu
SG67SqiD9HL0LDrEdpq+IJOevW7t/Oqz+DWcO+VAi1T62xup4JL+WJI1UGtDPeET
0SRq3kXb8n4te58Y6NXjyodYlon+BESY42JPw4Uw6rjcmDHQVphg5Dtl343OcfN9
CNehtXavuszQdRU9uY1RUdVsvg44xhR60huFJ80CAwEAAaOBgzCBgDAOBgNVHQ8B
Af8EBAMCA7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQW
BBT9J7UEZRalIioijC1h4WsrRr9bpDAfBgNVHSMEGDAWgBRkRRFBxmIJWihWWm42
q3T1nI5IYDAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4ICAQAC3AN0
8Ptp6xCOhZ8ja/RLKdF97WGyJS04KoPXyyB0qmh1zpYzHtcUcy6Zw41CiwS9d+7s
XfEi780ifmL9XOGxNWTFBvwLMJGq4ukdt//q/y5nhNdINbbZ+LNvZna9gPEBW0cK
YR5HEqMMk2eW2Lgo/pg4ska7leI/7ESoVqehNv3Pt9ewVLq38xecRC1K/Q2Z6vlU
zt+RbLYW7W7jIXc/cH7TUCDG9sT4tRptTlJdq9sBPVFFBXEZ+QZlY6b0ipeelqnL
Wrbsc8XWc/WogEEovBgDpqtqas+jfPI5x9DnopfaSeMP9m9BXYEk+D2nnukw5anp
tid4kSqTUOL8q8wTkEnvTd8nqXfFTC3H+Xe5OMGifXkJTRoQsqFS4M/2jLI36MpN
KHlNG3O5B1ML+vB0SedxaOD4PwWYQwq0kDWMOHOhe4OwyGgdr8MmPCzNFLbx5ppm
apMAco1RXzN/MaBJsYaq5MrTJw4XF8H1CMIEb4GHWb/9gLf4cPetFWzud48eg+YL
XADs+uzCGB0+lDMllVpyU+BSZtDUF77PWk1C5/33JNMg8Wzdj/tXs489B6AG1yED
fqcihzkQNp5WvwCtO5gY/+AYWZQ/zD0ICqjPfzofs7rb+s3qi8mRVfJJ/qOdGzxw
9NZPNkcfB8utd/pGbr4jEeHh+8Ds9vUgZFLXNw==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA0x1gOF8K9SFwQ9wohBe/EWsjLYdQ4z2yimW604BS/p1tM6xN
4JylRWC3rawllw+cTYjLWdgd5WX43u+6i9TG3Ni19bisZamuO2HitVxEeRY/DPlB
zIfobDGxSL/R0S1ug7HKqp/dbkL5XT4AlgZn9sj3ikYU4wLj7YdOQXyAIM5FYq5I
brtKqIP0cvQsOsR2mr4gk569bu386rP4NZw75UCLVPrbG6ngkv5YkjVQa0M94RPR
JGreRdvyfi17nxjo1ePKh1iWif4ERJjjYk/DhTDquNyYMdBWmGDkO2Xfjc5x830I
16G1dq+6zNB1FT25jVFR1Wy+DjjGFHrSG4UnzQIDAQABAoIBAQCX1uj9ol4fMI2u
QQpi9zFVNdl3RXvH9PgU0lYtCH6o4lFIeQUKJ6A25fk10Dq5C2E/4sNfOzFFbLIy
pfll2QOuk69LrCdSd1f5Hc4Q4uvcq0Nt8ViB4r4oExWPXWdrK2HxFk7NqW15gHIZ
vh5tyO29cY2Yxg7/t3R3wnlmYEVHUcS7HmhzgDveNzA0VLza3765ntgwXypY8N2j
heEQC1h5kMCurcKJyRXmlsXPRWizX0UBWDrMHFeqyhrH0BlRSFTNC3sKmyYaJQmp
daPNRr4zO0yfm8utVSbNHX2OM5DpIO1Ecq9Sd43QI+ATAtxFrhPoYK1rwll267CV
cJCRbz+BAoGBAPzz/1Xq8s1lGZ6eWz7H1JlzxYLH+TzQfrV1ym7xJgR/b3rVXiJ+
D+qL8zUJDa5xZyflXB7zCg4I7ALmNwMJzVLdIOpEntHK2NtRJ2rp4qH+kMXojays
zOGYfbRLNVe+mgAK9Pu8eOi8NzXqkB/S8rml3xqSOpUFsvlc2qiYhGdXAoGBANWo
XcQDpisRFcrn3J0+pKU57ZIRjxyOTDlEwH7k+x+PprCRFki80kW22u4l22FdDaip
s4vCuAm5tmEogEjINU6ZhSKHxonjaGXfzuZ3gAMk/PN7zFazlgfYEKng+fa1YuZ+
3Ubzq6py8enoffJ/PSF/lClKlV5sxjyilxeZmOd7AoGBAMtaJHUf0l4I3tXDnLsV
zvYmOixvMxEO1C5xKXKS7utCv45SJcE48vat17FVO+h3RmSuYKaI4BZ0Wbfi92q7
4JKzLpjm3X7uwfNehH/Q0t9EVYKk9/BPYs4h1zywEYwesJNEO7p8w/7mAMSZc5AB
+BwDGx6zW+EdmcoaOba8FgU1AoGAV2WftWaoukUq3O0rWUceolenznBQUiYDGAn/
k+imsKpaTS+MJgTXHp1FwNTLgHBH/g4s26azEYdeCzA+CYecBqLVyuIvXIghVErQ
n4WSX7bpoc+qLm0Xme3QIy1cEobwBckvSq6yMe8C9eOcYW2a2/EL8jgIEa/9ByCb
HZQ+77ECgYEAxR1eoxc/XV9rcftdaRl7+Db9Qvnhfwu7MFQ3lgomol1N1ckSjwnO
wo/HX4+8cMS5QN11d8l2hf+7TyuRCrQBLrYYkrVWb4Z+ote3ejrAsDg90xjOFYWf
MWSy7kPeDl3JTEdNPFIIa28EQhZYupD0ihBoVspz2eQ1Y66BOfqC9nU=
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDTHWA4Xwr1IXBD
3CiEF78RayMth1DjPbKKZbrTgFL+nW0zrE3gnKVFYLetrCWXD5xNiMtZ2B3lZfje
77qL1Mbc2LX1uKxlqa47YeK1XER5Fj8M+UHMh+hsMbFIv9HRLW6Dscqqn91uQvld
PgCWBmf2yPeKRhTjAuPth05BfIAgzkVirkhuu0qog/Ry9Cw6xHaaviCTnr1u7fzq
s/g1nDvlQItU+tsbqeCS/liSNVBrQz3hE9Ekat5F2/J+LXufGOjV48qHWJaJ/gRE
mONiT8OFMOq43Jgx0FaYYOQ7Zd+NznHzfQjXobV2r7rM0HUVPbmNUVHVbL4OOMYU
etIbhSfNAgMBAAECggEBAJfW6P2iXh8wja5BCmL3MVU12XdFe8f0+BTSVi0Ifqji
UUh5BQonoDbl+TXQOrkLYT/iw187MUVssjKl+WXZA66Tr0usJ1J3V/kdzhDi69yr
Q23xWIHivigTFY9dZ2srYfEWTs2pbXmAchm+Hm3I7b1xjZjGDv+3dHfCeWZgRUdR
xLseaHOAO943MDRUvNrfvrme2DBfKljw3aOF4RALWHmQwK6twonJFeaWxc9FaLNf
RQFYOswcV6rKGsfQGVFIVM0LewqbJholCal1o81GvjM7TJ+by61VJs0dfY4zkOkg
7URyr1J3jdAj4BMC3EWuE+hgrWvCWXbrsJVwkJFvP4ECgYEA/PP/VeryzWUZnp5b
PsfUmXPFgsf5PNB+tXXKbvEmBH9vetVeIn4P6ovzNQkNrnFnJ+VcHvMKDgjsAuY3
AwnNUt0g6kSe0crY21Enauniof6QxeiNrKzM4Zh9tEs1V76aAAr0+7x46Lw3NeqQ
H9LyuaXfGpI6lQWy+VzaqJiEZ1cCgYEA1ahdxAOmKxEVyufcnT6kpTntkhGPHI5M
OUTAfuT7H4+msJEWSLzSRbba7iXbYV0NqKmzi8K4Cbm2YSiASMg1TpmFIofGieNo
Zd/O5neAAyT883vMVrOWB9gQqeD59rVi5n7dRvOrqnLx6eh98n89IX+UKUqVXmzG
PKKXF5mY53sCgYEAy1okdR/SXgje1cOcuxXO9iY6LG8zEQ7ULnEpcpLu60K/jlIl
wTjy9q3XsVU76HdGZK5gpojgFnRZt+L3arvgkrMumObdfu7B816Ef9DS30RVgqT3
8E9iziHXPLARjB6wk0Q7unzD/uYAxJlzkAH4HAMbHrNb4R2Zyho5trwWBTUCgYBX
ZZ+1Zqi6RSrc7StZRx6iV6fOcFBSJgMYCf+T6KawqlpNL4wmBNcenUXA1MuAcEf+
DizbprMRh14LMD4Jh5wGotXK4i9ciCFUStCfhZJftumhz6oubReZ7dAjLVwShvAF
yS9KrrIx7wL145xhbZrb8QvyOAgRr/0HIJsdlD7vsQKBgQDFHV6jFz9dX2tx+11p
GXv4Nv1C+eF/C7swVDeWCiaiXU3VyRKPCc7Cj8dfj7xwxLlA3XV3yXaF/7tPK5EK
tAEuthiStVZvhn6i17d6OsCwOD3TGM4VhZ8xZLLuQ94OXclMR008UghrbwRCFli6
kPSKEGhWynPZ5DVjroE5+oL2dQ==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFSjCCAzKgAwIBAgIBATANBgkqhkiG9w0BAQsFADBFMRMwEQYDVQQKEwpNeSBD
b21wYW55MRYwFAYDVQQLEw1CbG9ja2NoYWluIENBMRYwFAYDVQQDEw1jYS5teWhv
c3QuZGV2MB4XDTIwMDMyMDIzMTAzNloXDTIxMDkyMDIzMTAzNFowRTETMBEGA1UE
ChMKTXkgQ29tcGFueTEWMBQGA1UECxMNQmxvY2tjaGFpbiBDQTEWMBQGA1UEAxMN
Y2EubXlob3N0LmRldjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL+v
wvbjRXpdjlBioUFFnnL3uCgfVMHw1zad8z36bBXr/MQDjAVgTfGrzI3FM3YVPTqb
peS7DuVfXOr+ULBU/mq6QuRgLTk9ynVbqcezajECtLZgbudkStF4ESFArjuaJ3IY
kYtL8TEKID31+EJIDyjZOwBjb87QoPaEICTIXLDYkoFv3heg9k8npYItWiKJpuJp
BSj23gCBm8zx/uqTZQOa1bEhhSBSI/okmM0dy+rGgquwZamH1HVH5xm7rok/wYry
EVb1AAT+j42Ug0qihF8mJrvlXLaN0G0QHAiqCf+wnS3DzBnaLPVLNcGJSeY6O8m+
HdCo+/y8WBJU6y+50HFk4qLVAoTYswn3X362W3q48DeCjvImty3g2vvfsVqvKkVd
bQy6C4ok3PXjXyhaxdFNx8f4SUXuXIfXyUMdSt2MNN0r4QY8KAWC9Kkic75Ui8sG
wHw2sBtEQxj1EX/QRAiqWeLT5J/CL3fHhmas+NaICeU3lH7pyLgKjs8+aZ44g9dt
g+AHEBqQvFMRW6wcIJLlWPT4zUo//LWGIbnSUGzz34frzVljg5f/ZsxxTTa10qGT
VOb6Jdln12JvnlObMdr45HSFFAXZrJB+g3z6/K9mFml29Py6dnDgQAFdlRW/LLns
4yEx5YXYrkCRse5PUHqt/APVvUp9kbWGnSB0EeoNAgMBAAGjRTBDMA4GA1UdDwEB
/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRkRRFBxmIJWihW
Wm42q3T1nI5IYDANBgkqhkiG9w0BAQsFAAOCAgEAFsXRgMoPT4RYSaB9oO2e/1vc
3e2rkNG2Bd3twKV33r8hUuqH9lm1EcBOIoWkWSZC4bbAvNtaoTm/pEHgBjKHTiPg
liVCuL66OmXWpL0LjGibialKeyowMkJ5S4K/p7vdPvIYA7jAxL7LNDwcoLyMm+22
GmCqrFp8tmy3ELj1uJe5fvI69WME7zGG6rLRGVT+Hc7gtKwk/iDIPvCF4UkQ0Jai
8RWlNy4G5BBSMCY8AWkRsnmVg/njZr8e/CMuOlLbRtSX8Nx/JAoR8bwszy7N0vCi
OkTELZNjo7w0lXWtINxNb5ySnHF6WSxFjmdiAgWv0ka/KBtR/+9PMMTv1g0y6c5W
dQpSTCJyzHhYb8Yu12tj1jaZDrqT0rRD8klZTuPxDtIbuhnpairjIRygRsVOEtYv
RXFWdjp/keD0A41TNtGj5eNsACPBOcYZlsb9AGqqmSQfRJ4R+YJGsBnECmIZpsva
VvX2eKI7jAZcYjHZIzqASnNFxlIM8jTxi2ifpS/7T93/RIl2Es5PJ3SX+FniOtN5
ajlzWSo7SVvgaYOOeOybKZq4cBmQ/R3XAnK1NSgMgHWZv1NOgHZEhC9kpvj0QQnE
WzjC3KDBLzhRpe678C5iorf9FXMkvYfb1NVl5UYYa9/C4aaj4Jt4SiaIaqsq27fP
wkLM79un1zrcbPHQUcA=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
/rUweL14cs7iUki+lWEGjjvXmOFkORI=
-----END PRIVATE KEY-----