diff --git a/apps/authelia/kustomization.yaml b/apps/authelia/kustomization.yaml index 838eb25..e937e66 100644 --- a/apps/authelia/kustomization.yaml +++ b/apps/authelia/kustomization.yaml @@ -5,6 +5,7 @@ resources: - ./namespace.yaml - ./repository.yaml - ./release.yaml + - ./lldap.yaml components: - ../../common/postgres diff --git a/apps/authelia/lldap.yaml b/apps/authelia/lldap.yaml new file mode 100644 index 0000000..f51db7c --- /dev/null +++ b/apps/authelia/lldap.yaml @@ -0,0 +1,60 @@ +apiVersion: v1 +kind: Secret +metadata: + name: authelia-lldap +type: Opaque +stringData: + password: ENC[AES256_GCM,data:t9dCqqJrS0mhJMBXLKTKUgbOpwI3LGN134OlGmIaOsZg1bzWSV4sU0YAQMU=,iv:Bp2hO34VNtqy+7ZnWtqvmUNe2GKUh7KPZmRgXzyFqqA=,tag:qJ8iV6OyuNlVmnrPs13LNg==,type:str] + user-configs.json: ENC[AES256_GCM,data:7bhp9uWOM1NcfJ8DnnUdYCIFMZeCvmGr8S5gJPzw0kzXfXQfRbI2xfq4X5GdAbOCn9HHM1F+xJLaF6tno1ZmH26NN7FkXUZQCtqK9+yZgjHY8MZYsUZHdZlV40BcaYSCk7qtefGsCrITN2X/DAjrmedNeh0CF9rdov3ZKsi8nSGWGUeLpKcouhOpvbfLRSoEEfYUyUF1r5GscTuunh9uZ8DtoCJvBf8iyQ==,iv:3YuaXKKIHUgzWL07yItqR6rgI+YXbaoTVc4xdiZ/hWU=,tag:hyObOlrQVXgRHgDxcV/R0g==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: [] + lastmodified: "2024-11-22T04:09:11Z" + mac: ENC[AES256_GCM,data:3o1AYP26QEIMjCUZ4y6AH+CXevoJoJ+rX3ioMLRf8KAGy0mSOtacaSY9xRdDIjATu9aJgHmFbSw9CHTBpXxmaISZxQdMPMHQAmRxHnSuQiofPRkVtD1TlvCFcDTSgITWbvG3dpUoLdM57Mgd3z7KpI/+gEoDebYfryDaYXCoH3c=,iv:1C8QMJCJtvnGVPpLJE+l0U3hOknEC3XiWTQrPAQsHKc=,tag:fn+cMj1NImJSvNiuyzX5pQ==,type:str] + pgp: + - created_at: "2024-11-22T04:09:11Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hQIMA7pKPTYH5bqOAQ/+MgqnLWwHCWPxacANbHEEYsPENOyIywmYJnSnRqRLWhAn + 9K0/udCxwO30rnvo+p6/YLF2VSqFfz7pUm/z+MH6ypyY1B83HjCkjsaTQhPR5Q0K + CmhTR7TrQBNfa/flawhebWOjvmUJ9lJ9uqCnAB16S03Sn+PqDYlGTE6CMJ0oJuSr + VpxdvdvFZ1gfR7hlVrsKqvn47T3XIYDJohp9l819nQ1O2adTPfevZEN/JLwaWSLT + YtwJyg+6ogKD3q6UBv7hyyXH9ZlMHFxGWxmo1OXAA3E/vMvOacgmFW6pqoGMqwGU + D9Ch2x1MBobD342ZPPmsQNiI+34Q5cl+hVJJgL5jWk2kML67itM8pMUTyn+5NtWO + wWo6zu5q5IfqREwuerZtisocctrLB9QKPVGcjVihWfoenvlkf4yfCRDFzOPSAb0o + e2K+3fZknZlnb3Qb/rgD7XRiBhcif1zIHZxUQDv/Lq9GyuCM7dk8YKUVCtyeixQ4 + C+WFmp9ED8xzv1jR9lPcQhD+I2Cb7/9jlTXEetFHSzl4riYpKPjhKQedUWZ0YY1A + u0ORTBaLzcmrXTjGz24PxmWZDBjhV4Kgvn76rchqLrS8lvi1EbXoZB6ERhuhlz5Y + bm1FwUBxDRG04gFCPwWKV0AvMmhd+hOdyo4KeQbZCO+w3QqXnp/y6b3TtpEeQnPU + aAEJAhCMqxSAESN99AcEtW56mJsZmRCCi3NsRLwllDczeDfUznF2CSTCnJRDmjsU + bLf8jVjawLxfRnKmRyKX/cCYbuz9OmIFkOAWoSNVb/teiMrYYFq96kRDLHR1Llxr + EuX0poghRPqU + =tI6a + -----END PGP MESSAGE----- + fp: 1E0CF38FF7C9ADAED58B436ABA4A3D3607E5BA8E + - created_at: "2024-11-22T04:09:11Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hQIMA51kG++kLewoARAA15aOcSEfAIpEXXhmF4YToynn1NM9OsANHc3PR2uVzAPv + C4Wi8R1PNhGdV3aTuRN5WpSjkJEE3GNR0kA0Etao7Ip0d1UgXzg1wtwEd1Yyvtdh + ccK2/z0a4UJu8SMczChT1P18IASNksaxSAm+TOLFGcZeJFwQepsBaQIEfXYO3+hR + Jw+zcPmFaOzKoqdbAAWzvYhLxD2ocjZl7iiIOhz8fBSqWLO2oeJRp5Lk8Q14olTZ + 708BQ+aLlsVJyLkiV7SzlKfEDIymMDZSe7Q3i4JqOFOyHRIkIM5ZPOLoelqRNcY2 + zQphsk1U/MFp7LsR/d+5IKWBkqV5DYJWFunw+NRFHLg1/6+zmGnGbZ2gZfohvnKV + 5GUrYfWCBACIclpxY7PlVQ7d/aTDf3jdR0iVV3Jh+8Lvze1msPvI+BF67oDNMsTu + EIbRa6eHzxgSqrq3Za5eeUXd9Gxfg2g4KdkbG+FA9qQI6f5Y1q0tE9cFfOElTiBk + xTAckrBMHOMGozvx4/6xXHMmAxd80tX0ZjVyBsPBeb64oZGlsGuRngWT1Ob9gF4Q + sDfyd74kpQ9fHhIYs9XSLrPbH6yzVIFF/sHpMGgri43PCMW6vvnfP4JQgdMNdXRw + U+RWDxA6BOkP7XvNfGADiumeSGQ+PE/KP0TuUqMD7gr9X/VGH+/1e6zbI1iruhPS + XgFoSamAXKfYrYz94J9u0vA8D8ne9EKa8Ls6ybicyyZlGLri/qnoHNJAVhLWKdId + h68ksrI5l25Z1MkAcKVR1xlHUnRCwb2Xdbag0vV07So00wxAl1XNhtPeuQrykk0= + =V4Gd + -----END PGP MESSAGE----- + fp: 49F10679C425233EFB4B1B6F9D641BEFA42DEC28 + encrypted_regex: ^(data|stringData)$ + version: 3.9.0 diff --git a/apps/authelia/release.yaml b/apps/authelia/release.yaml index f717301..d40b373 100644 --- a/apps/authelia/release.yaml +++ b/apps/authelia/release.yaml @@ -27,6 +27,8 @@ spec: additionalSecrets: authelia-db-cluster-app: key: authelia-db-cluster-app + authelia-lldap: + key: authelia-lldap configMap: authentication_backend: @@ -46,7 +48,8 @@ spec: mail: mail user: uid=authelia,ou=people,dc=huizinga,dc=dev password: - value: "JustATest" + secret_name: authelia-lldap + path: password session: cookies: diff --git a/apps/lldap/bootstrap/bootstrap-job.yaml b/apps/lldap/bootstrap/bootstrap-job.yaml new file mode 100644 index 0000000..ca236ca --- /dev/null +++ b/apps/lldap/bootstrap/bootstrap-job.yaml @@ -0,0 +1,78 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: lldap-bootstrap + annotations: + kustomize.toolkit.fluxcd.io/force: enabled +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: lldap-bootstrap + image: lldap/lldap:v0.5.0 + + command: + - /bootstrap/bootstrap.sh + + env: + - name: LLDAP_URL + value: "http://lldap:17170" + + - name: LLDAP_ADMIN_USERNAME + value: admin + + - name: LLDAP_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: lldap-credentials + key: lldap-ldap-user-pass + + - name: DO_CLEANUP + value: "true" + + volumeMounts: + - name: bootstrap + mountPath: /bootstrap/bootstrap.sh + readOnly: true + subPath: bootstrap.sh + + - name: user-configs + mountPath: /bootstrap/user-configs + readOnly: true + + - name: group-configs + mountPath: /bootstrap/group-configs + readOnly: true + + volumes: + - name: bootstrap + configMap: + name: bootstrap + defaultMode: 0555 + items: + - key: bootstrap.sh + path: bootstrap.sh + + - name: user-configs + projected: + sources: + - secret: + name: lldap-bootstrap-configs + items: + - key: user-configs.json + path: user-configs.json + - secret: + name: authelia-lldap + items: + - key: user-configs.json + path: authelia-configs.json + + - name: group-configs + projected: + sources: + - secret: + name: lldap-bootstrap-configs + items: + - key: group-configs.json + path: group-configs.json diff --git a/apps/lldap/bootstrap/bootstrap.sh b/apps/lldap/bootstrap/bootstrap.sh new file mode 100644 index 0000000..6001eb3 --- /dev/null +++ b/apps/lldap/bootstrap/bootstrap.sh @@ -0,0 +1,606 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +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' + + if ! hash "${commands[@]}" 2>/dev/null; then + if hash 'apk' 2>/dev/null && [[ $EUID -eq 0 ]]; then + apk add "${commands[@]}" + elif hash 'apt' 2>/dev/null && [[ $EUID -eq 0 ]]; then + apt update -yqq + apt install -yqq "${commands[@]}" + else + local command='' + for command in "${commands[@]}"; do + if ! hash "$command" 2>/dev/null; then + printf 'Command not found "%s"\n' "$command" + fi + done + commands_not_found='true' + fi + fi + + if [[ "$commands_not_found" == 'true' ]]; then + return 1 + fi +} + +check_required_env_vars() { + local env_var_not_specified='false' + local dual_env_vars_list=( + 'LLDAP_URL' + 'LLDAP_ADMIN_USERNAME' + 'LLDAP_ADMIN_PASSWORD' + ) + + local dual_env_var_name='' + for dual_env_var_name in "${dual_env_vars_list[@]}"; do + local dual_env_var_file_name="${dual_env_var_name}_FILE" + + if [[ -z "${!dual_env_var_name}" ]] && [[ -z "${!dual_env_var_file_name}" ]]; then + printf 'Please specify "%s" or "%s" variable!\n' "$dual_env_var_name" "$dual_env_var_file_name" >&2 + env_var_not_specified='true' + else + if [[ -n "${!dual_env_var_file_name}" ]]; then + declare -g "$dual_env_var_name"="$(cat "${!dual_env_var_file_name}")" + fi + fi + done + + if [[ "$env_var_not_specified" == 'true' ]]; then + return 1 + fi +} + +check_configs_validity() { + local config_file='' config_invalid='false' + for config_file in "$@"; do + local error='' + if ! error="$(jq '.' -- "$config_file" 2>&1 >/dev/null)"; then + printf '%s: %s\n' "$config_file" "$error" + config_invalid='true' + fi + done + + if [[ "$config_invalid" == 'true' ]]; then + return 1 + fi +} + +auth() { + local url="$1" admin_username="$2" admin_password="$3" + + local response + response="$(curl --silent --request POST \ + --url "$url/auth/simple/login" \ + --header 'Content-Type: application/json' \ + --data "$(jo -- username="$admin_username" password="$admin_password")")" + + TOKEN="$(printf '%s' "$response" | jq --raw-output .token)" +} + +make_query() { + local query_file="$1" variables_file="$2" + + curl --silent --request POST \ + --url "$LLDAP_URL/api/graphql" \ + --header "Authorization: Bearer $TOKEN" \ + --header 'Content-Type: application/json' \ + --data @<(jq --slurpfile variables "$variables_file" '. + {"variables": $variables[0]}' "$query_file") +} + +get_group_list() { + local query='{"query":"query GetGroupList {groups {id displayName}}","operationName":"GetGroupList"}' + make_query <(printf '%s' "$query") <(printf '{}') +} + +get_group_array() { + get_group_list | jq --raw-output '.data.groups[].displayName' +} + +group_exists() { + if [[ "$(get_group_list | jq --raw-output --arg displayName "$1" '.data.groups | any(.[]; select(.displayName == $displayName))')" == 'true' ]]; then + return 0 + else + return 1 + fi +} + +get_group_id() { + get_group_list | jq --raw-output --arg displayName "$1" '.data.groups[] | if .displayName == $displayName then .id else empty end' +} + +create_group() { + local group_name="$1" + + if group_exists "$group_name"; then + printf 'Group "%s" (%s) already exists\n' "$group_name" "$(get_group_id "$group_name")" + return + fi + + # shellcheck disable=SC2016 + local query='{"query":"mutation CreateGroup($name: String!) {createGroup(name: $name) {id displayName}}","operationName":"CreateGroup"}' + + local response='' error='' + response="$(make_query <(printf '%s' "$query") <(jo -- name="$group_name"))" + 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 "%s" (%s) successfully created\n' "$group_name" "$(printf '%s' "$response" | jq --raw-output '.data.createGroup.id')" + fi +} + +delete_group() { + local group_name="$1" id='' + + if ! group_exists "$group_name"; then + printf '[WARNING] Group "%s" does not exist\n' "$group_name" + return + fi + + id="$(get_group_id "$group_name")" + + # shellcheck disable=SC2016 + local query='{"query":"mutation DeleteGroupQuery($groupId: Int!) {deleteGroup(groupId: $groupId) {ok}}","operationName":"DeleteGroupQuery"}' + + local response='' error='' + response="$(make_query <(printf '%s' "$query") <(jo -- groupId="$id"))" + 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 "%s" (%s) successfully deleted\n' "$group_name" "$id" + fi +} + +get_user_details() { + local id="$1" + + # shellcheck disable=SC2016 + local query='{"query":"query GetUserDetails($id: String!) {user(userId: $id) {id email displayName firstName lastName creationDate uuid groups {id displayName}}}","operationName":"GetUserDetails"}' + make_query <(printf '%s' "$query") <(jo -- id="$id") +} + +user_in_group() { + local user_id="$1" group_name="$2" + + if ! group_exists "$group_name"; then + printf '[WARNING] Group "%s" does not exist\n' "$group_name" + return + fi + + if ! user_exists "$user_id"; then + printf 'User "%s" is not exists\n' "$user_id" + return + fi + + if [[ "$(get_user_details "$user_id" | jq --raw-output --arg displayName "$group_name" '.data.user.groups | any(.[]; select(.displayName == $displayName))')" == 'true' ]]; then + return 0 + else + return 1 + fi +} + +add_user_to_group() { + local user_id="$1" group_name="$2" group_id='' + + if ! group_exists "$group_name"; then + printf '[WARNING] Group "%s" does not exist\n' "$group_name" + return + fi + + group_id="$(get_group_id "$group_name")" + + if user_in_group "$user_id" "$group_name"; then + printf 'User "%s" already in group "%s" (%s)\n' "$user_id" "$group_name" "$group_id" + return + fi + + # shellcheck disable=SC2016 + local query='{"query":"mutation AddUserToGroup($user: String!, $group: Int!) {addUserToGroup(userId: $user, groupId: $group) {ok}}","operationName":"AddUserToGroup"}' + + local response='' error='' + response="$(make_query <(printf '%s' "$query") <(jo -- user="$user_id" group="$group_id"))" + error="$(printf '%s' "$response" | jq '.errors | if . != null then .[].message else empty end')" + if [[ -n "$error" ]]; then + printf '%s\n' "$error" + else + printf 'User "%s" successfully added to the group "%s" (%s)\n' "$user_id" "$group_name" "$group_id" + fi +} + +remove_user_from_group() { + local user_id="$1" group_name="$2" group_id='' + + if ! group_exists "$group_name"; then + printf '[WARNING] Group "%s" does not exist\n' "$group_name" + return + fi + + group_id="$(get_group_id "$group_name")" + + # shellcheck disable=SC2016 + local query='{"operationName":"RemoveUserFromGroup","query":"mutation RemoveUserFromGroup($user: String!, $group: Int!) {removeUserFromGroup(userId: $user, groupId: $group) {ok}}"}' + + local response='' error='' + response="$(make_query <(printf '%s' "$query") <(jo -- user="$user_id" group="$group_id"))" + error="$(printf '%s' "$response" | jq '.errors | if . != null then .[].message else empty end')" + if [[ -n "$error" ]]; then + printf '%s\n' "$error" + else + printf 'User "%s" successfully removed from the group "%s" (%s)\n' "$user_id" "$group_name" "$group_id" + fi +} + +get_users_list() { + # shellcheck disable=SC2016 + local query='{"query": "query ListUsersQuery($filters: RequestFilter) {users(filters: $filters) {id email displayName firstName lastName creationDate}}","operationName": "ListUsersQuery"}' + make_query <(printf '%s' "$query") <(jo -- filters=null) +} + +user_exists() { + if [[ "$(get_users_list | jq --raw-output --arg id "$1" '.data.users | any(.[]; .id == $id)')" == 'true' ]]; then + return 0 + else + return 1 + fi +} + +delete_user() { + local id="$1" + + if ! user_exists "$id"; then + printf 'User "%s" is not exists\n' "$id" + return + fi + + # shellcheck disable=SC2016 + local query='{"query": "mutation DeleteUserQuery($user: String!) {deleteUser(userId: $user) {ok}}","operationName": "DeleteUserQuery"}' + + local response='' error='' + response="$(make_query <(printf '%s' "$query") <(jo -- user="$id"))" + 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 "%s" successfully deleted\n' "$id" + 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" \ + id="${2:-null}" \ + email="${3:-null}" \ + displayName="${4:-null}" \ + firstName="${5:-null}" \ + lastName="${6:-null}" \ + avatar_file="${7:-null}" \ + avatar_url="${8:-null}" \ + gravatar_avatar="${9:-false}" \ + weserv_avatar="${10:-false}" + + local variables_arr=( + '-s' "id=$id" + '-s' "email=$email" + '-s' "displayName=$displayName" + '-s' "firstName=$firstName" + '-s' "lastName=$lastName" + ) + + local temp_avatar_file='' + + if [[ "$gravatar_avatar" == 'true' ]]; then + avatar_url="https://gravatar.com/avatar/$(printf '%s' "$email" | sha256sum | cut -d ' ' -f 1)?size=512" + fi + + if [[ "$avatar_url" != 'null' ]]; then + temp_avatar_file="${TMP_AVATAR_DIR}/$(printf '%s' "$avatar_url" | md5sum | cut -d ' ' -f 1)" + + if ! [[ -f "$temp_avatar_file" ]]; then + if [[ "$weserv_avatar" == 'true' ]]; then + avatar_url="https://wsrv.nl/?url=$avatar_url&output=jpg" + fi + curl --silent --location --output "$temp_avatar_file" "$avatar_url" + fi + + avatar_file="$temp_avatar_file" + fi + + if [[ "$avatar_file" == 'null' ]]; then + variables_arr+=('-s' 'avatar=null') + else + variables_arr+=("avatar=%$avatar_file") + fi + + make_query <(printf '%s' "$query") <(jo -- user=:<(jo -- "${variables_arr[@]}")) +} + +create_user() { + local id="$1" + + if user_exists "$id"; then + printf 'User "%s" already exists\n' "$id" + return + fi + + # shellcheck disable=SC2016 + local query='{"query":"mutation CreateUser($user: CreateUserInput!) {createUser(user: $user) {id creationDate}}","operationName":"CreateUser"}' + + local response='' error='' + response="$(__common_user_mutation_query "$query" "$@")" + 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 "%s" successfully created\n' "$id" + fi +} + +update_user() { + local id="$1" + + if ! user_exists "$id"; then + printf 'User "%s" is not exists\n' "$id" + return + fi + + # shellcheck disable=SC2016 + local query='{"query":"mutation UpdateUser($user: UpdateUserInput!) {updateUser(user: $user) {ok}}","operationName":"UpdateUser"}' + + local response='' error='' + response="$(__common_user_mutation_query "$query" "$@")" + 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 "%s" successfully updated\n' "$id" + fi +} + +create_update_user() { + local id="$1" + + if user_exists "$id"; then + update_user "$@" + else + create_user "$@" + fi +} + +main() { + check_install_dependencies + check_required_env_vars + + local user_config_files=("${USER_CONFIGS_DIR}"/*.json) + local group_config_files=("${GROUP_CONFIGS_DIR}"/*.json) + local user_schema_files=() + local group_schema_files=() + + 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 + + until curl --silent -o /dev/null "$LLDAP_URL"; do + printf 'Waiting lldap to start...\n' + sleep 10 + done + + 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"]')" + + printf -- '\n--- groups ---\n' + local group_config='' + while read -r group_config; do + local group_name='' + group_name="$(printf '%s' "$group_config" | jq --raw-output '.name')" + create_group "$group_name" + redundant_groups="$(printf '%s' "$redundant_groups" | jq --compact-output --arg name "$group_name" '. - [$name]')" + done < <(jq --compact-output '.' -- "${group_config_files[@]}") + printf -- '--- groups ---\n' + + printf -- '\n--- redundant groups ---\n' + if [[ "$redundant_groups" == '[]' ]]; then + printf 'There are no redundant groups\n' + else + local group_name='' + while read -r group_name; do + if [[ "$DO_CLEANUP" == 'true' ]]; then + delete_group "$group_name" + else + printf '[WARNING] Group "%s" is not declared in config files\n' "$group_name" + fi + done < <(printf '%s' "$redundant_groups" | jq --raw-output '.[]') + fi + printf -- '--- redundant groups ---\n' + + local redundant_users='' + redundant_users="$(get_users_list | jq '[ .data.users[].id ]' | jq --compact-output --arg admin_id "$LLDAP_ADMIN_USERNAME" '. - [$admin_id]')" + + TMP_AVATAR_DIR="$(mktemp -d)" + + local user_config='' + while read -r user_config; do + local field='' id='' email='' displayName='' firstName='' lastName='' avatar_file='' avatar_url='' gravatar_avatar='' weserv_avatar='' password='' + for field in 'id' 'email' 'displayName' 'firstName' 'lastName' 'avatar_file' 'avatar_url' 'gravatar_avatar' 'weserv_avatar' 'password'; do + declare "$field"="$(printf '%s' "$user_config" | jq --raw-output --arg field "$field" '.[$field]')" + done + printf -- '\n--- %s ---\n' "$id" + + create_update_user "$id" "$email" "$displayName" "$firstName" "$lastName" "$avatar_file" "$avatar_url" "$gravatar_avatar" "$weserv_avatar" + redundant_users="$(printf '%s' "$redundant_users" | jq --compact-output --arg id "$id" '. - [$id]')" + + if [[ "$password" != 'null' ]] && [[ "$password" != '""' ]]; then + "$LLDAP_SET_PASSWORD_PATH" --base-url "$LLDAP_URL" --token "$TOKEN" --username "$id" --password "$password" + fi + + local redundant_user_groups='' + redundant_user_groups="$(get_user_details "$id" | jq '[ .data.user.groups[].displayName ]')" + + local group='' + while read -r group; do + if [[ -n "$group" ]]; then + add_user_to_group "$id" "$group" + redundant_user_groups="$(printf '%s' "$redundant_user_groups" | jq --compact-output --arg group "$group" '. - [$group]')" + fi + done < <(printf '%s' "$user_config" | jq --raw-output '.groups | if . == null then "" else .[] end') + + local user_group_name='' + while read -r user_group_name; do + if [[ "$DO_CLEANUP" == 'true' ]]; then + remove_user_from_group "$id" "$user_group_name" + else + printf '[WARNING] User "%s" is not declared as member of the "%s" group in the config files\n' "$id" "$user_group_name" + fi + done < <(printf '%s' "$redundant_user_groups" | jq --raw-output '.[]') + printf -- '--- %s ---\n' "$id" + done < <(jq --compact-output '.' -- "${user_config_files[@]}") + + rm -r "$TMP_AVATAR_DIR" + + printf -- '\n--- redundant users ---\n' + if [[ "$redundant_users" == '[]' ]]; then + printf 'There are no redundant users\n' + else + local id='' + while read -r id; do + if [[ "$DO_CLEANUP" == 'true' ]]; then + delete_user "$id" + else + printf '[WARNING] User "%s" is not declared in config files\n' "$id" + fi + done < <(printf '%s' "$redundant_users" | jq --raw-output '.[]') + fi + printf -- '--- redundant users ---\n' +} + +main "$@" diff --git a/apps/lldap/bootstrap/group-configs.json b/apps/lldap/bootstrap/group-configs.json new file mode 100644 index 0000000..e69de29 diff --git a/apps/lldap/bootstrap/kustomization.yaml b/apps/lldap/bootstrap/kustomization.yaml new file mode 100644 index 0000000..7e95252 --- /dev/null +++ b/apps/lldap/bootstrap/kustomization.yaml @@ -0,0 +1,18 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./bootstrap-job.yaml + - ../../authelia/lldap.yaml +configMapGenerator: + - name: bootstrap + options: + annotations: + kustomize.toolkit.fluxcd.io/substitute: disabled + files: + - bootstrap.sh + +secretGenerator: + - name: lldap-bootstrap-configs + files: + - user-configs.json + - group-configs.json diff --git a/apps/lldap/bootstrap/user-configs.json b/apps/lldap/bootstrap/user-configs.json new file mode 100644 index 0000000..ae5a0c6 --- /dev/null +++ b/apps/lldap/bootstrap/user-configs.json @@ -0,0 +1,8 @@ +{ + "id": "dreaded_x", + "email": "tim@huizinga.dev", + "password": "JustATest", + "displayName": "Tim Huizinga", + "firstName": "Tim", + "lastName": "Huizinga" +} diff --git a/apps/lldap/kustomization.yaml b/apps/lldap/kustomization.yaml index 8e68e66..1ae1dab 100644 --- a/apps/lldap/kustomization.yaml +++ b/apps/lldap/kustomization.yaml @@ -7,6 +7,7 @@ resources: - ./deployment.yaml - ./service.yaml - ./ingress.yaml + - ./bootstrap components: - ../../common/postgres diff --git a/reconcile.sh b/reconcile.sh new file mode 100755 index 0000000..83ac2c1 --- /dev/null +++ b/reconcile.sh @@ -0,0 +1,6 @@ +#!/bin/bash +flux reconcile source git flux-system +flux reconcile kustomization flux-system +flux reconcile kustomization infra-controllers +flux reconcile kustomization infra-configs +flux reconcile kustomization apps