475 lines
14 KiB
Bash
475 lines
14 KiB
Bash
|
#!/bin/bash
|
||
|
#
|
||
|
# Bitnami OpenLDAP library
|
||
|
|
||
|
# shellcheck disable=SC1091
|
||
|
|
||
|
# Load Generic Libraries
|
||
|
. /opt/bitnami/scripts/libfile.sh
|
||
|
. /opt/bitnami/scripts/libfs.sh
|
||
|
. /opt/bitnami/scripts/liblog.sh
|
||
|
. /opt/bitnami/scripts/libos.sh
|
||
|
. /opt/bitnami/scripts/libservice.sh
|
||
|
. /opt/bitnami/scripts/libvalidations.sh
|
||
|
|
||
|
########################
|
||
|
# Load global variables used on OpenLDAP configuration
|
||
|
# Globals:
|
||
|
# LDAP_*
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns:
|
||
|
# Series of exports to be used as 'eval' arguments
|
||
|
#########################
|
||
|
ldap_env() {
|
||
|
cat << "EOF"
|
||
|
# Paths
|
||
|
export LDAP_BASE_DIR="/opt/bitnami/openldap"
|
||
|
export LDAP_BIN_DIR="${LDAP_BASE_DIR}/bin"
|
||
|
export LDAP_SBIN_DIR="${LDAP_BASE_DIR}/sbin"
|
||
|
export LDAP_CONF_DIR="${LDAP_BASE_DIR}/etc"
|
||
|
export LDAP_SHARE_DIR="${LDAP_BASE_DIR}/share"
|
||
|
export LDAP_VOLUME_DIR="/bitnami/openldap"
|
||
|
export LDAP_DATA_DIR="${LDAP_VOLUME_DIR}/data"
|
||
|
export LDAP_ONLINE_CONF_DIR="${LDAP_VOLUME_DIR}/slapd.d"
|
||
|
export LDAP_PID_FILE="${LDAP_BASE_DIR}/var/run/slapd.pid"
|
||
|
export LDAP_CUSTOM_LDIF_DIR="${LDAP_CUSTOM_LDIF_DIR:-/ldifs}"
|
||
|
export LDAP_CUSTOM_SCHEMA_FILE="${LDAP_CUSTOM_SCHEMA_FILE:-/schema/custom.ldif}"
|
||
|
export PATH="${LDAP_BIN_DIR}:${LDAP_SBIN_DIR}:$PATH"
|
||
|
export LDAP_TLS_CERT_FILE="${LDAP_TLS_CERT_FILE:-}"
|
||
|
export LDAP_TLS_KEY_FILE="${LDAP_TLS_KEY_FILE:-}"
|
||
|
export LDAP_TLS_CA_FILE="${LDAP_TLS_CA_FILE:-}"
|
||
|
export LDAP_TLS_DH_PARAMS_FILE="${LDAP_TLS_DH_PARAMS_FILE:-}"
|
||
|
# Users
|
||
|
export LDAP_DAEMON_USER="slapd"
|
||
|
export LDAP_DAEMON_GROUP="slapd"
|
||
|
# Settings
|
||
|
export LDAP_PORT_NUMBER="${LDAP_PORT_NUMBER:-1389}"
|
||
|
export LDAP_LDAPS_PORT_NUMBER="${LDAP_LDAPS_PORT_NUMBER:-1636}"
|
||
|
export LDAP_ROOT="${LDAP_ROOT:-dc=example,dc=org}"
|
||
|
export LDAP_ADMIN_USERNAME="${LDAP_ADMIN_USERNAME:-admin}"
|
||
|
export LDAP_ADMIN_DN="${LDAP_ADMIN_USERNAME/#/cn=},${LDAP_ROOT}"
|
||
|
export LDAP_ADMIN_PASSWORD="${LDAP_ADMIN_PASSWORD:-adminpassword}"
|
||
|
export LDAP_ENCRYPTED_ADMIN_PASSWORD="$(echo -n $LDAP_ADMIN_PASSWORD | slappasswd -n -T /dev/stdin)"
|
||
|
export LDAP_EXTRA_SCHEMAS="${LDAP_EXTRA_SCHEMAS:-cosine,inetorgperson,nis}"
|
||
|
export LDAP_SKIP_DEFAULT_TREE="${LDAP_SKIP_DEFAULT_TREE:-no}"
|
||
|
export LDAP_USERS="${LDAP_USERS:-user01,user02}"
|
||
|
export LDAP_PASSWORDS="${LDAP_PASSWORDS:-bitnami1,bitnami2}"
|
||
|
export LDAP_USER_DC="${LDAP_USER_DC:-users}"
|
||
|
export LDAP_GROUP="${LDAP_GROUP:-readers}"
|
||
|
export LDAP_ENABLE_TLS="${LDAP_ENABLE_TLS:-no}"
|
||
|
export LDAP_ULIMIT_NOFILES="${LDAP_ULIMIT_NOFILES:-1024}"
|
||
|
EOF
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# Validate settings in LDAP_* environment variables
|
||
|
# Globals:
|
||
|
# LDAP_*
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns:
|
||
|
# None
|
||
|
#########################
|
||
|
ldap_validate() {
|
||
|
info "Validating settings in LDAP_* env vars"
|
||
|
local error_code=0
|
||
|
|
||
|
# Auxiliary functions
|
||
|
print_validation_error() {
|
||
|
error "$1"
|
||
|
error_code=1
|
||
|
}
|
||
|
check_allowed_port() {
|
||
|
local port_var="${1:?missing port variable}"
|
||
|
local validate_port_args=()
|
||
|
! am_i_root && validate_port_args+=("-unprivileged")
|
||
|
if ! err=$(validate_port "${validate_port_args[@]}" "${!port_var}"); then
|
||
|
print_validation_error "An invalid port was specified in the environment variable ${port_var}: ${err}."
|
||
|
fi
|
||
|
}
|
||
|
for var in LDAP_SKIP_DEFAULT_TREE LDAP_ENABLE_TLS; do
|
||
|
if ! is_yes_no_value "${!var}"; then
|
||
|
print_validation_error "The allowed values for $var are: yes or no"
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
if is_boolean_yes "$LDAP_ENABLE_TLS"; then
|
||
|
if [[ -z "$LDAP_TLS_CERT_FILE" ]]; then
|
||
|
print_validation_error "You must provide a X.509 certificate in order to use TLS"
|
||
|
elif [[ ! -f "$LDAP_TLS_CERT_FILE" ]]; then
|
||
|
print_validation_error "The X.509 certificate file in the specified path ${LDAP_TLS_CERT_FILE} does not exist"
|
||
|
fi
|
||
|
if [[ -z "$LDAP_TLS_KEY_FILE" ]]; then
|
||
|
print_validation_error "You must provide a private key in order to use TLS"
|
||
|
elif [[ ! -f "$LDAP_TLS_KEY_FILE" ]]; then
|
||
|
print_validation_error "The private key file in the specified path ${LDAP_TLS_KEY_FILE} does not exist"
|
||
|
fi
|
||
|
if [[ -z "$LDAP_TLS_CA_FILE" ]]; then
|
||
|
print_validation_error "You must provide a CA X.509 certificate in order to use TLS"
|
||
|
elif [[ ! -f "$LDAP_TLS_CA_FILE" ]]; then
|
||
|
print_validation_error "The CA X.509 certificate file in the specified path ${LDAP_TLS_CA_FILE} does not exist"
|
||
|
fi
|
||
|
fi
|
||
|
|
||
|
read -r -a users <<< "$(tr ',;' ' ' <<< "${LDAP_USERS}")"
|
||
|
read -r -a passwords <<< "$(tr ',;' ' ' <<< "${LDAP_PASSWORDS}")"
|
||
|
if [[ "${#users[@]}" -ne "${#passwords[@]}" ]]; then
|
||
|
print_validation_error "Specify the same number of passwords on LDAP_PASSWORDS as the number of users on LDAP_USERS!"
|
||
|
fi
|
||
|
|
||
|
if [[ -n "$LDAP_PORT_NUMBER" ]] && [[ -n "$LDAP_LDAPS_PORT_NUMBER" ]]; then
|
||
|
if [[ "$LDAP_PORT_NUMBER" -eq "$LDAP_LDAPS_PORT_NUMBER" ]]; then
|
||
|
print_validation_error "LDAP_PORT_NUMBER and LDAP_LDAPS_PORT_NUMBER are bound to the same port!"
|
||
|
fi
|
||
|
fi
|
||
|
[[ -n "$LDAP_PORT_NUMBER" ]] && check_allowed_port LDAP_PORT_NUMBER
|
||
|
[[ -n "$LDAP_LDAPS_PORT_NUMBER" ]] && check_allowed_port LDAP_LDAPS_PORT_NUMBER
|
||
|
|
||
|
[[ "$error_code" -eq 0 ]] || exit "$error_code"
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# Check if OpenLDAP is running
|
||
|
# Globals:
|
||
|
# LDAP_PID_FILE
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns:
|
||
|
# Whether slapd is running
|
||
|
#########################
|
||
|
is_ldap_running() {
|
||
|
local pid
|
||
|
pid="$(get_pid_from_file "${LDAP_PID_FILE}")"
|
||
|
if [[ -n "${pid}" ]]; then
|
||
|
is_service_running "${pid}"
|
||
|
else
|
||
|
false
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# Check if OpenLDAP is not running
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns:
|
||
|
# Whether slapd is not running
|
||
|
#########################
|
||
|
is_ldap_not_running() {
|
||
|
! is_ldap_running
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# Start OpenLDAP server in background
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns:
|
||
|
# None
|
||
|
#########################
|
||
|
ldap_start_bg() {
|
||
|
local -a flags=("-h" "ldap://:${LDAP_PORT_NUMBER}/ ldapi:/// " "-F" "${LDAP_CONF_DIR}/slapd.d")
|
||
|
if is_ldap_not_running; then
|
||
|
info "Starting OpenLDAP server in background"
|
||
|
ulimit -n "$LDAP_ULIMIT_NOFILES"
|
||
|
am_i_root && flags=("-u" "$LDAP_DAEMON_USER" "${flags[@]}")
|
||
|
debug_execute slapd "${flags[@]}"
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# Stop OpenLDAP server
|
||
|
# Arguments:
|
||
|
# $1 - max retries. Default: 12
|
||
|
# $2 - sleep between retries (in seconds). Default: 1
|
||
|
# Returns:
|
||
|
# None
|
||
|
#########################
|
||
|
ldap_stop() {
|
||
|
local -r retries="${1:-12}"
|
||
|
local -r sleep_time="${2:-1}"
|
||
|
|
||
|
are_db_files_locked() {
|
||
|
local return_value=0
|
||
|
read -r -a db_files <<< "$(find "$LDAP_DATA_DIR" -type f -print0 | xargs -0)"
|
||
|
for f in "${db_files[@]}"; do
|
||
|
debug_execute lsof -w "$f" && return_value=1
|
||
|
done
|
||
|
return $return_value
|
||
|
}
|
||
|
|
||
|
is_ldap_not_running && return
|
||
|
|
||
|
stop_service_using_pid "$LDAP_PID_FILE"
|
||
|
if ! retry_while are_db_files_locked "$retries" "$sleep_time"; then
|
||
|
error "OpenLDAP failed to stop"
|
||
|
return 1
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# Create LDAP online configuration
|
||
|
# Globals:
|
||
|
# LDAP_*
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns:
|
||
|
# None
|
||
|
#########################
|
||
|
ldap_create_online_configuration() {
|
||
|
info "Creating LDAP online configuration"
|
||
|
! am_i_root && replace_in_file "${LDAP_SHARE_DIR}/slapd.ldif" "uidNumber=0" "uidNumber=$(id -u)"
|
||
|
slapadd -F "$LDAP_ONLINE_CONF_DIR" -n 0 -l "${LDAP_SHARE_DIR}/slapd.ldif"
|
||
|
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# Configure LDAP credentials for admin user
|
||
|
# Globals:
|
||
|
# LDAP_*
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns:
|
||
|
# None
|
||
|
#########################
|
||
|
ldap_admin_credentials() {
|
||
|
info "Configure LDAP credentials for admin user = $LDAP_ADMIN_DN"
|
||
|
cat > "${LDAP_SHARE_DIR}/admin.ldif" << EOF
|
||
|
dn: olcDatabase={2}hdb,cn=config
|
||
|
changetype: modify
|
||
|
replace: olcSuffix
|
||
|
olcSuffix: $LDAP_ROOT
|
||
|
|
||
|
dn: olcDatabase={2}hdb,cn=config
|
||
|
changetype: modify
|
||
|
replace: olcRootDN
|
||
|
olcRootDN: $LDAP_ADMIN_DN
|
||
|
|
||
|
dn: olcDatabase={2}hdb,cn=config
|
||
|
changeType: modify
|
||
|
add: olcRootPW
|
||
|
olcRootPW: $LDAP_ENCRYPTED_ADMIN_PASSWORD
|
||
|
|
||
|
dn: olcDatabase={1}monitor,cn=config
|
||
|
changetype: modify
|
||
|
replace: olcAccess
|
||
|
olcAccess: {0}to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external, cn=auth" read by dn.base="${LDAP_ADMIN_DN}" read by * none
|
||
|
EOF
|
||
|
ldapmodify -Y EXTERNAL -H "ldapi:///" -f "${LDAP_SHARE_DIR}/admin.ldif"
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# Add LDAP schemas
|
||
|
# Globals:
|
||
|
# LDAP_*
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns
|
||
|
# None
|
||
|
#########################
|
||
|
ldap_add_schemas() {
|
||
|
info "Adding LDAP extra schemas === ${LDAP_EXTRA_SCHEMAS}"
|
||
|
read -r -a schemas <<< "$(tr ',;' ' ' <<< "${LDAP_EXTRA_SCHEMAS}")"
|
||
|
for schema in "${schemas[@]}"; do
|
||
|
ldapadd -Y EXTERNAL -H "ldapi:///" -f "${LDAP_CONF_DIR}/schema/${schema}.ldif"
|
||
|
done
|
||
|
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# Add custom schema
|
||
|
# Globals:
|
||
|
# LDAP_*
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns
|
||
|
# None
|
||
|
#########################
|
||
|
ldap_add_custom_schema() {
|
||
|
info "Adding custom Schema : $LDAP_CUSTOM_SCHEMA_FILE ..."
|
||
|
slapadd -F "$LDAP_ONLINE_CONF_DIR" -n 0 -l "$LDAP_CUSTOM_SCHEMA_FILE"
|
||
|
ldap_stop
|
||
|
while is_ldap_running; do sleep 1; done
|
||
|
ldap_start_bg
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# Create LDAP tree
|
||
|
# Globals:
|
||
|
# LDAP_*
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns:
|
||
|
# None
|
||
|
#########################
|
||
|
ldap_create_tree() {
|
||
|
info "Creating LDAP default tree"
|
||
|
local dc=""
|
||
|
local o="example"
|
||
|
read -r -a root <<< "$(tr ',;' ' ' <<< "${LDAP_ROOT}")"
|
||
|
for attr in "${root[@]}"; do
|
||
|
if [[ $attr = dc=* ]] && [[ -z "$dc" ]]; then
|
||
|
dc="${attr:3}"
|
||
|
elif [[ $attr = o=* ]] && [[ $o = "example" ]]; then
|
||
|
o="${attr:2}"
|
||
|
fi
|
||
|
done
|
||
|
cat > "${LDAP_SHARE_DIR}/tree.ldif" << EOF
|
||
|
# Root creation
|
||
|
dn: $LDAP_ROOT
|
||
|
objectClass: dcObject
|
||
|
objectClass: organization
|
||
|
dc: $dc
|
||
|
o: $o
|
||
|
|
||
|
dn: ${LDAP_USER_DC/#/ou=},${LDAP_ROOT}
|
||
|
objectClass: organizationalUnit
|
||
|
ou: users
|
||
|
|
||
|
EOF
|
||
|
read -r -a users <<< "$(tr ',;' ' ' <<< "${LDAP_USERS}")"
|
||
|
read -r -a passwords <<< "$(tr ',;' ' ' <<< "${LDAP_PASSWORDS}")"
|
||
|
local index=0
|
||
|
for user in "${users[@]}"; do
|
||
|
cat >> "${LDAP_SHARE_DIR}/tree.ldif" << EOF
|
||
|
# User $user creation
|
||
|
dn: ${user/#/cn=},${LDAP_USER_DC/#/ou=},${LDAP_ROOT}
|
||
|
cn: User$((index + 1 ))
|
||
|
sn: Bar$((index + 1 ))
|
||
|
objectClass: inetOrgPerson
|
||
|
objectClass: posixAccount
|
||
|
objectClass: shadowAccount
|
||
|
userPassword: ${passwords[$index]}
|
||
|
uid: $user
|
||
|
uidNumber: $((index + 1000 ))
|
||
|
gidNumber: $((index + 1000 ))
|
||
|
homeDirectory: /home/${user}
|
||
|
|
||
|
EOF
|
||
|
index=$((index + 1 ))
|
||
|
done
|
||
|
cat >> "${LDAP_SHARE_DIR}/tree.ldif" << EOF
|
||
|
# Group creation
|
||
|
dn: ${LDAP_GROUP/#/cn=},${LDAP_USER_DC/#/ou=},${LDAP_ROOT}
|
||
|
cn: $LDAP_GROUP
|
||
|
objectClass: groupOfNames
|
||
|
# User group membership
|
||
|
EOF
|
||
|
|
||
|
for user in "${users[@]}"; do
|
||
|
cat >> "${LDAP_SHARE_DIR}/tree.ldif" << EOF
|
||
|
member: ${user/#/cn=},${LDAP_USER_DC/#/ou=},${LDAP_ROOT}
|
||
|
EOF
|
||
|
done
|
||
|
|
||
|
debug_execute ldapadd -f "${LDAP_SHARE_DIR}/tree.ldif" -H "ldapi:///" -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD"
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# Add custom LDIF files
|
||
|
# Globals:
|
||
|
# LDAP_*
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns
|
||
|
# None
|
||
|
#########################
|
||
|
ldap_add_custom_ldifs() {
|
||
|
info "Loading custom LDIF files..."
|
||
|
warn "Ignoring LDAP_USERS, LDAP_PASSWORDS, LDAP_USER_DC and LDAP_GROUP environment variables..."
|
||
|
find "$LDAP_CUSTOM_LDIF_DIR" -maxdepth 1 \( -type f -o -type l \) -iname '*.ldif' -print0 | sort -z | xargs --null -I{} bash -c ". /opt/bitnami/scripts/libos.sh && debug_execute ldapadd -f {} -H 'ldapi:///' -D $LDAP_ADMIN_DN -w $LDAP_ADMIN_PASSWORD"
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# OpenLDAP configure permissions
|
||
|
# Globals:
|
||
|
# LDAP_*
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns:
|
||
|
# None
|
||
|
#########################
|
||
|
ldap_configure_permissions() {
|
||
|
debug "Ensuring expected directories/files exist..."
|
||
|
for dir in "$LDAP_SHARE_DIR" "$LDAP_DATA_DIR" "$LDAP_ONLINE_CONF_DIR"; do
|
||
|
ensure_dir_exists "$dir"
|
||
|
if am_i_root; then
|
||
|
chown -R "$LDAP_DAEMON_USER:$LDAP_DAEMON_GROUP" "$dir"
|
||
|
fi
|
||
|
done
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# Initialize OpenLDAP server
|
||
|
# Globals:
|
||
|
# LDAP_*
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns:
|
||
|
# None
|
||
|
#########################
|
||
|
ldap_initialize() {
|
||
|
info "Initializing OpenLDAP..."
|
||
|
|
||
|
ldap_configure_permissions
|
||
|
if ! is_dir_empty "$LDAP_DATA_DIR"; then
|
||
|
info "Using persisted data"
|
||
|
else
|
||
|
# Create OpenLDAP online configuration
|
||
|
ldap_create_online_configuration
|
||
|
info "Start BG"
|
||
|
ldap_start_bg
|
||
|
ldap_admin_credentials
|
||
|
if is_boolean_yes "$LDAP_ENABLE_TLS"; then
|
||
|
ldap_configure_tls
|
||
|
fi
|
||
|
if is_boolean_yes "$LDAP_SKIP_DEFAULT_TREE"; then
|
||
|
info "Skipping default schemas/tree structure"
|
||
|
else
|
||
|
# Initialize OpenLDAP with schemas/tree structure
|
||
|
ldap_add_schemas
|
||
|
if [[ -f "$LDAP_CUSTOM_SCHEMA_FILE" ]]; then
|
||
|
ldap_add_custom_schema
|
||
|
fi
|
||
|
if ! is_dir_empty "$LDAP_CUSTOM_LDIF_DIR"; then
|
||
|
ldap_add_custom_ldifs
|
||
|
else
|
||
|
ldap_create_tree
|
||
|
fi
|
||
|
fi
|
||
|
ldap_stop
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
########################
|
||
|
# OpenLDAP configure TLS
|
||
|
# Globals:
|
||
|
# LDAP_*
|
||
|
# Arguments:
|
||
|
# None
|
||
|
# Returns:
|
||
|
# None
|
||
|
#########################
|
||
|
ldap_configure_tls() {
|
||
|
info "Configuring TLS"
|
||
|
cat > "${LDAP_SHARE_DIR}/certs.ldif" << EOF
|
||
|
dn: cn=config
|
||
|
changetype: modify
|
||
|
replace: olcTLSCACertificateFile
|
||
|
olcTLSCACertificateFile: $LDAP_TLS_CA_FILE
|
||
|
-
|
||
|
replace: olcTLSCertificateFile
|
||
|
olcTLSCertificateFile: $LDAP_TLS_CERT_FILE
|
||
|
-
|
||
|
replace: olcTLSCertificateKeyFile
|
||
|
olcTLSCertificateKeyFile: $LDAP_TLS_KEY_FILE
|
||
|
EOF
|
||
|
if [[ -f "$LDAP_TLS_DH_PARAMS_FILE" ]]; then
|
||
|
cat >> "${LDAP_SHARE_DIR}/certs.ldif" << EOF
|
||
|
-
|
||
|
replace: olcTLSDHParamFile
|
||
|
olcTLSDHParamFile: $LDAP_TLS_DH_PARAMS_FILE
|
||
|
EOF
|
||
|
fi
|
||
|
debug_execute ldapmodify -Y EXTERNAL -H "ldapi:///" -f "${LDAP_SHARE_DIR}/certs.ldif"
|
||
|
}
|