Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preloading Modules / Dockerization of FAME / Dockerception (combined PR of all changes, do not merge!) #67

Open
wants to merge 54 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
06da9ab
merged changes
leba-gd Jul 24, 2019
4861433
comment out debug port binding of fame-web
leba-gd Jul 24, 2019
3340d70
let celery drop privileges
leba-gd Jul 31, 2019
d2984a0
enforce bash for scripts
leba-gd Jul 31, 2019
27ece3c
cherry-pick ti module fix
leba-gd Jul 31, 2019
caa615f
fix module update logic
leba-gd Jul 31, 2019
2366265
fix preloading (broken merge)
leba-gd Jul 31, 2019
56b5056
clarify log
leba-gd Jul 31, 2019
605db70
fix uploading of preloaded files
leba-gd Jul 31, 2019
0c23514
ensure user home is present
leba-gd Aug 5, 2019
4b81f81
make sure base containers are always fresh
leba-gd Aug 5, 2019
a55a41a
fix module installation logic
leba-gd Aug 5, 2019
b522d5d
fix isolatedprocessing agent
leba-gd Aug 5, 2019
1e3f93a
make agent debugging easier
leba-gd Aug 5, 2019
ebc2f69
ensure FAME temp folder is present and has proper permissions
leba-gd Aug 5, 2019
9ecbb91
fix single_module.py
leba-gd Aug 5, 2019
055ff83
ensure workers reload when modules are toggled
leba-gd Aug 5, 2019
d98ca75
fix display bug
leba-gd Aug 5, 2019
891643a
rework docker containers
leba-gd Aug 12, 2019
4c2bef1
only save module if something changed
leba-gd Aug 12, 2019
4ffc0c3
rework module update logic
leba-gd Aug 12, 2019
c0fbe59
change cwd to the directory of install.sh
leba-gd Aug 12, 2019
dce2691
bugfix: use list logic for log instead of set
leba-gd Aug 12, 2019
51c7396
fix dropdown (show also preloading modules)
leba-gd Aug 12, 2019
1d4ae7a
bugfix: copy whole list instead of the reference
leba-gd Aug 12, 2019
56c6854
bugfix: preserve filenames after preloading
leba-gd Aug 12, 2019
d71893b
change fame-web container docu
leba-gd Aug 12, 2019
d231c59
implement docker inception on core side
leba-gd Aug 15, 2019
b734ab3
docu
leba-gd Aug 15, 2019
c4342c3
implement ldap authentication
leba-gd Dec 18, 2019
a794a88
add system view for stale and pending analyses
leba-gd Dec 18, 2019
361f9cb
collection of bugfixes
leba-gd Dec 18, 2019
13469d4
tweak repository update/install
leba-gd Dec 18, 2019
3bc582f
tweak installation of requirements
leba-gd Dec 18, 2019
8547eda
convenience feature: add anchors to directly jump to modules in list
leba-gd Dec 18, 2019
0ada515
removed commented lines
leba-gd Dec 18, 2019
c6cc72d
implement Gael's suggestions from review
leba-gd Dec 19, 2019
5cf8db2
optimize run scripts and dockerfiles
leba-gd Dec 19, 2019
97f4f4b
update celery config
leba-gd Dec 19, 2019
e2eb1e1
more indentation fixes
leba-gd Dec 20, 2019
11a0615
add missing env vars
leba-gd Dec 20, 2019
59ee23a
remove indentation
leba-gd Dec 24, 2019
97a1743
simplify preloading modules (only acts on hashes)
leba-gd Dec 24, 2019
635f6ba
fix: only execute elevated install tasks in docker
leba-gd May 4, 2020
676a76f
adjust docker-compose defaults
leba-gd May 4, 2020
d8d3c4d
remove trailing whitespaces
leba-gd May 4, 2020
26691dc
Merge remote-tracking branch 'fame/master'
leba-gd Nov 9, 2020
680b31a
remove ldap auth (was moved to ad auth)
leba-gd Nov 9, 2020
9ceb492
remove duplicate function
leba-gd Nov 9, 2020
fc4a928
remove old docker-related files
leba-gd Nov 11, 2020
ebdbd7d
remove unneeded check for empty name
leba-gd Nov 11, 2020
93412e0
move notifiy_new_comment to web view
leba-gd Nov 11, 2020
e6de6a1
html formatting
leba-gd Nov 11, 2020
a26b121
add PYTHONUNBUFFERED to Dockerfile
leba-gd Nov 11, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Dockerfile.base
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM python:2-slim-stretch

RUN apt-get update && apt-get install -y python python-dev python-pip git && \
useradd -s /bin/false -U fame -m && \
pip install --no-cache-dir virtualenv && \
rm -rf /var/lib/apt/lists/*

COPY . /fame
COPY docker/fame/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh

WORKDIR /fame
RUN utils/run.sh -m pip install --no-cache-dir -r requirements.txt

ENV FAME_DOCKER=1

VOLUME [ "/fame/conf" ]

ENTRYPOINT [ "docker-entrypoint.sh" ]
CMD [ "/bin/bash" ]
13 changes: 13 additions & 0 deletions Dockerfile.web
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM fame-base

ENV FAME_WORKER=0

COPY docker/fame/run.web.sh /fame/run.sh

RUN utils/run.sh -m pip install --no-cache-dir -r requirements-web.txt

EXPOSE 8080

VOLUME [ "/fame/fame/modules", "/fame/storage", "/fame/web/static/img/avatars" ]

CMD [ "/fame/run.sh" ]
9 changes: 9 additions & 0 deletions Dockerfile.worker
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM fame-base

ENV FAME_WORKER=1

COPY docker/fame/run.worker.sh /fame/run.sh

RUN utils/run.sh -m pip install --no-cache-dir -r requirements-worker.txt

CMD [ "/fame/run.sh" ]
15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.DEFAULT_GOAL := all

mongo:
docker build --pull -t fame-mongo docker/mongo/

base:
docker build --pull -t fame-base -f Dockerfile.base .

web: base
docker build -t fame-web -f Dockerfile.web .

worker: base
docker build -t fame-worker -f Dockerfile.worker .

all: mongo base web worker
10 changes: 6 additions & 4 deletions agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,11 @@ def run_each_with_type(self, target, target_type):
return self.each_with_type(target, target_type)
except IsolatedExceptions.ModuleExecutionError, e:
self.log("error", "Could not run on %s: %s" % (target, e))
self.log("debug", traceback.format_exc())
return False
except:
tb = traceback.format_exc()
self.log("error", "Could not run on %s.\n %s" % (target, tb))
except Exception:
self.log("error", "Could not run on %s.\n" % (target,))
self.log("debug", traceback.format_exc())
return False


Expand All @@ -134,6 +135,7 @@ def fake_module(path, klass):

sys.modules[path] = klass


fake_module('fame.core.module', IsolatedModule)
fake_module('fame.common.exceptions', IsolatedExceptions)

Expand Down Expand Up @@ -169,7 +171,7 @@ def set_module(self, name, config):
module = import_module('module')

for _, obj in inspect.getmembers(module, inspect.isclass):
if obj.name and obj.name == name:
if hasattr(obj, "name") and obj.name == name:
self.queue = Queue()
self.module = obj()

Expand Down
13 changes: 12 additions & 1 deletion celeryconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@

def connect_to_db(**kwargs):
fame_init()

if fame_config.is_worker:
lucebac marked this conversation as resolved.
Show resolved Hide resolved
from fame.core.user import User
worker_user = User.get(email="worker@fame")
if worker_user:
fame_config.api_key = worker_user['api_key']


if fame_config.is_worker:
lucebac marked this conversation as resolved.
Show resolved Hide resolved
try:
from celeryconfig_worker import *
except ImportError:
pass

signals.worker_process_init.connect(connect_to_db)
3 changes: 3 additions & 0 deletions celeryconfig_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@


CELERY_IMPORTS = ('fame.worker.analysis', 'fame.worker.repository')
84 changes: 84 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
version: '3.4'

networks:
fame_internal:
internal: true

gateway:

traefik_default:
external:
name: traefik_default

x-mongo-env: &mongo_env
MONGO_HOST: "fame-mongo"
MONGO_PORT: "27017"
MONGO_DB: "fame"
MONGO_USERNAME: "fame"
MONGO_PASSWORD: "super-secret-password"

secrets:
ssh_priv_key:
file: ./ssh/id_rsa

services:
fame-mongo:
image: fame-mongo
environment:
MONGO_INITDB_DATABASE: "fame"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are the username and password set ?

container_name: fame-mongo
restart: unless-stopped
volumes:
- /opt/fame-mongo:/data/db:z
command: --auth
networks:
- "fame_internal"

fame-web:
image: fame-web
container_name: fame-web
depends_on:
- fame-mongo
environment:
<<: *mongo_env
FAME_INSTALL_COMMUNITY_REPO: "1"
FAME_URL: "http://localhost/"
FAME_ADMIN_FULLNAME: "The Admin"
FAME_ADMIN_EMAIL: "[email protected]"
FAME_ADMIN_GROUPS: "cert"
FAME_ADMIN_DEFAULT_SHARING: ""
lucebac marked this conversation as resolved.
Show resolved Hide resolved
FAME_ADMIN_PERMISSIONS: ""
lucebac marked this conversation as resolved.
Show resolved Hide resolved
FAME_ADMIN_PASSWORD: "tercespot"
FAME_PUBLIC_KEY: "ssh-rsa [...]"
FAME_SECRET_KEY: "" # cat /dev/urandom | head -c 256 | sha512sum
volumes:
- /opt/fame-modules:/fame/fame/modules:z
- /opt/fame-storage:/fame/storage:z
- /opt/fame-avatars:/fame/web/static/img/avatars:z
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_default"
- "traefik.port=8080"
- "traefik.frontend.rule=Host: fame.example.com"
networks:
- "fame_internal"
- "traefik_default"
# ports:
# - "80:8080"
lucebac marked this conversation as resolved.
Show resolved Hide resolved
restart: unless-stopped
hostname: fame-web

fame-worker:
image: fame-worker
container_name: fame-worker
depends_on:
- fame-mongo
- fame-web
environment:
<<: *mongo_env
FAME_URL: "http://fame-web:8080/"
networks:
- "fame_internal"
- "gateway"
secrets:
- ssh_priv_key
9 changes: 9 additions & 0 deletions docker/fame/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash

echo "[+] Ensuring empty __init__.py in modules directory"
touch /fame/fame/modules/__init__.py

echo "[+] Adjusting permissions"
chown fame:fame /fame -R

exec "$@"
19 changes: 19 additions & 0 deletions docker/fame/run.web.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash

utils/run.sh utils/install_docker.py

echo "[+] Ensuring presence of uwsgi"
utils/run.sh -m pip install uwsgi > /dev/null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these steps be in the Dockerfile ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pip yes, install_docker no. install_docker requires a running database which is not the case during the Docker build.


TIMEOUT=60

echo "[+] Waiting $TIMEOUT seconds for MongoDB to come up"
python docker/wait-for.py fame-mongo 27017 $TIMEOUT
if [ "$?" -ne "0" ]; then
echo "[X] Could not connect to MongoDB instance - is it up and running?"
exit 1
fi

echo "[+] Running webserver"
chown fame:fame /fame -R
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this step be in the Dockerfile ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This step should not be in the Dockerfile in my opinion, because there are several volumes that are mounted beneath /fame that may have the wrong permissions for FAME to access it. Thus, we do the chown at runtime.

exec /fame/env/bin/uwsgi -H /fame/env --uid fame --http :8080 -w webserver --callable app
34 changes: 34 additions & 0 deletions docker/fame/run.worker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash

echo "[+] Setting up git user and email"
git config --global user.name "FAME Web"
git config --global user.email "[email protected]"

echo "[+] Ensuring presence of temp dir"
mkdir -p temp && chown fame:fame temp/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two steps look like they should be in the Dockerfile

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setting up git, yes. But I am not sure about the temp dir. Sure, we can create it in the Dockerfile but the chown is still necessary here.


if [ -f /run/secrets/ssh_priv_key ]; then
echo "[+] Copying SSH private key"
mkdir -p config
cp /run/secrets/ssh_priv_key conf/id_rsa
chown fame:fame conf -R
chmod 600 conf/id_rsa
fi

TIMEOUT=60

echo "[+] Waiting $TIMEOUT seconds for MongoDB to come up"
python docker/wait-for.py fame-mongo 27017 $TIMEOUT
if [ "$?" -ne "0" ]; then
echo "[X] Could not connect to MongoDB instance - is it up and running?"
exit 1
fi

echo "[+] Waiting $TIMEOUT seconds for web server to come up"
python docker/wait-for.py fame-web 8080 $TIMEOUT
if [ "$?" -ne "0" ]; then
echo "[X] Could not connect to web server instance - is it up and running?"
exit 1
fi

exec utils/run.sh worker.py -r 5 -c -- '--uid fame --gid fame'
3 changes: 3 additions & 0 deletions docker/mongo/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM mongo

COPY adduser.js /docker-entrypoint-initdb.d/adduser.js
9 changes: 9 additions & 0 deletions docker/mongo/adduser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use fame";
db.createUser({
user: "fame",
pwd: "super-secret-password",
roles: [
{ role: "readWrite", db: "fame" },
{ role: "dbOwner", db: "fame" }
]
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that you are using a dedicated MongoDB instance for FAME and that it is only available to the dedicated Docker network, do we really need to bother with this form of authentication ? I think it would probably be simpler to use MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD directly in the docker-compose.yml

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, you are perfectly right. But the main drawback I saw in that (and therefore chose not to use it) is that MONGO_INITDB_ROOT_USERNAME creates a root user that has full access rights to everything in the MongoDB. Using the adduser script I was able to create a user that has only access to the FAME databases.
You are right that this should have no impact on a dedicated instance for FAME, it becomes only relevant if the MongoDB is a shared instance. I leave that decision up to you.

58 changes: 58 additions & 0 deletions docker/wait-for.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env python2
"""
Taken from: http://code.activestate.com/recipes/576655-wait-for-network-service-to-appear/
"""
import errno
import socket
import sys


def wait_net_service(server, port, timeout=None):
""" Wait for network service to appear
@param timeout: in seconds, if None or 0 wait forever
@return: True of False, if timeout is None may return only True or
throw unhandled network exception
"""
s = socket.socket()
if timeout:
from time import time as now
# time module is needed to calc timeout shared between two exceptions
end = now() + timeout

while True:
try:
if timeout:
next_timeout = end - now()
if next_timeout < 0:
return False
else:
s.settimeout(next_timeout)

s.connect((server, port))

except socket.timeout:
# this exception occurs only if timeout is set
if timeout:
return False

except socket.error:
# just ignore anything else until we run into timeout
pass
else:
s.close()
return True


if __name__ == "__main__":
if len(sys.argv) not in [3, 4]:
print "Usage: %s <host> <port> [<timeout>]" % (sys.argv[0])
sys.exit(1)

timeout = 60
if len(sys.argv) == 4:
timeout = int(sys.argv[3])

if wait_net_service(sys.argv[1], int(sys.argv[2]), timeout):
sys.exit(0)
else:
sys.exit(1)
5 changes: 3 additions & 2 deletions docs/concept.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ FAME relies on modules to add functionality. Modules are actually Python classes

Several kind of modules can be created:

* ``PreloadingModule``: these modules can be used to preload the information provided to FAME for analysis. This, for example, includes downloading a sample file when only a hash could was provided to FAME.
* ``ProcessingModule``: this is where FAME's magic is. A ``ProcessingModule`` should define some automated analysis that can be performed on some types of files / analysis information.
* ``ReportingModule``: this kind of module enables reporting options, such as send analysis results by email, or post a Slack notification when the analysis is finished.
* ``ThreatIntelligenceModule``: this kind of modules acts on IOCs. a ``ThreatIntelligenceModule`` has two roles:
Expand All @@ -79,8 +80,8 @@ FAME relies on three components:

.. image:: /images/concept-architecture.png

Components can all be on the same server, or split across multiple servers.
Components can all be on the same server, or split across multiple servers. A dockerized version of FAME is also available.

The web server is where antivirus modules and threat intelligence modules are executed.

Everything else (processing modules, reporting modules and intelligence modules lookups) is executed on workers.
Everything else (preloading modules, processing modules, reporting modules and intelligence modules lookups) is executed on workers.
Loading