solution: allow TLS configuration for proxy
This commit is contained in:
@@ -29,6 +29,31 @@ It configures following:
|
||||
- no JSON RPC is configured
|
||||
- upstreams configuration is configured in the file `upstreams.yaml`
|
||||
|
||||
.Configuration
|
||||
|===
|
||||
| Name | Example | Description
|
||||
|
||||
a| `version`
|
||||
|
|
||||
| Version of the format of the config
|
||||
|
||||
a| `port`
|
||||
a| `port: 12449`
|
||||
| Port to bind gRPC server. `2449` by default
|
||||
|
||||
a| `tls`
|
||||
|
|
||||
| TLS configuration for gRPC. See link:07-authentication.adoc[Authentication] for details
|
||||
|
||||
a| `proxy`
|
||||
|
|
||||
| Proxy configuration
|
||||
|
||||
a| `upstreams`
|
||||
|
|
||||
| Upstreams configuration
|
||||
|===
|
||||
|
||||
=== Enabling JSON RPC proxy
|
||||
|
||||
.Example proxy:
|
||||
@@ -51,4 +76,49 @@ upstreams:
|
||||
With that configuration Dshackle starts a JSON RPC proxy:
|
||||
|
||||
- JSON RPC server is listening on `0.0.0.0:8080`
|
||||
- `http://0.0.0.0:8080/eth` provides access to Ethereum API routed to an available upstream
|
||||
- `http://0.0.0.0:8080/eth` provides access to Ethereum API routed to an available upstream
|
||||
|
||||
.Full configuration:
|
||||
[source,yaml]
|
||||
----
|
||||
proxy:
|
||||
port: 8080
|
||||
tls:
|
||||
enabled: true
|
||||
server:
|
||||
certificate: server.crt
|
||||
key: server.p8.key
|
||||
client:
|
||||
require: true
|
||||
ca: ca.crt
|
||||
routes:
|
||||
- id: eth
|
||||
blockchain: ethereum
|
||||
- id: etc
|
||||
blockchain: ethereum_classic
|
||||
----
|
||||
|
||||
.Proxy configuration
|
||||
|===
|
||||
| Name | Example | Description
|
||||
|
||||
a| `host`
|
||||
a| `host: 0.0.0.0`
|
||||
| Host to bind proxy server. `127.0.0.1` by default
|
||||
|
||||
a| `port`
|
||||
a| `port: 8545`
|
||||
| Port to bind proxy server. `8080` by default
|
||||
|
||||
a| `enabled`
|
||||
a| `enabled: true`
|
||||
| Enable/disable proxy server
|
||||
|
||||
a| `tls`
|
||||
|
|
||||
| TLS configuration for proxy. See link:07-authentication.adoc[Authentication] for details
|
||||
|
||||
a| `routes`
|
||||
|
|
||||
| List of endpoints to proxy
|
||||
|===
|
||||
@@ -12,16 +12,22 @@ generate certificates, but the traditional `openssl` tool can be used as well.
|
||||
|
||||
==== Setup Server certificate
|
||||
|
||||
.Generate a server certificate
|
||||
.Generate a Certificate Authority
|
||||
[source,bash]
|
||||
----
|
||||
SERVER_CA="ca.myhost.dev"
|
||||
SERVER_IP="127.0.0.1"
|
||||
ORG="My Company"
|
||||
ORG_UNIT="Blockchain"
|
||||
export SERVER_CA="ca.myhost.dev"
|
||||
export ORG="My Company"
|
||||
export ORG_UNIT="Blockchain"
|
||||
|
||||
certstrap init --common-name "$SERVER_CA" --passphrase "" -o "$ORG" -ou "$ORG_UNIT CA"
|
||||
certstrap request-cert -ip $SERVER_IP --passphrase "" -o "$ORG" -ou "$ORG_UNIT Server"
|
||||
openssl pkcs8 -topk8 -inform PEM -outform PEM -in out/$SERVER_CA.key -out out/$SERVER_CA.p8.key -nocrypt
|
||||
----
|
||||
|
||||
.Generate a Server Certificate
|
||||
----
|
||||
export SERVER_IP="127.0.0.1"
|
||||
|
||||
certstrap request-cert -ip $SERVER_IP --common-name $SERVER_IP --passphrase "" -o "$ORG" -ou "$ORG_UNIT Server"
|
||||
certstrap sign $SERVER_IP --CA $SERVER_CA
|
||||
|
||||
openssl pkcs8 -topk8 -inform PEM -outform PEM -in out/$SERVER_IP.key -out out/$SERVER_IP.p8.key -nocrypt
|
||||
@@ -30,8 +36,9 @@ openssl pkcs8 -topk8 -inform PEM -outform PEM -in out/$SERVER_IP.key -out out/$S
|
||||
You are going to get following files now in `out` directory:
|
||||
|
||||
- `ca.myhost.dev.crt` your Certification Authority to sign or verify other certificates
|
||||
- `127.0.0.1.crt` server certificate, 127.0.0.1 is an IP address supposed to be used by that instance, and we use local
|
||||
host for demo purposes. For production use a real IP must be used.
|
||||
- `ca.myhost.dev.p8.key` is the private key for Certification Authority in PKSC 8 format
|
||||
- `127.0.0.1.crt` server certificate, 127.0.0.1 is an IP address supposed to be used by that instance, and we use local host for demo purposes.
|
||||
For production use a real IP must be used.
|
||||
- `127.0.0.1.p8.key` is private key for certificate in PKCS 8 format, it needed by Dshackle server to use certificate
|
||||
|
||||
Copy those files to directory with Dshackle and update configuration.
|
||||
@@ -144,5 +151,43 @@ upstreams:
|
||||
key: client_1.p8.key
|
||||
----
|
||||
|
||||
Now if you run second server it will connect to first server ("upstream") running on port 2449, will verify upstream
|
||||
with certificate `ca.myhost.dev.crt` and authenticate itself by using pair of `client_1.crt` and `client_1.p8.key`
|
||||
Now if you run second server it will connect to first server ("upstream") running on port 2449, will verify upstream with certificate `ca.myhost.dev.crt` and authenticate itself by using pair of `client_1.crt` and `client_1.p8.key`
|
||||
|
||||
=== Server TLS configuration
|
||||
|
||||
|===
|
||||
| Name | Example | Description
|
||||
|
||||
a| `enabled`
|
||||
a|
|
||||
[source,yaml]
|
||||
----
|
||||
tls:
|
||||
enabled: true
|
||||
----
|
||||
| Enabled or disable TLS. By default it checks if certificate is set, and then enables it. But if you enable the TLS
|
||||
but didn't specify the certificate or key, then the DShackle will fails to start with error.
|
||||
|
||||
a| `server.certificate`, `server.key`
|
||||
a|
|
||||
[source,yaml]
|
||||
----
|
||||
tls:
|
||||
server:
|
||||
certificate: server.com.crt
|
||||
key: server.com.p8.key
|
||||
----
|
||||
| Path to certificate and certificate private key
|
||||
|
||||
a| `client.ca`, `client.required`
|
||||
a|
|
||||
[source,yaml]
|
||||
----
|
||||
tls:
|
||||
client:
|
||||
ca: ca.crt
|
||||
required: true
|
||||
----
|
||||
a| Path to CA used to authenticate incoming connections, used if `required: true`
|
||||
|
||||
|===
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
83
src/main/kotlin/io/emeraldpay/dshackle/TlsSetup.kt
Normal file
83
src/main/kotlin/io/emeraldpay/dshackle/TlsSetup.kt
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
60
src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfig.kt
Normal file
60
src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfig.kt
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,6 +38,11 @@ class ProxyConfig {
|
||||
*/
|
||||
var port: Int = 8080
|
||||
|
||||
/**
|
||||
* TLS Auth required from clients.
|
||||
*/
|
||||
var tls: AuthConfig.ServerTlsAuth? = null
|
||||
|
||||
/**
|
||||
* List of available routes
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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>() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
191
src/test/groovy/io/emeraldpay/dshackle/TlsSetupSpec.groovy
Normal file
191
src/test/groovy/io/emeraldpay/dshackle/TlsSetupSpec.groovy
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
27
src/test/resources/tls-local/127.0.0.1.crt
Normal file
27
src/test/resources/tls-local/127.0.0.1.crt
Normal 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-----
|
||||
27
src/test/resources/tls-local/127.0.0.1.key
Normal file
27
src/test/resources/tls-local/127.0.0.1.key
Normal 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-----
|
||||
28
src/test/resources/tls-local/127.0.0.1.p8.key
Normal file
28
src/test/resources/tls-local/127.0.0.1.p8.key
Normal 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-----
|
||||
31
src/test/resources/tls-local/ca.myhost.dev.crt
Normal file
31
src/test/resources/tls-local/ca.myhost.dev.crt
Normal file
@@ -0,0 +1,31 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
wkLM79un1zrcbPHQUcA=
|
||||
-----END CERTIFICATE-----
|
||||
51
src/test/resources/tls-local/ca.myhost.dev.key
Normal file
51
src/test/resources/tls-local/ca.myhost.dev.key
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
52
src/test/resources/tls-local/ca.myhost.dev.p8.key
Normal file
52
src/test/resources/tls-local/ca.myhost.dev.p8.key
Normal file
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
/rUweL14cs7iUki+lWEGjjvXmOFkORI=
|
||||
-----END PRIVATE KEY-----
|
||||
Reference in New Issue
Block a user