diff --git a/app/org/thp/cortex/services/CustomWSAPI.scala b/app/org/thp/cortex/services/CustomWSAPI.scala new file mode 100644 index 000000000..9452066cf --- /dev/null +++ b/app/org/thp/cortex/services/CustomWSAPI.scala @@ -0,0 +1,118 @@ +package org.thp.cortex.services + +import scala.util.control.NonFatal + +import javax.inject.{Inject, Singleton} +import play.api.inject.ApplicationLifecycle +import play.api.libs.ws._ +import play.api.libs.ws.ahc.{AhcWSClient, AhcWSClientConfig, AhcWSClientConfigParser} +import play.api.{Configuration, Environment, Logger} + +import akka.stream.Materializer +import com.typesafe.sslconfig.ssl.TrustStoreConfig + +object CustomWSAPI { + private[CustomWSAPI] lazy val logger = Logger(getClass) + + def parseWSConfig(config: Configuration): AhcWSClientConfig = + new AhcWSClientConfigParser(new WSConfigParser(config.underlying, getClass.getClassLoader).parse(), config.underlying, getClass.getClassLoader) + .parse() + + def parseProxyConfig(config: Configuration): Option[WSProxyServer] = + config.getOptional[Configuration]("play.ws.proxy").map { proxyConfig ⇒ + DefaultWSProxyServer( + proxyConfig.get[String]("host"), + proxyConfig.get[Int]("port"), + proxyConfig.getOptional[String]("protocol"), + proxyConfig.getOptional[String]("user"), + proxyConfig.getOptional[String]("password"), + proxyConfig.getOptional[String]("ntlmDomain"), + proxyConfig.getOptional[String]("encoding"), + proxyConfig.getOptional[Seq[String]]("nonProxyHosts") + ) + } + + def getWS(config: Configuration)(implicit mat: Materializer): AhcWSClient = { + val clientConfig = parseWSConfig(config) + val clientConfigWithTruststore = config.getOptional[String]("play.cert") match { + case Some(p) ⇒ + logger.warn("""Use of "cert" parameter in configuration file is deprecated. Please use: + | ws.ssl { + | trustManager = { + | stores = [ + | { type = "PEM", path = "/path/to/cacert.crt" }, + | { type = "JKS", path = "/path/to/truststore.jks" } + | ] + | } + | } + """.stripMargin) + clientConfig.copy( + wsClientConfig = clientConfig + .wsClientConfig + .copy( + ssl = clientConfig + .wsClientConfig + .ssl + .withTrustManagerConfig( + clientConfig + .wsClientConfig + .ssl + .trustManagerConfig + .withTrustStoreConfigs( + clientConfig.wsClientConfig.ssl.trustManagerConfig.trustStoreConfigs :+ TrustStoreConfig( + filePath = Some(p), + data = None + ) + ) + ) + ) + ) + case None ⇒ clientConfig + } + AhcWSClient(clientConfigWithTruststore, None) + } + + def getConfig(config: Configuration, path: String): Configuration = + Configuration( + config + .getOptional[Configuration](s"play.$path") + .getOrElse(Configuration.empty) + .underlying + .withFallback(config.getOptional[Configuration](path).getOrElse(Configuration.empty).underlying) + ) +} + +@Singleton +class CustomWSAPI( + ws: AhcWSClient, + val proxy: Option[WSProxyServer], + config: Configuration, + environment: Environment, + lifecycle: ApplicationLifecycle, + mat: Materializer + ) extends WSClient { + private[CustomWSAPI] lazy val logger = Logger(getClass) + + @Inject() def this(config: Configuration, environment: Environment, lifecycle: ApplicationLifecycle, mat: Materializer) = + this(CustomWSAPI.getWS(config)(mat), CustomWSAPI.parseProxyConfig(config), config, environment, lifecycle, mat) + + override def close(): Unit = ws.close() + + override def url(url: String): WSRequest = { + val req = ws.url(url) + proxy.fold(req)(req.withProxyServer) + } + + override def underlying[T]: T = ws.underlying[T] + + def withConfig(subConfig: Configuration): CustomWSAPI = { + logger.debug(s"Override WS configuration using $subConfig") + try { + new CustomWSAPI(Configuration(subConfig.underlying.atKey("play").withFallback(config.underlying)), environment, lifecycle, mat) + } catch { + case NonFatal(e) ⇒ + logger.error(s"WSAPI configuration error, use default values", e) + this + } + } +} diff --git a/app/org/thp/cortex/services/WorkerSrv.scala b/app/org/thp/cortex/services/WorkerSrv.scala index b76fb9b51..690a99e41 100644 --- a/app/org/thp/cortex/services/WorkerSrv.scala +++ b/app/org/thp/cortex/services/WorkerSrv.scala @@ -9,7 +9,6 @@ import scala.io.Codec import scala.util.{Failure, Success, Try} import play.api.libs.json.{JsArray, JsObject, JsString, Json} -import play.api.libs.ws.WSClient import play.api.{Configuration, Logger} import akka.NotUsed @@ -37,7 +36,7 @@ class WorkerSrv @Inject() ( updateSrv: UpdateSrv, deleteSrv: DeleteSrv, findSrv: FindSrv, - ws: WSClient, + ws: CustomWSAPI, implicit val ec: ExecutionContext, implicit val mat: Materializer ) { diff --git a/app/org/thp/cortex/services/mappers/GroupUserMapper.scala b/app/org/thp/cortex/services/mappers/GroupUserMapper.scala index 993f64834..7af37d489 100644 --- a/app/org/thp/cortex/services/mappers/GroupUserMapper.scala +++ b/app/org/thp/cortex/services/mappers/GroupUserMapper.scala @@ -14,7 +14,6 @@ import org.elastic4play.controllers.Fields class GroupUserMapper( loginAttrName: String, nameAttrName: String, - rolesAttrName: Option[String], groupAttrName: String, organizationAttrName: Option[String], defaultRoles: Seq[String], @@ -29,7 +28,6 @@ class GroupUserMapper( this( configuration.getOptional[String]("auth.sso.attributes.login").getOrElse("login"), configuration.getOptional[String]("auth.sso.attributes.name").getOrElse("name"), - configuration.getOptional[String]("auth.sso.attributes.roles"), configuration.getOptional[String]("auth.sso.attributes.groups").getOrElse(""), configuration.getOptional[String]("auth.sso.attributes.organization"), configuration.getOptional[Seq[String]]("auth.sso.defaultRoles").getOrElse(Seq()), diff --git a/conf/application.sample b/conf/application.sample index d17916102..beb335ab4 100644 --- a/conf/application.sample +++ b/conf/application.sample @@ -113,7 +113,7 @@ auth { # URL of the authorization server #clientId = "client-id" #clientSecret = "client-secret" - #redirectUri = "https://my-thehive-instance.example/index.html#!/login" + #redirectUri = "https://my-cortex-instance.example/api/ssoLogin" #responseType = "code" #grantType = "authorization_code" @@ -124,8 +124,6 @@ auth { # The endpoint from which to obtain user details using the OAuth token, after successful login #userUrl = "https://auth-site.com/api/User" #scope = "openid profile" - # Type of authorization header - #authorizationHeader = "Bearer" # or token } # Single-Sign On @@ -139,22 +137,16 @@ auth { # Autologin user using SSO? #autologin = false - # Attributes mappings + # Name of mapping class from user resource to backend user ('simple' or 'group') + #mapper = group #attributes { - # login = "login" + # login = "user" # name = "name" # groups = "groups" - # roles = "roles" # list of roles, separated with comma - # organisation = "org" + # organization = "org" #} - - # Name of mapping class from user resource to backend user ('simple' or 'group') - #mapper = group - # Default roles for users with no groups mapped ("read", "analyze", "orgadmin") - #defaultRoles = [] - # Default organization - #defaultOrganization = "MyOrga" - + #defaultRoles = ["read"] + #defaultOrganization = "csirt" #groups { # # URL to retreive groups (leave empty if you are using OIDC) # #url = "https://auth-site.com/api/Groups" @@ -165,6 +157,16 @@ auth { # reader-profile-name = ["read"] # } #} + + #mapper = simple + #attributes { + # login = "user" + # name = "name" + # roles = "roles" + # organization = "org" + #} + #defaultRoles = ["read"] + #defaultOrganization = "csirt" } } @@ -211,4 +213,10 @@ responder { } } +# Proxy configuration to retrieve catalogs +# play.ws.proxy { +# host = proxy.example.com +# port = 3128 +# } + # It's the end my friend. Happy hunting!