@@ -0,0 +1,53 @@
|
||||
package io.emeraldpay.dshackle.monitoring.accesslog
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.Global
|
||||
import io.emeraldpay.dshackle.config.AccessLogConfig
|
||||
import io.emeraldpay.dshackle.config.MainConfig
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
class AccessLogWriterSpec extends Specification {
|
||||
|
||||
def "writes log event"() {
|
||||
setup:
|
||||
File dir = File.createTempDir("dshackle-test-")
|
||||
File accessLog = new File(dir, "accesslog.jsonl")
|
||||
println("Write access log to $accessLog.absolutePath")
|
||||
MainConfig config = new MainConfig()
|
||||
config.accessLogConfig = new AccessLogConfig(true).tap {
|
||||
it.filename = accessLog.absolutePath
|
||||
}
|
||||
AccessLogWriter logWriter = new AccessLogWriter(config)
|
||||
|
||||
when:
|
||||
def event = new Events.Status(
|
||||
Chain.ETHEREUM, UUID.fromString("9d8ecbf3-12fb-49cf-af9d-949a1050a000"),
|
||||
new Events.StreamRequestDetails(
|
||||
UUID.fromString("9d8ecbf3-12fb-49cf-af9d-949a1050a000"),
|
||||
Instant.ofEpochMilli(1626746880123),
|
||||
new Events.Remote(
|
||||
["127.0.0.1", "172.217.8.78"], "172.217.8.78", "UnitTest"
|
||||
)
|
||||
)
|
||||
)
|
||||
logWriter.submit([event])
|
||||
logWriter.flush()
|
||||
def act = accessLog.readLines()
|
||||
then:
|
||||
act.size() == 1
|
||||
with(act[0]) {
|
||||
def json = Global.objectMapper.readValue(it, Map)
|
||||
json["version"] == "accesslog/v1beta"
|
||||
json["id"] == "9d8ecbf3-12fb-49cf-af9d-949a1050a000"
|
||||
json["method"] == "Status"
|
||||
json["blockchain"] == "ETHEREUM"
|
||||
json["request"]["start"] == "2021-07-20T02:08:00.123Z"
|
||||
json["request"]["id"] == "9d8ecbf3-12fb-49cf-af9d-949a1050a000"
|
||||
json["request"]["remote"]["ip"] == "172.217.8.78"
|
||||
json["request"]["remote"]["userAgent"] == "UnitTest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* 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.monitoring.accesslog
|
||||
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.grpc.Attributes
|
||||
import io.grpc.Grpc
|
||||
import io.grpc.Metadata
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.junit.validator.TestClassValidator
|
||||
import spock.lang.Specification
|
||||
|
||||
class EventsBaseBuilderSpec extends Specification {
|
||||
|
||||
class TestEvent extends Events.Base {
|
||||
Events.StreamRequestDetails request
|
||||
|
||||
TestEvent(Events.StreamRequestDetails request) {
|
||||
super(UUID.randomUUID(), "TEST")
|
||||
this.request = request
|
||||
}
|
||||
}
|
||||
|
||||
class TestEventBuilder extends EventsBuilder.Base<TestEventBuilder>
|
||||
implements EventsBuilder.RequestReply<TestEvent, BlockchainOuterClass.NativeCallRequest, BlockchainOuterClass.NativeCallReplyItem> {
|
||||
|
||||
@Override
|
||||
protected TestEventBuilder getT() {
|
||||
return this
|
||||
}
|
||||
|
||||
@Override
|
||||
void onRequest(BlockchainOuterClass.NativeCallRequest msg) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
TestEvent onReply(BlockchainOuterClass.NativeCallReplyItem msg) {
|
||||
return new TestEvent(requestDetails)
|
||||
}
|
||||
}
|
||||
|
||||
def "Parse headers from direct local access"() {
|
||||
setup:
|
||||
def metadata = new Metadata()
|
||||
metadata.put(Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER), "grpc-go/1.30.0")
|
||||
def attributes = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(Inet4Address.getByName("127.0.0.1"), 2448))
|
||||
.build()
|
||||
when:
|
||||
def act = new TestEventBuilder()
|
||||
.tap {
|
||||
it.start(metadata, attributes)
|
||||
it.onRequest(BlockchainOuterClass.NativeCallRequest.getDefaultInstance())
|
||||
}
|
||||
.onReply(BlockchainOuterClass.NativeCallReplyItem.getDefaultInstance())
|
||||
then:
|
||||
act.request != null
|
||||
act.request.remote != null
|
||||
with(act.request.remote) {
|
||||
ips == ["127.0.0.1"]
|
||||
userAgent == "grpc-go/1.30.0"
|
||||
ip == "127.0.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
def "Extracts real remote ip"() {
|
||||
setup:
|
||||
def metadata = new Metadata()
|
||||
metadata.put(Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER), "grpc-go/1.30.0")
|
||||
metadata.put(Metadata.Key.of("x-real-ip", Metadata.ASCII_STRING_MARSHALLER), "30.56.100.15")
|
||||
def attributes = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(Inet4Address.getByName("127.0.0.1"), 2448))
|
||||
.build()
|
||||
when:
|
||||
def act = new TestEventBuilder()
|
||||
.tap {
|
||||
it.start(metadata, attributes)
|
||||
it.onRequest(BlockchainOuterClass.NativeCallRequest.getDefaultInstance())
|
||||
}
|
||||
.onReply(BlockchainOuterClass.NativeCallReplyItem.getDefaultInstance())
|
||||
then:
|
||||
with(act.request.remote) {
|
||||
ips == ["30.56.100.15", "127.0.0.1"]
|
||||
ip == "30.56.100.15"
|
||||
}
|
||||
}
|
||||
|
||||
def "Ignores remote ip header if already connected from remote"() {
|
||||
setup:
|
||||
def metadata = new Metadata()
|
||||
metadata.put(Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER), "grpc-go/1.30.0")
|
||||
metadata.put(Metadata.Key.of("x-real-ip", Metadata.ASCII_STRING_MARSHALLER), "192.168.1.1")
|
||||
def attributes = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(Inet4Address.getByName("30.56.100.15"), 2448))
|
||||
.build()
|
||||
when:
|
||||
def act = new TestEventBuilder()
|
||||
.tap {
|
||||
it.start(metadata, attributes)
|
||||
it.onRequest(BlockchainOuterClass.NativeCallRequest.getDefaultInstance())
|
||||
}
|
||||
.onReply(BlockchainOuterClass.NativeCallReplyItem.getDefaultInstance())
|
||||
then:
|
||||
with(act.request.remote) {
|
||||
ips == ["192.168.1.1", "30.56.100.15"]
|
||||
userAgent == "grpc-go/1.30.0"
|
||||
ip == "30.56.100.15"
|
||||
}
|
||||
}
|
||||
|
||||
def "Ignores invalid ip header"() {
|
||||
setup:
|
||||
def metadata = new Metadata()
|
||||
metadata.put(Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER), "grpc-go/1.30.0")
|
||||
metadata.put(Metadata.Key.of("x-real-ip", Metadata.ASCII_STRING_MARSHALLER), "271.194.19.1")
|
||||
def attributes = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(Inet4Address.getByName("30.56.100.15"), 2448))
|
||||
.build()
|
||||
when:
|
||||
def act = new TestEventBuilder()
|
||||
.tap {
|
||||
it.start(metadata, attributes)
|
||||
it.onRequest(BlockchainOuterClass.NativeCallRequest.getDefaultInstance())
|
||||
}
|
||||
.onReply(BlockchainOuterClass.NativeCallReplyItem.getDefaultInstance())
|
||||
then:
|
||||
with(act.request.remote) {
|
||||
ips == ["30.56.100.15"]
|
||||
userAgent == "grpc-go/1.30.0"
|
||||
ip == "30.56.100.15"
|
||||
}
|
||||
}
|
||||
|
||||
def "Ignores host addr in ip header"() {
|
||||
setup:
|
||||
def metadata = new Metadata()
|
||||
metadata.put(Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER), "grpc-go/1.30.0")
|
||||
metadata.put(Metadata.Key.of("x-real-ip", Metadata.ASCII_STRING_MARSHALLER), "google.com")
|
||||
def attributes = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(Inet4Address.getByName("30.56.100.15"), 2448))
|
||||
.build()
|
||||
when:
|
||||
def act = new TestEventBuilder()
|
||||
.tap {
|
||||
it.start(metadata, attributes)
|
||||
it.onRequest(BlockchainOuterClass.NativeCallRequest.getDefaultInstance())
|
||||
}
|
||||
.onReply(BlockchainOuterClass.NativeCallReplyItem.getDefaultInstance())
|
||||
then:
|
||||
with(act.request.remote) {
|
||||
ips == ["30.56.100.15"]
|
||||
userAgent == "grpc-go/1.30.0"
|
||||
ip == "30.56.100.15"
|
||||
}
|
||||
}
|
||||
|
||||
def "Extracts ipv6 addresses"() {
|
||||
setup:
|
||||
def metadata = new Metadata()
|
||||
metadata.put(Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER), "grpc-go/1.30.0")
|
||||
metadata.put(Metadata.Key.of("x-real-ip", Metadata.ASCII_STRING_MARSHALLER), "2001:0db8:0000:0000:0000:ff00:0042:8329")
|
||||
def attributes = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(Inet6Address.getByName("::1"), 2448))
|
||||
.build()
|
||||
when:
|
||||
def act = new TestEventBuilder()
|
||||
.tap {
|
||||
it.start(metadata, attributes)
|
||||
it.onRequest(BlockchainOuterClass.NativeCallRequest.getDefaultInstance())
|
||||
}
|
||||
.onReply(BlockchainOuterClass.NativeCallReplyItem.getDefaultInstance())
|
||||
then:
|
||||
with(act.request.remote) {
|
||||
ips == ["2001:db8:0:0:0:ff00:42:8329", "0:0:0:0:0:0:0:1"]
|
||||
ip == "2001:db8:0:0:0:ff00:42:8329"
|
||||
}
|
||||
}
|
||||
|
||||
def "Cleans up user agent"() {
|
||||
setup:
|
||||
def metadata = new Metadata()
|
||||
metadata.put(Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER), "grpc-go/1.30.0\nxss\n\r")
|
||||
def attributes = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(Inet4Address.getByName("30.56.100.15"), 2448))
|
||||
.build()
|
||||
when:
|
||||
def act = new TestEventBuilder()
|
||||
.tap {
|
||||
it.start(metadata, attributes)
|
||||
it.onRequest(BlockchainOuterClass.NativeCallRequest.getDefaultInstance())
|
||||
}
|
||||
.onReply(BlockchainOuterClass.NativeCallReplyItem.getDefaultInstance())
|
||||
then:
|
||||
with(act.request.remote) {
|
||||
userAgent == "grpc-go/1.30.0 xss"
|
||||
}
|
||||
}
|
||||
|
||||
def "Truncates up user agent to 128 characters max"() {
|
||||
setup:
|
||||
def metadata = new Metadata()
|
||||
metadata.put(Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER),
|
||||
"0123456_1_0123456_2_0123456_3_0123456_4_0123456_5_0123456_6_0123456_7_0123456_8_0123456_9_0123456_0_0123456_1_0123456_2_0123456_3_0123456_4_0123456_5")
|
||||
def attributes = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(Inet4Address.getByName("30.56.100.15"), 2448))
|
||||
.build()
|
||||
when:
|
||||
def act = new TestEventBuilder()
|
||||
.tap {
|
||||
it.start(metadata, attributes)
|
||||
it.onRequest(BlockchainOuterClass.NativeCallRequest.getDefaultInstance())
|
||||
}
|
||||
.onReply(BlockchainOuterClass.NativeCallReplyItem.getDefaultInstance())
|
||||
then:
|
||||
with(act.request.remote) {
|
||||
userAgent.length() == 128
|
||||
userAgent == "0123456_1_0123456_2_0123456_3_0123456_4_0123456_5_0123456_6_0123456_7_0123456_8_0123456_9_0123456_0_0123456_1_0123456_2_0123456_"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package io.emeraldpay.dshackle.monitoring.accesslog
|
||||
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import spock.lang.Specification
|
||||
|
||||
class EventsBuilderSubscribeBalanceSpec extends Specification {
|
||||
|
||||
def "Basic ethereum event"() {
|
||||
setup:
|
||||
def request = BlockchainOuterClass.BalanceRequest.newBuilder()
|
||||
.setAddress(
|
||||
Common.AnyAddress.newBuilder()
|
||||
.setAddressSingle(
|
||||
Common.SingleAddress.newBuilder()
|
||||
.setAddress("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D")
|
||||
)
|
||||
)
|
||||
.setAsset(
|
||||
Common.Asset.newBuilder()
|
||||
.setChainValue(100)
|
||||
.setCode("ETHER")
|
||||
)
|
||||
.build()
|
||||
def resp = BlockchainOuterClass.AddressBalance.newBuilder()
|
||||
.setAddress(Common.SingleAddress.newBuilder()
|
||||
.setAddress("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"))
|
||||
.setAsset(Common.Asset.newBuilder()
|
||||
.setChainValue(100)
|
||||
.setCode("ETHER"))
|
||||
.setBalance("1234560000000000000")
|
||||
.build()
|
||||
when:
|
||||
def act = new EventsBuilder.SubscribeBalance(true).tap {
|
||||
it.onRequest(request)
|
||||
}.onReply(resp)
|
||||
then:
|
||||
act.index == 0
|
||||
act.blockchain == Chain.ETHEREUM
|
||||
act.balanceRequest.asset == "ETHER"
|
||||
act.balanceRequest.addressType == "ADDRESS_SINGLE"
|
||||
act.addressBalance.asset == "ETHER"
|
||||
act.addressBalance.address == "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
|
||||
}
|
||||
|
||||
def "Basic bitcoin event"() {
|
||||
setup:
|
||||
def request = BlockchainOuterClass.BalanceRequest.newBuilder()
|
||||
.setAddress(
|
||||
Common.AnyAddress.newBuilder()
|
||||
.setAddressSingle(
|
||||
Common.SingleAddress.newBuilder()
|
||||
.setAddress("1NDyJtNTjmwk5xPNhjgAMu4HDHigtobu1s")
|
||||
)
|
||||
)
|
||||
.setAsset(
|
||||
Common.Asset.newBuilder()
|
||||
.setChainValue(1)
|
||||
.setCode("BTC")
|
||||
)
|
||||
.build()
|
||||
def resp = BlockchainOuterClass.AddressBalance.newBuilder()
|
||||
.setAddress(Common.SingleAddress.newBuilder()
|
||||
.setAddress("1NDyJtNTjmwk5xPNhjgAMu4HDHigtobu1s"))
|
||||
.setAsset(Common.Asset.newBuilder()
|
||||
.setChainValue(1)
|
||||
.setCode("BTC"))
|
||||
.setBalance("12345600000000")
|
||||
.build()
|
||||
when:
|
||||
def act = new EventsBuilder.SubscribeBalance(true).tap {
|
||||
it.onRequest(request)
|
||||
}.onReply(resp)
|
||||
then:
|
||||
act.index == 0
|
||||
act.blockchain == Chain.BITCOIN
|
||||
act.balanceRequest.asset == "BTC"
|
||||
act.balanceRequest.addressType == "ADDRESS_SINGLE"
|
||||
act.addressBalance.asset == "BTC"
|
||||
act.addressBalance.address == "1NDyJtNTjmwk5xPNhjgAMu4HDHigtobu1s"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user