diff --git a/Dockerfile b/Dockerfile index 744faf0..6b2804b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,40 @@ FROM mlocati/php-extension-installer:latest AS installer FROM php:7.4.9-cli-alpine3.12 +# USER root COPY --from=installer /usr/bin/install-php-extensions /usr/bin/ -RUN apk add --no-cache bash curl && \ - rm -rf /var/cache/apk/* - +RUN apk add --no-cache bash curl tini\ + && rm -rf /var/cache/apk/* \ + && install-php-extensions ldap \ + && mkdir -p /app + # && chown -R www-data:www-data /app # Install PHP extensions -RUN install-php-extensions ldap - -# INSTALL COMPOSER -SHELL ["/bin/ash", "-eo", "pipefail", "-c"] -RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer +# RUN install-php-extensions ldap WORKDIR /app COPY . . +# USER www-data + +# INSTALL COMPOSER +# && git clone git@github.com:Adambean/gitlab-ce-ldap-sync.git /app \ +SHELL ["/bin/bash", "-eo", "pipefail", "-c"] +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer \ + && composer install + +ADD ./docker /tmp/docker +RUN cp /tmp/docker/entrypoint.sh /entrypoint.sh \ + && chmod +x /entrypoint.sh \ + && cp /tmp/docker/healthcheck.sh /healthcheck.sh \ + && chmod +x /healthcheck.sh \ + && cp /tmp/docker/cron_task.sh /cron_task.sh \ + && chmod +x /cron_task.sh \ + && cp /tmp/docker/example_config.yml /app/example_config.yml \ + && rm -rf /tmp/docker + + +ENTRYPOINT ["tini", "--", "/entrypoint.sh"] -RUN composer install +HEALTHCHECK --timeout=5s CMD ["/healthcheck.sh"] -CMD ["update-ca-certificates", "&&", "php", "bin/console", "ldap:sync"] +# CMD ["update-ca-certificates", "&&", "php", "bin/console", "ldap:sync"] diff --git a/docker.sh b/docker.sh new file mode 100755 index 0000000..021c68c --- /dev/null +++ b/docker.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +DEFAULT_REPO="iamtaochen" +DEFAULT_NAME="gitlab-ldap-sync" + +MODE=$1; shift +if [ -z "$MODE" ]; then + MODE="build" +fi + +function build() { + NAME=$1 + if [ -z "$NAME" ]; then + NAME=$DEFAULT_NAME + fi + TAG=$2 + if [ -z "$TAG" ]; then + TAG="latest" + fi + docker build -t $NAME:$TAG . +} + +function push() +{ + NAME=$1 + if [ -z "$NAME" ]; then + NAME=$DEFAULT_NAME + fi + TAG=$2 + if [ -z "$TAG" ]; then + TAG="latest" + fi + REPO=$3 + if [ -z "$REPO" ]; then + REPO=$DEFAULT_REPO + fi + IMAG=$NAME:$TAG + REMOTE=$REPO/$IMAG + docker tag $IMAG $REMOTE + docker push $REMOTE +} + +if [ "$MODE" == "build" ]; then + build $@ +elif [ "$MODE" == "push" ]; then + push $@ +elif [ "$MODE" == "all" ]; then + build $@ + push $@ +fi + diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..e47a0fb --- /dev/null +++ b/docker/README.md @@ -0,0 +1,84 @@ +## how to use docker + +### Volume + - /etc/localtime:/etc/localtime:ro + - ./config.yml:/app/config.yml +you can mount config.yml at /app/config.yml as default. If you mount at different location, you shoulf +set the CONFIG_FILE as your file location + +### Enviriment + +#### SYNC_INTERVAL_DAY +default is 0; + +#### SYNC_INTERVAL_HOUR +default is 0; + +#### SYNC_INTERVAL_MINUTE +default is 5; + +#### CONFIG_FILE +where is the config.yml. default is /app/config.yml + +#### DRY_RUN +default is false. If you set as true, this docker don't sysn really. + +#### DEBUG_V +default is "v". if set as "NULL", there are no output + + +## Example +```yaml +version: "3.7" + +services: + + gitlab-ldap-sync: + build: + context: ./ldap-sync/github + dockerfile: Dockerfile + image: my/gitlab-ldap-sync + container_name: gitlab-ldap-sync + hostname: gitlab-ldap-sync + privileged: false + network_mode: host + volumes: + - /etc/localtime:/etc/localtime:ro + - ./ldap-sync/config.yml:/app/config.yml + environment: + DRY_RUN: false + SYNC_INTERVAL_MINUTE: 5 + DEBUG_V: "v" +``` + + + +## Example +```yaml +version: "3.7" + +services: + + gitlab-ldap-sync: + build: + context: ./ldap-sync/github + dockerfile: Dockerfile + image: my/gitlab-ldap-sync + container_name: gitlab-ldap-sync + hostname: gitlab-ldap-sync + privileged: false + network_mode: host + volumes: + - /etc/localtime:/etc/localtime:ro + - ./ldap-sync/config.yml:/app/config.yml + environment: + DRY_RUN: false + SYNC_INTERVAL_MINUTE: 5 + DEBUG_V: "v" +``` + + +### addingtion +config.yml add new setting. +`gitlab.options.unsyncExtraGroups` default is `false` +if set true, this script would ignore the groups cerated in gitlab but not in LDAP diff --git a/docker/cron_task.sh b/docker/cron_task.sh new file mode 100755 index 0000000..f8fe0b2 --- /dev/null +++ b/docker/cron_task.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +### +echo "-------------------------------------------------------------" +echo " Executing Cron Tasks: $(date)" +echo "-------------------------------------------------------------" +set -e + +WORK_DIR=/app +CONFIG_FILE_DEFAULT=$WORK_DIR/config.yml + +if [ -z "$CONFIG_FILE" ]; then + CONFIG_FILE=$CONFIG_FILE_DEFAULT +fi + +if [ ! -f "$CONFIG_FILE" ]; then + echo "Config file not found, use default config file." + $CONFIG_FILE=$WORK_DIR/config.yml.dist +fi + +if [ ! -f "$CONFIG_FILE_DEFAULT" ]; then + ln -s $CONFIG_FILE $WORK_DIR/config.yml +fi + + +if [ -z "$DRY_RUN" ]; then + DRY_RUN=false +fi + +if [ -z "$DEBUG_V" ]; then + DEBUG_V="-v" +elif [ $DEBUG_V = "NULL" ]; then + DEBUG_V="" +else + DEBUG_V=-$DEBUG_V +fi + +PHP_SCRIPT=$WORK_DIR/bin/console +if [ $DRY_RUN = true ]; then + CMD="update-ca-certificates && php $PHP_SCRIPT ldap:sync -d $DEBUG_V" +else + CMD="update-ca-certificates && php $PHP_SCRIPT ldap:sync $DEBUG_V" +fi + +echo "Start to run cron task : $CMD" +eval $CMD +echo "Done" diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..6d0bdd3 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +if [ -z "$SYNC_INTERVAL_DAY" ]; then + SYNC_INTERVAL_DAY=0 +fi + +if [ -z "$SYNC_INTERVAL_HOUR" ]; then + SYNC_INTERVAL_HOUR=0 +fi + +if [ -z "$SYNC_INTERVAL_MINUTE" ]; then + SYNC_INTERVAL_MINUTE=5 +fi + +if [ $SYNC_INTERVAL_DAY -gt 0 ]; then + DAY_SYMBOL="*/$SYNC_INTERVAL_DAY" +else + DAY_SYMBOL="*" +fi + +if [ $SYNC_INTERVAL_HOUR -gt 0 ]; then + HOUR_SYMBOL="*/$SYNC_INTERVAL_HOUR" +else + HOUR_SYMBOL="*" +fi + +if [ $SYNC_INTERVAL_MINUTE -gt 0 ]; then + MINUTE_SYMBOL="*/$SYNC_INTERVAL_MINUTE" +else + MINUTE_SYMBOL="*" +fi + +CRON_FILE=/var/spool/cron/crontabs/root +# if [ -f "$CRON_FILE" ]; then +# rm -rf $CRON_FILE +# fi + +CRON_TASK_CMD="$MINUTE_SYMBOL $HOUR_SYMBOL $DAY_SYMBOL * * /cron_task.sh" + +echo "-------------------------------------------------------------" +echo " Start at : $(date)" +echo "-------------------------------------------------------------" +echo "manual excute: /cron_task.sh" +bash /cron_task.sh +echo "Done" +echo "-------------------------------------------------------------" + +echo "Cron task: $CRON_TASK_CMD" +echo "$CRON_TASK_CMD" > $CRON_FILE + +echo "Starting crond" +exec crond -f -l 0 \ No newline at end of file diff --git a/docker/example_config.yml b/docker/example_config.yml new file mode 100644 index 0000000..406eff6 --- /dev/null +++ b/docker/example_config.yml @@ -0,0 +1,50 @@ +# If you don't know what you're doing check "README.md" for more details before +# filing a request for help. + +ldap: + debug: false + winCompatibilityMode: false + + server: + host: ~ + port: ~ + version: 3 + encryption: ~ + + bindDn: ~ + bindPassword: ~ + + queries: + baseDn: '' + + userDn: '' + userFilter: "(objectClass=inetOrgPerson)" + userUniqueAttribute: "uid" + userMatchAttribute: "uid" + userNameAttribute: "cn" + userEmailAttribute: "mail" + + groupDn: '' + groupFilter: "(objectClass=groupOfUniqueNames)" + groupUniqueAttribute: "cn" + groupMemberAttribute: "memberUid" + +gitlab: + debug: false + + options: + userNamesToIgnore: [] + groupNamesToIgnore: [] + + createEmptyGroups: false + deleteExtraGroups: false + newMemberAccessLevel: 30 + + groupNamesOfAdministrators: [] + groupNamesOfExternal: [] + + instances: + example: + url: ~ + token: ~ + ldapServerName: ~ diff --git a/docker/healthcheck.sh b/docker/healthcheck.sh new file mode 100755 index 0000000..0655a9b --- /dev/null +++ b/docker/healthcheck.sh @@ -0,0 +1,7 @@ +#!/bin/bash + + +set -x + +# Make sure cron daemon is still running +ps -o comm | grep crond || exit 1 diff --git a/src/LdapSyncCommand.php b/src/LdapSyncCommand.php index 6434e01..3e98bec 100644 --- a/src/LdapSyncCommand.php +++ b/src/LdapSyncCommand.php @@ -77,8 +77,7 @@ public function configure(): void ->setDescription("Sync LDAP users and groups with a Gitlab CE/EE self-hosted installation.") ->addOption("dryrun", "d", InputOption::VALUE_NONE, "Dry run: Do not persist any changes.") ->addOption("continueOnFail", null, InputOption::VALUE_NONE, "Do not abort on certain errors. (Continue running if possible.)") - ->addArgument("instance", InputArgument::OPTIONAL, "Sync with a specific instance, or leave unspecified to work with all.") - ; + ->addArgument("instance", InputArgument::OPTIONAL, "Sync with a specific instance, or leave unspecified to work with all."); } /** @@ -310,7 +309,6 @@ private function validateConfig(array &$config, array &$problems = null): bool $this->logger->$type(sprintf("Configuration: %s", $message)); $problems[$type][] = $message; - }; // << LDAP @@ -564,7 +562,16 @@ private function validateConfig(array &$config, array &$problems = null): bool } elseif (!is_bool($config["gitlab"]["options"]["createEmptyGroups"])) { $addProblem("error", "gitlab->options->createEmptyGroups is not a boolean."); } - + if (!isset($config["gitlab"]["options"]["unsyncExtraGroups"])) { + $addProblem("warning", "gitlab->options->unsyncExtraGroups missing. (Assuming true.)"); + $config["gitlab"]["options"]["unsyncExtraGroups"] = true; + } elseif ("" === $config["gitlab"]["options"]["unsyncExtraGroups"]) { + $addProblem("warning", "gitlab->options->unsyncExtraGroups not specified. (Assuming true.)"); + $config["gitlab"]["options"]["unsyncExtraGroups"] = true; + } elseif (!is_bool($config["gitlab"]["options"]["unsyncExtraGroups"])) { + $addProblem("error", "gitlab->options->unsyncExtraGroups is not a boolean."); + } + if (!isset($config["gitlab"]["options"]["deleteExtraGroups"])) { $addProblem("warning", "gitlab->options->deleteExtraGroups missing. (Assuming false.)"); $config["gitlab"]["options"]["deleteExtraGroups"] = false; @@ -1105,8 +1112,7 @@ private function deployGitlabUsersAndGroups(array $config, string $gitlabInstanc $this->logger->debug("Gitlab: Connecting"); $gitlab = \Gitlab\Client::create($gitlabConfig["url"]) - ->authenticate($gitlabConfig["token"], \Gitlab\Client::AUTH_HTTP_TOKEN) - ; + ->authenticate($gitlabConfig["token"], \Gitlab\Client::AUTH_HTTP_TOKEN); // << Handle users $usersSync = [ @@ -1462,6 +1468,7 @@ private function deployGitlabUsersAndGroups(array $config, string $gitlabInstanc $ldapGroupMembers = $ldapGroupsSafe[$gitlabGroupName]; $gitlabGroupPath = $slugifyGitlabPath->slugify($gitlabGroupName); + $groupsSync["extra"][$gitlabGroupId] = $gitlabGroupName; if ((is_array($ldapGroupMembers) && !empty($ldapGroupMembers)) || !$config["gitlab"]["options"]["deleteExtraGroups"]) { $this->logger->info(sprintf("Not deleting Gitlab group #%d \"%s\" [%s]: Has members in directory group, or config gitlab->options->deleteExtraGroups is disabled.", $gitlabGroupId, $gitlabGroupName, $gitlabGroupPath)); continue; @@ -1481,12 +1488,8 @@ private function deployGitlabUsersAndGroups(array $config, string $gitlabInstanc $gitlabGroup = null; !$this->dryRun ? ($gitlabGroup = $gitlab->api("groups")->remove($gitlabGroupId)) : $this->logger->warning("Operation skipped due to dry run."); - - $groupsSync["extra"][$gitlabGroupId] = $gitlabGroupName; - $this->gitlabApiCoolDown(); } - asort($groupsSync["extra"]); $this->logger->notice(sprintf("%d Gitlab group(s) deleted.", $groupsSync["extraNum"] = count($groupsSync["extra"]))); @@ -1547,8 +1550,12 @@ private function deployGitlabUsersAndGroups(array $config, string $gitlabInstanc $usersToSyncMembership = ($usersSync["found"] + $usersSync["new"] + $usersSync["update"]); asort($usersToSyncMembership); $groupsToSyncMembership = ($groupsSync["found"] + $groupsSync["new"] + $groupsSync["update"]); + if ($config["gitlab"]["options"]["unsyncExtraGroups"]) + { + $this->logger->info("unsyncExtraGroups is enabled, so unsyncing extra groups from directory groups..."); + $groupsToSyncMembership = array_diff($groupsToSyncMembership, $groupsSync["extra"]); + } asort($groupsToSyncMembership); - $this->logger->notice("Synchronising Gitlab group members with directory group members..."); foreach ($groupsToSyncMembership as $gitlabGroupId => $gitlabGroupName) { if ("Root" == $gitlabGroupName) { @@ -1659,9 +1666,11 @@ private function deployGitlabUsersAndGroups(array $config, string $gitlabInstanc $this->logger->info(sprintf("Adding user #%d \"%s\" to group #%d \"%s\" [%s].", $gitlabUserId, $gitlabUserName, $gitlabGroupId, $gitlabGroupName, $gitlabGroupPath)); $gitlabGroupMember = null; - - !$this->dryRun ? ($gitlabGroupMember = $gitlab->api("groups")->addMember($gitlabGroupId, $gitlabUserId, $config["gitlab"]["options"]["newMemberAccessLevel"])) : $this->logger->warning("Operation skipped due to dry run."); - + try { + !$this->dryRun ? ($gitlabGroupMember = $gitlab->api("groups")->addMember($gitlabGroupId, $gitlabUserId, $config["gitlab"]["options"]["newMemberAccessLevel"])) : $this->logger->warning("Operation skipped due to dry run."); + } catch (\Exception $e) { + $this->logger->error(sprintf("Gitlab failure: %s", $e->getMessage()), ["error" => $e]); + } $gitlabGroupMemberId = (is_array($gitlabGroupMember) && isset($gitlabGroupMember["id"]) && is_int($gitlabGroupMember["id"])) ? $gitlabGroupMember["id"] : sprintf("dry:%s:%d", $gitlabGroupPath, $gitlabUserId); $userGroupMembersSync["new"][$gitlabUserId] = $gitlabUserName; @@ -1670,7 +1679,6 @@ private function deployGitlabUsersAndGroups(array $config, string $gitlabInstanc asort($userGroupMembersSync["new"]); $this->logger->notice(sprintf("%d Gitlab group \"%s\" [%s] member(s) added.", $userGroupMembersSync["newNum"] = count($userGroupMembersSync["new"]), $gitlabGroupName, $gitlabGroupPath)); - // Delete extra group members $this->logger->notice("Deleting extra group members..."); foreach ($userGroupMembersSync["found"] as $gitlabUserId => $gitlabUserName) { @@ -1789,7 +1797,7 @@ private function generateRandomPassword(int $length): string */ private function getBuiltInUserNames() { - return ["root", "ghost", "support-bot", "alert-bot"]; + return ["root", "ghost", "support-bot", "alert-bot","visual-review-bot"]; } /**