Hi! Now that my custom script language runs like I...
# scripting
o
Hi! Now that my custom script language runs like I want it, new problems arise. As the scripts are run on remote servers and can be triggered by remote calls using web endpoints and other things, I want to restrict the things my scripts have access to. Namely: If I want to NOT ALLOW my scripting language to have access to the system environment or file system - how can such things be disabled, hopefully using the
ScriptCompilationConfiguration
? Does anybody know how to do it? Thanks!
p
interesting. I checked what happens if you try to explore the host where play.kotlinlang.org runs on (I think there's AWS Lambda under the hood). Executing this code:
Copy code
import java.io.File

fun main() {
    println(File(".").absolutePath)
}
gives me
Copy code
Exception in thread "main" java.security.AccessControlException: Access control exception due to security reasons in web playground: 
 access denied ("java.util.PropertyPermission" "user.dir" "read")
 at java.lang.SecurityManager.checkPermission (:-1)
so I think JB uses Java's SecurityManager. I don't have experience with this, so just throwing it here as a likely hint
the same happens when trying to access any env var
it also makes more sense this way compared to configuring the compilation phase, because the Kotlin compiler shouldn't be aware of such runtime behavior
o
I was going into this direction, too. But it is somehow flawed in that the Java security manager really is a global thing so, doing anything to the security manager affects everything within the JVM, not only my small portion of customer script code. Which is not what I want or need... 😞 E.g. the application of course needs access to system environment at least at startup to parse a HOCON config file that might use env vars for secrets from Kubernetes... In my particular case, my DSL "compiles" to a KFunction. So, both at "compile phase" (aka running the Kotlin script) as well as at "execution phase" (aka running the produced KFunction) I need to disable access to the system, but not at any other point in time...
p
ah, got it
I don't have a better idea for now, although it's an interesting problem. My mind is trying to go into a direction where the component responsible for running the Kotlin script is really simple and well-separated, thus tinkering with it may cause no harm, and then it somehow (I have too little data to suggest something concrete) communicates with a more powerful component where you can do whatever you want
m
SecurityManager can be made per-thread, but it's deprecated and being removed from Java.
👆 1
The only supported way to do this is unfortunately to use kernel level sandboxing
one jvm per user
e
Ummm...maybe you could use a system call to 'chroot' just before compiling and executing the DSL. But after this point you cannot access the filesystem to compile other DSLs, only to the folder you specify in this call.
o
I also saw that it's marked as deprecated... but with no successor, right? This is really bad. The effort to run things in a separate JVM is horrible - all of the data that goes in and out needs to be somehow transported... And the KFunction my DSL creates - how could I call that in a separate JVM? Do I need to write its byte code to disk first? Gee.... it would be much easier if at compile (aka runtime) of my Kotlin script, I could disallow any use of java.lang.System and java.io.File classes...
Given that SecurityManager is deprecated, how will Kotlin Playground then tackle the very same problem?
@Olaf Gottschalk perhaps worth starting from the problem: could you share more details on what the DSL and the KFunction actually do? i.e. the business context
o
Business context is a DSL that allows to describe data transformation. So, a business transformation might look like this:
Copy code
jsonTransformation {
  set mandatory "name" type string map { it.lowercase() } from "NAME"
}
Given this transformation an incoming JSON
{"NAME":"Olaf"}
would be transformed to
{"name":"olaf"}
. Easy. When my DSL code runs, the result of executing the function
jsonTransformation(block: JsonTransformationDsl.()->Unit)
results in a callable of type
(Map<String, Any?>) -> Map<String, Any?>
(my transformation) that then will be applied (called) on many, many sets of JSON data. So, the business user has to define a transformation using the DSL above (which also supports a web interface). Because the DSL is plain Kotlin, it now also allows this hack:
Copy code
jsonTransformation {
  set mandatory "name" type string map { it.lowercase() } from "NAME"
  set mandatory "env" to java.lang.System.getenv()
}
To expose the complete environment which might contain secrets into every transformed JSON... Basically, within my DSL code that gets used, I need to restrict access to anything like
java.lang.System
or
java.io.File
. But I do not see how the
ScriptCompilationConfiguration
would allow this. A very very rudimentary approach I took was a hook on
beforeCompilation
that scans for forbidden keywords and then fails the compilation. But disallowing plain "System" in my code would be too oversimplified. It would not allow
Copy code
jsonTransformation {
  set mandatory "System" from "sys"
}
when it finds the word "System"... :-)
p
it's challenging to tackle this problem on the Kotlin level (in compilation time), I can think of e.g. the user adding a dependency on some library with
@file:DependsOn(...)
that can do whatever they want, so this would need to be disabled as well
o
Yes. Really challenging. And TBH I did not really want to go deep into security engineering. Sigh.
p
I think the use case of running some code remotely wasn't even considered by the Kotlin folks - Kotlin Scripting was meant to create simple scripts locally, or perhaps scripting hosts that run locally as well (= smaller blast radius in case of malicious actors)
m
It may be easier to use JS for this and compile the snippets using Kotlin/JS
👍 1
Run them with GraalJS for instance
p
wouldn't it still give access to the file system via Node's API? or am I missing something?
o
@mikehearn I think it's not feasible to use Kotlin/JS - the performance hit might be huge. I am within a JVM and I need fast byte code that transforms data as fast as possible...
I did not know that Java (JVM's) security system is so flawed... 😞
1
m
@Piotr Krzemiński No, GraalJS out of the box gives you like a browser would, no API calls
👍 1
@Olaf Gottschalk GraalJS is pretty fast. It runs on the JVM and JIT compiles the same way the JVM does.
Java team have given up on in-process security because of spectre and other problems, the SecurityManager infrastructure was not widely used and hard to maintain 😞 Unfortunately, process level sandboxing isn't easy to use.
o
Do you know how I can switch do use Kotlin/JS within a Java process? I don't seem to find this in
ScriptCompilationConfiguration
...
p
Kotlin scripting doesn't support JS, you'd have to use the embeddable Kotlin compiler directly (and include your DSL in the classpath). It's not that hard, see e.g. https://github.com/typesafegithub/github-workflows-kt/blob/52701fc95016d449a150a68[…]/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt - the
compileBinding
function accepts a list of source files and produces a path where .class files are put (it's ZIPped to create a JAR in a different place). I guess what you'd have to do is to just use the JS target to produce a JS file, and pass it to GraalJS this way: https://www.graalvm.org/javascript/#getting-started (haven't played with this yet, so just linking to their docs)
the question then is how to bridge the two worlds, the transformation running on GraalJS and the rest of the app which is JVM. Maybe Mike has some experience
m
That's pretty easy. The GraalVM docs show you how. You can cast javascript objects to java and back again for example.
👍 1
p
just for completeness, in your case you'd have to use
K2JSCompiler
class and an associated
K2JSCompilerArguments
o
This is all a very, very big change for my purpose including lots of new things to program and learn. So, I for now need to stick to SecurityManager, just like Kotlin Playground does. Any chance to see the source code for Kotlin Playground to see just how they did it? It is very complicated to even decide WHICH files need to be allowed for read (classpath is not enough...) and how to really decide what file has nothing to do with running the code itself...
👍 1
p
Regarding how Kotlin Playground does it, try looking here https://github.com/JetBrains/kotlin-compiler-server and if you don't find it, ask on #C02B3PECK6E
o
Thanks. This project is really awkward. No documentation, little code comments, and it does not build when I want to build it. 😞 The nightmare. So far, I have not yet understood how this works and how this pulls the trick to use the deprecated SecurityManager. Maybe somebody can hint me into understanding this project? I tried to build the docker version using the shell script supplied, but it cannot download the Gradle binaries, probably due to me behind a proxy and unclear where to set what. Normal settings are all there, but I fear that within the docker build, my normal Gradle settings are not effective... something like that?
p
best to ask the JetBrains folks how they use it 🤷 as I wrote, I'd do it on #C02B3PECK6E
h
In theory, you could also use R8/Proguard and add a „compile time“ check to forbid any blacklisted apis. But transforming the byte code takes some time (but compiling too). Or use ASM directly at runtime.
p
FWIW, there's a somewhat similar discussion on Reddit, at least some challenges are like in this Slack thread: https://www.reddit.com/r/Kotlin/comments/1gtzdev/evaluation_language_in_kotlin/