├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── __init__.py ├── app ├── __init__.py └── modules │ ├── Junos.py │ ├── PaloAlto.py │ ├── __init__.py │ ├── apikeymgmt.py │ ├── default-applications.json │ ├── firewall.py │ ├── firewalls.py │ ├── handler.py │ └── status.py ├── assimilator.conf ├── assimilator.crt ├── assimilator.key ├── assimilator.wsgi ├── assimilator_vhost.conf ├── docs ├── Makefile ├── build │ ├── .buildinfo │ ├── .doctrees │ │ ├── environment.pickle │ │ ├── index.doctree │ │ └── user │ │ │ ├── api.doctree │ │ │ ├── firewallmgmt.doctree │ │ │ ├── firststeps.doctree │ │ │ ├── install.doctree │ │ │ ├── intro.doctree │ │ │ ├── keymanagement.doctree │ │ │ ├── objects.doctree │ │ │ ├── route.doctree │ │ │ └── rules.doctree │ ├── .nojekyll │ ├── _sources │ │ ├── index.rst.txt │ │ └── user │ │ │ ├── api.rst.txt │ │ │ ├── firewallmgmt.rst.txt │ │ │ ├── firststeps.rst.txt │ │ │ ├── install.rst.txt │ │ │ ├── intro.rst.txt │ │ │ ├── keymanagement.rst.txt │ │ │ ├── objects.rst.txt │ │ │ ├── route.rst.txt │ │ │ └── rules.rst.txt │ ├── _static │ │ ├── ajax-loader.gif │ │ ├── alabaster.css │ │ ├── basic.css │ │ ├── comment-bright.png │ │ ├── comment-close.png │ │ ├── comment.png │ │ ├── css │ │ │ ├── badge_only.css │ │ │ └── theme.css │ │ ├── custom.css │ │ ├── doctools.js │ │ ├── down-pressed.png │ │ ├── down.png │ │ ├── file.png │ │ ├── fonts │ │ │ ├── Inconsolata-Bold.ttf │ │ │ ├── Inconsolata-Regular.ttf │ │ │ ├── Lato-Bold.ttf │ │ │ ├── Lato-Regular.ttf │ │ │ ├── RobotoSlab-Bold.ttf │ │ │ ├── RobotoSlab-Regular.ttf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ │ ├── jquery-3.1.0.js │ │ ├── jquery.js │ │ ├── js │ │ │ ├── modernizr.min.js │ │ │ └── theme.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── underscore-1.3.1.js │ │ ├── underscore.js │ │ ├── up-pressed.png │ │ ├── up.png │ │ └── websupport.js │ ├── genindex.html │ ├── index.html │ ├── objects.inv │ ├── search.html │ ├── searchindex.js │ └── user │ │ ├── api.html │ │ ├── firewallmgmt.html │ │ ├── firststeps.html │ │ ├── install.html │ │ ├── intro.html │ │ ├── keymanagement.html │ │ ├── objects.html │ │ ├── route.html │ │ └── rules.html └── source │ ├── conf.py │ ├── index.rst │ └── user │ ├── api.rst │ ├── firewallmgmt.rst │ ├── firststeps.rst │ ├── install.rst │ ├── intro.rst │ └── keymanagement.rst ├── entrypoint ├── generate_certificate.sh ├── install.sh ├── media └── logo.png ├── requirements.txt ├── run.py └── tests └── test_api.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # Environments 83 | .env 84 | .venv 85 | env/ 86 | venv/ 87 | ENV/ 88 | 89 | # Spyder project settings 90 | .spyderproject 91 | .spyproject 92 | 93 | # Rope project settings 94 | .ropeproject 95 | 96 | # mkdocs documentation 97 | /site 98 | 99 | # mypy 100 | .mypy_cache/ 101 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: python 3 | python: 4 | - '2.7' 5 | 6 | services: 7 | - docker 8 | 9 | install: 10 | - docker build -t assimilator . 11 | - docker run -d -p 443:443/tcp --name assimilator assimilator 12 | 13 | before_script: 14 | - pip install -r requirements.txt 15 | 16 | script: 17 | - docker ps | grep assimilator 18 | - py.test tests 19 | 20 | after_success: 21 | - if [ "$TRAVIS_BRANCH" == "docker" ]; then 22 | docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"; 23 | docker push videlanicolas/assimilator; 24 | fi 25 | 26 | deploy: 27 | provider: releases 28 | api_key: 29 | secure: "KBXbfaajhjplkD0UD8dA7GLVKc4nmMR5VBgqsncyw8aH71LbMsR9Z1MYjjvfCSvzm056dVWTg/w5mDVea62jzDtqSuFvzhAEp5Q23y6Bh4rftoB6ACVNqcQjSwFAm5M2edAed0bgk8U9mI1e9u3b5MWv9hiRT+Mz0j5C6yhwzEbtp23gwC8SwlF9S6WpadcogBSVE/IKxDASL2Nqf2YICIcq0ODAm1cqzR88ETu/ZTLKeL/S0mCBIPvn8xj3JhrjqZPLCPZAbX18IjIbXtAp9Kq+3hbYSfn/ifMlUD5R3J0A8cj4voye+DaalL3+x8kYwuTOF5QKlR1VyaTRhfaV4MTgHhILjGCe0HXFbx0hm8eWLlnjznnTOEgroTWEKQf1t7PGRk9HGgIbsZ3TooxZ97WFgGZkX4voYL8klnOmUKEVUkePY+elM2vs+d4Z00L/aliSQPE4e5vsdvu8vYNFR8hAl/lkDwJ4Oi4kRmEC4/HiWHuC9d8cigFu6VhIe1UIm5jWwna5X44CsOlVTf8Rhfqj0BFX1it8d1KUFcqnpBqCEJ0mOdLcitFFVcmEUSJlo73LWn32FNCQvZJWAonogFbOiLpBoISi1Shd0zzbw14q7WaMIhGfN2b57XdoCi16xrBO0yloDBImd0MeZhfX6UkIXPMJv/1Ko6bOnuEE3xc=" 30 | skip_cleanup: true 31 | on: 32 | repo: videlanicolas/assimilator 33 | tags: true 34 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:xenial 2 | 3 | #Install dependencies 4 | RUN apt-get update &&\ 5 | apt-get install -y apache2 libapache2-mod-wsgi openssl python-dev libxml2-dev libxslt1-dev python-pip lib32z1-dev libffi-dev && apt-get clean &&\ 6 | rm -rf /var/lib/apt/lists/* 7 | 8 | #Copy and install dependencies 9 | COPY requirements.txt /tmp/requirements.txt 10 | RUN pip install -r /tmp/requirements.txt 11 | 12 | #Version information and maintainer 13 | LABEL version:"1.2.3" maintainer:"Nicolas Videla" 14 | 15 | #Expose only SSL 16 | EXPOSE 443/tcp 17 | 18 | #Create directories 19 | RUN mkdir -p /var/www/assimilator/app /etc/apache2/ssl 20 | 21 | #Create log file 22 | RUN touch /var/log/assimilator.log 23 | 24 | #Date 25 | ARG CACHE_DATE=2016-01-01 26 | 27 | #Copy configuration 28 | COPY assimilator_vhost.conf /etc/apache2/sites-available/assimilator_vhost.conf 29 | COPY run.py /var/www/assimilator/run.py 30 | COPY assimilator.wsgi /var/www/assimilator/assimilator.wsgi 31 | COPY assimilator.conf /etc/assimilator/assimilator.conf 32 | 33 | #Create firewalls.json file 34 | RUN touch /etc/assimilator/firewalls.json 35 | 36 | #Create apikey storage 37 | RUN touch /etc/assimilator/api.key 38 | RUN touch /var/www/assimilator/__init__.py 39 | 40 | #Install assimilator 41 | COPY app/ /var/www/assimilator/app/ 42 | 43 | #Copy private RSA key 44 | COPY assimilator.key /etc/apache2/ssl/assimilator.key 45 | COPY assimilator.crt /etc/apache2/ssl/assimilator.crt 46 | 47 | #Assigning permissions 48 | RUN chown -R www-data:www-data /var/www/assimilator/ 49 | RUN chown www-data:www-data /etc/apache2/ssl/assimilator.key /etc/apache2/ssl/assimilator.crt /etc/apache2/sites-available/assimilator_vhost.conf /var/log/assimilator.log 50 | RUN chmod 600 /etc/assimilator/* 51 | 52 | #Enable mods 53 | RUN a2enmod ssl wsgi 54 | 55 | #Enable API 56 | RUN a2ensite assimilator_vhost 57 | 58 | #Run apache 59 | COPY entrypoint /usr/bin/entrypoint 60 | ENTRYPOINT entrypoint -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Assimilator 2 | 3 | [![Build Status](https://travis-ci.org/videlanicolas/assimilator.svg?branch=master)](https://travis-ci.org/videlanicolas/assimilator.svg?branch=master) 4 | 5 | The first restful API to control all firewall brands. Configure any firewall with restful API calls, no more manual rule configuration. Centralize all your firewalls into one API. 6 | 7 | ### Multiplatform 8 | 9 | - [x] Palo Alto 10 | - [x] Juniper 11 | - [ ] Cisco 12 | - [ ] Fortinet 13 | - [ ] Checkpoint 14 | - [ ] PfSense 15 | - [ ] AWS 16 | 17 | ### Authentication 18 | 19 | - API key through HTTP headers. 20 | - Flexible authorization, allow certain URI path with certain HTTP methods. 21 | 22 | ### JSON 23 | 24 | - All request/response body are in JSON. No more XML, plain text or custom responses. 25 | 26 | ### Python 27 | 28 | - Fully scripted in Python Flask. 29 | - Easy to update and add new modules. 30 | - Ready for any automatic task. 31 | 32 | ### Open Source 33 | 34 | - No more Panorama, CSM or NSM. 35 | - Integrates with Apache2 with mod WSGI. 36 | - Assimilator gives a full RESTful experience for free. 37 | 38 | ## How it works 39 | 40 | All firewalls share a common ground on their configuration, for example: 41 | 42 | - List of commands showing the actual configuration (AKA the running configuration). 43 | - Rules or policies filtering IP packets. 44 | - Objects: 45 | - Addresses (i.e. 10.1.1.1 <-> Administration_Server). 46 | - Address group (i.e. Administration_Farm <-> [ Administration_Server01 , Administration_Server02 ]). 47 | - Port or service (i.e. TCP/80 <-> http). 48 | - Port or service group (i.e. Application_ports <-> { TCP/6600 , TCP/6610 }). 49 | - Interfaces. 50 | - Zones. 51 | - Routing table. 52 | - PBR (policy based route). 53 | 54 | Assimilator makes it possible to configure via the five RESTful methods all these portions of configuration with JSON objects: 55 | 56 | - GET: Show the object. 57 | - POST: Add new object. 58 | - PATCH: Append new data to object. 59 | - PUT: Replace data in object. 60 | - DELETE: Remove object from configuration. 61 | 62 | #### URL Format 63 | /api/***site***/***resource*** 64 | 65 | #### Example 66 | ``` 67 | Request: GET /api/headquarters/config 68 | 69 | Response: HTTP 200 70 | {"config" : "<...>"} 71 | 72 | Request: POST /api/branch/rules 73 | {"name" : "Test01", "from" : "trust", "to" : "untrust", 74 | "source" : "10.1.1.1", "destination" : "8.8.8.8", "action" : "allow", 75 | "application" : "junos-dns-udp"} 76 | Response: HTTP 201 77 | {} 78 | Request: DELETE /api/branch1/rules 79 | {"name" : "Permit Any"} 80 | Response: HTTP 200 81 | {} 82 | 83 | Request: PUT /api/branch2/objects/address-group 84 | {"name" : "Admin_Servers", "members" : [ "Server02" ] } 85 | Response: HTTP 200 86 | {} 87 | 88 | Request: PATCH /api/paloalto/headquarters/route 89 | {"name" : "internal", "destination" : "10.0.0.0/8", "next-hop" : "172.16.1.2" } 90 | Response: HTTP 200 91 | {} 92 | ``` 93 | ## Installation 94 | With Docker (recommended): 95 | ```bash 96 | cd /opt 97 | git clone https://github.com/videlanicolas/assimilator && cd assimilator 98 | ./generate_certificate.sh 99 | docker build -t assimilator /opt/assimilator/ 100 | docker run -d -p 443:443/tcp assimilator 101 | ``` 102 | Without Docker: 103 | ```bash 104 | cd /opt 105 | git clone https://github.com/videlanicolas/assimilator && cd assimilator 106 | ./generate_certificate.sh 107 | sudo ./install.sh 108 | ``` 109 | 110 | ## Documentation 111 | Read the documentation. 112 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/__init__.py -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/app/__init__.py -------------------------------------------------------------------------------- /app/modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/app/modules/__init__.py -------------------------------------------------------------------------------- /app/modules/apikeymgmt.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from flask import request 3 | from functools import wraps 4 | from app.modules.handler import require_appkey 5 | import logging, ConfigParser, json, random, string 6 | 7 | #Get logger 8 | logger = logging.getLogger(__name__) 9 | 10 | def check_auth(username, password): 11 | config = ConfigParser.RawConfigParser() 12 | config.read('/etc/assimilator/assimilator.conf') 13 | if config.get('Key Management','type') == 'static': 14 | return config.get('Key Management','user') == username and config.get('Key Management','password') == password 15 | else: 16 | logger.error("No valid auth type in configuration file.") 17 | raise 18 | 19 | def requires_auth(f): 20 | @wraps(f) 21 | def decorated(*args, **kwargs): 22 | logger.info("{0} {1} {2} {3}".format(request.remote_addr, request.method, request.url, str(request.args))) 23 | logger.debug("data: {0}".format(str(request.form))) 24 | auth = request.authorization 25 | logger.debug('Check_auth: ' + str(auth)) 26 | if not auth or not check_auth(auth.username, auth.password): 27 | logger.warning("Unauthorized.") 28 | return {'error' : 'Unauthorized.'}, 401 29 | return f(*args, **kwargs) 30 | return decorated 31 | 32 | class mgmt_all(Resource): 33 | @requires_auth 34 | def get(self): 35 | config = ConfigParser.RawConfigParser() 36 | config.read('/etc/assimilator/assimilator.conf') 37 | try: 38 | apikeys = json.loads(open(config.get('General','apikeyfile')).read()) 39 | except (ValueError, IOError): 40 | return {}, 204 41 | except Exception as e: 42 | logger.error("Cannot JSON parse API key file.") 43 | else: 44 | return apikeys 45 | 46 | class mgmt(Resource): 47 | @requires_auth 48 | def get(self,id): 49 | config = ConfigParser.RawConfigParser() 50 | config.read('/etc/assimilator/assimilator.conf') 51 | try: 52 | apikeys = json.loads(open(config.get('General','apikeyfile')).read()) 53 | except Exception as e: 54 | logger.error("Cannot JSON parse API key file.") 55 | return {}, 204 56 | try: 57 | return apikeys[str(id)] 58 | except Exception as e: 59 | logger.warning("ID not found.") 60 | return {'error' : 'ID not found.'}, 404 61 | @requires_auth 62 | def post(self,id): 63 | config = ConfigParser.RawConfigParser() 64 | config.read('/etc/assimilator/assimilator.conf') 65 | try: 66 | apikeys = json.loads(open(config.get('General','apikeyfile')).read()) 67 | except Exception as e: 68 | logger.error("Cannot JSON parse API key file.") 69 | return {}, 204 70 | try: 71 | apikeys[str(id)]['token'].append({'path' : request.json['path'], 'method' : request.json['method']}) 72 | with open(config.get('General','apikeyfile'),'w') as f: 73 | json.dump(apikeys,f) 74 | return {'path' : request.json['path'], 'method' : request.json['method']} 75 | except Exception as e: 76 | logger.warning("Bad token format.") 77 | return {'error' : 'Bad token format.'}, 400 78 | @requires_auth 79 | def delete(self,id): 80 | config = ConfigParser.RawConfigParser() 81 | config.read('/etc/assimilator/assimilator.conf') 82 | try: 83 | apikeys = json.loads(open(config.get('General','apikeyfile')).read()) 84 | except Exception as e: 85 | logger.error("Cannot JSON parse API key file.") 86 | return {}, 204 87 | try: 88 | if str(id) not in apikeys: 89 | logger.warning("ID not found.") 90 | return {'error' : 'ID not found.'}, 404 91 | else: 92 | del apikeys[str(id)] 93 | with open(config.get('General','apikeyfile'),'w') as f: 94 | json.dump(apikeys,f) 95 | return request.json, 200 96 | except Exception as e: 97 | logger.warning("Exception found: {0}".format(str(e))) 98 | return {'error' : 'Unknown error.'}, 500 99 | class generate(Resource): 100 | @requires_auth 101 | def get(self): 102 | return {'error' : 'Use POST to generate apikey.'}, 404 103 | @requires_auth 104 | def post(self): 105 | config = ConfigParser.RawConfigParser() 106 | config.read('/etc/assimilator/assimilator.conf') 107 | try: 108 | apikeys = dict() 109 | apikeys = json.loads(open(config.get('General','apikeyfile')).read()) 110 | except ValueError: 111 | logger.warning("No JSON data on apikeyfile.") 112 | except Exception as e: 113 | logger.error("Cannot JSON parse API key file.") 114 | return {'error' : 'Error parsing apikeyfile.'}, 500 115 | try: 116 | if apikeys: 117 | aux = list() 118 | for k,v in apikeys.iteritems(): 119 | aux.append(int(k)) 120 | id = str(sorted(aux)[-1] + 1) 121 | else: 122 | id = "1" 123 | key = {id : {"token" : list(), "comment" : request.json['comment'] if 'comment' in request.json else None ,"key" : ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(100))}} 124 | apikeys[id] = key[id] 125 | with open(config.get('General','apikeyfile'),'w') as f: 126 | json.dump(apikeys,f) 127 | return key, 201 128 | except Exception as e: 129 | logger.warning("Exception found: {0}".format(str(e))) 130 | return {'error' : 'Invalid token format.'}, 400 -------------------------------------------------------------------------------- /app/modules/default-applications.json: -------------------------------------------------------------------------------- 1 | {"list" : [{"protocol": "tcp", "name": "junos-ftp", "port": "21"}, {"protocol": "udp", "name": "junos-tftp", "port": "69"}, {"protocol": "tcp", "name": "junos-rtsp", "port": "554"}, {"protocol": "tcp", "name": "junos-netbios-session", "port": "139"}, {"protocol": "tcp", "name": "junos-smb-session", "port": "445"}, {"protocol": "tcp", "name": "junos-ssh", "port": "22"}, {"protocol": "tcp", "name": "junos-telnet", "port": "23"}, {"protocol": "tcp", "name": "junos-smtp", "port": "25"}, {"protocol": "tcp", "name": "junos-tacacs", "port": "49"}, {"protocol": "tcp", "name": "junos-tacacs-ds", "port": "65"}, {"protocol": "udp", "name": "junos-dhcp-client", "port": "68"}, {"protocol": "udp", "name": "junos-dhcp-server", "port": "67"}, {"protocol": "udp", "name": "junos-bootpc", "port": "68"}, {"protocol": "udp", "name": "junos-bootps", "port": "67"}, {"protocol": "tcp", "name": "junos-finger", "port": "79"}, {"protocol": "tcp", "name": "junos-http", "port": "80"}, {"protocol": "tcp", "name": "junos-https", "port": "443"}, {"protocol": "tcp", "name": "junos-pop3", "port": "110"}, {"protocol": "tcp", "name": "junos-ident", "port": "113"}, {"protocol": "tcp", "name": "junos-nntp", "port": "119"}, {"protocol": "udp", "name": "junos-ntp", "port": "123"}, {"protocol": "tcp", "name": "junos-imap", "port": "143"}, {"protocol": "tcp", "name": "junos-imaps", "port": "993"}, {"protocol": "tcp", "name": "junos-bgp", "port": "179"}, {"protocol": "tcp", "name": "junos-ldap", "port": "389"}, {"protocol": "tcp", "name": "junos-snpp", "port": "444"}, {"protocol": "udp", "name": "junos-biff", "port": "512"}, {"protocol": "udp", "name": "junos-who", "port": "513"}, {"protocol": "udp", "name": "junos-syslog", "port": "514"}, {"protocol": "tcp", "name": "junos-printer", "port": "515"}, {"protocol": "udp", "name": "junos-rip", "port": "520"}, {"protocol": "udp", "name": "junos-radius", "port": "1812"}, {"protocol": "udp", "name": "junos-radacct", "port": "1813"}, {"protocol": "tcp", "name": "junos-nfsd-tcp", "port": "2049"}, {"protocol": "udp", "name": "junos-nfsd-udp", "port": "2049"}, {"protocol": "tcp", "name": "junos-cvspserver", "port": "2401"}, {"protocol": "tcp", "name": "junos-ldp-tcp", "port": "646"}, {"protocol": "udp", "name": "junos-ldp-udp", "port": "646"}, {"protocol": "tcp", "name": "junos-xnm-ssl", "port": "3220"}, {"protocol": "tcp", "name": "junos-xnm-clear-text", "port": "3221"}, {"protocol": "udp", "name": "junos-ike", "port": "500"}, {"protocol": "6", "name": "junos-aol", "port": "5190-5193"}, {"protocol": "udp", "name": "junos-chargen", "port": "19"}, {"protocol": "udp", "name": "junos-dhcp-relay", "port": "67"}, {"protocol": "udp", "name": "junos-discard", "port": "9"}, {"protocol": "udp", "name": "junos-dns-udp", "port": "53"}, {"protocol": "tcp", "name": "junos-dns-tcp", "port": "53"}, {"protocol": "udp", "name": "junos-echo", "port": "7"}, {"protocol": "tcp", "name": "junos-gopher", "port": "70"}, {"protocol": "udp", "name": "junos-gnutella", "port": "6346-6347"}, {"protocol": "tcp", "name": "junos-gprs-gtp-c-tcp", "port": "2123"}, {"protocol": "udp", "name": "junos-gprs-gtp-c-udp", "port": "2123"}, {"protocol": "tcp", "name": "junos-gprs-gtp-c", "port": "2123"}, {"protocol": "tcp", "name": "junos-gprs-gtp-u-tcp", "port": "2152"}, {"protocol": "udp", "name": "junos-gprs-gtp-u-udp", "port": "2152"}, {"protocol": "tcp", "name": "junos-gprs-gtp-", "port": "2152"}, {"protocol": "tcp", "name": "junos-gprs-gtp-v0-tcp", "port": "3386"}, {"protocol": "udp", "name": "junos-gprs-gtp-v0-udp", "port": "3386"}, {"protocol": "tcp", "name": "junos-gprs-gtp-v0", "port": "3386"}, {"protocol": "132", "name": "junos-gprs-sctp", "port": "0"}, {"protocol": "tcp", "name": "junos-http-ext", "port": "7001"}, {"protocol": "tcp", "name": "junos-internet-locator-service", "port": "389"}, {"protocol": "udp", "name": "junos-ike-nat", "port": "4500"}, {"protocol": "tcp", "name": "junos-irc", "port": "6660-6669"}, {"protocol": "udp", "name": "junos-l2tp", "port": "1701"}, {"protocol": "tcp", "name": "junos-lpr", "port": "515"}, {"protocol": "tcp", "name": "junos-mail", "port": "25"}, {"protocol": "tcp", "name": "junos-h323", "port": "1720"}, {"protocol": "udp", "name": "junos-mgcp-ua", "port": "2427"}, {"protocol": "udp", "name": "junos-mgcp-ca", "port": "2727"}, {"protocol": "tcp", "name": "junos-msn", "port": "1863"}, {"protocol": "tcp", "name": "junos-ms-rpc-tcp", "port": "135"}, {"protocol": "udp", "name": "junos-ms-rpc-udp", "port": "135"}, {"protocol": "tcp", "name": "junos-ms-sql", "port": "1433"}, {"protocol": "udp", "name": "junos-nbname", "port": "137"}, {"protocol": "udp", "name": "junos-nbds", "port": "138"}, {"protocol": "udp", "name": "junos-nfs", "port": "111"}, {"protocol": "tcp", "name": "junos-ns-global", "port": "15397"}, {"protocol": "tcp", "name": "junos-ns-global-pro", "port": "15397"}, {"protocol": "udp", "name": "junos-nsm", "port": "69"}, {"protocol": "udp", "name": "junos-pc-anywhere", "port": "5632"}, {"protocol": "tcp", "name": "junos-pptp", "port": "1723"}, {"protocol": "tcp", "name": "junos-realaudio", "port": "554"}, {"protocol": "tcp", "name": "junos-sccp", "port": "2000"}, {"protocol": "udp", "name": "junos-sip", "port": "5060"}, {"protocol": "tcp", "name": "junos-rsh", "port": "514"}, {"protocol": "tcp", "name": "junos-smb", "port": "139"}, {"protocol": "udp", "name": "junos-sql-monitor", "port": "1434"}, {"protocol": "tcp", "name": "junos-sqlnet-v1", "port": "1525"}, {"protocol": "tcp", "name": "junos-sqlnet-v2", "port": "1521"}, {"protocol": "tcp", "name": "junos-sun-rpc-tcp", "port": "111"}, {"protocol": "udp", "name": "junos-sun-rpc-udp", "port": "111"}, {"protocol": "udp", "name": "junos-talk", "port": "517"}, {"protocol": "udp", "name": "junos-ntalk", "port": "518"}, {"protocol": "udp", "name": "junos-uucp", "port": "540"}, {"protocol": "udp", "name": "junos-vdo-live", "port": "7000-7010"}, {"protocol": "tcp", "name": "junos-vnc", "port": "5800"}, {"protocol": "tcp", "name": "junos-wais", "port": "210"}, {"protocol": "tcp", "name": "junos-whois", "port": "43"}, {"protocol": "tcp", "name": "junos-winframe", "port": "1494"}, {"protocol": "tcp", "name": "junos-x-windows", "port": "6000-6063"}, {"protocol": "tcp", "name": "junos-ymsg", "port": "5000-5010"}, {"protocol": "tcp", "name": "junos-wxcontrol", "port": "3578"}, {"protocol": "tcp", "name": "junos-snmp-agentx", "port": "705"}, {"protocol": "udp", "name": "junos-stun", "port": "3478-3479"}, {"protocol": "255", "name": "junos-persistent-nat", "port": "65535"}, {"protocol": "udp", "name": "junos-r2cp", "port": "28672"}]} -------------------------------------------------------------------------------- /app/modules/firewall.py: -------------------------------------------------------------------------------- 1 | import logging, json, ConfigParser 2 | 3 | #Get logger 4 | logger = logging.getLogger(__name__) 5 | 6 | class Firewall(): 7 | def __init__(self,firewall): 8 | self.firewall = firewall 9 | def getConfig(self): 10 | config = ConfigParser.RawConfigParser() 11 | config.read("/etc/assimilator/assimilator.conf") 12 | return json.loads(open(config.get('General','firewalls')).read())[self.firewall] 13 | def getMaster(self): 14 | return self.firewall_config['primary'] 15 | def filter(self,args,_entries): 16 | #Filter algorithm 17 | for opt in args: 18 | filter = list() 19 | for entry in _entries: 20 | if opt in entry: 21 | if type(entry[opt]) == list: 22 | for e in entry[opt]: 23 | if args[opt].lower() in e.lower(): 24 | break 25 | else: 26 | filter.append(entry) 27 | elif type(entry[opt]) == bool: 28 | a = True if args[opt].lower() == 'true' else False if args[opt].lower() == 'false' else None 29 | if a == None or a != entry[opt]: 30 | filter.append(entry) 31 | elif type(entry[opt]) == dict: 32 | if json.loads(args[opt]) != entry[opt]: 33 | filter.append(entry) 34 | else: 35 | if args[opt].lower() not in entry[opt].lower(): 36 | filter.append(entry) 37 | else: 38 | filter.append(entry) 39 | for f in filter: 40 | del _entries[_entries.index(f)] 41 | return _entries -------------------------------------------------------------------------------- /app/modules/firewalls.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from flask import request 3 | from functools import wraps 4 | from app.modules.handler import require_appkey 5 | import logging, ConfigParser, json 6 | 7 | #Get logger 8 | logger = logging.getLogger(__name__) 9 | 10 | def check_auth(username, password): 11 | config = ConfigParser.RawConfigParser() 12 | config.read('/etc/assimilator/assimilator.conf') 13 | if config.get('Firewall Management','type') == 'static': 14 | return config.get('Firewall Management','user') == username and config.get('Firewall Management','password') == password 15 | else: 16 | logger.error("No valid auth type in configuration file.") 17 | raise 18 | 19 | def requires_auth(f): 20 | @wraps(f) 21 | def decorated(*args, **kwargs): 22 | logger.info("{0} {1} {2} {3}".format(request.remote_addr, request.method, request.url, str(request.args))) 23 | logger.debug("data: {0}".format(str(request.form))) 24 | auth = request.authorization 25 | logger.debug('Check_auth: ' + str(auth)) 26 | if not auth or not check_auth(auth.username, auth.password): 27 | logger.warning("Unauthorized.") 28 | return {'error' : 'Unauthorized.'}, 401 29 | return f(*args, **kwargs) 30 | return decorated 31 | 32 | class firewalls_all(Resource): 33 | @requires_auth 34 | def get(self): 35 | config = ConfigParser.RawConfigParser() 36 | config.read('/etc/assimilator/assimilator.conf') 37 | try: 38 | with open(config.get('General','firewalls')) as f: 39 | firewalls = json.loads(f.read()) 40 | except (ValueError, IOError): 41 | logger.warning("No data returned.") 42 | return {}, 204 43 | except Exception as e: 44 | logger.error("Cannot JSON parse Firewalls file.") 45 | return {'error' : 'Cannot JSON parse Firewalls file.'}, 500 46 | else: 47 | return firewalls 48 | @requires_auth 49 | def post(self): 50 | config = ConfigParser.RawConfigParser() 51 | config.read('/etc/assimilator/assimilator.conf') 52 | try: 53 | with open(config.get('General','firewalls')) as f: 54 | data = f.read() 55 | firewalls = json.loads(data) 56 | except ValueError: 57 | if data: 58 | logger.error("Cannot JSON parse Firewalls file.") 59 | return {'error' : 'Cannot JSON parse Firewalls file.'}, 500 60 | else: 61 | logger.info("No data on firewall file.") 62 | except Exception as e: 63 | logger.error("Exception while parsing Firewalls file.") 64 | return {'error' : 'Exception while parsing Firewalls file.'}, 500 65 | finally: 66 | firewalls = request.json 67 | try: 68 | with open(config.get('General','firewalls'),'w') as f: 69 | json.dump(firewalls,f) 70 | return request.json 71 | except Exception as e: 72 | logger.error("Exception while parsing Firewalls file.") 73 | return {'error' : 'Exception while parsing Firewalls file.'}, 500 74 | 75 | @requires_auth 76 | def delete(self): 77 | config = ConfigParser.RawConfigParser() 78 | config.read('/etc/assimilator/assimilator.conf') 79 | try: 80 | with open(config.get('General','firewalls')) as f: 81 | data = f.read() 82 | firewalls = json.loads(data) 83 | except ValueError: 84 | if not data: 85 | logger.error("Firewall file already empty.") 86 | return {'error' : 'Firewall file already empty.'}, 204 87 | else: 88 | logger.error("Cannot JSON parse Firewalls file.") 89 | return {'error' : 'Cannot JSON parse Firewalls file.'}, 500 90 | except Exception as e: 91 | logger.error("Exception while parsing Firewalls file.") 92 | return {'error' : 'Exception while parsing Firewalls file.'}, 500 93 | else: 94 | logger.info("Firewall file with existing configuration.") 95 | try: 96 | with open(config.get('General','firewalls'),'w'): 97 | pass 98 | return firewalls, 200 99 | except Exception as e: 100 | logger.error("Error while deleting Firewall file: {0}".format(str(e))) 101 | return {'error' : 'Error while deleting Firewall file.'}, 500 102 | 103 | class firewalls(Resource): 104 | @requires_auth 105 | def get(self,site): 106 | config = ConfigParser.RawConfigParser() 107 | config.read('/etc/assimilator/assimilator.conf') 108 | try: 109 | with open(config.get('General','firewalls')) as f: 110 | firewall = json.loads(f.read()) 111 | except Exception as e: 112 | logger.error("Cannot JSON parse API key file.") 113 | return {}, 204 114 | try: 115 | return firewall[site] 116 | except Exception as e: 117 | logger.warning("Firewall brand or site not found.") 118 | return {'error' : 'Firewall brand or site not found.'}, 404 119 | @requires_auth 120 | def post(self,site): 121 | config = ConfigParser.RawConfigParser() 122 | config.read('/etc/assimilator/assimilator.conf') 123 | try: 124 | with open(config.get('General','firewalls')) as f: 125 | data = f.read() 126 | firewalls = json.loads(data) 127 | except ValueError: 128 | if data: 129 | logger.error("Cannot JSON parse Firewalls file.") 130 | return {'error' : 'Cannot JSON parse Firewalls file.'}, 500 131 | else: 132 | logger.warning("No data on firewall file.") 133 | firewalls = dict() 134 | except Exception as e: 135 | logger.error("Exception while parsing Firewalls file.") 136 | return {'error' : 'Exception while parsing Firewalls file.'}, 500 137 | finally: 138 | firewalls[site] = request.json 139 | try: 140 | with open(config.get('General','firewalls'),'w') as f: 141 | json.dump(firewalls,f) 142 | return request.json 143 | except Exception as e: 144 | logger.error("Exception while parsing Firewalls file.") 145 | return {'error' : 'Exception while parsing Firewalls file.'}, 500 146 | @requires_auth 147 | def delete(self,site): 148 | config = ConfigParser.RawConfigParser() 149 | config.read('/etc/assimilator/assimilator.conf') 150 | try: 151 | with open(config.get('General','firewalls')) as f: 152 | data = f.read() 153 | firewalls = json.loads(data) 154 | except ValueError: 155 | if data: 156 | logger.error("Cannot JSON parse Firewalls file.") 157 | return {'error' : 'Cannot JSON parse Firewalls file.'}, 500 158 | else: 159 | logger.warning("No data on firewall file.") 160 | return {'error' : 'No data on firewall file.'}, 204 161 | except Exception as e: 162 | logger.error("Exception while parsing Firewalls file.") 163 | return {'error' : 'Exception while parsing Firewalls file.'}, 500 164 | else: 165 | ret = firewalls[site] 166 | del firewalls[site] 167 | try: 168 | with open(config.get('General','firewalls'),'w') as f: 169 | json.dump(firewalls,f) 170 | return ret 171 | except Exception as e: 172 | logger.error("Exception while parsing Firewalls file.") 173 | return {'error' : 'Exception while parsing Firewalls file.'}, 500 -------------------------------------------------------------------------------- /app/modules/status.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | 3 | class status(Resource): 4 | def get(self): 5 | return {'status':'OK'}, 200 -------------------------------------------------------------------------------- /assimilator.conf: -------------------------------------------------------------------------------- 1 | ##################################################################################################################### 2 | # Assimilator configuration # 3 | # # 4 | #This is the configuration file for Assimilator, from here you can configure all the parameters used by Assimilator.# 5 | # # 6 | # Author: Nicolas Videla# 7 | ##################################################################################################################### 8 | 9 | ##################################################### 10 | # General # 11 | # Logging, log level and location of config files. # 12 | ##################################################### 13 | 14 | [General] 15 | 16 | #Where logs will go 17 | logfile = /var/log/assimilator.log 18 | 19 | #Log level [DEBUG, INFO, WARN, ERROR, CRIT, FATAL] 20 | loglevel = DEBUG 21 | 22 | #Time format 23 | format = %d/%m/%Y %H:%M:%S 24 | 25 | #File containing api keys 26 | apikeyfile = /etc/assimilator/api.key 27 | 28 | #Firewall data file 29 | firewalls = /etc/assimilator/firewalls.json 30 | 31 | #Listen address 32 | address = 0.0.0.0 33 | 34 | #Listening port 35 | port = 443 36 | 37 | [Key Management] 38 | 39 | ##################################################### 40 | # Key Management # 41 | #Access to API key generation and token assignation.# 42 | ##################################################### 43 | 44 | #static, file, ldap, radius 45 | type = static 46 | 47 | #Static auth 48 | user = admin 49 | password = secret 50 | 51 | #File auth 52 | userfile = /etc/passwd 53 | 54 | [Firewall Management] 55 | 56 | ##################################################### 57 | # Firewall Management # 58 | # Access to static Firewall configuration. # 59 | ##################################################### 60 | 61 | #static, file, ldap, radius 62 | type = static 63 | 64 | #Static auth 65 | user = admin 66 | password = secret 67 | 68 | #File auth 69 | userfile = /etc/passwd 70 | -------------------------------------------------------------------------------- /assimilator.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFXTCCA0WgAwIBAgIJAI/YGdTZysq4MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQwHhcNMTcwMjA4MTQxMjM4WhcNMjcwMjA2MTQxMjM4WjBF 5 | MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC 7 | CgKCAgEAugqfs3YKuKfqjkWTas2emwhLRWLmUH/ts4PD4tmF/tryvryDVGxoODKB 8 | XdR7Tb8TqKnYWzkYdVg0nxecv1pcgUhUE1gcfaYRWg1ob8DIbvKbwLaCstXGfehu 9 | d1xat2h5f7s6oSXS5QM10a2C8efckHgghyRH6KCA1ZOb5MstjrH5rGNpZsELKpMu 10 | rXy3ti3y2QRB+hT2qK/lX5VAFGgo15BPKhH13zCwb97aFUMFERswbySW+sP6G06Z 11 | e80XSaS8ukdrQurt6UJzLBOPC6bEi6Ks3C1EcSQMGBwvf6kWzUBQ94Gg/4w7IsNe 12 | 6BsyNHbVAXfr+bl6Of6RgbO5FbC1rXXODu/gL/iRJU21mqNWMngIBaqelrwpIwpC 13 | ESNyexQCiK1xtud9qLT0n7YiaTxclRETGVxPaOjefzoyvwIThWV6G0pXGA8ehd8R 14 | PdgjKLcLTgOJusO1Eu4uabtMnlCA/wW8s9wgThjKMi9UGw2J7pARrgl5JVA/rwoz 15 | Axqrmn3XjQkKJvxXblOL0+4Tn3Qj+7EGnua8NgLsOyMomyM0zemv6GHUjjICSFwh 16 | d4r2A5aF0sbsRXER43gfTlHSvYcnl509VWz12FhMxLkLdHPF6ipe7fJNi8tiHdT/ 17 | e3Fi6KB04ChDvjGRUATfCJt8z1U98H0596h/CtlbZh/rwrOX9d0CAwEAAaNQME4w 18 | HQYDVR0OBBYEFI7KWN9Qgt7k2RcZVHomHsszUr/6MB8GA1UdIwQYMBaAFI7KWN9Q 19 | gt7k2RcZVHomHsszUr/6MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB 20 | AC3hz8Pto502Iq3Q9H5BlW9MHSoGFJRBydpH8qUg0h858Ay5grMmyINBWoIL9/mv 21 | sa6nx+jM7Azk8DtZ6dl2v76FZ6G8KUCJj2k5Mmct3ePbDaWOC9M6WMfZv1+z6HxN 22 | DJ1MEntU3CfcP2H3e/GuAxw38PfipXk7CVGhgxArWNBaEcFSgeRfX9ldGQ5c5120 23 | wlc319ujo25cy2atbDG2zqczT4NB9GD2spE8tFfRLzoOvrIzPcl4w3h5ZjjXRO8H 24 | yDtCHeDrnKP6L4kaP8zcs17d1qy2kEULb8s93C+Nk6Dax/VykxAbB/2BW7aoM1hz 25 | GO0Ayh8or6uoX2vTNkyuCWKVR1+g7BzNL5b7L/IhwR4jwxGB0k+jg3YIa/W2VXWg 26 | YyRqNCsZufU+cTRUDmiI8jFZL00iDBBSegnObaE+4nSHlAaCYzdyv6lv66EutmSw 27 | D9tB+cMmx4arC8vpXoyPSdoSTHpalzW5UATMoAQbUu7DGTrJtqAUuDYwa7LxoqJ9 28 | OGF5FoPPrRn6Q6RpfACvkxWyXMwkQUf+ZBus2y5rroVrn/MwW4Ae2Er7NzpeL1SF 29 | MKh/evGk3BCUU+7dQ7sWpGm6gNK/38TK2gHtb9eNdNyaYrY2jln0G6RXHKY4nEtt 30 | kHAxOs/ZToofHbKOJXno8BXTu3lTiG4GejJRC/i+KLuC 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /assimilator.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKgIBAAKCAgEAugqfs3YKuKfqjkWTas2emwhLRWLmUH/ts4PD4tmF/tryvryD 3 | VGxoODKBXdR7Tb8TqKnYWzkYdVg0nxecv1pcgUhUE1gcfaYRWg1ob8DIbvKbwLaC 4 | stXGfehud1xat2h5f7s6oSXS5QM10a2C8efckHgghyRH6KCA1ZOb5MstjrH5rGNp 5 | ZsELKpMurXy3ti3y2QRB+hT2qK/lX5VAFGgo15BPKhH13zCwb97aFUMFERswbySW 6 | +sP6G06Ze80XSaS8ukdrQurt6UJzLBOPC6bEi6Ks3C1EcSQMGBwvf6kWzUBQ94Gg 7 | /4w7IsNe6BsyNHbVAXfr+bl6Of6RgbO5FbC1rXXODu/gL/iRJU21mqNWMngIBaqe 8 | lrwpIwpCESNyexQCiK1xtud9qLT0n7YiaTxclRETGVxPaOjefzoyvwIThWV6G0pX 9 | GA8ehd8RPdgjKLcLTgOJusO1Eu4uabtMnlCA/wW8s9wgThjKMi9UGw2J7pARrgl5 10 | JVA/rwozAxqrmn3XjQkKJvxXblOL0+4Tn3Qj+7EGnua8NgLsOyMomyM0zemv6GHU 11 | jjICSFwhd4r2A5aF0sbsRXER43gfTlHSvYcnl509VWz12FhMxLkLdHPF6ipe7fJN 12 | i8tiHdT/e3Fi6KB04ChDvjGRUATfCJt8z1U98H0596h/CtlbZh/rwrOX9d0CAwEA 13 | AQKCAgEArBkBzNCIcHMc3olsKmOVsdKFVuV7KsJ80BA3F9WjR7Og1GKsll9GNZ46 14 | 3+KcQbpdZCvh8dkqT/rNitIb9UOQySNwawiaKn2CFazLjH1orIGPJUFwPCDXYkeu 15 | UIpFfN6PbmJzhPjpU2KZ2aJJvJ+BAL+vT3R3dLFtHKVUk9yEAzmOPjMkIKK5QqQu 16 | jNwPUd7FhI2UvtO/rSIx7C6zvWzxQx/Cq6c7rEBtJr+fPAXoXP+q2VWeuNCrv7EA 17 | +G4rebuDvkos5hVPCfWndwGM1PlrXcSZZYAWjRcMfQ0tv0lax3oUrDDlDyDY33qd 18 | g9cMpU28E4Ss7TDv6VAdqD7qGy+BjyRg6YPhqeNtMuqqIkwlxok+57nfL2QkFtM4 19 | SPf2HwAMqnOESV05BHmRk5gP8hloc+icPsKzXhphGFKId4keDr1x3jn4LFjqSB5n 20 | hzrp+TVWXYoVMFL7sZk4AFvUkAjxdBcsjv54rH478d3p5i/TfWPXU8mr9iRtjKGy 21 | bNCxLR625p94L0M4ER3md+EL8nuELE/6PcsIVf01ul+QNEaklGFnctyXP546fxRb 22 | e42IidIW2drc248oCMgkBG4tzC5AJ8lS2VmXvEOoSfO2WUmRPQTVxd+OE/g3mOWH 23 | 8/0isO5gcL/AMrbIk0Lv4kLjpneTUH9e5dpvWBr2rVDrs41t8eECggEBAO35PW3Y 24 | bfIAXVduekTGCclYEcC6jaFa2f2gpsYiHNjKsPSLR8sdf9bQ6EAqUgywYlvb+IR6 25 | KoW44AwhJP/uqVL0nb3R5PFiFjdjPgBQ95mGSBBAtKWCkggQA+4MSXLqazWJSdoO 26 | sMN7X0vlgTgEt+RpQ24/wsA4A05LI0fs+1O4Ph0mfpyYfAXr9sVRqmBEyg9uB83v 27 | dFi/lteXXO1AGVB0qrubaUfBieqklCbqEj7kTiggfV2bA0qp4RzfqUKEGxNGY3d7 28 | DMlY+0Dw/rgh62yoT02eOOTh+U2amaD2smx3a7SxjSYQev1qjlvQIjYw002wJrWH 29 | HXGeBDGwvGHjPQUCggEBAMgiUmkMVQEM5IZZ6/gNkuh0uKudc81RTXcHaMjAT78u 30 | ul6g95tZmmRbcVLYx1nJjpoA9Ut5imNes3aLI3RDfdhoBqSkjlNfxxl+vZ3sS8Np 31 | 5J5rQWUtHfy5dM4/iLN5LLsUmbloBOuR9J2U2Xq+F5cYOM7rVXIA+BzhHr2cMeyc 32 | RMXUwcr3f9smPBh2itDuYAicN+3eBvTlWh5MO4AdfgMVAe6XTyCOXBXE04Jdd/Iv 33 | x8dtiKSTAv0VowuQ+NzbO/FLj4OBQ9CAuqKHAP9QtoDcdu+hEwS4SouN1e5gInQM 34 | /3/RKqs0LGTrT7XdDdLuDTms4nafUyZASvtllUTd7PkCggEBANXRvT0mWts3Mqct 35 | T9Tdb8umQwU9WaZiID4AC/k4i1zK+iYvwwkgb04PlK6al/Box2esBObbcbuG+mBL 36 | o5gF08QCoHz+y8uLC1Um6X2VQnOCMNvRl76izB1MbouEJLaJJF0NBvtTecWa42wp 37 | QWqY6rXjUsALsCvP6EB/yKqGpud5Qu4kOVzwsXcMpkRy9TfcdwVYItEJVyuqsZjy 38 | mn9duaENwp4grH7Zyda3m5o/dLdnoUXys1HYxyNd8jNkTwvB8SXnd9XSDRIYzmBy 39 | aRv+9i5CKEcgZBRbagotX9496uEEiBTp9139LtTl2hVqtGKmiiViodU3GuCMT05q 40 | c0ksyRUCggEAa1h9e3rx3S5dJPL2boZZVfXFV8eMZYRGWKHQwlzkaVdW/4q3RMw6 41 | 07l4f0dnJfHGWzRIXcNDRyl0eREIY0QQLBMf/Q+Gh5HH6RTh/+LOgHI7/fzphqs8 42 | emG/FSFe4WUNaQsTqS7x9KjE3AEK5ZswbArB7bqsigmbC2J0ap9s58yp1rLXf1Zh 43 | bdqEQVrT+lzhhbzFAwEkGhcqzzV0dLNc86pA7wiZmIzDN6nrCPG5dF3Melt1/1Ab 44 | OkRwv7NyyIOPE03EHHUpOpzRfnuLPWa9fUDDSqTK3q06zr+1D7bgw/50zMoUaOAD 45 | L95HUvTOhWTKEzV4AshSgSHbFhzWg6nFgQKCAQEA4QPzBeg1ac8YTBhF6Qplp1TX 46 | 0ZweZBawXh852UFiRNgqPDhFiqVL+dJivXatKUjd8xwQ9+1YQH93/dXDxhYq+vmI 47 | HK+75o1RwlrjdNEmINYGJlwn+oT33xfnYKwDLe4ilMd6da6J0sxBoeSF8zCG0Tjc 48 | Q7VvwPcfa4bBDbWV0KCjqxnXJKK4QW0fWOK0qarUOlCyvozsmkNhT8avhYLZ+Ezn 49 | IVJ7UGGxpwSxSehq4SHIKi+pvEz+R/Nl6IHH0iEdcVO53kGHUqQE2zanC/dQruio 50 | ueDYprwQwTlrgY1BlZumN4EjjatBD/vkn3AwJ/XidAd9WuqrQPQTKLZTojybag== 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /assimilator.wsgi: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('/var/www/assimilator') 3 | sys.stdout = sys.stderr 4 | 5 | from run import app as application -------------------------------------------------------------------------------- /assimilator_vhost.conf: -------------------------------------------------------------------------------- 1 | 2 | WSGIDaemonProcess assimilator user=www-data group=www-data threads=1000 3 | WSGIScriptAlias / /var/www/assimilator/assimilator.wsgi 4 | WSGIScriptReloading On 5 | WSGIPassAuthorization On 6 | 7 | SSLEngine On 8 | SSLCertificateFile /etc/apache2/ssl/assimilator.crt 9 | SSLCertificateKeyFile /etc/apache2/ssl/assimilator.key 10 | #SSLCertificateChainFile /path/to/DigiCertCA.crt 11 | 12 | 13 | WSGIProcessGroup assimilator 14 | WSGIApplicationGroup %{GLOBAL} 15 | Order deny,allow 16 | Allow from all 17 | 18 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = Assimilator 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/build/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: a260be3544410f6c7a1538bb65c1c0ae 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/build/.doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/.doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/build/.doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/.doctrees/index.doctree -------------------------------------------------------------------------------- /docs/build/.doctrees/user/api.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/.doctrees/user/api.doctree -------------------------------------------------------------------------------- /docs/build/.doctrees/user/firewallmgmt.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/.doctrees/user/firewallmgmt.doctree -------------------------------------------------------------------------------- /docs/build/.doctrees/user/firststeps.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/.doctrees/user/firststeps.doctree -------------------------------------------------------------------------------- /docs/build/.doctrees/user/install.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/.doctrees/user/install.doctree -------------------------------------------------------------------------------- /docs/build/.doctrees/user/intro.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/.doctrees/user/intro.doctree -------------------------------------------------------------------------------- /docs/build/.doctrees/user/keymanagement.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/.doctrees/user/keymanagement.doctree -------------------------------------------------------------------------------- /docs/build/.doctrees/user/objects.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/.doctrees/user/objects.doctree -------------------------------------------------------------------------------- /docs/build/.doctrees/user/route.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/.doctrees/user/route.doctree -------------------------------------------------------------------------------- /docs/build/.doctrees/user/rules.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/.doctrees/user/rules.doctree -------------------------------------------------------------------------------- /docs/build/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/.nojekyll -------------------------------------------------------------------------------- /docs/build/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. Assimilator documentation master file, created by 2 | sphinx-quickstart on Thu Jun 8 16:14:09 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Assimilator's documentation! 7 | ======================================= 8 | JSON REST API wrapper for vendor firewalls. 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | 13 | user/intro.rst 14 | user/install.rst 15 | user/firststeps.rst 16 | user/keymanagement.rst 17 | user/firewallmgmt.rst 18 | user/api.rst 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /docs/build/_sources/user/api.rst.txt: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | API 4 | === 5 | 6 | The juice of Assimilator relies on the /api. From here one can access all Firewall configuration, check rules, routes and network objects. Also the user can test an access to see if the Firewall grants the access. Assimilator has default resource URL for all firewalls (like rules, objects and routes) and private resource URL destined for each Firewall brand. This is to grasp the full functionality of Firewalls. 7 | 8 | Config 9 | ------ 10 | 11 | **/api//config** 12 | 13 | Gets the full configuration of the Firewall, in it's native format. In many cases this is XML. 14 | 15 | *Example* 16 | 17 | :: 18 | 19 | GET /api/argentina/config 20 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 21 | Content-Type: application/json 22 | 23 | :: 24 | 25 | 200 OK 26 | 27 | .. block-code: json 28 | 29 | { 30 | "config" : " ... " 31 | } 32 | 33 | 34 | Rules 35 | ----- 36 | 37 | **/api//rules** 38 | 39 | Get all rules in the selected Firewall. This can be filtered with URL arguments. 40 | 41 | *Example (PaloAlto)* 42 | 43 | :: 44 | 45 | GET /api/argentina/rules 46 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 47 | Content-Type: application/json 48 | 49 | :: 50 | 51 | 200 OK 52 | 53 | .. block-code: json 54 | 55 | { 56 | "rules" : [ 57 | { 58 | "log-end": false, 59 | "qos": { 60 | "marking": null, 61 | "type": null 62 | }, 63 | "negate-source": false, 64 | "disabled": true, 65 | "rule-type": "universal", 66 | "tag": [], 67 | "log-start": false, 68 | "hip-profiles": [], 69 | "negate-destination": false, 70 | "description": null, 71 | "category": [ 72 | "any" 73 | ], 74 | "from": [ 75 | "trust" 76 | ], 77 | "service": [ 78 | "any" 79 | ], 80 | "source": [ 81 | "any" 82 | ], 83 | "destination": [ 84 | "8.8.8.8", 85 | "8.8.4.4" 86 | ], 87 | "application": [ 88 | "dns" 89 | ], 90 | "profile-setting": null, 91 | "log-setting": null, 92 | "to": [ 93 | "untrust" 94 | ], 95 | "schedule": null, 96 | "source-user": [ 97 | "any" 98 | ], 99 | "icmp-unreachable": false, 100 | "name": "DNS Google Access", 101 | "disable-server-response-inspection": false, 102 | "action": "allow" 103 | }, 104 | ... 105 | ] 106 | } 107 | 108 | *Example with arguments (PaloAlto)* 109 | 110 | :: 111 | 112 | GET /api/argentina/rules?from=dmz&to=untrust 113 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 114 | Content-Type: application/json 115 | 116 | :: 117 | 118 | 200 OK 119 | 120 | .. block-code: json 121 | 122 | { 123 | "rules" : [ 124 | { 125 | "log-end": true, 126 | "qos": { 127 | "marking": null, 128 | "type": null 129 | }, 130 | "negate-source": false, 131 | "disabled": true, 132 | "rule-type": "universal", 133 | "tag": [], 134 | "log-start": false, 135 | "hip-profiles": [], 136 | "negate-destination": false, 137 | "description": null, 138 | "category": [ 139 | "any" 140 | ], 141 | "from": [ 142 | "dmz" 143 | ], 144 | "service": [ 145 | "any" 146 | ], 147 | "source": [ 148 | "any" 149 | ], 150 | "destination": [ 151 | "10.10.50.2", 152 | ], 153 | "application": [ 154 | "web-browsing", 155 | "ssl" 156 | ], 157 | "profile-setting": null, 158 | "log-setting": null, 159 | "to": [ 160 | "untrust" 161 | ], 162 | "schedule": null, 163 | "source-user": [ 164 | "any" 165 | ], 166 | "icmp-unreachable": false, 167 | "name": "Internet access", 168 | "disable-server-response-inspection": false, 169 | "action": "allow" 170 | }, 171 | ... 172 | ] 173 | } 174 | 175 | 176 | To add a rule one simply changes the method to POST and sends one of these JSON objects in the body of the request. 177 | 178 | :: 179 | 180 | POST /api/brasil/rules 181 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 182 | Content-Type: application/json 183 | { 184 | "log-end": true, 185 | "qos": { 186 | "marking": null, 187 | "type": null 188 | }, 189 | "negate-source": false, 190 | "disabled": true, 191 | "rule-type": "universal", 192 | "tag": [], 193 | "log-start": false, 194 | "hip-profiles": [], 195 | "negate-destination": false, 196 | "description": null, 197 | "category": [ 198 | "any" 199 | ], 200 | "from": [ 201 | "dmz" 202 | ], 203 | "service": [ 204 | "any" 205 | ], 206 | "source": [ 207 | "any" 208 | ], 209 | "destination": [ 210 | "10.10.50.2", 211 | ], 212 | "application": [ 213 | "web-browsing", 214 | "ssl" 215 | ], 216 | "profile-setting": null, 217 | "log-setting": null, 218 | "to": [ 219 | "untrust" 220 | ], 221 | "schedule": null, 222 | "source-user": [ 223 | "any" 224 | ], 225 | "icmp-unreachable": false, 226 | "name": "Internet access", 227 | "disable-server-response-inspection": false, 228 | "action": "allow" 229 | } 230 | 231 | Objects 232 | ------- 233 | 234 | **/api//objects/** 235 | 236 | Firewall objects identify hosts and ports in the rules, basically there are four type of objects: 237 | 238 | * Address: Hosts identified by an IP, IP range, subnet or FQDN. 239 | * Service: A combination of protocol and source/destination port. 240 | * Address Group: A group of Address objects. 241 | * Service Group: A group of service objects. 242 | 243 | 244 | 245 | 246 | Routes 247 | ------ 248 | 249 | -------------------------------------------------------------------------------- /docs/build/_sources/user/firewallmgmt.rst.txt: -------------------------------------------------------------------------------- 1 | .. _firewall management: 2 | 3 | Firewall management 4 | =================== 5 | 6 | This is the second part of the admin configuration, this part should be accessed through HTTP authenteication with the user and password specified in assimilator.conf file. Here the admin configures all Firewall credentials, with this information Assimilator will then access each Firewall and retrieve the information requested through API calls. 7 | Each Firewall brand has their our way to be accessed, in general it's an SSH connection but some of them use an API (PaloAlto or AWS). 8 | 9 | 10 | Add a Firewall 11 | -------------- 12 | 13 | To add a Firewall we make an admin POST request to /firewalls/, in the request's body we should send the JSON object with the Firewall's credentials. 14 | 15 | :: 16 | 17 | POST /firewalls/argentina HTTP/1.1 18 | Content-Type: application/json 19 | Authorization: Basic YWRtaW46c2VjcmV0 20 | { 21 | "brand" : , 22 | "description" : , 23 | #JSON object keys for the Firewall brand 24 | ... 25 | } 26 | 27 | To remove a Firewall from Assimilator we make a DELETE request. 28 | 29 | :: 30 | 31 | DELETE /firewalls/argentina HTTP/1.1 32 | Content-Type: application/json 33 | Authorization: Basic YWRtaW46c2VjcmV0 34 | 35 | To retrieve the Firewall configuration we make a GET request. 36 | 37 | :: 38 | 39 | GET /firewalls/argentina HTTP/1.1 40 | Content-Type: application/json 41 | Authorization: Basic YWRtaW46c2VjcmV0 42 | 43 | 44 | Each Firewall brand is configured differently, this is because each Firewall has their way to be accessed. For each Firewall there is a unique JSON object format. 45 | Below is the detailed configuration for each device. 46 | 47 | Palo Alto 48 | --------- 49 | 50 | PaloAlto firewalls have an XML API that only has the GET method. Through this Assimilator translates it to a friendlier API. 51 | 52 | :: 53 | 54 | GET /firewalls/argentina HTTP/1.1 55 | Content-Type: application/json 56 | Authorization: Basic YWRtaW46c2VjcmV0 57 | 58 | :: 59 | 60 | 200 OK 61 | 62 | .. code-block: json 63 | 64 | { 65 | "brand": "paloalto", 66 | "secondary": "192.168.1.2", 67 | "primary": "192.168.1.1", 68 | "key": "LUFRPT1fhejJjfjelajcmalseiVWFjhfu37Hu39fjLLifj38ejL00ffj398ejLKfjei4o0apLmfnjfkel49ii00fa9sf=", 69 | "description": "Firewall Argentina" 70 | } 71 | 72 | The key is the Firewall name through the api, in this example the key is 'argentina'. Inside this JSON object we have the following keys: 73 | 74 | :: 75 | 76 | "brand" : The Firewall's brand, this will indicate which translator script should be invoked when connecting to this firewall. 77 | "primary" : The Firewall's primary IP address, in PaloAlto this should be the Management IP address. 78 | "secondary" : The Firewall's secondary IP address, in PaloAlto this should be the Management IP address. 79 | "key" : XML API key to be used by Assimilator when connecting to this PaloAlto Firewall. 80 | "description" : Some description about this device. 81 | 82 | 83 | Juniper 84 | ------- 85 | 86 | Junos SRX and SSG have a similar configuration, both are XML based and are accessed through SSH. 87 | 88 | :: 89 | 90 | GET /firewalls/datacenter HTTP/1.1 91 | Content-Type: application/json 92 | Authorization: Basic YWRtaW46c2VjcmV0 93 | 94 | :: 95 | 96 | 200 OK 97 | 98 | .. code-block: json 99 | 100 | { 101 | "description": "Firewall SRX Datacenter.", 102 | "brand": "juniper", 103 | "privatekey": "", 104 | "primary": "172.16.1.1", 105 | "secondary": "172.16.1.2", 106 | "privatekeypass": "", 107 | "user": "assimilator", 108 | "timeout": 1200, 109 | "pass": "somepassword", 110 | "port": 22, 111 | "name": "datacenter" 112 | } 113 | 114 | The key is the Firewall name through the api, in this example the key is 'datacenter'. Juniper allows users to login either with a password or a certificate, the latter one is encouraged. 115 | Inside this JSON object we have the following keys: 116 | 117 | :: 118 | 119 | "brand" : The Firewall's brand, this will indicate which translator script should be invoked when connecting to this firewall. 120 | "primary" : The Firewall's primary IP address, in Juniper this should be the trust IP address. 121 | "secondary" : The Firewall's secondary IP address, in Juniper this should the trust IP address. 122 | "user" : The username that Assimilator should use while logging in, it usually is 'assimilator'. 123 | "privatekey" : Location of the certificate file to be used for SSH authentication, if not specified then user/password will be used. 124 | "privatekeypass" : The password to decrypt the private key from the certificate, if not specified then user/password will be used. 125 | "pass" : The password to be used for SSH login, this is used if privatekey and privatekeypass is not specified. 126 | "port" : The SSH port on the Firewall, usually 22. 127 | "description" : Some description about this device. 128 | -------------------------------------------------------------------------------- /docs/build/_sources/user/firststeps.rst.txt: -------------------------------------------------------------------------------- 1 | .. _first steps: 2 | 3 | First steps 4 | =========== 5 | 6 | The first thing you need to do is create a configuration file that adjusts to your needs. Many of these parameters have already been configured for you, but some minimal configuration is needed. 7 | 8 | An `example configuration `_ file can be found in the repo. This file specifies the initial configuration for Assimilator, this should be mounted as a volume in the Docker container with the '-v' argument on '/etc/assimilator/'. 9 | 10 | General 11 | ------- 12 | 13 | Logfile indicates where logs should be stored. 14 | 15 | logfile = /var/log/assimilator.log 16 | 17 | The log level that should be logged [DEBUG, INFO, WARN, ERROR, CRIT, FATAL]. 18 | 19 | loglevel = WARN 20 | 21 | The date and time format for the logs, the default is Syslog friendly. 22 | 23 | format = %d/%m/%Y %H:%M:%S 24 | 25 | The location for the API keys of each user, this file should exist only. API keys are managed through the REST api. 26 | 27 | apikeyfile = /etc/assimilator/api.key 28 | 29 | The location for all Firewall related authentication. This is managed thorugh the REST api. 30 | 31 | firewalls = /etc/assimilator/firewalls.json 32 | 33 | Where the API should listen. 34 | 35 | address = 0.0.0.0 36 | 37 | What port should Assimilator listen to, default is 443. 38 | 39 | port = 443 40 | 41 | Key Management 42 | -------------- 43 | 44 | This is the authentication required to modify Firewall credentials and user's API keys. 45 | 46 | From where should Assimilator authenticate users? For now, the only option is 'static'. 47 | 48 | type = static 49 | 50 | The user and password required for admin login to the API. 51 | 52 | user = admin 53 | password = secret 54 | 55 | 56 | Firewall Management 57 | ------------------- 58 | 59 | Same as Key Management, this section describes the admin user and password required to configure Firewall credentials. 60 | 61 | From where should Assimilator authenticate users? For now, the only option is 'static'. 62 | 63 | type = static 64 | 65 | The user and password required for admin login to the API. 66 | 67 | user = admin 68 | password = secret -------------------------------------------------------------------------------- /docs/build/_sources/user/install.rst.txt: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Install 4 | ======= 5 | 6 | Assimilator can be installed through Docker or cloned into a directory and run from there. Personally I prefer Docker since it's more reliable, but both ways work. 7 | 8 | The Docker Way 9 | -------------- 10 | 11 | The best way to install Assimilator is through `Docker `_: 12 | 13 | $ docker pull videlanicolas/assimilator:stable 14 | 15 | The latest build is constantly improving, I recomend the stable version or instead the latest tag which are also stable: 16 | 17 | $ docker pull videlanicolas/assimilator:1.2.2 18 | 19 | Run a container: 20 | 21 | $ docker run -d -v /path/to/configuration:/etc/assimilator/ -p 443:443 videlanicolas/assimilator:stable 22 | 23 | Docker containers are not peristent, so if you want to maintain your configured Firewalls and API keys you should mount an external directory into the container, that's what the -v is for. 24 | 25 | The Repo-Cloning Way 26 | -------------------- 27 | 28 | A.K.A I don't trust your Docker image. 29 | 30 | You can clone the repo from `Github `_ and build your image of Assimilator from the `dockerfile `_. 31 | 32 | $ git clone https://github.com/videlanicolas/assimilator.git 33 | $ docker build -t assimilator . 34 | 35 | If you don't want to use Docker there is a `bash script `_ to install the dependencies. Also there is `another bash script `_ to generate a random certificate for HTTPS connections. 36 | 37 | $ git clone https://github.com/videlanicolas/assimilator.git 38 | $ chmod +x install.sh generate_certificate.sh 39 | $ ./generate_certificate.sh 40 | $ ./install.sh -------------------------------------------------------------------------------- /docs/build/_sources/user/intro.rst.txt: -------------------------------------------------------------------------------- 1 | .. _introduction: 2 | 3 | Introduction 4 | ============ 5 | 6 | Assimilator was built to enable automotion through REST API, using JSON objects for easy understanding. With Assimilator a developer can self-serve his/her access through the network, while also auditors can request information without the need of network engineers. This API wraps around all possible vendor Firewalls, let it be appliances, virtual machines or cloud ACL. 7 | 8 | With Assimilator one can automatize Firewall rules easily, just by simply make an HTTP request one can add/remove/modify/view rules and routes. 9 | 10 | .. _`apache2`: 11 | 12 | MIT License 13 | ----------- 14 | 15 | Altough there are other repos and people working on Firewall automation, this is the only repo that serves an API. I'm currently working alone in this and that's why I released Assimilator under `MIT License`_, because I need other people to help with other Firewall brands and bug fixes. 16 | 17 | .. _`MIT License`: https://opensource.org/licenses/MIT 18 | 19 | 20 | Assimilator License 21 | ------------------- 22 | 23 | .. include:: ../../../LICENSE 24 | -------------------------------------------------------------------------------- /docs/build/_sources/user/keymanagement.rst.txt: -------------------------------------------------------------------------------- 1 | .. _key management: 2 | 3 | API Key Management 4 | ================== 5 | 6 | There are two URL from where the admin logs in, one of those is the Key management. 7 | 8 | Key management handles the API keys sent to Assimilator, it identifies API keys with a matching authorization token. When an API key is randomly generated it has no authorization to do stuff on the API, that's when authorization tokens come in. Each API key has a list of authorization tokens which contain a regex in the URL and available HTTP methods. 9 | For example: 10 | 11 | .. code-block:: json 12 | 13 | { 14 | "token": 15 | [ 16 | { 17 | "path": "/api/.*", 18 | "method": 19 | [ 20 | "GET", 21 | "POST", 22 | "PUT" 23 | ] 24 | } 25 | ], 26 | "key": "BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s" 27 | } 28 | 29 | 30 | Here we have an API key containing the key and token. The key is just 100 pseudo-random numbers and letters, this key should travel as an HTTP header named 'key' in the request. The other part is the token, it consists of a list where each object in that list consist of a dictionary with a 'path' and 'method'. 31 | The 'path' is a regex applied over the requested URL, and 'method' is a list of allowed HTTP methods over that regex match. Our request should match some object on this list, the following example shows a positive authentication. 32 | 33 | :: 34 | 35 | GET /api/hq/rules HTTP/1.1 36 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 37 | Content-Type: application/json 38 | 39 | This example shows a denied authorization. 40 | 41 | :: 42 | 43 | DELETE /api/hq/rules HTTP/1.1 44 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 45 | Content-Type: application/json 46 | 47 | With this scheme one can assign API keys to both users and scripts, therefore a user can easily use this API (ie. `Postman `_) and also a Python script (ie. with `Requests `_. 48 | 49 | Add a user 50 | ---------- 51 | 52 | To add a new user to the API use the configured user and password for admin access (located `here `_) as HTTP authentication. Make a GET to /keymgmt. 53 | 54 | :: 55 | 56 | GET /keymgmt HTTP/1.1 57 | Authorization: Basic YWRtaW46c2VjcmV0 58 | Content-Type: application/json 59 | 60 | If you never added a user to the API this request should return an empty JSON. If not, it will return a JSON dictionary of user numbers and their respective key and tokens. 61 | 62 | .. code-block:: json 63 | 64 | { 65 | "1" : 66 | { 67 | "comment" : "Audit" 68 | "token": 69 | [ 70 | { 71 | "path": "/api/.*", 72 | "method": [ 73 | "GET", 74 | "POST", 75 | "PUT", 76 | "PATCH", 77 | "DELETE" 78 | ] 79 | } 80 | ], 81 | "key": "BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s" 82 | }, 83 | "2" : 84 | { 85 | "comment": "NOC", 86 | "token": 87 | [ 88 | { 89 | "path": "/api/hq/.*", 90 | "method": [ 91 | "GET" 92 | ] 93 | }, 94 | { 95 | "path": "/api/branch1/.*", 96 | "method": [ 97 | "GET" 98 | ] 99 | } 100 | ], 101 | "key": "xTYRt9tKODjh42smjmoHno3j10OD3LGM3dZgHcen1S5NhICCRzdlrj6VJJwBpBTVgXmfpI3S63bo8aBGZT1CGR91rroBvTv8cer" 102 | } 103 | } 104 | 105 | To add a user you need to generate a new pseudo-random API key. 106 | 107 | :: 108 | 109 | POST /keymgmt/generate HTTP/1.1 110 | Authorization: Basic YWRtaW46c2VjcmV0 111 | Content-Type: application/json 112 | {"comment" : "Some User"} 113 | 114 | :: 115 | 116 | 201 CREATED 117 | 118 | .. code-block:: json 119 | 120 | { 121 | "3": { 122 | "comment": "Some User", 123 | "token": [], 124 | "key": "xWCALV3fPLqnUZ8avZaCeDGyXhTwrTSEMcf7iH7o1j6XG2gGJF75kAXk0l8b2GMsrvHELrXS1T8S4tjfN2SQB2RVH13B0gzGa0vh" 125 | } 126 | } 127 | 128 | And now assign new tokens to that user. 129 | 130 | :: 131 | 132 | POST /keymgmt/3 HTTP/1.1 133 | Authorization: Basic YWRtaW46c2VjcmV0 134 | Content-Type: application/json 135 | { 136 | "path": "\/api\/hq\/rules\/.*", 137 | "method": [ 138 | "GET", 139 | "POST" 140 | ] 141 | } 142 | 143 | :: 144 | 145 | 201 CREATED 146 | 147 | 148 | .. code-block:: json 149 | 150 | { 151 | "path": "/api/hq/rules/.*", 152 | "method": [ 153 | "GET", 154 | "POST" 155 | ] 156 | } 157 | 158 | Take note of the backslash. 159 | Check that it was successfull with GET. 160 | 161 | :: 162 | 163 | GET /keymgmt/3 HTTP/1.1 164 | Authorization: Basic YWRtaW46c2VjcmV0 165 | Content-Type: application/json 166 | 167 | :: 168 | 169 | 200 OK 170 | 171 | .. code-block:: json 172 | 173 | { 174 | "3": { 175 | "comment": "Some User", 176 | "token": [ 177 | { 178 | "path": "/api/hq/rules/.*", 179 | "method": [ 180 | "GET", 181 | "POST" 182 | ] 183 | }], 184 | "key": "xWCALV3fPLqnUZ8avZaCeDGyXhTwrTSEMcf7iH7o1j6XG2gGJF75kAXk0l8b2GMsrvHELrXS1T8S4tjfN2SQB2RVH13B0gzGa0vh" 185 | } 186 | } 187 | 188 | You can't delete specific authorizataion tokens, you would have to delete the entire API key and start over. For that one can use the DELETE method. 189 | 190 | :: 191 | 192 | DELETE /keymgmt/3 HTTP/1.1 193 | Authorization: Basic YWRtaW46c2VjcmV0 194 | Content-Type: application/json 195 | 196 | :: 197 | 198 | 200 OK 199 | -------------------------------------------------------------------------------- /docs/build/_sources/user/objects.rst.txt: -------------------------------------------------------------------------------- 1 | .. _objects: 2 | 3 | Objects 4 | ======= 5 | -------------------------------------------------------------------------------- /docs/build/_sources/user/route.rst.txt: -------------------------------------------------------------------------------- 1 | .. _routes: 2 | 3 | Routes 4 | ====== 5 | -------------------------------------------------------------------------------- /docs/build/_sources/user/rules.rst.txt: -------------------------------------------------------------------------------- 1 | .. _rules: 2 | 3 | Rules 4 | ===== 5 | -------------------------------------------------------------------------------- /docs/build/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/build/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/build/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/comment-close.png -------------------------------------------------------------------------------- /docs/build/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/comment.png -------------------------------------------------------------------------------- /docs/build/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../font/fontawesome_webfont.eot");src:url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("../font/fontawesome_webfont.woff") format("woff"),url("../font/fontawesome_webfont.ttf") format("truetype"),url("../font/fontawesome_webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:0.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} 2 | /*# sourceMappingURL=badge_only.css.map */ 3 | -------------------------------------------------------------------------------- /docs/build/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* This file intentionally left blank. */ 2 | -------------------------------------------------------------------------------- /docs/build/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node) { 70 | if (node.nodeType == 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 74 | var span = document.createElement("span"); 75 | span.className = className; 76 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 77 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 78 | document.createTextNode(val.substr(pos + text.length)), 79 | node.nextSibling)); 80 | node.nodeValue = val.substr(0, pos); 81 | } 82 | } 83 | else if (!jQuery(node).is("button, select, textarea")) { 84 | jQuery.each(node.childNodes, function() { 85 | highlight(this); 86 | }); 87 | } 88 | } 89 | return this.each(function() { 90 | highlight(this); 91 | }); 92 | }; 93 | 94 | /* 95 | * backward compatibility for jQuery.browser 96 | * This will be supported until firefox bug is fixed. 97 | */ 98 | if (!jQuery.browser) { 99 | jQuery.uaMatch = function(ua) { 100 | ua = ua.toLowerCase(); 101 | 102 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 103 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 104 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 105 | /(msie) ([\w.]+)/.exec(ua) || 106 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 107 | []; 108 | 109 | return { 110 | browser: match[ 1 ] || "", 111 | version: match[ 2 ] || "0" 112 | }; 113 | }; 114 | jQuery.browser = {}; 115 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 116 | } 117 | 118 | /** 119 | * Small JavaScript module for the documentation. 120 | */ 121 | var Documentation = { 122 | 123 | init : function() { 124 | this.fixFirefoxAnchorBug(); 125 | this.highlightSearchWords(); 126 | this.initIndexTable(); 127 | 128 | }, 129 | 130 | /** 131 | * i18n support 132 | */ 133 | TRANSLATIONS : {}, 134 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 135 | LOCALE : 'unknown', 136 | 137 | // gettext and ngettext don't access this so that the functions 138 | // can safely bound to a different name (_ = Documentation.gettext) 139 | gettext : function(string) { 140 | var translated = Documentation.TRANSLATIONS[string]; 141 | if (typeof translated == 'undefined') 142 | return string; 143 | return (typeof translated == 'string') ? translated : translated[0]; 144 | }, 145 | 146 | ngettext : function(singular, plural, n) { 147 | var translated = Documentation.TRANSLATIONS[singular]; 148 | if (typeof translated == 'undefined') 149 | return (n == 1) ? singular : plural; 150 | return translated[Documentation.PLURALEXPR(n)]; 151 | }, 152 | 153 | addTranslations : function(catalog) { 154 | for (var key in catalog.messages) 155 | this.TRANSLATIONS[key] = catalog.messages[key]; 156 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 157 | this.LOCALE = catalog.locale; 158 | }, 159 | 160 | /** 161 | * add context elements like header anchor links 162 | */ 163 | addContextElements : function() { 164 | $('div[id] > :header:first').each(function() { 165 | $('\u00B6'). 166 | attr('href', '#' + this.id). 167 | attr('title', _('Permalink to this headline')). 168 | appendTo(this); 169 | }); 170 | $('dt[id]').each(function() { 171 | $('\u00B6'). 172 | attr('href', '#' + this.id). 173 | attr('title', _('Permalink to this definition')). 174 | appendTo(this); 175 | }); 176 | }, 177 | 178 | /** 179 | * workaround a firefox stupidity 180 | * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 181 | */ 182 | fixFirefoxAnchorBug : function() { 183 | if (document.location.hash) 184 | window.setTimeout(function() { 185 | document.location.href += ''; 186 | }, 10); 187 | }, 188 | 189 | /** 190 | * highlight the search words provided in the url in the text 191 | */ 192 | highlightSearchWords : function() { 193 | var params = $.getQueryParameters(); 194 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 195 | if (terms.length) { 196 | var body = $('div.body'); 197 | if (!body.length) { 198 | body = $('body'); 199 | } 200 | window.setTimeout(function() { 201 | $.each(terms, function() { 202 | body.highlightText(this.toLowerCase(), 'highlighted'); 203 | }); 204 | }, 10); 205 | $('') 207 | .appendTo($('#searchbox')); 208 | } 209 | }, 210 | 211 | /** 212 | * init the domain index toggle buttons 213 | */ 214 | initIndexTable : function() { 215 | var togglers = $('img.toggler').click(function() { 216 | var src = $(this).attr('src'); 217 | var idnum = $(this).attr('id').substr(7); 218 | $('tr.cg-' + idnum).toggle(); 219 | if (src.substr(-9) == 'minus.png') 220 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 221 | else 222 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 223 | }).css('display', ''); 224 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 225 | togglers.click(); 226 | } 227 | }, 228 | 229 | /** 230 | * helper function to hide the search marks again 231 | */ 232 | hideSearchWords : function() { 233 | $('#searchbox .highlight-link').fadeOut(300); 234 | $('span.highlighted').removeClass('highlighted'); 235 | }, 236 | 237 | /** 238 | * make the url absolute 239 | */ 240 | makeURL : function(relativeURL) { 241 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 242 | }, 243 | 244 | /** 245 | * get the current relative url 246 | */ 247 | getCurrentURL : function() { 248 | var path = document.location.pathname; 249 | var parts = path.split(/\//); 250 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 251 | if (this == '..') 252 | parts.pop(); 253 | }); 254 | var url = parts.join('/'); 255 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 256 | }, 257 | 258 | initOnKeyListeners: function() { 259 | $(document).keyup(function(event) { 260 | var activeElementType = document.activeElement.tagName; 261 | // don't navigate when in search box or textarea 262 | if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { 263 | switch (event.keyCode) { 264 | case 37: // left 265 | var prevHref = $('link[rel="prev"]').prop('href'); 266 | if (prevHref) { 267 | window.location.href = prevHref; 268 | return false; 269 | } 270 | case 39: // right 271 | var nextHref = $('link[rel="next"]').prop('href'); 272 | if (nextHref) { 273 | window.location.href = nextHref; 274 | return false; 275 | } 276 | } 277 | } 278 | }); 279 | } 280 | }; 281 | 282 | // quick alias for translations 283 | _ = Documentation.gettext; 284 | 285 | $(document).ready(function() { 286 | Documentation.init(); 287 | }); -------------------------------------------------------------------------------- /docs/build/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/build/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/down.png -------------------------------------------------------------------------------- /docs/build/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/file.png -------------------------------------------------------------------------------- /docs/build/_static/fonts/Inconsolata-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/fonts/Inconsolata-Bold.ttf -------------------------------------------------------------------------------- /docs/build/_static/fonts/Inconsolata-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/fonts/Inconsolata-Regular.ttf -------------------------------------------------------------------------------- /docs/build/_static/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /docs/build/_static/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /docs/build/_static/fonts/RobotoSlab-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/fonts/RobotoSlab-Bold.ttf -------------------------------------------------------------------------------- /docs/build/_static/fonts/RobotoSlab-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/fonts/RobotoSlab-Regular.ttf -------------------------------------------------------------------------------- /docs/build/_static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/build/_static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/build/_static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/build/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o"); 80 | 81 | // Add expand links to all parents of nested ul 82 | $('.wy-menu-vertical ul').not('.simple').siblings('a').each(function () { 83 | var link = $(this); 84 | expand = $(''); 85 | expand.on('click', function (ev) { 86 | self.toggleCurrent(link); 87 | ev.stopPropagation(); 88 | return false; 89 | }); 90 | link.prepend(expand); 91 | }); 92 | }; 93 | 94 | nav.reset = function () { 95 | // Get anchor from URL and open up nested nav 96 | var anchor = encodeURI(window.location.hash); 97 | if (anchor) { 98 | try { 99 | var link = $('.wy-menu-vertical') 100 | .find('[href="' + anchor + '"]'); 101 | // If we didn't find a link, it may be because we clicked on 102 | // something that is not in the sidebar (eg: when using 103 | // sphinxcontrib.httpdomain it generates headerlinks but those 104 | // aren't picked up and placed in the toctree). So let's find 105 | // the closest header in the document and try with that one. 106 | if (link.length === 0) { 107 | var doc_link = $('.document a[href="' + anchor + '"]'); 108 | var closest_section = doc_link.closest('div.section'); 109 | // Try again with the closest section entry. 110 | link = $('.wy-menu-vertical') 111 | .find('[href="#' + closest_section.attr("id") + '"]'); 112 | 113 | } 114 | $('.wy-menu-vertical li.toctree-l1 li.current') 115 | .removeClass('current'); 116 | link.closest('li.toctree-l2').addClass('current'); 117 | link.closest('li.toctree-l3').addClass('current'); 118 | link.closest('li.toctree-l4').addClass('current'); 119 | } 120 | catch (err) { 121 | console.log("Error expanding nav for anchor", err); 122 | } 123 | } 124 | }; 125 | 126 | nav.onScroll = function () { 127 | this.winScroll = false; 128 | var newWinPosition = this.win.scrollTop(), 129 | winBottom = newWinPosition + this.winHeight, 130 | navPosition = this.navBar.scrollTop(), 131 | newNavPosition = navPosition + (newWinPosition - this.winPosition); 132 | if (newWinPosition < 0 || winBottom > this.docHeight) { 133 | return; 134 | } 135 | this.navBar.scrollTop(newNavPosition); 136 | this.winPosition = newWinPosition; 137 | }; 138 | 139 | nav.onResize = function () { 140 | this.winResize = false; 141 | this.winHeight = this.win.height(); 142 | this.docHeight = $(document).height(); 143 | }; 144 | 145 | nav.hashChange = function () { 146 | this.linkScroll = true; 147 | this.win.one('hashchange', function () { 148 | this.linkScroll = false; 149 | }); 150 | }; 151 | 152 | nav.toggleCurrent = function (elem) { 153 | var parent_li = elem.closest('li'); 154 | parent_li.siblings('li.current').removeClass('current'); 155 | parent_li.siblings().find('li.current').removeClass('current'); 156 | parent_li.find('> ul li.current').removeClass('current'); 157 | parent_li.toggleClass('current'); 158 | } 159 | 160 | return nav; 161 | }; 162 | 163 | module.exports.ThemeNav = ThemeNav(); 164 | 165 | if (typeof(window) != 'undefined') { 166 | window.SphinxRtdTheme = { StickyNav: module.exports.ThemeNav }; 167 | } 168 | 169 | },{"jquery":"jquery"}]},{},["sphinx-rtd-theme"]); 170 | -------------------------------------------------------------------------------- /docs/build/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/minus.png -------------------------------------------------------------------------------- /docs/build/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/plus.png -------------------------------------------------------------------------------- /docs/build/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ 8 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 9 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 10 | .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ 11 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 12 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 13 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 14 | .highlight .ge { font-style: italic } /* Generic.Emph */ 15 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 16 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 17 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 18 | .highlight .go { color: #333333 } /* Generic.Output */ 19 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 20 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 21 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 22 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 23 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 24 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 25 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 26 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 27 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 28 | .highlight .kt { color: #902000 } /* Keyword.Type */ 29 | .highlight .m { color: #208050 } /* Literal.Number */ 30 | .highlight .s { color: #4070a0 } /* Literal.String */ 31 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 32 | .highlight .nb { color: #007020 } /* Name.Builtin */ 33 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 34 | .highlight .no { color: #60add5 } /* Name.Constant */ 35 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 36 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 37 | .highlight .ne { color: #007020 } /* Name.Exception */ 38 | .highlight .nf { color: #06287e } /* Name.Function */ 39 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 40 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 41 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 42 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 43 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 44 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 45 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */ 46 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 47 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 48 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 49 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 50 | .highlight .sa { color: #4070a0 } /* Literal.String.Affix */ 51 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 52 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 53 | .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ 54 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 55 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 56 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 57 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 58 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 59 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 60 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 61 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 62 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 63 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 64 | .highlight .fm { color: #06287e } /* Name.Function.Magic */ 65 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 66 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 67 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 68 | .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ 69 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/build/_static/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.3.1 2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== 9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, 10 | h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= 11 | b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== 12 | null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= 13 | function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= 14 | e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, 17 | c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; 24 | b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, 25 | 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; 26 | b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; 27 | b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), 28 | function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ 29 | u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= 30 | function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= 31 | true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); 32 | -------------------------------------------------------------------------------- /docs/build/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/up-pressed.png -------------------------------------------------------------------------------- /docs/build/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/_static/up.png -------------------------------------------------------------------------------- /docs/build/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Index — Assimilator 1.0 documentation 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | 106 | 107 |
108 | 109 | 110 | 116 | 117 | 118 | 119 |
120 |
121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
138 | 139 |
    140 | 141 |
  • Docs »
  • 142 | 143 |
  • Index
  • 144 | 145 | 146 |
  • 147 | 148 | 149 | 150 |
  • 151 | 152 |
153 | 154 | 155 |
156 |
157 |
158 |
159 | 160 | 161 |

Index

162 | 163 |
164 | 165 |
166 | 167 | 168 |
169 |
170 | 171 |
172 |
173 |
174 | 175 | 176 |
177 | 178 |
179 |

180 | © Copyright 2017, Nicolas Videla. 181 | 182 |

183 |
184 | Built with Sphinx using a theme provided by Read the Docs. 185 | 186 |
187 | 188 |
189 |
190 | 191 |
192 | 193 |
194 | 195 | 196 | 197 | 198 | 199 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 227 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /docs/build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Welcome to Assimilator’s documentation! — Assimilator 1.0 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | 106 | 107 |
108 | 109 | 110 | 116 | 117 | 118 | 119 |
120 |
121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
138 | 139 |
    140 | 141 |
  • Docs »
  • 142 | 143 |
  • Welcome to Assimilator’s documentation!
  • 144 | 145 | 146 |
  • 147 | 148 | 149 | View page source 150 | 151 | 152 |
  • 153 | 154 |
155 | 156 | 157 |
158 |
159 |
160 |
161 | 162 |
163 |

Welcome to Assimilator’s documentation!

164 |

JSON REST API wrapper for vendor firewalls.

165 |
166 | 201 |
202 |
203 |
204 |

Indices and tables

205 | 210 |
211 | 212 | 213 |
214 |
215 | 216 |
217 |
218 |
219 | 220 | 226 | 227 | 228 |
229 | 230 |
231 |

232 | © Copyright 2017, Nicolas Videla. 233 | 234 |

235 |
236 | Built with Sphinx using a theme provided by Read the Docs. 237 | 238 |
239 | 240 |
241 |
242 | 243 |
244 | 245 |
246 | 247 | 248 | 249 | 250 | 251 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 279 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /docs/build/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/docs/build/objects.inv -------------------------------------------------------------------------------- /docs/build/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Search — Assimilator 1.0 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 | 51 | 105 | 106 |
107 | 108 | 109 | 115 | 116 | 117 | 118 |
119 |
120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 |
137 | 138 |
    139 | 140 |
  • Docs »
  • 141 | 142 |
  • Search
  • 143 | 144 | 145 |
  • 146 | 147 |
  • 148 | 149 |
150 | 151 | 152 |
153 |
154 |
155 |
156 | 157 | 165 | 166 | 167 |
168 | 169 |
170 | 171 |
172 |
173 | 174 |
175 |
176 |
177 | 178 | 179 |
180 | 181 |
182 |

183 | © Copyright 2017, Nicolas Videla. 184 | 185 |

186 |
187 | Built with Sphinx using a theme provided by Read the Docs. 188 | 189 |
190 | 191 |
192 |
193 | 194 |
195 | 196 |
197 | 198 | 199 | 200 | 201 | 202 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 231 | 232 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /docs/build/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({docnames:["index","user/api","user/firewallmgmt","user/firststeps","user/install","user/intro","user/keymanagement"],envversion:53,filenames:["index.rst","user/api.rst","user/firewallmgmt.rst","user/firststeps.rst","user/install.rst","user/intro.rst","user/keymanagement.rst"],objects:{},objnames:{},objtypes:{},terms:{"case":1,"default":[1,3],"function":1,"new":6,"null":1,"return":6,"static":3,"true":1,"var":3,"while":[2,5],AND:5,AWS:2,And:6,BUT:5,FOR:5,For:[2,3,6],NOT:5,THE:5,The:[0,1,2,3,5,6],There:6,USE:5,WITH:5,With:[5,6],about:2,abov:5,access:[1,2,5,6],acl:5,action:[1,5],add:[0,1,5],added:6,address:[1,2,3],adjust:3,admin:[2,3,6],all:[1,2,3,5],allow:[1,2,6],alon:5,alreadi:3,also:[1,4,5,6],alto:0,altough:5,ani:[1,5],anoth:4,api:[0,2,3,4,5],apikeyfil:3,appli:6,applianc:5,applic:[1,2,6],argentina:[1,2],argument:[1,3],aris:5,around:5,assign:6,assimil:[1,2,3,4,6],associ:5,audit:6,auditor:5,authent:[2,3,6],author:[2,5,6],authorizataion:6,autom:5,automat:5,automot:5,avail:6,backslash:6,base:2,bash:4,basic:[1,2,6],bdp0nyhzmdfz98kcmd3gubiqgw9eztgwgpf56dwnkd3lgm3dzpazicrkvntnqwh5ydglh5sj9ktg7rer4le94zyxdigdlthhf8:[1,6],becaus:[2,5],been:3,below:2,best:4,block:[],bodi:[1,2],both:[2,4,6],branch1:6,brand:[1,2,5],brasil:1,brows:1,bug:5,build:4,built:5,call:2,can:[1,3,4,5,6],categori:1,certif:[2,4],chang:1,charg:5,check:[1,6],chmod:4,claim:5,clone:0,cloud:5,code:[],com:4,combin:1,come:6,comment:6,condit:5,conf:2,config:0,configur:[1,2,3,4,6],connect:[2,4,5],consist:6,constantli:4,contain:[3,4,6],content:[1,2,6],contract:5,copi:5,copyright:5,creat:[3,6],credenti:[2,3],crit:3,current:5,damag:5,datacent:2,date:3,deal:5,debug:3,decrypt:2,delet:[2,6],deliv:[],deni:6,depend:4,describ:3,descript:[1,2],destin:1,detail:2,develop:5,devic:2,dictionari:6,differ:2,directori:4,disabl:1,distribut:5,dmz:1,doc:[],docker:[0,3],dockerfil:4,document:5,don:4,each:[1,2,3,6],easi:5,easili:[5,6],either:2,empti:6,enabl:5,encourag:2,end:1,engin:5,entir:6,error:3,etc:[3,4],event:5,exampl:[1,2,3,6],exist:3,express:5,extern:4,fals:1,fatal:3,file:[2,3,5],filter:1,firewal:[0,1,4,5],first:0,fit:5,fix:5,follow:[2,5,6],format:[1,2,3],found:3,four:1,fqdn:1,free:5,friendli:3,friendlier:2,from:[1,2,3,4,5,6],full:1,furnish:5,gener:[0,2,4,6],generate_certif:4,get:[1,2,6],git:4,github:4,grant:[1,5],grasp:1,group:1,handl:6,has:[1,2,6],have:[2,3,6],header:6,help:5,her:5,here:[1,2,6],herebi:5,hip:1,his:5,hola:[],holder:5,host:1,http:[2,4,5,6],icmp:1,identifi:[1,6],imag:4,impli:5,improv:4,includ:5,index:0,indic:[2,3],info:3,inform:[2,5],initi:3,insid:2,inspect:1,instal:0,instead:4,internet:1,introduct:0,invok:2,javascript:[],json:[0,1,2,3,5,6],juic:1,junip:0,juno:2,just:[5,6],kei:[0,1,2,4],keymgmt:6,kind:5,latest:4,latter:2,let:5,letter:6,level:3,liabil:5,liabl:5,licens:0,like:1,limit:5,list:6,listen:3,locat:[2,3,6],log:[1,2,3,6],logfil:3,login:[2,3],loglevel:3,machin:5,maintain:4,make:[2,5,6],manag:0,mani:[1,3],mark:1,match:6,merchant:5,merg:5,method:[1,2,6],minim:3,mit:0,modifi:[3,5],modul:0,more:4,mount:[3,4],name:[1,2,6],nativ:1,need:[3,5,6],negat:1,network:[1,5],never:6,noc:6,noninfring:5,note:6,notic:5,now:[3,6],number:6,object:[0,2,5,6],obtain:5,one:[1,2,5,6],onli:[2,3,5],option:3,other:[5,6],otherwis:5,our:[2,6],out:5,over:6,page:0,palo:0,paloalto:[1,2],paramet:3,part:[2,6],particular:5,pass:2,password:[2,3,6],patch:6,path:[4,6],peopl:5,perist:4,permiss:5,permit:5,person:[4,5],port:[1,2,3],portion:5,posit:6,possibl:5,post:[1,2,6],postman:6,prefer:4,primari:2,privat:[1,2],privatekei:2,privatekeypass:2,profil:1,protocol:1,provid:5,pseudo:6,publish:5,pull:4,purpos:5,put:6,python:6,qos:1,random:[4,6],randomli:6,rang:1,recomend:4,regex:6,relat:3,releas:5,reli:1,reliabl:4,remov:[2,5],repo:[0,3,5],request:[1,2,5,6],requir:3,resourc:1,respect:6,respons:1,rest:[0,3,5],restrict:5,retriev:2,right:5,rout:[0,5],rubi:[],rule:[0,5,6],run:4,same:3,schedul:1,scheme:6,script:[2,4,6],search:0,second:2,secondari:2,secret:3,section:3,see:1,select:1,self:5,sell:5,send:[1,2],sent:6,serv:5,server:1,servic:1,set:1,shall:5,should:[2,3,4,6],show:6,similar:2,simpli:[1,5],sinc:4,softwar:5,some:[2,3,6],sourc:1,specif:6,specifi:[2,3],srx:2,ssg:2,ssh:2,ssl:1,stabl:4,start:[1,6],step:0,store:3,stuff:6,subject:5,sublicens:5,subnet:1,substanti:5,successful:6,syslog:3,tag:[1,4],take:6,test:1,them:2,therefor:6,thi:[1,2,3,5,6],thing:3,thorugh:3,those:6,through:[2,3,4,5],time:3,token:6,tort:5,translat:2,travel:6,trust:[2,4],two:6,type:[1,2,3,6],under:5,understand:5,uniqu:2,univers:1,unreach:1,untrust:1,url:[1,6],use:[2,4,5,6],used:2,user:[0,1,2,3],usernam:2,using:5,usual:2,vendor:[0,5],version:4,videlanicola:4,view:5,virtual:5,volum:3,wai:[0,2],want:4,warn:3,warranti:5,web:1,what:[3,4],when:[2,6],where:[3,6],whether:5,which:[2,4,6],whom:5,why:5,without:5,work:[4,5],would:6,wrap:5,wrapper:0,xml:[1,2],xtyrt9tkodjh42smjmohno3j10od3lgm3dzghcen1s5nhiccrzdlrj6vjjwbpbtvgxmfpi3s63bo8abgzt1cgr91rrobvtv8c:6,xwcalv3fplqnuz8avzacedgyxhtwrtsemcf7ih7o1j6xg2ggjf75kaxk0l8b2gmsrvhelrxs1t8s4tjfn2sqb2rvh13b0gzga0vh:6,you:[3,4,6],your:[3,4],ywrtaw46c2vjcmv0:[2,6]},titles:["Welcome to Assimilator\u2019s documentation!","API","Firewall management","First steps","Install","Introduction","API Key Management"],titleterms:{The:4,add:[2,6],alto:2,api:[1,6],assimil:[0,5],clone:4,config:1,docker:4,document:0,firewal:[2,3],first:3,gener:3,indic:0,instal:4,introduct:5,junip:2,kei:[3,6],licens:5,manag:[2,3,6],mit:5,object:1,palo:2,repo:4,rout:1,rule:1,step:3,tabl:0,user:6,wai:4,welcom:0}}) -------------------------------------------------------------------------------- /docs/build/user/firststeps.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | First steps — Assimilator 1.0 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | 52 | 53 | 114 | 115 |
116 | 117 | 118 | 124 | 125 | 126 | 127 |
128 |
129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 |
146 | 147 |
    148 | 149 |
  • Docs »
  • 150 | 151 |
  • First steps
  • 152 | 153 | 154 |
  • 155 | 156 | 157 | View page source 158 | 159 | 160 |
  • 161 | 162 |
163 | 164 | 165 |
166 |
167 |
168 |
169 | 170 |
171 |

First steps

172 |

The first thing you need to do is create a configuration file that adjusts to your needs. Many of these parameters have already been configured for you, but some minimal configuration is needed.

173 |

An example configuration file can be found in the repo. This file specifies the initial configuration for Assimilator, this should be mounted as a volume in the Docker container with the ‘-v’ argument on ‘/etc/assimilator/’.

174 |
175 |

General

176 |

Logfile indicates where logs should be stored.

177 |
178 |
logfile = /var/log/assimilator.log
179 |

The log level that should be logged [DEBUG, INFO, WARN, ERROR, CRIT, FATAL].

180 |
181 |
loglevel = WARN
182 |

The date and time format for the logs, the default is Syslog friendly.

183 |
184 |
format = %d/%m/%Y %H:%M:%S
185 |

The location for the API keys of each user, this file should exist only. API keys are managed through the REST api.

186 |
187 |
apikeyfile = /etc/assimilator/api.key
188 |

The location for all Firewall related authentication. This is managed thorugh the REST api.

189 |
190 |
firewalls = /etc/assimilator/firewalls.json
191 |

Where the API should listen.

192 |
193 |
address = 0.0.0.0
194 |

What port should Assimilator listen to, default is 443.

195 |
196 |
port = 443
197 |
198 |
199 |

Key Management

200 |

This is the authentication required to modify Firewall credentials and user’s API keys.

201 |

From where should Assimilator authenticate users? For now, the only option is ‘static’.

202 |
203 |
type = static
204 |

The user and password required for admin login to the API.

205 |
206 |
user = admin 207 | password = secret
208 |
209 |
210 |

Firewall Management

211 |

Same as Key Management, this section describes the admin user and password required to configure Firewall credentials.

212 |

From where should Assimilator authenticate users? For now, the only option is ‘static’.

213 |
214 |
type = static
215 |

The user and password required for admin login to the API.

216 |
217 |
user = admin 218 | password = secret
219 |
220 |
221 | 222 | 223 |
224 |
225 | 226 |
227 |
228 |
229 | 230 | 238 | 239 | 240 |
241 | 242 |
243 |

244 | © Copyright 2017, Nicolas Videla. 245 | 246 |

247 |
248 | Built with Sphinx using a theme provided by Read the Docs. 249 | 250 |
251 | 252 |
253 |
254 | 255 |
256 | 257 |
258 | 259 | 260 | 261 | 262 | 263 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 291 | 292 | 293 | 294 | -------------------------------------------------------------------------------- /docs/build/user/install.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Install — Assimilator 1.0 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | 52 | 53 | 113 | 114 |
115 | 116 | 117 | 123 | 124 | 125 | 126 |
127 |
128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
145 | 146 |
    147 | 148 |
  • Docs »
  • 149 | 150 |
  • Install
  • 151 | 152 | 153 |
  • 154 | 155 | 156 | View page source 157 | 158 | 159 |
  • 160 | 161 |
162 | 163 | 164 |
165 |
166 |
167 |
168 | 169 |
170 |

Install

171 |

Assimilator can be installed through Docker or cloned into a directory and run from there. Personally I prefer Docker since it’s more reliable, but both ways work.

172 |
173 |

The Docker Way

174 |

The best way to install Assimilator is through Docker:

175 |
176 |
$ docker pull videlanicolas/assimilator:stable
177 |

The latest build is constantly improving, I recomend the stable version or instead the latest tag which are also stable:

178 |
179 |
$ docker pull videlanicolas/assimilator:1.2.2
180 |

Run a container:

181 |
182 |
$ docker run -d -v /path/to/configuration:/etc/assimilator/ -p 443:443 videlanicolas/assimilator:stable
183 |

Docker containers are not peristent, so if you want to maintain your configured Firewalls and API keys you should mount an external directory into the container, that’s what the -v is for.

184 |
185 |
186 |

The Repo-Cloning Way

187 |

A.K.A I don’t trust your Docker image.

188 |

You can clone the repo from Github and build your image of Assimilator from the dockerfile.

189 |
190 |
$ git clone https://github.com/videlanicolas/assimilator.git 191 | $ docker build -t assimilator .
192 |

If you don’t want to use Docker there is a bash script to install the dependencies. Also there is another bash script to generate a random certificate for HTTPS connections.

193 |
194 |
$ git clone https://github.com/videlanicolas/assimilator.git 195 | $ chmod +x install.sh generate_certificate.sh 196 | $ ./generate_certificate.sh 197 | $ ./install.sh
198 |
199 |
200 | 201 | 202 |
203 |
204 | 205 |
206 |
207 |
208 | 209 | 217 | 218 | 219 |
220 | 221 |
222 |

223 | © Copyright 2017, Nicolas Videla. 224 | 225 |

226 |
227 | Built with Sphinx using a theme provided by Read the Docs. 228 | 229 |
230 | 231 |
232 |
233 | 234 |
235 | 236 |
237 | 238 | 239 | 240 | 241 | 242 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /docs/build/user/intro.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Introduction — Assimilator 1.0 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | 52 | 53 | 113 | 114 |
115 | 116 | 117 | 123 | 124 | 125 | 126 |
127 |
128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
145 | 146 |
    147 | 148 |
  • Docs »
  • 149 | 150 |
  • Introduction
  • 151 | 152 | 153 |
  • 154 | 155 | 156 | View page source 157 | 158 | 159 |
  • 160 | 161 |
162 | 163 | 164 |
165 |
166 |
167 |
168 | 169 |
170 |

Introduction

171 |

Assimilator was built to enable automotion through REST API, using JSON objects for easy understanding. With Assimilator a developer can self-serve his/her access through the network, while also auditors can request information without the need of network engineers. This API wraps around all possible vendor Firewalls, let it be appliances, virtual machines or cloud ACL.

172 |

With Assimilator one can automatize Firewall rules easily, just by simply make an HTTP request one can add/remove/modify/view rules and routes.

173 |
174 |

MIT License

175 |

Altough there are other repos and people working on Firewall automation, this is the only repo that serves an API. I’m currently working alone in this and that’s why I released Assimilator under MIT License, because I need other people to help with other Firewall brands and bug fixes.

176 |
177 |
178 |

Assimilator License

179 |
180 |

MIT License

181 |

Copyright (c) 2017

182 |

Permission is hereby granted, free of charge, to any person obtaining a copy 183 | of this software and associated documentation files (the “Software”), to deal 184 | in the Software without restriction, including without limitation the rights 185 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 186 | copies of the Software, and to permit persons to whom the Software is 187 | furnished to do so, subject to the following conditions:

188 |

The above copyright notice and this permission notice shall be included in all 189 | copies or substantial portions of the Software.

190 |

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 191 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 192 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 193 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 194 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 195 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 196 | SOFTWARE.

197 |
198 |
199 |
200 | 201 | 202 |
203 |
204 | 205 |
206 |
207 |
208 | 209 | 217 | 218 | 219 |
220 | 221 |
222 |

223 | © Copyright 2017, Nicolas Videla. 224 | 225 |

226 |
227 | Built with Sphinx using a theme provided by Read the Docs. 228 | 229 |
230 | 231 |
232 |
233 | 234 |
235 | 236 |
237 | 238 | 239 | 240 | 241 | 242 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /docs/build/user/objects.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Objects — Assimilator 1.0 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 | 51 | 105 | 106 |
107 | 108 | 109 | 115 | 116 | 117 | 118 |
119 |
120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 |
137 | 138 |
    139 | 140 |
  • Docs »
  • 141 | 142 |
  • Objects
  • 143 | 144 | 145 |
  • 146 | 147 | 148 | View page source 149 | 150 | 151 |
  • 152 | 153 |
154 | 155 | 156 |
157 |
158 |
159 |
160 | 161 |
162 |

Objects

163 |
164 | 165 | 166 |
167 |
168 | 169 |
170 |
171 |
172 | 173 | 174 |
175 | 176 |
177 |

178 | © Copyright 2017, Nicolas Videla. 179 | 180 |

181 |
182 | Built with Sphinx using a theme provided by Read the Docs. 183 | 184 |
185 | 186 |
187 |
188 | 189 |
190 | 191 |
192 | 193 | 194 | 195 | 196 | 197 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /docs/build/user/route.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Routes — Assimilator 1.0 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | 108 | 109 |
110 | 111 | 112 | 118 | 119 | 120 | 121 |
122 |
123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 |
140 | 141 |
    142 | 143 |
  • Docs »
  • 144 | 145 |
  • Routes
  • 146 | 147 | 148 |
  • 149 | 150 | 151 | View page source 152 | 153 | 154 |
  • 155 | 156 |
157 | 158 | 159 |
160 |
161 |
162 |
163 | 164 |
165 |

Routes

166 |
167 | 168 | 169 |
170 |
171 | 172 |
173 |
174 |
175 | 176 | 182 | 183 | 184 |
185 | 186 |
187 |

188 | © Copyright 2017, Nicolas Videla. 189 | 190 |

191 |
192 | Built with Sphinx using a theme provided by Read the Docs. 193 | 194 |
195 | 196 |
197 |
198 | 199 |
200 | 201 |
202 | 203 | 204 | 205 | 206 | 207 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 235 | 236 | 237 | 238 | -------------------------------------------------------------------------------- /docs/build/user/rules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Rules — Assimilator 1.0 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | 52 | 53 | 109 | 110 |
111 | 112 | 113 | 119 | 120 | 121 | 122 |
123 |
124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 |
141 | 142 |
    143 | 144 |
  • Docs »
  • 145 | 146 |
  • Rules
  • 147 | 148 | 149 |
  • 150 | 151 | 152 | View page source 153 | 154 | 155 |
  • 156 | 157 |
158 | 159 | 160 |
161 |
162 |
163 |
164 | 165 |
166 |

Rules

167 |
168 | 169 | 170 |
171 |
172 | 173 |
174 |
175 |
176 | 177 | 185 | 186 | 187 |
188 | 189 |
190 |

191 | © Copyright 2017, Nicolas Videla. 192 | 193 |

194 |
195 | Built with Sphinx using a theme provided by Read the Docs. 196 | 197 |
198 | 199 |
200 |
201 | 202 |
203 | 204 |
205 | 206 | 207 | 208 | 209 | 210 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Assimilator documentation build configuration file, created by 5 | # sphinx-quickstart on Thu Jun 8 16:14:09 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = ['sphinx.ext.autodoc', 35 | 'sphinx.ext.doctest', 36 | 'sphinx.ext.todo', 37 | 'sphinx.ext.viewcode', 38 | 'sphinx.ext.githubpages'] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # The suffix(es) of source filenames. 44 | # You can specify multiple suffix as a list of string: 45 | # 46 | # source_suffix = ['.rst', '.md'] 47 | source_suffix = '.rst' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = 'Assimilator' 54 | copyright = '2017, Nicolas Videla' 55 | author = 'Nicolas Videla' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | version = '1.0' 63 | # The full version, including alpha/beta/rc tags. 64 | release = '1.0' 65 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation 67 | # for a list of supported languages. 68 | # 69 | # This is also used if you do content translation via gettext catalogs. 70 | # Usually you set "language" from the command line for these cases. 71 | language = None 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | # This patterns also effect to html_static_path and html_extra_path 76 | exclude_patterns = [] 77 | 78 | # The name of the Pygments (syntax highlighting) style to use. 79 | pygments_style = 'sphinx' 80 | 81 | # If true, `todo` and `todoList` produce output, else they produce nothing. 82 | todo_include_todos = True 83 | 84 | 85 | # -- Options for HTML output ---------------------------------------------- 86 | 87 | # The theme to use for HTML and HTML Help pages. See the documentation for 88 | # a list of builtin themes. 89 | # 90 | html_theme = 'sphinx_rtd_theme' 91 | 92 | # Theme options are theme-specific and customize the look and feel of a theme 93 | # further. For a list of options available for each theme, see the 94 | # documentation. 95 | # 96 | # html_theme_options = {} 97 | 98 | # Add any paths that contain custom static files (such as style sheets) here, 99 | # relative to this directory. They are copied after the builtin static files, 100 | # so a file named "default.css" will overwrite the builtin "default.css". 101 | html_static_path = ['_static'] 102 | 103 | 104 | # -- Options for HTMLHelp output ------------------------------------------ 105 | 106 | # Output file base name for HTML help builder. 107 | htmlhelp_basename = 'Assimilatordoc' 108 | 109 | 110 | # -- Options for LaTeX output --------------------------------------------- 111 | 112 | latex_elements = { 113 | # The paper size ('letterpaper' or 'a4paper'). 114 | # 115 | # 'papersize': 'letterpaper', 116 | 117 | # The font size ('10pt', '11pt' or '12pt'). 118 | # 119 | # 'pointsize': '10pt', 120 | 121 | # Additional stuff for the LaTeX preamble. 122 | # 123 | # 'preamble': '', 124 | 125 | # Latex figure (float) alignment 126 | # 127 | # 'figure_align': 'htbp', 128 | } 129 | 130 | # Grouping the document tree into LaTeX files. List of tuples 131 | # (source start file, target name, title, 132 | # author, documentclass [howto, manual, or own class]). 133 | latex_documents = [ 134 | (master_doc, 'Assimilator.tex', 'Assimilator Documentation', 135 | 'Nicolas Videla', 'manual'), 136 | ] 137 | 138 | 139 | # -- Options for manual page output --------------------------------------- 140 | 141 | # One entry per manual page. List of tuples 142 | # (source start file, name, description, authors, manual section). 143 | man_pages = [ 144 | (master_doc, 'assimilator', 'Assimilator Documentation', 145 | [author], 1) 146 | ] 147 | 148 | 149 | # -- Options for Texinfo output ------------------------------------------- 150 | 151 | # Grouping the document tree into Texinfo files. List of tuples 152 | # (source start file, target name, title, author, 153 | # dir menu entry, description, category) 154 | texinfo_documents = [ 155 | (master_doc, 'Assimilator', 'Assimilator Documentation', 156 | author, 'Assimilator', 'One line description of project.', 157 | 'Miscellaneous'), 158 | ] 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Assimilator documentation master file, created by 2 | sphinx-quickstart on Thu Jun 8 16:14:09 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Assimilator's documentation! 7 | ======================================= 8 | JSON REST API wrapper for vendor firewalls. 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | 13 | user/intro.rst 14 | user/install.rst 15 | user/firststeps.rst 16 | user/keymanagement.rst 17 | user/firewallmgmt.rst 18 | user/api.rst 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /docs/source/user/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | API 4 | === 5 | 6 | The juice of Assimilator relies on the /api. From here one can access all Firewall configuration, check rules, routes and network objects. Also the user can test an access to see if the Firewall grants the access. Assimilator has default resource URL for all firewalls (like rules, objects and routes) and private resource URL destined for each Firewall brand. This is to grasp the full functionality of Firewalls. 7 | 8 | Config 9 | ------ 10 | 11 | **/api//config** 12 | 13 | Gets the full configuration of the Firewall, in it's native format. In many cases this is XML. 14 | 15 | *Example* 16 | 17 | :: 18 | 19 | GET /api/argentina/config 20 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 21 | Content-Type: application/json 22 | 23 | :: 24 | 25 | 200 OK 26 | 27 | .. block-code: json 28 | 29 | { 30 | "config" : " ... " 31 | } 32 | 33 | 34 | Rules 35 | ----- 36 | 37 | **/api//rules** 38 | 39 | Get all rules in the selected Firewall. This can be filtered with URL arguments. 40 | 41 | *Example (PaloAlto)* 42 | 43 | :: 44 | 45 | GET /api/argentina/rules 46 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 47 | Content-Type: application/json 48 | 49 | :: 50 | 51 | 200 OK 52 | 53 | .. block-code: json 54 | 55 | { 56 | "rules" : [ 57 | { 58 | "log-end": false, 59 | "qos": { 60 | "marking": null, 61 | "type": null 62 | }, 63 | "negate-source": false, 64 | "disabled": true, 65 | "rule-type": "universal", 66 | "tag": [], 67 | "log-start": false, 68 | "hip-profiles": [], 69 | "negate-destination": false, 70 | "description": null, 71 | "category": [ 72 | "any" 73 | ], 74 | "from": [ 75 | "trust" 76 | ], 77 | "service": [ 78 | "any" 79 | ], 80 | "source": [ 81 | "any" 82 | ], 83 | "destination": [ 84 | "8.8.8.8", 85 | "8.8.4.4" 86 | ], 87 | "application": [ 88 | "dns" 89 | ], 90 | "profile-setting": null, 91 | "log-setting": null, 92 | "to": [ 93 | "untrust" 94 | ], 95 | "schedule": null, 96 | "source-user": [ 97 | "any" 98 | ], 99 | "icmp-unreachable": false, 100 | "name": "DNS Google Access", 101 | "disable-server-response-inspection": false, 102 | "action": "allow" 103 | }, 104 | ... 105 | ] 106 | } 107 | 108 | *Example with arguments (PaloAlto)* 109 | 110 | :: 111 | 112 | GET /api/argentina/rules?from=dmz&to=untrust 113 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 114 | Content-Type: application/json 115 | 116 | :: 117 | 118 | 200 OK 119 | 120 | .. block-code: json 121 | 122 | { 123 | "rules" : [ 124 | { 125 | "log-end": true, 126 | "qos": { 127 | "marking": null, 128 | "type": null 129 | }, 130 | "negate-source": false, 131 | "disabled": true, 132 | "rule-type": "universal", 133 | "tag": [], 134 | "log-start": false, 135 | "hip-profiles": [], 136 | "negate-destination": false, 137 | "description": null, 138 | "category": [ 139 | "any" 140 | ], 141 | "from": [ 142 | "dmz" 143 | ], 144 | "service": [ 145 | "any" 146 | ], 147 | "source": [ 148 | "any" 149 | ], 150 | "destination": [ 151 | "10.10.50.2", 152 | ], 153 | "application": [ 154 | "web-browsing", 155 | "ssl" 156 | ], 157 | "profile-setting": null, 158 | "log-setting": null, 159 | "to": [ 160 | "untrust" 161 | ], 162 | "schedule": null, 163 | "source-user": [ 164 | "any" 165 | ], 166 | "icmp-unreachable": false, 167 | "name": "Internet access", 168 | "disable-server-response-inspection": false, 169 | "action": "allow" 170 | }, 171 | ... 172 | ] 173 | } 174 | 175 | 176 | To add a rule one simply change the method to POST and sends one of these JSON objects in the body of the request. 177 | 178 | :: 179 | 180 | POST /api/brasil/rules 181 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 182 | Content-Type: application/json 183 | { 184 | "log-end": true, 185 | "qos": { 186 | "marking": null, 187 | "type": null 188 | }, 189 | "negate-source": false, 190 | "disabled": true, 191 | "rule-type": "universal", 192 | "tag": [], 193 | "log-start": false, 194 | "hip-profiles": [], 195 | "negate-destination": false, 196 | "description": null, 197 | "category": [ 198 | "any" 199 | ], 200 | "from": [ 201 | "dmz" 202 | ], 203 | "service": [ 204 | "any" 205 | ], 206 | "source": [ 207 | "any" 208 | ], 209 | "destination": [ 210 | "10.10.50.2", 211 | ], 212 | "application": [ 213 | "web-browsing", 214 | "ssl" 215 | ], 216 | "profile-setting": null, 217 | "log-setting": null, 218 | "to": [ 219 | "untrust" 220 | ], 221 | "schedule": null, 222 | "source-user": [ 223 | "any" 224 | ], 225 | "icmp-unreachable": false, 226 | "name": "Internet access", 227 | "disable-server-response-inspection": false, 228 | "action": "allow" 229 | } 230 | 231 | To delete a rule, use DELETE. 232 | 233 | :: 234 | 235 | POST /api/brasil/rules 236 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 237 | Content-Type: application/json 238 | { 239 | "name" : "Some Rule Name" 240 | } 241 | 242 | To replace rules values use PUT, here we **replace** the values of 'source' with new values. 243 | 244 | :: 245 | 246 | POST /api/brasil/rules 247 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 248 | Content-Type: application/json 249 | { 250 | "name" : "Some Rule Name", 251 | "source" : 252 | { 253 | "192.168.1.50", 254 | "192.168.1.40" 255 | } 256 | } 257 | 258 | To append new objects to a rule use PATCH, here we **add** objects to destination. 259 | 260 | :: 261 | 262 | POST /api/brasil/rules 263 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 264 | Content-Type: application/json 265 | { 266 | "name" : "Some Rule Name", 267 | "destination" : 268 | { 269 | "100.200.100.10" 270 | } 271 | } 272 | 273 | Match 274 | ----- 275 | 276 | **/api//rules/match** 277 | 278 | A very useful resource is match. With it one can test a source, destination and port to check if the Firewall allows that connection. Many Firewalls already have this funcionality, other don't (AWS). What they lack is the ease of use. Assimilator only requires source, destination and port (optionally a protocol), other required input by the Firewalls (such as dmz zones) are resolved by Assimilator either through route tables or configuration. 279 | If the access is granted then it returns the rule that allows it. 280 | 281 | :: 282 | 283 | GET /api/uruguay/rules/match?source=192.168.4.5&destination=100.150.100.150&port=443 284 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 285 | Content-Type: application/json 286 | 287 | :: 288 | 289 | 200 ok 290 | 291 | .. block-code: json 292 | 293 | { 294 | 295 | } 296 | 297 | 298 | Objects 299 | ------- 300 | 301 | **/api//objects/** 302 | 303 | Firewall objects identify hosts and ports in the rules, basically there are four type of objects: 304 | 305 | * Address: Hosts identified by an IP, IP range, subnet or FQDN. 306 | * Service: A combination of protocol and source/destination port. 307 | * Address Group: A group of Address objects. 308 | * Service Group: A group of service objects. 309 | 310 | With Assimilator one can create/modify/delete objects easily. 311 | 312 | :: 313 | 314 | POST /api/chile/objects/address 315 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 316 | Content-Type: application/json 317 | { 318 | "name" : "Corp_DNS", 319 | 320 | } 321 | 322 | 323 | 324 | Routes 325 | ------ 326 | 327 | **/api//route** 328 | 329 | -------------------------------------------------------------------------------- /docs/source/user/firewallmgmt.rst: -------------------------------------------------------------------------------- 1 | .. _firewall management: 2 | 3 | Firewall management 4 | =================== 5 | 6 | This is the second part of the admin configuration, this part should be accessed through HTTP authenteication with the user and password specified in assimilator.conf file. Here the admin configures all Firewall credentials, with this information Assimilator will then access each Firewall and retrieve the information requested through API calls. 7 | Each Firewall brand has their our way to be accessed, in general it's an SSH connection but some of them use an API (PaloAlto or AWS). 8 | 9 | 10 | Add a Firewall 11 | -------------- 12 | 13 | To add a Firewall we make an admin POST request to /firewalls/, in the request's body we should send the JSON object with the Firewall's credentials. 14 | 15 | :: 16 | 17 | POST /firewalls/argentina HTTP/1.1 18 | Content-Type: application/json 19 | Authorization: Basic YWRtaW46c2VjcmV0 20 | { 21 | "brand" : , 22 | "description" : , 23 | #JSON object keys for the Firewall brand 24 | ... 25 | } 26 | 27 | To remove a Firewall from Assimilator we make a DELETE request. 28 | 29 | :: 30 | 31 | DELETE /firewalls/argentina HTTP/1.1 32 | Content-Type: application/json 33 | Authorization: Basic YWRtaW46c2VjcmV0 34 | 35 | To retrieve the Firewall configuration we make a GET request. 36 | 37 | :: 38 | 39 | GET /firewalls/argentina HTTP/1.1 40 | Content-Type: application/json 41 | Authorization: Basic YWRtaW46c2VjcmV0 42 | 43 | 44 | Each Firewall brand is configured differently, this is because each Firewall has their way to be accessed. For each Firewall there is a unique JSON object format. 45 | Below is the detailed configuration for each device. 46 | 47 | Palo Alto 48 | --------- 49 | 50 | PaloAlto firewalls have an XML API that only has the GET method. Through this Assimilator translates it to a friendlier API. 51 | 52 | :: 53 | 54 | GET /firewalls/argentina HTTP/1.1 55 | Content-Type: application/json 56 | Authorization: Basic YWRtaW46c2VjcmV0 57 | 58 | :: 59 | 60 | 200 OK 61 | 62 | .. code-block: json 63 | 64 | { 65 | "brand": "paloalto", 66 | "secondary": "192.168.1.2", 67 | "primary": "192.168.1.1", 68 | "key": "LUFRPT1fhejJjfjelajcmalseiVWFjhfu37Hu39fjLLifj38ejL00ffj398ejLKfjei4o0apLmfnjfkel49ii00fa9sf=", 69 | "description": "Firewall Argentina" 70 | } 71 | 72 | The key is the Firewall name through the api, in this example the key is 'argentina'. Inside this JSON object we have the following keys: 73 | 74 | :: 75 | 76 | "brand" : The Firewall's brand, this will indicate which translator script should be invoked when connecting to this firewall. 77 | "primary" : The Firewall's primary IP address, in PaloAlto this should be the Management IP address. 78 | "secondary" : The Firewall's secondary IP address, in PaloAlto this should be the Management IP address. 79 | "key" : XML API key to be used by Assimilator when connecting to this PaloAlto Firewall. 80 | "description" : Some description about this device. 81 | 82 | 83 | Juniper 84 | ------- 85 | 86 | Junos SRX and SSG have a similar configuration, both are XML based and are accessed through SSH. 87 | 88 | :: 89 | 90 | GET /firewalls/datacenter HTTP/1.1 91 | Content-Type: application/json 92 | Authorization: Basic YWRtaW46c2VjcmV0 93 | 94 | :: 95 | 96 | 200 OK 97 | 98 | .. code-block: json 99 | 100 | { 101 | "description": "Firewall SRX Datacenter.", 102 | "brand": "juniper", 103 | "privatekey": "", 104 | "primary": "172.16.1.1", 105 | "secondary": "172.16.1.2", 106 | "privatekeypass": "", 107 | "user": "assimilator", 108 | "timeout": 1200, 109 | "pass": "somepassword", 110 | "port": 22, 111 | "name": "datacenter" 112 | } 113 | 114 | The key is the Firewall name through the api, in this example the key is 'datacenter'. Juniper allows users to login either with a password or a certificate, the latter one is encouraged. 115 | Inside this JSON object we have the following keys: 116 | 117 | :: 118 | 119 | "brand" : The Firewall's brand, this will indicate which translator script should be invoked when connecting to this firewall. 120 | "primary" : The Firewall's primary IP address, in Juniper this should be the trust IP address. 121 | "secondary" : The Firewall's secondary IP address, in Juniper this should the trust IP address. 122 | "user" : The username that Assimilator should use while logging in, it usually is 'assimilator'. 123 | "privatekey" : Location of the certificate file to be used for SSH authentication, if not specified then user/password will be used. 124 | "privatekeypass" : The password to decrypt the private key from the certificate, if not specified then user/password will be used. 125 | "pass" : The password to be used for SSH login, this is used if privatekey and privatekeypass is not specified. 126 | "port" : The SSH port on the Firewall, usually 22. 127 | "description" : Some description about this device. 128 | -------------------------------------------------------------------------------- /docs/source/user/firststeps.rst: -------------------------------------------------------------------------------- 1 | .. _first steps: 2 | 3 | First steps 4 | =========== 5 | 6 | The first thing you need to do is create a configuration file that adjusts to your needs. Many of these parameters have already been configured for you, but some minimal configuration is needed. 7 | 8 | An `example configuration `_ file can be found in the repo. This file specifies the initial configuration for Assimilator, this should be mounted as a volume in the Docker container with the '-v' argument on '/etc/assimilator/'. 9 | 10 | General 11 | ------- 12 | 13 | Logfile indicates where logs should be stored. 14 | 15 | logfile = /var/log/assimilator.log 16 | 17 | The log level that should be logged [DEBUG, INFO, WARN, ERROR, CRIT, FATAL]. 18 | 19 | loglevel = WARN 20 | 21 | The date and time format for the logs, the default is Syslog friendly. 22 | 23 | format = %d/%m/%Y %H:%M:%S 24 | 25 | The location for the API keys of each user, this file should exist only. API keys are managed through the REST api. 26 | 27 | apikeyfile = /etc/assimilator/api.key 28 | 29 | The location for all Firewall related authentication. This is managed thorugh the REST api. 30 | 31 | firewalls = /etc/assimilator/firewalls.json 32 | 33 | Where the API should listen. 34 | 35 | address = 0.0.0.0 36 | 37 | What port should Assimilator listen to, default is 443. 38 | 39 | port = 443 40 | 41 | Key Management 42 | -------------- 43 | 44 | This is the authentication required to modify Firewall credentials and user's API keys. 45 | 46 | From where should Assimilator authenticate users? For now, the only option is 'static'. 47 | 48 | type = static 49 | 50 | The user and password required for admin login to the API. 51 | 52 | user = admin 53 | password = secret 54 | 55 | 56 | Firewall Management 57 | ------------------- 58 | 59 | Same as Key Management, this section describes the admin user and password required to configure Firewall credentials. 60 | 61 | From where should Assimilator authenticate users? For now, the only option is 'static'. 62 | 63 | type = static 64 | 65 | The user and password required for admin login to the API. 66 | 67 | user = admin 68 | password = secret -------------------------------------------------------------------------------- /docs/source/user/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Install 4 | ======= 5 | 6 | Assimilator can be installed through Docker or cloned into a directory and run from there. Personally I prefer Docker since it's more reliable, but both ways work. 7 | 8 | The Docker Way 9 | -------------- 10 | 11 | The best way to install Assimilator is through `Docker `_: 12 | 13 | $ docker pull videlanicolas/assimilator:stable 14 | 15 | The latest build is constantly improving, I recomend the stable version or instead the latest tag which are also stable: 16 | 17 | $ docker pull videlanicolas/assimilator:1.2.2 18 | 19 | Run a container: 20 | 21 | $ docker run -d -v /path/to/configuration:/etc/assimilator/ -p 443:443 videlanicolas/assimilator:stable 22 | 23 | Docker containers are not peristent, so if you want to maintain your configured Firewalls and API keys you should mount an external directory into the container, that's what the -v is for. 24 | 25 | The Repo-Cloning Way 26 | -------------------- 27 | 28 | A.K.A I don't trust your Docker image. 29 | 30 | You can clone the repo from `Github `_ and build your image of Assimilator from the `dockerfile `_. 31 | 32 | $ git clone https://github.com/videlanicolas/assimilator.git 33 | $ docker build -t assimilator . 34 | 35 | If you don't want to use Docker there is a `bash script `_ to install the dependencies. Also there is `another bash script `_ to generate a random certificate for HTTPS connections. 36 | 37 | $ git clone https://github.com/videlanicolas/assimilator.git 38 | $ chmod +x install.sh generate_certificate.sh 39 | $ ./generate_certificate.sh 40 | $ ./install.sh -------------------------------------------------------------------------------- /docs/source/user/intro.rst: -------------------------------------------------------------------------------- 1 | .. _introduction: 2 | 3 | Introduction 4 | ============ 5 | 6 | Assimilator was built to enable automotion through REST API, using JSON objects for easy understanding. With Assimilator a developer can self-serve his/her access through the network, while also auditors can request information without the need of network engineers. This API wraps around all possible vendor Firewalls, let it be appliances, virtual machines or cloud ACL. 7 | 8 | With Assimilator one can automatize Firewall rules easily, just by simply make an HTTP request one can add/remove/modify/view rules and routes. 9 | 10 | .. _`apache2`: 11 | 12 | MIT License 13 | ----------- 14 | 15 | Altough there are other repos and people working on Firewall automation, this is the only repo that serves an API. I'm currently working alone in this and that's why I released Assimilator under `MIT License`_, because I need other people to help with other Firewall brands and bug fixes. 16 | 17 | .. _`MIT License`: https://opensource.org/licenses/MIT 18 | 19 | 20 | Assimilator License 21 | ------------------- 22 | 23 | .. include:: ../../../LICENSE 24 | -------------------------------------------------------------------------------- /docs/source/user/keymanagement.rst: -------------------------------------------------------------------------------- 1 | .. _key management: 2 | 3 | API Key Management 4 | ================== 5 | 6 | There are two URL from where the admin logs in, one of those is the Key management. 7 | 8 | Key management handles the API keys sent to Assimilator, it identifies API keys with a matching authorization token. When an API key is randomly generated it has no authorization to do stuff on the API, that's when authorization tokens come in. Each API key has a list of authorization tokens which contain a regex in the URL and available HTTP methods. 9 | For example: 10 | 11 | .. code-block:: json 12 | 13 | { 14 | "token": 15 | [ 16 | { 17 | "path": "/api/.*", 18 | "method": 19 | [ 20 | "GET", 21 | "POST", 22 | "PUT" 23 | ] 24 | } 25 | ], 26 | "key": "BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s" 27 | } 28 | 29 | 30 | Here we have an API key containing the key and token. The key is just 100 pseudo-random numbers and letters, this key should travel as an HTTP header named 'key' in the request. The other part is the token, it consists of a list where each object in that list consist of a dictionary with a 'path' and 'method'. 31 | The 'path' is a regex applied over the requested URL, and 'method' is a list of allowed HTTP methods over that regex match. Our request should match some object on this list, the following example shows a positive authentication. 32 | 33 | :: 34 | 35 | GET /api/hq/rules HTTP/1.1 36 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 37 | Content-Type: application/json 38 | 39 | This example shows a denied authorization. 40 | 41 | :: 42 | 43 | DELETE /api/hq/rules HTTP/1.1 44 | key: BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s 45 | Content-Type: application/json 46 | 47 | With this scheme one can assign API keys to both users and scripts, therefore a user can easily use this API (ie. `Postman `_) and also a Python script (ie. with `Requests `_. 48 | 49 | Add a user 50 | ---------- 51 | 52 | To add a new user to the API use the configured user and password for admin access (located `here `_) as HTTP authentication. Make a GET to /keymgmt. 53 | 54 | :: 55 | 56 | GET /keymgmt HTTP/1.1 57 | Authorization: Basic YWRtaW46c2VjcmV0 58 | Content-Type: application/json 59 | 60 | If you never added a user to the API this request should return an empty JSON. If not, it will return a JSON dictionary of user numbers and their respective key and tokens. 61 | 62 | .. code-block:: json 63 | 64 | { 65 | "1" : 66 | { 67 | "comment" : "Audit" 68 | "token": 69 | [ 70 | { 71 | "path": "/api/.*", 72 | "method": [ 73 | "GET", 74 | "POST", 75 | "PUT", 76 | "PATCH", 77 | "DELETE" 78 | ] 79 | } 80 | ], 81 | "key": "BDP0NyHZMDfz98kcmD3GuBIQGW9EZTgWGPf56dWnkD3LGM3dZPaZICrKVnTnQWh5YdGLh5SJ9ktg7ReR4le94zyxdigdLTHHf8s" 82 | }, 83 | "2" : 84 | { 85 | "comment": "NOC", 86 | "token": 87 | [ 88 | { 89 | "path": "/api/hq/.*", 90 | "method": [ 91 | "GET" 92 | ] 93 | }, 94 | { 95 | "path": "/api/branch1/.*", 96 | "method": [ 97 | "GET" 98 | ] 99 | } 100 | ], 101 | "key": "xTYRt9tKODjh42smjmoHno3j10OD3LGM3dZgHcen1S5NhICCRzdlrj6VJJwBpBTVgXmfpI3S63bo8aBGZT1CGR91rroBvTv8cer" 102 | } 103 | } 104 | 105 | To add a user you need to generate a new pseudo-random API key. 106 | 107 | :: 108 | 109 | POST /keymgmt/generate HTTP/1.1 110 | Authorization: Basic YWRtaW46c2VjcmV0 111 | Content-Type: application/json 112 | {"comment" : "Some User"} 113 | 114 | :: 115 | 116 | 201 CREATED 117 | 118 | .. code-block:: json 119 | 120 | { 121 | "3": { 122 | "comment": "Some User", 123 | "token": [], 124 | "key": "xWCALV3fPLqnUZ8avZaCeDGyXhTwrTSEMcf7iH7o1j6XG2gGJF75kAXk0l8b2GMsrvHELrXS1T8S4tjfN2SQB2RVH13B0gzGa0vh" 125 | } 126 | } 127 | 128 | And now assign new tokens to that user. 129 | 130 | :: 131 | 132 | POST /keymgmt/3 HTTP/1.1 133 | Authorization: Basic YWRtaW46c2VjcmV0 134 | Content-Type: application/json 135 | { 136 | "path": "\/api\/hq\/rules\/.*", 137 | "method": [ 138 | "GET", 139 | "POST" 140 | ] 141 | } 142 | 143 | :: 144 | 145 | 201 CREATED 146 | 147 | 148 | .. code-block:: json 149 | 150 | { 151 | "path": "/api/hq/rules/.*", 152 | "method": [ 153 | "GET", 154 | "POST" 155 | ] 156 | } 157 | 158 | Take note of the backslash. 159 | Check that it was successfull with GET. 160 | 161 | :: 162 | 163 | GET /keymgmt/3 HTTP/1.1 164 | Authorization: Basic YWRtaW46c2VjcmV0 165 | Content-Type: application/json 166 | 167 | :: 168 | 169 | 200 OK 170 | 171 | .. code-block:: json 172 | 173 | { 174 | "3": { 175 | "comment": "Some User", 176 | "token": [ 177 | { 178 | "path": "/api/hq/rules/.*", 179 | "method": [ 180 | "GET", 181 | "POST" 182 | ] 183 | }], 184 | "key": "xWCALV3fPLqnUZ8avZaCeDGyXhTwrTSEMcf7iH7o1j6XG2gGJF75kAXk0l8b2GMsrvHELrXS1T8S4tjfN2SQB2RVH13B0gzGa0vh" 185 | } 186 | } 187 | 188 | You can't delete specific authorizataion tokens, you would have to delete the entire API key and start over. For that one can use the DELETE method. 189 | 190 | :: 191 | 192 | DELETE /keymgmt/3 HTTP/1.1 193 | Authorization: Basic YWRtaW46c2VjcmV0 194 | Content-Type: application/json 195 | 196 | :: 197 | 198 | 200 OK 199 | -------------------------------------------------------------------------------- /entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | chown -R www-data:www-data /etc/assimilator/ 3 | service apache2 start 4 | service apache2 stop 5 | apache2ctl -X -------------------------------------------------------------------------------- /generate_certificate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | openssl req -x509 -newkey rsa:4096 -keyout assimilator.key -out assimilator.crt -days 3650 3 | openssl rsa -in assimilator.key -out assimilator.key 4 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Installing dependencies..." 4 | apt-get update && apt-get install -y apache2 libapache2-mod-wsgi openssl python-lxml 5 | 6 | echo "Creating directories..." 7 | mkdir /etc/assimilator /var/www/assimilator /etc/apache2/ssl 8 | touch /var/log/assimilator.log 9 | 10 | echo "Copying configuration files..." 11 | cp ${PWD}/assimilator_vhost.conf /etc/apache2/sites-available/assimilator_vhost.conf 12 | cp ${PWD}/run.py /var/www/assimilator/run.py 13 | cp ${PWD}/assimilator.wsgi /var/www/assimilator/assimilator.wsgi 14 | cp ${PWD}/assimilator.conf /etc/assimilator/assimilator.conf 15 | touch /etc/assimilator/firewalls.json 16 | touch /etc/assimilator/api.key 17 | touch /var/www/assimilator/__init__.py 18 | cp -R ${PWD}/app /var/www/assimilator/ 19 | 20 | echo "Generating RSA key pair..." 21 | openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/apache2/ssl/assimilator.key -out /etc/apache2/ssl/assimilator.crt 22 | 23 | echo "Asigning permissions..." 24 | chown -R www-data /var/www/assimilator/ 25 | chgrp -R www-data /var/www/assimilator/ 26 | chown www-data /etc/apache2/ssl/assimilator.key /etc/apache2/ssl/assimilator.crt /etc/apache2/sites-available/assimilator_vhost.conf /var/log/assimilator.log /etc/assimilator/assimilator.conf /etc/assimilator/api.key /etc/assimilator/firewalls.json 27 | chgrp www-data /etc/apache2/ssl/assimilator.key /etc/apache2/ssl/assimilator.crt /etc/apache2/sites-available/assimilator_vhost.conf /var/log/assimilator.log /etc/assimilator/assimilator.conf /etc/assimilator/api.key /etc/assimilator/firewalls.json 28 | chmod 600 /etc/assimilator/* 29 | 30 | echo "Enabling apache2 mods..." 31 | a2enmod ssl wsgi 32 | 33 | echo "Activating apache configuration file..." 34 | a2ensite assimilator_vhost 35 | 36 | echo "Restarting apache..." 37 | service apache2 restart 38 | -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videlanicolas/assimilator/568f5d690ddbcd37f55ffd2611fe486a9e41052d/media/logo.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | lxml == 3.6.1 2 | junos-eznc == 2.0.1 3 | werkzeug == 0.11.10 4 | flask == 0.11.1 5 | flask_restful == 0.3.5 6 | bs4 == 0.0.1 7 | requests == 2.10.0 -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!flask/bin/python 2 | from werkzeug.exceptions import HTTPException 3 | from flask import Flask,jsonify,render_template, request 4 | from flask_restful import Resource, Api 5 | from traceback import format_exc 6 | import app.modules.handler as handler 7 | import app.modules.apikeymgmt as keymgmt 8 | import app.modules.firewalls as firewalls 9 | import app.modules.status as status 10 | import ConfigParser, os 11 | import logging 12 | import bs4 13 | 14 | #Read configuration file 15 | config = ConfigParser.RawConfigParser() 16 | try: 17 | config.read('/etc/assimilator/assimilator.conf') 18 | LOG_FILE = config.get('General','logfile') 19 | LOG_LEVEL = config.get('General','loglevel') 20 | assert LOG_LEVEL in ['DEBUG', 'INFO', 'WARN', 'ERROR', 'CRIT', 'FATAL'] 21 | if LOG_LEVEL == 'DEBUG': 22 | LOG_LEVEL = logging.DEBUG 23 | elif LOG_LEVEL == 'INFO': 24 | LOG_LEVEL = logging.INFO 25 | elif LOG_LEVEL == 'WARN': 26 | LOG_LEVEL = logging.WARNING 27 | elif LOG_LEVEL == 'ERROR': 28 | LOG_LEVEL = logging.ERROR 29 | elif LOG_LEVEL == 'CRIT': 30 | LOG_LEVEL = logging.CRITICAL 31 | elif LOG_LEVEL == 'FATAL': 32 | LOG_LEVEL = logging.FATAL 33 | datefmt = config.get('General','format') 34 | except Exception as e: 35 | print "Error parsing configuration file: {0}".format(str(e)) 36 | quit() 37 | 38 | logging.getLogger("requests").setLevel(logging.ERROR) 39 | logging.getLogger("bs4").setLevel(logging.ERROR) 40 | 41 | #Logging configuration 42 | logging.basicConfig(filename=LOG_FILE,level=LOG_LEVEL,format='%(asctime)s - %(levelname)s - %(message)s', datefmt=datefmt) 43 | 44 | app = Flask(__name__) 45 | api = Api(app) 46 | 47 | #Error handling 48 | @app.errorhandler(Exception) 49 | def handle_error(e): 50 | logging.critical(str(format_exc()) + '\n' + str(e)) 51 | code = 500 52 | if isinstance(e, HTTPException): 53 | code = e.code 54 | return jsonify(error='Unknown Error'), code 55 | 56 | #API Keys management 57 | api.add_resource(keymgmt.mgmt, '/keymgmt/') 58 | api.add_resource(keymgmt.mgmt_all, '/keymgmt') 59 | api.add_resource(keymgmt.generate, '/keymgmt/generate') 60 | #Firewall Management 61 | api.add_resource(firewalls.firewalls, '/firewalls/') 62 | api.add_resource(firewalls.firewalls_all, '/firewalls') 63 | 64 | #API REST Resources 65 | #List Firewalls 66 | api.add_resource(handler.listfirewalls, '/api/listfirewalls') 67 | 68 | #STATUS 69 | api.add_resource(status.status, '/api/status') 70 | 71 | #CONFIG 72 | api.add_resource(handler.config, '/api//config') 73 | 74 | #RULES 75 | api.add_resource(handler.rules, '/api//rules') 76 | api.add_resource(handler.rules_move, '/api//rules/move') 77 | api.add_resource(handler.rules_rename, '/api//rules/rename') 78 | api.add_resource(handler.rules_match, '/api//rules/match') 79 | 80 | #INTERFACES 81 | api.add_resource(handler.interfaces, '/api//interfaces') 82 | 83 | #OBJECTS 84 | api.add_resource(handler.objects, '/api//objects/') 85 | api.add_resource(handler.objects_rename, '/api//objects//rename') 86 | 87 | #ROUTE 88 | api.add_resource(handler.route, '/api//route') 89 | #api.add_resource(handler.route_match, '/api///route/match') 90 | 91 | ####Device Specific#### 92 | ##Palo Alto## 93 | #LOCK 94 | api.add_resource(handler.lock, '/api//locks') 95 | api.add_resource(handler.lock_option, '/api//locks/') 96 | api.add_resource(handler.lock_admin, '/api//locks//') 97 | 98 | #COMMIT 99 | api.add_resource(handler.commit, '/api//commit') 100 | 101 | #LOGGING 102 | api.add_resource(handler.logging, '/api//logging') 103 | 104 | ##Palo Alto## 105 | #Hitcount 106 | api.add_resource(handler.hitcount, '/api//rules/hitcount') 107 | 108 | if __name__ == '__main__': 109 | app.run(host='0.0.0.0',debug=False) 110 | -------------------------------------------------------------------------------- /tests/test_api.py: -------------------------------------------------------------------------------- 1 | def test_answer(): 2 | assert 5 == 5 3 | --------------------------------------------------------------------------------