From 4bd5d74975d94dd1b9ff7bb3effed520080374d2 Mon Sep 17 00:00:00 2001 From: crudo Date: Sat, 16 Dec 2017 23:03:03 +0100 Subject: [PATCH] Import --- .gitignore | 104 ++++++++++++++++++++++++++++++ README.md | 30 +++++++++ config.yml | 44 +++++++++++++ openldap/.gitignore | 1 + openldap/Dockerfile | 20 ++++++ openldap/Makefile | 38 +++++++++++ openldap/README.md | 62 ++++++++++++++++++ openldap/init.ldif | 22 +++++++ openldap/slapd.conf | 127 +++++++++++++++++++++++++++++++++++++ setup.py | 20 ++++++ src/phi.py | 22 +++++++ src/phi/__init__.py | 0 src/phi/api/__init__.py | 0 src/phi/api/app.py | 14 ++++ src/phi/api/rest.py | 5 ++ src/phi/api/routes.py | 8 +++ src/phi/app.py | 25 ++++++++ src/phi/config.py | 35 ++++++++++ src/phi/ldap/__init__.py | 0 src/phi/ldap/client.py | 29 +++++++++ src/phi/ldap/commands.py | 10 +++ src/phi/ldap/connection.py | 53 ++++++++++++++++ src/phi/logging.py | 15 +++++ test/test_dummy.py | 2 + 24 files changed, 686 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config.yml create mode 100644 openldap/.gitignore create mode 100644 openldap/Dockerfile create mode 100644 openldap/Makefile create mode 100644 openldap/README.md create mode 100644 openldap/init.ldif create mode 100644 openldap/slapd.conf create mode 100644 setup.py create mode 100644 src/phi.py create mode 100644 src/phi/__init__.py create mode 100644 src/phi/api/__init__.py create mode 100644 src/phi/api/app.py create mode 100644 src/phi/api/rest.py create mode 100644 src/phi/api/routes.py create mode 100644 src/phi/app.py create mode 100644 src/phi/config.py create mode 100644 src/phi/ldap/__init__.py create mode 100644 src/phi/ldap/client.py create mode 100644 src/phi/ldap/commands.py create mode 100644 src/phi/ldap/connection.py create mode 100644 src/phi/logging.py create mode 100644 test/test_dummy.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af2f537 --- /dev/null +++ b/.gitignore @@ -0,0 +1,104 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +.static_storage/ +.media/ +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..cb4a689 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# Phi + +Post-Human Interface. + +APIs for the Unit hacklab. + +## Installation + +Requirements: + +* Python >= 3.4 + +Create a new virtualenv and run `pip install .` inside of it. + +## LDAP client example + +``` +>>> from phi.config import get_config +>>> config_file, config = get_config() +>>> ldap_config = config['ldap'] +>>> +>>> from phi.ldap.client import Client +>>> ldap_client = Client(**ldap_config) +>>> +>>> from phi.ldap.connection import open_connection +>>> open_connection(ldap_client.connection) +>>> +>>> from phi.ldap.commands import whoami +>>> whoami(client) +``` diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..1ad13e6 --- /dev/null +++ b/config.yml @@ -0,0 +1,44 @@ +--- +core: + listen: + host: 127.0.0.1 + port: 8080 + + +ldap: + host: 127.0.0.1 + port: 389 + + encryption: TLSv1.2 # Can either be None or TLSv1.2. Default: None + ciphers: "HIGH" + validate: False # Can either be True or False. Default: False + + username: cn=root,dc=unit,dc=macaomilano,dc=org + password: root + + base_dn: dc=unit,dc=macaomilano,dc=org + + +logging: + version: 1 + formatters: + default: + format: '[%(name)s %(levelname)s] %(message)s' + + handlers: + console: + class: logging.StreamHandler + formatter: default + stream: ext://sys.stdout + file: + class: logging.FileHandler + formatter: default + filename: phi.log + + loggers: + phi: + level: DEBUG + handlers: [console, file] + aiohttp: + level: DEBUG + handlers: [console, file] diff --git a/openldap/.gitignore b/openldap/.gitignore new file mode 100644 index 0000000..cfaad76 --- /dev/null +++ b/openldap/.gitignore @@ -0,0 +1 @@ +*.pem diff --git a/openldap/Dockerfile b/openldap/Dockerfile new file mode 100644 index 0000000..c2ba8a1 --- /dev/null +++ b/openldap/Dockerfile @@ -0,0 +1,20 @@ +FROM alpine:3.7 + +RUN apk add --no-cache \ + openldap \ + openldap-back-mdb \ + openldap-overlay-refint openldap-overlay-memberof \ + openldap-clients + +COPY slapd.conf /etc/openldap/slapd.conf +COPY key.pem /var/slapd/key.pem +COPY cert.pem /var/slapd/cert.pem + +RUN chown -R ldap:ldap /var/slapd /etc/openldap + +EXPOSE 389 +ENTRYPOINT ["/usr/sbin/slapd",\ + "-u","ldap","-g","ldap",\ + "-f","/etc/openldap/slapd.conf",\ + "-d","1",\ + "-h","ldap://"] diff --git a/openldap/Makefile b/openldap/Makefile new file mode 100644 index 0000000..4c0add4 --- /dev/null +++ b/openldap/Makefile @@ -0,0 +1,38 @@ +.PHONY: all +all: build run + +.PHONY: build +build: gen-cert + docker build --no-cache -t unit/slapd . + +.PHONY: gen-cert +gen-cert: + openssl req \ + -x509 -nodes -days 365 -sha256 \ + -subj '/C=IT/ST=Lombardia/L=Milano/CN=unit.macaomilano.org' \ + -newkey rsa:2048 -keyout key.pem -out cert.pem + +.PHONY: clean +clean: + rm -f key.pem cert.pem + +.PHONY: run +run: + docker run -p 389:389 --rm unit/slapd + +.PHONY: shell +shell: + LDAPTLS_REQCERT=never zsh + +.PHONY: populate +populate: + ldapmodify -ZZ -H ldap://127.0.0.1 \ + -x -D "cn=root,dc=unit,dc=macaomilano,dc=org" -w root \ + -a -f init.ldif + +.PHONY: inspect +inspect: + ldapsearch -ZZ -h 127.0.0.1 \ + -x -D "cn=root,dc=unit,dc=macaomilano,dc=org" -w root \ + -b "dc=unit,dc=macaomilano,dc=org" \ + '(objectclass=*)' diff --git a/openldap/README.md b/openldap/README.md new file mode 100644 index 0000000..c528fb4 --- /dev/null +++ b/openldap/README.md @@ -0,0 +1,62 @@ +# OpenLDAP container + +Beware that this is intended for development purposes only and should not +be used in production. + +Make sure the latest Docker version in installed and the Docker daemon +is running. + +## Building the container image + +Before being able to use this container you must build it. Just run `make build` +from within the `openldap` directory in the root of this project. + +The created Docker image should be now present in your library. + +``` +% docker images 'unit/slapd' +REPOSITORY TAG IMAGE ID CREATED SIZE +unit/slapd latest c04d952b53d3 2 minutes ago 8.92MB +``` + +This will also create in the `openldap` directory in the root of this repository +two files containing the private key and the server certificate for the just +built docker image. + +``` +% ls -l *.pem +-rw-r--r-- 1 crudo users 1265 16 dic 22.25 cert.pem +-rw------- 1 crudo users 1704 16 dic 22.25 key.pem +``` + +## Running the container image + +Just run `make run`. This will start an OpenLDAP daemon bound to `127.0.0.1` +on port `389`. + +The root user DN is `cn=root,dn=unit,dc=macaomilano,dc=org` and its password +is `root`. + +Sending `SIGINT` (or pressing `Ctrl+C`) will stop the daemon and remove the +running docker instance.. + +## Issuing client commands + +In order to operate LDAP commands you need the `ldapsearch` and `ldapmodify` +binaries. + +Such commands require some environment variables to be set. The `make shell` +command will start a new shell with those variables already set. Currently +only Zsh is supported. + +## Populate the server + +The `init.ldif` file that can be found in the `openldap` directory in the root +of this repository contains a basic structure that can be imported to the +OpenLDAP directory server. Just run `make populate` from withing the same directory. + +## Inspect the server + +Running the command `make inspect` from within the `openldap` directory in +the root of this repository will show all the stored information in the +OpenLDAP directory server. diff --git a/openldap/init.ldif b/openldap/init.ldif new file mode 100644 index 0000000..0d741a0 --- /dev/null +++ b/openldap/init.ldif @@ -0,0 +1,22 @@ +version: 1 + +dn: dc=unit,dc=macaomilano,dc=org +objectClass: organization +objectClass: dcObject +dc: unit +o: Unit + +dn: ou=Hackers,dc=unit,dc=macaomilano,dc=org +objectClass: organizationalUnit +objectClass: top +ou: Hackers + +dn: ou=Services,dc=unit,dc=macaomilano,dc=org +objectClass: top +objectClass: organizationalUnit +ou: Services + +dn: ou=Groups,dc=unit,dc=macaomilano,dc=org +objectClass: top +objectClass: organizationalUnit +ou: Groups diff --git a/openldap/slapd.conf b/openldap/slapd.conf new file mode 100644 index 0000000..9046918 --- /dev/null +++ b/openldap/slapd.conf @@ -0,0 +1,127 @@ +####################################################################### +# Modules +####################################################################### + +include /etc/openldap/schema/core.schema +include /etc/openldap/schema/cosine.schema +include /etc/openldap/schema/corba.schema +include /etc/openldap/schema/inetorgperson.schema +include /etc/openldap/schema/nis.schema +include /etc/openldap/schema/collective.schema +include /etc/openldap/schema/openldap.schema + +modulepath /usr/lib/openldap +moduleload back_mdb +moduleload refint +moduleload memberof + + +####################################################################### +# Core +####################################################################### + +pidfile /var/slapd/slapd.pid +argsfile /var/slapd/slapd.args +loglevel conns + +serverID 0 + + +####################################################################### +# Security +####################################################################### + +#TLSCACertificateFile /var/slapd/fullchain.pem +TLSCertificateFile /var/slapd/cert.pem +TLSCertificateKeyFile /var/slapd/key.pem +TLSCipherSuite HIGH + +# Sample security restrictions +# Define global ACLs to disable default read access. +# Require integrity protection (prevent hijacking) +# Require 112-bit (3DES or better) encryption for updates +# Require 63-bit encryption for simple bind +security ssf=1 simple_bind=256 update_ssf=256 + + +####################################################################### +# MDB database definitions +####################################################################### + +database mdb +maxsize 1073741824 +suffix "dc=unit,dc=macaomilano,dc=org" + +# Overlays to be loaded for the database. +overlay memberof + +# Cleartext passwords, especially for the rootdn, should +# be avoid. See slappasswd(8) and slapd.conf(5) for details. +# Use of strong authentication encouraged. +rootdn "cn=root,dc=unit,dc=macaomilano,dc=org" +rootpw {SHA}3Hbp8MAAbo+RngxRXGbbujmC94U= + +# The database directory MUST exist prior to running slapd AND +# should only be accessible by the slapd and slap tools. +# Mode 700 recommended. +directory /var/slapd +mode 0700 + +password-hash {CRYPT} +password-crypt-salt-format "$6$%.16s" + +# Indices to maintain +index objectClass pres,eq +index uid,cn,sn,mail eq,sub +index memberof pres,eq + + +####################################################################### +# MemberOf configuration +####################################################################### + +memberof-group-oc groupOfNames +memberof-memberof-ad memberOf +memberof-member-ad member +memberof-dangling error +memberof-refint true + + +####################################################################### +# ACLs +####################################################################### + +# Sample access control policy: +# Root DSE: allow anyone to read it +# Subschema (sub)entry DSE: allow anyone to read it +# Other DSEs: +# Allow self write access +# Allow authenticated users read access +# Allow anonymous users to authenticate +# Directives needed to implement policy: +#access to dn.base="" by * read +#access to dn.base="cn=Subschema" by * read +#access to * +# by self write +# by users read +# by anonymous auth +# +# if no access controls are present, the default policy +# allows anyone and everyone to read anything but restricts +# updates to rootdn. (e.g., "access to * by * read") +# +# rootdn can always read and write EVERYTHING! + +access to dn.base="" + by * read + +access to attrs=entry + by * read + +access to attrs=userPassword + by self write + by anonymous auth + +access to dn.subtree="ou=Hackers,dc=unit,dc=macaomilano,dc=org" + by self write + by dn.subtree="ou=Services,dc=unit,dc=macaomilano,dc=org" read diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1fe070e --- /dev/null +++ b/setup.py @@ -0,0 +1,20 @@ +from setuptools import setup + +setup( + name='phi', + version='0.0.1', + + description='Post-Human Interface', + # license='', + url='https://git.unit.macaomilano.org/unit/panel', + + author='unit', + author_email='unit@paranoici.org', + + package_dir={'': 'src'}, + packages=['phi', 'phi.api', 'phi.ldap'], + scripts=['src/phi.py'], + + install_requires=['aiohttp', 'pyYAML', 'ldap3'], + tests_require=['pytest', 'pytest-aiohttp'] +) diff --git a/src/phi.py b/src/phi.py new file mode 100644 index 0000000..52164e1 --- /dev/null +++ b/src/phi.py @@ -0,0 +1,22 @@ +from pprint import pformat as pp + +from phi.config import get_config +from phi.logging import setup_logging, get_logger +from phi.app import setup_app, run_app + + +log = get_logger(__name__) + + +if __name__ == '__main__': + config_file, config = get_config() + + # Beware that everything happened until now + # could not possibly get logged. + setup_logging(config.get('logging', {})) + + log.info("Found configuration at '{}':\n{}" + .format(config_file, pp(config))) + + app = setup_app(config) + run_app(app) diff --git a/src/phi/__init__.py b/src/phi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/phi/api/__init__.py b/src/phi/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/phi/api/app.py b/src/phi/api/app.py new file mode 100644 index 0000000..111ac95 --- /dev/null +++ b/src/phi/api/app.py @@ -0,0 +1,14 @@ +from aiohttp import web + +from phi.logging import get_logger +from phi.api.routes import api_routes + +log = get_logger(__name__) + + +def api_app(ldap_client=None): + log.info("Initializing API sub-app.") + app = web.Application() + app.router.add_routes(api_routes) + app['ldap_client'] = ldap_client + return app diff --git a/src/phi/api/rest.py b/src/phi/api/rest.py new file mode 100644 index 0000000..5ebd19d --- /dev/null +++ b/src/phi/api/rest.py @@ -0,0 +1,5 @@ +from aiohttp.web import Response + + +def status(request): + Response(text="Status: it's working!") diff --git a/src/phi/api/routes.py b/src/phi/api/routes.py new file mode 100644 index 0000000..191ce7b --- /dev/null +++ b/src/phi/api/routes.py @@ -0,0 +1,8 @@ +from aiohttp.web import get + +from phi.api.rest import status + + +api_routes = [ + get('/status', status) +] diff --git a/src/phi/app.py b/src/phi/app.py new file mode 100644 index 0000000..4a9369b --- /dev/null +++ b/src/phi/app.py @@ -0,0 +1,25 @@ +from asyncio import get_event_loop +from aiohttp import web + +from phi.api.app import api_app +from phi.ldap.client import Client + + +def setup_app(config): + loop = get_event_loop() + + app = web.Application(loop=loop) + app['config'] = config + + ldap_client = Client(**config.get('ldap', {})) + + api = api_app(ldap_client=ldap_client) + app.add_subapp('/api', api) + + return app + + +def run_app(app): + web.run_app(app, + host=app['config']['core']['listen'].get('host', '127.0.0.1'), + port=app['config']['core']['listen'].get('port', '8080')) diff --git a/src/phi/config.py b/src/phi/config.py new file mode 100644 index 0000000..259863d --- /dev/null +++ b/src/phi/config.py @@ -0,0 +1,35 @@ +import os.path +import yaml + + +NAME = 'phi' + +CONFIG_FILE = 'config.yml' +CONFIG_PATHS = ['./', + '~/.config/' + NAME + '/', + '/usr/local/etc/' + NAME + '/', + '/etc/' + NAME + '/'] +CONFIG_FILES = [os.path.join(p, CONFIG_FILE) + for p in CONFIG_PATHS] + + +def get_config(): + """Return the path of the found configuration file and its content + + :returns: (path, config) + :rtype: (str, dict) + """ + for f in CONFIG_FILES: + try: + with open(f, 'r') as c: + config = yaml.safe_load(c) + return (f, config) + except FileNotFoundError: + # Skip to the next file. + # We only care if the file is preset but it's not + # accessible or if the file is not present at all + # in any of CONFIG_PATHS. + pass + else: + raise FileNotFoundError("Could not find {} in any of {}." + .format(CONFIG_FILE, ', '.join(CONFIG_PATHS))) diff --git a/src/phi/ldap/__init__.py b/src/phi/ldap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/phi/ldap/client.py b/src/phi/ldap/client.py new file mode 100644 index 0000000..77acae5 --- /dev/null +++ b/src/phi/ldap/client.py @@ -0,0 +1,29 @@ +from ldap3.utils.log import set_library_log_detail_level, PROTOCOL + +from phi.logging import get_logger +from phi.ldap.connection import make_connection + +log = get_logger(__name__) +set_library_log_detail_level(PROTOCOL) + + +class Client: + def __init__(self, host=None, port=389, + encryption=None, ciphers=None, validate=False, + username=None, password=None, + base_dn=None): + self.host = host + self.port = port + self.encryption = encryption + self.ciphers = ciphers + self.validate = validate + self.username = username + self.password = password + self.base_dn = base_dn + + self.connection = make_connection(host=self.host, port=self.port, + encryption=self.encryption, + ciphers=self.ciphers, + validate=self.validate, + username=self.username, + password=self.password) diff --git a/src/phi/ldap/commands.py b/src/phi/ldap/commands.py new file mode 100644 index 0000000..7a9ae72 --- /dev/null +++ b/src/phi/ldap/commands.py @@ -0,0 +1,10 @@ +def inspect(client): + id = client.connection.search(client.base_dn, + '(objectclass=*)') + response, result = client.connection.get_response(id) + return response + + +def whoami(client): + response = client.connection.extend.standard.who_am_i() + return response diff --git a/src/phi/ldap/connection.py b/src/phi/ldap/connection.py new file mode 100644 index 0000000..238a121 --- /dev/null +++ b/src/phi/ldap/connection.py @@ -0,0 +1,53 @@ +from ssl import CERT_REQUIRED, PROTOCOL_TLSv1_2 +from ldap3 import Tls, Server, Connection, ASYNC + +from phi.logging import get_logger + +log = get_logger(__name__) + + +def make_connection(host=None, port=389, + encryption=None, ciphers=None, validate=False, + username=None, password=None): + # TLSv1.2 is supported since Python 3.4 + if encryption is None: + log.info("The connection to the LDAP server will not be encrypted.") + tls = None + elif encryption == "TLSv1.2": + log.info("The connection to the LDAP server will use TLSv1.2.") + tls = Tls(version=PROTOCOL_TLSv1_2) + else: + raise NotImplementedError("Sorry, use TLSv1.2.") + + if encryption is not None and ciphers is not None: + log.info("The connection to the LDAP server will use the " + "following ciphers: {}".format(ciphers)) + tls.ciphers = ciphers + + if encryption is not None and validate is True: + log.info("The certificate hostname will be checked to match the " + "remote hostname.") + tls.validate = CERT_REQUIRED + + server = Server(host=host, port=port, tls=tls) + connection = Connection(server, user=username, password=password, + client_strategy=ASYNC) + + return connection + + +def open_connection(connection): + log.info("Opening connection to LDAP server.") + connection.open() + + if connection.server.tls is not None and connection.server.ssl is False: + log.info("Issuing StartTLS command.") + connection.start_tls() + + log.info("Issuing BIND command.") + connection.bind() + + +def close_connection(connection): + log.info("Issuing UNBIND command.") + connection.unbind() diff --git a/src/phi/logging.py b/src/phi/logging.py new file mode 100644 index 0000000..313fc55 --- /dev/null +++ b/src/phi/logging.py @@ -0,0 +1,15 @@ +from logging import getLogger +from logging.config import dictConfig + +from phi.config import NAME + + +root = getLogger(NAME) + + +def setup_logging(config): + dictConfig(config) + + +def get_logger(name): + return root.getChild(name) diff --git a/test/test_dummy.py b/test/test_dummy.py new file mode 100644 index 0000000..f4f5361 --- /dev/null +++ b/test/test_dummy.py @@ -0,0 +1,2 @@ +def test_dummy(): + assert True