Skip to content

Commit a0a7f0b

Browse files
committed
Replace pseudofunction mechanism with JS sandboxing
1 parent ccbd1f6 commit a0a7f0b

File tree

13 files changed

+75
-187
lines changed

13 files changed

+75
-187
lines changed

backend/mockingbird-api/src/main/scala/ru/tinkoff/tcb/mockingbird/Mockingbird.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import scalapb.zio_grpc.Server
55
import scalapb.zio_grpc.ServerLayer
66
import scalapb.zio_grpc.ServiceList
77
import scalapb.zio_grpc.server.ZServerCallHandler
8-
98
import com.mongodb.ConnectionString
109
import io.grpc.ServerBuilder
1110
import io.grpc.Status
@@ -18,7 +17,6 @@ import sttp.client3.asynchttpclient.zio.AsyncHttpClientZioBackend
1817
import tofu.logging.Logging
1918
import tofu.logging.impl.ZUniversalLogging
2019
import zio.managed.*
21-
2220
import ru.tinkoff.tcb.mockingbird.api.AdminApiHandler
2321
import ru.tinkoff.tcb.mockingbird.api.AdminHttp
2422
import ru.tinkoff.tcb.mockingbird.api.MetricsHttp
@@ -48,6 +46,8 @@ import ru.tinkoff.tcb.mockingbird.stream.EphemeralCleaner
4846
import ru.tinkoff.tcb.mockingbird.stream.EventSpawner
4947
import ru.tinkoff.tcb.mockingbird.stream.SDFetcher
5048
import ru.tinkoff.tcb.utils.metrics.makeRegistry
49+
import ru.tinkoff.tcb.utils.resource.readStr
50+
import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox
5151

5252
object Mockingbird extends scala.App {
5353
type FL = WLD & ServerConfig & PublicHttp & EventSpawner & ResourceManager & EphemeralCleaner & GrpcRequestHandler
@@ -122,6 +122,7 @@ object Mockingbird extends scala.App {
122122
)
123123
} yield scopedBackend
124124
},
125+
(ZLayer.service[ServerConfig].project(_.sandbox) ++ ZLayer.fromZIO(ZIO.attempt(readStr("prelude.js")).map(Option(_)))) >>> GraalJsSandbox.live,
125126
mongoLayer,
126127
aesEncoder,
127128
collection(_.stub) >>> HttpStubDAOImpl.live,
@@ -166,10 +167,12 @@ object Mockingbird extends scala.App {
166167
.exec(bytes)
167168
.provideSome[RequestContext](
168169
Tracing.live,
170+
MockingbirdConfiguration.server,
169171
MockingbirdConfiguration.mongo,
170172
mongoLayer,
171173
collection(_.state) >>> PersistentStateDAOImpl.live,
172174
collection(_.grpcStub) >>> GrpcStubDAOImpl.live,
175+
(ZLayer.service[ServerConfig].project(_.sandbox) ++ ZLayer.fromZIO(ZIO.attempt(readStr("prelude.js")).map(Option(_)))) >>> GraalJsSandbox.live,
173176
GrpcStubResolverImpl.live,
174177
GrpcRequestHandlerImpl.live
175178
)

backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/api/PublicApiHandler.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package ru.tinkoff.tcb.mockingbird.api
33
import scala.concurrent.duration.FiniteDuration
44
import scala.util.control.NonFatal
55
import scala.xml.Node
6-
76
import io.circe.Json
87
import io.circe.parser.parse
98
import io.circe.syntax.*
@@ -13,7 +12,6 @@ import sttp.client3.*
1312
import sttp.client3.circe.*
1413
import sttp.model.Method
1514
import zio.interop.catz.core.*
16-
1715
import ru.tinkoff.tcb.criteria.*
1816
import ru.tinkoff.tcb.criteria.Typed.*
1917
import ru.tinkoff.tcb.logging.MDCLogging
@@ -37,6 +35,7 @@ import ru.tinkoff.tcb.mockingbird.scenario.CallbackEngine
3735
import ru.tinkoff.tcb.mockingbird.scenario.ScenarioEngine
3836
import ru.tinkoff.tcb.utils.circe.optics.JsonOptic
3937
import ru.tinkoff.tcb.utils.regex.*
38+
import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox
4039
import ru.tinkoff.tcb.utils.transformation.json.*
4140
import ru.tinkoff.tcb.utils.transformation.string.*
4241
import ru.tinkoff.tcb.utils.transformation.xml.*
@@ -50,6 +49,7 @@ final class PublicApiHandler(
5049
stateDAO: PersistentStateDAO[Task],
5150
resolver: StubResolver,
5251
engine: CallbackEngine,
52+
implicit val jsSandbox: GraalJsSandbox,
5353
private val httpBackend: SttpBackend[Task, ?],
5454
proxyConfig: ProxyConfig
5555
) {
@@ -243,8 +243,9 @@ object PublicApiHandler {
243243
ssd <- ZIO.service[PersistentStateDAO[Task]]
244244
resolver <- ZIO.service[StubResolver]
245245
engine <- ZIO.service[ScenarioEngine]
246+
jsSandbox <- ZIO.service[GraalJsSandbox]
246247
sttpClient <- ZIO.service[SttpBackend[Task, Any]]
247248
proxyCfg <- ZIO.service[ProxyConfig]
248-
} yield new PublicApiHandler(hsd, ssd, resolver, engine, sttpClient, proxyCfg)
249+
} yield new PublicApiHandler(hsd, ssd, resolver, engine, jsSandbox, sttpClient, proxyCfg)
249250
}
250251
}

backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/config/Configuration.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import net.ceedubs.ficus.Ficus.*
88
import net.ceedubs.ficus.readers.ArbitraryTypeReader.*
99
import net.ceedubs.ficus.readers.EnumerationReader.*
1010

11-
case class ServerConfig(interface: String, port: Int, allowedOrigins: Seq[String])
11+
case class JsSandboxConfig(allowedClasses: Set[String] = Set())
12+
13+
case class ServerConfig(interface: String, port: Int, allowedOrigins: Seq[String], sandbox: JsSandboxConfig)
1214

1315
case class SecurityConfig(secret: String)
1416

backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/grpc/GrpcRequestHandler.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ package ru.tinkoff.tcb.mockingbird.grpc
33
import scalapb.zio_grpc.RequestContext
44
import scalapb.zio_grpc.ZManagedChannel
55
import scalapb.zio_grpc.client.ClientCalls
6-
76
import io.circe.Json
87
import io.circe.syntax.KeyOps
98
import io.grpc.CallOptions
109
import io.grpc.ManagedChannelBuilder
1110
import zio.Duration
12-
1311
import ru.tinkoff.tcb.mockingbird.api.Tracing
1412
import ru.tinkoff.tcb.mockingbird.api.WLD
1513
import ru.tinkoff.tcb.mockingbird.error.StubSearchError
@@ -18,13 +16,14 @@ import ru.tinkoff.tcb.mockingbird.model.FillResponse
1816
import ru.tinkoff.tcb.mockingbird.model.GProxyResponse
1917
import ru.tinkoff.tcb.mockingbird.model.PersistentState
2018
import ru.tinkoff.tcb.mockingbird.model.Scope
19+
import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox
2120
import ru.tinkoff.tcb.utils.transformation.json.*
2221

2322
trait GrpcRequestHandler {
2423
def exec(bytes: Array[Byte]): RIO[WLD & RequestContext, Array[Byte]]
2524
}
2625

27-
class GrpcRequestHandlerImpl(stubResolver: GrpcStubResolver) extends GrpcRequestHandler {
26+
class GrpcRequestHandlerImpl(stubResolver: GrpcStubResolver, implicit val jsSandbox: GraalJsSandbox) extends GrpcRequestHandler {
2827
override def exec(bytes: Array[Byte]): RIO[WLD & RequestContext, Array[Byte]] =
2928
for {
3029
context <- ZIO.service[RequestContext]
@@ -86,7 +85,7 @@ class GrpcRequestHandlerImpl(stubResolver: GrpcStubResolver) extends GrpcRequest
8685
}
8786

8887
object GrpcRequestHandlerImpl {
89-
val live: URLayer[GrpcStubResolver, GrpcRequestHandler] = ZLayer.fromFunction(new GrpcRequestHandlerImpl(_))
88+
val live: URLayer[GrpcStubResolver & GraalJsSandbox, GrpcRequestHandlerImpl] = ZLayer.fromFunction(new GrpcRequestHandlerImpl(_, _))
9089
}
9190

9291
object GrpcRequestHandler {

backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/scenario/ScenarioEngine.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package ru.tinkoff.tcb.mockingbird.scenario
22

33
import java.nio.charset.Charset
44
import java.util.Base64
5-
65
import io.circe.Json
76
import io.circe.syntax.*
87
import kantan.xpath.Node as KNode
@@ -13,7 +12,6 @@ import sttp.client3.*
1312
import sttp.client3.circe.*
1413
import sttp.model.Method
1514
import zio.interop.catz.core.*
16-
1715
import ru.tinkoff.tcb.criteria.*
1816
import ru.tinkoff.tcb.criteria.Typed.*
1917
import ru.tinkoff.tcb.logging.MDCLogging
@@ -42,6 +40,7 @@ import ru.tinkoff.tcb.mockingbird.model.XMLCallbackRequest
4240
import ru.tinkoff.tcb.mockingbird.model.XmlOutput
4341
import ru.tinkoff.tcb.mockingbird.stream.SDFetcher
4442
import ru.tinkoff.tcb.utils.id.SID
43+
import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox
4544
import ru.tinkoff.tcb.utils.transformation.json.*
4645
import ru.tinkoff.tcb.utils.transformation.string.*
4746
import ru.tinkoff.tcb.utils.transformation.xml.*
@@ -62,6 +61,7 @@ final class ScenarioEngine(
6261
stateDAO: PersistentStateDAO[Task],
6362
resolver: ScenarioResolver,
6463
fetcher: SDFetcher,
64+
implicit val jsSandbox: GraalJsSandbox,
6565
private val httpBackend: SttpBackend[Task, ?]
6666
) extends CallbackEngine {
6767
private val log = MDCLogging.`for`[WLD](this)
@@ -217,7 +217,8 @@ object ScenarioEngine {
217217
psd <- ZIO.service[PersistentStateDAO[Task]]
218218
resolver <- ZIO.service[ScenarioResolver]
219219
fetcher <- ZIO.service[SDFetcher]
220+
jsSandbox <- ZIO.service[GraalJsSandbox]
220221
sttpClient <- ZIO.service[SttpBackend[Task, Any]]
221-
} yield new ScenarioEngine(sd, psd, resolver, fetcher, sttpClient)
222+
} yield new ScenarioEngine(sd, psd, resolver, fetcher, jsSandbox, sttpClient)
222223
}
223224
}

backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/sandboxing/ClassAccessRule.scala

Lines changed: 0 additions & 13 deletions
This file was deleted.

backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/sandboxing/GraalJsSandbox.scala

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,22 @@ import scala.reflect.ClassTag
44
import scala.reflect.classTag
55
import scala.util.Try
66
import scala.util.Using
7-
87
import org.graalvm.polyglot.*
9-
10-
import ru.tinkoff.tcb.utils.instances.predicate.or.*
8+
import ru.tinkoff.tcb.mockingbird.config.JsSandboxConfig
119

1210
class GraalJsSandbox(
13-
classAccessRules: List[ClassAccessRule] = GraalJsSandbox.DefaultAccess,
11+
jsSandboxConfig: JsSandboxConfig,
1412
prelude: Option[String] = None
1513
) {
16-
private val accessRule = classAccessRules.asInstanceOf[List[String => Boolean]].combineAll
17-
14+
private val allowedClasses = GraalJsSandbox.DefaultAccess ++ jsSandboxConfig.allowedClasses
1815
private val preludeSource = prelude.map(Source.create("js", _))
1916

2017
def eval[T: ClassTag](code: String, environment: Map[String, Any] = Map.empty): Try[T] =
2118
Using(
2219
Context
2320
.newBuilder("js")
2421
.allowHostAccess(HostAccess.ALL)
25-
.allowHostClassLookup((t: String) => accessRule(t))
22+
.allowHostClassLookup((t: String) => allowedClasses(t))
2623
.option("engine.WarnInterpreterOnly", "false")
2724
.build()
2825
) { context =>
@@ -36,24 +33,31 @@ class GraalJsSandbox(
3633
}
3734

3835
object GraalJsSandbox {
39-
val DefaultAccess: List[ClassAccessRule] = List(
40-
ClassAccessRule.Exact("java.lang.Byte"),
41-
ClassAccessRule.Exact("java.lang.Boolean"),
42-
ClassAccessRule.Exact("java.lang.Double"),
43-
ClassAccessRule.Exact("java.lang.Float"),
44-
ClassAccessRule.Exact("java.lang.Integer"),
45-
ClassAccessRule.Exact("java.lang.Long"),
46-
ClassAccessRule.Exact("java.lang.Math"),
47-
ClassAccessRule.Exact("java.lang.Short"),
48-
ClassAccessRule.Exact("java.lang.String"),
49-
ClassAccessRule.Exact("java.math.BigDecimal"),
50-
ClassAccessRule.Exact("java.math.BigInteger"),
51-
ClassAccessRule.Exact("java.time.LocalDate"),
52-
ClassAccessRule.Exact("java.time.LocalDateTime"),
53-
ClassAccessRule.Exact("java.time.format.DateTimeFormatter"),
54-
ClassAccessRule.Exact("java.util.List"),
55-
ClassAccessRule.Exact("java.util.Map"),
56-
ClassAccessRule.Exact("java.util.Random"),
57-
ClassAccessRule.Exact("java.util.Set")
36+
val live: URLayer[Option[String] & JsSandboxConfig, GraalJsSandbox] = ZLayer {
37+
for {
38+
sandboxConfig <- ZIO.service[JsSandboxConfig]
39+
prelude <- ZIO.service[Option[String]]
40+
} yield new GraalJsSandbox(sandboxConfig, prelude)
41+
}
42+
43+
val DefaultAccess: Set[String] = Set(
44+
"java.lang.Byte",
45+
"java.lang.Boolean",
46+
"java.lang.Double",
47+
"java.lang.Float",
48+
"java.lang.Integer",
49+
"java.lang.Long",
50+
"java.lang.Math",
51+
"java.lang.Short",
52+
"java.lang.String",
53+
"java.math.BigDecimal",
54+
"java.math.BigInteger",
55+
"java.time.LocalDate",
56+
"java.time.LocalDateTime",
57+
"java.time.format.DateTimeFormatter",
58+
"java.util.List",
59+
"java.util.Map",
60+
"java.util.Random",
61+
"java.util.Set"
5862
)
5963
}

backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/json/js_eval/package.scala

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ru.tinkoff.tcb.utils.transformation.json
22

3-
import java.lang.Boolean as JBoolean
3+
import java.math as jm
4+
import java.lang as jl
45
import scala.jdk.CollectionConverters.*
56

67
import io.circe.Json
@@ -11,7 +12,7 @@ package object js_eval {
1112
val circe2js: Json.Folder[AnyRef] = new Json.Folder[AnyRef] {
1213
override def onNull: AnyRef = null
1314

14-
override def onBoolean(value: Boolean): AnyRef = JBoolean.valueOf(value)
15+
override def onBoolean(value: Boolean): AnyRef = jl.Boolean.valueOf(value)
1516

1617
override def onNumber(value: JsonNumber): AnyRef = value.toBigDecimal.map(_.bigDecimal).orNull
1718

@@ -25,4 +26,12 @@ package object js_eval {
2526
.toMap
2627
.asJava
2728
}
29+
30+
val fold2Json: PartialFunction[AnyRef, Json] = {
31+
case b: jl.Boolean => Json.fromBoolean(b)
32+
case s: String => Json.fromString(s)
33+
case bd: jm.BigDecimal => Json.fromBigDecimal(bd)
34+
case i: jl.Integer => Json.fromInt(i.intValue())
35+
case l: jl.Long => Json.fromLong(l.longValue())
36+
}
2837
}

backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/json/package.scala

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package ru.tinkoff.tcb.utils.transformation
22

3-
import java.lang as jl
4-
import java.math as jm
53
import scala.util.Failure
64
import scala.util.Success
75
import scala.util.control.TailCalls
@@ -10,14 +8,14 @@ import scala.util.control.TailCalls.TailRec
108
import io.circe.Json
119
import io.circe.JsonNumber as JNumber
1210
import kantan.xpath.*
13-
import mouse.boolean.*
1411

1512
import ru.tinkoff.tcb.utils.circe.*
1613
import ru.tinkoff.tcb.utils.circe.optics.JsonOptic
1714
import ru.tinkoff.tcb.utils.json.json2StringFolder
1815
import ru.tinkoff.tcb.utils.regex.OneOrMore
1916
import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox
2017
import ru.tinkoff.tcb.utils.transformation.xml.nodeTemplater
18+
import ru.tinkoff.tcb.utils.transformation.json.js_eval.fold2Json
2119

2220
package object json {
2321
private val JORx = """\$([\:~])?\{([\p{L}\d\.\[\]\-_]+)\}""".r
@@ -81,39 +79,14 @@ package object json {
8179
}.result
8280
}
8381

84-
def eval: Json =
85-
transformValues { case js @ JsonString(str) =>
86-
str
87-
.foldTemplate(
88-
Json.fromString,
89-
Json.fromInt,
90-
Json.fromLong
91-
)
92-
.orElse {
93-
FunRx
94-
.findFirstIn(str)
95-
.isDefined
96-
.option(
97-
FunRx
98-
.replaceSomeIn(str, m => m.matched.foldTemplate(identity, _.toString(), _.toString()))
99-
)
100-
.map(Json.fromString)
101-
}
102-
.getOrElse(js)
103-
}.result
104-
105-
def eval2(implicit sandbox: GraalJsSandbox): Json =
82+
def eval(implicit sandbox: GraalJsSandbox): Json =
10683
transformValues {
10784
case js @ JsonString(CodeRx(code)) =>
10885
(sandbox.eval[AnyRef](code) match {
109-
case Success(str: String) => Option(Json.fromString(str))
110-
case Success(bd: jm.BigDecimal) => Option(Json.fromBigDecimal(bd))
111-
case Success(i: jl.Integer) => Option(Json.fromInt(i.intValue()))
112-
case Success(l: jl.Long) => Option(Json.fromLong(l.longValue()))
86+
case Success(value) if fold2Json.isDefinedAt(value) => Option(fold2Json(value))
11387
case Success(other) => throw new Exception(s"${other.getClass.getCanonicalName}: $other")
11488
case Failure(exception) => throw exception
11589
}).getOrElse(js)
116-
case JsonString(other) => throw new Exception(other)
11790
}.result
11891

11992
def patch(values: Json, schema: Map[JsonOptic, String]): Json =

0 commit comments

Comments
 (0)