Direkt zum Hauptinhalt

Installation via Docker

Beschreibung:

Ein ldap Server für Adressbücher mit carddav sync.

Installation:

Docker installieren

apt install docker.io docker-compose curl

Nun Projektverzeichnisse erstellen



ldap-carddav-stack/
├── docker-compose.yml
├── .env
├── ldap-carddav/
│   └── config.php     # deine Konfiguration
├── Dockerfile         # für ldap-carddav


mkdir -p /root/ldap-carddav-stack/ldap-carddav
mkdir -p /root/ldap-carddav-stack/ldap-carddav-data

Dockerfile erstellen zum image bauen

nano /root/ldap-carddav-stack/Dockerfile

Inhalt

FROM debian:bullseye

ENV DEBIAN_FRONTEND=noninteractive

# Abhängigkeiten installieren
RUN apt-get update && apt-get install -y \
    apache2 \
    php \
    php-ldap \
    php-xml \
    php-mbstring \
    php-sqlite3 \
    sqlite3 \
    libapache2-mod-php \
    nano \
    curl \
    ldap-utils \
    composer \
    && apt-get clean

# ldap-carddav klonen
RUN git clone https://github.com/isubsoft/ldap-carddav.git /var/www/html/ldap-carddav

# Composer-Abhängigkeiten installieren
WORKDIR /var/www/html/ldap-carddav
RUN composer install

# Rewrite-Modul aktivieren
RUN a2enmod rewrite

# 000-default.conf ersetzen
RUN rm /etc/apache2/sites-enabled/000-default.conf && \
    echo '<VirtualHost *:80>\n\
    ServerAdmin admin@example.org\n\
    DocumentRoot /var/www/html/ldap-carddav\n\
\n\
    <Directory /var/www/html/ldap-carddav>\n\
        Options Indexes FollowSymLinks\n\
        AllowOverride All\n\
        Require all granted\n\
        DirectoryIndex server.php\n\
        RewriteEngine On\n\
        RewriteCond %{REQUEST_FILENAME} !-f\n\
        RewriteCond %{REQUEST_FILENAME} !-d\n\
        RewriteRule ^(.*)$ server.php [QSA,L]\n\
    </Directory>\n\
\n\
    Redirect 301 /.well-known/carddav /server.php\n\
</VirtualHost>' > /etc/apache2/sites-enabled/000-default.conf

# Apache starten
CMD ["apachectl", "-D", "FOREGROUND"]

EXPOSE 80

Die .env Datei

nano /root/ldap-carddav-stack/.env

Inhalt

LDAP_ORGANISATION=ExampleCorp
LDAP_DOMAIN=example
LDAP_TOP_DOMAIN=local
LDAP_ADMIN_PASSWORD=admin

Die compose Datei

nano /root/ldap-carddav-stack/docker-compose.yml

Inhalt

version: '3.8'

services:
  ldap:
    image: osixia/openldap:1.5.0
    container_name: ldap
    environment:
      LDAP_ORGANISATION: ${LDAP_ORGANISATION}
      LDAP_DOMAIN: ${LDAP_DOMAIN}.${LDAP_TOP_DOMAIN}
      LDAP_ADMIN_PASSWORD: ${LDAP_ADMIN_PASSWORD}
    volumes:
      - ./ldap_data:/var/lib/ldap
      - ./ldap_config:/etc/ldap/slapd.d
    ports:
      - "389:389"

  phpldapadmin:
    image: osixia/phpldapadmin:0.9.0
    container_name: phpldapadmin
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: ldap
    ports:
      - "6443:443"

  carddav:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: ldap-carddav
    ports:
      - "80:80"
    volumes:
      - ./ldap-carddav/conf.php:/var/www/html/ldap-carddav/conf/conf.php:ro
      - ./ldap-carddav-data:/var/www/html/ldap-carddav/data
    depends_on:
      - ldap
    environment:
      - LDAP_HOST=ldap
      - LDAP_BASE_DN=ou=${LDAP_ORGANISATION},dc=${LDAP_DOMAIN},dc=${LDAP_TOP_DOMAIN}
      - LDAP_BIND_DN=cn=admin,ou=${LDAP_ORGANISATION},dc=${LDAP_DOMAIN},dc=${LDAP_TOP_DOMAIN}
      - LDAP_BIND_PASSWORD=${LDAP_ADMIN_PASSWORD}

Container starten:

docker-compose up -d

PHP File

nano /root/ldap-carddav-stack/ldap-carddav/conf.php

Inhalt

<?php

$config = [];

// === TEMP / DATA ===
$config['tmpdir'] = '%systempdir';
$config['datadir'] = '/var/www/html/ldap-carddav/data';

// === DATABASE ===
$config['sync_database'] = [
    'dsn' => 'sqlite:/var/www/html/ldap-carddav/data/cards.db',
    'username' => '',
    'password' => '',
    'options' => [],
    'init_commands' => []
];

// === LDAP SERVER CONFIG ===
$config['server']['ldap'] = [
    'host' => getenv('LDAP_HOST') ?: 'localhost',
    'network_timeout' => 10,
    'connection_security' => 'none'
];

// === LDAP AUTH ===
$config['auth']['ldap'] = [
    'base_dn' => getenv('LDAP_BASE_DN') ?: 'dc=example,dc=local',
    'bind_dn' => '%dn',
    'bind_pass' => '%p',
    'search_base_dn' => '',
    'search_filter' => '(&(objectclass=inetOrgPerson)(uid=%u))',
    'search_bind_dn' => getenv('LDAP_BIND_DN') ?: 'cn=admin,dc=example,dc=local',
    'search_bind_pw' => getenv('LDAP_BIND_PASSWORD') ?: 'admin',
    'scope' => 'list'
];

// === PRINCIPAL SEARCH ===
$config['principal']['ldap'] = [
    'base_dn' => getenv('LDAP_BASE_DN') ?: 'dc=example,dc=local',
    'search_base_dn' => '',
    'search_filter' => '(&(objectclass=inetOrgPerson)(uid=*))',
    'search_bind_dn' => getenv('LDAP_BIND_DN') ?: 'cn=admin,dc=example,dc=local',
    'search_bind_pw' => getenv('LDAP_BIND_PASSWORD') ?: 'admin',
    'scope' => 'list',
    'fieldmap' => [
        'id' => 'uid',
        'displayname' => 'cn',
        'mail' => 'mail'
    ]
];

// Hinweis: Die folgenden Einträge sind stark gekürzt. Siehe Original für volle Struktur.
// Du kannst z. B. $config['card']['addressbook']['ldap']['me'], ['global'], ['personal'] wie oben mit getenv() einbinden.

// Beispiel für ein Adressbuch-Eintrag mit bind_dn über ENV
$config['card']['addressbook']['ldap']['personal'] = [
    'name' => 'starface',
    'description' => 'Starface Kontakte',
    'user_specific' => true,
    'writable' => true,

    'group_LDAP_Object_Classes' => ['groupOfNames'],
    'group_required_fields' => ['cn', 'member'],
    'group_LDAP_rdn' => 'cn',
    'group_member_map' => [ 'MEMBER' => [ 'field_name' => 'member' ] ],

    'base_dn' => getenv('LDAP_BASE_DN') ?: 'dc=example,dc=local',
    'filter' => '(objectClass=inetOrgPerson)',
    'bind_dn' => getenv('LDAP_BIND_DN') ?: 'cn=admin,dc=example,dc=local',
    'bind_pass' => getenv('LDAP_BIND_PASSWORD') ?: 'admin',
    'scope' => 'sub',

    'LDAP_Object_Classes' => ['inetOrgPerson'],
    'required_fields' => ['cn','sn'],
    'LDAP_rdn' => 'cn',

    // Schreibrechte aktivieren
    //'field_acl' => [
   //     'eval' => 'w'
   //     'list' => ['displayName', 'homePhone', 'telephoneNumber', 'facsimileTelephoneNumber', 'pager', 'mobile', 'homePostalAddress', 'preferredLanguage']
											  // leere Liste = alles erlaubt
 //   ],

    // Vollständige Feldzuordnung für Outlook / CalDAV
    'fieldmap' => [
        'FN' => ['field_name' => 'cn'],
        'N' => ['field_name' => [
            'last_name' => 'sn',
            'first_name' => 'givenName',
            'prefix' => 'personalTitle'
        ]],
        'EMAIL' => ['field_name' => 'mail'],
        'ORG' => ['field_name' => [
            'org_name' => 'o',
            'org_unit_name' => 'ou'
        ]],
        'TITLE' => ['field_name' => 'title'],
        'ROLE' => ['field_name' => 'employeeType'],
        'NICKNAME' => ['field_name' => 'displayName'],
        'PHOTO' => [[
            'field_name' => 'jpegphoto',
            'parameters' => [],
            'reverse_map_parameter_index' => 0,
            'decode_file' => true
        ]],
        'NOTE' => ['field_name' => 'description'],
        'TEL' => [
     [   // Fax number
    'field_name' => 'facsimileTelephoneNumber',
    'parameters' => [
        ['TYPE' => ['fax']],
        ['TYPE' => ['fax', 'work']],
        ['TYPE' => ['work', 'fax']],
        ['TYPE' => 'facsimile'],
        ['TYPE' => ['voice', 'fax']],
        ['TYPE' => ['fax', 'voice']],
        null
    ],
    'reverse_map_parameter_index' => 0
],
    [   // Work number
        'field_name' => 'telephoneNumber',
        'parameters' => [
            ['TYPE' => ['work']],
            ['TYPE' => ['voice', 'work']],
            ['TYPE' => 'work'],
            ['TYPE' => 'voice'],
            null
        ],
        'reverse_map_parameter_index' => 0
    ],
    [   // Home number
        'field_name' => 'homePhone',
        'parameters' => [
            ['TYPE' => ['home']],
            ['TYPE' => ['voice', 'home']],
            ['TYPE' => 'home'],
            null
        ],
        'reverse_map_parameter_index' => 0
    ],
    [   // Mobile number
        'field_name' => 'mobile',
        'parameters' => [
            ['TYPE' => ['cell']],
            ['TYPE' => ['voice', 'cell']],
            ['TYPE' => 'cell'],
            null
        ],
        'reverse_map_parameter_index' => 0
    ],
    [   // Pager
        'field_name' => 'pager',
        'parameters' => [
            ['TYPE' => ['pager']],
            null
        ],
        'reverse_map_parameter_index' => 0
    ]
],
   'NOTE' => [
    'field_name' => 'description',
    'parameters' => [],
    'reverse_map_parameter_index' => 0
],
     'ADR' => [
    [
        'field_name' => [
            'po_box'      => 'postOfficeBox',
            'street'      => 'street',            
            'locality'    => 'l',
            'province'    => 'st',
            'postal_code' => 'postalCode'
        ],
        'parameters' => ['TYPE' => 'work'],
        'map_component_separator' => ';',
        'reverse_map_parameter_index' => 0
    ],
    // Privatadresse
    [
        'field_name' => 'homePostalAddress',
        'parameters' => ['TYPE' => 'home'],
        'map_component_separator' => '$',
        'reverse_map_parameter_index' => 0
    ]
],
        'LANG' => ['field_name' => 'preferredLanguage']
    ]
];

Aufrufen LDAP und co:

  • phpLDAPadmin: https://localhost:6443
    Benutzername aus unserem Beispiel : cn=admin,dc=example,dc=local
    Passwort aus unserem Beispiel : admin

    grafik.png



  • ldap-carddav WebDAV/CardDAV: http://localhost/ldap-carddav/
    Die Benutzer dazu werden im LDAP Webgui angelegt, dazu ein ein eigenes Kaptitel

Datenbank initialiseren

docker-compose exec carddav /bin/bash
sqlite3 /var/www/html/ldap-carddav/data/cards.db < /var/www/html/ldap-carddav//sql/sqlite/ddl.sql
php /var/www/html/ldap-carddav/src/App/syncdb.php init

Danach vom conatiner wieder abmelden und chmod 777 über die card.db

chmod 777 /root/ldap-carddav-stack/ldap-carddav-data/cards.db

PHP ini Änderungen durchführen und neu mit der Datenbank  synchroniesieren:

Wenn die PHP geändert wird um zum Beispiel Felder hinzugefügt werden, muss ide Datenbank neu initialisert werden.

docker-compose exec carddav /bin/bash
php /var/www/html/ldap-carddav/src/App/syncdb.php

Ausgabe:
Dort 0 Auswählen

hoose the entity you want to operate upon. Enter 0 for addressbook and 1 for user: 

Nun mit 3 bestätigen

Enter the operation to perform on address book. Enter 0 to list, 1 to add, 2 to rename and 3 to delete:

Nun Adressbuchname eingeben:personal.

Enter name of the address book to delete: 

Nun wurde das Buch gelöscht

Address book 'personal' has been deleted.

Nun kann ein neuer init stattfinden

php /var/www/html/ldap-carddav/src/App/syncdb.php init

Ausgabe:

Initializing sync database ...
Address book 'personal' has been successfully added to sync database.
Address book(s) successfully imported.

Benutzer anlegen:

Im LDAP Webgui einloggen unter

https://<ip>:6443

grafik.png

Dort neues child Element anlegen...

grafik.png

Vom Typ PosixGroup

grafik.png

mit dem namen users

grafik.png

Nun nochmals bestätigen

grafik.png

nun eine weitere elemt vom typ organizationRole mit den namen write anlegen

grafik.png

nun den Namen vergeben

grafik.png

runter scrollen bis create object

grafik.png

 

Fehlersuche:

Sollte Verbindung nicht zustande kommen, dann mal in die Apache2 log schauen.
Im container anmelden

docker-compose exec carddav /bin/bash
cat /var/log/apache2/error.log

Ausgabe:

grafik.png

Testen der Daten

Hinweis:  Paramter -w ist das Passwort aus der .env Datei für ldap

ldapwhoami -x -D "cn=admin,dc=example,dc=local" -w admin -H ldap://ldap

Ausgabe:

grafik.png

Testen ob ein Objekt sich ändern lässt

ldapmodify -x -D "cn=admin,dc=example,dc=local" -w admin -H ldap://ldap


Einfügen und dann strg+d drücken

dn: cn=shacker,dc=example,dc=local
changetype: modify
replace: mail
mail: test@example.org

Ein komplettes Objekt ausgeben

ldapsearch -x \
  -D "cn=admin,dc=example,dc=local" \
  -w admin \
  -b "cn=Wolf\, SDaniel,dc=example,dc=local" \
  -LLL

Ausgabe:

grafik.png