Merge branch 'docker' into 'master'

make demo entirely runnable with docker-compose

See merge request yaal/canaille!45
This commit is contained in:
Éloi Rivard 2022-04-04 13:29:11 +00:00
commit 791aadb310
12 changed files with 396 additions and 17 deletions

View file

@ -19,13 +19,13 @@ Development environment
.. code-block:: console .. code-block:: console
cd demo cd demo
./run.sh ./run.sh # or `docker-compose up` to run it with docker
Then you have access to: Then you have access to:
- A canaille server at http://127.0.0.1:5000 - A canaille server at http://localhost:5000
- A dummy client at http://127.0.0.1:5001 - A dummy client at http://localhost:5001
- Another dummy client at http://127.0.0.1:5002 - Another dummy client at http://localhost:5002
The canaille server has some default users: The canaille server has some default users:

View file

@ -25,7 +25,7 @@ It aims to be very light, simple to install and simple to maintain. Its main fea
```bash ```bash
cd demo cd demo
./run.sh ./run.sh # or `docker-compose up` to run it with docker
``` ```
# Documentation # Documentation

16
demo/Dockerfile-canaille Normal file
View file

@ -0,0 +1,16 @@
FROM python:slim
RUN \
apt update && \
apt -y upgrade && \
apt install -y \
gcc \
libsasl2-dev \
libldap2-dev \
libssl-dev
COPY setup.cfg setup.py /opt/canaille/
RUN pip install --editable /opt/canaille
WORKDIR /opt/canaille
ENTRYPOINT ["flask", "run", "--host=0.0.0.0"]

13
demo/Dockerfile-client Normal file
View file

@ -0,0 +1,13 @@
FROM python:slim
RUN \
apt update && \
apt -y upgrade
RUN pip install \
flask \
"authlib<1.0.0" \
requests
WORKDIR /opt/client
ENTRYPOINT ["flask", "run", "--host=0.0.0.0"]

View file

@ -1,8 +1,10 @@
# Demo and development # Demo and development
To check out how canaille looks like, or to start contributions, just run it with `./run.sh`! To check out how canaille looks like, or to start contributions, just run the demo:
- with `docker-compose up` to install and run it in preconfigured docker containers
- or with `./run.sh` to install it natively in a virtual environment and run it locally!
# Prerequisites # Prerequisites for native demo installation
You need to have `OpenLDAP` somewhere in your system. You need to have `OpenLDAP` somewhere in your system.
@ -10,7 +12,7 @@ You can either:
- install it with your distro packages *(for instance `sudo apt install slapd ldap-utils` with Ubuntu)*. - install it with your distro packages *(for instance `sudo apt install slapd ldap-utils` with Ubuntu)*.
it is not required to launch the system ldap service. it is not required to launch the system ldap service.
- have `docker` plus `docker-compose` installed on your system, the `./run.sh` script will download and - have `docker` plus `docker-compose` installed on your system, the `./run.sh` script will download and
run a OpenLDAP image. run an OpenLDAP image.
canaille depends on [python-ldap](https://github.com/python-ldap/python-ldap), and this package needs canaille depends on [python-ldap](https://github.com/python-ldap/python-ldap), and this package needs
some headers to be installed on your system to be built. For instance on Ubuntu you can install this: some headers to be installed on your system to be built. For instance on Ubuntu you can install this:
@ -32,15 +34,15 @@ sudo aa-complain /usr/sbin/slapd
Then you have access to: Then you have access to:
- A canaille server at http://127.0.0.1:5000 - A canaille server at http://localhost:5000
- A dummy client at http://127.0.0.1:5001 - A dummy client at http://localhost:5001
- Another dummy client at http://127.0.0.1:5002 - Another dummy client at http://localhost:5002
The canaille server has some default users: The canaille server has some default users:
- A regular user which login and password are **user**; - A regular user which login and password are **user**;
- A moderator user which login and password are **moderator**; - A moderator user which login and password are **moderator**;
- An admin user which admin and password are **admin**. - An admin user which login and password are **admin**.
- A new user which admin and password are **new**. This user has no password yet, - A new user which login is **james**. This user has no password yet,
and his first attempt to log-in will result in sending a password initialization and his first attempt to log-in would result in sending a password initialization
email. email (if a smtp server is configurated).

View file

@ -0,0 +1,180 @@
# All the Flask configuration values can be used:
# https://flask.palletsprojects.com/en/1.1.x/config/#builtin-configuration-values
# The flask secret key for cookies. You MUST change this.
SECRET_KEY = "change me before you go in production"
# Your organization name.
NAME = "Canaille"
# The interface on which canaille will be served
# SERVER_NAME = "auth.mydomain.tld"
# PREFERRED_URL_SCHEME = "https"
# You can display a logo to be recognized on login screens
LOGO = "/static/img/canaille-head.png"
# Your favicon. If unset the LOGO will be used.
FAVICON = "/static/img/canaille-c.png"
# The name of a theme in the 'theme' directory, or an absolute path
# to a theme. Defaults to 'default'. Theming is done with
# https://github.com/tktech/flask-themer
# THEME = "default"
# If unset, language is detected
# LANGUAGE = "en"
# Path to the RFC8414 metadata file. You should update those files
# with your production URLs.
OAUTH2_METADATA_FILE = "conf/oauth-authorization-server.json"
OIDC_METADATA_FILE = "conf/openid-configuration.json"
# If you have a sentry instance, you can set its dsn here:
# SENTRY_DSN = "https://examplePublicKey@o0.ingest.sentry.io/0"
# If HIDE_INVALID_LOGINS is set to true, when a user tries to sign in with
# an invalid login, a message is shown saying that the login does not
# exist. If HIDE_INVALID_LOGINS is set to false (the default) a message is
# shown saying that the password is wrong, but does not give a clue
# wether the login exists or not.
# HIDE_INVALID_LOGINS = false
# The validity duration of registration invitations, in seconds.
# Defaults to 2 days
# INVITATION_EXPIRATION = 172800
[LOGGING]
# LEVEL can be one value among:
# DEBUG, INFO, WARNING, ERROR, CRITICAL
# Defaults to WARNING
# LEVEL = "WARNING"
LEVEL = "DEBUG"
# The path of the log file. If not set (the default) logs are
# written in the standard error output.
# PATH = ""
[LDAP]
URI = "ldap://ldap:389"
ROOT_DN = "dc=mydomain,dc=tld"
BIND_DN = "cn=admin,dc=mydomain,dc=tld"
BIND_PW = "admin"
TIMEOUT = 10
# Where to search for users?
USER_BASE = "ou=users,dc=mydomain,dc=tld"
# The object class to use for creating new users
# USER_CLASS = "inetOrgPerson"
# The attribute to identify an object in the User dn.
USER_ID_ATTRIBUTE = "uid"
# Filter to match users on sign in. Supports a variable
# {login} that can be used to compare against several fields:
# USER_FILTER = "(|(uid={login})(mail={login}))"
# Where to search for groups?
GROUP_BASE = "ou=groups,dc=mydomain,dc=tld"
# The object class to use for creating new groups
# GROUP_CLASS = "groupOfNames"
# The attribute to identify an object in the User dn.
# GROUP_ID_ATTRIBUTE = "cn"
# The attribute to use to identify a group
# GROUP_NAME_ATTRIBUTE = "cn"
# A filter to check if a user belongs to a group
# A 'user' variable is available.
# GROUP_USER_FILTER = "member={user.dn}"
# You can define access controls that define what users can do on canaille
# An access control consists in a FILTER to match users, a list of PERMISSIONS
# matched users will be able to perform, and fields users will be able
# to READ and WRITE. Users matching several filters will cumulate permissions.
#
# A 'FILTER' parameter that is a LDAP filter used to determine if a user
# belongs to an access control. If absent, all the users will match this
# access control. If your LDAP server has the 'memberof' overlay, you can
# filter against group membership.
# Here are some examples
# FILTER = 'uid=admin'
# FILTER = 'memberof=cn=admins,ou=groups,dc=mydomain,dc=tld'
#
# The 'PERMISSIONS' parameter that is an list of items the users in the access
# control will be able to manage. 'PERMISSIONS' is optionnal. Values can be:
# - "use_oidc" to allow OpenID Connect authentication
# - "manage_oidc" to allow OpenID Connect client managements
# - "manage_users" to allow other users management
# - "manage_groups" to allow group edition and creation
# - "delete_account" allows a user to delete his own account. If used with
# manage_users, the user can delete any account
# - "impersonate_users" to allow a user to take the identity of another user
#
# The 'READ' and 'WRITE' attributes are the LDAP attributes of the user
# object that users will be able to read and/or write.
[ACL.DEFAULT]
PERMISSIONS = ["use_oidc"]
READ = ["uid", "groups"]
WRITE = ["jpegPhoto", "givenName", "sn", "userPassword", "telephoneNumber", "mail", "labeledURI"]
[ACL.ADMIN]
FILTER = "memberof=cn=admins,ou=groups,dc=mydomain,dc=tld"
PERMISSIONS = [
"manage_users",
"manage_groups",
"manage_oidc",
"delete_account",
"impersonate_users",
]
WRITE = ["groups"]
[ACL.HALF_ADMIN]
FILTER = "memberof=cn=moderators,ou=groups,dc=mydomain,dc=tld"
PERMISSIONS = ["manage_users", "manage_groups", "delete_account"]
WRITE = ["groups"]
# The jwt configuration. You can generate a RSA keypair with:
# openssl genrsa -out private.pem 4096
# openssl rsa -in private.pem -pubout -outform PEM -out public.pem
[JWT]
# The path to the private key.
PRIVATE_KEY = "conf/private.pem"
# The path to the public key.
PUBLIC_KEY = "conf/public.pem"
# The key type parameter
# KTY = "RSA"
# The key algorithm
# ALG = "RS256"
# The time the JWT will be valid, in seconds
# EXP = 3600
[JWT.MAPPING]
# Mapping between JWT fields and LDAP attributes from your
# User objectClass.
# {attribute} will be replaced by the user ldap attribute value.
# Default values fits inetOrgPerson.
SUB = "{{ user.uid[0] }}"
NAME = "{{ user.cn[0] }}"
PHONE_NUMBER = "{{ user.telephoneNumber[0] }}"
EMAIL = "{{ user.mail[0] }}"
GIVEN_NAME = "{{ user.givenName[0] }}"
FAMILY_NAME = "{{ user.sn[0] }}"
PREFERRED_USERNAME = "{{ user.displayName[0] }}"
LOCALE = "{{ user.preferredLanguage[0] }}"
ADDRESS = "{{ user.postalAddress[0] }}"
PICTURE = "{% if user.jpegPhoto %}{{ url_for('account.photo', uid=user.uid[0], field='jpegPhoto', _external=True) }}{% endif %}"
WEBSITE = "{{ user.labeledURI[0] }}"
# The SMTP server options. If not set, mail related features such as
# user invitations, and password reset emails, will be disabled.
[SMTP]
# HOST = "localhost"
# PORT = 25
# TLS = false
# LOGIN = ""
# PASSWORD = ""
FROM_ADDR = "admin@mydomain.tld"

View file

@ -0,0 +1,7 @@
SECRET_KEY="46bf9fb5-88d5-489b-9312-899588377ff0"
NAME = "Client 1"
SESSION_COOKIE_NAME="client1-session"
OAUTH_CLIENT_ID="1JGkkzCbeHpGtlqgI5EENByf"
OAUTH_CLIENT_SECRET="2xYPSReTQRmGG1yppMVZQ0ASXwFejPyirvuPbKhNa6TmKC5x"
OAUTH_AUTH_SERVER="http://canaille:5000"

View file

@ -0,0 +1,7 @@
SECRET_KEY="8e953ecc-13be-497b-806f-c65faa1e328f"
NAME = "Client 2"
SESSION_COOKIE_NAME="client2-session"
OAUTH_CLIENT_ID="gn4yFN7GDykL7QP8v8gS9YfV"
OAUTH_CLIENT_SECRET="ouFJE5WpICt6hxTyf8icXPeeklMektMY4gV0Rmf3aY60VElA"
OAUTH_AUTH_SERVER="http://canaille:5000"

View file

@ -0,0 +1,31 @@
{
"issuer":
"http://localhost:5000",
"authorization_endpoint":
"http://localhost:5000/oauth/authorize",
"token_endpoint":
"http://localhost:5000/oauth/token",
"token_endpoint_auth_methods_supported":
["client_secret_basic", "private_key_jwt",
"client_secret_post", "none"],
"token_endpoint_auth_signing_alg_values_supported":
["RS256", "ES256"],
"userinfo_endpoint":
"http://localhost:5000/oauth/userinfo",
"jwks_uri":
"http://localhost:5000/oauth/jwks.json",
"registration_endpoint":
"http://localhost:5000/oauth/register",
"introspection_endpoint":
"https://mydomain.tld/oauth/introspect",
"scopes_supported":
["openid", "profile", "email", "address",
"phone", "groups"],
"response_types_supported":
["code", "token", "id_token", "code token",
"code id_token", "token id_token"],
"service_documentation":
"http://localhost:5000/documentation.html",
"ui_locales_supported":
["en-US", "en-GB", "en-CA", "fr-FR", "fr-CA"]
}

View file

@ -0,0 +1,66 @@
{
"issuer":
"http://localhost:5000",
"authorization_endpoint":
"http://localhost:5000/oauth/authorize",
"token_endpoint":
"http://canaille:5000/oauth/token",
"token_endpoint_auth_methods_supported":
["client_secret_basic", "private_key_jwt",
"client_secret_post", "none"],
"token_endpoint_auth_signing_alg_values_supported":
["RS256"],
"userinfo_endpoint":
"http://canaille:5000/oauth/userinfo",
"check_session_iframe":
"http://canaille:5000/oauth/check_session",
"end_session_endpoint":
"http://canaille:5000/oauth/end_session",
"jwks_uri":
"http://canaille:5000/oauth/jwks.json",
"registration_endpoint":
"http://canaille:5000/oauth/register",
"introspection_endpoint":
"https://canaille:5000/oauth/introspect",
"scopes_supported":
["openid", "profile", "email", "address",
"phone", "groups"],
"response_types_supported":
["code", "token", "id_token", "code token",
"code id_token", "token id_token"],
"acr_values_supported":
["urn:mace:incommon:iap:silver",
"urn:mace:incommon:iap:bronze"],
"subject_types_supported":
["public", "pairwise"],
"userinfo_signing_alg_values_supported":
["RS256", "ES256", "HS256"],
"userinfo_encryption_alg_values_supported":
["RSA1_5", "A128KW"],
"userinfo_encryption_enc_values_supported":
["A128CBC-HS256", "A128GCM"],
"id_token_signing_alg_values_supported":
["RS256", "ES256", "HS256"],
"id_token_encryption_alg_values_supported":
["RSA1_5", "A128KW"],
"id_token_encryption_enc_values_supported":
["A128CBC-HS256", "A128GCM"],
"request_object_signing_alg_values_supported":
["none", "RS256", "ES256"],
"display_values_supported":
["page", "popup"],
"claim_types_supported":
["normal", "distributed"],
"claims_supported":
["sub", "iss", "auth_time", "acr",
"name", "given_name", "family_name", "nickname",
"profile", "picture", "website",
"email", "email_verified", "locale", "zoneinfo",
"groups"],
"claims_parameter_supported":
true,
"service_documentation":
"http://localhost:5000/oauth/service_documentation.html",
"ui_locales_supported":
["en-US", "en-GB", "en-CA", "fr-FR", "fr-CA"]
}

View file

@ -7,9 +7,66 @@ services:
environment: environment:
- LDAP_DOMAIN=mydomain.tld - LDAP_DOMAIN=mydomain.tld
volumes: volumes:
- ./ldif/bootstrap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/50-boostrap.ldif:ro - ./ldif/memberof.ldif:/container/service/slapd/assets/config/bootstrap/ldif/03-memberOf.ldif:ro
# memberof overlay is already present in openldap docker image but only for groupOfUniqueNames. We need to overwrite it (until canaille can handle groupOfUniqueNames).
# https://github.com/osixia/docker-openldap/blob/master/image/service/slapd/assets/config/bootstrap/ldif/03-memberOf.ldif
- ../canaille/ldap_backend/schemas/oauth2-openldap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/40-oauth2.ldif:ro - ../canaille/ldap_backend/schemas/oauth2-openldap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/40-oauth2.ldif:ro
- ./ldif/bootstrap-tree.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/50-boostrap-tree.ldif:ro
- ./ldif/bootstrap-data.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/60-boostrap-data.ldif:ro
command: --copy-service --loglevel debug command: --copy-service --loglevel debug
ports: ports:
- 5389:389 - 5389:389
- 5636:636 - 5636:636
canaille:
depends_on:
- ldap
build:
context: ..
dockerfile: demo/Dockerfile-canaille
environment:
- AUTHLIB_INSECURE_TRANSPORT=1
- FLASK_ENV=development
- CONFIG=/opt/canaille/conf/canaille.toml
- FLASK_APP=canaille
volumes:
- ../canaille:/opt/canaille/canaille
- ./conf-docker:/opt/canaille/conf
ports:
- 5000:5000
client1:
depends_on:
- canaille
build:
context: .
dockerfile: Dockerfile-client
environment:
- FLASK_ENV=development
- CONFIG=/opt/client/conf/client1.cfg
- FLASK_APP=client
volumes:
- ./client:/opt/client/client
- ./conf-docker:/opt/client/conf
- ../canaille/static:/opt/canaille/static
command: --port=5001
ports:
- 5001:5001
client2:
depends_on:
- canaille
build:
context: .
dockerfile: Dockerfile-client
environment:
- FLASK_ENV=development
- CONFIG=/opt/client/conf/client2.cfg
- FLASK_APP=client
volumes:
- ./client:/opt/client/client
- ./conf-docker:/opt/client/conf
- ../canaille/static:/opt/canaille/static
command: --port=5002
ports:
- 5002:5002

View file

@ -4,7 +4,7 @@ if [ "$SLAPD_BINARY" == "NATIVE" ] || ([ "$SLAPD_BINARY" == "" ] && type slapd >
env BIN=$BIN:/usr/bin:/usr/sbin env/bin/python ldap-server.py env BIN=$BIN:/usr/bin:/usr/sbin env/bin/python ldap-server.py
elif [ "$SLAPD_BINARY" == "DOCKER" ] || ([ "$SLAPD_BINARY" == "" ] && type docker-compose > /dev/null 2>&1); then elif [ "$SLAPD_BINARY" == "DOCKER" ] || ([ "$SLAPD_BINARY" == "" ] && type docker-compose > /dev/null 2>&1); then
docker-compose up docker-compose run --service-ports --rm ldap
else else
echo "Cannot start the LDAP server. Please install openldap or docker on your system." echo "Cannot start the LDAP server. Please install openldap or docker on your system."