make-things-work-brutally
crudo 2017-12-16 23:03:03 +01:00
commit 4bd5d74975
24 changed files with 686 additions and 0 deletions

104
.gitignore vendored 100644
View File

@ -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/

30
README.md 100644
View File

@ -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)
```

44
config.yml 100644
View File

@ -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]

1
openldap/.gitignore vendored 100644
View File

@ -0,0 +1 @@
*.pem

View File

@ -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://"]

38
openldap/Makefile 100644
View File

@ -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=*)'

62
openldap/README.md 100644
View File

@ -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.

22
openldap/init.ldif 100644
View File

@ -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

127
openldap/slapd.conf 100644
View File

@ -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

20
setup.py 100644
View File

@ -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']
)

22
src/phi.py 100644
View File

@ -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)

View File

View File

14
src/phi/api/app.py 100644
View File

@ -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

View File

@ -0,0 +1,5 @@
from aiohttp.web import Response
def status(request):
Response(text="Status: it's working!")

View File

@ -0,0 +1,8 @@
from aiohttp.web import get
from phi.api.rest import status
api_routes = [
get('/status', status)
]

25
src/phi/app.py 100644
View File

@ -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'))

35
src/phi/config.py 100644
View File

@ -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)))

View File

View File

@ -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)

View File

@ -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

View File

@ -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()

15
src/phi/logging.py 100644
View File

@ -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)

View File

@ -0,0 +1,2 @@
def test_dummy():
assert True