Bezpieczeństwo w PASOE cz. IV – LDAP, domeny i CP

W poprzednim artykule wyszliśmy poza technologię Progress Software i dzięki Apache Directory Studio przeprowadziliśmy uwierzytelnienie użytkowników korzystających z restowej aplikacji PASOE.

Pora wykonać następny krok (naprzód!) i uwzględnić w całym procesie token – obiekt CLIENT-PRINCIPAL. Obiekt ten został wprowadzony w OE10, wraz z pojawieniem się technologii Auditing. Przypomnę, że chodziło o rejestrowanie zmian w systemie bazodanowym i niezbędnym było posiadanie informacji, kto te zmiany wprowadza. Domyślnie, auditing korzysta z wartości user-id użytkownika, który jest zdefiniowany w bazie.

Jednakże w kontekście aplikacji bardziej odpowiedni jest identyfikator użytkownika sesji OpenEdge. Dzięki takiemu identyfikatorowi – tokenowi, aplikacja nie musi sprawdzać za każdym razem gdy jest to konieczne, kto jest aktualnie zalogowany. Wszystkie potrzebne informacje są bezpiecznie przechowywane w obiekcie sesji CLIENT-PRINCIPAL. Obiekt ten może zawierać nie tylko dane użytkownika, ale dodatkowe właściwości jak np. role potrzebne w procesie autoryzacji.

CLIENT-PRINCIPAL (CP) jest szczególnie przydatny w aplikacjach rozproszonych (AppServer, PASOE) ponieważ może zarządzać kontrolą dostępu dla wielu procesów aplikacji.

Nie będę omawiał wszystkich szczegółów związanych z CP, gdyż jest to temat zbyt obszerny. Bardzo ważne jest to, że do “zapieczętowania” (seal) obiektu i późniejszego jego użycia w sesji, użytkownik musi być zdefiniowany w tzw. domenie i musi być znany klucz (Access Code) do domeny. Czyli: definiujemy domeny w bazie (nazwa domeny, klucz dostępu), a następnie definiujemy użytkownika w danej domenie. OK, to powinno nam na razie wystarczyć. Na poniższym rysunku widać narzędzie Database Administration i okno do definiowania domen ( opcja: Admin -> Security -> Domain Maintenance -> Domains…).

Teraz uwaga! użytkownicy nie muszą znajdować się w bazie, mogą być zdefiniowani w systemie LDAP, co zaraz pokażę. Definicje domen w bazie będą jednak potrzebne. Później wyjaśnię dlaczego.

Teraz jest dobry moment żeby podłączyć PASOE do serwera bazy danych.

Proszę zwrócić uwagę na dwie domeny: adminusers i webusers (opis: LDAP), które zdefiniowałem na potrzeby tego artykułu. Kodem dostępu do domen jest odpowiednio: adminpa, webpa.
Tworzymy prosty tekstowy plik zawierający pary wartości: nazwa domeny, kod dostępu. Zobaczmy taki przykładowy plik ldapasreg.csv.

Posłuży on jako parametr wejściowy do utworzenia zaszyfrowanego pliku rejestru, który może służyć do zapieczętowania tokena. W oknie znakowym podajemy komendę:
gendomreg ldapasreg.csv ldapasreg.bin.
Wygenerowany plik ldapasreg.bin kopiujemy do podkatalogu PASOE conf.

Teraz zabieramy się za modyfikację struktury w Apache Directory Studio. Po uruchomieniu instancji serwera i otwarciu połączenia dodajemy dwie grupy: OED:adminusers, OED:webusers. Jak łatwo się domyślić, to są nasze domeny. W LDAP nie ma obiektów typu domena, więc definiujemy grupy, a żeby odróżnić je od zwykłych grup dodajmy np. przedrostek. Użyłem przedrostka OED:, ponieważ oznacza OpenEdge Domain, ale można użyć dowolnego jaki nam pasuje.


W grupie OED:adminusers dodałem użytkownika Piotr Adminowy (czyli admin1), a w domenie OED:webusers użytkownika Marek Webowy (user1). Tym samym należą oni do dwóch grup (drugą jest PSCUser).

Przechodzimy do pliku: [working directory]\[instancja pasoe]\webapps\ROOT\WEB-INF\oeablSecurity.properties i dodajemy ustawienia:

## OEClientPrincipalFilter.registryFile=ABLDomainRegistry.keystore
OEClientPrincipalFilter.registryFile=ldapasreg.bin
...
 ## Used to obtain an OE Domain name from the authenticated user's list of
 ## roles if the user did not supply one (overrides the default domain property)
 OEClientPrincipalFilter.domainRoleFilter=ROLE_OED:(.*)

Pierwszy parametr określa binarny plik, który utworzyliśmy wcześniej, drugi to prefix określający domenę. Z poprzedniego artykułu wiemy, że dla określenie roli, do nazwy grupy użytkownika zostanie dodany prefix ROLE_ (parametr ldap.authpopulator.rolePrefix). Natomiast dla określenia domeny, zostanie zidentyfikowany ciąg znaków ROLE_OED: i wszystko co nastąpi po nim, to nazwa domeny. Czyli dla użytkownika admin1 będzie to adminusers, a dla user1 webusers.
Ponieważ w pliku binarnym istnieje taka domena i kod dostępu, uwierzytelnienie powinno się udać.

Zanim przejdę dalej trzeba odpowiedzieć na jedno ważne pytanie: dlaczego informacje o domenach znajdują się w trzech miejscach: bazie danych, pliku bin, serwerze LDAP? Aby to zrozumieć musimy przypomniec sobie, że PASOE to połączenie dwóch technologii: ABL i Java (Tomcat). W Spring Security (Java) tworzony jest token, który Tomcat może zapieczętować dzięki informacjom w pliku bin. Token ten jest transformowany do CLIENT-PRINCIPAL ABL, dzięki czemu możemy korzystać z metod ABL aby np. modyfikować role, zarządzać stanem CP itd. CP nie korzysta z pliku bin, dane o domenach pobiera z bazy. Tutaj w bazie nie trzeba definiować użytkowników w domenach, są oni zdefiniowani tylko na serwerze LDAP.

Ponieważ możemy teraz korzystać z obiektu CP, dodajmy dwie procedury w konfiguracji naszej instancji PASOE, dla otwarcia sesji i rozpoczęciu żądania. Całe procedury można pobrać tutaj.


Pierwsza procedura wczytuje parametry domen z bazy.
cpStartup.p

// Loading domain registry for the session
m_lOK = SECURITY-POLICY:LOAD-DOMAINS(1).

pcActivate.p weryfikuje poprawność zapieczętowania CP i wydruk jego atrybutów do logu.
cpActivate.p

// Verify if the Client-Principal is sealed successfully or not. If not, return error.
IF lOK THEN 
DO:
    // Dump the Client-Principal attributes
   
    MESSAGE "    ID:         '" + hCP:QUALIFIED-USER-ID + "'".
    MESSAGE "    session-id:" hCP:SESSION-ID.
    MESSAGE "    state:" hCP:LOGIN-STATE.
    MESSAGE "    roles: " + hCP:ROLES.
    MESSAGE "     domain: " + hCP:DOMAIN-NAME.
    
    cList = hCP:LIST-PROPERTY-NAMES.
    iListSize = NUM-ENTRIES(cList, ",").
    IF ( 0 < iListSize ) THEN 
    DO iListPos = 1 TO iListSize:
        DEFINE VARIABLE cProp AS CHARACTER NO-UNDO.
        DEFINE VARIABLE cVal  AS CHARACTER NO-UNDO.

        MESSAGE "    properties:".
        cProp = ENTRY(iListPos, cList, ",").
        cVal = hCP:GET-PROPERTY(cProp).
        MESSAGE "          property:" cProp ", value:" cVal.
    END.
    
    // Set the Client-Principal as client to connect with DB.
    SET-DB-CLIENT(hCP).
    
END.

Restartujemy teraz PASOE i wywołujemy ekran logowania: http://localhost:[port]/rest/_oepingService/_oeping
Podajemy dane: user1, user1. Dane są uwierzytelnione, ale najciekawsze informacje znajdujemy w logu agenta ldapas.agent.[data].log
Po pierwsze mamy wpis o prawidłowym wczytaniu domen z pliku cpStartup.p

Po drugie możemy odczytać parametry tokena CLIENT-PRINCIPAL wygenerowane przez plik cpActivate.p.

Widać, że pełne id użytkownika zostało uzupełnione o nazwę domeny a także, że użytkownik dostał nową rolę.
To chyba jest najbardziej zaawansowany z artykułów, które do tej pory Wam przedstawiłem. Jeśli ktoś przećwiczy cały proces – gratuluję!
W razie pytań piszcie, jak zwykle, na PUG Poland.