Compare commits
No commits in common. "057ddf2a0b13bfccba20f36890692b22a2b80520" and "f3f8ae9f1a79085318c99576476f980e9ea68486" have entirely different histories.
057ddf2a0b
...
f3f8ae9f1a
9 changed files with 283 additions and 350 deletions
|
@ -1,89 +0,0 @@
|
||||||
---
|
|
||||||
name: Build and publish Debian package
|
|
||||||
on: [create]
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: docker
|
|
||||||
container:
|
|
||||||
image: docker.io/brenard/debian-python-deb:latest
|
|
||||||
steps:
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Build Debian package
|
|
||||||
env:
|
|
||||||
MAINTAINER_NAME: ${{ vars.MAINTAINER_NAME }}
|
|
||||||
MAINTAINER_EMAIL: ${{ vars.MAINTAINER_EMAIL }}
|
|
||||||
DEBIAN_CODENAME: ${{ vars.DEBIAN_CODENAME }}
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.GPG_KEY }}"|base64 -d|gpg --import
|
|
||||||
./build.sh
|
|
||||||
mv check_syncrepl_extended dist/
|
|
||||||
- name: Upload Debian package files
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
path: |
|
|
||||||
dist/*.buildinfo
|
|
||||||
dist/*.changes
|
|
||||||
dist/*.deb
|
|
||||||
dist/*.dsc
|
|
||||||
dist/*.tar.gz
|
|
||||||
dist/release_notes.md
|
|
||||||
dist/check_syncrepl_extended
|
|
||||||
|
|
||||||
publish-forgejo:
|
|
||||||
runs-on: docker
|
|
||||||
container:
|
|
||||||
image: docker.io/brenard/debian-python-deb:latest
|
|
||||||
steps:
|
|
||||||
- name: Download Debian package files
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
|
|
||||||
- name: Create the release
|
|
||||||
id: create-release
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
mkdir release
|
|
||||||
mv *.deb release/
|
|
||||||
mv check_syncrepl_extended release/
|
|
||||||
md5sum release/* > md5sum.txt
|
|
||||||
sha512sum release/* > sha512sum.txt
|
|
||||||
mv md5sum.txt sha512sum.txt release/
|
|
||||||
{
|
|
||||||
echo 'release_note<<EOF'
|
|
||||||
cat release_notes.md
|
|
||||||
echo 'EOF'
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Publish release on Forgejo
|
|
||||||
uses: actions/forgejo-release@v1
|
|
||||||
with:
|
|
||||||
direction: upload
|
|
||||||
url: https://gitea.zionetrix.net
|
|
||||||
token: ${{ secrets.forgejo_token }}
|
|
||||||
release-dir: release
|
|
||||||
release-notes: ${{ steps.create-release.outputs.release_note }}
|
|
||||||
|
|
||||||
publish-aptly:
|
|
||||||
runs-on: docker
|
|
||||||
container:
|
|
||||||
image: docker.io/brenard/aptly-publish:latest
|
|
||||||
steps:
|
|
||||||
- name: "Download Debian package files"
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
|
|
||||||
- name: "Publish Debian package on Aptly repository"
|
|
||||||
uses: https://gitea.zionetrix.net/bn8/aptly-publish@master
|
|
||||||
with:
|
|
||||||
api_url: ${{ vars.apt_api_url }}
|
|
||||||
api_username: ${{ vars.apt_api_username }}
|
|
||||||
api_password: ${{ secrets.apt_api_password }}
|
|
||||||
repo_name: ${{ vars.apt_repo_name }}
|
|
||||||
path: "./"
|
|
||||||
source_name: ${{ vars.apt_source_name }}
|
|
|
@ -1,21 +0,0 @@
|
||||||
---
|
|
||||||
name: Run tests
|
|
||||||
on: [push]
|
|
||||||
jobs:
|
|
||||||
test-precommit:
|
|
||||||
runs-on: docker
|
|
||||||
container:
|
|
||||||
image: docker.io/brenard/python-pre-commit:latest
|
|
||||||
steps:
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install python dependencies
|
|
||||||
env:
|
|
||||||
DEBIAN_FRONTEND: noninteractive
|
|
||||||
run: |
|
|
||||||
apt-get -qq update
|
|
||||||
apt-get -qq -y install --no-install-recommends python3-ldap
|
|
||||||
- name: Run pre-commit
|
|
||||||
run: pre-commit run --all-files
|
|
|
@ -1,64 +0,0 @@
|
||||||
# Pre-commit hooks to run tests and ensure code is cleaned.
|
|
||||||
# See https://pre-commit.com for more information
|
|
||||||
---
|
|
||||||
repos:
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
||||||
rev: v0.1.6
|
|
||||||
hooks:
|
|
||||||
- id: ruff
|
|
||||||
args: ["--fix"]
|
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
|
||||||
rev: v3.15.0
|
|
||||||
hooks:
|
|
||||||
- id: pyupgrade
|
|
||||||
args: ["--keep-percent-format", "--py37-plus"]
|
|
||||||
- repo: https://github.com/psf/black
|
|
||||||
rev: 23.11.0
|
|
||||||
hooks:
|
|
||||||
- id: black
|
|
||||||
args: ["--target-version", "py37", "--line-length", "100"]
|
|
||||||
- repo: https://github.com/PyCQA/isort
|
|
||||||
rev: 5.12.0
|
|
||||||
hooks:
|
|
||||||
- id: isort
|
|
||||||
args: ["--profile", "black", "--line-length", "100"]
|
|
||||||
- repo: https://github.com/PyCQA/flake8
|
|
||||||
rev: 6.1.0
|
|
||||||
hooks:
|
|
||||||
- id: flake8
|
|
||||||
args: ["--max-line-length=100"]
|
|
||||||
- repo: https://github.com/codespell-project/codespell
|
|
||||||
rev: v2.2.2
|
|
||||||
hooks:
|
|
||||||
- id: codespell
|
|
||||||
args:
|
|
||||||
- --ignore-words-list=exten
|
|
||||||
- --skip="./.*,*.csv,*.json,*.ini,*.subject,*.txt,*.html,*.log,*.conf"
|
|
||||||
- --quiet-level=2
|
|
||||||
- --ignore-regex=.*codespell-ignore$
|
|
||||||
# - --write-changes # Uncomment to write changes
|
|
||||||
exclude_types: [csv, json]
|
|
||||||
- repo: https://github.com/adrienverge/yamllint
|
|
||||||
rev: v1.32.0
|
|
||||||
hooks:
|
|
||||||
- id: yamllint
|
|
||||||
ignore: .github/
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
|
||||||
rev: v2.7.1
|
|
||||||
hooks:
|
|
||||||
- id: prettier
|
|
||||||
args: ["--print-width", "100"]
|
|
||||||
- repo: local
|
|
||||||
hooks:
|
|
||||||
- id: pylint
|
|
||||||
name: pylint
|
|
||||||
entry: pylint
|
|
||||||
language: system
|
|
||||||
types: [python]
|
|
||||||
require_serial: true
|
|
||||||
- repo: https://github.com/PyCQA/bandit
|
|
||||||
rev: 1.7.5
|
|
||||||
hooks:
|
|
||||||
- id: bandit
|
|
||||||
args: [--skip, "B101", --recursive]
|
|
||||||
minimum_pre_commit_version: 3.2.0
|
|
70
.woodpecker.yml
Normal file
70
.woodpecker.yml
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
clone:
|
||||||
|
git:
|
||||||
|
image: woodpeckerci/plugin-git
|
||||||
|
tags: true
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
test-pylint:
|
||||||
|
group: test
|
||||||
|
image: debian
|
||||||
|
commands:
|
||||||
|
- DEBIAN_FRONTEND=noninteractive apt-get -qq update < /dev/null > /dev/null
|
||||||
|
- DEBIAN_FRONTEND=noninteractive apt-get -qq -y install --no-install-recommends python3-ldap pylint3 < /dev/null > /dev/null
|
||||||
|
- python3 -m pylint check_syncrepl_extended
|
||||||
|
|
||||||
|
test-flake8:
|
||||||
|
group: test
|
||||||
|
image: pipelinecomponents/flake8
|
||||||
|
commands:
|
||||||
|
- flake8 check_syncrepl_extended
|
||||||
|
|
||||||
|
build:
|
||||||
|
image: brenard/debian-python-deb
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
commands:
|
||||||
|
- echo "$GPG_KEY"|base64 -d|gpg --import
|
||||||
|
- ./build.sh --quiet
|
||||||
|
secrets: [ maintainer_name, maintainer_email, gpg_key, debian_codename ]
|
||||||
|
|
||||||
|
publish-dryrun:
|
||||||
|
group: publish
|
||||||
|
image: alpine
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
commands:
|
||||||
|
- ls dist/* dist/check-syncrepl-extended-*/check_syncrepl_extended
|
||||||
|
|
||||||
|
publish-gitea:
|
||||||
|
group: publish
|
||||||
|
image: plugins/gitea-release
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
settings:
|
||||||
|
api_key:
|
||||||
|
from_secret: gitea_token
|
||||||
|
base_url: https://gitea.zionetrix.net
|
||||||
|
note: dist/release_notes.md
|
||||||
|
files:
|
||||||
|
- dist/check-syncrepl-extended-*/check_syncrepl_extended
|
||||||
|
- dist/*.deb
|
||||||
|
checksum:
|
||||||
|
- md5
|
||||||
|
- sha512
|
||||||
|
|
||||||
|
publish-apt:
|
||||||
|
group: publish
|
||||||
|
image: brenard/aptly-publish
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
settings:
|
||||||
|
api_url:
|
||||||
|
from_secret: apt_api_url
|
||||||
|
api_username:
|
||||||
|
from_secret: apt_api_username
|
||||||
|
api_password:
|
||||||
|
from_secret: apt_api_password
|
||||||
|
repo_name:
|
||||||
|
from_secret: apt_repo_name
|
||||||
|
path: dist
|
||||||
|
source_name: check-syncrepl-extended
|
29
README.md
29
README.md
|
@ -1,21 +1,19 @@
|
||||||
# Script to check LDAP syncrepl replication state between two servers
|
# Script to check LDAP syncrepl replication state between two servers
|
||||||
|
|
||||||
This script check LDAP syncrepl replication state between two servers.
|
This script check LDAP syncrepl replication state between two servers.
|
||||||
One server is consider as provider and the other as consumer.
|
One server is consider as provider and the other as consumer.
|
||||||
|
|
||||||
This script can check replication state with two method :
|
This script can check replication state with two method :
|
||||||
|
- by the fisrt, entryCSN of all entries of LDAP directory will be compare between two servers
|
||||||
|
- by the second, all values of all atributes of all entries will be compare between two servers.
|
||||||
|
|
||||||
- by the first, entryCSN of all entries of LDAP directory will be compare between two servers
|
In all case, contextCSN of servers will be compare and entries not present in consumer or in provider will be notice. You can decide to disable contextCSN verification by using argument *--no-check-contextCSN*.
|
||||||
- by the second, all values of all attributes of all entries will be compare between two servers.
|
|
||||||
|
|
||||||
In all case, contextCSN of servers will be compare and entries not present in consumer or in provider will be notice. You can decide to disable contextCSN verification by using argument _--no-check-contextCSN_.
|
This script is also able to *"touch"* LDAP object on provider to force synchronisation of this object. This mechanism consist to add *'%%TOUCH%%'* value to an attribute of this object and remove it just after. The
|
||||||
|
touched attribute is specify by parameter *--touch*. Of course, couple of DN and password provided, must have write right on this attribute.
|
||||||
|
|
||||||
This script is also able to _"touch"_ LDAP object on provider to force synchronisation of this object. This mechanism consist to add _'%%TOUCH%%'_ value to an attribute of this object and remove it just after. The
|
If your prefer, you can use *--replace-touch* parameter to replace value of touched attribute instead of adding the touched value. Use-ful in case of single-value attribute.
|
||||||
touched attribute is specify by parameter _--touch_. Of course, couple of DN and password provided, must have write right on this attribute.
|
|
||||||
|
|
||||||
If your prefer, you can use _--replace-touch_ parameter to replace value of touched attribute instead of adding the touched value. Use-ful in case of single-value attribute.
|
To use this script as an Icinga (or Nagios) plugin, use *-n* argument
|
||||||
|
|
||||||
To use this script as an Icinga (or Nagios) plugin, use _-n_ argument
|
|
||||||
|
|
||||||
## Requirement
|
## Requirement
|
||||||
|
|
||||||
|
@ -23,13 +21,12 @@ A single couple of DN and password able to connect to both server and without re
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
- python 3 (for python 2.7 compatibility, see python2.7 branch)
|
* python 3 (for python 2.7 compatibility, see python2.7 branch)
|
||||||
- python-ldap
|
* python-ldap
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### If you plan to use it with NRPE
|
### If you plan to use it with NRPE
|
||||||
|
|
||||||
```
|
```
|
||||||
apt install -y python3-ldap git
|
apt install -y python3-ldap git
|
||||||
git clone https://gitea.zionetrix.net/bn8/check_syncrepl_extended.git /usr/local/src/check_syncrepl_extended
|
git clone https://gitea.zionetrix.net/bn8/check_syncrepl_extended.git /usr/local/src/check_syncrepl_extended
|
||||||
|
@ -42,7 +39,6 @@ service nagios-nrpe-server reload
|
||||||
```
|
```
|
||||||
|
|
||||||
### Otherwise
|
### Otherwise
|
||||||
|
|
||||||
```
|
```
|
||||||
apt install python3-ldap git
|
apt install python3-ldap git
|
||||||
git clone https://gitea.zionetrix.net/bn8/check_syncrepl_extended.git /usr/local/src/check_syncrepl_extended
|
git clone https://gitea.zionetrix.net/bn8/check_syncrepl_extended.git /usr/local/src/check_syncrepl_extended
|
||||||
|
@ -50,7 +46,6 @@ ln -s /usr/local/src/check_syncrepl_extended/check_syncrepl_extended /usr/local/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: check_syncrepl_extended [-h] [-v] [-p PROVIDER] [-c CONSUMER]
|
usage: check_syncrepl_extended [-h] [-v] [-p PROVIDER] [-c CONSUMER]
|
||||||
[-i SERVERID] [-T] [-D DN] [-P PWD] [-b BASEDN]
|
[-i SERVERID] [-T] [-D DN] [-P PWD] [-b BASEDN]
|
||||||
|
@ -95,7 +90,7 @@ optional arguments:
|
||||||
Don't check servers contextCSN (Default: False)
|
Don't check servers contextCSN (Default: False)
|
||||||
-a, --attributes Check attributes values (Default: check only entryCSN)
|
-a, --attributes Check attributes values (Default: check only entryCSN)
|
||||||
--exclude-attributes EXCL_ATTRS
|
--exclude-attributes EXCL_ATTRS
|
||||||
Don't check this attribute (only in attribute check
|
Don't check this attribut (only in attribute check
|
||||||
mode)
|
mode)
|
||||||
--touch TOUCH Touch attribute giving in parameter to force resync a
|
--touch TOUCH Touch attribute giving in parameter to force resync a
|
||||||
this LDAP object from provider. A value '%TOUCH%' will
|
this LDAP object from provider. A value '%TOUCH%' will
|
||||||
|
@ -120,6 +115,6 @@ Copyright (c) 2017 Benjamin Renard
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation.
|
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
|
2
build.sh
2
build.sh
|
@ -24,7 +24,7 @@ sed -i "s/^VERSION *=.*$/VERSION = '$VERSION'/" $BDIR/check_syncrepl_extended
|
||||||
|
|
||||||
if [ -z "$DEBIAN_CODENAME" ]
|
if [ -z "$DEBIAN_CODENAME" ]
|
||||||
then
|
then
|
||||||
echo "Retrieve debian codename using lsb_release..."
|
echo "Retreive debian codename using lsb_release..."
|
||||||
DEBIAN_CODENAME=$( lsb_release -c -s )
|
DEBIAN_CODENAME=$( lsb_release -c -s )
|
||||||
else
|
else
|
||||||
echo "Use debian codename from environment ($DEBIAN_CODENAME)"
|
echo "Use debian codename from environment ($DEBIAN_CODENAME)"
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
# One server is consider as provider and the other as consumer.
|
# One server is consider as provider and the other as consumer.
|
||||||
#
|
#
|
||||||
# This script can check replication state with two method :
|
# This script can check replication state with two method :
|
||||||
# - by the first, entryCSN of all entries of LDAP directory will be
|
# - by the fisrt, entryCSN of all entries of LDAP directory will be
|
||||||
# compare between two servers
|
# compare between two servers
|
||||||
# - by the second, all values of all attributes of all entries will
|
# - by the second, all values of all atributes of all entries will
|
||||||
# be compare between two servers.
|
# be compare between two servers.
|
||||||
#
|
#
|
||||||
# In all case, contextCSN of servers will be compare and entries not
|
# In all case, contextCSN of servers will be compare and entries not
|
||||||
|
@ -35,48 +35,47 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import getpass
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import getpass
|
||||||
|
|
||||||
import ldap
|
import ldap
|
||||||
from ldap import LDAPError # pylint: disable=no-name-in-module
|
from ldap import LDAPError # pylint: disable=no-name-in-module
|
||||||
from ldap import modlist
|
|
||||||
from ldap.controls import SimplePagedResultsControl
|
from ldap.controls import SimplePagedResultsControl
|
||||||
|
from ldap import modlist
|
||||||
|
|
||||||
VERSION = "0.0"
|
VERSION = '0.0'
|
||||||
TOUCH_VALUE = b"%%TOUCH%%"
|
TOUCH_VALUE = b'%%TOUCH%%'
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description=("Script to check LDAP syncrepl replication state between two servers."),
|
description=(
|
||||||
|
"Script to check LDAP syncrepl replication state between "
|
||||||
|
"two servers."),
|
||||||
epilog=(
|
epilog=(
|
||||||
"Author: Benjamin Renard <brenard@easter-eggs.com>, "
|
'Author: Benjamin Renard <brenard@easter-eggs.com>, '
|
||||||
f"Version: {VERSION}, "
|
f'Version: {VERSION}, '
|
||||||
"Source: https://gitea.zionetrix.net/bn8/check_syncrepl_extended"
|
'Source: https://gitea.zionetrix.net/bn8/check_syncrepl_extended')
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-p",
|
"-p", "--provider",
|
||||||
"--provider",
|
|
||||||
dest="provider",
|
dest="provider",
|
||||||
action="store",
|
action="store",
|
||||||
type=str,
|
type=str,
|
||||||
help="LDAP provider URI (example: ldaps://ldapmaster.foo:636)",
|
help="LDAP provider URI (example: ldaps://ldapmaster.foo:636)"
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-c",
|
"-c", "--consumer",
|
||||||
"--consumer",
|
|
||||||
dest="consumer",
|
dest="consumer",
|
||||||
action="store",
|
action="store",
|
||||||
type=str,
|
type=str,
|
||||||
help="LDAP consumer URI (example: ldaps://ldapslave.foo:636)",
|
help="LDAP consumer URI (example: ldaps://ldapslave.foo:636)"
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-i",
|
"-i", "--serverID",
|
||||||
"--serverID",
|
|
||||||
dest="serverid",
|
dest="serverid",
|
||||||
action="store",
|
action="store",
|
||||||
type=int,
|
type=int,
|
||||||
|
@ -85,67 +84,74 @@ parser.add_argument(
|
||||||
"setups where each master has a unique ID and a contextCSN for "
|
"setups where each master has a unique ID and a contextCSN for "
|
||||||
"each replicated master exists. A valid serverID is a integer "
|
"each replicated master exists. A valid serverID is a integer "
|
||||||
"value from 0 to 4095 (limited to 3 hex digits, example: '12' "
|
"value from 0 to 4095 (limited to 3 hex digits, example: '12' "
|
||||||
"compares the contextCSN matching '#00C#')"
|
"compares the contextCSN matching '#00C#')"),
|
||||||
),
|
default=False
|
||||||
default=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-T",
|
"-T", "--starttls",
|
||||||
"--starttls",
|
|
||||||
dest="starttls",
|
dest="starttls",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Start TLS on LDAP provider/consumers connections",
|
help="Start TLS on LDAP provider/consumers connections",
|
||||||
default=False,
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-D",
|
"-D", "--dn",
|
||||||
"--dn",
|
|
||||||
dest="dn",
|
dest="dn",
|
||||||
action="store",
|
action="store",
|
||||||
type=str,
|
type=str,
|
||||||
help="LDAP bind DN (example: uid=nagios,ou=sysaccounts,o=example",
|
help="LDAP bind DN (example: uid=nagios,ou=sysaccounts,o=example"
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-P", "--pwd", dest="pwd", action="store", type=str, help="LDAP bind password", default=None
|
"-P", "--pwd",
|
||||||
|
dest="pwd",
|
||||||
|
action="store",
|
||||||
|
type=str,
|
||||||
|
help="LDAP bind password",
|
||||||
|
default=None
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-b",
|
"-b", "--basedn",
|
||||||
"--basedn",
|
|
||||||
dest="basedn",
|
dest="basedn",
|
||||||
action="store",
|
action="store",
|
||||||
type=str,
|
type=str,
|
||||||
help="LDAP base DN (example: o=example)",
|
help="LDAP base DN (example: o=example)"
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-f",
|
"-f", "--filter",
|
||||||
"--filter",
|
|
||||||
dest="filterstr",
|
dest="filterstr",
|
||||||
action="store",
|
action="store",
|
||||||
type=str,
|
type=str,
|
||||||
help="LDAP filter (default: (objectClass=*))",
|
help="LDAP filter (default: (objectClass=*))",
|
||||||
default="(objectClass=*)",
|
default='(objectClass=*)'
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-d", "--debug", dest="debug", action="store_true", help="Debug mode", default=False
|
"-d", "--debug",
|
||||||
|
dest="debug",
|
||||||
|
action="store_true",
|
||||||
|
help="Debug mode",
|
||||||
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-n",
|
"-n", "--nagios",
|
||||||
"--nagios",
|
|
||||||
dest="nagios",
|
dest="nagios",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Nagios check plugin mode",
|
help="Nagios check plugin mode",
|
||||||
default=False,
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-q", "--quiet", dest="quiet", action="store_true", help="Quiet mode", default=False
|
"-q", "--quiet",
|
||||||
|
dest="quiet",
|
||||||
|
action="store_true",
|
||||||
|
help="Quiet mode",
|
||||||
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
@ -153,7 +159,7 @@ parser.add_argument(
|
||||||
dest="nocheckcert",
|
dest="nocheckcert",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Don't check the server certificate (Default: False)",
|
help="Don't check the server certificate (Default: False)",
|
||||||
default=False,
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
@ -161,24 +167,25 @@ parser.add_argument(
|
||||||
dest="nocheckcontextcsn",
|
dest="nocheckcontextcsn",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Don't check servers contextCSN (Default: False)",
|
help="Don't check servers contextCSN (Default: False)",
|
||||||
default=False,
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--only-check-contextCSN",
|
"--only-check-contextCSN",
|
||||||
dest="onlycheckcontextcsn",
|
dest="onlycheckcontextcsn",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help=("Only check servers root contextCSN (objects check disabled, default : False)"),
|
help=(
|
||||||
default=False,
|
"Only check servers root contextCSN (objects check disabled, "
|
||||||
|
"default : False)"),
|
||||||
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-a",
|
"-a", "--attributes",
|
||||||
"--attributes",
|
|
||||||
dest="attrs",
|
dest="attrs",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Check attributes values (Default: check only entryCSN)",
|
help="Check attributes values (Default: check only entryCSN)",
|
||||||
default=False,
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
@ -186,8 +193,8 @@ parser.add_argument(
|
||||||
dest="excl_attrs",
|
dest="excl_attrs",
|
||||||
action="store",
|
action="store",
|
||||||
type=str,
|
type=str,
|
||||||
help="Don't check this attribute (only in attribute check mode)",
|
help="Don't check this attribut (only in attribute check mode)",
|
||||||
default=None,
|
default=None
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
@ -196,13 +203,13 @@ parser.add_argument(
|
||||||
action="store",
|
action="store",
|
||||||
type=str,
|
type=str,
|
||||||
help=(
|
help=(
|
||||||
"Touch attribute giving in parameter to force resync a this LDAP "
|
'Touch attribute giving in parameter to force resync a this LDAP '
|
||||||
f'object from provider. A value "{TOUCH_VALUE.decode()}" will be '
|
f'object from provider. A value "{TOUCH_VALUE.decode()}" will be '
|
||||||
"add to this attribute and remove after. The user use to connect "
|
'add to this attribute and remove after. The user use to connect '
|
||||||
"to the LDAP directory must have write permission on this "
|
'to the LDAP directory must have write permission on this '
|
||||||
"attribute on each object."
|
'attribute on each object.'
|
||||||
),
|
),
|
||||||
default=None,
|
default=None
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
@ -210,7 +217,7 @@ parser.add_argument(
|
||||||
dest="replacetouch",
|
dest="replacetouch",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="In touch mode, replace value instead of adding.",
|
help="In touch mode, replace value instead of adding.",
|
||||||
default=False,
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
@ -218,7 +225,7 @@ parser.add_argument(
|
||||||
dest="removetouchvalue",
|
dest="removetouchvalue",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="In touch mode, remove touch value if present.",
|
help="In touch mode, remove touch value if present.",
|
||||||
default=False,
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
@ -226,8 +233,10 @@ parser.add_argument(
|
||||||
dest="page_size",
|
dest="page_size",
|
||||||
action="store",
|
action="store",
|
||||||
type=int,
|
type=int,
|
||||||
help=("Page size: if defined, paging control using LDAP v3 extended control will be enabled."),
|
help=(
|
||||||
default=None,
|
"Page size: if defined, paging control using LDAP v3 extended "
|
||||||
|
"control will be enabled."),
|
||||||
|
default=None
|
||||||
)
|
)
|
||||||
|
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
|
@ -235,8 +244,7 @@ options = parser.parse_args()
|
||||||
if options.nocheckcontextcsn and options.onlycheckcontextcsn:
|
if options.nocheckcontextcsn and options.onlycheckcontextcsn:
|
||||||
parser.error(
|
parser.error(
|
||||||
"You can't use both --no-check-contextCSN and "
|
"You can't use both --no-check-contextCSN and "
|
||||||
"--only-check-contextCSN parameters and the same time"
|
"--only-check-contextCSN parameters and the same time")
|
||||||
)
|
|
||||||
if options.nagios:
|
if options.nagios:
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@ -256,14 +264,14 @@ if not options.basedn:
|
||||||
|
|
||||||
if not 0 <= options.serverid <= 4095:
|
if not 0 <= options.serverid <= 4095:
|
||||||
parser.error(
|
parser.error(
|
||||||
"ServerID should be a integer value from 0 to 4095 (limited to 3 hexadecimal digits)."
|
"ServerID should be a integer value from 0 to 4095 "
|
||||||
)
|
"(limited to 3 hexadecimal digits).")
|
||||||
if options.nagios:
|
if options.nagios:
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if options.touch and not options.attrs:
|
if options.touch and not options.attrs:
|
||||||
logging.info("Force option attrs on touch mode")
|
logging.info('Force option attrs on touch mode')
|
||||||
options.attrs = True
|
options.attrs = True
|
||||||
|
|
||||||
if options.dn and options.pwd is None:
|
if options.dn and options.pwd is None:
|
||||||
|
@ -271,7 +279,7 @@ if options.dn and options.pwd is None:
|
||||||
|
|
||||||
excl_attrs = []
|
excl_attrs = []
|
||||||
if options.excl_attrs:
|
if options.excl_attrs:
|
||||||
for ex in options.excl_attrs.split(","):
|
for ex in options.excl_attrs.split(','):
|
||||||
excl_attrs.append(ex.strip())
|
excl_attrs.append(ex.strip())
|
||||||
|
|
||||||
FORMAT = "%(asctime)s - %(levelname)s: %(message)s"
|
FORMAT = "%(asctime)s - %(levelname)s: %(message)s"
|
||||||
|
@ -288,9 +296,10 @@ else:
|
||||||
|
|
||||||
|
|
||||||
class LdapServer:
|
class LdapServer:
|
||||||
uri = None
|
|
||||||
dn = None
|
uri = ""
|
||||||
pwd = None
|
dn = ""
|
||||||
|
pwd = ""
|
||||||
start_tls = False
|
start_tls = False
|
||||||
|
|
||||||
con = 0
|
con = 0
|
||||||
|
@ -321,20 +330,21 @@ class LdapServer:
|
||||||
def getContextCSN(self, basedn=False, serverid=False):
|
def getContextCSN(self, basedn=False, serverid=False):
|
||||||
if not basedn:
|
if not basedn:
|
||||||
basedn = self.dn
|
basedn = self.dn
|
||||||
data = self.search(basedn, "(objectclass=*)", attrs=["contextCSN"], scope="base")
|
data = self.search(
|
||||||
|
basedn, '(objectclass=*)', attrs=['contextCSN'], scope='base')
|
||||||
if data:
|
if data:
|
||||||
contextCSNs = data[0][0][1]["contextCSN"]
|
contextCSNs = data[0][0][1]['contextCSN']
|
||||||
logging.debug("Found contextCSNs %s", contextCSNs)
|
logging.debug('Found contextCSNs %s', contextCSNs)
|
||||||
if serverid is False:
|
if serverid is False:
|
||||||
return contextCSNs[0]
|
return contextCSNs[0]
|
||||||
csnid = str(format(serverid, "X")).zfill(3)
|
csnid = str(format(serverid, 'X')).zfill(3)
|
||||||
sub = str.encode(f"#{csnid}#", encoding="ascii", errors="replace")
|
sub = str.encode(f'#{csnid}#', encoding="ascii", errors="replace")
|
||||||
CSN = [s for s in contextCSNs if sub in s]
|
CSN = [s for s in contextCSNs if sub in s]
|
||||||
if not CSN:
|
if not CSN:
|
||||||
logging.error(
|
logging.error(
|
||||||
"No contextCSN matching with ServerID %s (=%s) could be found.",
|
"No contextCSN matching with ServerID %s (=%s) could be "
|
||||||
serverid,
|
"found.",
|
||||||
sub,
|
serverid, sub
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
return CSN[0]
|
return CSN[0]
|
||||||
|
@ -342,19 +352,21 @@ class LdapServer:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_scope(scope):
|
def get_scope(scope):
|
||||||
if scope == "base":
|
if scope == 'base':
|
||||||
return ldap.SCOPE_BASE # pylint: disable=no-member
|
return ldap.SCOPE_BASE # pylint: disable=no-member
|
||||||
if scope == "one":
|
if scope == 'one':
|
||||||
return ldap.SCOPE_ONELEVEL # pylint: disable=no-member
|
return ldap.SCOPE_ONELEVEL # pylint: disable=no-member
|
||||||
if scope == "sub":
|
if scope == 'sub':
|
||||||
return ldap.SCOPE_SUBTREE # pylint: disable=no-member
|
return ldap.SCOPE_SUBTREE # pylint: disable=no-member
|
||||||
raise Exception(f'Unknown LDAP scope "{scope}"') # pylint: disable=broad-exception-raised
|
raise Exception(f'Unknown LDAP scope "{scope}"')
|
||||||
|
|
||||||
def search(self, basedn, filterstr, attrs=None, scope=None):
|
def search(self, basedn, filterstr, attrs=None, scope=None):
|
||||||
if self.page_size:
|
if self.page_size:
|
||||||
return self.paged_search(basedn, filterstr, attrs=attrs, scope=scope)
|
return self.paged_search(
|
||||||
|
basedn, filterstr, attrs=attrs, scope=scope)
|
||||||
res_id = self.con.search(
|
res_id = self.con.search(
|
||||||
basedn, self.get_scope(scope if scope else "sub"), filterstr, attrs if attrs else []
|
basedn, self.get_scope(scope if scope else 'sub'),
|
||||||
|
filterstr, attrs if attrs else []
|
||||||
)
|
)
|
||||||
ret = []
|
ret = []
|
||||||
while 1:
|
while 1:
|
||||||
|
@ -368,16 +380,13 @@ class LdapServer:
|
||||||
def paged_search(self, basedn, filterstr, attrs=None, scope=None):
|
def paged_search(self, basedn, filterstr, attrs=None, scope=None):
|
||||||
ret = []
|
ret = []
|
||||||
page = 0
|
page = 0
|
||||||
pg_ctrl = SimplePagedResultsControl(True, self.page_size, "")
|
pg_ctrl = SimplePagedResultsControl(True, self.page_size, '')
|
||||||
while page == 0 or pg_ctrl.cookie:
|
while page == 0 or pg_ctrl.cookie:
|
||||||
page += 1
|
page += 1
|
||||||
logging.debug("Page search: loading page %d", page)
|
logging.debug('Page search: loading page %d', page)
|
||||||
res_id = self.con.search_ext(
|
res_id = self.con.search_ext(
|
||||||
basedn,
|
basedn, self.get_scope(scope if scope else 'sub'),
|
||||||
self.get_scope(scope if scope else "sub"),
|
filterstr, attrs if attrs else [], serverctrls=[pg_ctrl]
|
||||||
filterstr,
|
|
||||||
attrs if attrs else [],
|
|
||||||
serverctrls=[pg_ctrl],
|
|
||||||
)
|
)
|
||||||
# pylint: disable=unused-variable
|
# pylint: disable=unused-variable
|
||||||
res_type, res_data, res_id, serverctrls = self.con.result3(res_id)
|
res_type, res_data, res_id, serverctrls = self.con.result3(res_id)
|
||||||
|
@ -394,11 +403,11 @@ class LdapServer:
|
||||||
if not ldif:
|
if not ldif:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
logging.debug("Update object %s: %s", dn, ldif)
|
logging.debug('Update object %s: %s', dn, ldif)
|
||||||
self.con.modify_s(dn, ldif)
|
self.con.modify_s(dn, ldif)
|
||||||
return True
|
return True
|
||||||
except LDAPError:
|
except LDAPError:
|
||||||
logging.error("Error updating object %s", dn, exc_info=True)
|
logging.error('Error updating object %s', dn, exc_info=True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -423,9 +432,14 @@ class LdapServer:
|
||||||
else:
|
else:
|
||||||
new[attr].append(TOUCH_VALUE)
|
new[attr].append(TOUCH_VALUE)
|
||||||
try:
|
try:
|
||||||
logging.info('Touch object "%s" on attribute "%s": %s => %s', dn, attr, old, new)
|
logging.info(
|
||||||
|
'Touch object "%s" on attribute "%s": %s => %s',
|
||||||
|
dn, attr, old, new
|
||||||
|
)
|
||||||
if self.update_object(dn, old, new):
|
if self.update_object(dn, old, new):
|
||||||
logging.info('Restore original value of attribute "%s" of object "%s"', attr, dn)
|
logging.info(
|
||||||
|
'Restore original value of attribute "%s" of object "%s"',
|
||||||
|
attr, dn)
|
||||||
if options.removetouchvalue and TOUCH_VALUE in old[attr]:
|
if options.removetouchvalue and TOUCH_VALUE in old[attr]:
|
||||||
old[attr].remove(TOUCH_VALUE)
|
old[attr].remove(TOUCH_VALUE)
|
||||||
self.update_object(dn=dn, old=new, new=old)
|
self.update_object(dn=dn, old=new, new=old)
|
||||||
|
@ -437,7 +451,8 @@ class LdapServer:
|
||||||
|
|
||||||
if options.nocheckcert:
|
if options.nocheckcert:
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
ldap.set_option(
|
||||||
|
ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
||||||
|
|
||||||
servers = [options.provider, options.consumer]
|
servers = [options.provider, options.consumer]
|
||||||
|
|
||||||
|
@ -446,36 +461,44 @@ LdapObjects = {}
|
||||||
LdapServersCSN = {}
|
LdapServersCSN = {}
|
||||||
|
|
||||||
for srv in servers:
|
for srv in servers:
|
||||||
logging.info("Connect to %s", srv)
|
logging.info('Connect to %s', srv)
|
||||||
LdapServers[srv] = LdapServer(
|
LdapServers[srv] = LdapServer(srv, options.dn, options.pwd,
|
||||||
srv, options.dn, options.pwd, options.starttls, page_size=options.page_size
|
options.starttls,
|
||||||
)
|
page_size=options.page_size)
|
||||||
|
|
||||||
if not LdapServers[srv].connect():
|
if not LdapServers[srv].connect():
|
||||||
if options.nagios:
|
if options.nagios:
|
||||||
print(f"UNKWNON - Failed to connect to {srv}")
|
print(f'UNKWNON - Failed to connect to {srv}')
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
else:
|
else:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if not options.nocheckcontextcsn:
|
if not options.nocheckcontextcsn:
|
||||||
LdapServersCSN[srv] = LdapServers[srv].getContextCSN(options.basedn, options.serverid)
|
LdapServersCSN[srv] = LdapServers[srv].getContextCSN(
|
||||||
logging.info("ContextCSN of %s: %s", srv, LdapServersCSN[srv])
|
options.basedn, options.serverid)
|
||||||
|
logging.info('ContextCSN of %s: %s', srv, LdapServersCSN[srv])
|
||||||
|
|
||||||
if not options.onlycheckcontextcsn:
|
if not options.onlycheckcontextcsn:
|
||||||
logging.info("List objects from %s", srv)
|
logging.info('List objects from %s', srv)
|
||||||
LdapObjects[srv] = {}
|
LdapObjects[srv] = {}
|
||||||
|
|
||||||
if options.attrs:
|
if options.attrs:
|
||||||
for obj in LdapServers[srv].search(options.basedn, options.filterstr, []):
|
for obj in LdapServers[srv].search(
|
||||||
logging.debug("Found on %s: %s", srv, obj[0][0])
|
options.basedn, options.filterstr, []
|
||||||
|
):
|
||||||
|
logging.debug('Found on %s: %s', srv, obj[0][0])
|
||||||
LdapObjects[srv][obj[0][0]] = obj[0][1]
|
LdapObjects[srv][obj[0][0]] = obj[0][1]
|
||||||
else:
|
else:
|
||||||
for obj in LdapServers[srv].search(options.basedn, options.filterstr, ["entryCSN"]):
|
for obj in LdapServers[srv].search(
|
||||||
logging.debug("Found on %s: %s / %s", srv, obj[0][0], obj[0][1]["entryCSN"][0])
|
options.basedn, options.filterstr, ['entryCSN']
|
||||||
LdapObjects[srv][obj[0][0]] = obj[0][1]["entryCSN"][0]
|
):
|
||||||
|
logging.debug(
|
||||||
|
'Found on %s: %s / %s',
|
||||||
|
srv, obj[0][0], obj[0][1]['entryCSN'][0]
|
||||||
|
)
|
||||||
|
LdapObjects[srv][obj[0][0]] = obj[0][1]['entryCSN'][0]
|
||||||
|
|
||||||
logging.info("%s objects founds", len(LdapObjects[srv]))
|
logging.info('%s objects founds', len(LdapObjects[srv]))
|
||||||
|
|
||||||
|
|
||||||
if not options.onlycheckcontextcsn:
|
if not options.onlycheckcontextcsn:
|
||||||
|
@ -487,11 +510,14 @@ if not options.onlycheckcontextcsn:
|
||||||
not_sync[srv] = []
|
not_sync[srv] = []
|
||||||
|
|
||||||
if options.attrs:
|
if options.attrs:
|
||||||
logging.info("Check if objects a are synchronized (by comparing attributes's values)")
|
logging.info(
|
||||||
|
"Check if objects a are synchronized (by comparing attributes's "
|
||||||
|
"values)")
|
||||||
else:
|
else:
|
||||||
logging.info("Check if objects are synchronized (by comparing entryCSN)")
|
logging.info(
|
||||||
|
'Check if objets are synchronized (by comparing entryCSN)')
|
||||||
for obj in LdapObjects[options.provider]:
|
for obj in LdapObjects[options.provider]:
|
||||||
logging.debug("Check obj %s", obj)
|
logging.debug('Check obj %s', obj)
|
||||||
for srv_name, srv in LdapObjects.items():
|
for srv_name, srv in LdapObjects.items():
|
||||||
if srv_name == options.provider:
|
if srv_name == options.provider:
|
||||||
continue
|
continue
|
||||||
|
@ -507,9 +533,7 @@ if not options.onlycheckcontextcsn:
|
||||||
attrs_list.append(attr)
|
attrs_list.append(attr)
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"Obj %s not synchronized: %s not present on %s",
|
"Obj %s not synchronized: %s not present on %s",
|
||||||
obj,
|
obj, ','.join(attrs_list), srv_name
|
||||||
",".join(attrs_list),
|
|
||||||
srv_name,
|
|
||||||
)
|
)
|
||||||
touch = True
|
touch = True
|
||||||
else:
|
else:
|
||||||
|
@ -519,8 +543,7 @@ if not options.onlycheckcontextcsn:
|
||||||
attrs_list.append(attr)
|
attrs_list.append(attr)
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"Obj %s not synchronized: %s not same value(s)",
|
"Obj %s not synchronized: %s not same value(s)",
|
||||||
obj,
|
obj, ','.join(attrs_list)
|
||||||
",".join(attrs_list),
|
|
||||||
)
|
)
|
||||||
touch = True
|
touch = True
|
||||||
if attrs_list:
|
if attrs_list:
|
||||||
|
@ -528,29 +551,29 @@ if not options.onlycheckcontextcsn:
|
||||||
else:
|
else:
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"Obj %s not synchronized: %s <-> %s",
|
"Obj %s not synchronized: %s <-> %s",
|
||||||
obj,
|
obj, LdapObjects[options.provider][obj], srv[obj]
|
||||||
LdapObjects[options.provider][obj],
|
|
||||||
srv[obj],
|
|
||||||
)
|
)
|
||||||
not_sync[srv_name].append(obj)
|
not_sync[srv_name].append(obj)
|
||||||
if touch and options.touch:
|
if touch and options.touch:
|
||||||
orig_value = []
|
orig_value = []
|
||||||
if options.touch in LdapObjects[options.provider][obj]:
|
if options.touch in LdapObjects[options.provider][obj]:
|
||||||
orig_value = LdapObjects[options.provider][obj][options.touch]
|
orig_value = LdapObjects[options.provider][obj][options.touch]
|
||||||
LdapServers[options.provider].touch_object(obj, options.touch, orig_value)
|
LdapServers[options.provider].touch_object(
|
||||||
|
obj, options.touch, orig_value)
|
||||||
else:
|
else:
|
||||||
logging.debug("Obj %s: not found on %s", obj, srv_name)
|
logging.debug('Obj %s: not found on %s', obj, srv_name)
|
||||||
not_found[srv_name].append(obj)
|
not_found[srv_name].append(obj)
|
||||||
if options.touch:
|
if options.touch:
|
||||||
orig_value = []
|
orig_value = []
|
||||||
if options.touch in LdapObjects[options.provider][obj]:
|
if options.touch in LdapObjects[options.provider][obj]:
|
||||||
orig_value = LdapObjects[options.provider][obj][options.touch]
|
orig_value = LdapObjects[options.provider][obj][options.touch]
|
||||||
LdapServers[options.provider].touch_object(obj, options.touch, orig_value)
|
LdapServers[options.provider].touch_object(
|
||||||
|
obj, options.touch, orig_value)
|
||||||
|
|
||||||
for obj in LdapObjects[options.consumer]:
|
for obj in LdapObjects[options.consumer]:
|
||||||
logging.debug("Check obj %s of consumer", obj)
|
logging.debug('Check obj %s of consumer', obj)
|
||||||
if obj not in LdapObjects[options.provider]:
|
if obj not in LdapObjects[options.provider]:
|
||||||
logging.debug("Obj %s: not found on provider", obj)
|
logging.debug('Obj %s: not found on provider', obj)
|
||||||
not_found[options.provider].append(obj)
|
not_found[options.provider].append(obj)
|
||||||
|
|
||||||
if options.nagios:
|
if options.nagios:
|
||||||
|
@ -559,75 +582,91 @@ if options.nagios:
|
||||||
|
|
||||||
if not options.nocheckcontextcsn:
|
if not options.nocheckcontextcsn:
|
||||||
if not LdapServersCSN[options.provider]:
|
if not LdapServersCSN[options.provider]:
|
||||||
errors.append("ContextCSN of LDAP server provider could not be found")
|
errors.append('ContextCSN of LDAP server provider could not be found')
|
||||||
else:
|
else:
|
||||||
long_output.append(
|
long_output.append(
|
||||||
f"ContextCSN on LDAP server provider = {LdapServersCSN[options.provider]}"
|
f'ContextCSN on LDAP server provider = {LdapServersCSN[options.provider]}')
|
||||||
)
|
|
||||||
for srv_name, srv_csn in LdapServersCSN.items():
|
for srv_name, srv_csn in LdapServersCSN.items():
|
||||||
if srv_name == options.provider:
|
if srv_name == options.provider:
|
||||||
continue
|
continue
|
||||||
if not srv_csn:
|
if not srv_csn:
|
||||||
errors.append(f"ContextCSN of {srv_name} not found")
|
errors.append(f'ContextCSN of {srv_name} not found')
|
||||||
elif srv_csn != LdapServersCSN[options.provider]:
|
elif srv_csn != LdapServersCSN[options.provider]:
|
||||||
errors.append(f"ContextCSN of {srv_name} not the same of provider")
|
errors.append(
|
||||||
long_output.append(f"ContextCSN on LDAP server {srv_name} = {srv_csn}")
|
f'ContextCSN of {srv_name} not the same of provider')
|
||||||
|
long_output.append(
|
||||||
|
f'ContextCSN on LDAP server {srv_name} = {srv_csn}')
|
||||||
|
|
||||||
if not options.onlycheckcontextcsn:
|
if not options.onlycheckcontextcsn:
|
||||||
if not_found[options.consumer]:
|
if not_found[options.consumer]:
|
||||||
errors.append(f"{len(not_found[options.consumer])} not found object(s) on consumer")
|
errors.append(
|
||||||
long_output.append(f"Object(s) not found on server {options.consumer} (consumer):")
|
f'{len(not_found[options.consumer])} not found object(s) on '
|
||||||
|
'consumer')
|
||||||
|
long_output.append(
|
||||||
|
f'Object(s) not found on server {options.consumer} '
|
||||||
|
'(consumer):')
|
||||||
for obj in not_found[options.consumer]:
|
for obj in not_found[options.consumer]:
|
||||||
long_output.append(f" - {obj}")
|
long_output.append(f' - {obj}')
|
||||||
if not_found[options.provider]:
|
if not_found[options.provider]:
|
||||||
errors.append(f"{len(not_found[options.provider])} not found object(s) on provider")
|
errors.append(
|
||||||
long_output.append(f"Object(s) not found on server {options.provider} (provider):")
|
f'{len(not_found[options.provider])} not found object(s) on '
|
||||||
|
'provider')
|
||||||
|
long_output.append(
|
||||||
|
f'Object(s) not found on server {options.provider} '
|
||||||
|
'(provider):')
|
||||||
for obj in not_found[options.provider]:
|
for obj in not_found[options.provider]:
|
||||||
long_output.append(f" - {obj}")
|
long_output.append(f' - {obj}')
|
||||||
if not_sync[options.consumer]:
|
if not_sync[options.consumer]:
|
||||||
errors.append(
|
errors.append(
|
||||||
f"{len(not_sync[options.consumer])} not synchronized object(s) on consumer"
|
f'{len(not_sync[options.consumer])} not synchronized object(s) '
|
||||||
)
|
'on consumer')
|
||||||
long_output.append(
|
long_output.append(
|
||||||
f"Object(s) not synchronized on server {options.consumer} (consumer):"
|
f'Object(s) not synchronized on server {options.consumer} '
|
||||||
)
|
'(consumer):')
|
||||||
for obj in not_sync[options.consumer]:
|
for obj in not_sync[options.consumer]:
|
||||||
long_output.append(f" - {obj}")
|
long_output.append(f' - {obj}')
|
||||||
if errors:
|
if errors:
|
||||||
print(f'CRITICAL: {", ".join(errors)}')
|
print(f'CRITICAL: {", ".join(errors)}')
|
||||||
print("\n\n")
|
print('\n\n')
|
||||||
print("\n".join(long_output))
|
print("\n".join(long_output))
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
else:
|
else:
|
||||||
print("OK: consumer and provider are synchronized")
|
print('OK: consumer and provider are synchronized')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
noerror = True
|
noerror = True
|
||||||
for srv in servers:
|
for srv in servers:
|
||||||
if not options.nocheckcontextcsn:
|
if not options.nocheckcontextcsn:
|
||||||
if not LdapServersCSN[options.provider]:
|
if not LdapServersCSN[options.provider]:
|
||||||
logging.warning("ContextCSN of LDAP server provider could not be found")
|
logging.warning(
|
||||||
|
'ContextCSN of LDAP server provider could not be found')
|
||||||
noerror = False
|
noerror = False
|
||||||
else:
|
else:
|
||||||
for srv_name, srv_csn in LdapServersCSN.items():
|
for srv_name, srv_csn in LdapServersCSN.items():
|
||||||
if srv_name == options.provider:
|
if srv_name == options.provider:
|
||||||
continue
|
continue
|
||||||
if not srv_csn:
|
if not srv_csn:
|
||||||
logging.warning("ContextCSN of %s not found", srv_name)
|
logging.warning('ContextCSN of %s not found', srv_name)
|
||||||
noerror = False
|
noerror = False
|
||||||
elif srv_csn != LdapServersCSN[options.provider]:
|
elif srv_csn != LdapServersCSN[options.provider]:
|
||||||
logging.warning("ContextCSN of %s not the same of provider", srv_name)
|
logging.warning(
|
||||||
|
'ContextCSN of %s not the same of provider',
|
||||||
|
srv_name)
|
||||||
noerror = False
|
noerror = False
|
||||||
|
|
||||||
if not options.onlycheckcontextcsn:
|
if not options.onlycheckcontextcsn:
|
||||||
if not_found[srv]:
|
if not_found[srv]:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"Not found objects on %s :\n - %s", srv, "\n - ".join(not_found[srv])
|
'Not found objects on %s :\n - %s',
|
||||||
|
srv, '\n - '.join(not_found[srv])
|
||||||
)
|
)
|
||||||
noerror = False
|
noerror = False
|
||||||
if not_sync[srv]:
|
if not_sync[srv]:
|
||||||
logging.warning("Not sync objects on %s: %s", srv, "\n - ".join(not_sync[srv]))
|
logging.warning(
|
||||||
|
'Not sync objects on %s: %s',
|
||||||
|
srv, '\n - '.join(not_sync[srv])
|
||||||
|
)
|
||||||
noerror = False
|
noerror = False
|
||||||
|
|
||||||
if noerror:
|
if noerror:
|
||||||
logging.info("No sync problem detected")
|
logging.info('No sync problem detected')
|
||||||
|
|
4
debian/control
vendored
4
debian/control
vendored
|
@ -12,9 +12,9 @@ Description: Check LDAP syncrepl replication state between two servers
|
||||||
This script check LDAP syncrepl replication state between two servers. One
|
This script check LDAP syncrepl replication state between two servers. One
|
||||||
server is consider as provider and the other as consumer.
|
server is consider as provider and the other as consumer.
|
||||||
This script can check replication state with two method :
|
This script can check replication state with two method :
|
||||||
- by the first, entryCSN of all entries of LDAP directory will be compare
|
- by the fisrt, entryCSN of all entries of LDAP directory will be compare
|
||||||
between two servers
|
between two servers
|
||||||
- by the second, all values of all attributes of all entries will be compare
|
- by the second, all values of all atributes of all entries will be compare
|
||||||
between two servers.
|
between two servers.
|
||||||
In all case, contextCSN of servers will be compare and entries not present in
|
In all case, contextCSN of servers will be compare and entries not present in
|
||||||
consumer or in provider will be notice. You can decide to disable contextCSN
|
consumer or in provider will be notice. You can decide to disable contextCSN
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
---
|
|
||||||
name: check_syncrepl_extended
|
name: check_syncrepl_extended
|
||||||
description: "file:///README.md"
|
description: "file:///README.md"
|
||||||
url: "https://gitea.zionetrix.net/bn8/check_syncrepl_extended"
|
url: "https://gitea.zionetrix.net/bn8/check_syncrepl_extended"
|
||||||
|
@ -8,17 +7,21 @@ target: Database
|
||||||
type: Plugin
|
type: Plugin
|
||||||
license: gplv2
|
license: gplv2
|
||||||
releases:
|
releases:
|
||||||
- name: v2017.09.11
|
-
|
||||||
|
name: v2017.09.11
|
||||||
description: "v2017.09.11 Release"
|
description: "v2017.09.11 Release"
|
||||||
files:
|
files:
|
||||||
- name: check_syncrepl_extended
|
-
|
||||||
|
name: check_syncrepl_extended
|
||||||
url: "file:///check_syncrepl_extended"
|
url: "file:///check_syncrepl_extended"
|
||||||
description: "First release upload on Icinga Exchange"
|
description: "First release upload on Icinga Exchange"
|
||||||
checksum: cee2e8fdc243edcbab30d3b034e8b9bf
|
checksum: cee2e8fdc243edcbab30d3b034e8b9bf
|
||||||
- name: v2017.09.12
|
-
|
||||||
|
name: v2017.09.12
|
||||||
description: "v2017.09.12 Release"
|
description: "v2017.09.12 Release"
|
||||||
files:
|
files:
|
||||||
- name: check_syncrepl_extended
|
-
|
||||||
|
name: check_syncrepl_extended
|
||||||
url: "file:///check_syncrepl_extended"
|
url: "file:///check_syncrepl_extended"
|
||||||
description: "Improve ContextCSN checking (thanks Orhan)"
|
description: "Improve ContextCSN checking (thanks Orhan)"
|
||||||
checksum: a954ca49855c23f2f098f4696c4c0ebd
|
checksum: a954ca49855c23f2f098f4696c4c0ebd
|
||||||
|
|
Loading…
Reference in a new issue