Add client auth (#294)
This commit is contained in:
@@ -28,8 +28,8 @@ class AuthorizationConfigReaderSpec extends Specification {
|
||||
def act = AuthorizationConfig.default()
|
||||
then:
|
||||
!act.enabled
|
||||
act.externalPublicKeyPath == ""
|
||||
act.providerPrivateKeyPath == ""
|
||||
act.serverConfig.externalPublicKeyPath == ""
|
||||
act.serverConfig.providerPrivateKeyPath == ""
|
||||
}
|
||||
|
||||
def "exceptions if no settings"() {
|
||||
@@ -48,5 +48,15 @@ class AuthorizationConfigReaderSpec extends Specification {
|
||||
"configs/auth-without-key-owner.yaml" | "Public key owner in not specified"
|
||||
"configs/auth-with-wrong-priv-key.yaml" | "There is no such file: classpath:keys/priv-wrong.p8.key"
|
||||
"configs/auth-with-wrong-pub-key.yaml" | "There is no such file: classpath:keys/pub-wrong.key"
|
||||
"configs/auth-without-any-config.yaml" | "Token auth server settings are not specified"
|
||||
}
|
||||
|
||||
def "client settings is correct"() {
|
||||
setup:
|
||||
def yamlIs = this.class.getClassLoader().getResourceAsStream("configs/auth-with-client-settings.yaml")
|
||||
when:
|
||||
def act = reader.read(yamlIs)
|
||||
then:
|
||||
act.clientConfig == new AuthorizationConfig.ClientConfig("classpath:keys/priv-wrong.p8.key")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,22 +91,29 @@ class MainConfigReaderSpec extends Specification {
|
||||
chains == ["ethereum"]
|
||||
options.minPeers == 3
|
||||
}
|
||||
upstreams.size() == 3
|
||||
upstreams.size() == 4
|
||||
with(upstreams[0]) {
|
||||
id == "remote"
|
||||
}
|
||||
with(upstreams[1]) {
|
||||
id == "local"
|
||||
id == "remoteTokenAuth"
|
||||
connection instanceof UpstreamsConfig.GrpcConnection
|
||||
with(connection as UpstreamsConfig.GrpcConnection) {
|
||||
it.tokenAuth.publicKeyPath == "/path/to/key.pem"
|
||||
}
|
||||
}
|
||||
with(upstreams[2]) {
|
||||
id == "local"
|
||||
}
|
||||
with(upstreams[3]) {
|
||||
id == "infura"
|
||||
}
|
||||
}
|
||||
act.authorization != null
|
||||
with(act.authorization) {
|
||||
enabled
|
||||
providerPrivateKeyPath == "classpath:keys/priv.p8.key"
|
||||
externalPublicKeyPath == "classpath:keys/public.pem"
|
||||
serverConfig.providerPrivateKeyPath == "classpath:keys/priv.p8.key"
|
||||
serverConfig.externalPublicKeyPath == "classpath:keys/public.pem"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,14 @@ import brave.Tracing
|
||||
import brave.grpc.GrpcTracing
|
||||
import io.emeraldpay.dshackle.Chain
|
||||
import io.emeraldpay.dshackle.FileResolver
|
||||
import io.emeraldpay.dshackle.config.AuthorizationConfig
|
||||
import io.emeraldpay.dshackle.config.ChainsConfig
|
||||
import io.emeraldpay.dshackle.config.CompressionConfig
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.quorum.NotNullQuorum
|
||||
import io.emeraldpay.dshackle.upstream.CallTargetsHolder
|
||||
import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods
|
||||
import io.emeraldpay.dshackle.upstream.grpc.auth.GrpcAuthContext
|
||||
import org.springframework.context.ApplicationEventPublisher
|
||||
import reactor.core.scheduler.Schedulers
|
||||
import spock.lang.Specification
|
||||
@@ -33,6 +35,8 @@ class ConfiguredUpstreamsSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
null,
|
||||
Schedulers.boundedElastic(),
|
||||
AuthorizationConfig.default(),
|
||||
new GrpcAuthContext()
|
||||
)
|
||||
def methods = new UpstreamsConfig.Methods(
|
||||
[
|
||||
@@ -65,6 +69,8 @@ class ConfiguredUpstreamsSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
null,
|
||||
Schedulers.boundedElastic(),
|
||||
AuthorizationConfig.default(),
|
||||
new GrpcAuthContext()
|
||||
)
|
||||
def methods = new UpstreamsConfig.Methods(
|
||||
[
|
||||
@@ -96,6 +102,8 @@ class ConfiguredUpstreamsSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
null,
|
||||
Schedulers.boundedElastic(),
|
||||
AuthorizationConfig.default(),
|
||||
new GrpcAuthContext()
|
||||
)
|
||||
expect:
|
||||
configurer.getHash(node, src) == expected
|
||||
@@ -122,6 +130,8 @@ class ConfiguredUpstreamsSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
null,
|
||||
Schedulers.boundedElastic(),
|
||||
AuthorizationConfig.default(),
|
||||
new GrpcAuthContext()
|
||||
)
|
||||
when:
|
||||
def h1 = configurer.getHash(null, "hohoho")
|
||||
@@ -153,6 +163,8 @@ class ConfiguredUpstreamsSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
null,
|
||||
Schedulers.boundedElastic(),
|
||||
AuthorizationConfig.default(),
|
||||
new GrpcAuthContext()
|
||||
)
|
||||
def methodsGroup = new UpstreamsConfig.MethodGroups(
|
||||
["filter"] as Set,
|
||||
|
||||
@@ -22,7 +22,13 @@ import java.security.interfaces.RSAPublicKey
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
|
||||
class AuthProcessorV1Test {
|
||||
private val processor = AuthProcessorV1(AuthorizationConfig(true, "drpc", "", ""))
|
||||
private val processor = AuthProcessorV1(
|
||||
AuthorizationConfig(
|
||||
true, "drpc",
|
||||
AuthorizationConfig.ServerConfig.default(),
|
||||
AuthorizationConfig.ClientConfig.default()
|
||||
)
|
||||
)
|
||||
private val rsaKeyReader = RsaKeyReader()
|
||||
private val privProviderPath = ResourceUtils.getFile("classpath:keys/priv.p8.key").path
|
||||
private val publicDrpcPath = ResourceUtils.getFile("classpath:keys/public-drpc.pem").path
|
||||
|
||||
@@ -42,7 +42,14 @@ class AuthServiceTest {
|
||||
val tokenWrapper = AuthContext.TokenWrapper(
|
||||
"token", Instant.now(), "sessionId"
|
||||
)
|
||||
val authService = AuthService(AuthorizationConfig(true, "drpc", "privPath", "pubPath"), rsaKeyReader, factory)
|
||||
val authService = AuthService(
|
||||
AuthorizationConfig(
|
||||
true, "drpc",
|
||||
AuthorizationConfig.ServerConfig("privPath", "pubPath"),
|
||||
AuthorizationConfig.ClientConfig.default()
|
||||
),
|
||||
rsaKeyReader, factory
|
||||
)
|
||||
val pair = KeyReader.Keys(mock(PrivateKey::class.java), mock(PublicKey::class.java))
|
||||
|
||||
`when`(rsaKeyReader.getKeyPair("privPath", "pubPath"))
|
||||
@@ -64,7 +71,14 @@ class AuthServiceTest {
|
||||
"token", Instant.now(), "sessionIdNext"
|
||||
)
|
||||
val pair = KeyReader.Keys(mock(PrivateKey::class.java), mock(PublicKey::class.java))
|
||||
val authService = AuthService(AuthorizationConfig(true, "drpc", "privPath", "pubPath"), rsaKeyReader, factory)
|
||||
val authService = AuthService(
|
||||
AuthorizationConfig(
|
||||
true, "drpc",
|
||||
AuthorizationConfig.ServerConfig("privPath", "pubPath"),
|
||||
AuthorizationConfig.ClientConfig.default()
|
||||
),
|
||||
rsaKeyReader, factory
|
||||
)
|
||||
|
||||
`when`(rsaKeyReader.getKeyPair("privPath", "pubPath")).thenReturn(pair)
|
||||
`when`(mockV1Processor.process(pair, token))
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package io.emeraldpay.dshackle.upstream.grpc.auth
|
||||
|
||||
import com.auth0.jwt.JWT
|
||||
import com.auth0.jwt.algorithms.Algorithm
|
||||
import io.emeraldpay.api.proto.AuthOuterClass
|
||||
import io.emeraldpay.api.proto.AuthOuterClass.AuthRequest
|
||||
import io.emeraldpay.api.proto.ReactorAuthGrpc.ReactorAuthStub
|
||||
import io.emeraldpay.dshackle.auth.processor.SESSION_ID
|
||||
import io.emeraldpay.dshackle.config.AuthorizationConfig
|
||||
import org.bouncycastle.openssl.PEMParser
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.any
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.springframework.util.ResourceUtils
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.test.StepVerifier
|
||||
import java.io.StringReader
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.security.KeyFactory
|
||||
import java.security.PrivateKey
|
||||
import java.security.interfaces.RSAPrivateKey
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.time.Duration
|
||||
import java.util.UUID
|
||||
|
||||
class GrpcUpstreamsAuthTest {
|
||||
private val privateKeyPath = ResourceUtils.getFile("classpath:keys/priv-drpc.p8.key").path
|
||||
private val providerPublicKeyPath = ResourceUtils.getFile("classpath:keys/public.pem").path
|
||||
private val grpcAuthContext = GrpcAuthContext()
|
||||
private val authConfig = AuthorizationConfig(
|
||||
true, "drpc",
|
||||
AuthorizationConfig.ServerConfig.default(),
|
||||
AuthorizationConfig.ClientConfig(privateKeyPath)
|
||||
)
|
||||
|
||||
private val providerPrivateKeyPath = ResourceUtils.getFile("classpath:keys/priv.p8.key").path
|
||||
|
||||
private val upstreamId = "providerId"
|
||||
|
||||
@BeforeEach
|
||||
fun clearSessions() {
|
||||
grpcAuthContext.removeToken(upstreamId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `success auth`() {
|
||||
val sessionId = UUID.randomUUID().toString()
|
||||
val authStub = Mockito.mock(ReactorAuthStub::class.java)
|
||||
val grpcAuth = GrpcUpstreamsAuth(authStub, authConfig, grpcAuthContext, providerPublicKeyPath)
|
||||
val token = JWT.create()
|
||||
.withClaim(SESSION_ID, sessionId)
|
||||
.sign(Algorithm.RSA256(generatePrivateKey(providerPrivateKeyPath) as RSAPrivateKey))
|
||||
|
||||
`when`(authStub.authenticate(any(AuthRequest::class.java)))
|
||||
.thenReturn(
|
||||
Mono.just(
|
||||
AuthOuterClass.AuthResponse.newBuilder()
|
||||
.setProviderToken(token)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
|
||||
val result = grpcAuth.auth(upstreamId)
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectNext(GrpcUpstreamsAuth.AuthResult(true))
|
||||
.then {
|
||||
assertEquals(sessionId, grpcAuthContext.getToken(upstreamId))
|
||||
}
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `auth is failed`() {
|
||||
val providerId = "providerId"
|
||||
val sessionId = UUID.randomUUID().toString()
|
||||
val authStub = Mockito.mock(ReactorAuthStub::class.java)
|
||||
val grpcAuth = GrpcUpstreamsAuth(authStub, authConfig, grpcAuthContext, providerPublicKeyPath)
|
||||
val token = JWT.create()
|
||||
.withClaim(SESSION_ID, sessionId)
|
||||
.sign(Algorithm.RSA256(generatePrivateKey(privateKeyPath) as RSAPrivateKey))
|
||||
|
||||
`when`(authStub.authenticate(any(AuthRequest::class.java)))
|
||||
.thenReturn(
|
||||
Mono.just(
|
||||
AuthOuterClass.AuthResponse.newBuilder()
|
||||
.setProviderToken(token)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
|
||||
val result = grpcAuth.auth(providerId)
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectNext(
|
||||
GrpcUpstreamsAuth.AuthResult(
|
||||
false,
|
||||
"Error during auth - The Token's Signature resulted invalid when verified using the Algorithm: SHA256withRSA"
|
||||
)
|
||||
)
|
||||
.then {
|
||||
assertEquals(null, grpcAuthContext.getToken(upstreamId))
|
||||
}
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `replace sessionId for the same provider`() {
|
||||
val providerId = "providerId"
|
||||
val sessionId = UUID.randomUUID().toString()
|
||||
val sessionId1 = UUID.randomUUID().toString()
|
||||
val authStub = Mockito.mock(ReactorAuthStub::class.java)
|
||||
val grpcAuth = GrpcUpstreamsAuth(authStub, authConfig, grpcAuthContext, providerPublicKeyPath)
|
||||
val token = JWT.create()
|
||||
.withClaim(SESSION_ID, sessionId)
|
||||
.sign(Algorithm.RSA256(generatePrivateKey(providerPrivateKeyPath) as RSAPrivateKey))
|
||||
val token1 = JWT.create()
|
||||
.withClaim(SESSION_ID, sessionId1)
|
||||
.sign(Algorithm.RSA256(generatePrivateKey(providerPrivateKeyPath) as RSAPrivateKey))
|
||||
|
||||
`when`(authStub.authenticate(any(AuthRequest::class.java)))
|
||||
.thenReturn(
|
||||
Mono.just(
|
||||
AuthOuterClass.AuthResponse.newBuilder()
|
||||
.setProviderToken(token)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
.thenReturn(
|
||||
Mono.just(
|
||||
AuthOuterClass.AuthResponse.newBuilder()
|
||||
.setProviderToken(token1)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
|
||||
grpcAuth.auth(providerId).block()
|
||||
val result = grpcAuth.auth(providerId)
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectNext(GrpcUpstreamsAuth.AuthResult(true))
|
||||
.then {
|
||||
assertEquals(sessionId1, grpcAuthContext.getToken(upstreamId))
|
||||
}
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
|
||||
private fun generatePrivateKey(path: String): PrivateKey {
|
||||
val privateKeyReader = StringReader(Files.readString(Paths.get(path)))
|
||||
val privatePem = PEMParser(privateKeyReader).readPemObject()
|
||||
val privateKeySpec = PKCS8EncodedKeySpec(privatePem.content)
|
||||
|
||||
return KeyFactory.getInstance("RSA").generatePrivate(privateKeySpec)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
client:
|
||||
private-key: "classpath:keys/priv-wrong.p8.key"
|
||||
@@ -1,6 +1,7 @@
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
keys:
|
||||
provider-private-key: "classpath:keys/priv-wrong.p8.key"
|
||||
external-public-key: "classpath:keys/pub-wrong.key"
|
||||
server:
|
||||
keys:
|
||||
provider-private-key: "classpath:keys/priv-wrong.p8.key"
|
||||
external-public-key: "classpath:keys/pub-wrong.key"
|
||||
@@ -1,6 +1,7 @@
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
keys:
|
||||
provider-private-key: "classpath:keys/priv.p8.key"
|
||||
external-public-key: "classpath:keys/pub-wrong.key"
|
||||
server:
|
||||
keys:
|
||||
provider-private-key: "classpath:keys/priv.p8.key"
|
||||
external-public-key: "classpath:keys/pub-wrong.key"
|
||||
3
src/test/resources/configs/auth-without-any-config.yaml
Normal file
3
src/test/resources/configs/auth-without-any-config.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
@@ -1,3 +1,5 @@
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
publicKeyOwner: drpc
|
||||
server:
|
||||
nothing: true
|
||||
@@ -1,5 +1,6 @@
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
keys:
|
||||
external-public-key: /keys/pub.key
|
||||
server:
|
||||
keys:
|
||||
external-public-key: /keys/pub.key
|
||||
@@ -1,5 +1,6 @@
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
keys:
|
||||
provider-private-key: /keys/priv.p8.key
|
||||
server:
|
||||
keys:
|
||||
provider-private-key: /keys/priv.p8.key
|
||||
@@ -14,9 +14,10 @@ tls:
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
keys:
|
||||
provider-private-key: "classpath:keys/priv.p8.key"
|
||||
external-public-key: "classpath:keys/public.pem"
|
||||
server:
|
||||
keys:
|
||||
provider-private-key: "classpath:keys/priv.p8.key"
|
||||
external-public-key: "classpath:keys/public.pem"
|
||||
|
||||
cache:
|
||||
redis:
|
||||
|
||||
@@ -6,4 +6,10 @@ upstreams:
|
||||
tls:
|
||||
ca: /etc/ca.myservice.com.crt
|
||||
certificate: /etc/client1.myservice.com.crt
|
||||
key: /etc/client1.myservice.com.key
|
||||
key: /etc/client1.myservice.com.key
|
||||
- id: remoteTokenAuth
|
||||
connection:
|
||||
grpc:
|
||||
host: "11.12.10.115"
|
||||
token-auth:
|
||||
public-key: /path/to/key.pem
|
||||
Reference in New Issue
Block a user