METRON-1638 Retrieve Pcap results in pdml format (merrimanr) closes apache/metron#1120
Project: http://git-wip-us.apache.org/repos/asf/metron/repo Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/3e5ef41d Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/3e5ef41d Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/3e5ef41d Branch: refs/heads/master Commit: 3e5ef41d9b8639fb1155686e615c02a59b735397 Parents: f316d15 Author: merrimanr <merrim...@gmail.com> Authored: Thu Jul 19 17:13:22 2018 -0500 Committer: rmerriman <merrim...@gmail.com> Committed: Thu Jul 19 17:13:22 2018 -0500 ---------------------------------------------------------------------- dependencies_with_url.csv | 3 + .../docker/rpm-docker/SPECS/metron.spec | 1 + metron-interface/metron-rest-client/pom.xml | 5 + .../apache/metron/rest/model/pcap/Field.java | 164 ++++++++++++ .../apache/metron/rest/model/pcap/Packet.java | 53 ++++ .../org/apache/metron/rest/model/pcap/Pdml.java | 103 ++++++++ .../apache/metron/rest/model/pcap/Proto.java | 114 +++++++++ metron-interface/metron-rest/README.md | 34 ++- metron-interface/metron-rest/pom.xml | 1 - .../src/main/config/rest_application.yml | 5 +- .../apache/metron/rest/MetronRestConstants.java | 1 + .../apache/metron/rest/config/PcapConfig.java | 6 + .../metron/rest/controller/PcapController.java | 31 ++- .../apache/metron/rest/service/PcapService.java | 6 + .../rest/service/impl/PcapServiceImpl.java | 47 +++- .../service/impl/PcapToPdmlScriptWrapper.java | 59 +++++ .../src/main/scripts/pcap_to_pdml.sh | 19 ++ .../apache/metron/rest/config/TestConfig.java | 7 + .../PcapControllerIntegrationTest.java | 65 +++-- .../rest/mock/MockPcapToPdmlScriptWrapper.java | 55 ++++ .../rest/service/impl/PcapServiceImplTest.java | 250 +++++++++++++++++-- metron-interface/pom.xml | 3 + 22 files changed, 981 insertions(+), 51 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/dependencies_with_url.csv ---------------------------------------------------------------------- diff --git a/dependencies_with_url.csv b/dependencies_with_url.csv index 40d3e06..bf3e382 100644 --- a/dependencies_with_url.csv +++ b/dependencies_with_url.csv @@ -141,12 +141,15 @@ com.fasterxml.jackson.dataformat:jackson-dataformat-smile:jar:2.6.6:compile,ASLv com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.6.6:compile,ASLv2,https://github.com/FasterXML/jackson com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:jar:2.7.4:compile,ASLv2,http://wiki.fasterxml.com/JacksonForCbor com.fasterxml.jackson.dataformat:jackson-dataformat-smile:jar:2.7.4:compile,ASLv2,http://wiki.fasterxml.com/JacksonForSmile +com.fasterxml.jackson.dataformat:jackson-dataformat-xml:jar:2.9.5:compile,ASLv2,https://github.com/FasterXML/jackson com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.7.4:compile,ASLv2,https://github.com/FasterXML/jackson com.fasterxml.jackson.datatype:jackson-datatype-joda:jar:2.8.1:compile,ASLv2,https://github.com/FasterXML/jackson-datatype-joda com.fasterxml.jackson.datatype:jackson-datatype-joda:jar:2.9.5:compile,ASLv2,https://github.com/FasterXML/jackson-datatype-joda com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.9.5:compile,ASLv2,https://github.com/FasterXML/jackson-modules-java8 com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.9.5:compile,ASLv2,https://github.com/FasterXML/jackson-modules-java8 com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.9.5:compile,ASLv2,https://github.com/FasterXML/jackson-modules-java8 +com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.9.5:compile,ASLv2,https://github.com/FasterXML/jackson-modules-java8 +com.fasterxml.woodstox:woodstox-core:jar:5.0.3:compile,ASLv2,https://github.com/FasterXML/woodstox com.fasterxml:classmate:jar:1.3.1:compile,ASLv2,http://github.com/cowtowncoder/java-classmate com.fasterxml:classmate:jar:1.3.4:compile,ASLv2,http://github.com/cowtowncoder/java-classmate com.google.code.gson:gson:jar:2.2.4:compile,The Apache Software License, Version 2.0,http://code.google.com/p/google-gson/ http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-deployment/packaging/docker/rpm-docker/SPECS/metron.spec ---------------------------------------------------------------------- diff --git a/metron-deployment/packaging/docker/rpm-docker/SPECS/metron.spec b/metron-deployment/packaging/docker/rpm-docker/SPECS/metron.spec index 4b88fd0..3f090c8 100644 --- a/metron-deployment/packaging/docker/rpm-docker/SPECS/metron.spec +++ b/metron-deployment/packaging/docker/rpm-docker/SPECS/metron.spec @@ -419,6 +419,7 @@ This package installs the Metron Rest %{metron_home} %dir %{metron_home}/lib %{metron_home}/config/rest_application.yml %{metron_home}/bin/metron-rest.sh +%{metron_home}/bin/pcap_to_pdml.sh %attr(0644,root,root) %{metron_home}/lib/metron-rest-%{full_version}.jar # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest-client/pom.xml ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest-client/pom.xml b/metron-interface/metron-rest-client/pom.xml index a2f1288..d3fecfd 100644 --- a/metron-interface/metron-rest-client/pom.xml +++ b/metron-interface/metron-rest-client/pom.xml @@ -52,6 +52,11 @@ <artifactId>javax.persistence</artifactId> <version>${eclipse.javax.persistence.version}</version> </dependency> + <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-xml</artifactId> + <version>${jackson.version}</version> + </dependency> </dependencies> </project> http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Field.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Field.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Field.java new file mode 100644 index 0000000..9c2878b --- /dev/null +++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Field.java @@ -0,0 +1,164 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.rest.model.pcap; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +import java.util.ArrayList; +import java.util.List; + +public class Field { + + @JacksonXmlProperty(isAttribute = true) + private String name; + @JacksonXmlProperty(isAttribute = true) + private String pos; + @JacksonXmlProperty(isAttribute = true) + private String showname; + @JacksonXmlProperty(isAttribute = true) + private String size; + @JacksonXmlProperty(isAttribute = true) + private String value; + @JacksonXmlProperty(isAttribute = true) + private String show; + @JacksonXmlProperty(isAttribute = true) + private String unmaskedvalue; + @JacksonXmlProperty(isAttribute = true) + private String hide; + @JacksonXmlProperty(localName = "field") + @JacksonXmlElementWrapper(useWrapping = false) + private List<Field> fields; + @JacksonXmlProperty(localName = "proto") + @JacksonXmlElementWrapper(useWrapping = false) + private List<Proto> protos; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPos() { + return pos; + } + + public void setPos(String pos) { + this.pos = pos; + } + + public String getShowname() { + return showname; + } + + public void setShowname(String showname) { + this.showname = showname; + } + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getShow() { + return show; + } + + public void setShow(String show) { + this.show = show; + } + + public String getUnmaskedvalue() { + return unmaskedvalue; + } + + public void setUnmaskedvalue(String unmaskedvalue) { + this.unmaskedvalue = unmaskedvalue; + } + + public String getHide() { + return hide; + } + + public void setHide(String hide) { + this.hide = hide; + } + + public List<Field> getFields() { + return fields; + } + + public void setFields(List<Field> fields) { + this.fields = fields; + } + + public List<Proto> getProtos() { + return protos; + } + + public void setProtos(List<Proto> protos) { + this.protos = protos; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Field field = (Field) o; + + return (getName() != null ? getName().equals(field.getName()) : field.getName() != null) && + (getPos() != null ? getPos().equals(field.getPos()) : field.getPos() == null) && + (getShowname() != null ? getShowname().equals(field.getShowname()) : field.getShowname() == null) && + (getSize() != null ? getSize().equals(field.getSize()) : field.getSize() == null) && + (getValue() != null ? getValue().equals(field.getValue()) : field.getValue() == null) && + (getShow() != null ? getShow().equals(field.getShow()) : field.getShow() == null) && + (getUnmaskedvalue() != null ? getUnmaskedvalue().equals(field.getUnmaskedvalue()) : field.getUnmaskedvalue() == null) && + (getHide() != null ? getHide().equals(field.getHide()) : field.getHide() == null) && + (getFields() != null ? getFields().equals(field.getFields()) : field.getFields() == null) && + (getProtos() != null ? getProtos().equals(field.getProtos()) : field.getProtos() == null); + } + + @Override + public int hashCode() { + int result = getName() != null ? getName().hashCode() : 0; + result = 31 * result + (getPos() != null ? getPos().hashCode() : 0); + result = 31 * result + (getShowname() != null ? getShowname().hashCode() : 0); + result = 31 * result + (getSize() != null ? getSize().hashCode() : 0); + result = 31 * result + (getValue() != null ? getValue().hashCode() : 0); + result = 31 * result + (getShow() != null ? getShow().hashCode() : 0); + result = 31 * result + (getUnmaskedvalue() != null ? getUnmaskedvalue().hashCode() : 0); + result = 31 * result + (getHide() != null ? getHide().hashCode() : 0); + result = 31 * result + (getFields() != null ? getFields().hashCode() : 0); + result = 31 * result + (getProtos() != null ? getProtos().hashCode() : 0); + return result; + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Packet.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Packet.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Packet.java new file mode 100644 index 0000000..de21e6b --- /dev/null +++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Packet.java @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.rest.model.pcap; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +import java.util.List; + +public class Packet { + + @JacksonXmlProperty(localName = "proto") + @JacksonXmlElementWrapper(useWrapping = false) + private List<Proto> protos; + + public List<Proto> getProtos() { + return protos; + } + + public void setProtos(List<Proto> protos) { + this.protos = protos; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Packet packet = (Packet) o; + + return (getProtos() != null ? getProtos().equals(packet.getProtos()) : packet.getProtos() == null); + } + + @Override + public int hashCode() { + return getProtos() != null ? getProtos().hashCode() : 0; + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Pdml.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Pdml.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Pdml.java new file mode 100644 index 0000000..f44f96b --- /dev/null +++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Pdml.java @@ -0,0 +1,103 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.rest.model.pcap; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +import java.util.List; + +public class Pdml { + + @JacksonXmlProperty(isAttribute = true) + private String version; + @JacksonXmlProperty(isAttribute = true) + private String creator; + @JacksonXmlProperty(isAttribute = true) + private String time; + @JacksonXmlProperty(isAttribute = true, localName = "capture_file") + private String captureFile; + @JacksonXmlProperty(localName = "packet") + @JacksonXmlElementWrapper(useWrapping = false) + private List<Packet> packets; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getCreator() { + return creator; + } + + public void setCreator(String creator) { + this.creator = creator; + } + + public String getTime() { + return time; + } + + public void setTime(String time) { + this.time = time; + } + + public String getCaptureFile() { + return captureFile; + } + + public void setCaptureFile(String captureFile) { + this.captureFile = captureFile; + } + + public List<Packet> getPackets() { + return packets; + } + + public void setPackets(List<Packet> packets) { + this.packets = packets; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Pdml pdml = (Pdml) o; + + return (getVersion() != null ? getVersion().equals(pdml.getVersion()) : pdml.getVersion() != null) && + (getCreator() != null ? getCreator().equals(pdml.getCreator()) : pdml.getCreator() == null) && + (getTime() != null ? getTime().equals(pdml.getTime()) : pdml.getTime() == null) && + (getCaptureFile() != null ? getCaptureFile().equals(pdml.getCaptureFile()) : pdml.getCaptureFile() == null) && + (getPackets() != null ? getPackets().equals(pdml.getPackets()) : pdml.getPackets() == null); + } + + @Override + public int hashCode() { + int result = getVersion() != null ? getVersion().hashCode() : 0; + result = 31 * result + (getCreator() != null ? getCreator().hashCode() : 0); + result = 31 * result + (getTime() != null ? getTime().hashCode() : 0); + result = 31 * result + (getCaptureFile() != null ? getCaptureFile().hashCode() : 0); + result = 31 * result + (getPackets() != null ? getPackets().hashCode() : 0); + return result; + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Proto.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Proto.java b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Proto.java new file mode 100644 index 0000000..bdd5c1f --- /dev/null +++ b/metron-interface/metron-rest-client/src/main/java/org/apache/metron/rest/model/pcap/Proto.java @@ -0,0 +1,114 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.rest.model.pcap; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +import java.util.List; + +public class Proto { + + @JacksonXmlProperty(isAttribute = true) + private String name; + @JacksonXmlProperty(isAttribute = true) + private String pos; + @JacksonXmlProperty(isAttribute = true) + private String showname; + @JacksonXmlProperty(isAttribute = true) + private String size; + @JacksonXmlProperty(isAttribute = true) + private String hide; + @JacksonXmlProperty(localName = "field") + @JacksonXmlElementWrapper(useWrapping = false) + private List<Field> fields; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPos() { + return pos; + } + + public void setPos(String pos) { + this.pos = pos; + } + + public String getShowname() { + return showname; + } + + public void setShowname(String showname) { + this.showname = showname; + } + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + + public String getHide() { + return hide; + } + + public void setHide(String hide) { + this.hide = hide; + } + + public List<Field> getFields() { + return fields; + } + + public void setFields(List<Field> fields) { + this.fields = fields; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Proto proto = (Proto) o; + + return (getName() != null ? getName().equals(proto.getName()) : proto.getName() != null) && + (getPos() != null ? getPos().equals(proto.getPos()) : proto.getPos() == null) && + (getShowname() != null ? getShowname().equals(proto.getShowname()) : proto.getShowname() == null) && + (getSize() != null ? getSize().equals(proto.getSize()) : proto.getSize() == null) && + (getHide() != null ? getHide().equals(proto.getHide()) : proto.getHide() == null) && + (getFields() != null ? getFields().equals(proto.getFields()) : proto.getFields() == null); + } + + @Override + public int hashCode() { + int result = getName() != null ? getName().hashCode() : 0; + result = 31 * result + (getPos() != null ? getPos().hashCode() : 0); + result = 31 * result + (getShowname() != null ? getShowname().hashCode() : 0); + result = 31 * result + (getSize() != null ? getSize().hashCode() : 0); + result = 31 * result + (getHide() != null ? getHide().hashCode() : 0); + result = 31 * result + (getFields() != null ? getFields().hashCode() : 0); + return result; + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/README.md ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/README.md b/metron-interface/metron-rest/README.md index 7ccedc8..7b3a263 100644 --- a/metron-interface/metron-rest/README.md +++ b/metron-interface/metron-rest/README.md @@ -210,6 +210,17 @@ Setting active profiles is done with the METRON_SPRING_PROFILES_ACTIVE variable. METRON_SPRING_PROFILES_ACTIVE="vagrant,dev" ``` +## Pcap Query + +The REST application exposes endpoints for querying Pcap data. For more information about filtering options see [Query Filter Utility](/metron-platform/metron-pcap-backend#query-filter-utility). + +There is an endpoint available that will return Pcap data in [PDML](https://wiki.wireshark.org/PDML) format. [Wireshark](https://www.wireshark.org/) must be installed for this feature to work. +Installing wireshark in CentOS can be done with `yum -y install wireshark`. + +The REST application uses a Java Process object to call out to the `pcap_to_pdml.sh` script. This script is installed at `$METRON_HOME/bin/pcap_to_pdml.sh` by default. +Out of the box it is a simple wrapper around the tshark command to transform raw pcap data to PDML. However it can be extended to do additional processing as long as the expected input/output is maintained. +REST will supply the script with raw pcap data through standard in and expects PDML data serialized as XML. + ## API Request and Response objects are JSON formatted. The JSON schemas are available in the Swagger UI. @@ -243,6 +254,8 @@ Request and Response objects are JSON formatted. The JSON schemas are available | [ `GET /api/v1/metaalert/remove/alert`](#get-apiv1metaalertremovealert)| | [ `GET /api/v1/metaalert/update/status/{guid}/{status}`](#get-apiv1metaalertupdatestatusguidstatus)| | [ `GET /api/v1/pcap/fixed`](#get-apiv1pcapfixed)| +| [ `GET /api/v1/pcap/{jobId}`](#get-apiv1pcapjobid)| +| [ `GET /api/v1/pcap/{jobId}/pdml`](#get-apiv1pcapjobidpdml)| | [ `GET /api/v1/search/search`](#get-apiv1searchsearch)| | [ `POST /api/v1/search/search`](#get-apiv1searchsearch)| | [ `POST /api/v1/search/group`](#get-apiv1searchgroup)| @@ -490,9 +503,26 @@ Request and Response objects are JSON formatted. The JSON schemas are available ### `POST /api/v1/pcap/fixed` * Description: Executes a Fixed Pcap Query. * Input: - * fixedPcapRequest - A Fixed Pcap Request which includes fixed filter fields like ip source address and protocol. + * fixedPcapRequest - A Fixed Pcap Request which includes fixed filter fields like ip source address and protocol + * Returns: + * 200 - Returns a job status with job ID. + +### `POST /api/v1/pcap/{jobId}` + * Description: Gets job status for Pcap query job. + * Input: + * jobId - Job ID of submitted job + * Returns: + * 200 - Returns a job status for the Job ID. + * 404 - Job is missing. + +### `POST /api/v1/pcap/{jobId}/pdml` + * Description: Gets Pcap Results for a page in PDML format. + * Input: + * jobId - Job ID of submitted job + * page - Page number * Returns: - * 200 - Returns a PcapResponse containing an array of pcaps. + * 200 - Returns PDML in json format. + * 404 - Job or page is missing. ### `POST /api/v1/search/search` * Description: Searches the indexing store. GUIDs must be quoted to ensure correct results. http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/pom.xml ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/pom.xml b/metron-interface/metron-rest/pom.xml index 733ef6a..1bf0fd6 100644 --- a/metron-interface/metron-rest/pom.xml +++ b/metron-interface/metron-rest/pom.xml @@ -36,7 +36,6 @@ <spring-kafka.version>2.0.4.RELEASE</spring-kafka.version> <spring.version>5.0.5.RELEASE</spring.version> <eclipse.link.version>2.6.4</eclipse.link.version> - <jackson.version>2.9.5</jackson.version> <jsonpath.version>2.4.0</jsonpath.version> </properties> <dependencies> http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/src/main/config/rest_application.yml ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/main/config/rest_application.yml b/metron-interface/metron-rest/src/main/config/rest_application.yml index 4cc51ff..3999393 100644 --- a/metron-interface/metron-rest/src/main/config/rest_application.yml +++ b/metron-interface/metron-rest/src/main/config/rest_application.yml @@ -52,4 +52,7 @@ storm: kerberos: enabled: ${SECURITY_ENABLED} principal: ${METRON_PRINCIPAL_NAME} - keytab: ${METRON_SERVICE_KEYTAB} \ No newline at end of file + keytab: ${METRON_SERVICE_KEYTAB} + +pcap: + pdml.script.path: ${METRON_HOME}/bin/pcap_to_pdml.sh \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java index 8e14e38..b65d037 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java @@ -79,4 +79,5 @@ public class MetronRestConstants { public static final String PCAP_BASE_INTERIM_RESULT_PATH_SPRING_PROPERTY = "pcap.base.interim.result.path"; public static final String PCAP_FINAL_OUTPUT_PATH_SPRING_PROPERTY = "pcap.final.output.path"; public static final String PCAP_PAGE_SIZE_SPRING_PROPERTY = "pcap.page.size"; + public static final String PCAP_PDML_SCRIPT_PATH_SPRING_PROPERTY = "pcap.pdml.script.path"; } http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/PcapConfig.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/PcapConfig.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/PcapConfig.java index a0b7f18..323df05 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/PcapConfig.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/PcapConfig.java @@ -19,6 +19,7 @@ package org.apache.metron.rest.config; import org.apache.metron.job.manager.InMemoryJobManager; import org.apache.metron.job.manager.JobManager; +import org.apache.metron.rest.service.impl.PcapToPdmlScriptWrapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -39,5 +40,10 @@ public class PcapConfig { return new PcapJobSupplier(); } + @Bean + public PcapToPdmlScriptWrapper pcapToPdmlScriptWrapper() { + return new PcapToPdmlScriptWrapper(); + } + } http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/PcapController.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/PcapController.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/PcapController.java index 6663659..47bc6a0 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/PcapController.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/PcapController.java @@ -24,6 +24,7 @@ import io.swagger.annotations.ApiResponses; import org.apache.metron.rest.RestException; import org.apache.metron.rest.model.pcap.FixedPcapRequest; import org.apache.metron.rest.model.pcap.PcapStatus; +import org.apache.metron.rest.model.pcap.Pdml; import org.apache.metron.rest.security.SecurityUtils; import org.apache.metron.rest.service.PcapService; import org.springframework.beans.factory.annotation.Autowired; @@ -33,6 +34,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -46,16 +48,18 @@ public class PcapController { @ApiResponses(value = { @ApiResponse(message = "Returns a job status with job ID.", code = 200)}) @RequestMapping(value = "/fixed", method = RequestMethod.POST) ResponseEntity<PcapStatus> fixed(@ApiParam(name="fixedPcapRequest", value="A Fixed Pcap Request" - + " which includes fixed filter fields like ip source address and protocol.", required=true)@RequestBody FixedPcapRequest fixedPcapRequest) throws RestException { + + " which includes fixed filter fields like ip source address and protocol", required=true)@RequestBody FixedPcapRequest fixedPcapRequest) throws RestException { PcapStatus pcapStatus = pcapQueryService.fixed(SecurityUtils.getCurrentUser(), fixedPcapRequest); return new ResponseEntity<>(pcapStatus, HttpStatus.OK); } - @ApiOperation(value = "Gets job status for running job.") - @ApiResponses(value = { @ApiResponse(message = "Returns a job status for the passed job.", code = 200)}) + @ApiOperation(value = "Gets job status for Pcap query job.") + @ApiResponses(value = { + @ApiResponse(message = "Returns a job status for the Job ID.", code = 200), + @ApiResponse(message = "Job is missing.", code = 404) + }) @RequestMapping(value = "/{jobId}", method = RequestMethod.GET) - ResponseEntity<PcapStatus> getStatus(@ApiParam(name="jobId", value="Job ID of submitted job" - + " which includes fixed filter fields like ip source address and protocol.", required=true)@PathVariable String jobId) throws RestException { + ResponseEntity<PcapStatus> getStatus(@ApiParam(name="jobId", value="Job ID of submitted job", required=true)@PathVariable String jobId) throws RestException { PcapStatus jobStatus = pcapQueryService.getJobStatus(SecurityUtils.getCurrentUser(), jobId); if (jobStatus != null) { return new ResponseEntity<>(jobStatus, HttpStatus.OK); @@ -64,6 +68,23 @@ public class PcapController { } } + @ApiOperation(value = "Gets Pcap Results for a page in PDML format.") + @ApiResponses(value = { + @ApiResponse(message = "Returns PDML in json format.", code = 200), + @ApiResponse(message = "Job or page is missing.", code = 404) + }) + @RequestMapping(value = "/{jobId}/pdml", method = RequestMethod.GET) + ResponseEntity<Pdml> pdml(@ApiParam(name="jobId", value="Job ID of submitted job", required=true)@PathVariable String jobId, + @ApiParam(name="page", value="Page number", required=true)@RequestParam Integer page) throws RestException { + Pdml pdml = pcapQueryService.getPdml(SecurityUtils.getCurrentUser(), jobId, page); + if (pdml != null) { + return new ResponseEntity<>(pdml, HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + @ApiOperation(value = "Kills running job.") @ApiResponses(value = { @ApiResponse(message = "Kills passed job.", code = 200)}) @RequestMapping(value = "/kill/{jobId}", method = RequestMethod.DELETE) http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/PcapService.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/PcapService.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/PcapService.java index 8073573..9421ce3 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/PcapService.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/PcapService.java @@ -17,9 +17,12 @@ */ package org.apache.metron.rest.service; +import org.apache.hadoop.fs.Path; import org.apache.metron.rest.RestException; import org.apache.metron.rest.model.pcap.FixedPcapRequest; import org.apache.metron.rest.model.pcap.PcapStatus; +import org.apache.metron.rest.model.pcap.Pdml; +import org.apache.metron.rest.model.pcap.Pdml; public interface PcapService { @@ -29,4 +32,7 @@ public interface PcapService { PcapStatus killJob(String username, String jobId) throws RestException; + Path getPath(String username, String jobId, Integer page) throws RestException; + + Pdml getPdml(String username, String jobId, Integer page) throws RestException; } http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/PcapServiceImpl.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/PcapServiceImpl.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/PcapServiceImpl.java index 6c21e77..7894b1a 100644 --- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/PcapServiceImpl.java +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/PcapServiceImpl.java @@ -17,7 +17,7 @@ */ package org.apache.metron.rest.service.impl; -import java.io.IOException; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; @@ -34,11 +34,15 @@ import org.apache.metron.rest.config.PcapJobSupplier; import org.apache.metron.rest.model.pcap.FixedPcapRequest; import org.apache.metron.rest.model.pcap.PcapRequest; import org.apache.metron.rest.model.pcap.PcapStatus; +import org.apache.metron.rest.model.pcap.Pdml; import org.apache.metron.rest.service.PcapService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; +import java.io.IOException; +import java.io.InputStream; + @Service public class PcapServiceImpl implements PcapService { @@ -46,13 +50,15 @@ public class PcapServiceImpl implements PcapService { private Configuration configuration; private PcapJobSupplier pcapJobSupplier; private JobManager<Path> jobManager; + private PcapToPdmlScriptWrapper pcapToPdmlScriptWrapper; @Autowired - public PcapServiceImpl(Environment environment, Configuration configuration, PcapJobSupplier pcapJobSupplier, JobManager<Path> jobManager) { + public PcapServiceImpl(Environment environment, Configuration configuration, PcapJobSupplier pcapJobSupplier, JobManager<Path> jobManager, PcapToPdmlScriptWrapper pcapToPdmlScriptWrapper) { this.environment = environment; this.configuration = configuration; this.pcapJobSupplier = pcapJobSupplier; this.jobManager = jobManager; + this.pcapToPdmlScriptWrapper = pcapToPdmlScriptWrapper; } @Override @@ -103,6 +109,43 @@ public class PcapServiceImpl implements PcapService { return getJobStatus(username, jobId); } + @Override + public Path getPath(String username, String jobId, Integer page) throws RestException { + Path path = null; + try { + Statusable<Path> statusable = jobManager.getJob(username, jobId); + if (statusable != null && statusable.isDone()) { + Pageable<Path> pageable = statusable.get(); + if (pageable != null && page <= pageable.getSize() && page > 0) { + path = pageable.getPage(page - 1); + } + } + } catch (JobNotFoundException e) { + // do nothing and return null pcapStatus + } catch (JobException | InterruptedException e) { + throw new RestException(e); + } + return path; + } + + @Override + public Pdml getPdml(String username, String jobId, Integer page) throws RestException { + Pdml pdml = null; + Path path = getPath(username, jobId, page); + try { + FileSystem fileSystem = getFileSystem(); + if (path!= null && fileSystem.exists(path)) { + String scriptPath = environment.getProperty(MetronRestConstants.PCAP_PDML_SCRIPT_PATH_SPRING_PROPERTY); + InputStream processInputStream = pcapToPdmlScriptWrapper.execute(scriptPath, fileSystem, path); + pdml = new XmlMapper().readValue(processInputStream, Pdml.class); + processInputStream.close(); + } + } catch (IOException e) { + throw new RestException(e); + } + return pdml; + } + protected void setPcapOptions(String username, PcapRequest pcapRequest) throws IOException { PcapOptions.JOB_NAME.put(pcapRequest, "jobName"); PcapOptions.USERNAME.put(pcapRequest, username); http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/PcapToPdmlScriptWrapper.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/PcapToPdmlScriptWrapper.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/PcapToPdmlScriptWrapper.java new file mode 100644 index 0000000..b5e3033 --- /dev/null +++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/PcapToPdmlScriptWrapper.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.rest.service.impl; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.metron.rest.MetronRestConstants; +import org.apache.metron.rest.RestException; +import org.apache.metron.rest.model.pcap.Pdml; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +public class PcapToPdmlScriptWrapper { + + public InputStream execute(String scriptPath, FileSystem fileSystem, Path pcapPath) throws IOException { + ProcessBuilder processBuilder = getProcessBuilder(scriptPath, pcapPath.toUri().getPath()); + Process process = processBuilder.start(); + InputStream rawInputStream = getRawInputStream(fileSystem, pcapPath); + OutputStream processOutputStream = process.getOutputStream(); + IOUtils.copy(rawInputStream, processOutputStream); + rawInputStream.close(); + if (process.isAlive()) { + // need to close processOutputStream if script doesn't exit with an error + processOutputStream.close(); + return process.getInputStream(); + } else { + String errorMessage = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8); + throw new IOException(errorMessage); + } + } + + protected InputStream getRawInputStream(FileSystem fileSystem, Path path) throws IOException { + return fileSystem.open(path); + } + + protected ProcessBuilder getProcessBuilder(String... command) { + return new ProcessBuilder(command); + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/src/main/scripts/pcap_to_pdml.sh ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/main/scripts/pcap_to_pdml.sh b/metron-interface/metron-rest/src/main/scripts/pcap_to_pdml.sh new file mode 100755 index 0000000..81c3781 --- /dev/null +++ b/metron-interface/metron-rest/src/main/scripts/pcap_to_pdml.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +tshark -i - -T pdml http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java index a5a0236..942ff78 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java @@ -51,8 +51,10 @@ import org.apache.metron.job.manager.JobManager; import org.apache.metron.rest.RestException; import org.apache.metron.rest.mock.MockPcapJob; import org.apache.metron.rest.mock.MockPcapJobSupplier; +import org.apache.metron.rest.mock.MockPcapToPdmlScriptWrapper; import org.apache.metron.rest.mock.MockStormCLIClientWrapper; import org.apache.metron.rest.mock.MockStormRestTemplate; +import org.apache.metron.rest.service.impl.PcapToPdmlScriptWrapper; import org.apache.metron.rest.service.impl.StormCLIWrapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -205,4 +207,9 @@ public class TestConfig { mockPcapJobSupplier.setMockPcapJob(mockPcapJob); return mockPcapJobSupplier; } + + @Bean + public PcapToPdmlScriptWrapper pcapToPdmlScriptWrapper() { + return new MockPcapToPdmlScriptWrapper(); + } } http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/PcapControllerIntegrationTest.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/PcapControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/PcapControllerIntegrationTest.java index 2363204..2fa64cd 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/PcapControllerIntegrationTest.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/PcapControllerIntegrationTest.java @@ -183,9 +183,6 @@ public class PcapControllerIntegrationTest { public void testGetStatus() throws Exception { MockPcapJob mockPcapJob = (MockPcapJob) wac.getBean("mockPcapJob"); - this.mockMvc.perform(get(pcapUrl + "/jobId").with(httpBasic(user, password))) - .andExpect(status().isNotFound()); - mockPcapJob.setStatus(new JobStatus().withJobId("jobId").withState(JobStatus.State.RUNNING)); this.mockMvc.perform(post(pcapUrl + "/fixed").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(fixedJson)) @@ -226,6 +223,9 @@ public class PcapControllerIntegrationTest { .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(jsonPath("$.jobStatus").value("KILLED")); + + this.mockMvc.perform(get(pcapUrl + "/someJobId").with(httpBasic(user, password))) + .andExpect(status().isNotFound()); } @Test @@ -233,23 +233,23 @@ public class PcapControllerIntegrationTest { MockPcapJob mockPcapJob = (MockPcapJob) wac.getBean("mockPcapJob"); this.mockMvc.perform(get(pcapUrl + "/jobId123").with(httpBasic(user, password))) - .andExpect(status().isNotFound()); + .andExpect(status().isNotFound()); mockPcapJob.setStatus(new JobStatus().withJobId("jobId123").withState(JobStatus.State.RUNNING)); this.mockMvc.perform(post(pcapUrl + "/fixed").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(fixedJson)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(jsonPath("$.jobId").value("jobId123")) - .andExpect(jsonPath("$.jobStatus").value("RUNNING")); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(jsonPath("$.jobId").value("jobId123")) + .andExpect(jsonPath("$.jobStatus").value("RUNNING")); mockPcapJob.setStatus(new JobStatus().withJobId("jobId123").withState(JobStatus.State.KILLED)); this.mockMvc.perform(delete(pcapUrl + "/kill/{id}", "jobId123").with(httpBasic(user, password))) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(jsonPath("$.jobId").value("jobId123")) - .andExpect(jsonPath("$.jobStatus").value("KILLED")); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(jsonPath("$.jobId").value("jobId123")) + .andExpect(jsonPath("$.jobStatus").value("KILLED")); mockPcapJob.setStatus(new JobStatus().withJobId("jobId").withState(JobStatus.State.KILLED)); } @@ -259,10 +259,47 @@ public class PcapControllerIntegrationTest { MockPcapJob mockPcapJob = (MockPcapJob) wac.getBean("mockPcapJob"); this.mockMvc.perform(get(pcapUrl + "/jobId123").with(httpBasic(user, password))) - .andExpect(status().isNotFound()); + .andExpect(status().isNotFound()); this.mockMvc.perform(delete(pcapUrl + "/kill/{id}", "jobId123").with(httpBasic(user, password))) - .andExpect(status().isNotFound()); + .andExpect(status().isNotFound()); + } + + @Test + public void testGetPdml() throws Exception { + MockPcapJob mockPcapJob = (MockPcapJob) wac.getBean("mockPcapJob"); + + mockPcapJob.setStatus(new JobStatus().withJobId("jobId").withState(JobStatus.State.RUNNING)); + + this.mockMvc.perform(post(pcapUrl + "/fixed").with(httpBasic(user, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(fixedJson)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(jsonPath("$.jobId").value("jobId")) + .andExpect(jsonPath("$.jobStatus").value("RUNNING")); + + Pageable<Path> pageable = new PcapPages(Arrays.asList(new Path("./target"))); + mockPcapJob.setIsDone(true); + mockPcapJob.setPageable(pageable); + + this.mockMvc.perform(get(pcapUrl + "/jobId/pdml?page=1").with(httpBasic(user, password))) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(jsonPath("$.version").value("0")) + .andExpect(jsonPath("$.creator").value("wireshark/2.6.1")) + .andExpect(jsonPath("$.time").value("Thu Jun 28 14:14:38 2018")) + .andExpect(jsonPath("$.captureFile").value("/tmp/pcap-data-201806272004-289365c53112438ca55ea047e13a12a5+0001.pcap")) + .andExpect(jsonPath("$.packets[0].protos[0].name").value("geninfo")) + .andExpect(jsonPath("$.packets[0].protos[0].fields[0].name").value("num")) + .andExpect(jsonPath("$.packets[0].protos[1].name").value("ip")) + .andExpect(jsonPath("$.packets[0].protos[1].fields[0].name").value("ip.addr")) + ; + + this.mockMvc.perform(get(pcapUrl + "/jobId/pdml?page=0").with(httpBasic(user, password))) + .andExpect(status().isNotFound()); + + this.mockMvc.perform(get(pcapUrl + "/jobId/pdml?page=2").with(httpBasic(user, password))) + .andExpect(status().isNotFound()); } + } http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/mock/MockPcapToPdmlScriptWrapper.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/mock/MockPcapToPdmlScriptWrapper.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/mock/MockPcapToPdmlScriptWrapper.java new file mode 100644 index 0000000..940648c --- /dev/null +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/mock/MockPcapToPdmlScriptWrapper.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.rest.mock; + +import org.adrianwalker.multilinestring.Multiline; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.metron.rest.service.impl.PcapToPdmlScriptWrapper; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class MockPcapToPdmlScriptWrapper extends PcapToPdmlScriptWrapper { + + /** + *<?xml version="1.0" encoding="utf-8"?> + *<?xml-stylesheet type="text/xsl" href="pdml2html.xsl"?> + *<pdml version="0" creator="wireshark/2.6.1" time="Thu Jun 28 14:14:38 2018" capture_file="/tmp/pcap-data-201806272004-289365c53112438ca55ea047e13a12a5+0001.pcap"> + *<packet> + *<proto name="geninfo" pos="0" showname="General information" size="722" hide="no"> + *<field name="num" pos="0" show="1" showname="Number" value="1" size="722"/> + *</proto> + *<proto name="ip" showname="Internet Protocol Version 4, Src: 192.168.66.1, Dst: 192.168.66.121" size="20" pos="14" hide="yes"> + *<field name="ip.addr" showname="Source or Destination Address: 192.168.66.121" hide="yes" size="4" pos="30" show="192.168.66.121" value="c0a84279"/> + *<field name="ip.flags" showname="Flags: 0x4000, Don't fragment" size="2" pos="20" show="0x00004000" value="4000"> + *<field name="ip.flags.mf" showname="..0. .... .... .... = More fragments: Not set" size="2" pos="20" show="0" value="0" unmaskedvalue="4000"/> + *</field> + *</proto> + *</packet> + *</pdml> + */ + @Multiline + private String pdmlXml; + + @Override + public InputStream execute(String scriptPath, FileSystem fileSystem, Path pcapPath) throws IOException { + return new ByteArrayInputStream(pdmlXml.getBytes()); + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/PcapServiceImplTest.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/PcapServiceImplTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/PcapServiceImplTest.java index 8b628b3..d818c77 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/PcapServiceImplTest.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/PcapServiceImplTest.java @@ -17,25 +17,15 @@ */ package org.apache.metron.rest.service.impl; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.powermock.api.mockito.PowerMockito.doReturn; - -import java.util.HashMap; -import java.util.Map; +import org.adrianwalker.multilinestring.Multiline; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; import org.apache.metron.common.Constants; +import org.apache.metron.common.utils.JSONUtils; import org.apache.metron.job.JobException; import org.apache.metron.job.JobNotFoundException; import org.apache.metron.job.JobStatus; -import org.apache.metron.job.JobStatus.State; import org.apache.metron.job.Pageable; import org.apache.metron.job.manager.InMemoryJobManager; import org.apache.metron.job.manager.JobManager; @@ -48,33 +38,154 @@ import org.apache.metron.rest.mock.MockPcapJob; import org.apache.metron.rest.mock.MockPcapJobSupplier; import org.apache.metron.rest.model.pcap.FixedPcapRequest; import org.apache.metron.rest.model.pcap.PcapStatus; +import org.apache.metron.rest.model.pcap.Pdml; import org.hamcrest.CoreMatchers; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; import org.springframework.core.env.Environment; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.anyVararg; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.powermock.api.mockito.PowerMockito.doReturn; +import static org.powermock.api.mockito.PowerMockito.whenNew; + @SuppressWarnings("ALL") +@RunWith(PowerMockRunner.class) +@PrepareForTest({PcapToPdmlScriptWrapper.class, ProcessBuilder.class}) public class PcapServiceImplTest { @Rule public final ExpectedException exception = ExpectedException.none(); + /** + *<?xml version="1.0" encoding="utf-8"?> + *<?xml-stylesheet type="text/xsl" href="pdml2html.xsl"?> + *<pdml version="0" creator="wireshark/2.6.1" time="Thu Jun 28 14:14:38 2018" capture_file="/tmp/pcap-data-201806272004-289365c53112438ca55ea047e13a12a5+0001.pcap"> + *<packet> + *<proto name="geninfo" pos="0" showname="General information" size="722" hide="no"> + *<field name="num" pos="0" show="1" showname="Number" value="1" size="722"/> + *</proto> + *<proto name="ip" showname="Internet Protocol Version 4, Src: 192.168.66.1, Dst: 192.168.66.121" size="20" pos="14" hide="yes"> + *<field name="ip.addr" showname="Source or Destination Address: 192.168.66.121" hide="yes" size="4" pos="30" show="192.168.66.121" value="c0a84279"/> + *<field name="ip.flags" showname="Flags: 0x4000, Don't fragment" size="2" pos="20" show="0x00004000" value="4000"> + *<field name="ip.flags.mf" showname="..0. .... .... .... = More fragments: Not set" size="2" pos="20" show="0" value="0" unmaskedvalue="4000"/> + *</field> + *</proto> + *</packet> + *</pdml> + */ + @Multiline + private String pdmlXml; + + /** + *{ + "version": "0", + "creator": "wireshark/2.6.1", + "time": "Thu Jun 28 14:14:38 2018", + "captureFile": "/tmp/pcap-data-201806272004-289365c53112438ca55ea047e13a12a5+0001.pcap", + "packets": [ + { + "protos": [ + { + "name": "geninfo", + "pos": "0", + "showname": "General information", + "size": "722", + "hide": "no", + "fields": [ + { + "name": "num", + "pos": "0", + "showname": "Number", + "size": "722", + "value": "1", + "show": "1" + } + ] + }, + { + "name": "ip", + "pos": "14", + "showname": "Internet Protocol Version 4, Src: 192.168.66.1, Dst: 192.168.66.121", + "size": "20", + "hide": "yes", + "fields": [ + { + "name": "ip.addr", + "pos": "30", + "showname": "Source or Destination Address: 192.168.66.121", + "size": "4", + "value": "c0a84279", + "show": "192.168.66.121", + "hide": "yes" + }, + { + "name": "ip.flags", + "pos": "20", + "showname": "Flags: 0x4000, Don't fragment", + "size": "2", + "value": "4000", + "show": "0x00004000", + "fields": [ + { + "name": "ip.flags.mf", + "pos": "20", + "showname": "..0. .... .... .... = More fragments: Not set", + "size": "2", + "value": "0", + "show": "0", + "unmaskedvalue": "4000" + } + ] + } + ] + } + ] + } + ] + } + */ + @Multiline + private String expectedPdml; + Environment environment; Configuration configuration; MockPcapJobSupplier mockPcapJobSupplier; + PcapToPdmlScriptWrapper pcapToPdmlScriptWrapper; @Before public void setUp() throws Exception { environment = mock(Environment.class); configuration = mock(Configuration.class); mockPcapJobSupplier = new MockPcapJobSupplier(); + pcapToPdmlScriptWrapper = new PcapToPdmlScriptWrapper(); when(environment.getProperty(MetronRestConstants.PCAP_BASE_PATH_SPRING_PROPERTY)).thenReturn("/base/path"); when(environment.getProperty(MetronRestConstants.PCAP_BASE_INTERIM_RESULT_PATH_SPRING_PROPERTY)).thenReturn("/base/interim/result/path"); when(environment.getProperty(MetronRestConstants.PCAP_FINAL_OUTPUT_PATH_SPRING_PROPERTY)).thenReturn("/final/output/path"); when(environment.getProperty(MetronRestConstants.PCAP_PAGE_SIZE_SPRING_PROPERTY)).thenReturn("100"); + when(environment.getProperty(MetronRestConstants.PCAP_PDML_SCRIPT_PATH_SPRING_PROPERTY)).thenReturn("/path/to/pdml/script"); } @Test @@ -97,7 +208,7 @@ public class PcapServiceImplTest { mockPcapJobSupplier.setMockPcapJob(mockPcapJob); JobManager jobManager = new InMemoryJobManager<>(); - PcapServiceImpl pcapService = spy(new PcapServiceImpl(environment, configuration, mockPcapJobSupplier, jobManager)); + PcapServiceImpl pcapService = spy(new PcapServiceImpl(environment, configuration, mockPcapJobSupplier, jobManager, pcapToPdmlScriptWrapper)); FileSystem fileSystem = mock(FileSystem.class); doReturn(fileSystem).when(pcapService).getFileSystem(); mockPcapJob.setStatus(new JobStatus() @@ -149,7 +260,7 @@ public class PcapServiceImplTest { mockPcapJobSupplier.setMockPcapJob(mockPcapJob); JobManager jobManager = new InMemoryJobManager<>(); - PcapServiceImpl pcapService = spy(new PcapServiceImpl(environment, configuration, mockPcapJobSupplier, jobManager)); + PcapServiceImpl pcapService = spy(new PcapServiceImpl(environment, configuration, mockPcapJobSupplier, jobManager, pcapToPdmlScriptWrapper)); FileSystem fileSystem = mock(FileSystem.class); doReturn(fileSystem).when(pcapService).getFileSystem(); mockPcapJob.setStatus(new JobStatus() @@ -184,7 +295,7 @@ public class PcapServiceImplTest { FixedPcapRequest fixedPcapRequest = new FixedPcapRequest(); JobManager jobManager = mock(JobManager.class); PcapJobSupplier pcapJobSupplier = new PcapJobSupplier(); - PcapServiceImpl pcapService = spy(new PcapServiceImpl(environment, configuration, pcapJobSupplier, jobManager)); + PcapServiceImpl pcapService = spy(new PcapServiceImpl(environment, configuration, pcapJobSupplier, jobManager, pcapToPdmlScriptWrapper)); FileSystem fileSystem = mock(FileSystem.class); doReturn(fileSystem).when(pcapService).getFileSystem(); when(jobManager.submit(pcapJobSupplier, "user")).thenThrow(new JobException("some job exception")); @@ -208,7 +319,7 @@ public class PcapServiceImplTest { when(mockPcapJob.get()).thenReturn(pageable); when(jobManager.getJob("user", "jobId")).thenReturn(mockPcapJob); - PcapServiceImpl pcapService = new PcapServiceImpl(environment, configuration, mockPcapJobSupplier, jobManager); + PcapServiceImpl pcapService = new PcapServiceImpl(environment, configuration, mockPcapJobSupplier, jobManager, pcapToPdmlScriptWrapper); PcapStatus expectedPcapStatus = new PcapStatus(); expectedPcapStatus.setJobId("jobId"); expectedPcapStatus.setJobStatus(JobStatus.State.SUCCEEDED.name()); @@ -222,7 +333,7 @@ public class PcapServiceImplTest { @Test public void getStatusShouldReturnNullOnMissingStatus() throws Exception { JobManager jobManager = new InMemoryJobManager(); - PcapServiceImpl pcapService = new PcapServiceImpl(environment, configuration, new PcapJobSupplier(), jobManager); + PcapServiceImpl pcapService = new PcapServiceImpl(environment, configuration, new PcapJobSupplier(), jobManager, pcapToPdmlScriptWrapper); Assert.assertNull(pcapService.getJobStatus("user", "jobId")); } @@ -235,7 +346,7 @@ public class PcapServiceImplTest { JobManager jobManager = mock(JobManager.class); when(jobManager.getJob("user", "jobId")).thenThrow(new JobException("some job exception")); - PcapServiceImpl pcapService = new PcapServiceImpl(environment, configuration, new PcapJobSupplier(), jobManager); + PcapServiceImpl pcapService = new PcapServiceImpl(environment, configuration, new PcapJobSupplier(), jobManager, pcapToPdmlScriptWrapper); pcapService.getJobStatus("user", "jobId"); } @@ -244,10 +355,10 @@ public class PcapServiceImplTest { MockPcapJob mockPcapJob = mock(MockPcapJob.class); JobManager jobManager = mock(JobManager.class); JobStatus actualJobStatus = new JobStatus() - .withJobId("jobId") - .withState(State.KILLED) - .withDescription("description") - .withPercentComplete(100.0); + .withJobId("jobId") + .withState(JobStatus.State.KILLED) + .withDescription("description") + .withPercentComplete(100.0); Pageable pageable = mock(Pageable.class); when(pageable.getSize()).thenReturn(0); when(mockPcapJob.getStatus()).thenReturn(actualJobStatus); @@ -255,10 +366,10 @@ public class PcapServiceImplTest { when(mockPcapJob.get()).thenReturn(pageable); when(jobManager.getJob("user", "jobId")).thenReturn(mockPcapJob); - PcapServiceImpl pcapService = new PcapServiceImpl(environment, configuration, mockPcapJobSupplier, jobManager); + PcapServiceImpl pcapService = new PcapServiceImpl(environment, configuration, mockPcapJobSupplier, jobManager, pcapToPdmlScriptWrapper); PcapStatus status = pcapService.killJob("user", "jobId"); verify(jobManager, times(1)).killJob("user", "jobId"); - assertThat(status.getJobStatus(), CoreMatchers.equalTo(State.KILLED.toString())); + assertThat(status.getJobStatus(), CoreMatchers.equalTo(JobStatus.State.KILLED.toString())); } @Test @@ -267,10 +378,97 @@ public class PcapServiceImplTest { JobManager jobManager = mock(JobManager.class); doThrow(new JobNotFoundException("Not found test exception.")).when(jobManager).killJob("user", "jobId"); - PcapServiceImpl pcapService = new PcapServiceImpl(environment, configuration, mockPcapJobSupplier, jobManager); + PcapServiceImpl pcapService = new PcapServiceImpl(environment, configuration, mockPcapJobSupplier, jobManager, pcapToPdmlScriptWrapper); PcapStatus status = pcapService.killJob("user", "jobId"); verify(jobManager, times(1)).killJob("user", "jobId"); assertNull(status); } + @Test + public void getPathShouldProperlyReturnPath() throws Exception { + Path actualPath = new Path("/path"); + MockPcapJob mockPcapJob = mock(MockPcapJob.class); + JobManager jobManager = mock(JobManager.class); + Pageable pageable = mock(Pageable.class); + PcapServiceImpl pcapService = new PcapServiceImpl(environment, configuration, new PcapJobSupplier(), jobManager, pcapToPdmlScriptWrapper); + + when(pageable.getSize()).thenReturn(2); + when(mockPcapJob.isDone()).thenReturn(true); + when(mockPcapJob.get()).thenReturn(pageable); + when(pageable.getPage(0)).thenReturn(actualPath); + when(jobManager.getJob("user", "jobId")).thenReturn(mockPcapJob); + + Assert.assertEquals("/path", pcapService.getPath("user", "jobId", 1).toUri().getPath()); + } + + @Test + public void getPathShouldReturnNullOnInvalidPageSize() throws Exception { + MockPcapJob mockPcapJob = mock(MockPcapJob.class); + JobManager jobManager = mock(JobManager.class); + Pageable pageable = mock(Pageable.class); + PcapServiceImpl pcapService = new PcapServiceImpl(environment, configuration, new PcapJobSupplier(), jobManager, pcapToPdmlScriptWrapper); + + when(pageable.getSize()).thenReturn(2); + when(mockPcapJob.isDone()).thenReturn(true); + when(mockPcapJob.get()).thenReturn(pageable); + when(jobManager.getJob("user", "jobId")).thenReturn(mockPcapJob); + + Assert.assertNull(pcapService.getPath("user", "jobId", 0)); + Assert.assertNull(pcapService.getPath("user", "jobId", 3)); + } + + @Test + public void getPdmlShouldGetPdml() throws Exception { + Path path = new Path("./target"); + PcapToPdmlScriptWrapper pcapToPdmlScriptWrapper = spy(new PcapToPdmlScriptWrapper()); + PcapServiceImpl pcapService = spy(new PcapServiceImpl(environment, configuration, new PcapJobSupplier(), new InMemoryJobManager<>(), pcapToPdmlScriptWrapper)); + FileSystem fileSystem = mock(FileSystem.class); + doReturn(fileSystem).when(pcapService).getFileSystem(); + when(fileSystem.exists(path)).thenReturn(true); + doReturn(path).when(pcapService).getPath("user", "jobId", 1); + doReturn(new ByteArrayInputStream(pdmlXml.getBytes())).when(pcapToPdmlScriptWrapper).getRawInputStream(fileSystem, path); + ProcessBuilder pb = PowerMockito.mock(ProcessBuilder.class); + Process p = PowerMockito.mock(Process.class); + OutputStream outputStream = new ByteArrayOutputStream(); + when(p.getOutputStream()).thenReturn(outputStream); + when(p.isAlive()).thenReturn(true); + when(p.getInputStream()).thenReturn(new ByteArrayInputStream(pdmlXml.getBytes())); + whenNew(ProcessBuilder.class).withParameterTypes(String[].class).withArguments(anyVararg()).thenReturn(pb); + PowerMockito.when(pb.start()).thenReturn(p); + + assertEquals(JSONUtils.INSTANCE.load(expectedPdml, Pdml.class), pcapService.getPdml("user", "jobId", 1)); + } + + @Test + public void getPdmlShouldReturnNullOnNonexistentPath() throws Exception { + Path path = new Path("/some/path"); + + PcapServiceImpl pcapService = spy(new PcapServiceImpl(environment, configuration, new PcapJobSupplier(), new InMemoryJobManager<>(), pcapToPdmlScriptWrapper)); + FileSystem fileSystem = mock(FileSystem.class); + doReturn(fileSystem).when(pcapService).getFileSystem(); + when(fileSystem.exists(path)).thenReturn(false); + doReturn(path).when(pcapService).getPath("user", "jobId", 1); + + assertNull(pcapService.getPdml("user", "jobId", 1)); + } + + @Test + public void getPdmlShouldThrowException() throws Exception { + exception.expect(RestException.class); + exception.expectMessage("some exception"); + + Path path = new Path("./target"); + PcapToPdmlScriptWrapper pcapToPdmlScriptWrapper = spy(new PcapToPdmlScriptWrapper()); + PcapServiceImpl pcapService = spy(new PcapServiceImpl(environment, configuration, new PcapJobSupplier(), new InMemoryJobManager<>(), pcapToPdmlScriptWrapper)); + FileSystem fileSystem = mock(FileSystem.class); + doReturn(fileSystem).when(pcapService).getFileSystem(); + when(fileSystem.exists(path)).thenReturn(true); + doReturn(path).when(pcapService).getPath("user", "jobId", 1); + ProcessBuilder pb = PowerMockito.mock(ProcessBuilder.class); + doReturn(pb).when(pcapToPdmlScriptWrapper).getProcessBuilder("/path/to/pdml/script", "target"); + PowerMockito.when(pb.start()).thenThrow(new IOException("some exception")); + + pcapService.getPdml("user", "jobId", 1); + } + } http://git-wip-us.apache.org/repos/asf/metron/blob/3e5ef41d/metron-interface/pom.xml ---------------------------------------------------------------------- diff --git a/metron-interface/pom.xml b/metron-interface/pom.xml index e6ccd2d..c8f863c 100644 --- a/metron-interface/pom.xml +++ b/metron-interface/pom.xml @@ -25,6 +25,9 @@ </parent> <description>Interfaces for Metron</description> <url>https://metron.apache.org/</url> + <properties> + <jackson.version>2.9.5</jackson.version> + </properties> <scm> <connection>scm:git:https://git-wip-us.apache.org/repos/asf/metron.git</connection> <developerConnection>scm:git:https://git-wip-us.apache.org/repos/asf/metron.git</developerConnection>