Develop snort IDS and content inspect service 25/54225/4
authorEddie Arrage <eddie.arrage@huawei.com>
Tue, 20 Mar 2018 23:51:35 +0000 (23:51 +0000)
committerEddie Arrage <eddie.arrage@huawei.com>
Sat, 31 Mar 2018 00:11:00 +0000 (00:11 +0000)
- Initial commit to show potential structure of a sample service
- This wil be part of a larger sample application currently dubbed
Service Delivery Controller
- Docker container needs to be built and employs open-source Linux packages
- Service is deployable in Istio service mesh using provided yaml
- Control snort daemon and add custom rules with GRPC messaging
- Process snort alerts actively and send to redis and upstream service
mesh components
- Integrates a web server for better HTTP signature detection

- Improved build script for CI with variables
- Render k8s yaml snort manifest dynamically with command
line options
- Improve snort_client sample script for runtime modifications
including passing args on CLI, error checking
- Update nginx proxy interface
- Added logging to snort server and alert process

Change-Id: Ic56f9fcd9ed21f64b84b85ac8ee280d69af7b7c9
Signed-off-by: Eddie Arrage <eddie.arrage@huawei.com>
17 files changed:
samples/scenarios/service_delivery_controller.yaml [new file with mode: 0644]
samples/services/snort_ids/docker/Dockerfile [new file with mode: 0644]
samples/services/snort_ids/docker/build.sh [new file with mode: 0755]
samples/services/snort_ids/docker/grpc/build_proto.sh [new file with mode: 0755]
samples/services/snort_ids/docker/grpc/nginx_pb2.py [new file with mode: 0644]
samples/services/snort_ids/docker/grpc/nginx_pb2_grpc.py [new file with mode: 0644]
samples/services/snort_ids/docker/grpc/snort.proto [new file with mode: 0644]
samples/services/snort_ids/docker/grpc/snort_alerts.py [new file with mode: 0644]
samples/services/snort_ids/docker/grpc/snort_client.py [new file with mode: 0644]
samples/services/snort_ids/docker/grpc/snort_pb2.py [new file with mode: 0644]
samples/services/snort_ids/docker/grpc/snort_pb2_grpc.py [new file with mode: 0644]
samples/services/snort_ids/docker/grpc/snort_server.py [new file with mode: 0644]
samples/services/snort_ids/docker/process/alert_process.sh [new file with mode: 0755]
samples/services/snort_ids/docker/process/grpc_process.sh [new file with mode: 0755]
samples/services/snort_ids/docker/process/start_process.sh [new file with mode: 0755]
samples/services/snort_ids/yaml/manifest.template [new file with mode: 0644]
samples/services/snort_ids/yaml/render_yaml.py [new file with mode: 0644]

diff --git a/samples/scenarios/service_delivery_controller.yaml b/samples/scenarios/service_delivery_controller.yaml
new file mode 100644 (file)
index 0000000..c3a9411
--- /dev/null
@@ -0,0 +1 @@
+<Toplevel yaml for entire sample scenario (applciation) goes here>
diff --git a/samples/services/snort_ids/docker/Dockerfile b/samples/services/snort_ids/docker/Dockerfile
new file mode 100644 (file)
index 0000000..50686ed
--- /dev/null
@@ -0,0 +1,77 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+FROM fedora:27
+# Use fedora base for snort
+LABEL maintainer="Eddie Arrage" maintainer_email="eddie.arrage@huawei.com"
+LABEL version="0.1" description="Clover - Snort IDS / Content Inspect Service"
+
+RUN \
+    yum update -y \
+&& \
+    yum install -y \
+# Core snort install elements
+    https://www.snort.org/downloads/snort/daq-2.0.6-1.f21.x86_64.rpm \
+    https://www.snort.org/downloads/snort/snort-2.9.11.1-1.f25.x86_64.rpm \
+# Debug packages that aren't required for normal operation
+    wget libdnet net-tools iputils procps \
+    python-pip \
+# For snort to process inbound http client traffic, install nginx server
+    nginx \
+    ldconfig \
+&& \
+    mkdir -p /etc/snort/rules \
+&& \
+    mkdir /usr/local/lib/snort_dynamicrules \
+&& \
+    chmod -R 5775 /etc/snort \
+&& \
+    chmod -R 5775 /var/log/snort \
+&& \
+    chmod -R 5775 /usr/local/lib/snort_dynamicrules \
+&& \
+    chown -R snort:snort /etc/snort \
+&& \
+    chown -R snort:snort /var/log/snort \
+&& \
+    chown -R snort:snort /usr/local/lib/snort_dynamicrules \
+&& \
+    touch /etc/snort/rules/white_list.rules \
+&& \
+    touch /etc/snort/rules/black_list.rules \
+&& \
+    touch /etc/snort/rules/local.rules \
+# Install snort community rules for now
+&& \
+    wget https://www.snort.org/rules/community -O ~/community.tar.gz \
+&& \
+    tar -xvf ~/community.tar.gz -C ~/ \
+&& \
+    cp ~/community-rules/* /etc/snort/rules \
+&& \
+# Modify snort.conf file
+    sed -i 's/include \$RULE\_PATH/#include \$RULE\_PATH/' /etc/snort/snort.conf \
+&& \
+    sed -i 's/var WHITE\_LIST\_PATH \.\.\/rules/var WHITE\_LIST\_PATH \/etc\/snort\/rules/' /etc/snort/snort.conf \
+&& \
+    sed -i 's/var BLACK\_LIST\_PATH \.\.\/rules/var BLACK\_LIST\_PATH \/etc\/snort\/rules/' /etc/snort/snort.conf \
+&& \
+    sed -i 's/\# output unified2\: filename merged\.log, limit 128, nostamp, mpls\_event\_types, vlan\_event\_types/output unified2\: filename snort\.log, limit 128/' /etc/snort/snort.conf \
+&& \
+    sed -i 's/\#include \$RULE\_PATH\/local\.rules/include \$RULE\_PATH\/local\.rules\ninclude \$RULE\_PATH\/community\.rules/' /etc/snort/snort.conf \
+&& \
+    cd /usr/lib64 \
+&& \
+# Account for libdnet issue
+    ln -s libdnet.so.1.0.1 libdnet.1 \
+&& \
+# Install required python libraries
+    python -m pip install grpcio redis idstools
+
+COPY /process /process
+COPY /grpc /grpc
+CMD ./process/start_process.sh
diff --git a/samples/services/snort_ids/docker/build.sh b/samples/services/snort_ids/docker/build.sh
new file mode 100755 (executable)
index 0000000..e587d8b
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+IMAGE_PATH=${IMAGE_PATH:-"localhost:5000"}
+IMAGE_NAME=${IMAGE_NAME:-"clover-ns-snort-ids"}
+
+docker build -t $IMAGE_NAME .
+docker tag $IMAGE_NAME $IMAGE_PATH/$IMAGE_NAME
+docker push $IMAGE_PATH/$IMAGE_NAME
diff --git a/samples/services/snort_ids/docker/grpc/build_proto.sh b/samples/services/snort_ids/docker/grpc/build_proto.sh
new file mode 100755 (executable)
index 0000000..69bfeb7
--- /dev/null
@@ -0,0 +1,9 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+
+python -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. snort.proto
diff --git a/samples/services/snort_ids/docker/grpc/nginx_pb2.py b/samples/services/snort_ids/docker/grpc/nginx_pb2.py
new file mode 100644 (file)
index 0000000..3600b6d
--- /dev/null
@@ -0,0 +1,360 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: nginx.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='nginx.proto',
+  package='nginx',
+  syntax='proto3',
+  serialized_pb=_b('\n\x0bnginx.proto\x12\x05nginx\"3\n\x0c\x41lertMessage\x12\x10\n\x08\x65vent_id\x18\x01 \x01(\t\x12\x11\n\tredis_key\x18\x02 \x01(\t\"w\n\x0b\x43onfigProxy\x12\x13\n\x0bserver_port\x18\x01 \x01(\t\x12\x13\n\x0bserver_name\x18\x02 \x01(\t\x12\x15\n\rlocation_path\x18\x03 \x01(\t\x12\x12\n\nproxy_path\x18\x04 \x01(\t\x12\x13\n\x0bmirror_path\x18\x05 \x01(\t\"_\n\x0c\x43onfigServer\x12\x13\n\x0bserver_port\x18\x01 \x01(\t\x12\x13\n\x0bserver_name\x18\x02 \x01(\t\x12\x11\n\tsite_root\x18\x03 \x01(\t\x12\x12\n\nsite_index\x18\x04 \x01(\t\"j\n\x08\x43onfigLB\x12\x13\n\x0bserver_port\x18\x01 \x01(\t\x12\x13\n\x0bserver_name\x18\x02 \x01(\t\x12\x10\n\x08slb_list\x18\x03 \x01(\t\x12\x11\n\tslb_group\x18\x04 \x01(\t\x12\x0f\n\x07lb_path\x18\x05 \x01(\t\"\x1d\n\nNginxReply\x12\x0f\n\x07message\x18\x01 \x01(\t2\xeb\x01\n\nController\x12\x36\n\x0bModifyProxy\x12\x12.nginx.ConfigProxy\x1a\x11.nginx.NginxReply\"\x00\x12\x38\n\x0cModifyServer\x12\x13.nginx.ConfigServer\x1a\x11.nginx.NginxReply\"\x00\x12\x30\n\x08ModifyLB\x12\x0f.nginx.ConfigLB\x1a\x11.nginx.NginxReply\"\x00\x12\x39\n\rProcessAlerts\x12\x13.nginx.AlertMessage\x1a\x11.nginx.NginxReply\"\x00\x62\x06proto3')
+)
+
+
+
+
+_ALERTMESSAGE = _descriptor.Descriptor(
+  name='AlertMessage',
+  full_name='nginx.AlertMessage',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='event_id', full_name='nginx.AlertMessage.event_id', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='redis_key', full_name='nginx.AlertMessage.redis_key', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=22,
+  serialized_end=73,
+)
+
+
+_CONFIGPROXY = _descriptor.Descriptor(
+  name='ConfigProxy',
+  full_name='nginx.ConfigProxy',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='server_port', full_name='nginx.ConfigProxy.server_port', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='server_name', full_name='nginx.ConfigProxy.server_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='location_path', full_name='nginx.ConfigProxy.location_path', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='proxy_path', full_name='nginx.ConfigProxy.proxy_path', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='mirror_path', full_name='nginx.ConfigProxy.mirror_path', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=75,
+  serialized_end=194,
+)
+
+
+_CONFIGSERVER = _descriptor.Descriptor(
+  name='ConfigServer',
+  full_name='nginx.ConfigServer',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='server_port', full_name='nginx.ConfigServer.server_port', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='server_name', full_name='nginx.ConfigServer.server_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='site_root', full_name='nginx.ConfigServer.site_root', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='site_index', full_name='nginx.ConfigServer.site_index', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=196,
+  serialized_end=291,
+)
+
+
+_CONFIGLB = _descriptor.Descriptor(
+  name='ConfigLB',
+  full_name='nginx.ConfigLB',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='server_port', full_name='nginx.ConfigLB.server_port', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='server_name', full_name='nginx.ConfigLB.server_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='slb_list', full_name='nginx.ConfigLB.slb_list', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='slb_group', full_name='nginx.ConfigLB.slb_group', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='lb_path', full_name='nginx.ConfigLB.lb_path', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=293,
+  serialized_end=399,
+)
+
+
+_NGINXREPLY = _descriptor.Descriptor(
+  name='NginxReply',
+  full_name='nginx.NginxReply',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='message', full_name='nginx.NginxReply.message', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=401,
+  serialized_end=430,
+)
+
+DESCRIPTOR.message_types_by_name['AlertMessage'] = _ALERTMESSAGE
+DESCRIPTOR.message_types_by_name['ConfigProxy'] = _CONFIGPROXY
+DESCRIPTOR.message_types_by_name['ConfigServer'] = _CONFIGSERVER
+DESCRIPTOR.message_types_by_name['ConfigLB'] = _CONFIGLB
+DESCRIPTOR.message_types_by_name['NginxReply'] = _NGINXREPLY
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+AlertMessage = _reflection.GeneratedProtocolMessageType('AlertMessage', (_message.Message,), dict(
+  DESCRIPTOR = _ALERTMESSAGE,
+  __module__ = 'nginx_pb2'
+  # @@protoc_insertion_point(class_scope:nginx.AlertMessage)
+  ))
+_sym_db.RegisterMessage(AlertMessage)
+
+ConfigProxy = _reflection.GeneratedProtocolMessageType('ConfigProxy', (_message.Message,), dict(
+  DESCRIPTOR = _CONFIGPROXY,
+  __module__ = 'nginx_pb2'
+  # @@protoc_insertion_point(class_scope:nginx.ConfigProxy)
+  ))
+_sym_db.RegisterMessage(ConfigProxy)
+
+ConfigServer = _reflection.GeneratedProtocolMessageType('ConfigServer', (_message.Message,), dict(
+  DESCRIPTOR = _CONFIGSERVER,
+  __module__ = 'nginx_pb2'
+  # @@protoc_insertion_point(class_scope:nginx.ConfigServer)
+  ))
+_sym_db.RegisterMessage(ConfigServer)
+
+ConfigLB = _reflection.GeneratedProtocolMessageType('ConfigLB', (_message.Message,), dict(
+  DESCRIPTOR = _CONFIGLB,
+  __module__ = 'nginx_pb2'
+  # @@protoc_insertion_point(class_scope:nginx.ConfigLB)
+  ))
+_sym_db.RegisterMessage(ConfigLB)
+
+NginxReply = _reflection.GeneratedProtocolMessageType('NginxReply', (_message.Message,), dict(
+  DESCRIPTOR = _NGINXREPLY,
+  __module__ = 'nginx_pb2'
+  # @@protoc_insertion_point(class_scope:nginx.NginxReply)
+  ))
+_sym_db.RegisterMessage(NginxReply)
+
+
+
+_CONTROLLER = _descriptor.ServiceDescriptor(
+  name='Controller',
+  full_name='nginx.Controller',
+  file=DESCRIPTOR,
+  index=0,
+  options=None,
+  serialized_start=433,
+  serialized_end=668,
+  methods=[
+  _descriptor.MethodDescriptor(
+    name='ModifyProxy',
+    full_name='nginx.Controller.ModifyProxy',
+    index=0,
+    containing_service=None,
+    input_type=_CONFIGPROXY,
+    output_type=_NGINXREPLY,
+    options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ModifyServer',
+    full_name='nginx.Controller.ModifyServer',
+    index=1,
+    containing_service=None,
+    input_type=_CONFIGSERVER,
+    output_type=_NGINXREPLY,
+    options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ModifyLB',
+    full_name='nginx.Controller.ModifyLB',
+    index=2,
+    containing_service=None,
+    input_type=_CONFIGLB,
+    output_type=_NGINXREPLY,
+    options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ProcessAlerts',
+    full_name='nginx.Controller.ProcessAlerts',
+    index=3,
+    containing_service=None,
+    input_type=_ALERTMESSAGE,
+    output_type=_NGINXREPLY,
+    options=None,
+  ),
+])
+_sym_db.RegisterServiceDescriptor(_CONTROLLER)
+
+DESCRIPTOR.services_by_name['Controller'] = _CONTROLLER
+
+# @@protoc_insertion_point(module_scope)
diff --git a/samples/services/snort_ids/docker/grpc/nginx_pb2_grpc.py b/samples/services/snort_ids/docker/grpc/nginx_pb2_grpc.py
new file mode 100644 (file)
index 0000000..2b36fc0
--- /dev/null
@@ -0,0 +1,97 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+import grpc
+
+import nginx_pb2 as nginx__pb2
+
+
+class ControllerStub(object):
+  """The controller service definition.
+  """
+
+  def __init__(self, channel):
+    """Constructor.
+
+    Args:
+      channel: A grpc.Channel.
+    """
+    self.ModifyProxy = channel.unary_unary(
+        '/nginx.Controller/ModifyProxy',
+        request_serializer=nginx__pb2.ConfigProxy.SerializeToString,
+        response_deserializer=nginx__pb2.NginxReply.FromString,
+        )
+    self.ModifyServer = channel.unary_unary(
+        '/nginx.Controller/ModifyServer',
+        request_serializer=nginx__pb2.ConfigServer.SerializeToString,
+        response_deserializer=nginx__pb2.NginxReply.FromString,
+        )
+    self.ModifyLB = channel.unary_unary(
+        '/nginx.Controller/ModifyLB',
+        request_serializer=nginx__pb2.ConfigLB.SerializeToString,
+        response_deserializer=nginx__pb2.NginxReply.FromString,
+        )
+    self.ProcessAlerts = channel.unary_unary(
+        '/nginx.Controller/ProcessAlerts',
+        request_serializer=nginx__pb2.AlertMessage.SerializeToString,
+        response_deserializer=nginx__pb2.NginxReply.FromString,
+        )
+
+
+class ControllerServicer(object):
+  """The controller service definition.
+  """
+
+  def ModifyProxy(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def ModifyServer(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def ModifyLB(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def ProcessAlerts(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+
+def add_ControllerServicer_to_server(servicer, server):
+  rpc_method_handlers = {
+      'ModifyProxy': grpc.unary_unary_rpc_method_handler(
+          servicer.ModifyProxy,
+          request_deserializer=nginx__pb2.ConfigProxy.FromString,
+          response_serializer=nginx__pb2.NginxReply.SerializeToString,
+      ),
+      'ModifyServer': grpc.unary_unary_rpc_method_handler(
+          servicer.ModifyServer,
+          request_deserializer=nginx__pb2.ConfigServer.FromString,
+          response_serializer=nginx__pb2.NginxReply.SerializeToString,
+      ),
+      'ModifyLB': grpc.unary_unary_rpc_method_handler(
+          servicer.ModifyLB,
+          request_deserializer=nginx__pb2.ConfigLB.FromString,
+          response_serializer=nginx__pb2.NginxReply.SerializeToString,
+      ),
+      'ProcessAlerts': grpc.unary_unary_rpc_method_handler(
+          servicer.ProcessAlerts,
+          request_deserializer=nginx__pb2.AlertMessage.FromString,
+          response_serializer=nginx__pb2.NginxReply.SerializeToString,
+      ),
+  }
+  generic_handler = grpc.method_handlers_generic_handler(
+      'nginx.Controller', rpc_method_handlers)
+  server.add_generic_rpc_handlers((generic_handler,))
diff --git a/samples/services/snort_ids/docker/grpc/snort.proto b/samples/services/snort_ids/docker/grpc/snort.proto
new file mode 100644 (file)
index 0000000..8d69baa
--- /dev/null
@@ -0,0 +1,36 @@
+// Copyright (c) Authors of Clover
+//
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Apache License, Version 2.0
+// which accompanies this distribution, and is available at
+// http://www.apache.org/licenses/LICENSE-2.0
+
+
+syntax = "proto3";
+package snort;
+
+service Controller {
+
+  rpc AddRules (AddRule) returns (SnortReply) {}
+  rpc StartSnort (ControlSnort) returns (SnortReply) {}
+  rpc StopSnort (ControlSnort) returns (SnortReply) {}
+}
+
+message ControlSnort {
+  string pid = 1;
+}
+
+message AddRule {
+  string protocol = 1;
+  string dest_port = 2;
+  string dest_ip = 3;
+  string src_port = 4;
+  string src_ip = 5;
+  string msg = 6;
+  string sid = 7;
+  string rev = 8;
+}
+
+message SnortReply {
+  string message = 1;
+}
diff --git a/samples/services/snort_ids/docker/grpc/snort_alerts.py b/samples/services/snort_ids/docker/grpc/snort_alerts.py
new file mode 100644 (file)
index 0000000..eda2bd6
--- /dev/null
@@ -0,0 +1,57 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+import redis
+import logging
+import grpc
+import nginx_pb2
+import nginx_pb2_grpc
+from idstools import unified2
+
+
+HOST_IP = 'redis'
+PROXY_GRPC = 'http-proxy:50054'
+
+logging.basicConfig(filename='alert.log', level=logging.DEBUG)
+
+connect = False
+while not connect:
+    try:
+        r = redis.StrictRedis(host=HOST_IP, port=6379, db=0)
+        r.delete('snort_events')
+        connect = True
+    except Exception as e:
+        logging.debug(e)
+        connect = False
+
+reader = unified2.SpoolRecordReader("/var/log/snort",
+                                    "", follow=True)
+
+
+def sendGrpcAlert(event_id, redis_key):
+    try:
+        channel = grpc.insecure_channel(PROXY_GRPC)
+        stub = nginx_pb2_grpc.ControllerStub(channel)
+        stub.ProcessAlerts(nginx_pb2.AlertMessage(
+            event_id=event_id, redis_key=redis_key))
+    except Exception as e:
+        logging.debug(e)
+
+
+for record in reader:
+    try:
+        if isinstance(record, unified2.Event):
+            snort_event = "snort_event:" + str(record['event-id'])
+            r.sadd('snort_events', str(record['event-id']))
+            r.hmset(snort_event, record)
+            sendGrpcAlert(str(record['event-id']), 'snort_events')
+        # elif isinstance(record, unified2.Packet):
+            # print("Packet:")
+        # elif isinstance(record, unified2.ExtraData):
+            # print("Extra-Data:")
+    except Exception as e:
+        logging.debug(e)
diff --git a/samples/services/snort_ids/docker/grpc/snort_client.py b/samples/services/snort_ids/docker/grpc/snort_client.py
new file mode 100644 (file)
index 0000000..d59b4ee
--- /dev/null
@@ -0,0 +1,106 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+from __future__ import print_function
+from kubernetes import client, config
+
+import grpc
+import argparse
+
+import snort_pb2
+import snort_pb2_grpc
+
+
+def run(args, grpc_port='50052'):
+    # get pod ip for grpc
+    pod_ip = get_podip(args['service_name'])
+    if pod_ip == '':
+        return "Can not find service: {}".format(args['service_name'])
+    snort_grpc = pod_ip + ':' + grpc_port
+    # snort_grpc = 'localhost:50052'
+    channel = grpc.insecure_channel(snort_grpc)
+    stub = snort_pb2_grpc.ControllerStub(channel)
+
+    # execute command in service
+    if args['cmd'] == 'addtcp':
+        return add_tcprule(stub)
+    elif args['cmd'] == 'addicmp':
+        return add_icmprule(stub)
+    elif args['cmd'] == 'start':
+        return start_snort(stub)
+    elif args['cmd'] == 'stop':
+        return stop_snort(stub)
+    else:
+        return "Invalid command: {}".format(args['cmd'])
+
+
+def get_podip(pod_name):
+    ip = ''
+    if pod_name != '':
+        config.load_kube_config()
+        v1 = client.CoreV1Api()
+        ret = v1.list_pod_for_all_namespaces(watch=False)
+        for i in ret.items:
+            if i.metadata.name.lower().find(pod_name.lower()) != -1:
+                print("Pod IP: {}".format(i.status.pod_ip))
+                ip = i.status.pod_ip
+                return str(ip)
+    return str(ip)
+
+
+def add_tcprule(stub):
+    try:
+        response = stub.AddRules(snort_pb2.AddRule(
+             protocol='tcp', dest_port='any', dest_ip='$HOME_NET',
+             src_port='any', src_ip='any', msg='tcp test', sid='10000002',
+             rev='001'))
+        print(stop_snort(stub))
+        print(start_snort(stub))
+    except Exception as e:
+        return e
+    return response.message
+
+
+def add_icmprule(stub):
+    try:
+        response = stub.AddRules(snort_pb2.AddRule(
+            protocol='icmp', dest_port='any', dest_ip='$HOME_NET',
+            src_port='any', src_ip='any', msg='icmp test', sid='10000001',
+            rev='001'))
+        print(stop_snort(stub))
+        print(start_snort(stub))
+    except Exception as e:
+        return e
+    return response.message
+
+
+def start_snort(stub):
+    try:
+        response = stub.StartSnort(snort_pb2.ControlSnort(pid='0'))
+    except Exception as e:
+        return e
+    return response.message
+
+
+def stop_snort(stub):
+    try:
+        response = stub.StopSnort(snort_pb2.ControlSnort(pid='0'))
+    except Exception as e:
+        return e
+    return response.message
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+            '--service_name', required=True,
+            help='Snort service/pod name to reconfigure')
+    parser.add_argument(
+            '--cmd', required=True,
+            help='Command to execute in snort service')
+    args = parser.parse_args()
+    print(run(vars(args)))
diff --git a/samples/services/snort_ids/docker/grpc/snort_pb2.py b/samples/services/snort_ids/docker/grpc/snort_pb2.py
new file mode 100644 (file)
index 0000000..93641ef
--- /dev/null
@@ -0,0 +1,238 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: snort.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='snort.proto',
+  package='snort',
+  syntax='proto3',
+  serialized_pb=_b('\n\x0bsnort.proto\x12\x05snort\"\x1b\n\x0c\x43ontrolSnort\x12\x0b\n\x03pid\x18\x01 \x01(\t\"\x88\x01\n\x07\x41\x64\x64Rule\x12\x10\n\x08protocol\x18\x01 \x01(\t\x12\x11\n\tdest_port\x18\x02 \x01(\t\x12\x0f\n\x07\x64\x65st_ip\x18\x03 \x01(\t\x12\x10\n\x08src_port\x18\x04 \x01(\t\x12\x0e\n\x06src_ip\x18\x05 \x01(\t\x12\x0b\n\x03msg\x18\x06 \x01(\t\x12\x0b\n\x03sid\x18\x07 \x01(\t\x12\x0b\n\x03rev\x18\x08 \x01(\t\"\x1d\n\nSnortReply\x12\x0f\n\x07message\x18\x01 \x01(\t2\xac\x01\n\nController\x12/\n\x08\x41\x64\x64Rules\x12\x0e.snort.AddRule\x1a\x11.snort.SnortReply\"\x00\x12\x36\n\nStartSnort\x12\x13.snort.ControlSnort\x1a\x11.snort.SnortReply\"\x00\x12\x35\n\tStopSnort\x12\x13.snort.ControlSnort\x1a\x11.snort.SnortReply\"\x00\x62\x06proto3')
+)
+
+
+
+
+_CONTROLSNORT = _descriptor.Descriptor(
+  name='ControlSnort',
+  full_name='snort.ControlSnort',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='pid', full_name='snort.ControlSnort.pid', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=22,
+  serialized_end=49,
+)
+
+
+_ADDRULE = _descriptor.Descriptor(
+  name='AddRule',
+  full_name='snort.AddRule',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='protocol', full_name='snort.AddRule.protocol', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='dest_port', full_name='snort.AddRule.dest_port', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='dest_ip', full_name='snort.AddRule.dest_ip', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='src_port', full_name='snort.AddRule.src_port', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='src_ip', full_name='snort.AddRule.src_ip', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='msg', full_name='snort.AddRule.msg', index=5,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='sid', full_name='snort.AddRule.sid', index=6,
+      number=7, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rev', full_name='snort.AddRule.rev', index=7,
+      number=8, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=52,
+  serialized_end=188,
+)
+
+
+_SNORTREPLY = _descriptor.Descriptor(
+  name='SnortReply',
+  full_name='snort.SnortReply',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='message', full_name='snort.SnortReply.message', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=190,
+  serialized_end=219,
+)
+
+DESCRIPTOR.message_types_by_name['ControlSnort'] = _CONTROLSNORT
+DESCRIPTOR.message_types_by_name['AddRule'] = _ADDRULE
+DESCRIPTOR.message_types_by_name['SnortReply'] = _SNORTREPLY
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+ControlSnort = _reflection.GeneratedProtocolMessageType('ControlSnort', (_message.Message,), dict(
+  DESCRIPTOR = _CONTROLSNORT,
+  __module__ = 'snort_pb2'
+  # @@protoc_insertion_point(class_scope:snort.ControlSnort)
+  ))
+_sym_db.RegisterMessage(ControlSnort)
+
+AddRule = _reflection.GeneratedProtocolMessageType('AddRule', (_message.Message,), dict(
+  DESCRIPTOR = _ADDRULE,
+  __module__ = 'snort_pb2'
+  # @@protoc_insertion_point(class_scope:snort.AddRule)
+  ))
+_sym_db.RegisterMessage(AddRule)
+
+SnortReply = _reflection.GeneratedProtocolMessageType('SnortReply', (_message.Message,), dict(
+  DESCRIPTOR = _SNORTREPLY,
+  __module__ = 'snort_pb2'
+  # @@protoc_insertion_point(class_scope:snort.SnortReply)
+  ))
+_sym_db.RegisterMessage(SnortReply)
+
+
+
+_CONTROLLER = _descriptor.ServiceDescriptor(
+  name='Controller',
+  full_name='snort.Controller',
+  file=DESCRIPTOR,
+  index=0,
+  options=None,
+  serialized_start=222,
+  serialized_end=394,
+  methods=[
+  _descriptor.MethodDescriptor(
+    name='AddRules',
+    full_name='snort.Controller.AddRules',
+    index=0,
+    containing_service=None,
+    input_type=_ADDRULE,
+    output_type=_SNORTREPLY,
+    options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='StartSnort',
+    full_name='snort.Controller.StartSnort',
+    index=1,
+    containing_service=None,
+    input_type=_CONTROLSNORT,
+    output_type=_SNORTREPLY,
+    options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='StopSnort',
+    full_name='snort.Controller.StopSnort',
+    index=2,
+    containing_service=None,
+    input_type=_CONTROLSNORT,
+    output_type=_SNORTREPLY,
+    options=None,
+  ),
+])
+_sym_db.RegisterServiceDescriptor(_CONTROLLER)
+
+DESCRIPTOR.services_by_name['Controller'] = _CONTROLLER
+
+# @@protoc_insertion_point(module_scope)
diff --git a/samples/services/snort_ids/docker/grpc/snort_pb2_grpc.py b/samples/services/snort_ids/docker/grpc/snort_pb2_grpc.py
new file mode 100644 (file)
index 0000000..4a04299
--- /dev/null
@@ -0,0 +1,80 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+import grpc
+
+import snort_pb2 as snort__pb2
+
+
+class ControllerStub(object):
+  # missing associated documentation comment in .proto file
+  pass
+
+  def __init__(self, channel):
+    """Constructor.
+
+    Args:
+      channel: A grpc.Channel.
+    """
+    self.AddRules = channel.unary_unary(
+        '/snort.Controller/AddRules',
+        request_serializer=snort__pb2.AddRule.SerializeToString,
+        response_deserializer=snort__pb2.SnortReply.FromString,
+        )
+    self.StartSnort = channel.unary_unary(
+        '/snort.Controller/StartSnort',
+        request_serializer=snort__pb2.ControlSnort.SerializeToString,
+        response_deserializer=snort__pb2.SnortReply.FromString,
+        )
+    self.StopSnort = channel.unary_unary(
+        '/snort.Controller/StopSnort',
+        request_serializer=snort__pb2.ControlSnort.SerializeToString,
+        response_deserializer=snort__pb2.SnortReply.FromString,
+        )
+
+
+class ControllerServicer(object):
+  # missing associated documentation comment in .proto file
+  pass
+
+  def AddRules(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def StartSnort(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def StopSnort(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+
+def add_ControllerServicer_to_server(servicer, server):
+  rpc_method_handlers = {
+      'AddRules': grpc.unary_unary_rpc_method_handler(
+          servicer.AddRules,
+          request_deserializer=snort__pb2.AddRule.FromString,
+          response_serializer=snort__pb2.SnortReply.SerializeToString,
+      ),
+      'StartSnort': grpc.unary_unary_rpc_method_handler(
+          servicer.StartSnort,
+          request_deserializer=snort__pb2.ControlSnort.FromString,
+          response_serializer=snort__pb2.SnortReply.SerializeToString,
+      ),
+      'StopSnort': grpc.unary_unary_rpc_method_handler(
+          servicer.StopSnort,
+          request_deserializer=snort__pb2.ControlSnort.FromString,
+          response_serializer=snort__pb2.SnortReply.SerializeToString,
+      ),
+  }
+  generic_handler = grpc.method_handlers_generic_handler(
+      'snort.Controller', rpc_method_handlers)
+  server.add_generic_rpc_handlers((generic_handler,))
diff --git a/samples/services/snort_ids/docker/grpc/snort_server.py b/samples/services/snort_ids/docker/grpc/snort_server.py
new file mode 100644 (file)
index 0000000..3c2fdb1
--- /dev/null
@@ -0,0 +1,98 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+from concurrent import futures
+import time
+
+import grpc
+import subprocess
+import os
+import logging
+
+import snort_pb2
+import snort_pb2_grpc
+
+_ONE_DAY_IN_SECONDS = 60 * 60 * 24
+GRPC_PORT = '[::]:50052'
+
+
+class Controller(snort_pb2_grpc.ControllerServicer):
+
+    def __init__(self):
+        logging.basicConfig(filename='snort.log', level=logging.DEBUG)
+        self.snort = 0
+        self.StartSnort("", "")
+
+    # Add custom rules
+    def AddRules(self, r, context):
+        try:
+            # file_local = 'testfile'
+            file_local = '/etc/snort/rules/local.rules'
+            f = open(file_local, 'a')
+            rule = 'alert {} {} {} -> {} {} '.format(
+                r.protocol, r.src_ip, r.src_port, r.dest_ip, r.dest_port) \
+                + '(msg:"{}"; sid:{}; rev:{};)\n'.format(r.msg, r.sid, r.rev)
+            f.write(rule)
+            f.close
+            msg = "Added to local rules"
+        except Exception as e:
+            msg = "Failed to add to local rules"
+            logging.debug(e)
+        return snort_pb2.SnortReply(message=msg)
+
+    def StartSnort(self, request, context):
+        try:
+            if self.snort == 0:
+                p = subprocess.Popen(
+                  ["snort -i eth0 -u snort -g snort -c /etc/snort/snort.conf \
+                                                -k none"], shell=True)
+                self.snort = p
+                msg = "Started Snort on pid: {}".format(p.pid)
+            else:
+                msg = "Snort already running"
+        except Exception as e:
+            self.snort = 0
+            logging.debug(e)
+            msg = "Failed to start Snort"
+        return snort_pb2.SnortReply(message=msg)
+
+    def StopSnort(self, request, context):
+        try:
+            subprocess.Popen.kill(self.snort)
+            msg1 = "Stopped Snort on pid: {}, ".format(self.snort.pid)
+            self.snort = 0
+        except Exception as e:
+            msg1 = "Failed to stop Snort, "
+            logging.debug(e)
+        try:
+            # clear logs
+            logPath = '/var/log/snort'
+            logList = os.listdir(logPath)
+            for logName in logList:
+                os.remove(logPath+"/"+logName)
+            msg2 = "Cleared Snort logs"
+        except Exception as e:
+            msg2 = "Failed to clear logs"
+            logging.debug(e)
+        msg = msg1 + msg2
+        return snort_pb2.SnortReply(message=msg)
+
+
+def serve():
+    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
+    snort_pb2_grpc.add_ControllerServicer_to_server(Controller(), server)
+    server.add_insecure_port(GRPC_PORT)
+    server.start()
+    try:
+        while True:
+            time.sleep(_ONE_DAY_IN_SECONDS)
+    except KeyboardInterrupt:
+        server.stop(0)
+
+
+if __name__ == '__main__':
+    serve()
diff --git a/samples/services/snort_ids/docker/process/alert_process.sh b/samples/services/snort_ids/docker/process/alert_process.sh
new file mode 100755 (executable)
index 0000000..6934c8a
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+# start nginx server to handle incoming http requests
+/usr/sbin/nginx &
+
+# Process snort alerts
+python grpc/snort_alerts.py
+
diff --git a/samples/services/snort_ids/docker/process/grpc_process.sh b/samples/services/snort_ids/docker/process/grpc_process.sh
new file mode 100755 (executable)
index 0000000..d58c468
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+python grpc/snort_server.py
+
diff --git a/samples/services/snort_ids/docker/process/start_process.sh b/samples/services/snort_ids/docker/process/start_process.sh
new file mode 100755 (executable)
index 0000000..8d94b80
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+# Alert script processes snort alerts
+./process/alert_process.sh &
+
+# Main script to start grpc server that controls snort
+./process/grpc_process.sh -D
+
diff --git a/samples/services/snort_ids/yaml/manifest.template b/samples/services/snort_ids/yaml/manifest.template
new file mode 100644 (file)
index 0000000..178765b
--- /dev/null
@@ -0,0 +1,38 @@
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  name: {{ deploy_name }}
+  labels:
+    app: {{ deploy_name }}
+spec:
+  template:
+    metadata:
+      labels:
+        app: {{ deploy_name }}
+    spec:
+      containers:
+        - name: {{ deploy_name }}
+          image: {{ image_path }}/{{ image_name }}:{{ image_tag }}
+          ports:
+           - containerPort: {{ grpc_port }}
+           - containerPort: {{ redis_port }}
+           - containerPort: {{ http_port }}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ deploy_name }}
+  labels:
+    app: {{ deploy_name }}
+spec:
+  ports:
+  - port: {{ grpc_port }}
+    name: grpc
+  - port: {{ redis_port }}
+    name: redis
+  - port: {{ http_port }}
+    name: http
+  selector:
+    app: {{ deploy_name }}
+---
diff --git a/samples/services/snort_ids/yaml/render_yaml.py b/samples/services/snort_ids/yaml/render_yaml.py
new file mode 100644 (file)
index 0000000..e23f540
--- /dev/null
@@ -0,0 +1,62 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+import argparse
+
+from jinja2 import Template
+
+
+def render_yaml(args):
+    template_file = 'manifest.template'
+    out_file = 'snort.yaml'
+
+    try:
+        with open(template_file) as f:
+            tmpl = Template(f.read())
+        output = tmpl.render(
+            image_path=args['image_path'],
+            image_name=args['image_name'],
+            image_tag=args['image_tag'],
+            deploy_name=args['deploy_name'],
+            grpc_port=args['grpc_port'],
+            redis_port=args['redis_port'],
+            http_port=args['http_port']
+        )
+        with open(out_file, "wb") as fh:
+            fh.write(output)
+        return "Generated manifest for {}".format(args['deploy_name'])
+    except Exception as e:
+        print(e)
+        return "Unable to generate manifest for {}".format(
+                                        args['deploy_name'])
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+            '--image_name', default='clover-ns-snort-ids',
+            help='The image name to use')
+    parser.add_argument(
+            '--image_path', default='localhost:5000',
+            help='The path to the images to use')
+    parser.add_argument(
+            '--image_tag', default='latest',
+            help='The image tag to use')
+    parser.add_argument(
+            '--deploy_name', default='snort-ids',
+            help='The k8s deploy name to use')
+    parser.add_argument(
+            '--redis_port', default='6379',
+            help='The redis port to connect to for alerts')
+    parser.add_argument(
+            '--http_port', default='80',
+            help='Analyze http traffic on this port')
+    parser.add_argument(
+            '--grpc_port', default='50052',
+            help='The image tag to use')
+    args = parser.parse_args()
+    print(render_yaml(vars(args)))