diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3e4e359 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,43 @@ +# from https://github.com/alexkaratarakis/gitattributes/blob/master/Java.gitattributes + +# Handle line endings automatically for files detected as text +# and leave all files detected as binary untouched. +* text=auto + +# +# The above will handle all files NOT found below +# +# These files are text and should be normalized (Convert crlf => lf) +*.css text +*.df text +*.htm text +*.html text +*.java text +*.js text +*.json text +*.jsp text +*.jspf text +*.jspx text +*.properties text +*.sh text +*.tld text +*.txt text +*.tag text +*.tagx text +*.xml text +*.yml text + +# These files are binary and should be left untouched +# (binary is a macro for -text -diff) +*.class binary +*.dll binary +*.ear binary +*.gif binary +*.ico binary +*.jar binary +*.jpg binary +*.jpeg binary +*.png binary +*.so binary +*.war binary + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e5f50ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.project +/.settings +/.svn +**/.*.md.html diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ebe8e47 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,39 @@ +# https://github.com/travis-ci/travis-yml/blob/master/schema.json +language: bash # using 'bash' instead of 'minimal' to prevent https://travis-ci.community/t/job-stuck-booting/4976 + +sudo: true + +services: + - docker + +cache: + directories: + - $HOME/.trivy/cache + +env: + global: + DOCKER_REGISTRY=docker.io + DOCKER_REPO=vegardit/openldap + TRIVY_CACHE_DIR=$HOME/.trivy/cache + + +before_script: + # workaround for + # - https://stackoverflow.com/questions/34130613/how-to-make-travis-ci-work-with-a-local-git-branch-instead-of-a-specific-commit + # - https://unhashable.com/getting-the-current-branch-name-during-a-pull-request-in-travis-ci/ + - if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then + export GIT_BRANCH="$TRAVIS_BRANCH"; + else + export GIT_BRANCH="$TRAVIS_PULL_REQUEST_BRANCH"; + fi + #- printenv | sort + - mkdir -p "$TRIVY_CACHE_DIR" + +script: + - echo "$DOCKER_PASSWORD" | docker login -u="$DOCKER_USERNAME" "$DOCKER_REGISTRY" --password-stdin && + if [[ "$TRAVIS_BRANCH" == "master" ]]; then + DOCKER_PUSH=1 bash ./build-image.sh; + else + bash ./build-image.sh; + fi + - ls -l $TRIVY_CACHE_DIR/trivy \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2fc2fbc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +# Contributing to docker-openldap + +Thanks for contributing to docker-openldap! + +## License + +By contributing your code, you agree to license your contribution under the [Apache License 2.0](LICENSE.txt). diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..11069ed --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md index 9dfdda9..8b5da68 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,199 @@ -# docker-openldap -Opinionated docker image based on minideb (Debian 10 "buster") to run an OpenLDAP 2.4 server. +# vegardit/openldap + +[![Build Status](https://travis-ci.com/vegardit/docker-openldap.svg?branch=master "Tavis CI")](https://travis-ci.com/vegardit/docker-openldap) +[![License](https://img.shields.io/github/license/vegardit/docker-openldap.svg?label=license)](#license) +[![Docker Pulls](https://img.shields.io/docker/pulls/vegardit/openldap.svg)](https://hub.docker.com/r/vegardit/openldap) +[![Docker Stars](https://img.shields.io/docker/stars/vegardit/openldap.svg)](https://hub.docker.com/r/vegardit/openldap) +[![Docker Image Size](https://images.microbadger.com/badges/image/vegardit/openldap.svg)](https://hub.docker.com/r/vegardit/openldap) + +1. [What is it?](#what-is-it) +1. [Configuration](#config) + 1. [Initial configuration](#initial-config) + 1. [Customizing the Password Policy](#ppolicy) + 1. [Changing UID/GID of OpenLDAP service user](#uidgid) + 1. [Periodic LDAP Backup](#backup) + 1. [Synchronizing timezone/time with docker host](#timesync) + 1. [Performance tuning](#performance-tuning) + 1. [Troubleshooting](#troubleshooting) +1. [References](#references) +1. [License](#license) + + +## What is it? + +Opinionated docker image based on [minideb](https://github.com/bitnami/minideb) (Debian 10 "buster") to run an [OpenLDAP 2.4](https://www.openldap.org/doc/admin24/) server. + +To keep the image light and simple, it does not configure TLS. Instead we recommend configuring a [Traefik 2.x](https://traefik.io) [TCP service](https://docs.traefik.io/routing/services/#configuring-tcp-services) with e.g. an auto-renewing [Let's Encrypt configuration](https://docs.traefik.io/https/acme/) in front of the OpenLDAP service. + + +## Configuration + +### Initial configuration + +Various parts of the LDAP server can be configured via environment variables. All environment variables starting with `LDAP_INIT_` +are only evaluated on the **first** container launch. Changing their values later has no effect when restarting or updating the container. + +To customize the initial configuration you can set the following environment variables: + +```sh +LDAP_INIT_ORG_DN='dc=example,dc=com' +LDAP_INIT_ORG_NAME='Example Corporation' +LDAP_INIT_ADMIN_GROUP_DN='cn=ldapadmins,ou=Groups,${LDAP_INIT_ORG_DN}' +LDAP_INIT_ROOT_USER_DN='uid=admin,${LDAP_INIT_ORG_DN}' +LDAP_INIT_ROOT_USER_PW='changeit' +LDAP_INIT_RFC2307BIS_SCHEMA=0 # 0=use NIS (RFC2307) schema, 1=use RFC2307bis schema +``` + +Environment variables can for example be set using `docker run` with `-e`, e.g. + +```sh +docker run -itd \ + -e LDAP_INIT_ORG_DN='o=yourorg' \ + -e LDAP_INIT_ROOT_USER_PW='newpassword' \ + -e LDAP_INIT_ORG_NAME='Company Inc' \ + -e LDAP_INIT_PPOLICY_PW_MIN_LENGTH='12' \ + vegardit/openldap +``` + +Alternatively you can use an [env-file](https://docs.docker.com/compose/env-file/) to store all changed variables and use the option `--env-file` with `docker run`, e.g.: + +```sh +docker run -itd --env-file environment vegardit/openldap +``` + +In environment file values must not be enclosed using quotes (`'` or `"`), please remove them. See this example file: [example/docker/example.env]. + +The initial LDAP tree structure is imported from [/opt/ldifs/init_org_tree.ldif](ldifs/init_org_tree.ldif). +You can mount a custom file at `/opt/ldifs/init_org_tree.ldif` if you require changes. + +LDAP entries (users, groups) are imported from [/opt/ldifs/init_org_entries.ldif](ldifs/init_org_entries.ldif). +You can mount a custom file at `/opt/ldifs/init_org_entries.ldif` if you require changes. + +### Customizing the Password Policy + +On **initial** container launch, the [password policy](https://www.openldap.org/doc/admin24/overlays.html#Password%20Policies) is imported from [/opt/ldifs/init_org_ppolicy.ldif](ldifs/init_org_ppolicy.ldif) + +The following parameters can be modified via environment variables **before** initial container launch: + +```sh +LDAP_INIT_PPOLICY_DEFAULT_DN='cn=DefaultPasswordPolicy,ou=Policies,${LDAP_INIT_ORG_DN}' +LDAP_INIT_PPOLICY_PW_MIN_LENGTH=8 +LDAP_INIT_PPOLICY_MAX_FAILURES=3 +LDAP_INIT_PPOLICY_LOCKOUT_DURATION=300 +``` + +If more customizations are required, simply mount a custom policy file at `/opt/ldifs/init_org_ppolicy.ldif` **before** initial container launch. + +**Password Quality Checker:** + +[pqChecker](https://www.meddeb.net/pqchecker/) is configured as default password quality checker using the rule `0|01010101` with +the following meaning: + +|Pos. |Value |Effective Rule +|----:|:-----:|:---------- +|0-1 | `0\|`|Don't broadcast passwords. +|2-4 | `01` |Minimum 1 uppercase character. +|5-6 | `01` |Minimum 1 lowercase character. +|7-8 | `01` |Minimum 1 digit. +|9-10 | `01` |Minimum 1 special character. +|11-..| empty | No characters are disallowed in passwords. + +The pqChecker rule syntax is explained here in more detail: https://www.meddeb.net/pqchecker/?Idx=2 + +A custom rule can be provided via an environment variable, e.g.: + +```sh +LDAP_PPOLICY_PQCHECKER_RULE='0|01020101@!+-#' +``` + +### Changing UID/GID of OpenLDAP service user + +The UID/GID of the user running the OpenLDAP service can be aligned with the docker host, using the environment variables +`LDAP_OPENLDAP_UID` and `LDAP_OPENLDAP_GID`. + +During each container start it is verified that the given UID/GID matches the currently effective UID/GID. If not, the UID/GID +of the `openldap` user are changed accordingly and `chown` on `/etc/ldap` and `/var/lib/slapd` is executed before the OpenLDAP service is started. + +### Periodic LDAP Backup + +This image automatically generates a daily LDIF export at `2 a.m.` to `/var/lib/ldap/data.ldif`. + +The following environment variables can be used to configure the automatic LDAP backup: +```bash +LDAP_BACKUP_TIME='02:00' # Format is "HH:MM", i.e. 24-hour format with minute precision +LDAP_BACKUP_FILE='/var/lib/ldap/data.ldif' +``` + +To disable automatic backup set an empty value for the environment variable `LDAP_BACKUP_TIME`. + +### Synchronizing timezone/time with docker host + +To use the same timezone and/or time of the docker host you can run the docker image with `--volume /etc/localtime:/etc/localtime:ro --volume /etc/timezone:/etc/timezone:ro` + +Docker compose file example: +```yaml +version: '3.7' +services: + openldap: + image: vegardit/openldap:latest + volumes: + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro +``` + +### Performance tuning + +#### DB Indexes + +The database indexes that are configured during initial container launch are imported from [/opt/ldifs/init_backend_indexes.ldif](ldifs/init_mdb_indexes.ldif) + +To use other indexes, simply mount a custom file at `/opt/ldifs/init_backend_indexes.ldif` **before** initial container launch. + +#### Memory usage + +The maximum number of open files is set to `1024` by default to prevent excessive RAM consumption as reported [here](https://github.com/docker/docker/issues/8231). + +The following environment variable can be used to increase this limit: + +```sh +LDAP_NOFILE_LIMIT=2048 +``` + +### Troubleshooting + +The slapd service logs to stdout. You can change the active log levels by setting this environment variable: + +```sh +LDAP_LOG_LEVELS='Config Stats' +``` + +The following [log levels](https://www.openldap.org/doc/admin24/slapdconfig.html#loglevel%20%3Clevel%3E) are available: +``` +Any (-1) enable all debugging +Trace (1) trace function calls +Packets (2) debug packet handling +Args (4) heavy trace debugging +Conns (8) connection management +BER (16) print out packets sent and received +Filter (32) search filter processing +Config (64) configuration processing +ACL (128) access control list processing +Stats (256) stats log connections/operations/results +Stats2 (512) stats log entries sent +Shell (1024) print communication with shell backends +Parse (2048) print entry parsing debugging +Sync (16384) syncrepl consumer processing +None (32768) only messages that get logged whatever log level is set +``` + + +## References + +- OpenLDAP Software 2.4 Administrator's Guide https://www.openldap.org/doc/admin24/guide.html +- OpenLDAP Online Configuration Reference https://tylersguides.com/guides/openldap-online-configuration-reference/ +- slapd-config(5) - Linux man page https://linux.die.net/man/5/slapd-config + + +## License + +All files in this repository are released under the [Apache License 2.0](LICENSE.txt). diff --git a/build-debug-image.sh b/build-debug-image.sh new file mode 100644 index 0000000..2255e72 --- /dev/null +++ b/build-debug-image.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# +# Copyright 2019-2020 by Vegard IT GmbH, Germany, https://vegardit.com +# SPDX-License-Identifier: Apache-2.0 +# +# @author Sebastian Thomschke, Vegard IT GmbH +# +# https://github.com/vegardit/docker-openldap +# + +bash "$(dirname "$0")/build-image.sh" --build-arg DEBUG_BUILD=1 diff --git a/build-image.sh b/build-image.sh new file mode 100644 index 0000000..219c66c --- /dev/null +++ b/build-image.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# +# Copyright 2019-2020 by Vegard IT GmbH, Germany, https://vegardit.com +# SPDX-License-Identifier: Apache-2.0 +# +# @author Sebastian Thomschke, Vegard IT GmbH +# +# https://github.com/vegardit/docker-openldap +# + +set -e -x +if [ ! -n "$BASH" ]; then /usr/bin/env bash "$0" "$@"; exit; fi + +DOCKER_REGISTRY=${DOCKER_REGISTRY:-docker.io} +DOCKER_REPO=${DOCKER_REPO:-vegardit/openldap} + +last_commit_date=$(date -d @$(git log -1 --format="%at") --utc +"%Y%m%d_%H%M%S") + +docker build $(dirname $0)/image \ + --compress \ + --build-arg GIT_BRANCH="${GIT_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}" \ + --build-arg GIT_COMMIT_DATE="$(date -d @$(git log -1 --format='%at') --utc +'%Y-%m-%d %H:%M:%S UTC')" \ + --build-arg GIT_COMMIT_HASH="$(git rev-parse --short HEAD)" \ + --build-arg GIT_REPO_URL="$(git config --get remote.origin.url)" \ + `# using the current date as value for BASE_LAYER_CACHE_KEY, i.e. the base layer cache (that holds system packages with security updates) will be invalidate once per day` \ + --build-arg BASE_LAYER_CACHE_KEY=$(date +%Y%m%d) \ + -t $DOCKER_REPO:latest \ + -t $DOCKER_REPO:latest-buster \ + -t $DOCKER_REPO:${last_commit_date} \ + -t $DOCKER_REPO:${last_commit_date}-buster \ + "$@" + +# +# perform security audit using https://github.com/aquasecurity/trivy +# +mkdir -p ${TRIVY_CACHE_DIR:-$HOME/.trivy/cache} +docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v ${TRIVY_CACHE_DIR:-$HOME/.trivy/cache}:/root/.cache/ \ + aquasec/trivy --no-progress --exit-code 0 --severity HIGH,CRITICAL $DOCKER_REPO:${last_commit_date} +docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v ${TRIVY_CACHE_DIR:-$HOME/.trivy/cache}:/root/.cache/ \ + aquasec/trivy --no-progress --ignore-unfixed --exit-code 1 --severity HIGH,CRITICAL $DOCKER_REPO:${last_commit_date} +sudo chown -R $USER:$(id -gn) $TRIVY_CACHE_DIR || true + +# +# determine effective LDAP version and apply tags +# +ldap_version=$(docker run $DOCKER_REPO:${last_commit_date} "dpkg -s slapd | grep 'Version:' | grep -oP 'Version: \K\d+\.\d+\.\d+'") +docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REPO:${ldap_version%.*}.x #2.4.x +docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REPO:${ldap_version%.*}.x-buster #2.4.x +docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REPO:${ldap_version%%.*}.x #2.x +docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REPO:${ldap_version%%.*}.x-buster #2.x + +# +# push image with tags to remote docker registry +# +if [[ "${DOCKER_PUSH:-0}" == "1" ]]; then + docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:latest + docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:latest-buster + docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version} #2.4.47 + docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version}-buster #2.4.47 + docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%.*}.x #2.4.x + docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%.*}.x-buster #2.4.x + docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%%.*}.x #2.x + docker image tag $DOCKER_REPO:${last_commit_date} $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%%.*}.x-buster #2.x + + docker push $DOCKER_REGISTRY/$DOCKER_REPO:latest + docker push $DOCKER_REGISTRY/$DOCKER_REPO:latest-buster + docker push $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version} #2.4.47 + docker push $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version}-buster #2.4.47 + docker push $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%.*}.x #2.4.x + docker push $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%.*}.x-buster #2.4.x + docker push $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%%.*}.x #2.x + docker push $DOCKER_REGISTRY/$DOCKER_REPO:${ldap_version%%.*}.x-buster #2.x +fi diff --git a/example/docker-compose/docker-compose.yml b/example/docker-compose/docker-compose.yml new file mode 100644 index 0000000..e6a63b6 --- /dev/null +++ b/example/docker-compose/docker-compose.yml @@ -0,0 +1,66 @@ +############################# +# @author Sebastian Thomschke, Vegard IT GmbH +############################# + +version: '3.7' + +# https://docs.docker.com/compose/compose-file/ + +services: + + ldap_service: + # https://github.com/vegardit/docker-openldap + image: vegardit/openldap:latest # https://hub.docker.com/r/vegardit/openldap + environment: + #DEBUG_RUN_SH: 1 + LDAP_INIT_ORG_DN: o=example.com + LDAP_INIT_ORG_NAME: Example Inc. + LDAP_INIT_ROOT_USER_DN: uid=admin,o=example.com + LDAP_INIT_ROOT_USER_PW: CHANGE@it + LDAP_BACKUP_TIME: # disable backup by setting to empty string + ports: + - 389:389 + volumes: + - ./ldifs/init_org_entries.ldif:/opt/ldifs/init_org_entries.ldif:ro + - ./ldifs/init_org_tree.ldif:/opt/ldifs/init_org_tree.ldif:ro + + ldap_ui: + # https://github.com/alekseydemidov/pla-ssp + image: netflyer/openldap-ui:latest # https://hub.docker.com/r/netflyer/openldap-ui + ports: + - 8080:8080 + - 8081:8081 + environment: + ############################### + # PHP LDAP Admin (Port 8080) + ############################### + PHPLDAPADMIN_LDAP_BASE: o=example.com + PHPLDAPADMIN_LDAP_CLIENT_TLS: 'false' + PHPLDAPADMIN_LDAP_HOSTS: ldap_service + ############################### + # SelfService Password (Port 8081) https://github.com/ltb-project/self-service-password + ############################### + DEBUG_MODE: 'false' + #LDAP connection https://ltb-project.org/documentation/self-service-password/1.3/config_ldap + LDAP_URL: ldap://ldap_service + LDAP_BASE_DN: ou=Users,o=example.com + LDAP_USER_DN: uid=ldapbind,ou=TechnicalAccounts,ou=Users,o=example.com + LDAP_USER_PASSWORD: CHANGE@it + LDAP_FILTER: '(\&(objectClass=inetOrgPerson)(uid={login}))' # backslash is mandatory! + LDAP_LOGIN_ATTRIBUTE: 'uid' + LDAP_FULLNAME_ATTRIBUTE: 'cn' + CHANGE_SSHKEY: 'true' + MAIL_ADDRESS_USE_LDAP: 'true' + USE_TOKENS: 'false' # don't use "forgot my password" links + USE_QUESTIONS: 'false' # https://ltb-project.org/documentation/self-service-password/latest/config_questions + # Password policy https://ltb-project.org/documentation/self-service-password/1.3/config_ppolicy + PWD_MIN_LENGTH: 8 + PWD_MAX_LENGTH: 32 + PWD_MIN_LOWER: 1 + PWD_MIN_UPPER: 1 + PWD_MIN_DIGIT: 1 + PWD_MIN_SPECIAL: 1 + PWD_NO_REUSE: 'false' + PWD_SHOW_POLICY: 'always' + PWD_SHOW_POLICY_POS: 'below' + USE_PWNEDPASSWORD: 'false' diff --git a/example/docker-compose/ldifs/init_org_entries.ldif b/example/docker-compose/ldifs/init_org_entries.ldif new file mode 100644 index 0000000..fa5ca06 --- /dev/null +++ b/example/docker-compose/ldifs/init_org_entries.ldif @@ -0,0 +1,68 @@ +version: 1 + +############################## +# Employees +# slappasswd -h {SHA} -s changeit +############################## +dn: uid=alice,ou=Employees,ou=Users,${LDAP_INIT_ORG_DN} +ou: Employees +ou: Users +description: Alice Young +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: ldapPublicKey +uid: alice +cn: Alice Young +sn: Young +givenName: Alice +mail: alice@example.com +userPassword: CHANGE@it +sshPublicKey: + +dn: uid=bob,ou=Employees,ou=Users,${LDAP_INIT_ORG_DN} +ou: Employees +ou: Users +description: Bob Old +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: ldapPublicKey +uid: bob +cn: Bob Old +sn: Old +givenName: Bob +mail: bob@example.com +userPassword: CHANGE@it +sshPublicKey: + + +############################## +# Technical Accounts +############################## +dn: uid=ldapbind,ou=TechnicalAccounts,ou=Users,${LDAP_INIT_ORG_DN} +ou: TechnicalAccounts +ou: Users +description: ldapbind user +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +uid: ldapbind +cn: ldapbind +sn: ldapbind +userPassword: CHANGE@it + +############################## +# Groups +############################## +dn: cn=ldapadmins,ou=Groups,${LDAP_INIT_ORG_DN} +ou: Groups +description: LDAP Administrators Group +objectClass: top +objectClass: groupOfUniqueNames +cn: ldapadmins +uniqueMember: uid=alice,ou=Employees,ou=Users,${LDAP_INIT_ORG_DN} +uniqueMember: uid=bob,ou=Employees,ou=Users,${LDAP_INIT_ORG_DN} diff --git a/example/docker-compose/ldifs/init_org_tree.ldif b/example/docker-compose/ldifs/init_org_tree.ldif new file mode 100644 index 0000000..9447714 --- /dev/null +++ b/example/docker-compose/ldifs/init_org_tree.ldif @@ -0,0 +1,43 @@ +version: 1 + +############################## +# LDAP tree definition +############################## +dn: ${LDAP_INIT_ORG_DN} +description: ${LDAP_INIT_ORG_NAME} +objectClass: top +objectClass: organization +${LDAP_INIT_ORG_DN_ATTR} + +dn: ou=Groups,${LDAP_INIT_ORG_DN} +ou: Groups +description: Groups +objectClass: top +objectClass: organizationalUnit + +dn: ou=Users,${LDAP_INIT_ORG_DN} +ou: Users +description: Users Accounts +objectClass: top +objectClass: organizationalUnit + +dn: ou=Employees,ou=Users,${LDAP_INIT_ORG_DN} +ou: Employees +ou: Users +description: Internal Users (Employees) +objectClass: top +objectClass: organizationalUnit + +dn: ou=External,ou=Users,${LDAP_INIT_ORG_DN} +ou: External +ou: Users +description: External Users (Contractors, Customers) +objectClass: top +objectClass: organizationalUnit + +dn: ou=TechnicalAccounts,ou=Users,${LDAP_INIT_ORG_DN} +ou: TechnicalAccounts +ou: Users +description: Technical Accounts +objectClass: top +objectClass: organizationalUnit diff --git a/example/docker/example.env b/example/docker/example.env new file mode 100644 index 0000000..6bcca4d --- /dev/null +++ b/example/docker/example.env @@ -0,0 +1,24 @@ +#See https://docs.docker.com/compose/env-file/ + +#DEBUG_RUN_SH=0 +#INIT_SH_FILE= + +LDAP_INIT_ORG_DN=o=yourorg +LDAP_INIT_ORG_NAME=Example Inc +#LDAP_INIT_ADMIN_GROUP_DN=cn=ldapadmins,ou=Groups,o=yourorg +#LDAP_INIT_ROOT_USER_DN=uid=admin,o=yourorg +LDAP_INIT_ROOT_USER_PW=password + +#LDAP_INIT_PPOLICY_DEFAULT_DN=cn=DefaultPasswordPolicy,ou=Policies,o=yourorg +LDAP_INIT_PPOLICY_PW_MIN_LENGTH=8 +#LDAP_INIT_PPOLICY_MAX_FAILURES=3 +#LDAP_INIT_PPOLICY_LOCKOUT_DURATION=300 +#LDAP_INIT_RFC2307BIS_SCHEMA=0 +#LDAP_PPOLICY_PQCHECKER_RULE=0|01010101 + +#LDAP_NOFILE_LIMIT=1024 +#LDAP_LOG_LEVELS=Config Stats + +# Format is "HH:MM", i.e. 24-hour format with minute precision +#LDAP_BACKUP_TIME=02:00 +#LDAP_BACKUP_FILE=/var/lib/ldap/data.ldif diff --git a/image/Dockerfile b/image/Dockerfile new file mode 100644 index 0000000..043766c --- /dev/null +++ b/image/Dockerfile @@ -0,0 +1,131 @@ +# Copyright 2019-2020 by Vegard IT GmbH, Germany, https://vegardit.com +# SPDX-License-Identifier: Apache-2.0 +# +# @author Sebastian Thomschke, Vegard IT GmbH +# +# https://github.com/vegardit/docker-openldap +# + +# built daily: +# https://hub.docker.com/r/bitnami/minideb/tags?page=1&name=buster +FROM bitnami/minideb:buster + +# built monthly: +# https://hub.docker.com/_/debian?tab=tags&page=1&name=buster-slim +# FROM debian:buster-slim + +LABEL maintainer="Vegard IT GmbH (vegardit.com)" + +# if set to 1 debug tools are added to the image (htop,less,mc,vim) +ARG DEBUG_BUILD=0 + +ARG PQCHECKER_URL=https://www.meddeb.net/pub/pqchecker/deb/8/pqchecker_2.0.0_amd64.deb +ARG PQCHECKER_MD5=c005ce596e97d13e39485e711dcbc7e1 + +ARG DEBIAN_FRONTEND=noninteractive +ARG LC_ALL=C + +ARG BASE_LAYER_CACHE_KEY + +SHELL ["/bin/bash", "-c"] + +RUN \ + apt-get update -y && \ + # https://github.com/phusion/baseimage-docker/issues/319 + apt-get install -y apt-utils 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) && \ + ############################################################ + if [ "${DEBUG_BUILD}" = "1" ]; then \ + echo "Installing debugging tools..." && \ + apt-get update -y && \ + apt-get install --no-install-recommends -y libcomerr2 mc && \ + apt-get install --no-install-recommends -y htop less procps vim && \ + echo 'set ignorecase\n\ +set showmatch\n\ +set novisualbell\n\ +set noerrorbells\n\ +syntax enable\n\ +set mouse-=a' > ~/.vimrc; \ + fi && \ + ############################################################ + echo "Installing slapd..." && \ + echo 'slapd slapd/root_password password whatever' | debconf-set-selections && \ + echo 'slapd slapd/root_password_again password whatever' | debconf-set-selections && \ + apt-get install --no-install-recommends -y slapd ldap-utils && \ + echo "OpenLDAP $(apt-cache show slapd | grep Version)" >> /opt/build_info && \ + # workaround for 'service slapd stop' not working, see https://stackoverflow.com/a/58792698/5116073 + sed -i 's/--exec $SLAPD 2/--name slapd 2/' /etc/init.d/slapd && \ + ############################################################ + echo "Installing pqChecker password quality checker module..." && \ + # https://www.meddeb.net/pqchecker/ + apt-get install --no-install-recommends -y curl && \ + curl -k -o /tmp/pqchecker.deb -SL "${PQCHECKER_URL}" && \ + echo "${PQCHECKER_MD5} /tmp/pqchecker.deb" | md5sum -c - && \ + dpkg -i /tmp/pqchecker.deb && \ + rm /tmp/pqchecker.deb && \ + apt-get remove --auto-remove -y curl && \ + ############################################################ + echo "Moving config and data directories..." && \ + mv /etc/ldap/slapd.d /etc/ldap/slapd.d_orig && \ + mkdir /etc/ldap/slapd.d && \ + mv /var/lib/ldap /var/lib/ldap_orig && \ + mkdir /var/lib/ldap && \ + ############################################################ + echo "apt-get clean up..." && \ + apt-get clean autoclean && \ + apt-get autoremove --purge -y && \ + ############################################################ + echo "Removing logs, caches and temp files..." && \ + rm -rf /var/cache/{apt,debconf} \ + /var/lib/apt/lists/* \ + /var/log/{apt,alternatives.log,bootstrap.log,dpkg.log} \ + /tmp/* /var/tmp/* + +VOLUME ["/etc/ldap/slapd.d", "/var/lib/ldap"] + +ARG GIT_REPO_URL +ARG GIT_BRANCH +ARG GIT_COMMIT_HASH +ARG GIT_COMMIT_DATE + +LABEL \ + org.label-schema.vcs-ref=$GIT_COMMIT_HASH \ + org.label-schema.vcs-url="https://github.com/vegardit/docker-openldap" + +RUN \ + echo "GIT_REPO: $GIT_REPO_URL" > /opt/build_info && \ + echo "GIT_BRANCH: $GIT_BRANCH" >> /opt/build_info && \ + echo "GIT_COMMIT: $GIT_COMMIT_HASH @ $GIT_COMMIT_DATE" >> /opt/build_info && \ + cat /opt/build_info + +COPY ldifs /opt/ldifs +COPY run.sh /opt/run.sh + +# Default configuration: can be overridden at the docker command line +ENV \ + DEBUG_RUN_SH=0 \ + INIT_SH_FILE='' \ + LDAP_INIT_ORG_DN='o=example.com' \ + LDAP_INIT_ORG_NAME='Example Corporation' \ + LDAP_INIT_ADMIN_GROUP_DN='cn=ldapadmins,ou=Groups,${LDAP_INIT_ORG_DN}' \ + LDAP_INIT_ROOT_USER_DN='uid=admin,${LDAP_INIT_ORG_DN}' \ + LDAP_INIT_ROOT_USER_PW='changeit' \ + LDAP_INIT_PPOLICY_DEFAULT_DN='cn=DefaultPasswordPolicy,ou=Policies,${LDAP_INIT_ORG_DN}' \ + LDAP_INIT_PPOLICY_PW_MIN_LENGTH=8 \ + LDAP_INIT_PPOLICY_MAX_FAILURES=3 \ + LDAP_INIT_PPOLICY_LOCKOUT_DURATION=300 \ + LDAP_INIT_RFC2307BIS_SCHEMA=0 \ + LDAP_PPOLICY_PQCHECKER_RULE='0|01010101' \ + LDAP_NOFILE_LIMIT=1024 \ + LDAP_LOG_LEVELS='Config Stats' \ + # Format is "HH:MM", i.e. 24-hour format with minute precision + LDAP_BACKUP_TIME='02:00' \ + LDAP_BACKUP_FILE='/var/lib/ldap/data.ldif' \ + LDAP_OPENLDAP_UID='' \ + LDAP_OPENLDAP_GID='' + +EXPOSE 389 + +ENTRYPOINT ["/bin/sh", "-c"] + +# exec is required for propagating SIGTERM from docker to child process +CMD ["exec /bin/bash /opt/run.sh"] \ No newline at end of file diff --git a/image/ldifs/init_frontend.ldif b/image/ldifs/init_frontend.ldif new file mode 100644 index 0000000..f1ad0e1 --- /dev/null +++ b/image/ldifs/init_frontend.ldif @@ -0,0 +1,12 @@ +version: 1 + +# Global Database Options @ https://linux.die.net/man/5/slapd-config + +dn: olcDatabase={-1}frontend,cn=config +changetype: modify +replace: olcPasswordHash +olcPasswordHash: {SSHA} +- +# maximum number of seconds (in real time) slapd will spend answering a search request +replace: olcTimeLimit +olcTimeLimit: 60 diff --git a/image/ldifs/init_mdb.ldif b/image/ldifs/init_mdb.ldif new file mode 100644 index 0000000..704e62f --- /dev/null +++ b/image/ldifs/init_mdb.ldif @@ -0,0 +1,20 @@ +version: 1 + +# General Database Options @ https://linux.die.net/man/5/slapd-config + +dn: olcDatabase={1}mdb,cn=config +changetype: modify +######################## +# configure Base DN +######################## +replace: olcSuffix +olcSuffix: ${LDAP_INIT_ORG_DN} +- +######################## +# configure root user +######################## +replace: olcRootDN +olcRootDN: ${LDAP_INIT_ROOT_USER_DN} +- +replace: olcRootPW +olcRootPW: ${LDAP_INIT_ROOT_USER_PW_HASHED} diff --git a/image/ldifs/init_mdb_acls.ldif b/image/ldifs/init_mdb_acls.ldif new file mode 100644 index 0000000..d8cf7e1 --- /dev/null +++ b/image/ldifs/init_mdb_acls.ldif @@ -0,0 +1,37 @@ +version: 1 + +# General Database Options @ https://linux.die.net/man/5/slapd-config + +dn: olcDatabase={1}mdb,cn=config +changetype: modify +######################## +# configure ACLs +######################## +replace: olcAccess +olcAccess: {0}to attrs=userPassword + by dn="${LDAP_INIT_ROOT_USER_DN}" write + by group/groupOfUniqueNames/uniqueMember="${LDAP_INIT_ADMIN_GROUP_DN}" write + by self write + by anonymous auth + by * none +olcAccess: {1}to attrs=shadowLastChange + by self write + by users read + by * none +olcAccess: {2}to attrs=sshPublicKey + by dn="${LDAP_INIT_ROOT_USER_DN}" write + by group/groupOfUniqueNames/uniqueMember="${LDAP_INIT_ADMIN_GROUP_DN}" write + by self write + by users read + by * none +# This ACL gives incoming connections the ability to read the rootDSE. +# It is important to allow this, as clients may need to obtain information from the base level (such as your supported SASL Mechanisms). +# The ".base" portion of the ACL restricts clients to querying only the top level +olcAccess: {3}to dn.base="" + by * read +olcAccess: {4}to * + by dn="${LDAP_INIT_ROOT_USER_DN}" write + by group/groupOfUniqueNames/uniqueMember="${LDAP_INIT_ADMIN_GROUP_DN}" write + by users read + by anonymous auth + by * none diff --git a/image/ldifs/init_mdb_indexes.ldif b/image/ldifs/init_mdb_indexes.ldif new file mode 100644 index 0000000..ae7d7f0 --- /dev/null +++ b/image/ldifs/init_mdb_indexes.ldif @@ -0,0 +1,15 @@ +version: 1 + +# General Database Options @ https://linux.die.net/man/5/slapd-config + +dn: olcDatabase={1}mdb,cn=config +changetype: modify +######################## +# configure DB indexes +######################## +replace: olcDbIndex +olcDbIndex: objectClass pres,eq +olcDbIndex: uid,mail pres,eq +olcDbIndex: uidNumber,gidNumber eq +olcDbIndex: cn,givenName,sn,ou pres,eq,approx,sub +olcDbIndex: member,memberUid,memberOf,uniqueMember pres,eq diff --git a/image/ldifs/init_module_memberof.ldif b/image/ldifs/init_module_memberof.ldif new file mode 100644 index 0000000..dbe304d --- /dev/null +++ b/image/ldifs/init_module_memberof.ldif @@ -0,0 +1,31 @@ +version: 1 + +dn: cn=module{0},cn=config +changetype: modify +add: olcModuleLoad +olcModuleLoad: memberof +olcModuleLoad: refint + +dn: olcOverlay=memberof,olcDatabase={1}mdb,cn=config +objectClass: top +objectClass: olcConfig +objectClass: olcOverlayConfig +objectClass: olcMemberOf +olcOverlay: memberof +olcMemberOfDangling: ignore +olcMemberOfRefInt: TRUE +olcMemberOfGroupOC: groupOfUniqueNames +olcMemberOfMemberAD: uniqueMember +olcMemberOfMemberOfAD: memberOf + +dn: olcOverlay=refint,olcDatabase={1}mdb,cn=config +objectClass: top +objectClass: olcConfig +objectClass: olcOverlayConfig +objectClass: olcRefintConfig +olcOverlay: refint +olcRefintAttribute: owner +olcRefintAttribute: manager +olcRefintAttribute: member +olcRefintAttribute: memberOf +olcRefintAttribute: uniqueMember diff --git a/image/ldifs/init_module_ppolicy.ldif b/image/ldifs/init_module_ppolicy.ldif new file mode 100644 index 0000000..da2409b --- /dev/null +++ b/image/ldifs/init_module_ppolicy.ldif @@ -0,0 +1,18 @@ +version: 1 + +dn: cn=module{0},cn=config +changetype: modify +add: olcModuleLoad +olcModuleLoad: ppolicy + +dn: olcOverlay=ppolicy,olcDatabase={1}mdb,cn=config +objectClass: top +objectClass: olcConfig +objectClass: olcOverlayConfig +objectClass: olcPPolicyConfig +olcOverlay: ppolicy +olcPPolicyDefault: ${LDAP_INIT_PPOLICY_DEFAULT_DN} +olcPPolicyHashCleartext: TRUE +# return AccountLocked instead of InvalidCredentials error: +olcPPolicyUseLockout: TRUE +olcPPolicyForwardUpdates: FALSE diff --git a/image/ldifs/init_module_unique.ldif b/image/ldifs/init_module_unique.ldif new file mode 100644 index 0000000..fe6293a --- /dev/null +++ b/image/ldifs/init_module_unique.ldif @@ -0,0 +1,18 @@ +version: 1 + +dn: cn=module{0},cn=config +changetype: modify +add: olcModuleLoad +olcModuleLoad: unique + +dn: olcOverlay=unique,olcDatabase={1}mdb,cn=config +objectClass: top +objectClass: olcConfig +objectClass: olcOverlayConfig +objectClass: olcUniqueConfig +olcOverlay: unique +olcUniqueURI: ldap:///${LDAP_INIT_ORG_DN}?cn?sub +olcUniqueURI: ldap:///${LDAP_INIT_ORG_DN}?uid?sub +olcUniqueURI: ldap:///ou=Users,${LDAP_INIT_ORG_DN}?mail?sub +olcUniqueURI: ldap:///ou=Users,${LDAP_INIT_ORG_DN}?gidNumber?sub +olcUniqueURI: ldap:///ou=Users,${LDAP_INIT_ORG_DN}?uidNumber?sub diff --git a/image/ldifs/init_org_entries.ldif b/image/ldifs/init_org_entries.ldif new file mode 100644 index 0000000..0236c2c --- /dev/null +++ b/image/ldifs/init_org_entries.ldif @@ -0,0 +1,95 @@ +version: 1 + +############################## +# Internal Users +############################## +dn: uid=employee1,ou=Internal,ou=Users,${LDAP_INIT_ORG_DN} +ou: Internal +ou: Users +description: Employee 1 +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: ldapPublicKey +uid: employee1 +cn: Employee1 +sn: Employee1 +givenName: Employee1 +mail: employee1@example.com +userPassword: changeit + + +############################## +# External Users +############################## +dn: uid=guest1,ou=External,ou=Users,${LDAP_INIT_ORG_DN} +ou: External +ou: Users +description: Guest 1 +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +uid: guest1 +cn: Guest1 +sn: Guest1 +givenName: Guest1 +mail: guest1@example.com +userPassword: changeit + + +############################## +# Technical Users +############################## +dn: uid=machine1,ou=TechnicalAccounts,ou=Users,${LDAP_INIT_ORG_DN} +ou: External +ou: Users +description: Machine 1 +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: ldapPublicKey +uid: machine1 +cn: Machine1 +sn: Machine1 +givenName: Machine1 +mail: machine1@example.com +userPassword: changeit + + +############################## +# Groups +############################## +dn: cn=ldapadmins,ou=Groups,${LDAP_INIT_ORG_DN} +ou: Groups +objectClass: top +objectClass: groupOfUniqueNames +cn: ldapadmins +description: Ldap Admin Group +uniqueMember: uid=employee1,ou=External,ou=Users,${LDAP_INIT_ORG_DN} + +dn: cn=employees,ou=Groups,${LDAP_INIT_ORG_DN} +ou: Groups +objectClass: top +objectClass: groupOfUniqueNames +cn: employees +description: Employees Group +uniqueMember: uid=employee1,ou=External,ou=Users,${LDAP_INIT_ORG_DN} + +dn: cn=guests,ou=Groups,${LDAP_INIT_ORG_DN} +ou: Groups +objectClass: top +objectClass: groupOfUniqueNames +cn: guests +description: Guests Group +uniqueMember: uid=guest1,ou=External,ou=Users,${LDAP_INIT_ORG_DN} + +dn: cn=machines,ou=Groups,${LDAP_INIT_ORG_DN} +ou: Groups +objectClass: top +objectClass: groupOfUniqueNames +cn: machines +description: Machines Group +uniqueMember: uid=machine1,ou=TechnicalAccounts,ou=Users,${LDAP_INIT_ORG_DN} diff --git a/image/ldifs/init_org_ppolicy.ldif b/image/ldifs/init_org_ppolicy.ldif new file mode 100644 index 0000000..32bd152 --- /dev/null +++ b/image/ldifs/init_org_ppolicy.ldif @@ -0,0 +1,40 @@ +version: 1 + +############################## +# Password Policy +############################## +dn: ou=Policies,${LDAP_INIT_ORG_DN} +ou: Policies +objectClass: top +objectClass: organizationalUnit + +dn: cn=DefaultPasswordPolicy,ou=Policies,${LDAP_INIT_ORG_DN} +objectClass: top +objectClass: device +objectClass: pwdPolicy +objectClass: pwdPolicyChecker +cn: DefaultPasswordPolicy +pwdAttribute: userPassword +# 0=reset count of consecutive password failures only on successful authentication +pwdFailureCountInterval: 0 +# maximum number of consecutive password failures locking the account +pwdMaxFailure: ${LDAP_INIT_PPOLICY_MAX_FAILURES:-3} +pwdMinAge: 0 +pwdMustChange: TRUE +# if TRUE user must send current password during password modification operation +pwdSafeModify: FALSE +# number of passwords maintained in a list of previously used passwords +pwdInHistory: 0 +# number of further bind operations after a password has expired +pwdGraceAuthNLimit: 0 +# account lock duration in seconds (300=5min) +pwdLockoutDuration: ${LDAP_INIT_PPOLICY_LOCKOUT_DURATION:-300} +pwdAllowUserChange: TRUE +#0=do not show expiration warning on bind +pwdExpireWarning: 0 +pwdLockout: TRUE +pwdMaxAge: 0 +# Password Quality +pwdMinLength: ${LDAP_INIT_PPOLICY_PW_MIN_LENGTH:-8} +pwdCheckQuality: 2 +pwdCheckModule: /usr/lib/ldap/pqchecker.so diff --git a/image/ldifs/init_org_tree.ldif b/image/ldifs/init_org_tree.ldif new file mode 100644 index 0000000..88692c1 --- /dev/null +++ b/image/ldifs/init_org_tree.ldif @@ -0,0 +1,43 @@ +version: 1 + +############################## +# LDAP tree definition +############################## +dn: ${LDAP_INIT_ORG_DN} +description: ${LDAP_INIT_ORG_NAME} +objectClass: top +objectClass: organization +${LDAP_INIT_ORG_DN_ATTR} + +dn: ou=Groups,${LDAP_INIT_ORG_DN} +ou: Groups +description: Groups +objectClass: top +objectClass: organizationalUnit + +dn: ou=Users,${LDAP_INIT_ORG_DN} +ou: Users +description: Users Accounts +objectClass: top +objectClass: organizationalUnit + +dn: ou=Internal,ou=Users,${LDAP_INIT_ORG_DN} +ou: Internal +ou: Users +description: Internal Users (Employees) +objectClass: top +objectClass: organizationalUnit + +dn: ou=External,ou=Users,${LDAP_INIT_ORG_DN} +ou: External +ou: Users +description: External Users (Contractors, Customers) +objectClass: top +objectClass: organizationalUnit + +dn: ou=TechnicalAccounts,ou=Users,${LDAP_INIT_ORG_DN} +ou: TechnicalAccounts +ou: Users +description: Technical Accounts +objectClass: top +objectClass: organizationalUnit diff --git a/image/ldifs/schema_ldapPublicKey.ldif b/image/ldifs/schema_ldapPublicKey.ldif new file mode 100644 index 0000000..fd55679 --- /dev/null +++ b/image/ldifs/schema_ldapPublicKey.ldif @@ -0,0 +1,15 @@ +dn: cn=openssh-lpk,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: openssh-lpk +olcAttributeTypes: ( 1.3.6.1.4.1.24552.500.1.1.1.13 + NAME 'sshPublicKey' + DESC 'MANDATORY: OpenSSH Public key' + EQUALITY octetStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 + ) +olcObjectClasses: ( 1.3.6.1.4.1.24552.500.1.1.2.0 + NAME 'ldapPublicKey' + DESC 'MANDATORY: OpenSSH LPK objectclass' + SUP top + AUXILIARY MAY ( sshPublicKey $ uid ) + ) diff --git a/image/ldifs/schema_rfc2307bis02.ldif b/image/ldifs/schema_rfc2307bis02.ldif new file mode 100644 index 0000000..67ebdc0 --- /dev/null +++ b/image/ldifs/schema_rfc2307bis02.ldif @@ -0,0 +1,53 @@ +# https://tools.ietf.org/html/draft-howard-rfc2307bis-02 +dn: cn=rfc2307bis,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: rfc2307bis +olcAttributeTypes: ( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field; the common name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' DESC 'The absolute path to the home directory' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.4 NAME 'loginShell' DESC 'The path to the login shell' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.6 NAME 'shadowMin' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.7 NAME 'shadowMax' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.8 NAME 'shadowWarning' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.9 NAME 'shadowInactive' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.10 NAME 'shadowExpire' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.11 NAME 'shadowFlag' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.12 NAME 'memberUid' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' DESC 'Netgroup triple' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.15 NAME 'ipServicePort' DESC 'Service port number' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol' DESC 'Service protocol name' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber' DESC 'IP protocol number' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber' DESC 'ONC RPC number' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber' DESC 'IPv4 addresses as a dotted decimal omitting leading zeros or IPv6 addresses as defined in RFC2373' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber' DESC 'IP network omitting leading zeros, eg. 192.168' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber' DESC 'IP netmask omitting leading zeros, eg. 255.255.255.0' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.22 NAME 'macAddress' DESC 'MAC address in maximal, colon separated hex notation, eg. 00:00:92:90:ee:e2' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.23 NAME 'bootParameter' DESC 'rpc.bootparamd parameter' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.24 NAME 'bootFile' DESC 'Boot image name' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.26 NAME 'nisMapName' DESC 'Name of a generic NIS map' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{64} ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry' DESC 'A generic NIS entry' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.28 NAME 'nisPublicKey' DESC 'NIS public key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.29 NAME 'nisSecretKey' DESC 'NIS secret key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.30 NAME 'nisDomain' DESC 'NIS domain' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.31 NAME 'automountMapName' DESC 'Automount Map Name' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.32 NAME 'automountKey' DESC 'Automount Key value' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.33 NAME 'automountInformation' DESC 'Automount information' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcObjectClasses: ( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction of an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $ description ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.1 NAME 'shadowAccount' DESC 'Additional attributes for shadow passwords' SUP top AUXILIARY MUST uid MAY ( userPassword $ description $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive $ shadowExpire $ shadowFlag ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of a group of accounts' SUP top AUXILIARY MUST gidNumber MAY ( userPassword $ memberUid $ description ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.3 NAME 'ipService' DESC 'Abstraction an Internet Protocol service. Maps an IP port and protocol (such as tcp or udp) to one or more names; the distinguished value of the cn attribute denotes the services canonical name' SUP top STRUCTURAL MUST ( cn $ ipServicePort $ ipServiceProtocol ) MAY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.4 NAME 'ipProtocol' DESC 'Abstraction of an IP protocol. Maps a protocol number to one or more names. The distinguished value of the cn attribute denotes the protocol canonical name' SUP top STRUCTURAL MUST ( cn $ ipProtocolNumber ) MAY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.5 NAME 'oncRpc' DESC 'Abstraction of an Open Network Computing (ONC) [RFC1057] Remote Procedure Call (RPC) binding. This class maps an ONC RPC number to a name. The distinguished value of the cn attribute denotes the RPC service canonical name' SUP top STRUCTURAL MUST ( cn $ oncRpcNumber ) MAY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.6 NAME 'ipHost' DESC 'Abstraction of a host, an IP device. The distinguished value of the cn attribute denotes the hosts canonical name. Device SHOULD be used as a structural class' SUP top AUXILIARY MUST ( cn $ ipHostNumber ) MAY ( userPassword $ l $ description $ manager ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.7 NAME 'ipNetwork' DESC 'Abstraction of a network. The distinguished value of the cn attribute denotes the network canonical name' SUP top STRUCTURAL MUST ipNetworkNumber MAY ( cn $ ipNetmaskNumber $ l $ description $ manager ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup' DESC 'Abstraction of a netgroup. May refer to other netgroups' SUP top STRUCTURAL MUST cn MAY ( nisNetgroupTriple $ memberNisNetgroup $ description ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.9 NAME 'nisMap' DESC 'A generic abstraction of a NIS map' SUP top STRUCTURAL MUST nisMapName MAY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.10 NAME 'nisObject' DESC 'An entry in a NIS map' SUP top STRUCTURAL MUST ( cn $ nisMapEntry $ nisMapName ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.11 NAME 'ieee802Device' DESC 'A device with a MAC address; device SHOULD be used as a structural class' SUP top AUXILIARY MAY macAddress ) +olcObjectClasses: ( 1.3.6.1.1.1.2.12 NAME 'bootableDevice' DESC 'A device with boot parameters; device SHOULD be used as a structural class' SUP top AUXILIARY MAY ( bootFile $ bootParameter ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.14 NAME 'nisKeyObject' DESC 'An object with a public and secret key' SUP top AUXILIARY MUST ( cn $ nisPublicKey $ nisSecretKey ) MAY ( uidNumber $ description ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.15 NAME 'nisDomainObject' DESC 'Associates a NIS domain with a naming context' SUP top AUXILIARY MUST nisDomain ) +olcObjectClasses: ( 1.3.6.1.1.1.2.16 NAME 'automountMap' SUP top STRUCTURAL MUST automountMapName MAY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.17 NAME 'automount' DESC 'Automount information' SUP top STRUCTURAL MUST ( automountKey $ automountInformation ) MAY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.18 NAME 'groupOfMembers' DESC 'A group with members (DNs)' SUP top STRUCTURAL MUST cn MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description $ member ) ) diff --git a/image/ldifs/schema_sudo.ldif b/image/ldifs/schema_sudo.ldif new file mode 100644 index 0000000..3729e62 --- /dev/null +++ b/image/ldifs/schema_sudo.ldif @@ -0,0 +1,11 @@ +dn: cn=sudo,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: sudo +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.1 NAME 'sudoUser' DESC 'User(s) who may run sudo' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.2 NAME 'sudoHost' DESC 'Host(s) who may run sudo' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.3 NAME 'sudoCommand' DESC 'Command(s) to be executed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.4 NAME 'sudoRunAs' DESC 'User(s) impersonated by sudo (deprecated)' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.5 NAME 'sudoOption' DESC 'Options(s) followed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.6 NAME 'sudoRunAsUser' DESC 'User(s) impersonated by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.7 NAME 'sudoRunAsGroup' DESC 'Group(s) impersonated by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcObjectClasses: ( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' DESC 'Sudoer Entries' SUP top STRUCTURAL MUST cn MAY ( sudoUser $ sudoHost $ sudoCommand $ sudoRunAs $ sudoRunAsUser $ sudoRunAsGroup $ sudoOption $ description ) ) diff --git a/image/run.sh b/image/run.sh new file mode 100644 index 0000000..095be59 --- /dev/null +++ b/image/run.sh @@ -0,0 +1,238 @@ +#!/usr/bin/env bash +# +# Copyright 2019-2020 by Vegard IT GmbH, Germany, https://vegardit.com +# SPDX-License-Identifier: Apache-2.0 +# +# @author Sebastian Thomschke, Vegard IT GmbH +# +# https://github.com/vegardit/docker-openldap +# + +set -e -u +if [ -z "${BASH_VERSINFO:-}" ]; then /usr/bin/env bash "$0" "$@"; exit; fi +set -o pipefail +trap 'echo >&2 "$(date +%H:%M:%S) Error - exited with status $? at line $LINENO:"; pr -tn $0 | tail -n+$((LINENO - 3)) | head -n7' ERR + +if [ "${DEBUG_RUN_SH:-}" == "1" ]; then + set -x +fi + +cat <<'EOF' + _ __ __ __________ +| | / /__ ____ _____ __________/ / / _/_ __/ +| | / / _ \/ __ `/ __ `/ ___/ __ / / / / / +| |/ / __/ /_/ / /_/ / / / /_/ / _/ / / / +|___/\___/\__, /\__,_/_/ \__,_/ /___/ /_/ + /____/ + +EOF + +cat /opt/build_info +echo + +if [ -f "$INIT_SH_FILE" ]; then + source "$INIT_SH_FILE" +fi + + +function log() { + if [ -p /dev/stdin ]; then + while read line; do + echo "[$(date "+%Y-%m-%d %H:%M:%S") ${BASH_SOURCE}:${BASH_LINENO}] $line" + done + else + echo "[$(date "+%Y-%m-%d %H:%M:%S") ${BASH_SOURCE}:${BASH_LINENO}] ${@}" + fi +} + + +# display slapd build info +slapd -VVV 2>&1 | log || true + + +# Limit maximum number of open file descriptors otherwise slapd consumes two +# orders of magnitude more of RAM, see https://github.com/docker/docker/issues/8231 +ulimit -n $LDAP_NOFILE_LIMIT + + +################################################################# +# Adjust UID/GID and file permissions based on env var config +################################################################# +if [ -n "${LDAP_OPENLDAP_UID:-}" ]; then + effective_uid=$(id -u openldap) + if [ "$LDAP_OPENLDAP_UID" != "$effective_uid" ]; then + log "Changing UID of openldap user from $effective_uid to $LDAP_OPENLDAP_UID..." + usermod -o -u "$LDAP_OPENLDAP_UID" openldap + fi +fi +if [ -n "${LDAP_OPENLDAP_GID:-}" ]; then + effective_gid=$(id -g openldap) + if [ "$LDAP_OPENLDAP_GID" != "$effective_gid" ]; then + log "Changing GID of openldap user from $effective_gid to $LDAP_OPENLDAP_GID..." + usermod -o -u "$LDAP_OPENLDAP_GID" openldap + fi +fi +chown -R openldap:openldap /etc/ldap +chown -R openldap:openldap /var/lib/ldap +chown -R openldap:openldap /var/lib/ldap_orig +chown -R openldap:openldap /var/run/slapd + + +################################################################# +# Configure LDAP server on initial container launch +################################################################# +if [ ! -e /etc/ldap/slapd.d/initialized ]; then + + function interpolate_vars() { + # https://stackoverflow.com/a/40167919 + local line lineEscaped + while IFS= read -r line || [ -n "$line" ]; do # the `||` clause ensures that the last line is read even if it doesn't end with \n + # escape all chars that could trigger an expansion + IFS= read -r lineEscaped < <(echo "$line" | tr '`([$' '\1\2\3\4') + # selectively re-enable ${ references + lineEscaped=${lineEscaped//$'\4'{/\${} + # escape embedded double quotes to preserve them + lineEscaped=${lineEscaped//\"/\\\"} + eval "printf '%s\n' \"$lineEscaped\"" | tr '\1\2\3\4' '`([$' + done + } + + function substr_before() { + echo "${1%%$2*}" + } + + function str_replace() { + IFS= read -r -d $'\0' str + echo "${str/$1/$2}" + } + + function ldif() { + log "--------------------------------------------" + local action=$1 && shift + local file=${!#} + log "Loading [$file]..." + interpolate_vars < $file > /tmp/$(basename $file) + ldap$action -H ldapi:/// "${@:1:${#}-1}" -f /tmp/$(basename $file) + } + + # interpolate variable placeholders in env vars starting with "LDAP_INIT_" + for name in ${!LDAP_INIT_*}; do + declare "${name}=$(echo "${!name}" | interpolate_vars)" + done + + # pre-populate folders in case they are empty + for folder in "/var/lib/ldap" "/etc/ldap/slapd.d"; do + if [ "$folder" -ef "${folder}_orig" ]; then + continue + fi + if [ -z "$(ls $folder)" ]; then + log "Initializing [$folder]..." + cp -r --preserve=all ${folder}_orig/. $folder + fi + done + + LDAP_INIT_ROOT_USER_PW_HASHED=$(slappasswd -s "${LDAP_INIT_ROOT_USER_PW}") + /etc/init.d/slapd start + sleep 3 + + if [ "${LDAP_INIT_RFC2307BIS_SCHEMA:-}" == "1" ]; then + log "Replacing NIS (RFC2307) schema with RFC2307bis schema..." + ldapdelete -Y EXTERNAL cn={2}nis,cn=schema,cn=config + ldif add -Y EXTERNAL /opt/ldifs/schema_rfc2307bis02.ldif + fi + + ldif add -Y EXTERNAL /etc/ldap/schema/ppolicy.ldif + ldif add -Y EXTERNAL /opt/ldifs/schema_sudo.ldif + ldif add -Y EXTERNAL /opt/ldifs/schema_ldapPublicKey.ldif + + ldif modify -Y EXTERNAL /opt/ldifs/init_frontend.ldif + ldif add -Y EXTERNAL /opt/ldifs/init_module_memberof.ldif + ldif modify -Y EXTERNAL /opt/ldifs/init_mdb.ldif + ldif modify -Y EXTERNAL /opt/ldifs/init_mdb_acls.ldif + ldif modify -Y EXTERNAL /opt/ldifs/init_mdb_indexes.ldif + ldif add -Y EXTERNAL /opt/ldifs/init_module_unique.ldif + ldif add -Y EXTERNAL /opt/ldifs/init_module_ppolicy.ldif + + LDAP_INIT_ORG_DN_ATTR=$(substr_before $LDAP_INIT_ORG_DN "," | str_replace "=" ": ") # referenced by init_org_tree.ldif + ldif add -x -D "$LDAP_INIT_ROOT_USER_DN" -w "$LDAP_INIT_ROOT_USER_PW" /opt/ldifs/init_org_tree.ldif + ldif add -x -D "$LDAP_INIT_ROOT_USER_DN" -w "$LDAP_INIT_ROOT_USER_PW" /opt/ldifs/init_org_ppolicy.ldif + ldif add -x -D "$LDAP_INIT_ROOT_USER_DN" -w "$LDAP_INIT_ROOT_USER_PW" /opt/ldifs/init_org_entries.ldif + + log "--------------------------------------------" + + echo "1" > /etc/ldap/slapd.d/initialized + rm -f /tmp/*.ldif + + log "Creating periodic LDAP backup at [$LDAP_BACKUP_FILE]..." + slapcat -n 1 -l $LDAP_BACKUP_FILE || true + + /etc/init.d/slapd stop + sleep 3 +fi + +echo "$LDAP_PPOLICY_PQCHECKER_RULE" > /etc/ldap/pqchecker/pqparams.dat + + +################################################################# +# Configure LDAP backup task +################################################################# +if [ -n "${LDAP_BACKUP_TIME:-}" ]; then + log "--------------------------------------------" + log "Configuring LDAP backup task to run daily: time=[${LDAP_BACKUP_TIME}] file=[$LDAP_BACKUP_FILE]..." + function backup_ldap() { + while true; do + while [ "$(date +%H:%M)" != "${LDAP_BACKUP_TIME}" ]; do + sleep 10s + done + log "Creating periodic LDAP backup at [$LDAP_BACKUP_FILE]..." + slapcat -n 1 -l $LDAP_BACKUP_FILE || true + sleep 23h + done + } + + backup_ldap & +fi + + +################################################################# +# Start LDAP service +################################################################# +log "--------------------------------------------" +log "Starting OpenLDAP: slapd..." + +#required for propagating SIGTERM from docker to service process +#https://unix.stackexchange.com/questions/146756/forward-sigterm-to-child-in-bash/444676#444676 +function trap_handler() { + local signal=$1 + if [ -n "${service_process_pid:-}" ]; then + log "Sending [$signal] to PID [$service_process_pid] ..." + kill -s $signal $service_process_pid 2>/dev/null + else + log "Received [$signal] before service started." + sig_received_before_service_started=$signal + fi +} + +trap 'trap_handler TERM' SIGTERM SIGINT SIGHUP # https://github.com/openldap/openldap/search?q=SIGTERM +trap 'trap_handler QUIT' SIGQUIT +trap 'trap_handler USR1' SIGUSR1 # https://github.com/openldap/openldap/search?q=SIGUSR1 +trap 'trap_handler USR2' SIGUSR2 # https://github.com/openldap/openldap/search?q=SIGUSR2 + +/usr/sbin/slapd \ + $(for logLevel in ${LDAP_LOG_LEVELS:-}; do echo -n "-d $logLevel "; done) \ + -h "ldap:/// ldapi:///" \ + -u openldap -g openldap \ + -F /etc/ldap/slapd.d 2>&1 | log & + +service_process_pid=$(jobs -p | tail -1) +log "OpenLDAP PID: $service_process_pid" + +if [ -n "${sig_received_before_service_started:-}" ]; then + kill -s $sig_received_before_service_started $service_process_pid 2>/dev/null +fi + +wait $service_process_pid +trap - TERM INT +wait $service_process_pid +exit_status=$? +exit $exit_status