svn merge

Trochę głupio w SVN jest zorganizowane zarządzanie gałęziami. Niby pojęciowo łatwo, bo to tylko kopia katalogu, ale prowadzi to do problemów.

Ponieważ każdą kopię katalogu można traktować jak branch, to z każdego takiego katalogu można merge'ować zmiany. Ale jak zapamiętać, co gdzie było merge'owane? Subversion zapisuje atrybut svn:mergeinfo. Wygląda ładnie i elegancko, do momentu gdy trzeba z tym popracować.

Okazuje się, że można wykonać merge, ale nie zapisać metadanych (commitując zawartość brancha, ale omijając sam katalog). W ten sposób zepsuty branch nie nadaje się już właściwie do niczego. Można próbować uzupełnić kolejnym commitem metadane, żeby z innych branchy (z trunka?) nadal można było ściągać zmiany, ale o merge'owaniu w drugą stronę można zapomnieć. Łatwiej jest usunąć branch i utworzyć go ponownie.

Gdy przychodzi do merge'owania gałęzi, nigdy nie wiem, kiedy użyć svn merge, a kiedy svn merge --reintegrate. SVN Book mówi, że pierwsze jest do wciągania zmian z trunka do gałęzi, a drugie do odwrotnej operacji. Prawda jest taka, że to działa tylko przy modelu pracy, gdy gałąź po reintegracji z trunkiem jest usuwana. Gdy pracuje się na gałęzi dłużej, czasem trzeba z trunka merge'ować z opcją --reintegrate, a czasem bez -- i nie jest oczywiste, kiedy które. Moim zdaniem to porażka programistów od interfejsu i backendu.

Wysłany: 17 kwietnia 2012, 11:07:40; 2 komentarze

jak pracować z nazwami DNS, gdy potrzebne są adresy IP?

Od dość dawna wiem, że nazwy DNS-owe są wspaniałą rzeczą i że administrator powinien ich używać zawsze. Ale co zrobić, gdy da się dostarczyć jedynie adres IP?

Na taką właśnie sytuację trafiłem (dość dawno temu) przy okazji cfengine 2.x. Klucze publiczne poszczególnych serwerów są umieszczane w /var/cfengine/ppkeys w formacie root-10.20.121.15.pub. Nie ma nazwy DNS, prawda?

Na szczęście rozwiązanie jest proste: składować klucze publiczne w katalogu /var/cfengine/ppkeys/hosts, a w samym /var/cfengine/ppkeys trzymać wygenerowaną listę symlinków:

[root@cfmaster ppkeys]# pwd
/var/cfengine/ppkeys
[root@cfmaster ppkeys]# ls
hosts/                     root-10.20.130.247.pub@
localhost.priv             root-10.20.18.11.pub@
localhost.pub              root-10.20.24.3.pub@
Makefile                   root-10.16.2.135.pub@
root-10.20.137.6.pub@      root-10.16.2.71.pub@
[...]
[root@cfmaster ppkeys]# ls hosts/
audi.atled.pl.pub          taunus.atled.pl.pub
dodge.atled.pl.pub         trabant.atled.pl.pub
ferrari.atled.pl.pub       us000149.atled.pl.pub
[...]
[root@cfmaster ppkeys]# ls -l root-10.20.130.247.pub
lrwxrwxrwx 1 root root 31 Mar  7 10:49 root-10.20.130.247.pub -> uv000255.atled.pl.pub
[root@cfmaster ppkeys]#

W ten sposób, gdy trafiła mi się zmiana adresu IP któregoś serwera, nie zastanawiałem się, któremu kluczowi zmienić nazwę. Po prostu regenerowałem symlinki.

[root@cfmaster ppkeys]# cfrun lexus
cfrun(0):         .......... [ Hailing lexus.atled.pl ] ..........
WARNING - You do not have a public key from host lexus.atled.pl = 10.20.121.219
          Do you want to accept one on trust? (yes/no)

--> ^C
[root@cfmaster ppkeys]# rm *(@)
[root@cfmaster ppkeys]# make
for pubkey in hosts/*.pub; do host=${pubkey#*/}; host=${host%.pub}; ln -sf $pubkey root-`getent hosts $host | awk '{print $1}'`.pub; done
[root@cfmaster ppkeys]# cfrun lexus

cfrun(0):         .......... [ Hailing lexus.atled.pl ] ..........

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cfengine:lexus:
Executing script /usr/bin/svn update --quiet --config-dir /var/cfengine/svn/config...(timeout=90,uid=-1,gid=-1)
[...]

Dla wyjaśnienia, w zsh glob *(@) oznacza wszystkie pliki, które są symlinkami.

Podobnej techniki używam do utrzymywania katalogu z certyfikatami X.509 (takiego, który można podać jako CA path). Wrzucam pliki z nazwami czytelnymi dla człowieka (np. jarowit.net.cert.pem), a makefile mi regeneruje symlinki (nazwa symlinka jest ustalana komendą openssl x509 -noout -hash -in file.cert.pem).

Podsumowując, nie używaj identyfikatorów, które ci nic nie mówią. Używaj czytelnych nazw, a identyfikatory sobie generuj, czy to usługą katalogową, czy za pomocą haszowania. To oszczędza później kłopotu, gdy identyfikator się zmieni z jakiegoś powodu.

Wysłany: 07 marca 2012, 12:36:24; 7 komentarzy

tablet Core Ivy 7 Complete: recenzja

Niedawno (trzy tygodnie temu) sprawiłem sobie tablet firmowany przez tScore. To polska firma założona w 2010 roku, zajmująca się sprowadzaniem i sprzedażą tabletów (pod własną marką). Oficjalnym kanałem sprzedaży jest Allegro.

Mój tablet to Core Ivy 7 Complete, kosztujący około 650zł. Sprzęt jest równoważny tabletowi Amlogic LonPad A97, niestety sprzedający nigdzie tego nie opisuje. A szkoda, bo choćby zdjęć można łatwiej poszukać. Tablet pracuje pod kontrolą Androida 2.3.3.

Ekran ma przekątną 7 cali przy rozdzielczości 800x600 pikseli. W niedużym urządzeniu proporcje 4:3 sprawdzają się lepiej niż panoramiczne 16:9, ponieważ w pozycji portretu ekran nie jest tak strasznie wąski.

tScore chwali się, że to ekran piezoelektryczny. Rzeczywiście, ekran zachowuje się trochę inaczej niż ekrany oporowe czy pojemnościowe. Przede wszystkim nie ugina się jak ekran oporowy, ale działa z dowolnym przedmiotem wskazującym (np. tył długopisu, palec w rękawiczce). Inaczej też reaguje na przeciągnięcie palcem po ekranie podczas przewijania, ale daje się do tego przyzwyczaić. Uwaga: to nie jest ekran multitouch.

Bateria spisuje się całkiem przyzwoicie. Dystrybutor twierdzi, że wytrzymuje 8-10 godzin ciągłej pracy. Podejrzewam, że aż tyle nie wytrzyma (ale to nic dziwnego, mało kto podaje rzeczywisty przeciętny czas pracy), ale battery lifetime nie powinien odbiegać wiele od nominalnego. Urządzenie wymaga ładowania co dwa-trzy dni, a trochę go używam, jak zresztą robię z każdą nową zabawką. Słowem, nie martwię się, że nagle mi się wyczerpie bateria i raczej nie jest to Dell Streak 7.

Tablet ma gniazdo miniUSB. Mini, nie micro, co mnie całkiem ucieszyło. Niestety nie można go w ten sposób ładować i potrzebna jest osobna ładowarka 5V, dostarczana z tabletem, rzecz jasna. Gorzej, że gniazdo ładowarki nie jest specjalnie popularne, więc aktualnie mam problem ze znalezieniem zapasowego zasilacza, o zasilaczu samochodowym nie wspominając (nie jeżdżę, więc nie przeszkadza mi to, ale mogłoby się chcieć mieć tę możliwość). Jest jeszcze port na kartę microSD (nazywane przez sprzedawcę TF), przyjmujący oczywiście karty SDHC.

Tablet jest wyposażony w akcelerometr, więc pulpit ładnie się obraca przy przekręceniu urządzenia. Brak za to odbiornika GPS. Dla mnie żaden problem, bo mam osobny na bluetooth. Nie ma również kamery, więc ewentualne odczytywanie kodów kreskowych czy QR staje się trudniejsze.

Oprogramowaniu dostarczonego z tabletem nie mam wiele do zarzucenia. Może tyle, że w menu ustawień jedna z pozycji była po azjatycku, ale to metoda wprowadzania znaków (klawiatura), więc można to zignorować. W menu daje się dotrzeć do konfiguracji sieci komórkowej, mimo że urządzenie nie jest telefonem GSM, ale znowu nie jest to rzecz przeszkadzająca. Brakło za to strefy czasowej Warszawa i poprawkę na czas letni będę chyba musiał wprowadzić zmieniając strefę.

Połączenia sieciowe to trochę gorsza sprawa. Jest Wi-Fi i jest bluetooth, ale z internetem przez bluetooth (profil DUN, używając telefonu jako modemu) nie daje się połączyć. Niestety to przypadłość prawie wszystkich Androidów, zupełnie dla mnie niezrozumiała, bo takie połączenia w desktopowych Linuksach są łatwe do zestawienia. Zrootować tablet jeszcze mi się nie udało, ale próbowałem tylko z wnętrza (Gingerbreak). Może z Windows przez USB się uda, choć najpierw muszę zdobyć dostęp do komputera z Windows na pokładzie.

Podsumowując, tablet Core Ivy 7 Complete to całkiem niezły sprzęt jak na 650 wydanych złotych. Najważniejsze dla mnie kryteria, czyli nieduży-ale-duży ekran (7" 4:3) i przyzwoity czas pracy na baterii, są spełnione.

Wysłany: 08 lutego 2012, 12:24:18; 4 komentarze

Postfix: podświetlanie składni map LDAP dla Vima

Na wzór podświetlania składni dla slapd.conf, napisałem podświetlanie dla postfiksowych map LDAP: http://dozzie.jarowit.net/jogger/pfldap/pfldap.vim.

Podobnie jak w poprzednim wpisie, jeszcze nie opublikowałem skryptu na www.vim.org, bo czegoś mu brakuje (choć niedużo: jedynie obsługi porównań bardziej skomplikowanych niż mail=foo@* w zapytaniu LDAP).

Przykładowa składnia (choć na jarowicie akurat używam Exima, nie Postfiksa):
podświetlanie mapy LDAP Postfiksa

Wysłany: 01 lutego 2012, 19:03:14; Dodaj komentarz

slapd.conf: podświetlanie składni dla Vima

Od dość dawna mnie uwierało, że nie ma podświetlania składni dla slapd.conf (plik konfiguracyjny OpenLDAP). W końcu się wkurzyłem i napisałem własne reguły, a w każdym razie ich większość: http://dozzie.jarowit.net/jogger/slapd/slapd.vim.

Te reguły są całkiem używalne, ale brakuje im paru rzeczy. Do tego czasu nie chcę ich publikować na www.vim.org.

Przede wszystkim chcę jeszcze dorobić specjalne podświetlanie niektórych ważnych parametrów w definicjach database (np. rootdn czy suffix), a dyrektywy limit, ldapsyntax i ditcontentrule nie otrzymują żadnego specjalnego traktowania ponad podświetlenie nazwy dyrektywy. Za to ładnie się wyświetlają definicje uprawnień (access to ...) i schemy (attributetype i objectclass).

Przykłady podświetlania (argument rootpw został zmieniony na losowy):
główny konfig
uprawnienia
schema

Wysłany: 01 lutego 2012, 16:17:13; Dodaj komentarz

admin's attitude: test łatwiejszy niż deployment

Już pracowałem w ten sposób, i to nawet świadomie.

Dzisiaj kolega z pokoju miał telco (proszę wybaczyć makaronizm). Podczas dyskusji stwierdził, że przetestowanie zmian w naszym systemie powinno być równie łatwe, albo nawet łatwiejsze, niż wprowadzenie zmian na produkcji. Podpisuję się pod tym zdaniem obiema rękami.

Chodzi o to, że admini (choć nie tylko oni) mają tendencję do wybierania prostszych rozwiązań. Jeśli łatwiejsze będzie wprowadzenie zmian na produkcji niż ich przetestowanie, to niektóre zmiany (głównie te drobne) będą aplikowane bezpośrednio, a to może się skończyć awarią. Jeśli zaś uprzednie przetestowanie jest łatwiejsze, to adminowi nie będzie się chciało robić czegoś trudniejszą i niekoszerną metodą, skoro nie ma w tym żadnego zysku.

Oczywiście sztuczne utrudnianie ręcznego deploymentu nie ma sensu. Nie chodzi o to, żeby skomplikować sobie życie.

Wysłany: 31 stycznia 2012, 12:18:01; Dodaj komentarz

zarządzanie wieloma serwerami: model pracy i narzędzia

Obiecywałem sobie, że napiszę piękny, duży artykuł o cfengine, który będzie wprowadzać czytelnika do wszystkiego: do składni reguł (CFE 2.x), do narzędzi, których używam, do sposobów pracy, a nawet będzie służył za cfengine cheat sheet. Dupa, raczej nic z tego nie wyjdzie. Jestem zbyt leniwy.

W zamian za to stworzę parę mniejszych artykułów. Coś na wzór pisania na wiki: najpierw szkic artykułu, a potem po jednej, po dwie informacje. Małe przyrostowe edycje są zawsze łatwiejsze w przeprowadzeniu niż jeden big bang. Dziś chciałem przede wszystkim pokazać mój model pracy.

Przy czterech setkach serwerów wiadomo, że potrzebny jest jakiś system automatyzacji pracy. Na rynku jest wiele różnych, z których niektóre opierają się na jednoczesnej pracy -- mniej lub bardziej interaktywnej -- na wielu serwerach (ClusterSSH, PSSH, Pdsh), inne na wysyłaniu skryptów na serwery docelowe (Spacewalk, zdaje się że IBM Tivoli), a jeszcze inne na ściąganiu napisanego w dedykowanym języku skryptu ze zdalnego serwera. Popularnymi przykładami są cfengine i Puppet.

Najbardziej udanymi są (moim zdaniem) cfengine i Puppet. Są niezawodne i samodzielne. Rozumiem przez to, że operator przygotowuje zestaw reguł do wykonania i wywiesza w Wiadomym Miejscu™ i może zaufać swojej infrastrukturze, że ta wykona zlecone zadania i reguły nie wywalą się na obecności jakiegoś nadmiarowego katalogu. Jest to istotnie różna sytuacja od dystrybuowania skryptów shellowych do wykonania. Skrypty trzeba pisać defensywnie, pamiętając za każdym razem, że nowo tworzony katalog może już istnieć, że komenda cd może się nie powieść i o podobnych, mało ciekawych, a mogących spowodować spore zniszczenia szczegółach.

Skoro mamy niezawodny mechanizm aplikowania plików konfiguracyjnych i wykonywania innych zadań administracyjnych, pora przyjrzeć się zarządzanym serwerom. W wśród czterystu serwerów większość da się przydzielić do grup na podstawie funkcji, jaką serwery pełnią i na podstawie usług, jakie mają włączone. W obrębie grup funkcyjnych zestaw usług będzie podobny, więc operacje do wykonania (zatem i reguły cfengine) nie będą się zbytnio różnić. W obrębie grup usługowych podobne będą pliki konfiguracyjne (jeśli będą miały dużo dużych różnic, to pora zastanowić się, czy na pewno tak powinny wyglądać; to duży kłopot, gdy każdy serwer jest inaczej skonfigurowany).

Konfigi danej usługi będą (powinny być) do siebie podobne na wszystkich serwerach. Mogą się różnić szczegółami, ale będą miały podobną strukturę. Dlaczego by nie zastosować systemu szablonów i utrzymywać jednego tylko pliku?

W tym miejscu na arenę wkracza cfgen. To narzędzie dostaje listę serwerów (poszczególne serwery są reprezentowane przez pliki z parametrami) i zestaw szablonów dla poszczególnych usług, a wynikiem jego działania jest zbiór gotowych konfigów, po jednym zestawie dla każdego serwera. cfgen jako języka szablonów używa Template::Toolkita, a parametry serwerów są zapisane w formacie YAML. cfgen posługuje się pojęciem usługi: lista usług oczekiwanych na serwerze jest zdefiniowana w jego parametrach. Tej listy cfgen używa do wygenerowania konfigów (niepotrzebne nie są wypełniane, bo i po co?), a przy okazji dostarcza ją do szablonów, czego można użyć do wypełnienia listy klas w szablonie reguł dla cfengine.

Puppet jest zintegrowany z systemem szablonów ERB. Nie podoba mi się to, ponieważ ERB ma brzydką składnię (przez pociąg rubystów do wsadzania Rubyego gdzie popadnie, również tam, gdzie nie jest to wygodne). Poza tym, skoro to nie jest zewnętrzne narzędzie (jak na przykład jest to zrobione z Facterem), trudno jest je wymienić albo użyć z innym systemem zarządzania serwerami.

Mamy już system dystrybucji konfiguracji (wraz z wykonywaniem operacji na serwerach), mamy system szablonów, mamy listę serwerów z ich parametrami. Przydałoby się teraz zapisać szablony i parametry (które razem wzięte nazywam źródłami) w systemie kontroli wersji. W sumie niegłupim byłoby wersjonować również gotowe konfigi (choć nie jest to niezbędne; każdy punkt w czasie można wygenerować wypełniając szablony parametrami).

Jeśli oba, źródła i gotowe konfigi, znajdują się w różnych repozytoriach, przydatnym będzie narzędzie do przebudowywania zmian. cronbuilder powstał właśnie jako takie uniwersalne narzędzie. Potrafi sobie poradzić z gitem i z Subversion (w dowolnej kombinacji source/destination). Osobiście uważam, że do źródeł lepszy jest git, a do wygenerowanych konfigów SVN.

Serwery zarządzane teraz mogą zostać skonfigurowane tak, żeby używać kopii roboczej repozytorium z gotowymi konfigami. Zysk z tego jest taki, że widać z daleka, która maszyna ma przestarzałe reguły (co jest objawem jakichś problemów). Oczywiście można się też zdać na metodę transportową cfengine.

Przydaje się też parę procedur RPC (xmlrpcd), głównie przy rejestracji nowego serwera w sieci CFE (trzeba mu przecież utworzyć plik z parametrami i wygenerować konfigi). Aktualnie i procedura rejestracji, i skrypt uruchamiany na rejestrowanym serwerze są napisane specjalnie dla mojej sieci, ale przynajmniej skrypt mam zamiar przepisać jako bardziej uniwersalne narzędzie.

Na koniec przydałby się system zbierania informacji o zadaniach wykonanych na poszczególnych serwerach. Wprawdzie cfengine jest całkiem samodzielny, ale przecież zdarzają się problemy, z którymi sobie nie poradzi. Napisałem zestaw skryptów i procedur XML-RPC do transportowania logów audytowych cfengine, ale to nie jest eleganckie rozwiązanie. Lepszym pomysłem jest Fluentd, daemon służący do transportu logów. Niestety nie znalazłem go w momencie pisania systemu -- stąd sznurek i taśma klejąca, ale planuję zastąpić moją konstrukcję gotowym klockiem. W każdym razie, logi lądują w bazie danych, skąd są wyciągane w postaci raportów (domowej roboty narzędzie webowe).


Podsumujmy:

  • mam listę znanych serwerów
  • każdy serwer ma listę usług, których się po nim oczekuje (i parametrów dla tych usług)
  • każdej usłudze przysługuje zestaw plików konfiguracyjnych (a raczej szablonów) i reguł cfengine
  • zmiany w szablonach i parametrach są rejestrowane w systemie kontroli wersji
  • z wykonania reguł logi są zbierane i agregowane w bazie danych, skąd są wyciągane jako raporty
  • mam też (pomysł na) narzędzie do rejestracji serwerów w sieci

Cóż, ledwie opisałem swój pomysł na architekturę systemu, a już zajmuje tyle miejsca. Ile by zajął pełnoprawny artykuł o cfengine?

Wysłany: 25 stycznia 2012, 19:22:22; 3 komentarze

monitorowanie: mniej znaczy lepiej

Dobrze jest mieć monitoring. Jeszcze lepiej, jeśli monitoring sprawdza jak najmniej.

Postawiłem w pracy Nagiosa, który monitoruje niewiele, niecałe dwadzieścia serwerów, a każdy z serwerów ma koło ośmiu sprawdzanych usług (w tym uptime, który ma być nie mniejszy niż 3 dni). Nie używałem żadnego autowykrywacza usług i serwerów -- uważam taki soft nie tylko za zbędny, ale wręcz za szkodliwy dla mojego zdrowia.

Przyznam że trochę mi zajęło wypracowanie takiego podejścia. Niekoniecznie działo się to w kolejności, jaka znalazła się w tym wpisie i niekoniecznie przez zacytowane materiały; starałem się przedstawić ciąg przyczynowo-skutkowy, a nie moją historię.

Podstawą monitoringu jest pierwsza, najbardziej najważniejsza zasada: every alert requires an action. Nie wolno zignorować żadnego ostrzeżenia z systemu monitoringu. Jeśli zaczniesz te powiadomienia ignorować, to prosta droga do katastrofy, gdy kiedyś się pojawi informacja, na którą trzeba zareagować. Zasada jest wzięta wprost z wpisu na blogu Server Fault, choć wykoncypowałem ją wcześniej.

Akcja może być dwojaka, albo powiadomienie dotyczy rzeczywistego zdarzenia i trzeba na to zdarzenie zareagować, albo to false positive spowodowany zbyt niskim progiem dla ostrzeżeń. Informacja o przepełnianiu się dysku na serwerze onyx.atled.pl może być spowodowana tym, że miejsca rzeczywiście zaczyna brakować; wtedy trzeba się zalogować na ten serwer, sprawdzić co dużo zajmuje i na przykład to usunąć. Może być i tak, że zwyczajnie dane się bardzo rozrosły, więc trzeba kupić drugi dysk. Może być jeszcze inaczej -- zajętość dysku jest w normie. Wtedy to próg jest zbyt niski, więc trzeba go podnieść. W każdym z przypadków operator musi jakoś zareagować na ostrzeżenie.

Zasada pierwsza zakłada, że system monitoringu już działa i jest prawidłowo skonfigurowany, to znaczy nie zasypuje operatora ostrzeżeniami o nieistotnych zdarzeniach (np. o niedostępności kamery IP z magazynu, którego nikt nie używa ani nawet o nim nie pamięta). Typowo w dashboardzie nie powinny się znajdować żadne ostrzeżenia, co nazywam stanem green screen. Nie jest to łatwe do osiągnięcia, wymaga samodyscypliny i mnóstwa mrówczej pracy. Zabbix i Zenoss dostarczają narzędzi do automatycznego wykrywania usług. Po użyciu nagle się okazuje, że Zabbix czy Zenoss sprawdzają kilkaset parametrów, o których istnieniu operator do tej pory nawet nie wiedział. Trzeba naprawdę dużo systematycznej pracy, żeby wszystkim tym parametrom ustawić progi.

A może tak podejść do sprawy odwrotnie? Ja wiem co mam w sieci. Wiem co mnie interesuje. Reszta usług -- zakładam -- jest nieistotna. Jeśli okaże się, że o jakimś parametrze nie wiedziałem albo zapomniałem, to go dodam -- po pierwszym razie, gdy mnie ugryzie w dupę. Zaczynam z małą liczbą parametrów, ale za to tych naprawdę ważnych. W miarę upływu czasu będę uzupełniać system monitoringu o kolejne usługi, a każda będzie ważna.

Dzięki temu liczba powiadomień może się utrzymywać na niskim poziomie, a dzięki małej liczbie powiadomień mogę obsłużyć każde jedno.

Przykład z życia: wspomniany już mój Nagios w pracy.

  • Zaczynał ze sprawdzaniem load average (choć nie był to znowu taki istotny parametr) i zajętości dysku.
  • Potem rozszerzyłem moją instalację o sprawdzanie kolejki MTA i szybkości odpowiedzi SMTP z localhosta (MTA działa w ogóle? będzie przesyłać e-maile?). Rozszerzyłem, bo się okazało, że czasem Exim mi się zatykał i e-maile przestawały przychodzić.
  • Dodałem monitorowanie uptime'u. Jeden z serwerów dość regularnie się rebootował. Dzięki temu pluginowi udało się namierzyć regularności w rebootach. Jako skutek uboczny pojawiło się monitorowanie temperatury (to była jedna z hipotez o przyczynie rebootów).
  • Zdefiniowałem sprawdzanie działania cfengine. Czasem (raz na tydzień na dwadzieścia serwerów) mój cfengine przestawał działać na którymś. Nie podoba mi się znaleźć się w sytuacji, gdy zatwierdzam jakieś zmiany, a za kilka tygodni okazuje się, że jeden serwer nie ma ich wprowadzonych. (Później uzupełniłem nieco reguły cfengine o samonaprawianie się, więc ten check przestał być niezbędny.)
  • Doszło monitorowanie lokalnej repliki drzewa LDAP, używanego przy uwierzytelnianiu użytkowników. Na niektórych serwerach slapd przestawał działać; trzeba było usuwać stare pliki bazy i pozwalać zadziałać mechanizmowi replikacji. Sprawdzanie LDAP pozwoliło po pierwsze dowiedzieć się o awarii zawczasu, a po drugie namierzyć przyczynę.
  • Uzupełniłem usługi o sprawdzanie, które zadania Baculi zdefiniowane w konfigu nie miały udanego uruchomienia dawniej niż kilka dni temu. Od tej pory przestałem się martwić o codzienne sprawdzanie poczty z nieudanymi zadaniami backupu.

Nie sprawdzam zajętości swapa, nie monitoruję wielkości ruchu sieciowego ani obciążenia dysków. Nie wiem do czego mogłyby te wartości w mojej instalacji służyć i jaki miałbym mieć z nich pożytek, a skoro tak -- te parametry są dla mnie nieistotne.

Powolutku, pomalutku mój Nagios się rozrastał do obecnej postaci, a dzisiaj wyraźnie było widać, że do czegoś się przydaje: jednemu z serwerów nawalił sprzęt i serwer przestał odpowiadać. Udało nam się dowiedzieć o problemie zanim którykolwiek z użytkowników zgłosił awarię. Sprawa została zgłoszona dostawcy sprzętu, a dwie godziny później serwer już wstał z powrotem.

Tak sprawna reakcja miała miejsce nie tylko dlatego, że mamy jakiś monitoring. Była możliwa przede wszystkim dlatego, że na każde ostrzeżenie reagujemy.

Wysłany: 06 grudnia 2011, 14:54:10; 24 komentarze

niskobudżetowy(?) dyndns.com

Niedawno na techblogu czytałem wpis o logowaniu się przez SSH z komputerem, który ma dynamicznie przydzielany adres IP. Przyszło mi do głowy, że może dynamicznie ustawiany DNS da się załatwić za pomocą bardziej eleganckich i standardowych konstrukcji niż skrypt uruchamiany przez nieinteraktywną sesję SSH?

Mój pomysł jest taki: weźmy dowolny serwer DNS, który obsługuje dynamic DNS updates (RFC 2136), na przykład BIND-a. Aktualizacje będziemy mu wysyłać ze specjalnego skryptu na jakiejś maszynie (żeby łatwiej było napisać zezwolenie dla skryptu, ewnetualnie żeby skrypt miał dostęp do sekretu DNSSEC). Ze skryptem natomiast łączyć się będziemy za pomocą XML-RPC. Słowem: zdalne uruchomienie procedury XML-RPC, której treścią jest zaktualizowanie wpisu w DNS.

Wykonanie opiera się o opisanego przeze mnie jakiś czas temu daemona xmlrpcd. Przedstawię przykład z użyciem BIND-a 9.6.

Najpierw trzeba wygenerować sekret, który będzie używany do uwierzytelniania żądań uaktualnienia rekordu. To uwierzytelnianie to będzie HMAC-MD5, więc wystarczy że sekret będzie dowolną wartością losową zakodowaną w Base64 (wybrałem 256 bitów):

perl -MMIME::Base64 -e 'read STDIN, $_, 256 / 8; print encode_base64 $_' < /dev/random

Output tego polecenia zapiszemy w konfiguracji BIND-a.

# named.conf
key "my-ddns-key" {
  algorithm hmac-md5;
  secret "CU5rRmwPsmKuWeDyKlPk9QGZKTb4aMStK2ul4aIZE1A=";
};

zone "dyn.atled.pl" IN {
  type master;
  file "/var/lib/bind/dyn.atled.pl.zone";
  update-policy { grant my-ddns-key subdomain dyn.atled.pl A; };
};

W ten sposób strefa dyn.atled.pl może mieć modyfikowane rekordy A, o ile żądanie zmiany jest uwierzytelnione odpowiednim kodem MAC. Oczywiście plik zawierający sekret powinien być chroniony (uprawnienia! najlepiej użyć dyrektywy include i wydzielić sekcję key do osobnego pliku).

Teraz pora na procedurę uaktualniającą wpis w DNS. Potrzebne perlowe moduły: Net::DNS i YAML

#!/usr/bin/perl
#
# /var/lib/xmlrpcd/methods/dyndns/update.pm
#
#-----------------------------------------------------------------------------

use warnings;
use strict;

use Net::DNS;
use YAML qw{LoadFile};

#-----------------------------------------------------------------------------

my $rpc_config = '/var/lib/xmlrpcd/etc/dyndns.yaml';

#-----------------------------------------------------------------------------

our $RPC_PARAMS = [qw{address user}];

sub entry_point {
  my ($addr, $user) = @_;

  $addr =~ s/^::ffff://;
  if ($addr =~ /:/) {
    die "IPv6 addresses are not supported yet.";
  }

  my $cfg = LoadFile($rpc_config);

  my $dns = new Net::DNS::Resolver(
    nameservers => [$cfg->{server}],
  );

  my $should_replace = 0;

  my $old_ip = $dns->query("$user.$cfg->{domain}", "A");
  if (defined $old_ip) {
    my @addresses = $old_ip->answer;
    if (@addresses != 1) {
      $should_replace = 1;
    } elsif ($addresses[0]->address ne $addr) {
      $should_replace = 1;
    }
  } else {
    $should_replace = 1;
  }

  if (not $should_replace) {
    return "Will keep current IP address.";
  }

  my $update = new Net::DNS::Update($cfg->{domain});
  $update->push(
    update => rr_del("$user.$cfg->{domain} A"),
    update => rr_add("$user.$cfg->{domain} $cfg->{record}{ttl} A $addr"),
  );
  $update->sign_tsig($cfg->{key}{name}, $cfg->{key}{secret});

  my $result = $dns->send($update);
  if (not defined $result) {
    die "Some error";
  }

  return "Updated: $user.$cfg->{domain} $cfg->{record}{ttl} A $addr";
}

#-----------------------------------------------------------------------------
1;
# vim:ft=perl

Procedura jest gotowa do użycia z xmlrpcd, ale oczekuje, że wołający się uwierzytelni (login podany podczas uwierzytelniania determinuje który rekord poprawić). Odsyłam do strony narzędzia i do dokumentacji po szczegóły konfiguracyjne daemona.

Powyższa procedura wymaga paru parametrów zapisanych w /var/lib/xmlrpcd/etc/dyndns.yaml. Poniżej przykładowa zawartość tego pliku:

domain: dyn.atled.pl
server: 127.0.0.1
key:
  name: my-ddns-key
  algo: hmac-md5
  secret: CU5rRmwPsmKuWeDyKlPk9QGZKTb4aMStK2ul4aIZE1A=
record:
  ttl: 60

Jak łatwo się domyślić, domain musi odpowiadać domenie, która będzie uaktualniana, server wskazuje adres serwera DNS, któremu aktualizacje będą wysyłane, key.name i key.secret muszą odpowiadać nazwie klucza i jego sekretowi z named.conf.

Od tej pory wystarczy na kliencie wywołać procedurę dyndns.update na odpowiednim serwerze. Przypominam, że procedura przewiduje uwierzytelnianie HTTP (i to na jego podstawie ustali, który rekord przestawiać).

Wysłany: 17 listopada 2011, 16:06:15; 3 komentarze

kung-fu administratora: RPC

Zadziwiające, jak niektóre kapitalne wprost narzędzia są przez administratorów ignorowane, a w ich zastępstwie stosuje się kłopotliwe, nieeleganckie konstrukcje.

Takim właśnie narzędziem są protokoły RPC. Idea RPC to, z grubsza rzecz biorąc, uruchomienie na serwerze pewnej funkcji na rzecz klienta, przy czym argumenty tej funkcji dostarcza klient i klient odbiera zwracaną wartość. Dzięki temu, że funkcja wywoływana jest oddzielona od programu klienckiego, może działać z dowolnymi potrzebnymi jej uprawnieniami, niezależnie od tego, z jakimi prawami pracuje klient.

Niestety, nie widziałem u nikogo użycia protokołów RPC (z wyjątkiem NFS i SMB/CIFS, które na RPC są zbudowane; ale to użycie sieciowego filesystemu, a nie bezpośrednio RPC). Nie widziałem nawet nikogo chwalącego się tym na blogu, usenecie czy forum dyskusyjnym. Po prostu cisza. Zamiast tego widuje się co jakiś czas pytanie w stylu "jak zrobić żeby przez panel webowy dało się przekonfigurować firewall?" i sugestię nadania Apache'owi uprawnień do uruchomienia sudo. This is not the way to go!

Innym przykładem błędu w sztuce jest użycie SSH do wielokrotnego bezobsługowego uruchamiania zadań na zdalnej maszynie. Dlaczego uważam to za niedorzeczne?

  • Zarządzanie kluczami publicznymi zdalnych maszyn jest uciążliwe, bo trzeba każdy pojedynczy klucz jakoś uprzednio dostarczyć. Nie ma czegoś takiego jak hierarchia certyfikatów.
  • Logowanie się na zdalny serwer, które nie wymaga podawania hasła, wymusza utrzymanie na tamtym serwerze pełnoprawnego konta użytkownika (razem z katalogiem domowym) i umieszczenia publicznej części klucza klienckiego, na podstawie którego logowanie się będzie się odbywać.
  • Jeśli skrypt, który będzie uruchamiany, przyjmuje jakieś parametry od klienta, walidacja ich może stać się horrorem: ani jednolitego standardu przesyłania argumentów i wyników, ani gotowych narzędzi do tego.
  • Nie ma jednolitego sygnalizowania błędów, a o obsłudze błędów jeszcze trzeba dodatkowo pamiętać, jeśli uruchamiany skrypt jest skryptem shellowym.

Trochę za dużo problemów jak dla mnie. Miałbym za każdym razem się martwić o to wszystko? Dużo rozsądniej by było użyć jakiegoś daemona, który przyjmuje żądania RPC, najlepiej owinięte w HTTP (co automatycznie załatwi uwierzytelnianie klienta (HTTP authentication), serwera i zabezpieczenie transmisji (HTTPs)), i na tej podstawie uruchamia odpowiednie procedury.

Z protokołów, które pasują do tego opisu, bardzo dobrze by się sprawdził XML-RPC, zwłaszcza że jest całkiem rozpowszechniony (np. standardowa dystrybucja Pythona dostarcza biblioteki xmlrpclib, podczas gdy o obsługę takiego SOAP trzeba się specjalnie postarać), ale tak właściwie dowolny protokół (który ma, rzecz jasna, opublikowaną specyfikację) by się nadał. Podstawowym kłopotem okazał się brak gotowego daemona (brokera) RPC.

Właśnie to uważam za zadziwiające: od ponad dwunastu lat mamy na rynku dobry protokół RPC, a administratorzy jeszcze go nie zaadoptowali. Rozumiem że przy poszczególnych pytaniach łatwiej było poradzić "sudo dla www-data", ale ciężko mi uwierzyć, że nigdy wcześniej żaden administrator-programista nie nadział się na problem uruchamiania procedury na zdalnym hoście.

W każdym razie postanowiłem wypełnić tę lukę własnym daemonem (napisanym w Perlu), który otrzymał mało pomysłową nazwę xmlrpcd. Na celu miał dostarczyć przede wszystkim łatwego w konfiguracji, lekkiego serwera HTTP obsługującego SSL i uwierzytelnianie, który by rozdzielał żądania do poszczególnych metod i potrafił je uruchamiać z różnymi uprawnieniami (w tym: z uprawnieniami roota). Niejako przy okazji powstał command-line'owy klient xmlrpcaller, który -- rzecz jasna -- jest używalny nie tylko z xmlrpcd.

Prosty przykład użycia jest na stronie narzędzia.

Wysłany: 28 października 2011, 16:15:29; 46 komentarzy

svn checkout && make && svn commit

W mojej pracy zdarza się potrzeba przebudowywania pewnych plików i zapisywania ich w repozytorium systemu kontroli wersji (Subversion albo git), głównie dla transportu na inną maszynę. Pliki wynikowe z czegoś muszą być wygenerowane, więc mam do czynienia również ze źródłami (a przynajmniej ja je tak nazywam). Źródła warto trzymać w repozytorium systemu kontroli wersji, niekoniecznie tym samym co wyniki.

Przy takiej organizacji danych aż prosi się o zadanie crona, które ściągnie źródła, uruchomi ich kompilację, a wyniki zapisze w repozytorium docelowym.

Powyższe zadanie nie wydaje się specjalnie trudne czy skomplikowane. Prosty skrypt uruchamiany z crona powinien załatwić sprawę. Niestety, przejście do szczegółów ujawnia masę spraw, którymi trzeba się zająć:

  • wybór: jeśli źródła się nie zmieniły, to budować czy nie?
    • czasem wynik dwóch kompilacji jest różny, ale równoważny; wtedy commit jest niepotrzebnym generowaniem ruchu w repozytorium
    • czasem wynik kompilacji zależy od czegoś spoza repozytorium (np. od wpisów w DNS albo bazie SQL)
  • jak stwierdzić że źródła się zmieniły?
  • jeli pliki wynikowe się nie zmieniły -- nie wysyłać e-maila (budowanie będzie się odbywać często, np. co 10 minut; mało interesującym jest czytać 144 razy dziennie że wszystko się udało i nic nie zrobiono)
  • jak uciszyć uruchamiane komendy, gdy jeszcze nie wiadomo czy powiadomienie będzie wysyłane (wtedy pełny log z wykonania będzie potrzebny), czy też nie będzie, bo nie było zmian w wynikach?
  • podsumowanie wykonanych operacji powinno w e-mailu znaleźć się przed logami, czyli w odwrotnej kolejności niż te informacje powstają
  • jeśli svn up na źródłach się nie powiodło, nie ma co budować
  • automat powinien sam wyprodukować commit message, którego treść powinna nieść użyteczne informacje
  • recovery from errors, i dla błędów budowania, i dla błędów systemu kontroli wersji (jeśli coś się nie powiodło, trzeba wyczyścić środowisko pracy, żeby następna próba miała szansę się udać)

Jak widać, to dość żmudna i powtarzalna robota, którą łatwo oddzielić od samego budowania. Uznałem, że najlepiej będzie stworzyć narzędzie, które raz na zawsze odciąży mnie od myślenia o obsłudze błędów i pozwoli zająć się procesem budowania i plikami źródłowymi.

I tak powstał cronbuilder.

Chciałem opisać tu jakiś przykład użycia, ale na stronie narzędzia właściwie jest już opisany, nawet z metodą pisania skryptu budującego.

Wysłany: 23 października 2011, 21:44:41; 7 komentarzy

Review Board na Debianie Squeeze: instrukcja prawidłowej instalacji

Miałem jakiś czas temu zadanie instalacji Review Board, webowego narzędzia do code review. Wtedy aktualną wersją była 1.5.3 i to jej instalację tu opusuję. Jako system, na którym to miało chodzić, wybrany został Debian Squeeze.

Oficjalna metoda instalacji każe użyć pythonowego narzędzia easy_install. To jest głupi wymóg. Jest całkiem sporo powodów żeby nie używać easy_install.

Prawidłowo wykonana instalacja wygląda tak: apt-get install python-reviewboard. Oczywiście ktoś najpierw musi przygotować potrzebne pakiety -- i o tym będzie wpis.

Perl ma fajne narzędzie dh-make-perl do produkowania pakietu DEB ze standardowego tarballa z modułem. Python, jak się okazuje, ma coś podobnego: py2dsc (pakiet python-stdeb). Będę go używać do przygotowania pakietów.

Do budowania potrzebne będą jeszcze build-essential i fakeroot (jakoś bardziej mi się podoba praca bez praw roota przy budowaniu pakietów). Instalacja jak zwykle:

apt-get install python-stdeb build-essential fakeroot 

Review Board wymaga do pracy Django Evolution, którego w oficjalnym repozytorium Debiana nie ma. Link do tarballa (tarballa, nie pythonowego jaja) znalazłem na stronie Python Package Index, z którego korzysta setuptools (pakiet django_evolution). Na tarballu trzeba uruchomić

py2dsc django_evolution-*.tar.gz

py2dsc utworzy katalog deb_dist, w którym będzie leżeć grzecznie cały pakiet źródłowy Debiana (*.dsc z przyległościami), już rozpakowany. Teraz wystarczy dpkg-buildpackage -b, a wynikiem jest pakiet DEB z Django Evolution.

Niestety python-djangoevolution koliduje plikami runtests.py, settings.py i run-pyflakes.py z katalogu /usr/share/pyshared/tests z pakietem python-django-djblets (który też będzie potrzebny dla Review Board). Żeby ten problem rozwiązać użyłem mechanizmu DPKG zwanego diversions. Polega to na tym, że podczas instalacji pakietu podmieniony plik jest zapisywany pod inną nazwą, chyba że instalowany pakiet nosi taką nazwę jak właściciel diversion. Wygląda to tak:

dpkg-divert --add --package python-django-djblets \
  --divert /usr/share/pyshared/tests/runtests.djangoevolution.py \
           /usr/share/pyshared/tests/runtests.py

dpkg-divert --add --package python-django-djblets \
  --divert /usr/share/pyshared/tests/settings.djangoevolution.py \
           /usr/share/pyshared/tests/settings.py

dpkg-divert --add --package python-django-djblets \
  --divert /usr/share/pyshared/tests/run-pyflakes.djangoevolution.py \
           /usr/share/pyshared/tests/run-pyflakes.py

UWAGA: To nie jest prawidłowe podejście. Prawidłowym podejściem jest usunąć powyższe pliki z pakietu python-djangoevolution.

Teraz, po dodaniu obejść dla kolidujących pakietów, można zainstalować python-djangoevolution i resztę pakietów, których Review Board wymaga do działania.

apt-get install python-pygments python-dateutil python-memcache python-tz \
  python-recaptcha python-ldap python-svn python-feedparser \
  python-django-djblets python-imaging python-django python-flup \
  python-paramiko
dpkg -i python-djangoevolution_*.deb

W tej liście brakuje m.in. python-psycopg2 (dla PostgreSQL). Dopasuj sobie czego tam potrzebujesz do bazy danych.

Teraz zostaje ściągnąć tarballa (znowu, nie jajko) Review Board (z PyPI albo ze strony narzędzia) i uruchomić py2dsc:

py2dsc ReviewBoard-1.5.3.tar.gz
cd deb_dist/*/ && dpkg-buildpackage -b

Po zbudowaniu i zainstalowaniu pakietu python-reviewboard reszta instalacji już może się odbyć zgodnie z procedurą z dokumentacji.

W tym wpisie nie wspomniałem o umieszczeniu pakietu python-reviewboard we własnym repozytorium pakietów, żeby można było instalować go za pomocą apt-get. Ufam, że wiesz jak się to robi i masz już takie repozytorium przygotowane.

UWAGA: pakiet python-reviewboard nie ma wylistowanego w zależnościach python-djangoevolution. Przy apt-getowaniu trzeba ten drugi ręcznie podać.

Wysłany: 30 września 2011, 13:15:06; 2 komentarze

sleep $RANDOM; push-logs

Dzisiaj dość prosta sprawa.

Mam pod opieką coś koło 350 maszyn. Potrzebuję żeby te maszyny raz na jakiś (nie za długi, tak ze 20 minut) czas wysłały logi z wykonania do centralnego serwera. Ot, proste zadanie cronowe:

*/20 *  * * *  root  /usr/local/sbin/push-logs

Problem, jakiego próbuję uniknąć, to skok obciążenia serwera odbierającego logi: 350 połączeń SSL naraz (o *:00, *:20 i *:40) może łatwo zeżreć zasoby serwera, zwłaszcza gdy ten jest jedynie wirtualką z jednym procesorem.

Podszedłem do sprawy podobnie jak to robi cfengine: zadanie najpierw śpi pewną liczbę sekund, a potem uruchamia skrypt wysyłający. Spokojnie mógłbym zapisać zadanie tak:

*/20 *  * * *  root  sleep $(( $RANDOM % (5 * 60) )); /usr/local/sbin/push-logs

Nie jest to jednak moim zdaniem ładne rozwiązanie: ten sam serwer będzie miał w wysyłaniu logów opóźnienie raz rzędu paru sekund, raz trzech minut. Fajnie by było mieć komendę, która zwróci liczbę różną na różnych hostach, ale dla danego hosta zawsze taką samą (albo przynajmniej rzadko się zmieniającą).

Pomysł: MD5 z nazwy hosta i ewentualnie reszty danych zwracanych przez uname(2). Rozwiązanie (skrypty nazwałem hosthash):

#!/usr/bin/perl -l

use warnings;
use strict;

use POSIX qw{uname};
use Digest::MD5 qw{md5};

if (@ARGV && $ARGV[0] eq '--help') {
  print "Usage: $0 [modulus]";
  print "";
  print "This script prints a 32-bit number (0 <= num < modulus).";
  print "The number is based on uname() (including hostname and kernel release).";
  exit;
}

my $hash = md5(join " ", uname());

my $int = unpack "N", $hash;

if (@ARGV) {
  print $int % $ARGV[0];
} else {
  print $int;
}

# vim:ft=perl
#!/usr/bin/python

from hashlib import md5
from os import uname
from sys import argv
from struct import unpack

if len(argv) > 1 and argv[1] == "--help":
  print "Usage: %s [modulus]" % argv[0]
  print ""
  print "This script prints a 32-bit number (0 <= num < modulus)."
  print "The number is based on uname() (including hostname and kernel release)."
  exit()

h = md5(" ".join(uname())).digest()
# XXX: to be compatible with Perl version
i = unpack(">L", h[0:4])[0]

if len(argv) > 1:
  print i % int(argv[1])
else:
  print i

# vim:ft=python:ts=2:sw=2:et

Teraz zadanie cronowe może wyglądać tak:

*/20 *  * * *  root  sleep $(hosthash $((5 * 60))); /usr/local/sbin/push-logs

Wysłany: 16 września 2011, 15:29:17; 34 komentarze

Postfix, Dovecot, LDAP, virtual users -- catchall

Dostałem za zadanie wprowadzić adres catch-all do serwera pocztowego. Osobiście uważam że to głupota, ale to nie ja tego serwera używam. Znaczy, nie mój cyrk, nie moje małpy.

Całość stoi na daemonach Postfix i Dovecot (1.2), użytkownicy jako użytkownicy wirtualni (virtual_mailbox_maps) są trzymani w LDAP, transport dovecot (dovecotowy deliver) jest ustawiony jako virtual_transport.

Postfix używa map z virtual_mailbox_maps jedynie jako listy do weryfikacji użytkowników (jest/nie ma), ale wyniku lookupu nie używa w żaden sposób, a przesyłkę przekazuje do transportu dovecot z takim odbiorcą, jak przyszedł. Niedobrze, bo to produkuje zwrotki: Postfix przyjmuje e-mail, podaje go Dovecotowi, a Dovecot mówi "nie ma takiego użytkownika".

Trzeba jeszcze dokonfigurować Dovecota. Po pierwsze, D musi przyjmować pocztę dla wszystkich, po drugie, powinien pchać ją na wskazany adres zbiorczy.

W sumie moje rozwiązanie wygląda tak:

# LDAP additional schema

# all of the following OIDs will use the tree 1.3.6.1.4.0
# (iso.org.dod.internet.private.reserved)

# why not derivative? see here
attributetype ( 1.3.6.1.4.0.1.1 NAME 'destMail'
  DESC 'Destination e-mail for alias'
  EQUALITY caseIgnoreIA5Match
  SUBSTR caseIgnoreIA5SubstringsMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )

objectclass   ( 1.3.6.1.4.0.66.2 NAME 'emailObject'
  DESC 'generic e-mail object'
  SUP top STRUCTURAL
  MUST ( cn $ mail )
  MAY ( description ) )

# mail: specifies domain for which this entry is
# destMail: destination account where catchall entries are to be saved
objectclass   ( 1.3.6.1.4.0.66.3 NAME 'emailCatchAll'
  DESC 'catch-all e-mail entry for a domain'
  SUP emailObject STRUCTURAL
  MUST ( destMail ) )

Pierwsze dwa wpisy już miałem wcześniej, więc dodanie trzeciego w postaci klasy emailCatchAll było proste. Teraz wpisy catch-all będą wyglądać tak:

dn: cn=catchall@example.com,dc=example.com
objectClass: top
objectClass: emailCatchAll
cn: catchall@example.com
mail: @example.com
destMail: catchall@example.com

Atrybuty commonName i destMail nie są tu w żaden sposób powiązane.

Mając wpisy catchall można przejść do konfiguracji Postfiksa i Dovecota:

# /etc/postfix/main.cf
virtual_mailbox_maps =
  ldap:/etc/postfix/users.ldap
  ldap:/etc/postfix/catchall.ldap
# /etc/postfix/catchall.ldap
version = 3
server_host = ldap://localhost/
search_base = dc=example.com
query_filter = (&(objectClass=emailCatchAll)(mail=%s))
result_attribute = destMail

Wprawdzie przez catch-all pierwsza mapa (users.ldap) przestaje być potrzebna, bo wynik lookupu jest ignorowany i jedyną potrzebną z niego informacją jest "tak, to konto istnieje", ale moim zdaniem łatwiej tę konfigurację zrozumieć w ten sposób.

# /etc/dovecot/dovecot.conf
auth default {
  # ...
  userdb ldap { # regular users
    args = /etc/dovecot/dovecot-ldap.conf
  }
  userdb ldap { # catch-all addresses
    args = /etc/dovecot/dovecot-catchall-ldap.conf
  }
  # ...
}
# /etc/dovecot/dovecot-catchall-ldap.conf
uris = ldap://localhost/
ldap_version = 3
base = dc=example.com
user_filter = (&(objectClass=emailCatchAll)(mail=@%d))
user_attrs = destMail=user

Dodałem drugą bazę użytkowników (ale nie bazę do uwierzytelniania, passdb), która wyszukuje, czy dla danego użytkownika (a raczej dla jego domeny) jest zdefiniowany adres catch-all w stylu postfiksowym (@example.com). Jeśli tak, nazwa użytkownika jest zmieniana na taką jak stoi w atrybucie destMail.

Podane rozwiązanie jest moim zdaniem dość eleganckie, bo kolejne domeny mogą mieć niezależne skrzynki zbierające nietrafione e-maile, a uruchomienie takiej skrzynki to zdefiniowanie jednego wpisu w LDAP.

Wysłany: 09 września 2011, 16:01:39; 8 komentarzy

debhelper: po co mi to, tak właściwie?

Tak już któryś raz z rzędu wciskam sekwencję Enterów przy instalacji pakietu w Debianie. Tym razem był to libnss-ldap i pytał o parametry serwera LDAP, z którym ma się połączyć.

Nasunęło mi to myśl, że ja prawie w ogóle z pytań debhelpera nie korzystam. Tam, gdzie takie pytania przyspieszyłyby pracę administratorowi (na przykład przy instalacji Exima albo modułu NSS/LDAP), ja i tak używam systemu dystrubucji konfiguracji. Wychodzi mi, że w większych sieciach debhelper jest zwyczajnie zbędny.

Wysłany: 29 sierpnia 2011, 16:07:53; 8 komentarzy

Subversion: SSL error: Key usage violation in certificate

W jednej sieci na serwerach używam repozytorium Subversion do transportu plików konfiguracyjnych. Dość typowa instalacja, z WebDAV-em i SSL (HTTPs). Certyfikat serwera to certyfikat self-signed wygenerowany za pomocą OpenSSL (na RHEL5, z domyślnymi ustawieniami).

Podczas dołączania Red Hata (RHEL) 6 do sieci napotkałem taki błąd:

svn: OPTIONS of 'https://<repo.server.address>/svn/trunk': SSL handshake failed: SSL error: Key usage violation in certificate has been detected. (https://<repo.server.address>)

Po krótkim przeszukaniu sieci i ręcznym sprawdzeniu okazuje się co następuje:

  • svn, a raczej libneon.so, jest zlinkowany z GnuTLS
  • svn w protokole SSL wybierał wymianę Diffiego-Hellmana do ustalenia klucza sesyjnego
  • GnuTLS wymaga żeby pole X.509v3 Key Usage certyfikatu zawierało opcję digitalSignature, jeśli w użyciu ma być wymiana DH

Pierwszą myślą, jaka mi przyszła do głowy, było: "znowu GnuTLS zjeb^Wzepsuł sprawę", ale w tym przypadku to jednak nie jego wina. Po pierwsze, OpenSSL ma źle dobrane defaulty w konfigu, skoro mi taki certyfikat wygenerował. Po drugie, OpenSSL nieprawidłowo akceptuje certyfikat bez odpowiedniej opcji. Ale podstawowa wina leży po stronie specyfikacji X.509, która użycie certyfikatu definiuje bardzo niskopoziomowo, przez co pojawiają się różnice między implementacjami co kiedy jest akceptowane.

Rozwiązania są trzy: wymienić pakiet neon na kliencie na taki zlinkowany z OpenSSL (za dużo roboty), przegenerować certyfikat, żeby zawierał odpowiednią opcję (not applicable w moim przypadku) albo wyłączyć negocjację DH po stronie serwera. Ostatnie dla Apache'a wygląda tak:

<VirtualHost *:443>
  # ...
  SSLEngine on
  SSLCipherSuite ALL:!EDH:!LOW
  # ...
</VirtualHost *:443>

Literatura:

Wysłany: 18 lipca 2011, 14:20:35; Dodaj komentarz

jak sobie zrobić dobrze: przygotowywanie konfiguracji, testowanie i aplikowanie jej na produkcji

Tak się jakoś złożyło, że mam pod opieką -- oprócz sieci *nastu serwerów linuksowych w mojej firmie i dłubania w sieci z kolejnymi trzystoma w sieci klienta -- pojedynczy serwer produkcyjny (usługa administracji serwerem świadczona na zewnątrz). Na tym serwerze są WWW, bazy danych i poczta (na którą aktualnie składają się Postfix, amavis, Dovecot i OpenLDAP).

O ile z silnikami bazodanowymi nie dłubię za bardzo, a konfiguracja Apache'a (przynajmniej na tym serwerze) nie wymaga specjalnych akrobacji, to przed wprowadzeniem poprawek do poczty wolę wszystko najpierw przygotować w środowisku testowym (osobny serwer z takim samym systemem operacyjnym i usługami jak na produkcji).

Brzmi prosto, ale bez pomocy automatów to sporo mało odkrywczej, uciążliwej roboty (m.in. kopiowanie stosownych plików w stosowne miejsca i przeładowanie usługi) którą trzeba wykonywać za każdym razem. Jestem leniwy, więc wymyśliłem sobie, że taniej mi będzie sporządzić system dystrybucji konfiguracji i procedurę wprowadzania zmian.

Myślałem nad czymś, co na tyle pomoże mi w pracy, żeby łatwiej i wygodniej mi było ustawiać usługę w koszerny sposób niż ręcznie paprząc w konfigach. Po jakimś tam czasie wykoncypowałem następujący proces:

  1. na serwerze produkcyjnym znajduje się kopia robocza konfigów utworzona z gałęzi production systemu kontroli wersji
  2. w regularnych odstępach czasu (np. co kwadrans) na produkcji z gałęzi production ściągane są ewentualne poprawki; jeśli rzeczywiście jakieś były, to są nanoszone w usługach właściwych (kopia + reload)
  3. wszelkie zmiany wykonywane przez administratora dokonywane są na żywych konfigach w środowisku testowym
  4. zmiany wprowadzone w konfigach są commitowane do systemu kontroli wersji (git w moim przypadku), do gałęzi devel
  5. po przetestowaniu w środowisku testowym, administrator merge'uje zmiany z gałęzi devel do gałęzi production
  6. administrator nie dotyka prawdziwych konfigów na środowisku produkcyjnym

Punkt trzeci jest istotny. W innych procesach które mi przychodziły do głowy, admin zmieniał pliki w kopii roboczej, zupełnie oderwanej od właściwej usługi. Średnio to wygodne, bo każda zauważona literówka to dodatkowy niepotrzebny commit. Administrator (a przynajmniej ja) woli najpierw wprowadzić zmiany i sprawdzić czy daemon wstanie, a commitować dopiero gdy działa.

Można powiedzieć że wilk syty, owca cała, a na dodatek jeszcze piwnica pełna powideł śliwkowych. Administrator pracuje tak jak lubi, poprawki są (a przynajmniej mają spore szanse być) najpierw przetestowane, a do tego zostaje ładna historia zmian.

Samych narzędzi na razie nie zamieszczam, ponieważ czekają na HowTo dla cfengine (liczę że w tym, a najdalej następnym tygodniu je skończę). Zresztą to nie narzędzia mi pomogły w pracy, a proces, który przedstawiłem w tym wpisie.

Wysłany: 13 lipca 2011, 02:17:39; Dodaj komentarz

instalacja modułów perlowych: site, vendor czy perl?

Każdy kto przygotowywał samodzielnie pakiety (DEB, RPM czy inny) z modułami Perla wie, że moduły z CPAN-u najczęściej używają ExtUtils::MakeMakera do budowy. Fajnie że to de facto standard, ale już gorzej że w dokumentacji nie ma objaśnionej jednej sprawy: jaki styl instalacji wybrać?

Do wyboru są trzy możliwości: perl, vendor i site, a wybiera się to tak:

perl Makefile.PL INSTALLDIRS=vendor
# make
# ...

Czym się te style instalacji właściwie różnią? Ano rozmieszczeniem plików po instalacji:

  • Wartość site jest przeznaczona dla modułów zbudowanych lokalnie przez administratora. Taki ./configure && make && make burdel, choć o tyle lepszy, że MakeMaker odnotowuje co i gdzie zainstalował.
  • Wartość perl została przewidziana dla modułów dostarczanych w standardowej dystrybucji Perla i właściwie nie powinna być używana przez przeciętnego admina.
  • Wartość vendor wymyślono specjalnie dla nas, administratorów. Ten układ katalogów jest używany przez opiekunów dystrybucji dla rozmieszczenia modułów dodatkowych, jak List::MoreUtils, DBI czy Net::LDAP, które następnie lądują w pakietach.

Ścieżki poszczególnych typów instalacji można sprawdzić za pomocą modułu Config (sprawdź perldoca) albo przełącznika -V:

perl -V:'install.*(lib|arch)'

Warto jeszcze sprawdzić kolejność przeszukiwania katalogów używaną przy ładowaniu modułów (zawartość @INC). Czasem się zdarza (Gentoo z Perlem 5.8 (stary), RHEL4), że jest nieintuicyjna. Powinna być taka, żeby najpierw ładować moduły zainstalowane lokalnie (site), potem za pomocą systemu pakietów (vendor), a dopiero jak to zawiedzie, żeby ładować to co przyszło z dystrybucją Perla.

Bierze się to stąd, że część modułów włączonych do standardowej dystrybucji Perla mogła dorobić się aktualizacji. Używałem w jednym programie modułu Log::Log4perl i chciałem skorzystać z logowania do sysloga. Do tego potrzebny był bundle Log::Dispatch z modułem Log::Dispatch::Syslog, a ten z kolei wymagał odpowiednio nowego Sys::Syslog. Okazało się, że Red Hat 4 i 5 dostarczają ten moduł w zbyt starej wersji. W RHEL5 nie było problemu, po prostu doinstalowałem odpowiedni pakiet i zadziałało; niestety w RHEL4 trzeba było poprawiać @INC, bo katalogi site i vendor znajdowały się po katalogach perl.

Wysłany: 18 czerwca 2011, 12:27:56; 2 komentarze

LDAP: wyszukiwanie po atrybucie i atrybuty pochodne

Niedawno przeprowadzałem rozruch Postfiksa z kontami w LDAP. Potrzebne były, a jakże, również aliasy pocztowe. O ile parę aliasów systemowych w pliku /etc/aliases jest jeszcze do utrzymania, to użytkownicze powinny być trzymane w LDAP, podobnie jak zostało to zrobione dla kont.

Wymyśliłem sobie że obiekt w katalogu będzie łączyć wiele adresów wirtualnych z wieloma adresami docelowymi. Na przykład e-mail na który wysyła się zamówienia może mieć kilka dozwolonych adresów: sales@example.com, sprzedaz@example.com, orders@example.com, zamowienia@example.com i tak dalej, wszystkie mają kierować przesyłkę na ten sam adres. Ale sprzedażą może się zajmować więcej niż jedna osoba; wtedy przesyłka ma trafić do wszystkich.

Atrybut mail do pierwszej części jest wystarczający, ale potrzebny mi był jeszcze drugi, o tej samej składni co mail, ale z inną nazwą. Zdefiniowałem sobie w schemie jak następuje:

attributetype ( 1.3.6.1.4.0.1.1 NAME 'destMail'
  DESC 'Destination e-mail for an alias'
  SUP mail )

objectclass   ( 1.3.6.1.4.0.66.1 NAME 'emailAlias'
  DESC 'e-mail alias entry'
  SUP top STRUCTURAL
  MUST ( cn $ mail $ destMail )
  MAY ( description ) )

Drzewo 1.3.6.1.4.0 jest zarezerwowane i u żadnego dostawcy nie może się pojawić, więc go bezczelnie użyłem do własnych celów.

Sama schema jest ładna, fajna, ale nie działa tak jak powinna. Załóżmy że mamy taki wpis:

dn: cn=sales,ou=aliases,dc=example.com
objectClass: top
objectClass: emailAlias
cn: sales
mail: sales@example.com
mail: orders@example.com
destMail: hansel.gingerbread@example.com
destMail: alice.wonderland@example.com

Podczas wyszukiwania (mail=sales@*) zachowuje się poprawnie, znaczy: element cn=sales,ou=aliases,dc=example.com jest zwracany. Ale jeśli trzeba wyszukać wszystkie wpisy Alicji i użyje się filtra (mail=alice.*@*), to nagle się okazuje że ten element też pasuje. Możliwe że przy tworzeniu zapytania takie było nasze zamierzenie, ale niewykluczone że to błąd w przygotowaniu bazy.

Takie zachowanie LDAP jest normalne i zgodne ze specyfikacją (RFC 4511, sekcja 4.5.1.7.1 "SearchRequest.filter.equalityMatch" z opisu operacji SEARCH), a po chwili zastanowienia nawet naturalne, tylko że dla mnie nadal jest trochę zaskakujące (mimo że znałem to już parę lat temu, teraz znowu mi się trafiło).

Rozwiązania są dwa: albo zdefiniować atrybut incMail podobnie do destMail i zamiast mail używać incMail, albo zdefiniować destMail tak żeby atrybut był identyczny z mail, ale nie był jego podtypem. Wybrałem to drugie (prostsze, mniej zmian):

attributetype ( 1.3.6.1.4.0.1.1 NAME 'destMail'
  DESC 'Destination e-mail for an alias'
  EQUALITY caseIgnoreIA5Match
  SUBSTR caseIgnoreIA5SubstringsMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )

Wysłany: 17 czerwca 2011, 15:00:30; Dodaj komentarz

Cyrus SASL: _sasl_plugin_load failed on sasl_auxprop_plug_init for plugin: ldapdb

Jakiś miesiąc temu stawiałem serwer pocztowy na Postfiksie na Gentoo (akurat na dystrybucję nie miałem wpływu; mówi się trudno). Miało być wszystko, z uwierzytelnianiem, użytkownikami wirtualnymi i w ogóle.

Do Postfiksa dodaje się uwierzytelnianie SASL, najczęściej w implementacji Cyrus SASL. Mnie się zachciało trzymać użytkowników w bazie LDAP.

Cyrus SASL został skompilowany z obsługą LDAP, co spowodowało zbudowanie pluginu libldapdb.so. Ten plugin nie do końca robił to czego oczekiwałem, więc zdałem się na saslauthd z mechanizmem uwierzytelniania ldap. Ustawiłem co trzeba w /etc/saslauthd.conf (z pakietem przyszło drobne HowTo od producenta, LDAP_SASLAUTHD; w Debianie znajduje się w pakiecie cyrus-sasl2-doc), a potem skonfigurowałem postfiksowego smtpd

# /etc/sasl2/smtpd.conf
pwcheck_method: saslauthd
mech_list: PLAIN LOGIN

Wszystko ładnie zadziałało, ale jedna rzecz mi się nie spodobała: logi.

Jun 17 08:50:54 mailserver postfix/inet/smtpd[24158]: auxpropfunc error invalid parameter supplied
Jun 17 08:50:54 mailserver postfix/inet/smtpd[24158]: _sasl_plugin_load failed on sasl_auxprop_plug_init for plugin: ldapdb

Okazuje się że plugin ldapdb i tak był inicjalizowany, mimo że później w ogóle nie był w użyciu. Rozwiązaniem mogłoby być usunięcie pliku z katalogu, ale to nie jest dobry pomysł. Po każdej aktualizacji miałbym pamiętać że coś jeszcze trzeba ręcznie kasować?

Na szczęście wystarczyło ustawić parametr ldapdb_uri na cokolwiek -- i już funkcja inicjalizacji była szczęśliwa.

# /etc/sasl2/smtpd.conf
# this URI is just to satisfy ldapdb plugin
ldapdb_uri: ldap://example.net/
pwcheck_method: saslauthd
mech_list: PLAIN LOGIN

Wysłany: 17 czerwca 2011, 12:26:09; 3 komentarze