From 0946d6a25d3e8e55d3ea0a91d3c77f8daafd8e67 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 9 Dec 2019 21:00:45 +0100 Subject: [PATCH] docs: add initial structure, add docs for policies and factors --- docs/Dockerfile | 16 ++ docs/css/custom.css | 342 ++++++++++++++++++++++++ docs/factors.md | 23 ++ docs/index.md | 31 +++ docs/installation/docker-compose.md | 26 ++ docs/installation/install.md | 6 + docs/installation/kubernetes.md | 3 + docs/k8s/deployment.yml | 33 +++ docs/k8s/ingress.yml | 21 ++ docs/k8s/service.yml | 17 ++ docs/policies.md | 64 +++++ mkdocs.yml | 16 ++ passbook/admin/forms/base.py | 2 +- passbook/policies/reputation/signals.py | 2 + 14 files changed, 601 insertions(+), 1 deletion(-) create mode 100644 docs/Dockerfile create mode 100644 docs/css/custom.css create mode 100644 docs/factors.md create mode 100755 docs/index.md create mode 100644 docs/installation/docker-compose.md create mode 100755 docs/installation/install.md create mode 100644 docs/installation/kubernetes.md create mode 100644 docs/k8s/deployment.yml create mode 100644 docs/k8s/ingress.yml create mode 100644 docs/k8s/service.yml create mode 100644 docs/policies.md create mode 100644 mkdocs.yml diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 000000000..1f2092fae --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.7-slim-buster as builder + +WORKDIR /mkdocs + +RUN apt-get update && apt-get install -y git && \ + pip install mkdocs && \ + pip install git+https://github.com/BeryJu/mkdocs-bootstrap4.git + +COPY docs/ docs +COPY mkdocs.yml . + +RUN mkdocs build + +FROM nginx + +COPY --from=builder /mkdocs/site /usr/share/nginx/html diff --git a/docs/css/custom.css b/docs/css/custom.css new file mode 100644 index 000000000..cc0c968c2 --- /dev/null +++ b/docs/css/custom.css @@ -0,0 +1,342 @@ +table { + width: 100%; + margin-bottom: 1rem; + color: #212529; +} + +table th, +table td { + padding: 0.75rem; + vertical-align: top; + border-top: 1px solid #dee2e6; +} + +table thead th { + vertical-align: bottom; + border-bottom: 2px solid #dee2e6; +} + +table tbody + tbody { + border-top: 2px solid #dee2e6; +} + +.table-sm th, +.table-sm td { + padding: 0.3rem; +} + +.table-bordered { + border: 1px solid #dee2e6; +} + +.table-bordered th, +.table-bordered td { + border: 1px solid #dee2e6; +} + +.table-bordered thead th, +.table-bordered thead td { + border-bottom-width: 2px; +} + +.table-borderless th, +.table-borderless td, +.table-borderless thead th, +.table-borderless tbody + tbody { + border: 0; +} + +table tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, 0.05); +} + +table tbody tr:hover { + color: #212529; + background-color: rgba(0, 0, 0, 0.075); +} + +.table-primary, +.table-primary > th, +.table-primary > td { + background-color: #b8daff; +} + +.table-primary th, +.table-primary td, +.table-primary thead th, +.table-primary tbody + tbody { + border-color: #7abaff; +} + +table .table-primary:hover { + background-color: #9fcdff; +} + +table .table-primary:hover > td, +table .table-primary:hover > th { + background-color: #9fcdff; +} + +.table-secondary, +.table-secondary > th, +.table-secondary > td { + background-color: #d6d8db; +} + +.table-secondary th, +.table-secondary td, +.table-secondary thead th, +.table-secondary tbody + tbody { + border-color: #b3b7bb; +} + +table .table-secondary:hover { + background-color: #c8cbcf; +} + +table .table-secondary:hover > td, +table .table-secondary:hover > th { + background-color: #c8cbcf; +} + +.table-success, +.table-success > th, +.table-success > td { + background-color: #c3e6cb; +} + +.table-success th, +.table-success td, +.table-success thead th, +.table-success tbody + tbody { + border-color: #8fd19e; +} + +table .table-success:hover { + background-color: #b1dfbb; +} + +table .table-success:hover > td, +table .table-success:hover > th { + background-color: #b1dfbb; +} + +.table-info, +.table-info > th, +.table-info > td { + background-color: #bee5eb; +} + +.table-info th, +.table-info td, +.table-info thead th, +.table-info tbody + tbody { + border-color: #86cfda; +} + +table .table-info:hover { + background-color: #abdde5; +} + +table .table-info:hover > td, +table .table-info:hover > th { + background-color: #abdde5; +} + +.table-warning, +.table-warning > th, +.table-warning > td { + background-color: #ffeeba; +} + +.table-warning th, +.table-warning td, +.table-warning thead th, +.table-warning tbody + tbody { + border-color: #ffdf7e; +} + +table .table-warning:hover { + background-color: #ffe8a1; +} + +table .table-warning:hover > td, +table .table-warning:hover > th { + background-color: #ffe8a1; +} + +.table-danger, +.table-danger > th, +.table-danger > td { + background-color: #f5c6cb; +} + +.table-danger th, +.table-danger td, +.table-danger thead th, +.table-danger tbody + tbody { + border-color: #ed969e; +} + +table .table-danger:hover { + background-color: #f1b0b7; +} + +table .table-danger:hover > td, +table .table-danger:hover > th { + background-color: #f1b0b7; +} + +.table-light, +.table-light > th, +.table-light > td { + background-color: #fdfdfe; +} + +.table-light th, +.table-light td, +.table-light thead th, +.table-light tbody + tbody { + border-color: #fbfcfc; +} + +table .table-light:hover { + background-color: #ececf6; +} + +table .table-light:hover > td, +table .table-light:hover > th { + background-color: #ececf6; +} + +.table-dark, +.table-dark > th, +.table-dark > td { + background-color: #c6c8ca; +} + +.table-dark th, +.table-dark td, +.table-dark thead th, +.table-dark tbody + tbody { + border-color: #95999c; +} + +table .table-dark:hover { + background-color: #b9bbbe; +} + +table .table-dark:hover > td, +table .table-dark:hover > th { + background-color: #b9bbbe; +} + +.table-active, +.table-active > th, +.table-active > td { + background-color: rgba(0, 0, 0, 0.075); +} + +table .table-active:hover { + background-color: rgba(0, 0, 0, 0.075); +} + +table .table-active:hover > td, +table .table-active:hover > th { + background-color: rgba(0, 0, 0, 0.075); +} + +table .thead-dark th { + color: #fff; + background-color: #343a40; + border-color: #454d55; +} + +table .thead-light th { + color: #495057; + background-color: #e9ecef; + border-color: #dee2e6; +} + +.table-dark { + color: #fff; + background-color: #343a40; +} + +.table-dark th, +.table-dark td, +.table-dark thead th { + border-color: #454d55; +} + +.table-dark.table-bordered { + border: 0; +} + +.table-dark.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(255, 255, 255, 0.05); +} + +.table-darktable tbody tr:hover { + color: #fff; + background-color: rgba(255, 255, 255, 0.075); +} + +@media (max-width: 575.98px) { + .table-responsive-sm { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .table-responsive-sm > .table-bordered { + border: 0; + } +} + +@media (max-width: 767.98px) { + .table-responsive-md { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .table-responsive-md > .table-bordered { + border: 0; + } +} + +@media (max-width: 991.98px) { + .table-responsive-lg { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .table-responsive-lg > .table-bordered { + border: 0; + } +} + +@media (max-width: 1199.98px) { + .table-responsive-xl { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .table-responsive-xl > .table-bordered { + border: 0; + } +} + +.table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +.table-responsive > .table-bordered { + border: 0; +} diff --git a/docs/factors.md b/docs/factors.md new file mode 100644 index 000000000..8e55930c4 --- /dev/null +++ b/docs/factors.md @@ -0,0 +1,23 @@ +# Factors + +A factor represents a single authenticating factor for a user. Common examples of this would be a password or an OTP. These factors can be combined in any order, and can be dynamically enabled using policies. + +## Password Factor + +This is the standard Password Factor. It allows you to select which Backend the password is checked with. here you can also specify which Policies are used to check the password. You can also specify which Factors a User has to pass to recover their account. + +## Dummy Factor + +This factor waits a random amount of time. Mostly used for debugging. + +## E-Mail Factor + +This factor is mostly for recovery, and used in conjunction with the Password Factor. + +## OTP Factor + +This is your typical One-Time Password implementation, compatible with Authy and Google Authenticator. You can enfore this Factor so that every user has to configure it, or leave it optional. + +## Captcha Factor + +While this factor doesn't really authenticate a user, it is part of the Authentication Flow. passbook uses Google's reCaptcha implementation. diff --git a/docs/index.md b/docs/index.md new file mode 100755 index 000000000..309acb089 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,31 @@ +# Welcome + +Welcome to the passbook Documentation. passbook is an open-source Identity Provider and Usermanagement software. It can be used as a central directory for users or customers and it can integrate with your existing Directory. + +passbook can also be used as part of an Application to facilitate User Enrollment, Password recovery and Social Login. + +passbook uses the following Terminology: + +### Policy + +A Policy is at a base level a yes/no gate. It will either evaluate to True or False depending on the Policy Kind and settings. For example, a "Group Membership Policy" evaluates to True if the User is member of the specified Group and False if not. This can be used to conditionally apply Factors and grant/deny access. + +### Provider + +A Provider is a way for other Applications to authenticate against passbook. Common Providers are OpenID Connect (OIDC) and SAML. + +### Source + +Sources are ways to get users into passbook. This might be an LDAP Connection to import Users from Active Directory, or an OAuth2 Connection to allow Social Logins. + +### Application + +An application links together Policies with a Provider, allowing you to control access. It also holds Information like UI Name, Icon and more. + +### Factors + +Factors represent Authentication Factors, like a Password or OTP. These Factors can be dynamically enabled using policies. This allows you to, for example, force users from a certain IP ranges to complete a Captcha to authenticate. + +### Property Mappings + +Property Mappings allow you to make Information available for external Applications. For example, if you want to login to AWS with passbook, you'd use Property Mappings to set the User's Roles based on their Groups. diff --git a/docs/installation/docker-compose.md b/docs/installation/docker-compose.md new file mode 100644 index 000000000..af9a29a27 --- /dev/null +++ b/docs/installation/docker-compose.md @@ -0,0 +1,26 @@ +# docker-compose + +This installation Method is for test-setups and small-scale productive setups. + +## Prerequisites + +- docker +- docker-compose + +## Install + +Download the latest `docker-compose.yml` from [here](https://git.beryju.org/BeryJu.org/passbook/raw/master/docker-compose.yml). Place it in a directory of your choice. + +passbook needs to know it's primary URL to create links in E-Mails and set cookies, so you have to run the following command: + +``` +export PASSBOOK_DOMAIN=domain.tld # this can be any domain or IP, it just needs to point to passbook. +``` + +The compose file references the current latest version, which can be overridden with the `SERVER_TAG` Environment variable. + +If you plan to use this setup for production, it is also advised to change the PostgreSQL Password by setting `PG_PASS` to a password of your choice. + +Now you can pull the Docker images needed by running `docker-compose pull`. After this has finished, run `docker-compose up -d` to start passbook. + +passbook will then be reachable on Port 80. You can optionally configure the packaged traefik to use Let's Encrypt for TLS Encryption. diff --git a/docs/installation/install.md b/docs/installation/install.md new file mode 100755 index 000000000..ebd88c387 --- /dev/null +++ b/docs/installation/install.md @@ -0,0 +1,6 @@ +# Installation + +There are two supported ways to install passbook: + +- [docker-compose](docker-compose.md) for test- or small productive setups +- [Kubernetes](./kubernetes.md) for larger Productive setups diff --git a/docs/installation/kubernetes.md b/docs/installation/kubernetes.md new file mode 100644 index 000000000..959437b57 --- /dev/null +++ b/docs/installation/kubernetes.md @@ -0,0 +1,3 @@ +# Kubernetes + +For a mid to high-load Installation, Kubernetes is recommended. passbook is installed using a helm-chart. diff --git a/docs/k8s/deployment.yml b/docs/k8s/deployment.yml new file mode 100644 index 000000000..d480ee22d --- /dev/null +++ b/docs/k8s/deployment.yml @@ -0,0 +1,33 @@ +--- +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: passbook-docs + namespace: prod-passbook-docs + labels: + app.kubernetes.io/name: passbook-docs + app.kubernetes.io/managed-by: passbook-docs +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: passbook-docs + template: + metadata: + labels: + app.kubernetes.io/name: passbook-docs + spec: + containers: + - name: passbook-docs + image: "docker.beryju.org/passbook/docs:latest" + ports: + - name: http + containerPort: 80 + protocol: TCP + resources: + limits: + cpu: 10m + memory: 20Mi + requests: + cpu: 10m + memory: 20Mi diff --git a/docs/k8s/ingress.yml b/docs/k8s/ingress.yml new file mode 100644 index 000000000..210826cad --- /dev/null +++ b/docs/k8s/ingress.yml @@ -0,0 +1,21 @@ +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + labels: + app.kubernetes.io/name: passbook-docs + name: passbook-docs + namespace: prod-passbook-docs +spec: + rules: + - host: docs.passbook.beryju.org + http: + paths: + - backend: + serviceName: passbook-docs-http + servicePort: http + path: / + tls: + - hosts: + - docs.passbook.beryju.org + secretName: passbook-docs-acme diff --git a/docs/k8s/service.yml b/docs/k8s/service.yml new file mode 100644 index 000000000..0e83a1a8a --- /dev/null +++ b/docs/k8s/service.yml @@ -0,0 +1,17 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: passbook-docs-http + namespace: prod-passbook-docs + labels: + app.kubernetes.io/name: passbook-docs +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: passbook-docs diff --git a/docs/policies.md b/docs/policies.md new file mode 100644 index 000000000..5d831fe8f --- /dev/null +++ b/docs/policies.md @@ -0,0 +1,64 @@ +# Policies + +## Kinds + +There are two different Kind of policies, a Standard Policy and a Password Policy. Normal Policies just evaluate to True or False, and can be used everywhere. Password Policies apply when a Password is set (during User enrollment, Recovery or anywhere else). These policies can be used to apply Password Rules like length, etc. The can also be used to expire passwords after a certain amount of time. + +## Standard Policies + +### Group-Membership Policy + +This policy evaluates to True if the current user is a Member of the selected group. + +### Reputation Policy + +passbook keeps track of failed login attempts by Source IP and Attempted Username. These values are saved as scores. Each failed login decreases the Score for the Client IP as well as the targeted Username by one. + +This policy can be used to for example prompt Clients with a low score to pass a Captcha before they can continue. + +### Field matcher Policy + +This policy allows you to evaluate arbitrary comparisons against the User instance. Currently supported fields are: + +- Username +- E-Mail +- Name +- Is_active +- Date joined + +Any of the following operations are supported: + +- Starts with +- Ends with +- Contains +- Regexp (standard Python engine) +- Exact + +### SSO Policy + +This policy evaluates to True if the current Authentication Flow has been initiated through an external Source, like OAuth and SAML. + +### Webhook Policy + +This policy allows you to send an arbitrary HTTP Request to any URL. You can then use JSONPath to extract the result you need. + +## Password Policies + +### Password Policy + +This Policy allows you to specify Password rules, like Length and required Characters. +The following rules can be set: + +- Minimum amount of Uppercase Characters +- Minimum amount of Lowercase Characters +- Minimum amount of Symbols Characters +- Minimum Length +- Symbol charset (define which characters are counted as symbols) + +### Have I Been Pwned Policy + +This Policy checks the hashed Password against the [Have I Been Pwned](https://haveibeenpwned.com/) API. This only sends the first 5 characters of the hashed password. The remaining comparison is done within passbook. + +### Password-Expiry Policy + +This policy can enforce regular password rotation by expiring set Passwords after a finite amount of time. This forces users to set a new password. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 000000000..8be00f447 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,16 @@ +site_name: passbook Docs +nav: + - Home: index.md + - Installation: + - Installation: installation/install.md + - docker-compose: installation/docker-compose.md + - Kubernetes: installation/kubernetes.md + - Policies: policies.md + - Factors: factors.md + +repo_url: https://git.beryju.org/BeryJu.org/passbook +theme: + name: bootstrap4 + +extra_css: + - css/custom.css diff --git a/passbook/admin/forms/base.py b/passbook/admin/forms/base.py index 3d6462aa3..9950a6e67 100644 --- a/passbook/admin/forms/base.py +++ b/passbook/admin/forms/base.py @@ -1,4 +1,4 @@ -"""p2 form helpers""" +"""passbook form helpers""" from django import forms from passbook.admin.fields import YAMLField diff --git a/passbook/policies/reputation/signals.py b/passbook/policies/reputation/signals.py index cb552236e..570396ef9 100644 --- a/passbook/policies/reputation/signals.py +++ b/passbook/policies/reputation/signals.py @@ -25,11 +25,13 @@ def update_score(request, username, amount): user_score.save() LOGGER.debug("Updated score", amount=amount, for_user=username) + @receiver(user_login_failed) def handle_failed_login(sender, request, credentials, **kwargs): """Lower Score for failed loging attempts""" update_score(request, credentials.get('username'), -1) + @receiver(user_logged_in) def handle_successful_login(sender, request, user, **kwargs): """Raise score for successful attempts"""