Skip to content

Commit

Permalink
Merge branch 'release/1.1.0'
Browse files Browse the repository at this point in the history
Conflicts:
	README.md
	ui/app/views/components/app-container.component.html
  • Loading branch information
To-om committed May 12, 2017
2 parents f111cb0 + 8921d15 commit fd785bf
Show file tree
Hide file tree
Showing 82 changed files with 3,073 additions and 603 deletions.
19 changes: 17 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
# Change Log

## [1.0.2](https://github.com/CERT-BDF/Cortex/tree/1.0.2) (2017-04-18)
## [1.1.0](https://github.com/CERT-BDF/Cortex/tree/1.1.0) (2017-05-12)

[Full Changelog](https://github.com/CERT-BDF/Cortex/compare/1.0.2...1.1.0)

**Implemented enhancements:**

- Add support to .deb and .rpm package generation [\#20](https://github.com/CERT-BDF/Cortex/issues/20)
- Display analyzers metadata [\#18](https://github.com/CERT-BDF/Cortex/issues/18)
- MISP integration feature request [\#21](https://github.com/CERT-BDF/Cortex/issues/21)

**Closed issues:**

- Display Cortex version on the footer [\#23](https://github.com/CERT-BDF/Cortex/issues/23)
- Use new logo and favicon [\#22](https://github.com/CERT-BDF/Cortex/issues/22)
- Scala code cleanup [\#19](https://github.com/CERT-BDF/Cortex/issues/19)

## [1.0.2](https://github.com/CERT-BDF/Cortex/tree/1.0.2) (2017-04-19)
[Full Changelog](https://github.com/CERT-BDF/Cortex/compare/1.0.1...1.0.2)

**Fixed bugs:**
Expand Down Expand Up @@ -29,4 +44,4 @@
## [1.0.0](https://github.com/CERT-BDF/Cortex/tree/1.0.0) (2017-02-01)


\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
52 changes: 52 additions & 0 deletions PGP-PUBLIC-KEY
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1

mQINBFkRxeYBEAC8N672/5USJztb1R4pn8zB2/fujg71uAh1ZEERbiMFHC0IJbuh
8aF0F+tn1YIbMuI8B1byMOLhSRnnuw630StbnUsVqRbLXg3plLDAg50I1v90pXfO
FiOOG1XApnb8NhS+huGwIXXLk65NE1MPwhPC9jxK1Bx/RJZNKg9UDz5+YPPEydhh
ZJ+35UKb94DuY/YsqMCSYJwlHL3ZnqENcQvylVD9TSdJCJ51xu655kbbZks1Amaz
j7aWJQs8ur9nhEw/au221rUdOrS0XW5m4XCfE7hTqjREjDEGsUHilbsO+RH2f2+h
PWalG/9aN2f1maiAezAuT/agFBWbuyYqBfSvuZOMCuLdXsiQNxdPJPcEQUIcO1Qf
g4RTWJCoXrlL2QQSSstswPrlZxloHsGvl0AtAlard2+rr6Fz1QG3hokOhFav8LS+
0lbwQpR1K3RY2HUrof1r4HCQHqEJ76q0bwk9vhd8cAA9tc5uTh+oBbKXaPyO/8Be
SUiBU94kJd12RR81FIF4bAcqgrTi8JDj+EaXeGH5b6eUiRmEWCSzQpq6nOYMYBQw
4r62v4zYYq0NgM6f8rZ+EeuvCiGxSGAzvtLtKRYZRYHZanasO/pyVy6cpRxiWVRR
5+smEarOU8ul1o8D19si/sjFAs9nQMs20R+O76wHDb32aAKTjiRWkTUAVQARAQAB
tENUaGVIaXZlIFByb2plY3QgKFRoZUhpdmUgcmVsZWFzZSBrZXkpIDxzdXBwb3J0
QHRoZWhpdmUtcHJvamVjdC5vcmc+iQI4BBMBAgAiBQJZEcXmAhsDBgsJCAcDAgYV
CAIJCgsEFgIDAQIeAQIXgAAKCRA9mbsYViy8HHcvD/9yZvUvNGDJ//8frjwuRvWa
3jUxoX6m+KcUSHOf1wc6SyVZ/E0AJKDXMkYBJAjv5FvJOu//XGcMNwiNtR8gtZnd
razokSbUcg8osijPWsNFQ5te7wMqo34M3GP2rpXz+QC6UMd2/UDk6mdWtgqzTIzi
MsXm4A9p5So54Twkl32ZIfCG952rpQdr5lTq4FM5usZhtQzpOtm240ChOozU8SBE
CKH49I4iNBiFUNCHNivmGEA8CpP6ouCJaAmMF6cruyxRsZnwImU1SvjXUJYGdBQ8
u5pEvSQLhCwo12o7dATUmBMLNW05ksewDYVOj52lMg29TAZ+e0dBKCzMyz6+HpIk
6XJ0NZFWwLgzex863KI7S/ytErUKSjUhwHfoWhEkLvpUtuhHRbv5Ryyg7da7bp+B
4Q3MJbAzgOvZMXPlJpu4luIB5eEwRYyYQ3W7f1dSSklkaFuKh1rc0EACREJ7Y2e4
Vt8++zRbp1oFGQ5+0s64HlEBiaujc7F2/BRFR1MKE9Fd6WH2YljkQ0DidY7IWdTK
FxN7YPM9jeF51FP+W0I7PvK415W66UF60CDBtPL0wE7cQuujB856IDwZKPKi5Uro
ivsVewKw/nN4T92Xk87sdnYDBvQ++nQmg5SDTET+IJ6cP9OzDQ3ecmgSSAPw02jT
pl/GX01j04rUUOOpEaXttrkCDQRZEcXmARAAqgHpWyaPlD0mdVLnXMg9cxVQyR8X
VTQ7dmszgsRNtGdr60T6FZ5ORb5EowkBHKe3vfyUtrYmDhyFr0sikitYxIjHAIdn
EFgHWRHBPcc/fwt8omPyVtnNcVTJ/p27t01hwBzGNVvDbI6Lqj1X6MmWY8tphJu6
Ox0GozJZU8J3FCor0BqI/yeR4X3rFRfiZfz2Ejc/6tktfyKlSyPS8tNkPn0CulNa
TpYAhZ3S4coGlaC1MQcRRbGjuMRXZozLxYVDF3AvGXuwAtH53nly+skIG9Exk9UW
jfy6SHlKjq9UW5kSeFE4uRJkJBQeqXg/rO337rquJkUobiizHnepRtnjHxnWpHNs
jiZbEYgbHJBqNhuf1qaTVdup8mTOSVzUynr1aNHrQCmHkVR/P/fWOF09R9p+lRCp
I3yZwXbACvFeScidMtm1T8aFItmcQ+QXtyrRPvOvy8jCO/gkDpKm/NE5f3nT4Tg6
DOKtEQlo657FyG3t7STZ6H7uD2dd2TTWxelCaO9GaR743ybJYb5H02V9Dm3qIYFb
1cafz8Q43kAqcq29/Sgpx8yHv2SmsFilAfuW2ic0Mi6DbRUiF7p1qKLIj+ZsP3Ax
Mo1FwZI4zHlKKwm2Uapyqmd+xF9y7Y2pFgNYfDuj6/nd0VPFZ1T+6+9RjPWT7TPB
1+Eaf/o3/v31YZ8AEQEAAYkCHwQYAQIACQUCWRHF5gIbDAAKCRA9mbsYViy8HBEZ
EACFmsm9jPNFkjw7w6XC1+V4lY9HUQagwwf41XcYWt5gYlJww2oO+PzG9MIfj+25
mrmkKoVbNZhJmjH1ZgME5aIDbw+gSn3tsuBKriuryPS9aKjfDDpN2SmxO3N+m+uR
OxwoFvzzQUeba8ItdED4ATUj5qBDcwdTBZWrPDlC/Pr1ASY4NrG6f5uSUyTNwaR+
l1kKAGZxDm3/8tkI9UAmvSC2i0yxuOyHj9rpuz57aAKAvMu3vNGd7eU4bLPCuZFu
FhZvU6wAd/1+oLIVYUVjF1Nh3RgF+mn2MzH5AvShIpZzOkajY91ebQYAZ55AU2iU
y3C1sjSRa1lWzxbThJBT5Sm1B35vTABaM1m25HcjumKeU2XtfN4EArlxuxZRHSbQ
2ceWDPm5TuvjMyZdb5u90xpJ/VGmFYxqjfR1nwku9mY1JKDvZgI9i3Gm+JgrDI4c
ldFFLTSdSASnOfW1P+flMKUkDOBIIOYJtZgZ9BU9U/lU5/ZssPoCWkfwy022anqu
wR6Z0Ao/cKE+l+XoXFOvtcRvJyfarXs9EON0s/HNyPRpy20KPyv2lCwm11bhGoI3
LGYf9udLuLm2Dex9U4+Hs2KWDT9QeVmS7OQUz0Yg184CkVWGLqaVIReYv9q7qPlI
CykvrXDiG+C5OTn1wvPRUX1wA1f7dRsYXOK3AHxz65zMSw==
=f0Hb
-----END PGP PUBLIC KEY BLOCK-----
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
[![Join the chat at https://gitter.im/TheHive-Project/TheHive](https://badges.gitter.im/TheHive-Project/TheHive.svg)](https://gitter.im/TheHive-Project/TheHive?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![](images/cortex-logo.png)

[![Join the chat at https://gitter.im/TheHive-Project/TheHive](https://badges.gitter.im/TheHive-Project/TheHive.svg)](https://gitter.im/TheHive-Project/TheHive)

**Cortex** tries to solve a common problem frequently encountered by SOCs, CSIRTs and security researchers in the course of threat intelligence, digital forensics and incident response: how to **analyze observables** they have collected, **at scale**, **by querying a single tool** instead of several?

Expand Down
11 changes: 4 additions & 7 deletions app/Module.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import play.api.{ Configuration, Environment, Mode }
import play.api.libs.concurrent.AkkaGuiceSupport

import com.google.inject.AbstractModule

import net.codingwell.scalaguice.ScalaModule

import controllers.{ AssetCtrl, AssetCtrlDev, AssetCtrlProd }
import net.codingwell.scalaguice.ScalaModule
import play.api.libs.concurrent.AkkaGuiceSupport
import play.api.{ Configuration, Environment, Mode }
import services.JobActor

class Module(environment: Environment, configuration: Configuration) extends AbstractModule with ScalaModule with AkkaGuiceSupport {

override def configure() = {
override def configure(): Unit = {
bindActor[JobActor]("JobActor")

if (environment.mode == Mode.Prod)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@ package controllers

import javax.inject.Inject

import scala.annotation.implicitNotFound
import scala.concurrent.{ ExecutionContext, Future }

import models.JsonFormat.{ analyzerWrites, dataActifactReads, jobWrites }
import models.{ DataArtifact, FileArtifact }
import play.api.libs.json.{ JsObject, JsString, Json }
import play.api.mvc.{ Action, AnyContent, Controller, Request }

import models.{ DataArtifact, FileArtifact }
import models.JsonFormat.{ analyzerWrites, dataActifactReads, jobWrites }
import services.{ AnalyzerSrv, JobSrv }

import scala.concurrent.{ ExecutionContext, Future }

class AnalyzerCtrl @Inject() (
analyzerSrv: AnalyzerSrv,
jobSrv: JobSrv,
Expand All @@ -38,7 +36,7 @@ class AnalyzerCtrl @Inject() (
private[controllers] def readFileArtifact(request: Request[AnyContent]) = {
for {
parts request.body.asMultipartFormData
filePart parts.file("data").headOption
filePart parts.file("data")
attrList parts.dataParts.get("_json")
attrStr attrList.headOption
attr Json.parse(attrStr).asOpt[JsObject]
Expand All @@ -47,11 +45,12 @@ class AnalyzerCtrl @Inject() (
("filename" JsString(filePart.filename)))
}

def analyze(analyzerId: String) = Action.async { request
def analyze(analyzerId: String): Action[AnyContent] = Action.async { request
readDataArtifact(request)
.orElse(readFileArtifact(request))
.map { artifact
jobSrv.create(artifact, analyzerId)
analyzerSrv.analyze(analyzerId, artifact)
//jobSrv.create(artifact, analyzerId)
.map(j Ok(Json.toJson(j)))
}
.getOrElse(Future.successful(BadRequest("???")))
Expand Down
6 changes: 3 additions & 3 deletions app/controllers/Asset.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ import javax.inject.{ Inject, Singleton }

import play.api.Environment
import play.api.http.HttpErrorHandler
import play.api.mvc.{ Action, AnyContent, Controller }
import play.api.mvc.{ Action, AnyContent }

trait AssetCtrl {
def get(file: String): Action[AnyContent]
}

@Singleton
class AssetCtrlProd @Inject() (errorHandler: HttpErrorHandler) extends Assets(errorHandler) with AssetCtrl {
def get(file: String) = at("/ui", file)
def get(file: String): Action[AnyContent] = at("/ui", file)
}

@Singleton
class AssetCtrlDev @Inject() (environment: Environment) extends ExternalAssets(environment) with AssetCtrl {
def get(file: String) = {
def get(file: String): Action[AnyContent] = {
if (file.startsWith("bower_components/")) {
at("ui", file)
}
Expand Down
30 changes: 12 additions & 18 deletions app/controllers/Job.scala → app/controllers/JobCtrl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,47 @@ import scala.annotation.implicitNotFound
import scala.concurrent.ExecutionContext
import scala.concurrent.duration.Duration
import scala.util.{ Failure, Success }

import play.api.libs.json.{ JsString, Json }
import play.api.mvc.{ Action, Controller }

import models.JsonFormat.{ jobStatusWrites, jobWrites }
import play.api.mvc.{ Action, AnyContent, Controller }
import models.JsonFormat._
import services.JobSrv

class JobCtrl @Inject() (
jobSrv: JobSrv,
implicit val ec: ExecutionContext) extends Controller {
def list(dataTypeFilter: Option[String], dataFilter: Option[String], analyzerFilter: Option[String], start: Int, limit: Int) = Action.async { request
def list(dataTypeFilter: Option[String], dataFilter: Option[String], analyzerFilter: Option[String], start: Int, limit: Int): Action[AnyContent] = Action.async { request
jobSrv.list(dataTypeFilter, dataFilter, analyzerFilter, start, limit).map {
case (total, jobs) Ok(Json.toJson(jobs)).withHeaders("X-Total" total.toString)
}
}

def get(jobId: String) = Action.async { request
def get(jobId: String): Action[AnyContent] = Action.async { request
jobSrv.get(jobId).map { job
Ok(Json.toJson(job))
}
}

def remove(jobId: String) = Action.async { request
def remove(jobId: String): Action[AnyContent] = Action.async { request
jobSrv.remove(jobId).map(_ Ok(""))
}

def report(jobId: String) = Action.async { request
def report(jobId: String): Action[AnyContent] = Action.async { request
jobSrv
.get(jobId)
.map { job
val report = job.report.value match {
case Some(Success(report)) report
case Some(Failure(error)) JsString(error.getMessage)
case None JsString("Running")
case Some(Success(r)) Json.toJson(r)
case Some(Failure(error)) JsString(error.getMessage)
case None JsString("Running")
}
Ok(jobWrites.writes(job) +
("status" jobStatusWrites.writes(job.status)) +
("report" report))
}
}

def waitReport(jobId: String, atMost: String) = Action.async { request
for {
job jobSrv.get(jobId)
(status, report) jobSrv.waitReport(jobId, Duration(atMost))
} yield Ok(jobWrites.writes(job) +
("status" jobStatusWrites.writes(job.status)) +
("report" report))
def waitReport(jobId: String, atMost: String): Action[AnyContent] = Action.async { request
jobSrv.waitReport(jobId, Duration(atMost))
.map { job Ok(Json.toJson(job)) }
}
}
32 changes: 32 additions & 0 deletions app/controllers/MispCtrl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package controllers

import javax.inject.Inject

import models.InvalidRequestError
import play.api.Logger
import play.api.libs.json.{ JsObject, JsValue }
import play.api.mvc.{ Action, AnyContent, Controller }
import services.MispSrv

import scala.concurrent.ExecutionContext

class MispCtrl @Inject() (mispSrv: MispSrv, implicit val ec: ExecutionContext) extends Controller {

private[MispCtrl] lazy val logger = Logger(getClass)
def modules: Action[AnyContent] = Action { _
Ok(mispSrv.moduleList)
}

def query: Action[JsValue] = Action.async(parse.json) { request
val module = (request.body \ "module").asOpt[String].getOrElse(throw InvalidRequestError("Module parameter is not present in request"))
val (mispType, dataJson) = request.body.as[JsObject].fields
.collectFirst {
case kv @ (k, _) if k != "module" kv
}
.getOrElse(throw InvalidRequestError("Request doesn't contain data to analyze"))
val data = dataJson.asOpt[String].getOrElse(throw InvalidRequestError("Data has invalid type (expected string)"))
mispSrv.query(module, mispType, data)
.map(Ok(_))
}
}

25 changes: 25 additions & 0 deletions app/controllers/StatusCtrl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package controllers

import javax.inject.{ Inject, Singleton }

import play.api.Configuration
import play.api.libs.json.Json
import play.api.libs.json.Json.toJsFieldJsValueWrapper
import play.api.mvc.{ Action, Controller }

@Singleton
class StatusCtrl @Inject() (
configuration: Configuration) extends Controller {

private[controllers] def getVersion(c: Class[_]) = Option(c.getPackage.getImplementationVersion).getOrElse("SNAPSHOT")

def get = Action { _
Ok(Json.obj(
"versions" Json.obj(
"Cortex" getVersion(classOf[models.Artifact]),
"Play" getVersion(classOf[Controller])),
"config" Json.obj(
"authType" "none",
"capabilities" Json.arr())))
}
}
9 changes: 4 additions & 5 deletions app/models/Analyzer.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package models

import scala.concurrent.Future
import play.api.libs.json.JsObject

abstract class Analyzer {
def analyze(artifact: Artifact): Future[JsObject]
val name: String
val version: String
val description: String
val dataTypeList: Seq[String]
val id = (name + "_" + version).replaceAll("\\.", "_")
val author: String
val url: String
val license: String
val id: String = (name + "_" + version).replaceAll("\\.", "_")
}
16 changes: 12 additions & 4 deletions app/models/Artifact.scala
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
package models

import play.api.libs.json.JsObject
import java.io.File
import java.nio.file.Files

import play.api.libs.json.JsObject

abstract class Artifact(attributes: JsObject) {
def dataTypeFilter(filter: String): Boolean = (attributes \ "dataType").asOpt[String].fold(false)(_.toLowerCase.contains(filter.toLowerCase))
def dataType: String = (attributes \ "dataType").asOpt[String].getOrElse("other")
def dataTypeFilter(filter: String): Boolean = dataType.toLowerCase.contains(filter.toLowerCase)
def dataFilter(filter: String): Boolean = false
}
class FileArtifact(val data: File, val attributes: JsObject) extends Artifact(attributes) {
override def finalize {
override def finalize() {
data.delete()
}
}
object FileArtifact {
def apply(data: File, attributes: JsObject) = {
def apply(data: File, attributes: JsObject): FileArtifact = {
val tempFile = File.createTempFile("cortex-", "-datafile")
data.renameTo(tempFile)
new FileArtifact(tempFile, attributes)
}
def apply(data: Array[Byte], attributes: JsObject): FileArtifact = {
val tempFile = File.createTempFile("cortex-", "-datafile")
Files.write(tempFile.toPath, data)
new FileArtifact(tempFile, attributes)
}
def unapply(fileArtifact: FileArtifact) = Some(fileArtifact.data fileArtifact.attributes)
}
case class DataArtifact(data: String, attributes: JsObject) extends Artifact(attributes) {
Expand Down
8 changes: 8 additions & 0 deletions app/models/Errors.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package models

abstract class CortexError(message: String) extends Exception(message)

case class JobNotFoundError(jobId: String) extends CortexError(s"Job $jobId not found")
case class UnexpectedError(message: String) extends CortexError(message)
case class AnalyzerNotFoundError(analyzerId: String) extends CortexError(s"Analyzer $analyzerId not found")
case class InvalidRequestError(message: String) extends CortexError(message)
Loading

0 comments on commit fd785bf

Please sign in to comment.