diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5419b63..23156ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,6 +63,17 @@ jobs: uses: actions/checkout@v4 # https://github.com/actions/checkout + - name: Run the sh-checker + uses: luizm/action-sh-checker@master # https://github.com/marketplace/actions/sh-checker + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SHFMT_OPTS: --simplify --keep-padding + with: + sh_checker_comment: true + sh_checker_checkbashisms_enable: true + sh_checker_shfmt_disable: true + + - name: Check Dockerfile uses: hadolint/hadolint-action@v3.1.0 with: diff --git a/build-image.sh b/build-image.sh index 6ed2260..527f3de 100644 --- a/build-image.sh +++ b/build-image.sh @@ -6,11 +6,12 @@ # SPDX-ArtifactOfProjectHomePage: https://github.com/vegardit/docker-openldap function curl() { - command curl -sSfL --connect-timeout 10 --max-time 30 --retry 3 --retry-all-errors "$@" + command curl -sSfL --connect-timeout 10 --max-time 30 --retry 3 --retry-all-errors "$@" } -shared_lib="$(dirname $0)/.shared" -[ -e "$shared_lib" ] || curl https://raw.githubusercontent.com/vegardit/docker-shared/v1/download.sh?_=$(date +%s) | bash -s v1 "$shared_lib" || exit 1 +shared_lib="$(dirname "${BASH_SOURCE[0]}")/.shared" +[[ -e $shared_lib ]] || curl "https://raw.githubusercontent.com/vegardit/docker-shared/v1/download.sh?_=$(date +%s)" | bash -s v1 "$shared_lib" || exit 1 +# shellcheck disable=SC1091 # Not following: $shared_lib/lib/build-image-init.sh was not specified as input source "$shared_lib/lib/build-image-init.sh" @@ -26,26 +27,31 @@ image_name=$image_repo:latest # build the image ################################################# log INFO "Building docker image [$image_name]..." -if [[ $OSTYPE == "cygwin" || $OSTYPE == "msys" ]]; then - project_root=$(cygpath -w "$project_root") +if [[ $OSTYPE == cygwin || $OSTYPE == msys ]]; then + project_root=$(cygpath -w "$project_root") fi set -x -docker pull $base_image_name -DOCKER_BUILDKIT=1 docker build "$project_root" \ - --file "image/Dockerfile" \ - --progress=plain \ - --build-arg INSTALL_SUPPORT_TOOLS=${INSTALL_SUPPORT_TOOLS:-0} \ - `# 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=$base_layer_cache_key \ - --build-arg BASE_IMAGE=$base_image_name \ - --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \ - --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)" \ - -t $image_name \ - "$@" + +docker --version +export DOCKER_BUILD_KIT=1 + +# shellcheck disable=SC2154 # base_layer_cache_key is referenced but not assigned. +docker build "$project_root" \ + --file "image/Dockerfile" \ + --progress=plain \ + --pull \ + --build-arg "INSTALL_SUPPORT_TOOLS=${INSTALL_SUPPORT_TOOLS:-0}" \ + `# 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="$base_layer_cache_key" \ + --build-arg BASE_IMAGE="$base_image_name" \ + --build-arg BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ + --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)" \ + --tag "$image_name" \ + "$@" set +x @@ -53,7 +59,7 @@ set +x # determine effective OpenLDAP version ################################################# # LC_ALL=en_US.utf8 -> workaround for "grep: -P supports only unibyte and UTF-8 locales" -ldap_version=$(docker run --rm $image_name dpkg -s slapd | LC_ALL=en_US.utf8 grep -oP 'Version: \K\d+\.\d+\.\d+') +ldap_version=$(docker run --rm "$image_name" dpkg -s slapd | LC_ALL=en_US.utf8 grep -oP 'Version: \K\d+\.\d+\.\d+') echo "ldap_version=$ldap_version" @@ -61,35 +67,35 @@ echo "ldap_version=$ldap_version" # apply tags ################################################# declare -a tags=() -tags+=($image_name) # :latest -tags+=($image_repo:${ldap_version}) # :2.5.12 -tags+=($image_repo:${ldap_version%.*}.x) # :2.5.x -tags+=($image_repo:${ldap_version%%.*}.x) # :2.x +tags+=("$image_name") # :latest +tags+=("$image_repo:${ldap_version}") # :2.5.12 +tags+=("$image_repo:${ldap_version%.*}.x") # :2.5.x +tags+=("$image_repo:${ldap_version%%.*}.x") # :2.x -for tag in ${tags[@]}; do - docker image tag $image_name $tag - if [[ "${DOCKER_PUSH:-}" == "true" ]]; then - docker image tag $image_name ghcr.io/$tag - fi +for tag in "${tags[@]}"; do + docker image tag "$image_name" "$tag" + if [[ ${DOCKER_PUSH:-} == true ]]; then + docker image tag "$image_name" "ghcr.io/$tag" + fi done ################################################# # perform security audit ################################################# -if [[ "${DOCKER_AUDIT_IMAGE:-1}" == 1 ]]; then - bash "$shared_lib/cmd/audit-image.sh" $image_name +if [[ ${DOCKER_AUDIT_IMAGE:-1} == 1 ]]; then + bash "$shared_lib/cmd/audit-image.sh" "$image_name" fi ################################################# # push image with tags to remote docker image registry ################################################# -if [[ "${DOCKER_PUSH:-}" == "true" ]]; then - for tag in ${tags[@]}; do - set -x - docker push $tag - docker push ghcr.io/$tag - set +x - done +if [[ ${DOCKER_PUSH:-} == true ]]; then + for tag in "${tags[@]}"; do + set -x + docker push "$tag" + docker push "ghcr.io/$tag" + set +x + done fi diff --git a/image/Dockerfile b/image/Dockerfile index c3c038c..f078105 100644 --- a/image/Dockerfile +++ b/image/Dockerfile @@ -1,6 +1,5 @@ -#syntax=docker/dockerfile:1.4 -# see https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md#user-content-syntax -# see https://docs.docker.com/build/dockerfile/frontend/ +#syntax=docker/dockerfile:1 +# see https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md # see https://docs.docker.com/engine/reference/builder/#syntax # # SPDX-FileCopyrightText: © Vegard IT GmbH (https://vegardit.com) @@ -11,7 +10,7 @@ # https://hub.docker.com/_/debian/tags?name=bookworm-slim ARG BASE_IMAGE=debian:bookworm-slim -# see https://github.com/hadolint/hadolint/wiki/DL3006 +# https://github.com/hadolint/hadolint/wiki/DL3006 Always tag the version of an image explicitly # hadolint ignore=DL3006 FROM ${BASE_IMAGE} diff --git a/image/run.sh b/image/run.sh index 20a34c7..fe02294 100644 --- a/image/run.sh +++ b/image/run.sh @@ -5,6 +5,7 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-ArtifactOfProjectHomePage: https://github.com/vegardit/docker-openldap +# shellcheck disable=SC1091 # Not following: /opt/bash-init.sh was not specified as input source /opt/bash-init.sh ################################################# @@ -29,8 +30,10 @@ log INFO "Timezone is $(date +"%Z %z")" ################################################# # load custom init script if specified ################################################# -if [[ -f $INIT_SH_FILE ]]; then +if [[ -f ${INIT_SH_FILE:-} ]]; then log INFO "Loading [$INIT_SH_FILE]..." + + # shellcheck disable=SC1090 # ShellCheck can't follow non-constant source source "$INIT_SH_FILE" fi @@ -41,22 +44,22 @@ slapd -VVV 2>&1 | log INFO || 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 +ulimit -n "$LDAP_NOFILE_LIMIT" ################################################################# # Adjust UID/GID and file permissions based on env var config ################################################################# -if [ -n "${LDAP_OPENLDAP_UID:-}" ]; then +if [[ -n ${LDAP_OPENLDAP_UID:-} ]]; then effective_uid=$(id -u openldap) - if [ "$LDAP_OPENLDAP_UID" != "$effective_uid" ]; then + if [[ $LDAP_OPENLDAP_UID != "$effective_uid" ]]; then log INFO "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 +if [[ -n ${LDAP_OPENLDAP_GID:-} ]]; then effective_gid=$(id -g openldap) - if [ "$LDAP_OPENLDAP_GID" != "$effective_gid" ]; then + if [[ $LDAP_OPENLDAP_GID != "$effective_gid" ]]; then log INFO "Changing GID of openldap user from $effective_gid to $LDAP_OPENLDAP_GID..." usermod -o -g "$LDAP_OPENLDAP_GID" openldap fi @@ -73,7 +76,8 @@ chown -R openldap:openldap /var/run/slapd if [ ! -e /etc/ldap/slapd.d/initialized ]; then function substr_before() { - echo "${1%%$2*}" + # shellcheck disable=SC2295 # Expansions inside ${..} need to be quoted separately, otherwise they match as patterns + echo "${1%%${2}*}" } function str_replace() { @@ -86,8 +90,9 @@ if [ ! -e /etc/ldap/slapd.d/initialized ]; then local action=$1 && shift local file=${!#} log INFO "Loading [$file]..." - interpolate < $file > /tmp/$(basename $file) - ldap$action -H ldapi:/// "${@:1:${#}-1}" -f /tmp/$(basename $file) + # shellcheck disable=SC2094 # Make sure not to read and write the same file in the same pipeline + interpolate <"$file" >"/tmp/$(basename "$file")" + "ldap$action" -H ldapi:/// "${@:1:${#}-1}" -f "/tmp/$(basename "$file")" } # interpolate variable placeholders in env vars starting with "LDAP_INIT_" @@ -97,24 +102,30 @@ if [ ! -e /etc/ldap/slapd.d/initialized ]; then # 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 + if [[ $folder -ef "${folder}_orig" ]]; then continue fi - if [ -z "$(ls $folder)" ]; then + if [[ -z $(ls $folder) ]]; then log INFO "Initializing [$folder]..." cp -r --preserve=all ${folder}_orig/. $folder fi done - if [ -z "${LDAP_INIT_ROOT_USER_PW:-}" ]; then - log ERROR "LDAP_INIT_ROOT_USER_PW variable is not set!" - exit 1 + if [[ -z ${LDAP_INIT_ROOT_USER_DN:-} ]]; then + log ERROR "LDAP_INIT_ROOT_USER_DN variable is not set!" + exit 1 fi - # LDAP_INIT_ROOT_USER_PW_HASHED is used in /opt/ldifs/init_mdb_acls.ldif - LDAP_INIT_ROOT_USER_PW_HASHED=$(slappasswd -s "${LDAP_INIT_ROOT_USER_PW}") + if [[ -z ${LDAP_INIT_ROOT_USER_PW:-} ]]; then + log ERROR "LDAP_INIT_ROOT_USER_PW variable is not set!" + exit 1 + fi - if [ "${LDAP_INIT_RFC2307BIS_SCHEMA:-}" == "1" ]; then + # shellcheck disable=SC2034 # LDAP_INIT_ROOT_USER_PW_HASHED appears unused + LDAP_INIT_ROOT_USER_PW_HASHED=$(slappasswd -s "${LDAP_INIT_ROOT_USER_PW}") + # LDAP_INIT_ROOT_USER_PW_HASHED is referenced in /opt/ldifs/init_mdb_acls.ldif + + if [[ ${LDAP_INIT_RFC2307BIS_SCHEMA:-} == 1 ]]; then log INFO "Replacing NIS (RFC2307) schema with RFC2307bis schema..." log INFO "Exporting initial slapd config..." @@ -124,14 +135,16 @@ if [ ! -e /etc/ldap/slapd.d/initialized ]; then find /etc/ldap/slapd.d/ -type f -delete log INFO "Create modified sldapd config file..." - # create ldif file where "{2}nis,cn=schema,cn=config" schema is replaced by "{2}rfc2307bis,cn=schema,cn=config" - # 1. add all schema entries before "dn: cn={2}nis,cn=schema,cn=config" from initial config to new config file - echo "${initial_sldapd_config%%dn: cn=\{2\}nis,cn=schema,cn=config*}" > /tmp/config.ldif - # 2. add "dn: cn={2}rfc2307bis,cn=schema,cn=config" entry - sed 's/rfc2307bis/{2}rfc2307bis/g' /opt/ldifs/schema_rfc2307bis02.ldif >> /tmp/config.ldif - echo >> /tmp/config.ldif # add empty new line - # 3. add entry "dn: cn={3}inetorgperson,cn=schema,cn=config" and following entries from initial config to new config file - echo "dn: cn={3}inetorgperson,cn=schema,cn=config${initial_sldapd_config#*dn: cn=\{3\}inetorgperson,cn=schema,cn=config}" >> /tmp/config.ldif + { + # create ldif file where "{2}nis,cn=schema,cn=config" schema is replaced by "{2}rfc2307bis,cn=schema,cn=config" + # 1. add all schema entries before "dn: cn={2}nis,cn=schema,cn=config" from initial config to new config file + echo "${initial_sldapd_config%%dn: cn=\{2\}nis,cn=schema,cn=config*}" + # 2. add "dn: cn={2}rfc2307bis,cn=schema,cn=config" entry + sed 's/rfc2307bis/{2}rfc2307bis/g' /opt/ldifs/schema_rfc2307bis02.ldif + echo # add empty new line + # 3. add entry "dn: cn={3}inetorgperson,cn=schema,cn=config" and following entries from initial config to new config file + echo "dn: cn={3}inetorgperson,cn=schema,cn=config${initial_sldapd_config#*dn: cn=\{3\}inetorgperson,cn=schema,cn=config}" + } >/tmp/config.ldif log INFO "Register modified slapd config with RFC2307bis schema..." slapadd -F /etc/ldap/slapd.d -n 0 -l /tmp/config.ldif @@ -140,7 +153,7 @@ if [ ! -e /etc/ldap/slapd.d/initialized ]; then /etc/init.d/slapd start # await ldap server start - for i in {1..8}; do + for _ in {1..8}; do ldapwhoami -H ldapi:/// && break sleep 1 done @@ -156,28 +169,30 @@ if [ ! -e /etc/ldap/slapd.d/initialized ]; then ldif add -Y EXTERNAL /opt/ldifs/init_module_unique.ldif ldif add -Y EXTERNAL /opt/ldifs/init_module_ppolicy.ldif - if [ "${LDAP_INIT_ALLOW_CONFIG_ACCESS:-false}" == "true" ]; then + if [[ ${LDAP_INIT_ALLOW_CONFIG_ACCESS:-false} == true ]]; then ldif modify -Y EXTERNAL /opt/ldifs/init_config_admin_access.ldif fi # calculate LDAP_INIT_ORG_COMPUTED_ATTRS variable, referenced in init_org_tree.ldif - if [[ -z ${LDAP_INIT_ORG_ATTR_O:-} ]] && [[ "$LDAP_INIT_ORG_DN" =~ [oO]=([^,]*) ]]; then + if [[ -z ${LDAP_INIT_ORG_ATTR_O:-} ]] && [[ ${LDAP_INIT_ORG_DN:-} =~ [oO]=([^,]*) ]]; then # derive 'o:' from LDAP_INIT_ORG_DN if LDAP_INIT_ORG_ATTR_O is unset and "O=..." is present # e.g. LDAP_INIT_ORG_DN="O=example.com" -> "o: example.com" # e.g. LDAP_INIT_ORG_DN="O=Example,DC=example,DC=com" -> "o: Example" LDAP_INIT_ORG_ATTR_O=${BASH_REMATCH[1]} fi - if [[ "$LDAP_INIT_ORG_DN" =~ [dD][cC]=([^,]*) ]]; then + if [[ $LDAP_INIT_ORG_DN =~ [dD][cC]=([^,]*) ]]; then LDAP_INIT_ORG_ATTR_DC=${BASH_REMATCH[1]} # derive 'o:' from LDAP_INIT_ORG_DN if LDAP_INIT_ORG_ATTR_O is unset and "DC=..." is present if [[ -z ${LDAP_INIT_ORG_ATTR_O:-} ]]; then # e.g. LDAP_INIT_ORG_DN="DC=example,DC=com" -> "o: example.com" LDAP_INIT_ORG_ATTR_O=$(echo "$LDAP_INIT_ORG_DN" | grep -ioP 'DC=\K[^,]+' | paste -sd '.') fi + # shellcheck disable=SC2034 # LDAP_INIT_ORG_COMPUTED_ATTRS appears unused LDAP_INIT_ORG_COMPUTED_ATTRS="objectClass: dcObject o: $LDAP_INIT_ORG_ATTR_O dc: $LDAP_INIT_ORG_ATTR_DC" elif [[ -n ${LDAP_INIT_ORG_ATTR_O:-} ]]; then + # shellcheck disable=SC2034 # LDAP_INIT_ORG_COMPUTED_ATTRS appears unused LDAP_INIT_ORG_COMPUTED_ATTRS="o: $LDAP_INIT_ORG_ATTR_O" else log ERROR "Unable to derive required 'o' attribute of objectClass 'organization' from LDAP_INIT_ORG_DN='$LDAP_INIT_ORG_DN'" @@ -190,26 +205,32 @@ dc: $LDAP_INIT_ORG_ATTR_DC" log INFO "--------------------------------------------" - echo "1" > /etc/ldap/slapd.d/initialized + echo "1" >/etc/ldap/slapd.d/initialized rm -f /tmp/*.ldif log INFO "Creating periodic LDAP backup at [$LDAP_BACKUP_FILE]..." - slapcat -n 1 -l $LDAP_BACKUP_FILE || true + 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 +echo "$LDAP_PPOLICY_PQCHECKER_RULE" >/etc/ldap/pqchecker/pqparams.dat ################################################################# # Configure background task for LDAP backup ################################################################# -if [ -n "${LDAP_BACKUP_TIME:-}" ]; then +if [[ -n ${LDAP_BACKUP_TIME:-} ]]; then + + if [[ -z ${LDAP_BACKUP_FILE:-} ]]; then + log ERROR "LDAP_BACKUP_FILE variable is not set!" + exit 1 + fi + log INFO "--------------------------------------------" log INFO "Configuring LDAP backup task to run daily: time=[${LDAP_BACKUP_TIME}] file=[$LDAP_BACKUP_FILE]..." - if [[ "$LDAP_BACKUP_TIME" != +([0-9][0-9]:[0-9][0-9]) ]]; then + if [[ $LDAP_BACKUP_TIME != +([0-9][0-9]:[0-9][0-9]) ]]; then log ERROR "The configured value [$LDAP_BACKUP_TIME] for LDAP_BACKUP_TIME is not in the expected 24-hour format [hh:mm]!" exit 1 fi @@ -219,7 +240,7 @@ if [ -n "${LDAP_BACKUP_TIME:-}" ]; then function backup_ldap() { while true; do - while [ "$(date +%H:%M)" != "${LDAP_BACKUP_TIME}" ]; do + while [[ ${LDAP_BACKUP_TIME} != "$(date +%H:%M)" ]]; do sleep 10s done log INFO "Creating periodic LDAP backup at [$LDAP_BACKUP_FILE]..." @@ -238,9 +259,15 @@ fi log INFO "--------------------------------------------" log INFO "Starting OpenLDAP: slapd..." +# build an array of “-d ” for each level in LDAP_LOG_LEVELS +log_opts=() +for lvl in ${LDAP_LOG_LEVELS:-}; do + log_opts+=("-d" "$lvl") +done + exec /usr/sbin/slapd \ - $(for logLevel in ${LDAP_LOG_LEVELS:-}; do echo -n "-d $logLevel "; done) \ - -h "ldap:/// ldapi:///" \ + "${log_opts[@]}" \ + -h "ldap:/// ldapi:/// ${LDAP_LDAPS_ENABLE:+ldaps:///}" \ -u openldap \ -g openldap \ -F /etc/ldap/slapd.d 2>&1 | log INFO