diff --git a/build.gradle b/build.gradle index 9fc414db..5b0c9a3b 100644 --- a/build.gradle +++ b/build.gradle @@ -98,6 +98,8 @@ dependencies { implementation libs.caffeine implementation libs.javax.annotations + implementation libs.auth0.jwt + testImplementation libs.cglib.nodep testImplementation libs.spockframework.core testImplementation libs.grpc.testing diff --git a/emerald-grpc b/emerald-grpc index 55af14cf..2c7c3286 160000 --- a/emerald-grpc +++ b/emerald-grpc @@ -1 +1 @@ -Subproject commit 55af14cf34701bd514ecd477acac51d1790c8d6c +Subproject commit 2c7c3286a5fe8316efb1ab21ce6ad94d4254ee5b diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3a9f63ad..86b8d996 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -124,6 +124,8 @@ spring-cloud-starter-sleuth = "org.springframework.cloud:spring-cloud-starter-sl spring-cloud-sleuth-zipkin = "org.springframework.cloud:spring-cloud-sleuth-zipkin:3.1.6" brave-instrumentation-grpc = "io.zipkin.brave:brave-instrumentation-grpc:5.15.0" +auth0-jwt = "com.auth0:java-jwt:4.4.0" + [bundles] apache-commons = ["commons-io", "apache-commons-lang3", "apache-commons-collections4"] etherjar = ["etherjar-domain", "etherjar-hex", "etherjar-rpc-api", "etherjar-rpc-http", "etherjar-rpc-ws", "etherjar-tx", "etherjar-contract", "etherjar-erc20"] diff --git a/src/main/kotlin/io/emeraldpay/dshackle/Config.kt b/src/main/kotlin/io/emeraldpay/dshackle/Config.kt index ea21e069..b832f22d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/Config.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/Config.kt @@ -16,6 +16,7 @@ */ package io.emeraldpay.dshackle +import io.emeraldpay.dshackle.config.AuthorizationConfig import io.emeraldpay.dshackle.config.CacheConfig import io.emeraldpay.dshackle.config.ChainsConfig import io.emeraldpay.dshackle.config.CompressionConfig @@ -153,4 +154,9 @@ open class Config( open fun chainsConfig(mainConfig: MainConfig): ChainsConfig { return mainConfig.chains } + + @Bean + open fun authorizationConfig(mainConfig: MainConfig): AuthorizationConfig { + return mainConfig.authorization + } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/GrpcServer.kt b/src/main/kotlin/io/emeraldpay/dshackle/GrpcServer.kt index 989ee733..1b7ebc84 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/GrpcServer.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/GrpcServer.kt @@ -16,6 +16,7 @@ */ package io.emeraldpay.dshackle +import io.emeraldpay.dshackle.auth.AuthInterceptor import io.emeraldpay.dshackle.config.MainConfig import io.emeraldpay.dshackle.monitoring.accesslog.AccessHandlerGrpc import io.grpc.Codec @@ -29,8 +30,6 @@ import io.micrometer.core.instrument.Metrics import io.micrometer.core.instrument.Tag import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.beans.factory.annotation.Qualifier import org.springframework.beans.factory.annotation.Value import org.springframework.scheduling.concurrent.CustomizableThreadFactory import org.springframework.stereotype.Service @@ -46,9 +45,7 @@ open class GrpcServer( private val tlsSetup: TlsSetup, private val accessHandler: AccessHandlerGrpc, private val grpcServerBraveInterceptor: ServerInterceptor, - @Autowired(required = false) - @Qualifier("serverSpansInterceptor") - private val serverSpansInterceptor: ServerInterceptor? + private val authInterceptor: AuthInterceptor ) { @Value("\${spring.application.max-metadata-size}") private var maxMetadataSize: Int = Defaults.maxMetadataSize @@ -87,9 +84,9 @@ open class GrpcServer( } serverBuilder.intercept(grpcServerBraveInterceptor) - serverSpansInterceptor?.let { - serverBuilder.intercept(it) - log.info("Collect spans from provider is enabled") + if (mainConfig.authorization.enabled) { + serverBuilder.intercept(authInterceptor) + log.info("Token authorization is turned on") } tlsSetup.setupServer("Native gRPC", mainConfig.tls, true)?.let { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/auth/AuthContext.kt b/src/main/kotlin/io/emeraldpay/dshackle/auth/AuthContext.kt new file mode 100644 index 00000000..d09cbff0 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/auth/AuthContext.kt @@ -0,0 +1,25 @@ +package io.emeraldpay.dshackle.auth + +import java.time.Instant +import java.util.concurrent.ConcurrentHashMap + +class AuthContext { + + companion object { + val sessions = ConcurrentHashMap() + + fun putTokenInContext(tokenWrapper: TokenWrapper) { + sessions[tokenWrapper.sessionId] = tokenWrapper + } + + fun removeToken(sessionId: String) { + sessions.remove(sessionId) + } + } + + data class TokenWrapper( + val token: String, + val issuedAt: Instant, + val sessionId: String + ) +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/auth/AuthInterceptor.kt b/src/main/kotlin/io/emeraldpay/dshackle/auth/AuthInterceptor.kt new file mode 100644 index 00000000..3d9bef78 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/auth/AuthInterceptor.kt @@ -0,0 +1,38 @@ +package io.emeraldpay.dshackle.auth + +import io.emeraldpay.dshackle.auth.processor.SESSION_ID +import io.grpc.Metadata +import io.grpc.Metadata.ASCII_STRING_MARSHALLER +import io.grpc.ServerCall +import io.grpc.ServerCallHandler +import io.grpc.ServerInterceptor +import io.grpc.Status +import org.springframework.stereotype.Component + +const val AUTH_METHOD_NAME = "emerald.Auth/Authenticate" +const val REFLECT_METHOD_NAME = "grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo" + +@Component +class AuthInterceptor : ServerInterceptor { + private val specialMethods = setOf(AUTH_METHOD_NAME, REFLECT_METHOD_NAME) + + override fun interceptCall( + call: ServerCall, + headers: Metadata, + next: ServerCallHandler + ): ServerCall.Listener { + val sessionId = headers.get( + Metadata.Key.of(SESSION_ID, ASCII_STRING_MARSHALLER) + ) + val isOrdinaryMethod = !specialMethods.contains(call.methodDescriptor.fullMethodName) + + if (isOrdinaryMethod && (sessionId == null || !AuthContext.sessions.containsKey(sessionId))) { + val cause = if (sessionId == null) "sessionId is not passed" else "Session $sessionId does not exist" + throw Status.UNAUTHENTICATED + .withDescription(cause) + .asException() + } + + return next.startCall(call, headers) + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/auth/AuthRpc.kt b/src/main/kotlin/io/emeraldpay/dshackle/auth/AuthRpc.kt new file mode 100644 index 00000000..ec0ea5fc --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/auth/AuthRpc.kt @@ -0,0 +1,47 @@ +package io.emeraldpay.dshackle.auth + +import io.emeraldpay.api.proto.AuthOuterClass +import io.emeraldpay.api.proto.ReactorAuthGrpc +import io.emeraldpay.dshackle.auth.service.AuthService +import io.grpc.Status +import io.grpc.StatusException +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.scheduler.Scheduler + +@Service +class AuthRpc( + private val authService: AuthService, + private val authScheduler: Scheduler +) : ReactorAuthGrpc.AuthImplBase() { + + companion object { + private val log = LoggerFactory.getLogger(AuthRpc::class.java) + } + + override fun authenticate(request: Mono): Mono { + log.info("Start auth process...") + return request + .subscribeOn(authScheduler) + .map { + val token = authService.authenticate(it.token) + AuthOuterClass.AuthResponse.newBuilder() + .setProviderToken(token) + .build() + }.onErrorResume { + if (it is StatusException) { + log.error(it.message) + Mono.error(it) + } else { + val message = "Internal error: ${it.message}" + log.error(message, it) + Mono.error( + Status.INTERNAL + .withDescription(message) + .asException() + ) + } + } + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/auth/processor/AuthProcessor.kt b/src/main/kotlin/io/emeraldpay/dshackle/auth/processor/AuthProcessor.kt new file mode 100644 index 00000000..6f05c9f2 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/auth/processor/AuthProcessor.kt @@ -0,0 +1,75 @@ +package io.emeraldpay.dshackle.auth.processor + +import com.auth0.jwt.JWT +import com.auth0.jwt.JWTVerifier +import com.auth0.jwt.algorithms.Algorithm +import io.emeraldpay.dshackle.auth.AuthContext +import io.emeraldpay.dshackle.auth.service.KeyReader +import io.emeraldpay.dshackle.config.AuthorizationConfig +import io.grpc.Status +import org.springframework.stereotype.Component +import java.security.PrivateKey +import java.security.PublicKey +import java.security.interfaces.RSAPrivateKey +import java.security.interfaces.RSAPublicKey +import java.time.Instant +import java.util.UUID + +const val SESSION_ID = "sessionId" +const val VERSION = "version" + +enum class AuthVersion { + V1; + + companion object { + fun getVersion(version: String) = values().find { it.name == version } + ?: throw Status.INVALID_ARGUMENT + .withDescription("Unsupported auth version $version") + .asException() + } +} + +abstract class AuthProcessor( + private val authorizationConfig: AuthorizationConfig +) { + + open fun process(keys: KeyReader.Keys, token: String): AuthContext.TokenWrapper { + try { + val verifier: JWTVerifier = JWT.require(verifyingAlgorithm(keys.externalPublicKey)) + .withIssuer(authorizationConfig.publicKeyOwner) + .build() + verifier.verify(token) + } catch (e: Exception) { + throw Status.INVALID_ARGUMENT + .withDescription("Invalid token: ${e.message}") + .asException() + } + + return processInternal(keys.providerPrivateKey) + } + + protected abstract fun processInternal(privateKey: PrivateKey): AuthContext.TokenWrapper + + protected abstract fun verifyingAlgorithm(publicKey: PublicKey): Algorithm +} + +@Component +open class AuthProcessorV1( + authorizationConfig: AuthorizationConfig +) : AuthProcessor(authorizationConfig) { + + override fun processInternal(privateKey: PrivateKey): AuthContext.TokenWrapper { + val issAt = Instant.now() + val sessionId = UUID.randomUUID().toString() + val token = JWT.create() + .withIssuedAt(issAt) + .withClaim(SESSION_ID, sessionId) + .withClaim(VERSION, AuthVersion.V1.toString()) + .sign(Algorithm.RSA256(privateKey as RSAPrivateKey)) + + return AuthContext.TokenWrapper(token, issAt, sessionId) + } + + override fun verifyingAlgorithm(publicKey: PublicKey): Algorithm = + Algorithm.RSA256(publicKey as RSAPublicKey, null) +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/auth/processor/AuthProcessorResolver.kt b/src/main/kotlin/io/emeraldpay/dshackle/auth/processor/AuthProcessorResolver.kt new file mode 100644 index 00000000..bc7aeab7 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/auth/processor/AuthProcessorResolver.kt @@ -0,0 +1,36 @@ +package io.emeraldpay.dshackle.auth.processor + +import com.auth0.jwt.interfaces.DecodedJWT +import io.grpc.Status +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component + +@Component +class AuthProcessorResolver( + private val authProcessorV1: AuthProcessor +) { + + companion object { + private val log = LoggerFactory.getLogger(AuthProcessorResolver::class.java) + } + + fun getAuthProcessor(token: DecodedJWT): AuthProcessor { + val claimVersion = token.getClaim(VERSION) + if (claimVersion.isMissing) { + throw Status.INVALID_ARGUMENT + .withDescription("Version is not specified in the token") + .asException() + } + + val version = AuthVersion.getVersion(claimVersion.asString()) + log.info("Using $version of authentication") + + if (version == AuthVersion.V1) { + return authProcessorV1 + } + + throw Status.INVALID_ARGUMENT + .withDescription("Unsupported auth version $version") + .asException() + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/auth/processor/TokenProcessor.kt b/src/main/kotlin/io/emeraldpay/dshackle/auth/processor/TokenProcessor.kt new file mode 100644 index 00000000..70a9ece3 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/auth/processor/TokenProcessor.kt @@ -0,0 +1,26 @@ +package io.emeraldpay.dshackle.auth.processor + +import io.emeraldpay.dshackle.auth.AuthContext +import org.slf4j.LoggerFactory +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Component +import java.time.Instant +import java.time.temporal.ChronoUnit + +@Component +open class TokenProcessor { + + companion object { + private val log = LoggerFactory.getLogger(TokenProcessor::class.java) + } + + @Scheduled(fixedRate = 30000) + fun invalidateTokens() { + AuthContext.sessions + .filter { Instant.now().isAfter(it.value.issuedAt.plus(1, ChronoUnit.HOURS)) } + .forEach { + log.info("Invalidate token with sessionId ${it.key}") + AuthContext.removeToken(it.key) + } + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/auth/service/AuthService.kt b/src/main/kotlin/io/emeraldpay/dshackle/auth/service/AuthService.kt new file mode 100644 index 00000000..6f761ee1 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/auth/service/AuthService.kt @@ -0,0 +1,37 @@ +package io.emeraldpay.dshackle.auth.service + +import com.auth0.jwt.JWT +import io.emeraldpay.dshackle.auth.AuthContext +import io.emeraldpay.dshackle.auth.processor.AuthProcessorResolver +import io.emeraldpay.dshackle.config.AuthorizationConfig +import io.grpc.Status +import org.springframework.stereotype.Service + +@Service +class AuthService( + private val authorizationConfig: AuthorizationConfig, + private val rsaKeyReader: KeyReader, + private val authProcessorResolver: AuthProcessorResolver +) { + + fun authenticate(token: String): String { + if (!authorizationConfig.enabled) { + throw Status.UNIMPLEMENTED + .withDescription("Authentication process is not enabled") + .asException() + } + + val keys = rsaKeyReader.getKeyPair( + authorizationConfig.providerPrivateKeyPath, authorizationConfig.externalPublicKeyPath + ) + val decodedJwt = JWT.decode(token) + + return authProcessorResolver + .getAuthProcessor(decodedJwt) + .process(keys, token) + .run { + AuthContext.putTokenInContext(this) + this.token + } + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/auth/service/KeyReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/auth/service/KeyReader.kt new file mode 100644 index 00000000..608ecf83 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/auth/service/KeyReader.kt @@ -0,0 +1,14 @@ +package io.emeraldpay.dshackle.auth.service + +import java.security.PrivateKey +import java.security.PublicKey + +interface KeyReader { + + fun getKeyPair(providerPrivateKeyPath: String, externalPublicKeyPath: String): Keys + + data class Keys( + val providerPrivateKey: PrivateKey, + val externalPublicKey: PublicKey + ) +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/auth/service/RsaKeyReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/auth/service/RsaKeyReader.kt new file mode 100644 index 00000000..f6f06be8 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/auth/service/RsaKeyReader.kt @@ -0,0 +1,34 @@ +package io.emeraldpay.dshackle.auth.service + +import org.bouncycastle.openssl.PEMParser +import org.springframework.stereotype.Component +import java.io.StringReader +import java.nio.file.Files +import java.nio.file.Paths +import java.security.KeyFactory +import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.X509EncodedKeySpec + +@Component +class RsaKeyReader : KeyReader { + private val factory = KeyFactory.getInstance("RSA") + + override fun getKeyPair(providerPrivateKeyPath: String, externalPublicKeyPath: String): KeyReader.Keys { + val privateKeyReader = StringReader(Files.readString(Paths.get(providerPrivateKeyPath))) + val publicKeyReader = StringReader(Files.readString(Paths.get(externalPublicKeyPath))) + + val privatePem = PEMParser(privateKeyReader).readPemObject() + val publicPem = PEMParser(publicKeyReader).readPemObject() + + val privateKeySpec = PKCS8EncodedKeySpec(privatePem.content) + val publicKeySpec = X509EncodedKeySpec(publicPem.content) + + val pubKey = factory.generatePublic(publicKeySpec) + val privateKey = factory.generatePrivate(privateKeySpec) + + return KeyReader.Keys( + privateKey, + pubKey + ) + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/AuthorizationConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/AuthorizationConfig.kt new file mode 100644 index 00000000..e3ee5bea --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/AuthorizationConfig.kt @@ -0,0 +1,14 @@ +package io.emeraldpay.dshackle.config + +data class AuthorizationConfig( + val enabled: Boolean, + val publicKeyOwner: String, + val providerPrivateKeyPath: String, + val externalPublicKeyPath: String +) { + + companion object { + @JvmStatic + fun default() = AuthorizationConfig(false, "", "", "") + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/AuthorizationConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/AuthorizationConfigReader.kt new file mode 100644 index 00000000..ef54e929 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/AuthorizationConfigReader.kt @@ -0,0 +1,53 @@ +package io.emeraldpay.dshackle.config + +import org.slf4j.LoggerFactory +import org.springframework.util.ResourceUtils +import org.yaml.snakeyaml.nodes.MappingNode +import java.io.FileNotFoundException + +class AuthorizationConfigReader : YamlConfigReader() { + + companion object { + private val log = LoggerFactory.getLogger(AuthorizationConfigReader::class.java) + } + + override fun read(input: MappingNode?): AuthorizationConfig { + val auth = getMapping(input, "auth") + if (auth == null) { + log.warn("Authorization is not using") + return AuthorizationConfig.default() + } + + val enabled = getValueAsBool(auth, "enabled") + if (enabled == null || !enabled) { + log.warn("Authorization is not enabled") + return AuthorizationConfig.default() + } + + val publicKeyOwner = getValueAsString(auth, "publicKeyOwner") + ?: throw IllegalStateException("Public key owner in not specified") + + val keyPair = getMapping(auth, "keys") ?: throw IllegalStateException("Auth keys is not specified") + val privateKey = getValueAsString(keyPair, "provider-private-key") + ?: throw IllegalStateException("Private key in not specified") + val publicKey = getValueAsString(keyPair, "external-public-key") + ?: throw IllegalStateException("External key in not specified") + + if (fileNotExists(privateKey)) { + throw IllegalStateException("There is no such file: $privateKey") + } + if (fileNotExists(publicKey)) { + throw IllegalStateException("There is no such file: $publicKey") + } + + return AuthorizationConfig(enabled, publicKeyOwner, privateKey, publicKey) + } + + private fun fileNotExists(path: String): Boolean { + return try { + !ResourceUtils.getFile(path).exists() + } catch (e: FileNotFoundException) { + true + } + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt index 8715842c..9d6b125a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt @@ -30,4 +30,5 @@ class MainConfig { var signature: SignatureConfig? = null var compression: CompressionConfig = CompressionConfig.default() var chains: ChainsConfig = ChainsConfig.default() + var authorization: AuthorizationConfig = AuthorizationConfig.default() } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfigReader.kt index 9fa6cf85..c518a71c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfigReader.kt @@ -33,6 +33,7 @@ class MainConfigReader( private val signatureConfigReader = SignatureConfigReader(fileResolver) private val compressionConfigReader = CompressionConfigReader() private val chainsConfigReader = ChainsConfigReader(upstreamsConfigReader) + private val authorizationConfigReader = AuthorizationConfigReader() override fun read(input: MappingNode?): MainConfig { val config = MainConfig() @@ -80,6 +81,9 @@ class MainConfigReader( chainsConfigReader.read(input).let { config.chains = it } + authorizationConfigReader.read(input).let { + config.authorization = it + } return config } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/context/SchedulersConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/context/SchedulersConfig.kt index 1b6ebc9b..fe9a38e2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/context/SchedulersConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/context/SchedulersConfig.kt @@ -40,6 +40,11 @@ open class SchedulersConfig { return makePool("grpc-client-channel", 10, monitoringConfig) } + @Bean + open fun authScheduler(monitoringConfig: MonitoringConfig): Scheduler { + return makeScheduler("auth-scheduler", 4, monitoringConfig) + } + private fun makeScheduler(name: String, size: Int, monitoringConfig: MonitoringConfig): Scheduler { return Schedulers.fromExecutorService(makePool(name, size, monitoringConfig)) } diff --git a/src/test/groovy/io/emeraldpay/dshackle/config/AuthorizationConfigReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/config/AuthorizationConfigReaderSpec.groovy new file mode 100644 index 00000000..77793be5 --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/config/AuthorizationConfigReaderSpec.groovy @@ -0,0 +1,52 @@ +package io.emeraldpay.dshackle.config + +import spock.lang.Specification + +class AuthorizationConfigReaderSpec extends Specification { + def reader = new AuthorizationConfigReader() + + def "if no auth option then default settings"() { + setup: + def yamlIs = this.class.getClassLoader().getResourceAsStream("configs/upstreams-basic.yaml") + when: + def act = reader.read(yamlIs) + then: + act == AuthorizationConfig.default() + } + + def "if auth is disabled then defaults settings"() { + setup: + def yamlIs = this.class.getClassLoader().getResourceAsStream("configs/auth-disabled.yaml") + when: + def act = reader.read(yamlIs) + then: + act == AuthorizationConfig.default() + } + + def "check default settings"() { + when: + def act = AuthorizationConfig.default() + then: + !act.enabled + act.externalPublicKeyPath == "" + act.providerPrivateKeyPath == "" + } + + def "exceptions if no settings"() { + setup: + def yamlIs = this.class.getClassLoader().getResourceAsStream(filePath) + when: + reader.read(yamlIs) + then: + def t = thrown(IllegalStateException) + t.message == message + where: + filePath | message + "configs/auth-without-public-key.yaml" | "External key in not specified" + "configs/auth-without-private-key.yaml" | "Private key in not specified" + "configs/auth-without-key-pair.yaml" | "Auth keys is not specified" + "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" + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/config/MainConfigReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/config/MainConfigReaderSpec.groovy index a03a7282..e5bcd15a 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/config/MainConfigReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/config/MainConfigReaderSpec.groovy @@ -102,5 +102,11 @@ class MainConfigReaderSpec extends Specification { id == "infura" } } + act.authorization != null + with(act.authorization) { + enabled + providerPrivateKeyPath == "classpath:keys/priv.p8.key" + externalPublicKeyPath == "classpath:keys/public.pem" + } } } diff --git a/src/test/kotlin/io/emeraldpay/dshackle/auth/processor/AuthProcessorResolverTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/auth/processor/AuthProcessorResolverTest.kt new file mode 100644 index 00000000..72932b16 --- /dev/null +++ b/src/test/kotlin/io/emeraldpay/dshackle/auth/processor/AuthProcessorResolverTest.kt @@ -0,0 +1,55 @@ +package io.emeraldpay.dshackle.auth.processor + +import com.auth0.jwt.JWT +import io.emeraldpay.dshackle.config.AuthorizationConfig +import io.grpc.StatusException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class AuthProcessorResolverTest { + private val authProcessorV1 = AuthProcessorV1(AuthorizationConfig.default()) + private val authProcessorResolver = AuthProcessorResolver(authProcessorV1) + + @Test + fun `get processor of V1 version`() { + val token = JWT.decode( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkcnBjIiwiaWF0IjoxNjkyMTg1OTMxLCJ2ZXJzaW9uI" + + "joiVjEifQ.BZILN0GQ7JzXGFz-GZIbFTT9E5L-miB4Nga0v4o_cQThk8gbDelBRzEfdsqxCq_ppPr3v_Own8M-vR9yQElx5nEdlI4xe5QAMdIvr3g" + + "12fMckydX9IsW4sVQ1kJJY8RrHb-WL-uI0WSWqoMSwf-Psb-UyiEHAjc3oK7fA72lBaGT4waPHOxRBPvezwg7N934vCZvZMAftFfVgmeEtbCeD7bF" + + "umEr0uEmkIKPTg4QwP-VMvqoLBYpMiJVzP_Ipg_wRHJ7fUN0BGEPjjMvhQ_6TWByiQUBz1kTMd0Ebf_kEuXFQeiwA-FXHJpWczzh66CbbmmWAWsi" + + "ehKw3KPZeBj0oQ" + ) + val processor = authProcessorResolver.getAuthProcessor(token) + + assertTrue(processor is AuthProcessorV1) + } + + @Test + fun `failed if no version is in a token`() { + val token = JWT.decode( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkcnBjIiwiaWF0IjoxNjkyMTg5NTUzfQ.sQ1Q3DFC7kOlHWdnWaxv2F" + + "vhGso6ajJQVWtrE1hYmL_AIH_Lz8LNpqFShlL2itEGfmyRz5-Vrbdf-yWjeyLyNDlf7pay2lNE5Pvm2-EtQd8GUYcCFFIz7Sxc2Iphe2" + + "YIx6kBwSlaR0RXBcmUlOtKrnON0bBNzSojBmtCT7-j4hTpoKhYr04Fr9EJWHfw7grVZjU8rEizAX_SR3ZNoufjK_pZaIyI9qUKVPYSepP" + + "lXtQzVjA80qSeYpkeFCOLwlQD_yTArDNWlwe7-CthtBOAtctoTMwyudfJezT2ilXrigzbauzU5BEi1cNxacHpjNuXhyY0TiacJGugWfRgaaGy6g" + ) + + val e = assertThrows(StatusException::class.java) { authProcessorResolver.getAuthProcessor(token) } + assertEquals("INVALID_ARGUMENT: Version is not specified in the token", e.message) + } + + @Test + fun `failed if the wrong version is specified`() { + val token = JWT.decode( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkcnBjIiwiaWF0IjoxNjkyMTg5OTI3LCJ2ZXJzaW9uIjoiVjIifQ.pT" + + "mG3Z-h9P1DlocTKZ3BpAKNdsvRGjXo71I0GUpUemAnauUaH-OgxUOHlNcZ2uB4f1poEWsbpExeuZ15YAiBf2WBCEV6J6xH1u0cPC1O-8" + + "hHNb46166ngzo-BtZ7Rn7FeVEayyICslc9iXM5GvoFJyJIdn9uLMzalyDJq2bUmIelym4edkQ3ybF-pqf8garuVVErnAsbOFXbYQlfO5ZJ" + + "4zq6PfJo7QSkreMzQ4tK_-JJIGG-EK1bQjsAD7JiXipY2cJY17VlHuavI0DfcJlOe-QggbTH63rxL6JvXCyFux7gI7zdqSBP1fNDJNzTLA" + + "cnR7jCN0kMIa8urQgdePZyRg" + ) + + val e = assertThrows(StatusException::class.java) { authProcessorResolver.getAuthProcessor(token) } + assertEquals("INVALID_ARGUMENT: Unsupported auth version V2", e.message) + } +} diff --git a/src/test/kotlin/io/emeraldpay/dshackle/auth/processor/AuthProcessorV1Test.kt b/src/test/kotlin/io/emeraldpay/dshackle/auth/processor/AuthProcessorV1Test.kt new file mode 100644 index 00000000..811ccb35 --- /dev/null +++ b/src/test/kotlin/io/emeraldpay/dshackle/auth/processor/AuthProcessorV1Test.kt @@ -0,0 +1,85 @@ +package io.emeraldpay.dshackle.auth.processor + +import com.auth0.jwt.JWT +import com.auth0.jwt.JWTVerifier +import com.auth0.jwt.RegisteredClaims +import com.auth0.jwt.algorithms.Algorithm +import io.emeraldpay.dshackle.auth.service.RsaKeyReader +import io.emeraldpay.dshackle.config.AuthorizationConfig +import io.grpc.StatusException +import org.bouncycastle.openssl.PEMParser +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.springframework.util.ResourceUtils +import java.io.StringReader +import java.nio.file.Files +import java.nio.file.Paths +import java.security.KeyFactory +import java.security.PublicKey +import java.security.interfaces.RSAPublicKey +import java.security.spec.X509EncodedKeySpec + +class AuthProcessorV1Test { + private val processor = AuthProcessorV1(AuthorizationConfig(true, "drpc", "", "")) + 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 + private val token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkcnBjIiwiaWF0IjoxNjkyMTg1OTMxLCJ2ZXJzaW9uI" + + "joiVjEifQ.BZILN0GQ7JzXGFz-GZIbFTT9E5L-miB4Nga0v4o_cQThk8gbDelBRzEfdsqxCq_ppPr3v_Own8M-vR9yQElx5nEdlI4xe5QAMdIvr3g" + + "12fMckydX9IsW4sVQ1kJJY8RrHb-WL-uI0WSWqoMSwf-Psb-UyiEHAjc3oK7fA72lBaGT4waPHOxRBPvezwg7N934vCZvZMAftFfVgmeEtbCeD7bF" + + "umEr0uEmkIKPTg4QwP-VMvqoLBYpMiJVzP_Ipg_wRHJ7fUN0BGEPjjMvhQ_6TWByiQUBz1kTMd0Ebf_kEuXFQeiwA-FXHJpWczzh66CbbmmWAWsi" + + "ehKw3KPZeBj0oQ" + private val keyPair = rsaKeyReader.getKeyPair(privProviderPath, publicDrpcPath) + + @Test + fun `verify tokens is successful`() { + val publicProviderPath = ResourceUtils.getFile("classpath:keys/public.pem").path + + val providerToken = processor.process(keyPair, token).token + val verifier: JWTVerifier = JWT.require(Algorithm.RSA256(generatePublicKey(publicProviderPath) as RSAPublicKey, null)) + .withClaim(VERSION, "V1") + .build() + val decodedToken = verifier.verify(providerToken) + assertTrue(!decodedToken.getClaim(SESSION_ID).isMissing) + assertTrue(!decodedToken.getClaim(RegisteredClaims.ISSUED_AT).isMissing) + } + + @Test + fun `verify token is failed by wrong key`() { + val publicProviderPath = ResourceUtils.getFile("classpath:keys/wrong-public.pem").path + val keyPair = rsaKeyReader.getKeyPair(privProviderPath, publicProviderPath) + + val e = assertThrows(StatusException::class.java) { processor.process(keyPair, token) } + assertEquals( + "INVALID_ARGUMENT: Invalid token: The Token's Signature resulted invalid when verified using the Algorithm: SHA256withRSA", + e.message + ) + } + + @Test + fun `verify token is failed if no issuer`() { + val invalidToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkcnBjY2NjIiwiaWF0IjoxNjkyMTg3NDMwLCJ2ZXJzaW" + + "9uIjoiVjEifQ.J1WJ1GvjNJ9JQiMK0bwvtGX1o9P93F5-921myIx3TMa2X48qIG0GVEcoMgv01ca-_aisW-Amk27ygI09dPKE__Ijr6JhZ" + + "fDVNkw_ZArtQTcjhhJiCl3pqsouOlojc8EolpYUmyOefqemqycG0B84ibKAWTdXOtjibt1P5szWjIIV9yOYV7lTJkC0B5swcjjaMvTEPU7y" + + "ZJhg_wvCvT67yFM1K_Wnhys3-j-Xv1Y2wOkxNt4i5LKFDtMZml5eTIEscDpjp5ARjaSTW_Rs1Eixqltx_wz1ALiS0QXOJpX7pVMJjRcth4Nu" + + "R87ej434XoHZWqDmvOEM6M855WeHaO761A" + + val e = assertThrows(StatusException::class.java) { processor.process(keyPair, invalidToken) } + assertEquals( + "INVALID_ARGUMENT: Invalid token: The Claim 'iss' value doesn't match the required issuer.", + e.message + ) + } + + private fun generatePublicKey(path: String): PublicKey { + val publicKeyReader = StringReader(Files.readString(Paths.get(path))) + + val publicPem = PEMParser(publicKeyReader).readPemObject() + + val publicKeySpec = X509EncodedKeySpec(publicPem.content) + + return KeyFactory.getInstance("RSA").generatePublic(publicKeySpec) + } +} diff --git a/src/test/kotlin/io/emeraldpay/dshackle/auth/processor/TokenProcessorTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/auth/processor/TokenProcessorTest.kt new file mode 100644 index 00000000..f69f9adb --- /dev/null +++ b/src/test/kotlin/io/emeraldpay/dshackle/auth/processor/TokenProcessorTest.kt @@ -0,0 +1,53 @@ +package io.emeraldpay.dshackle.auth.processor + +import io.emeraldpay.dshackle.auth.AuthContext +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.time.Instant +import java.time.temporal.ChronoUnit + +class TokenProcessorTest { + private val tokenProcessor = TokenProcessor() + + @BeforeEach + fun removeSessions() { + AuthContext.sessions.clear() + } + + @Test + fun `invalidate all tokens`() { + AuthContext.putTokenInContext( + AuthContext.TokenWrapper("token", Instant.now().minus(1, ChronoUnit.HOURS), "session1") + ) + AuthContext.putTokenInContext( + AuthContext.TokenWrapper("token", Instant.now().minus(1, ChronoUnit.HOURS), "session2") + ) + AuthContext.putTokenInContext( + AuthContext.TokenWrapper("token", Instant.now().minus(1, ChronoUnit.HOURS), "session3") + ) + + tokenProcessor.invalidateTokens() + + assertTrue(AuthContext.sessions.isEmpty()) + } + + @Test + fun `tokens are still in the context after invalidation`() { + val token1 = AuthContext.TokenWrapper("token", Instant.now().minus(30, ChronoUnit.MINUTES), "session1") + val token2 = AuthContext.TokenWrapper("token", Instant.now().minus(30, ChronoUnit.MINUTES), "session2") + val token3 = AuthContext.TokenWrapper("token", Instant.now().minus(30, ChronoUnit.MINUTES), "session3") + AuthContext.putTokenInContext(token1) + AuthContext.putTokenInContext(token2) + AuthContext.putTokenInContext(token3) + + tokenProcessor.invalidateTokens() + + assertEquals(3, AuthContext.sessions.size) + assertEquals( + mapOf(token1.sessionId to token1, token2.sessionId to token2, token3.sessionId to token3), + AuthContext.sessions + ) + } +} diff --git a/src/test/kotlin/io/emeraldpay/dshackle/auth/service/AuthServiceTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/auth/service/AuthServiceTest.kt new file mode 100644 index 00000000..b985ef56 --- /dev/null +++ b/src/test/kotlin/io/emeraldpay/dshackle/auth/service/AuthServiceTest.kt @@ -0,0 +1,85 @@ +package io.emeraldpay.dshackle.auth.service + +import io.emeraldpay.dshackle.auth.AuthContext +import io.emeraldpay.dshackle.auth.processor.AuthProcessor +import io.emeraldpay.dshackle.auth.processor.AuthProcessorResolver +import io.emeraldpay.dshackle.config.AuthorizationConfig +import io.grpc.StatusException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import java.security.PrivateKey +import java.security.PublicKey +import java.time.Instant +import java.util.concurrent.CompletableFuture + +class AuthServiceTest { + private val rsaKeyReader = mock(KeyReader::class.java) + private val mockV1Processor = mock(AuthProcessor::class.java) + private val factory = AuthProcessorResolver(mockV1Processor) + + private val token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkcnBjIiwiaWF0IjoxNjkyMTg1OTMxLCJ2ZXJzaW9uI" + + "joiVjEifQ.BZILN0GQ7JzXGFz-GZIbFTT9E5L-miB4Nga0v4o_cQThk8gbDelBRzEfdsqxCq_ppPr3v_Own8M-vR9yQElx5nEdlI4xe5QAMdIvr3g" + + "12fMckydX9IsW4sVQ1kJJY8RrHb-WL-uI0WSWqoMSwf-Psb-UyiEHAjc3oK7fA72lBaGT4waPHOxRBPvezwg7N934vCZvZMAftFfVgmeEtbCeD7bF" + + "umEr0uEmkIKPTg4QwP-VMvqoLBYpMiJVzP_Ipg_wRHJ7fUN0BGEPjjMvhQ_6TWByiQUBz1kTMd0Ebf_kEuXFQeiwA-FXHJpWczzh66CbbmmWAWsi" + + "ehKw3KPZeBj0oQ" + + @Test + fun `unimplemented error if auth is disabled`() { + val authService = AuthService(AuthorizationConfig.default(), rsaKeyReader, factory) + + val e = assertThrows(StatusException::class.java) { authService.authenticate("") } + assertEquals("UNIMPLEMENTED: Authentication process is not enabled", e.message) + } + + @Test + fun `auth is successful`() { + val tokenWrapper = AuthContext.TokenWrapper( + "token", Instant.now(), "sessionId" + ) + val authService = AuthService(AuthorizationConfig(true, "drpc", "privPath", "pubPath"), rsaKeyReader, factory) + val pair = KeyReader.Keys(mock(PrivateKey::class.java), mock(PublicKey::class.java)) + + `when`(rsaKeyReader.getKeyPair("privPath", "pubPath")) + .thenReturn(pair) + `when`(mockV1Processor.process(pair, token)).thenReturn(tokenWrapper) + + authService.authenticate(token) + verify(rsaKeyReader).getKeyPair("privPath", "pubPath") + verify(mockV1Processor).process(pair, token) + assertTrue(AuthContext.sessions.containsKey(tokenWrapper.sessionId)) + } + + @Test + fun `parallel try to auth is successful`() { + val tokenWrapper = AuthContext.TokenWrapper( + "token", Instant.now(), "sessionId" + ) + val tokenWrapper1 = AuthContext.TokenWrapper( + "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) + + `when`(rsaKeyReader.getKeyPair("privPath", "pubPath")).thenReturn(pair) + `when`(mockV1Processor.process(pair, token)) + .thenReturn(tokenWrapper) + .thenReturn(tokenWrapper1) + + val task = Runnable { authService.authenticate(token) } + + CompletableFuture.allOf( + CompletableFuture.runAsync(task), CompletableFuture.runAsync(task) + ).join() + + verify(rsaKeyReader, times(2)).getKeyPair("privPath", "pubPath") + verify(mockV1Processor, times(2)).process(pair, token) + assertTrue(AuthContext.sessions.containsKey(tokenWrapper.sessionId)) + assertTrue(AuthContext.sessions.containsKey(tokenWrapper1.sessionId)) + } +} diff --git a/src/test/kotlin/io/emeraldpay/dshackle/auth/service/RsaKeyReaderTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/auth/service/RsaKeyReaderTest.kt new file mode 100644 index 00000000..5f2f0cb0 --- /dev/null +++ b/src/test/kotlin/io/emeraldpay/dshackle/auth/service/RsaKeyReaderTest.kt @@ -0,0 +1,69 @@ +package io.emeraldpay.dshackle.auth.service + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.springframework.util.ResourceUtils +import java.math.BigInteger +import java.security.interfaces.RSAPrivateKey +import java.security.interfaces.RSAPublicKey + +class RsaKeyReaderTest { + private val rsaKeyReader = RsaKeyReader() + + @Test + fun `read rsa keys`() { + val privPath = ResourceUtils.getFile("classpath:keys/priv.p8.key").path + val publicPath = ResourceUtils.getFile("classpath:keys/public-drpc.pem").path + + val pair = rsaKeyReader.getKeyPair(privPath, publicPath) + val privateKey = pair.providerPrivateKey + val publicKey = pair.externalPublicKey + + assertEquals("RSA", publicKey.algorithm) + assertEquals("RSA", privateKey.algorithm) + assertTrue(privateKey is RSAPrivateKey) + assertTrue(publicKey is RSAPublicKey) + assertTrue { + (publicKey as RSAPublicKey) + .run { + val pubExponent = BigInteger("65537") + val pubModulus = BigInteger( + "240721495118071395463408682378448427874712549978328672120875120395" + + "572530650526403138656868181996853461531503006596703750232991335166031612653183175739344513002793" + + "13348833980409910131805866311837600488070189526246817317791758685747539443624701130779843529258916" + + "650627570641542946773123818063304222185205897480168513227047010620610697362185901253798594085716467" + + "92700927695491574061920135297311257486773262784524717857554194798974994838378321973611865011031457" + + "8859834631052697211398759550437148562864384448640344034211059348047816397935347753413946137935402" + + "562264627177210569215727831448291731093608051334025332988696191" + ) + publicExponent == pubExponent && modulus == pubModulus + } + } + assertTrue { + (privateKey as RSAPrivateKey) + .run { + val privModulus = BigInteger( + "2832492054027911929186769713106999105750517776225145627897719982164048887089826085864021820" + + "93695282142899084731750598834879176944012613634073603363270499693159806868257221336886781437442" + + "7025894179697109350195776864078690751332857203663624730020413376539574511375327958483292435380" + + "2072047577218574632871804997852794886795318485439021501388394533101191305015940756335545719496" + + "04557847518214006893286271137213491059994279470633688916032797849945887259137876037521925382454" + + "762238089597912107394417801381736627011587603418078578130206830219321107525729614610300336784925" + + "6741484379041499791605663418628739419306248595872949" + ) + val privExponent = BigInteger( + "2704777724417882789147602456408780048462378556719441898242139736695157047955437327090684858" + + "9022442508913912020206014886681429805316382175637569992035330289450733224635356250217508515675" + + "1746547003301653260530619509144105809847739245799589488158044325999968951997941276374874356761" + + "7744591423894125208163912446945277960899800043458394321204754817284255735206518583285355872405" + + "3966258052672146605639969648388744960700258048094135244952249403403074548577386549705289154261" + + "2346391566526533514196717633725503640437930121313225102536030460301806443109607461162231878852" + + "64869837629637060803692577216204745443259318662678133769" + ) + + privateExponent == privExponent && modulus == privModulus + } + } + } +} diff --git a/src/test/resources/configs/auth-disabled.yaml b/src/test/resources/configs/auth-disabled.yaml new file mode 100644 index 00000000..1be81df3 --- /dev/null +++ b/src/test/resources/configs/auth-disabled.yaml @@ -0,0 +1,2 @@ +auth: + enabled: false \ No newline at end of file diff --git a/src/test/resources/configs/auth-with-wrong-priv-key.yaml b/src/test/resources/configs/auth-with-wrong-priv-key.yaml new file mode 100644 index 00000000..96ed5bb6 --- /dev/null +++ b/src/test/resources/configs/auth-with-wrong-priv-key.yaml @@ -0,0 +1,6 @@ +auth: + enabled: true + publicKeyOwner: drpc + keys: + provider-private-key: "classpath:keys/priv-wrong.p8.key" + external-public-key: "classpath:keys/pub-wrong.key" \ No newline at end of file diff --git a/src/test/resources/configs/auth-with-wrong-pub-key.yaml b/src/test/resources/configs/auth-with-wrong-pub-key.yaml new file mode 100644 index 00000000..daf1260f --- /dev/null +++ b/src/test/resources/configs/auth-with-wrong-pub-key.yaml @@ -0,0 +1,6 @@ +auth: + enabled: true + publicKeyOwner: drpc + keys: + provider-private-key: "classpath:keys/priv.p8.key" + external-public-key: "classpath:keys/pub-wrong.key" \ No newline at end of file diff --git a/src/test/resources/configs/auth-without-key-owner.yaml b/src/test/resources/configs/auth-without-key-owner.yaml new file mode 100644 index 00000000..847f3756 --- /dev/null +++ b/src/test/resources/configs/auth-without-key-owner.yaml @@ -0,0 +1,2 @@ +auth: + enabled: true \ No newline at end of file diff --git a/src/test/resources/configs/auth-without-key-pair.yaml b/src/test/resources/configs/auth-without-key-pair.yaml new file mode 100644 index 00000000..97bf98f3 --- /dev/null +++ b/src/test/resources/configs/auth-without-key-pair.yaml @@ -0,0 +1,3 @@ +auth: + enabled: true + publicKeyOwner: drpc \ No newline at end of file diff --git a/src/test/resources/configs/auth-without-private-key.yaml b/src/test/resources/configs/auth-without-private-key.yaml new file mode 100644 index 00000000..1becf7a2 --- /dev/null +++ b/src/test/resources/configs/auth-without-private-key.yaml @@ -0,0 +1,5 @@ +auth: + enabled: true + publicKeyOwner: drpc + keys: + external-public-key: /keys/pub.key \ No newline at end of file diff --git a/src/test/resources/configs/auth-without-public-key.yaml b/src/test/resources/configs/auth-without-public-key.yaml new file mode 100644 index 00000000..3f46575c --- /dev/null +++ b/src/test/resources/configs/auth-without-public-key.yaml @@ -0,0 +1,5 @@ +auth: + enabled: true + publicKeyOwner: drpc + keys: + provider-private-key: /keys/priv.p8.key \ No newline at end of file diff --git a/src/test/resources/configs/dshackle-full.yaml b/src/test/resources/configs/dshackle-full.yaml index a3a9ad8a..c1afdc70 100644 --- a/src/test/resources/configs/dshackle-full.yaml +++ b/src/test/resources/configs/dshackle-full.yaml @@ -11,6 +11,13 @@ tls: require: false ca: "/path/ca.dshackle.test.crt" +auth: + enabled: true + publicKeyOwner: drpc + keys: + provider-private-key: "classpath:keys/priv.p8.key" + external-public-key: "classpath:keys/public.pem" + cache: redis: enabled: true diff --git a/src/test/resources/keys/priv-drpc.p8.key b/src/test/resources/keys/priv-drpc.p8.key new file mode 100644 index 00000000..e8aac34a --- /dev/null +++ b/src/test/resources/keys/priv-drpc.p8.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC+sCrzY7pN4L3C +uPyV8Q5dFilQYuP8kAWF2X6nAz2WFzHAPQJQXkwUeFz0T1Py0TtwrX6RcYu1KOuC +Bj78KvtptThFxFk9T9I3mpi00ZoIGVGPniW/4lMdN2DYqsXF/2r7K6dmtA9JQKAl +RjQiIkclwIn2/JYIFWi5viaeBl1XLG7m06TdksGt5JJ/c6yk0cFn0jlFeVLZr087 +EWkj+3NN5+7dNHc+LCGunKHXX+cl+GHGc3JO6a953t6POhHXZh+/lX+WzD83uKUN +bYdCn0eyz2++6/lOhYR7x6Nc2d+ikEGzqGDnalD3bJPLV0hm5fXTUyd2zipPDtXc +R3UQsdZ/AgMBAAECggEBAJmDyEBDxGQYD40pUigAdhjY04/k3TiiVd/pNj6MrFMd +/y6Nr0Q2djdwox0IlEQC0ECpXCEJcXj6JYd7Mc/fZqI2g8QfC90BWvLq4g+IQuJM +R85AepGdSl5WDADCkxAQzfbBsTZMtZW6knMPrr7gK7kn5apStRJy8em/POWxqAmZ +1WLZP/CC2EBNk8zI+vJkZpWh4bbAXYalPBr+e+QXMdBJMlm8sO/cexqQSG3A2Igj +4DO16wxQn7ZahUF/caM4Y+v4LyO8fF75h+CLW8dZ5f344hI8QoEQWzbRjeDlgmie +BMkOAiB2Q6ejLO+3iPptVyTdFGrz5PkVfiHbVaD4A3kCgYEA9JwKDSBA03EyeIuf +PFkbcdiAOr8mSrtnfaFjfICOGYmiC/FQjVVa4l984J0OLRAIr+aEHe4zKY2wcWhZ +EaJGBaBAD7yEUR5nAMP+8zVnUNgVjIvvQuDhiVzPSl7qFj4QZOpztv2zBKmneBsW +HE2M5fxBcNxewSuPVPGzEh/p9/sCgYEAx5FWnAPu6SbCpXyK9JNty/Y9d+Ls+g/R +U9zZPAeuIFDclDEteP/GfE8fRkQ50IRRoxP0DUUDnNgHkqnCk+L/yB7lg4N9LlaK +9IF3o/LLuj40LwWZa8/E+72+16XtjuBCVNcglv9Ph/maGL0sYLXOYJUYMvslJm/4 +ITtZicoiwE0CgYAwN/5HPh9pTvwrBSL7q3kchRgp/HpY6v5opoLNDS513ErEXeqK +IdRLoZUlVfBwc6OywRc0KzuMqnCounAsaLey7jcSow/WSc72OKyuVs2qAx6kWQVu +QpRTFqeKOpGcltl2ez0aSoKanbL0mypNo//taj+gNuC65ZJYpViubvoZNwKBgQC7 +Eti3Y1B/ql/oNPklD1ZrTaaNTWGLf2xgSrQwe5qUnHhJSlgwBsQPHzRX7/iqKH9G +eZvbHIiobDGdIBlP/Ah9lcjcIVQlLecQEJUQULb9HPZZ5cvNrnQe792H1Pj9Z+eG +FCtuoGHzOt1it2J0Pbj5/Ik0sQxAHdHhhPsIGT2+OQKBgQDWcgBLTd1VNVR0iX74 +aJx5VhYCMHQ+bA8qI46tyDid/c5GibXuWf8kHUfqlfcsRzv+HGsGRvyyvYn5LimE +HB9U3xy0z5q5i+G/6iwftw+ObaVgVYr5D5ovl4DT56nlG8IOYVNA1T1Heewkk5jc +ZrLvcE6YXcAKE1y5RWaDprVmjw== +-----END PRIVATE KEY----- diff --git a/src/test/resources/keys/priv.p8.key b/src/test/resources/keys/priv.p8.key new file mode 100644 index 00000000..014b3ec9 --- /dev/null +++ b/src/test/resources/keys/priv.p8.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDgYGhG/i+93a7W +oLK7T4SQkPH1+q1UppWxlPvQ3FunlOx8iGkTMKYx+iJEUm3E36eX466gVJYJpGPp +n+oYQ0f2QDx4zcElcqTvJhu644BBp2b1qim4f+iaWdEWUmdgIEfoePHe9NkUyIxK +m/TOYwtLSa8Rb+Vs7XePDUi9F/t/aQrHe6AwII8vKxgdvHUsqL+8A9oHIAMFZ6gL +4tasbFJqCfGaMyphW2AOnVNnkDOvLAGi2C9q9cjWnHZRhaHO34pEFnP6Wpor7rau +xSqS3WYtM1/Uxff/l5JCMB9AyRofbZSNWpsZYUesubSghzRfxcZzzk76lXPeDKS+ +mb8P1Jy1AgMBAAECggEBANZCeZFBbWmLZUfC0KoZdudt4gAYTqhFIzEeqZB6vHM8 +vM72nBAdJrcWut4vMG8Ne8aJPtoPq+6tMlOHx01AAlneYWCiakxOO7260EQYtwL6 +zlDNG2X5jq7GEemU6u5aJRCTXbSgbyNvlrgwUQc8gS51wF2QUq8/3CmYb9tSKXEx +nRSvIT4RzMHxPfHgkSppSIJt4AQcof25f7ImVFNdv3rSlKCKhPTjMf87gBzOqlxg +xyAfIuxqpaDeY9rjywVahhthDDfRk7zt7m52huSIxg1oQuRKHY8iExCYXZgqc5Il +oelZN9ZleA+y+2LjSdpiU6yp63IUXo52246knPo7PAkCgYEA/S1E0ZDrc51trjcj +3KbHbq+O0w81WCjUzSWwtykVh1cyhZbwasqFUq2ydeYvpyq1z0sxbkUCjB9TV3G7 +LvQg+cUJmUgAvJEuP9BwCb2lwCfBnqVK33T1QnbG/AynPoey5SiyZ7/AnRcujGLo +OwsttnxPw7NM1Us27bn+bF9bSgMCgYEA4uDshYJbJa6xgqkvdlZvZZnmxxjq0HP8 +htR3hdh2/cMsDRr/gjUknQPsTLZKVxTQw+qJaYVBZpskWd0y5Bx5e+mhitHGr7z9 +5FLz2wrWuQBFw0Lh97zcPhAiupAlkrpW2VFzc3wQvVjiJv9oaxFX3zXnFXXFN668 +Px2K423JnOcCgYBP3LTFqoIrITDU8L3WP6HPT3+tIjIAkji2UbpRvJr261GbhEZo +WWP+9Z3CxQ6pG7boId8A2rH4A3WlstGJ+SZhw37IpEbfNKizJowA9prPZ3sTES7y +GBG5moAgR9mFxwZudQz53yniI8riK9z3qwoLc2Ex6WBGNAiqqq4P/3BrgwKBgEX2 +urmJM3ZxYsg6SCqkCQI57ZLkOjVCL1Oc3abm0/r5XvJDqxuKK9pHxWz6of4sqxxf +jTR/JWXw3crgjbsOlOADcg+PFUhIbbslYZHgy8qNLPZD/88X7IsvCqahoRSYZgPq +PEIWtkrNyB/ij17FIGXzB2n0wXakaeTExXnQ92VtAoGAcwA5881UZWvXEdz7hoOt +BPssHkyKEPKWVhyyuyhJ4M2c1//vCdQM53RiWuNo5aU3kztS5+o2bQYm9VkmRL0o +yjR+UP61ekyK8vd+c0hG5m++Rp6SqjLSj3b4Ln7jtarM9OdXD12l1qZetBrDXnFi +bd1Cq+3lNGpl9Ut/u5DbWgw= +-----END PRIVATE KEY----- diff --git a/src/test/resources/keys/public-drpc.pem b/src/test/resources/keys/public-drpc.pem new file mode 100644 index 00000000..74eae90d --- /dev/null +++ b/src/test/resources/keys/public-drpc.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvrAq82O6TeC9wrj8lfEO +XRYpUGLj/JAFhdl+pwM9lhcxwD0CUF5MFHhc9E9T8tE7cK1+kXGLtSjrggY+/Cr7 +abU4RcRZPU/SN5qYtNGaCBlRj54lv+JTHTdg2KrFxf9q+yunZrQPSUCgJUY0IiJH +JcCJ9vyWCBVoub4mngZdVyxu5tOk3ZLBreSSf3OspNHBZ9I5RXlS2a9POxFpI/tz +Tefu3TR3Piwhrpyh11/nJfhhxnNyTumved7ejzoR12Yfv5V/lsw/N7ilDW2HQp9H +ss9vvuv5ToWEe8ejXNnfopBBs6hg52pQ92yTy1dIZuX101Mnds4qTw7V3Ed1ELHW +fwIDAQAB +-----END PUBLIC KEY----- diff --git a/src/test/resources/keys/public.pem b/src/test/resources/keys/public.pem new file mode 100644 index 00000000..d51e24a1 --- /dev/null +++ b/src/test/resources/keys/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4GBoRv4vvd2u1qCyu0+E +kJDx9fqtVKaVsZT70Nxbp5TsfIhpEzCmMfoiRFJtxN+nl+OuoFSWCaRj6Z/qGENH +9kA8eM3BJXKk7yYbuuOAQadm9aopuH/omlnRFlJnYCBH6Hjx3vTZFMiMSpv0zmML +S0mvEW/lbO13jw1IvRf7f2kKx3ugMCCPLysYHbx1LKi/vAPaByADBWeoC+LWrGxS +agnxmjMqYVtgDp1TZ5AzrywBotgvavXI1px2UYWhzt+KRBZz+lqaK+62rsUqkt1m +LTNf1MX3/5eSQjAfQMkaH22UjVqbGWFHrLm0oIc0X8XGc85O+pVz3gykvpm/D9Sc +tQIDAQAB +-----END PUBLIC KEY----- diff --git a/src/test/resources/keys/wrong-public.pem b/src/test/resources/keys/wrong-public.pem new file mode 100644 index 00000000..5e1bd644 --- /dev/null +++ b/src/test/resources/keys/wrong-public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy93QvZ7evi8V9crEQzPf +NxTdIx0tvvwUcp85TsoNfHJLAD41yjJaVPwXk2mD6KuoV7hkZjoGu5ow5pPKZUKp +MzOXsfVIAvGf0xEowWaANZflrX87ZwsWLK+4mZHSCA92THQoGncBu3kUGFPjukVn +vlKUcX+4lizXGDztMD35KrPXBNXziOMSxxqnL7jlWeNYDxqmCmquwy+kqF/mCwr1 +n+k6GxF1H7plSGHNZalLRB6jJUQiC6EYDYl1hPv3EdPRPwjKXQ12J6LZ+XVdOLYC +s8Q1I1ajPsT6/rg5MjXn7paWc9GrBVG57lC7R0n9zSfo64ol3DuVQognOcU3MQT9 +rQIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file