1
1
package services
2
2
3
3
import java .io .{ ByteArrayInputStream , FileInputStream , InputStream , SequenceInputStream }
4
- import javax .inject .Inject
4
+ import javax .inject .{ Inject , Singleton }
5
5
6
6
import akka .actor .ActorSystem
7
7
import models .JsonFormat ._
8
8
import models ._
9
9
import org .apache .commons .codec .binary .{ Base64 , Base64InputStream }
10
+ import util .JsonConfig
10
11
import play .api .libs .json .{ Json , _ }
11
12
import play .api .{ Configuration , Logger }
12
13
@@ -15,8 +16,11 @@ import scala.concurrent.{ ExecutionContext, Future }
15
16
import scala .sys .process ._
16
17
import scala .util .{ Failure , Success , Try }
17
18
19
+ @ Singleton
18
20
class MispSrv (
19
- loaderCommandOption : Option [String ],
21
+ mispModulesEnabled : Boolean ,
22
+ loaderCommand : String ,
23
+ mispModuleConfig : JsObject ,
20
24
externalAnalyzerSrv : ExternalAnalyzerSrv ,
21
25
jobSrv : JobSrv ,
22
26
akkaSystem : ActorSystem ) {
@@ -26,33 +30,37 @@ class MispSrv(
26
30
externalAnalyzerSrv : ExternalAnalyzerSrv ,
27
31
jobSrv : JobSrv ,
28
32
akkaSystem : ActorSystem ) = this (
29
- configuration.getString(" misp.modules.loader" ),
33
+ configuration.getBoolean(" misp.modules.enabled" ).getOrElse(false ),
34
+ configuration.getString(" misp.modules.loader" ).get,
35
+ JsonConfig .configWrites.writes(configuration.getConfig(" misp.modules.config" ).getOrElse(Configuration .empty)),
30
36
externalAnalyzerSrv,
31
37
jobSrv,
32
38
akkaSystem)
33
39
34
40
private [MispSrv ] lazy val logger = Logger (getClass)
35
41
private [MispSrv ] lazy val analyzeExecutionContext : ExecutionContext = akkaSystem.dispatchers.lookup(" analyzer" )
36
42
37
- lazy val list : Seq [MispModule ] =
38
- loaderCommandOption.fold(Seq .empty[MispModule ]) { loaderCommand ⇒
39
- Json .parse(s " $loaderCommand --list " .!! )
40
- .as[Seq [String ]]
41
- .map { moduleName ⇒
42
- moduleName → (for {
43
- moduleInfo ← Try (Json .parse(s " $loaderCommand --info $moduleName" .!! ))
44
- module ← Try (moduleInfo.as[MispModule ](reads(loaderCommand)))
45
- } yield module)
46
- }
47
- .flatMap {
48
- case (moduleName, Failure (error)) ⇒
49
- logger.warn(s " Load MISP module $moduleName fails " , error)
50
- Nil
51
- case (_, Success (module)) ⇒
52
- logger.info(s " Register MISP module ${module.name} ${module.version}" )
53
- Seq (module)
54
- }
55
- }
43
+ logger.info(s " MISP modules is ${if (mispModulesEnabled) " enabled" else " disabled" }, loader is $loaderCommand" )
44
+
45
+ lazy val list : Seq [MispModule ] = if (mispModulesEnabled) {
46
+ Json .parse(s " $loaderCommand --list " .!! )
47
+ .as[Seq [String ]]
48
+ .map { moduleName ⇒
49
+ moduleName → (for {
50
+ moduleInfo ← Try (Json .parse(s " $loaderCommand --info $moduleName" .!! ))
51
+ module ← Try (moduleInfo.as[MispModule ](reads(loaderCommand, mispModuleConfig)))
52
+ } yield module)
53
+ }
54
+ .flatMap {
55
+ case (moduleName, Failure (error)) ⇒
56
+ logger.warn(s " Load MISP module $moduleName fails " , error)
57
+ Nil
58
+ case (_, Success (module)) ⇒
59
+ logger.info(s " Register MISP module ${module.name} ${module.version}" )
60
+ Seq (module)
61
+ }
62
+ }
63
+ else Nil
56
64
57
65
def get (moduleName : String ): Option [MispModule ] = list.find(_.name == moduleName)
58
66
@@ -89,30 +97,31 @@ class MispSrv(
89
97
}
90
98
91
99
def query (module : String , mispType : String , data : String )(implicit ec : ExecutionContext ): Future [JsObject ] = {
92
- loaderCommandOption
93
- .flatMap { loaderCommand ⇒
94
- val artifact = toArtifact(mispType, data)
95
- get(module)
96
- .map { mispModule ⇒
97
- val mispReport = Future {
98
- val input = Json .obj(mispType → data)
99
- val output = (s " $loaderCommand --run $module" #< input.toString).!!
100
- Json .parse(output).as[JsObject ]
101
- }
102
- jobSrv.create(mispModule, artifact, mispReport.map(toReport))
103
- mispReport
104
-
100
+ val artifact = toArtifact(mispType, data)
101
+ val mispModule = if (mispModulesEnabled) {
102
+ get(module)
103
+ .map { mispModule ⇒
104
+ val mispReport = Future {
105
+ val input = Json .obj(" config" → mispModule.config, mispType → data)
106
+ val output = (s " $loaderCommand --run $module" #< input.toString).!!
107
+ Json .parse(output).as[JsObject ]
105
108
}
106
- .orElse {
107
- externalAnalyzerSrv
108
- .get(module)
109
- .map { analyzer ⇒
110
- externalAnalyzerSrv.analyze(analyzer, artifact)
111
- .map { report ⇒ toMispOutput(report) }
112
- }
109
+ jobSrv.create(mispModule, artifact, mispReport.map(toReport))
110
+ mispReport
111
+
112
+ }
113
+ }
114
+ else None
115
+ mispModule
116
+ .orElse {
117
+ externalAnalyzerSrv
118
+ .get(module)
119
+ .map { analyzer ⇒
120
+ externalAnalyzerSrv.analyze(analyzer, artifact)
121
+ .map { report ⇒ toMispOutput(report) }
113
122
}
114
123
}
115
- .getOrElse(Future .failed(new Exception (s " Module $module not found " )))
124
+ .getOrElse(Future .failed(new Exception (s " Module $module not found " ))) // TODO add appropriate exception
116
125
}
117
126
118
127
def analyze (module : MispModule , artifact : Artifact ): Future [Report ] = {
@@ -121,10 +130,13 @@ class MispSrv(
121
130
122
131
val input = artifact match {
123
132
case DataArtifact (data, _) ⇒
124
- stringStream(Json .obj(dataType2mispType(artifact.dataType).head → data).toString)
133
+ val mispType = dataType2mispType(artifact.dataType)
134
+ .filter(module.inputAttributes.contains)
135
+ .head
136
+ stringStream((Json .obj(" config" → module.config) + (mispType → JsString (data))).toString)
125
137
case FileArtifact (data, _) ⇒
126
138
new SequenceInputStream (Iterator (
127
- stringStream(""" {" attachment":"""" ),
139
+ stringStream(Json .obj( " config " → module.config).toString.replaceFirst( " }$ " , """ ," attachment":"""" ) ),
128
140
new Base64InputStream (new FileInputStream (data), true ),
129
141
stringStream(" \" }" )).asJavaEnumeration)
130
142
}
@@ -207,15 +219,26 @@ class MispSrv(
207
219
else mispTypes
208
220
}
209
221
210
- private def reads (loaderCommand : String ): Reads [MispModule ] =
222
+ private def reads (loaderCommand : String , mispModuleConfig : JsObject ): Reads [MispModule ] =
211
223
for {
212
224
name ← (__ \ " name" ).read[String ]
213
- version ← (__ \ " meta" \ " version" ).read[String ]
214
- description ← (__ \ " meta" \ " description" ).read[String ]
215
- author ← (__ \ " meta" \ " author" ).read[String ]
216
- config ← (__ \ " meta" \ " config" ).read[Seq [String ]]
225
+ version ← (__ \ " moduleinfo" \ " version" ).read[String ]
226
+ description ← (__ \ " moduleinfo" \ " description" ).read[String ]
227
+ author ← (__ \ " moduleinfo" \ " author" ).read[String ]
228
+ config = (mispModuleConfig \ name).asOpt[JsObject ].getOrElse(JsObject (Nil ))
229
+ requiredConfig ← (__ \ " config" ).read[Set [String ]]
230
+ missingConfig = requiredConfig -- config.keys
231
+ _ ← if (missingConfig.nonEmpty) {
232
+ val message = s " MISP module $name is disabled because the following configuration " +
233
+ s " item ${if (missingConfig.size > 1 ) " s are" else " is" } missing: ${missingConfig.mkString(" , " )}"
234
+ logger.warn(message)
235
+ Reads [Unit ](_ ⇒ JsError (message))
236
+ }
237
+ else {
238
+ Reads [Unit ](_ ⇒ JsSuccess (()))
239
+ }
217
240
input ← (__ \ " mispattributes" \ " input" ).read[Seq [String ]]
218
- dataTypes = input.map(mispType2dataType)
241
+ dataTypes = input.map(mispType2dataType).distinct
219
242
} yield MispModule (name, version, description, author, dataTypes, input, config, loaderCommand)
220
243
221
244
private val typeLookup = Map (
0 commit comments