mirror of
https://github.com/lldap/lldap.git
synced 2024-11-25 09:06:03 +00:00
Add support for bootstrapping schemas (#991)
* Moved default bootstrap dirs into single /bootstrap parent dir in order to have single docker volume bind (with fallback to previous folder hierarchy) * Added default values for LDAP user and credentials * Added support for bootstrapping schema Place schema files under /bootstrap/(user|group)-schemas/*.json Sample content: [ { "name" : "test_attrib", "attributeType" : "STRING", "isEditable" : true, "isList" : false, "isVisible" : true } ]
This commit is contained in:
parent
a6eac55fc7
commit
dcb45d4f6b
2 changed files with 194 additions and 18 deletions
|
@ -1,4 +1,4 @@
|
|||
# Bootstrapping lldap using [bootstrap.sh](bootstrap.sh) script
|
||||
# Bootstrapping lldap using [bootstrap.sh](/scripts/bootstrap.sh) script
|
||||
|
||||
bootstrap.sh allows managing your lldap in a git-ops, declarative way using JSON config files.
|
||||
|
||||
|
@ -12,7 +12,7 @@ The script can:
|
|||
* create groups
|
||||
* delete redundant users and groups (when `DO_CLEANUP` env var is true)
|
||||
* maintain the desired state described in JSON config files
|
||||
|
||||
* create user/group user-defined attributes
|
||||
|
||||
![](bootstrap-example-log-1.jpeg)
|
||||
|
||||
|
@ -27,11 +27,13 @@ The script can:
|
|||
|
||||
## Environment variables
|
||||
|
||||
- `LLDAP_URL` or `LLDAP_URL_FILE` - URL to your lldap instance or path to file that contains URL (**MANDATORY**)
|
||||
- `LLDAP_ADMIN_USERNAME` or `LLDAP_ADMIN_USERNAME_FILE` - admin username or path to file that contains username (**MANDATORY**)
|
||||
- `LLDAP_ADMIN_PASSWORD` or `LLDAP_ADMIN_PASSWORD_FILE` - admin password or path to file that contains password (**MANDATORY**)
|
||||
- `USER_CONFIGS_DIR` (default value: `/user-configs`) - directory where the user JSON configs could be found
|
||||
- `GROUP_CONFIGS_DIR` (default value: `/group-configs`) - directory where the group JSON configs could be found
|
||||
- `LLDAP_URL` or `LLDAP_URL_FILE` (default value: `http://localhost:17170`) - URL to your lldap instance or path to file that contains URL
|
||||
- `LLDAP_ADMIN_USERNAME` or `LLDAP_ADMIN_USERNAME_FILE` (default value: `admin`) - admin username or path to file that contains username
|
||||
- `LLDAP_ADMIN_PASSWORD` or `LLDAP_ADMIN_PASSWORD_FILE` (default value: `password`) - admin password or path to file that contains password
|
||||
- `USER_CONFIGS_DIR` (default value: `/bootstrap/user-configs`) - directory where the user JSON configs could be found
|
||||
- `GROUP_CONFIGS_DIR` (default value: `/bootstrap/group-configs`) - directory where the group JSON configs could be found
|
||||
- `USER_SCHEMAS_DIR` (default value: `/bootstrap/user-schemas`) - directory where the user schema JSON configs could be found
|
||||
- `GROUP_SCHEMAS_DIR` (default value: `/bootstrap/group-schemas`) - directory where the group schema JSON configs could be found
|
||||
- `LLDAP_SET_PASSWORD_PATH` - path to the `lldap_set_password` utility (default value: `/app/lldap_set_password`)
|
||||
- `DO_CLEANUP` (default value: `false`) - delete groups and users not specified in config files, also remove users from groups that they do not belong to
|
||||
|
||||
|
@ -96,6 +98,44 @@ Fields description:
|
|||
|
||||
```
|
||||
|
||||
### User and group schema config file example
|
||||
|
||||
User and group schema have the same structure.
|
||||
|
||||
Fields description:
|
||||
|
||||
* `name`: name of field, case insensitve - you should use lowercase
|
||||
* `attributeType`: `STRING` / `INTEGER` / `JPEG` / `DATE_TIME`
|
||||
* `isList`: single on multiple value field
|
||||
* `isEditable`: self-explanatory
|
||||
* `isVisible`: self-explanatory
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "uid",
|
||||
"attributeType": "INTEGER",
|
||||
"isEditable": false,
|
||||
"isList": false,
|
||||
"isVisible": true
|
||||
},
|
||||
{
|
||||
"name": "mailbox",
|
||||
"attributeType": "STRING",
|
||||
"isEditable": false,
|
||||
"isList": false,
|
||||
"isVisible": true
|
||||
},
|
||||
{
|
||||
"name": "mail_alias",
|
||||
"attributeType": "STRING",
|
||||
"isEditable": false,
|
||||
"isList": true,
|
||||
"isVisible": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Usage example
|
||||
|
||||
### Manually
|
||||
|
@ -110,11 +150,21 @@ export LLDAP_ADMIN_USERNAME=admin
|
|||
export LLDAP_ADMIN_PASSWORD=changeme
|
||||
export USER_CONFIGS_DIR="$(realpath ./configs/user)"
|
||||
export GROUP_CONFIGS_DIR="$(realpath ./configs/group)"
|
||||
export USER_SCHEMAS_DIR="$(realpath ./configs/user-schema)"
|
||||
export GROUP_SCHEMAS_DIR="$(realpath ./configs/group-schema)"
|
||||
export LLDAP_SET_PASSWORD_PATH="$(realpath ./lldap_set_password)"
|
||||
export DO_CLEANUP=false
|
||||
./bootstrap.sh
|
||||
```
|
||||
|
||||
### Manually from running docker container or service
|
||||
|
||||
After setting a docker container you can bootstrap users using:
|
||||
|
||||
```
|
||||
docker exec -e LLDAP_ADMIN_PASSWORD_FILE=password -v ./bootstrap:/bootstrap -it $(docker ps --filter name=lldap -q) /app/bootstrap.sh
|
||||
```
|
||||
|
||||
### Docker compose
|
||||
|
||||
Let's suppose you have the next file structure:
|
||||
|
@ -129,10 +179,17 @@ Let's suppose you have the next file structure:
|
|||
│ ├─ ...
|
||||
│ └─ user-n.json
|
||||
└─ group-configs
|
||||
├─ group-1.json
|
||||
| ├─ group-1.json
|
||||
| ├─ ...
|
||||
| └─ group-n.json
|
||||
└─ user-schemas
|
||||
| ├─ user-attrs-1.json
|
||||
| ├─ ...
|
||||
| └─ user-attrs-n.json
|
||||
└─ group-schemas
|
||||
├─ group-attrs-1.json
|
||||
├─ ...
|
||||
└─ group-n.json
|
||||
|
||||
└─ group-attrs-n.json
|
||||
```
|
||||
|
||||
You should mount `bootstrap` dir to lldap container and set the corresponding `env` variables:
|
||||
|
@ -160,6 +217,8 @@ services:
|
|||
- LLDAP_ADMIN_PASSWORD=changeme # same as LLDAP_LDAP_USER_PASS
|
||||
- USER_CONFIGS_DIR=/bootstrap/user-configs
|
||||
- GROUP_CONFIGS_DIR=/bootstrap/group-configs
|
||||
- USER_SCHEMAS_DIR=/bootstrap/user-schemas
|
||||
- GROUP_SCHEMAS_DIR=/bootstrap/group-schemas
|
||||
- DO_CLEANUP=false
|
||||
```
|
||||
|
||||
|
@ -205,14 +264,15 @@ spec:
|
|||
volumeMounts:
|
||||
- name: bootstrap
|
||||
mountPath: /bootstrap/bootstrap.sh
|
||||
readOnly: true
|
||||
subPath: bootstrap.sh
|
||||
|
||||
- name: user-configs
|
||||
mountPath: /user-configs
|
||||
mountPath: /bootstrap/user-configs
|
||||
readOnly: true
|
||||
|
||||
- name: group-configs
|
||||
mountPath: /group-configs
|
||||
mountPath: /bootstrap/group-configs
|
||||
readOnly: true
|
||||
|
||||
volumes:
|
||||
|
|
|
@ -3,14 +3,24 @@
|
|||
set -e
|
||||
set -o pipefail
|
||||
|
||||
LLDAP_URL="${LLDAP_URL}"
|
||||
LLDAP_ADMIN_USERNAME="${LLDAP_ADMIN_USERNAME}"
|
||||
LLDAP_ADMIN_PASSWORD="${LLDAP_ADMIN_PASSWORD}"
|
||||
USER_CONFIGS_DIR="${USER_CONFIGS_DIR:-/user-configs}"
|
||||
GROUP_CONFIGS_DIR="${GROUP_CONFIGS_DIR:-/group-configs}"
|
||||
LLDAP_URL="${LLDAP_URL:-http://localhost:17170}"
|
||||
LLDAP_ADMIN_USERNAME="${LLDAP_ADMIN_USERNAME:-admin}"
|
||||
LLDAP_ADMIN_PASSWORD="${LLDAP_ADMIN_PASSWORD:-password}"
|
||||
USER_SCHEMAS_DIR="${USER_SCHEMAS_DIR:-/bootstrap/user-schemas}"
|
||||
GROUP_SCHEMAS_DIR="${GROUP_SCHEMAS_DIR:-/bootstrap/group-schemas}"
|
||||
USER_CONFIGS_DIR="${USER_CONFIGS_DIR:-/bootstrap/user-configs}"
|
||||
GROUP_CONFIGS_DIR="${GROUP_CONFIGS_DIR:-/bootstrap/group-configs}"
|
||||
LLDAP_SET_PASSWORD_PATH="${LLDAP_SET_PASSWORD_PATH:-/app/lldap_set_password}"
|
||||
DO_CLEANUP="${DO_CLEANUP:-false}"
|
||||
|
||||
# Fallback to support legacy defaults
|
||||
if [[ ! -d $USER_CONFIGS_DIR ]] && [[ -d "/user-configs" ]]; then
|
||||
USER_CONFIGS_DIR="/user-configs"
|
||||
fi
|
||||
if [[ ! -d $GROUP_CONFIGS_DIR ]] && [[ -d "/group-configs" ]]; then
|
||||
GROUP_CONFIGS_DIR="/group-configs"
|
||||
fi
|
||||
|
||||
check_install_dependencies() {
|
||||
local commands=('curl' 'jq' 'jo')
|
||||
local commands_not_found='false'
|
||||
|
@ -280,6 +290,80 @@ delete_user() {
|
|||
fi
|
||||
}
|
||||
|
||||
get_group_property_list() {
|
||||
local query='{"query":"query GetGroupAttributesSchema { schema { groupSchema { attributes { name }}}}","operationName":"GetGroupAttributesSchema"}'
|
||||
make_query <(printf '%s' "$query") <(printf '{}')
|
||||
}
|
||||
group_property_exists() {
|
||||
if [[ "$(get_group_property_list | jq --raw-output --arg name "$1" '.data.schema.groupSchema.attributes | any(.[]; select(.name == $name))')" == 'true' ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
create_group_schema_property() {
|
||||
local name="$1"
|
||||
local attributeType="$2"
|
||||
local isEditable="$3"
|
||||
local isList="$4"
|
||||
local isVisible="$5"
|
||||
|
||||
if group_property_exists "$name"; then
|
||||
printf 'Group property "%s" already exists\n' "$name"
|
||||
return
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
local query='{"query":"mutation CreateGroupAttribute($name: String!, $attributeType: AttributeType!, $isList: Boolean!, $isVisible: Boolean!, $isEditable: Boolean!) {addGroupAttribute(name: $name, attributeType: $attributeType, isList: $isList, isVisible: $isVisible, isEditable: $isEditable) {ok}}","operationName":"CreateGroupAttribute"}'
|
||||
|
||||
local response='' error=''
|
||||
response="$(make_query <(printf '%s' "$query") <(jo -- name="$name" attributeType="$attributeType" isEditable="$isEditable" isList="$isList" isVisible="$isVisible"))"
|
||||
error="$(printf '%s' "$response" | jq --raw-output '.errors | if . != null then .[].message else empty end')"
|
||||
if [[ -n "$error" ]]; then
|
||||
printf '%s\n' "$error"
|
||||
else
|
||||
printf 'Group attribute "%s" successfully created\n' "$name"
|
||||
fi
|
||||
}
|
||||
|
||||
get_user_property_list() {
|
||||
local query='{"query":"query GetUserAttributesSchema { schema { userSchema { attributes { name }}}}","operationName":"GetUserAttributesSchema"}'
|
||||
make_query <(printf '%s' "$query") <(printf '{}')
|
||||
}
|
||||
user_property_exists() {
|
||||
if [[ "$(get_user_property_list | jq --raw-output --arg name "$1" '.data.schema.userSchema.attributes | any(.[]; select(.name == $name))')" == 'true' ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
create_user_schema_property() {
|
||||
local name="$1"
|
||||
local attributeType="$2"
|
||||
local isEditable="$3"
|
||||
local isList="$4"
|
||||
local isVisible="$5"
|
||||
|
||||
if user_property_exists "$name"; then
|
||||
printf 'User property "%s" already exists\n' "$name"
|
||||
return
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
local query='{"query":"mutation CreateUserAttribute($name: String!, $attributeType: AttributeType!, $isList: Boolean!, $isVisible: Boolean!, $isEditable: Boolean!) {addUserAttribute(name: $name, attributeType: $attributeType, isList: $isList, isVisible: $isVisible, isEditable: $isEditable) {ok}}","operationName":"CreateUserAttribute"}'
|
||||
|
||||
local response='' error=''
|
||||
response="$(make_query <(printf '%s' "$query") <(jo -- name="$name" attributeType="$attributeType" isEditable="$isEditable" isList="$isList" isVisible="$isVisible"))"
|
||||
error="$(printf '%s' "$response" | jq --raw-output '.errors | if . != null then .[].message else empty end')"
|
||||
if [[ -n "$error" ]]; then
|
||||
printf '%s\n' "$error"
|
||||
else
|
||||
printf 'User attribute "%s" successfully created\n' "$name"
|
||||
fi
|
||||
}
|
||||
|
||||
__common_user_mutation_query() {
|
||||
local \
|
||||
query="$1" \
|
||||
|
@ -387,8 +471,18 @@ main() {
|
|||
|
||||
local user_config_files=("${USER_CONFIGS_DIR}"/*.json)
|
||||
local group_config_files=("${GROUP_CONFIGS_DIR}"/*.json)
|
||||
local user_schema_files=()
|
||||
local group_schema_files=()
|
||||
|
||||
if ! check_configs_validity "${group_config_files[@]}" "${user_config_files[@]}"; then
|
||||
local file=''
|
||||
[[ -d "$USER_SCHEMAS_DIR" ]] && for file in "${USER_SCHEMAS_DIR}"/*.json; do
|
||||
user_schema_files+=("$file")
|
||||
done
|
||||
[[ -d "$GROUP_SCHEMAS_DIR" ]] && for file in "${GROUP_SCHEMAS_DIR}"/*.json; do
|
||||
group_schema_files+=("$file")
|
||||
done
|
||||
|
||||
if ! check_configs_validity "${group_config_files[@]}" "${user_config_files[@]}" "${group_schema_files[@]}" "${user_schema_files[@]}"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -399,6 +493,28 @@ main() {
|
|||
|
||||
auth "$LLDAP_URL" "$LLDAP_ADMIN_USERNAME" "$LLDAP_ADMIN_PASSWORD"
|
||||
|
||||
printf -- '\n--- group schemas ---\n'
|
||||
local group_schema_config_row=''
|
||||
[[ ${#group_schema_files[@]} -gt 0 ]] && while read -r group_schema_config_row; do
|
||||
local field='' name='' attributeType='' isEditable='' isList='' isVisible=''
|
||||
for field in 'name' 'attributeType' 'isEditable' 'isList' 'isVisible'; do
|
||||
declare "$field"="$(printf '%s' "$group_schema_config_row" | jq --raw-output --arg field "$field" '.[$field]')"
|
||||
done
|
||||
create_group_schema_property "$name" "$attributeType" "$isEditable" "$isList" "$isVisible"
|
||||
done < <(jq --compact-output '.[]' -- "${group_schema_files[@]}")
|
||||
printf -- '--- group schemas ---\n'
|
||||
|
||||
printf -- '\n--- user schemas ---\n'
|
||||
local user_schema_config_row=''
|
||||
[[ ${#user_schema_files[@]} -gt 0 ]] && while read -r user_schema_config_row; do
|
||||
local field='' name='' attributeType='' isEditable='' isList='' isVisible=''
|
||||
for field in 'name' 'attributeType' 'isEditable' 'isList' 'isVisible'; do
|
||||
declare "$field"="$(printf '%s' "$user_schema_config_row" | jq --raw-output --arg field "$field" '.[$field]')"
|
||||
done
|
||||
create_user_schema_property "$name" "$attributeType" "$isEditable" "$isList" "$isVisible"
|
||||
done < <(jq --compact-output '.[]' -- "${user_schema_files[@]}")
|
||||
printf -- '--- user schemas ---\n'
|
||||
|
||||
local redundant_groups=''
|
||||
redundant_groups="$(get_group_list | jq '[ .data.groups[].displayName ]' | jq --compact-output '. - ["lldap_admin","lldap_password_manager","lldap_strict_readonly"]')"
|
||||
|
||||
|
|
Loading…
Reference in a new issue