Skip to content

Commit bc5ba12

Browse files
committed
#237 Migrate alerts to store attachment in datastore
1 parent f32f78d commit bc5ba12

File tree

2 files changed

+77
-5
lines changed

2 files changed

+77
-5
lines changed

thehive-backend/app/models/Migration.scala

+72-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ package models
33
import java.util.Date
44
import javax.inject.Inject
55

6+
import akka.NotUsed
67
import akka.stream.Materializer
8+
import akka.stream.scaladsl.Source
79
import org.elastic4play.models.BaseModelDef
810
import org.elastic4play.services._
11+
import org.elastic4play.services.JsonFormat.attachmentFormat
912
import org.elastic4play.utils
1013
import org.elastic4play.utils.{ Hasher, RichJson }
11-
import play.api.{ Configuration, Logger }
1214
import play.api.libs.json.JsValue.jsValueToJsLookup
1315
import play.api.libs.json._
16+
import play.api.{ Configuration, Logger }
17+
import services.AlertSrv
1418

1519
import scala.collection.immutable.{ Set ISet }
1620
import scala.concurrent.{ ExecutionContext, Future }
@@ -21,6 +25,9 @@ case class UpdateMispAlertArtifact() extends EventMessage
2125

2226
class Migration(
2327
mispCaseTemplate: Option[String],
28+
mainHash: String,
29+
extraHashes: Seq[String],
30+
datastoreName: String,
2431
models: ISet[BaseModelDef],
2532
dblists: DBLists,
2633
eventSrv: EventSrv,
@@ -33,10 +40,17 @@ class Migration(
3340
eventSrv: EventSrv,
3441
ec: ExecutionContext,
3542
materializer: Materializer) = {
36-
this(configuration.getString("misp.caseTemplate"), models, dblists, eventSrv, ec, materializer)
43+
this(
44+
configuration.getString("misp.caseTemplate"),
45+
configuration.getString("datastore.hash.main").get,
46+
configuration.getStringSeq("datastore.hash.extra").get,
47+
configuration.getString("datastore.name").get,
48+
models, dblists,
49+
eventSrv, ec, materializer)
3750
}
3851

3952
import org.elastic4play.services.Operation._
53+
4054
val logger = Logger(getClass)
4155
private var requireUpdateMispAlertArtifact = false
4256

@@ -126,10 +140,65 @@ class Migration(
126140
"follow" (misp \ "follow").as[JsBoolean])
127141
},
128142
removeEntity("audit")(o (o \ "objectType").asOpt[String].contains("alert")))
129-
case DatabaseState(9) Nil
143+
case ds @ DatabaseState(9)
144+
object Base64 {
145+
def unapply(data: String): Option[Array[Byte]] = Try(java.util.Base64.getDecoder.decode(data)).toOption
146+
}
147+
148+
var dataIds = Set.empty[String]
149+
def containsOrAdd(id: String) = {
150+
dataIds.synchronized {
151+
if (dataIds.contains(id)) true
152+
else {
153+
dataIds = dataIds + id
154+
false
155+
}
156+
}
157+
}
158+
val mainHasher = Hasher(mainHash)
159+
val extraHashers = Hasher(mainHash +: extraHashes: _*)
160+
Seq(
161+
Operation((f: String Source[JsObject, NotUsed]) {
162+
case "alert" f("alert").flatMapConcat { alert
163+
val artifactsAndData = Future.traverse((alert \ "artifacts").asOpt[List[JsObject]].getOrElse(Nil)) { artifact
164+
(artifact \ "data").asOpt[String]
165+
.collect {
166+
case AlertSrv.dataExtractor(filename, contentType, data @ Base64(rawData))
167+
val attachmentId = mainHasher.fromByteArray(rawData).head.toString()
168+
ds.getEntity(datastoreName, s"${attachmentId}_0")
169+
.map(_ Nil)
170+
.recover {
171+
case _ if containsOrAdd(attachmentId) Nil
172+
case _
173+
Seq(Json.obj(
174+
"_type" datastoreName,
175+
"_id" s"${attachmentId}_0",
176+
"data" data))
177+
}
178+
.map { dataEntity
179+
val attachment = Attachment(filename, extraHashers.fromByteArray(rawData), rawData.length.toLong, contentType, attachmentId)
180+
(artifact - "data" + ("attachment" Json.toJson(attachment))) dataEntity
181+
}
182+
}
183+
.getOrElse(Future.successful(artifact Nil))
184+
}
185+
Source.fromFuture(artifactsAndData)
186+
.mapConcat { ad
187+
val updatedAlert = alert + ("artifacts" JsArray(ad.map(_._1)))
188+
updatedAlert :: ad.flatMap(_._2)
189+
}
190+
}
191+
case other f(other)
192+
}),
193+
mapAttribute("alert", "status") {
194+
case JsString("Update") JsString("Updated")
195+
case JsString("Ignore") JsString("Ignored")
196+
case other other
197+
})
130198
}
131199

132200
private val requestCounter = new java.util.concurrent.atomic.AtomicInteger(0)
201+
133202
def getRequestId: String = {
134203
utils.Instance.id + ":mig:" + requestCounter.incrementAndGet()
135204
}

thehive-backend/app/services/AlertSrv.scala

+5-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ trait AlertTransformer {
2828

2929
case class CaseSimilarity(caze: Case, similarIOCCount: Int, iocCount: Int, similarArtifactCount: Int, artifactCount: Int)
3030

31+
object AlertSrv {
32+
val dataExtractor = "^(.*);(.*);(.*)".r
33+
}
34+
3135
class AlertSrv(
3236
templates: Map[String, String],
3337
alertModel: AlertModel,
@@ -77,6 +81,7 @@ class AlertSrv(
7781
mat)
7882

7983
private[AlertSrv] lazy val logger = Logger(getClass)
84+
import AlertSrv._
8085

8186
def create(fields: Fields)(implicit authContext: AuthContext): Future[Alert] = {
8287

@@ -149,8 +154,6 @@ class AlertSrv(
149154
.recover { case _ None }
150155
}
151156

152-
private val dataExtractor = "^(.*);(.*);(.*)".r
153-
154157
def createCase(alert: Alert, customCaseTemplate: Option[String])(implicit authContext: AuthContext): Future[Case] = {
155158
alert.caze() match {
156159
case Some(id) caseSrv.get(id)

0 commit comments

Comments
 (0)