Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 276 additions & 0 deletions ANY_BANK_ROLE_REMOVAL_PLAN.md

Large diffs are not rendered by default.

412 changes: 412 additions & 0 deletions DYNAMIC_ENTITY_SPACE_MODEL_PLAN.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/MAKER_CHECKER_DYNAMIC_CODE_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The technical sandbox no longer provides a meaningful second line of defence:
enforces nothing (`DynamicUtil.scala:253-259` logs this).
- The GraalVM JavaScript context is created with `HostAccess.ALL`, `PolyglotAccess.ALL` and
unrestricted host class lookup (`DynamicUtil.scala:486-529`).
- The dependency validator (`dynamic_code_compile_validate_enable`) is off by default and only
- The OBP call allowlist (`dynamic_code_obp_calls_are_restricted`) is off by default and only
blocks reflection and `ExecutionContext`; it has no notion of file, network or process access.

So a single holder of `CanCreateDynamicResourceDoc` or `CanCreateConnectorMethod` has remote code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5472,14 +5472,12 @@ Roles follow a consistent naming pattern:
- CanGetBankLevelDynamicEndpoints
- CanUpdateBankLevelDynamicEndpoint
- CanDeleteBankLevelDynamicEndpoint
- CanCreateSystemLevelDynamicEntity
- CanGetSystemLevelDynamicEntities
- CanUpdateSystemLevelDynamicEntity
- CanDeleteSystemLevelDynamicEntity
- CanCreateBankLevelDynamicEntity
- CanGetBankLevelDynamicEntities
- CanUpdateBankLevelDynamicEntity
- CanDeleteBankLevelDynamicEntity
- CanCreateDynamicEntityDefinition
- CanGetDynamicEntityDefinitions
- CanUpdateDynamicEntityDefinition
- CanDeleteDynamicEntityDefinition
- CanBackupDynamicEntityDefinition
- CanDeleteCascadeDynamicEntityDefinition
- CanCreateDynamicResourceDoc
- CanGetDynamicResourceDoc
- CanGetAllDynamicResourceDocs
Expand Down
52 changes: 29 additions & 23 deletions obp-api/src/main/resources/props/sample.props.template
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,7 @@ create_just_in_time_entitlements=false
# This speeds up the process of granting of roles. Certain roles are excluded from this automation:
# - CanCreateEntitlementAtOneBank
# - CanCreateEntitlementAtAnyBank
# Nothing is granted this way in the system space (the space whose bank id is the literal SYS): a Role there is always granted by hand.
# If create_just_in_time_entitlements is again set to false after it was true for a while, any auto granted Entitlements to roles are kept in place.
# Note: In the entitlements model we set createdbyprocess="create_just_in_time_entitlements". For manual operations we set createdbyprocess="manual"
# -------------------------------------------------------------
Expand Down Expand Up @@ -1776,29 +1777,34 @@ personal_data_collection_consent_country_waiver_list = Austria, Belgium, Bulgari
# it must be set to true explicitly, there is no run-mode-based fallback.
allow_user_generated_scala_code=false

# enable dynamic code sandbox, default is false, this will make sandbox works for code running in Future, will make performance lower than disable
dynamic_code_sandbox_enable=false
# Here is the default permissions if you set the dynamic_code_sandbox_enable = true. If you need more permission need to add it manually here.
# Better search for comment code `val allowedRuntimePermissions = List[Permission]( ....` need to provide the fully qualified class name and proper parameters.
dynamic_code_sandbox_permissions=[\
new java.net.NetPermission("specifyStreamHandler"),\
new java.lang.reflect.ReflectPermission("suppressAccessChecks"),\
new java.lang.RuntimePermission("getenv.*"),\
new java.util.PropertyPermission("cglib.useCache", "read"),\
new java.util.PropertyPermission("net.sf.cglib.test.stressHashCodes", "read"),\
new java.util.PropertyPermission("cglib.debugLocation", "read"),\
new java.lang.RuntimePermission("accessDeclaredMembers"),\
new java.lang.RuntimePermission("getClassLoader")\
]


# enable dynamic code compile validation, default is false, if set it to true, it will validate all the dynamic method body when you create/update any
# dynamic scala method. Note, it only check all the obp code dependents for all the method in OBP code.
dynamic_code_compile_validate_enable=false
# The default support dependencies if set dynamic_code_compile_validate_enable = true. it can be the class level or the method level,
# you can add them in the following list. Better check search for comment code: val allowedCompilationMethods: Map[String, Set[String]] = Map( ...
# need to prepare the correct OBP scala code.
dynamic_code_compile_validate_dependencies=[\
# NOTE: dynamic_code_sandbox_enable and dynamic_code_sandbox_permissions were removed.
# They wrapped dynamic code in AccessController.doPrivileged with a restricted permission
# set, which stopped being enforceable when SecurityManager was removed in JDK 24 (JEP 486);
# OBP requires JVM 25, so the sandbox could never restrict file, network or reflection
# access while still costing a privileged wrapper on every dynamic call. Dynamic code runs
# with the full privileges of the OBP process. The controls that do work are
# allow_user_generated_scala_code (below), dynamic_code_compile_validate_enable and
# dynamic_code_requires_approval.


# When true, dynamic code may call ONLY the OBP methods listed in
# dynamic_code_allowed_obp_methods below; creating or updating a body that calls anything
# else is rejected with OBP-40047 naming the offending method. It is an allowlist on OBP's
# own API surface -- NOT a sandbox: it does not restrict file, network or reflection access,
# and it does not check general scala/java library calls.
#
# Renamed from dynamic_code_compile_validate_enable, which read as "check that it compiles".
# The old name is still honoured with a deprecation warning at boot.
#
# Defaults to false, so dynamic code may call any OBP method. Only relevant once
# allow_user_generated_scala_code is true.
dynamic_code_obp_calls_are_restricted=false
# The allowlist used when the above is true: typeName -> allowed methods ("*" for all).
# Renamed from dynamic_code_compile_validate_dependencies. Search the code for
# `val allowedCompilationMethods` for how it is parsed; entries must be valid OBP scala.
# NOTE: this default list allows no data access, so an endpoint that reads dynamic entities
# (e.g. code.DynamicData.DynamicDataProvider) must be added here before it can be created.
dynamic_code_allowed_obp_methods=[\
NewStyle.function.getClass.getTypeName -> "*",\
CompiledObjects.getClass.getTypeName -> "sandbox",\
HttpCode.getClass.getTypeName -> "200",\
Expand Down
3 changes: 0 additions & 3 deletions obp-api/src/main/scala/bootstrap/liftweb/Boot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,6 @@ class Boot extends MdcLoggable {
logger.info("Current Project TimeZone: " + TimeZone.getDefault)


// set dynamic_code_sandbox_enable to System.properties, so com.openbankproject.commons.ExecutionContext can read this value
APIUtil.getPropsValue("dynamic_code_sandbox_enable")
.foreach(it => System.setProperty("dynamic_code_sandbox_enable", it))
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4736,7 +4736,7 @@ object SwaggerDefinitionsJSON {
consent_request_id = None,
valid_from = Some(new Date()),
time_to_live = Some(3600),
my_resources = Some(code.api.v6_0_0.PostConsentMyResourcesJson(Some(List(code.api.v6_0_0.PostConsentPersonalDynamicEntityJson("", "FooBar", List("read", "write"))))))
my_resources = Some(code.api.v6_0_0.PostConsentMyResourcesJson(Some(List(code.api.v6_0_0.PostConsentPersonalDynamicEntityJson("SYS", "FooBar", List("read", "write"))))))
)

lazy val consentsJsonV310 = ConsentsJsonV310(List(consentJsonV310))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import org.http4s.{HttpRoutes, Request, Response}
* `code.api.dynamic.endpoint.helper.DynamicEndpoints.CompiledObjects` / `DynamicCompileEndpoint`).
* The doc's auth/validation chain (`ResourceDoc.authCheckIO`, the native mirror of
* `wrappedWithAuthCheck`) runs first, then the handler runs inside the dynamic-code security
* sandbox (`Sandbox.runInSandboxIO`, applied inside the compiled handler).
* body forcing / early-return recovery (`DynamicCodeBody.force`, inside the compiled handler).
*
* Piece B is tried first; a non-match falls through to Piece C; a non-match there returns
* `OptionT.none`, so the request falls through the Http4sApp chain (the Lift bridge produces the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import org.json4s._
import scala.language.implicitConversions
import cats.effect.IO
import code.api.util.APIUtil.{Http4sEndpointIO, OBPReturnType}
import code.api.util.DynamicUtil.{Sandbox, Validation}
import code.api.util.DynamicUtil.{DynamicCodeBody, Validation}
import code.api.util.{CallContext, CustomJsonFormats, DynamicUtil}
import org.http4s.{Request, Response}

Expand All @@ -49,9 +49,6 @@ import org.http4s.{Request, Response}
trait DynamicCompileEndpoint {
implicit val formats = CustomJsonFormats.formats

// * is any bankId
val boundBankId: String

protected def process(callContext: CallContext, request: Request[IO], pathParams: Map[String, String]): IO[Response[IO]]

val endpoint: Http4sEndpointIO = new Http4sEndpointIO {
Expand All @@ -62,7 +59,7 @@ trait DynamicCompileEndpoint {

validateDependencies()

Sandbox.sandbox(boundBankId).runInSandboxIO {
DynamicCodeBody.force {
process(cc, request, pathParams)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import org.json4s._
import cats.effect.IO
import code.api.dynamic.endpoint.helper.practise.{DynamicEndpointCodeGenerator, PractiseEndpointGroup}
import code.api.dynamic.endpoint.helper.practise.PractiseEndpointGroup
import code.api.util.DynamicUtil.{Sandbox, Validation}
import code.api.util.DynamicUtil.{DynamicCodeBody, Validation}
import code.api.util.APIUtil.{BooleanBody, DoubleBody, EmptyBody, LongBody, Http4sEndpointIO, PrimaryDataBody, ResourceDoc, StringBody, getDisabledEndpointOperationIds}
import code.api.util.{CallContext, DynamicUtil}
import net.liftweb.common.{Box, Failure, Full}
Expand Down Expand Up @@ -248,28 +248,21 @@ case class CompiledObjects(exampleRequestBody: Option[JValue], successResponseBo
CompiledObjects.compileProblems(exampleRequestBody, successResponseBody, methodBody)

/**
* This is used to check the security permission at the run time.
* all the obp partialFunctions will be wrapped into the sandbox which under the permission control.
*
* Wraps the compiled partial function as an endpoint. This used to bind a per-bank
* security sandbox; that sandbox could not be enforced on JDK 24+ and has been
* removed (see DynamicUtil.DynamicCodeBody), so what remains is forcing the body and
* recovering an early `return` from user code.
*/
def sandboxEndpoint(bankId: Option[String]) : Http4sEndpointIO = {
val sandbox = bankId match {
case Some(v) if StringUtils.isNotBlank(v) =>
Sandbox.sandbox(v)
case _ => Sandbox.sandbox("*")
}

def compiledEndpoint() : Http4sEndpointIO =
new Http4sEndpointIO {
override def isDefinedAt(req: Request[IO]): Boolean = partialFunction.isDefinedAt(req)

// run dynamic code in sandbox
override def apply(req: Request[IO]): CallContext => IO[Response[IO]] = { cc =>
val fn = partialFunction.apply(req)

sandbox.runInSandboxIO(fn(cc))
DynamicCodeBody.force(fn(cc))
}
}
}

private def toCaseObject(jValue: Option[JValue]): Product = CompiledObjects.toCaseObject(jValue)
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ object DynamicResourceDocsEndpointGroup extends EndpointGroup with code.util.Hel
ResourceDoc(
// partialFunction is a no-op stub — the runtime dispatch uses the native handler in
// dynamicHttp4sFunction (the compiled artifact is OBPEndpointIO, not the Lift OBPEndpoint).
dynamicHttp4sFunction = Some(compiledObjects.sandboxEndpoint(dynamicDoc.bankId)),
dynamicHttp4sFunction = Some(compiledObjects.compiledEndpoint()),
implementedInApiVersion = apiVersion,
partialFunctionName = dynamicDoc.partialFunctionName + "_" + (dynamicDoc.requestVerb + dynamicDoc.requestUrl).hashCode,
requestVerb = dynamicDoc.requestVerb,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ object PractiseEndpoint extends DynamicCompileEndpoint {
case class ResponseRootJsonClass(my_user_id: String, name: String, age: Long, hobby: List[String])

// * is any bankId, if bound to other bankId, just modify this value to correct one
override val boundBankId = "*"

// copy the whole method body as "dynamicResourceDoc" method body
override protected def
Expand Down
Loading
Loading