Uwaga:
Oryginalna wersja tego artykułu została opublikowana w IBM developerWorks i
jest własnością Westtech Information Services. Poniższy dokument jest
poprawioną przez zespół GDP wersją oryginalnego tekstu i nie jest już
aktualizowany.
|
Zarządzanie kluczem OpenSSH, część trzecia
1.
Poprawki w ssh-agent i keychain
Wielu z nas używa OpenSSH jako bezpiecznej, szyfrowanej alternatywy dla
sędziwych już narzędzi telnet i rsh. Jedną z ciekawszych opcji OpenSSH jest
jego zdolność do autoryzacji użytkownika używającego protokołów RSA i DSA,
które są bazowane na parach depełniających się "kluczy" numerycznych. Jednym z
głównych uroków autoryzacji RSA i DSA jest zdolność do ustanawiania połączenia
ze zdalnymi systemami bez dostarczania hasła. Aby lepiej to zrozumieć, należy
zajrzeć do poprzednich części tej serii, które stanowią odpowiednio uwierzytelnianie RSA/DSA
(część pierwsza) oraz ssh-agent i keychain (część
druga).
Od września 2001, kiedy to druga część została opublikowana na stronach
developerWorks, a następnie wzmiankowana na Slashdot i Freshmeat (w dziale Źródła są odnośniki do tych artykułów) wielu
użytkowników zaczęło używać keychain, a on sam uległ wielu zmianom. Dostałem
około dwudziestu wysokiej jakości łatek od deweloperów z całego świata. Wiele z
tych łatek włączyłem do źródeł keychaina, który jest dostępny obecnie w wersji
1.8 (spójrz do działu Źródła). Wysłałem wiele
szczerych podziękowań do wszystkich, którzy przysłali łatki, zgłosili błędy,
żądane opcje oraz notki z uznaniem.
Zaostrzenie bezpieczeństwa ssh
W moim ostatnim
artykule, poświęciłem sporo czasu na duskusję o wadach i zaletach
wynikających z używania ssh-agent. Kilka dni później drugi artykuł pojawił się
na developerWorks, a ja otrzymałem e-mail od Charlesa Kerneya z Sarnoff
Corporation, w którym autor grzecznie poinformował mnie o nowych możliwościach
przykazywania autoryzacji agenta OpenSSH. W uzupełnieniu Charles podkreślił, że
używanie ssh-agent na niezaufanych maszynach jest odrobinę niebezpieczne: jeśli
ktoś zdoła zdobyć dostęp superużytkownika, wtedy rozszyfrowany klucz może
zostać "wyciągnięty" z ssh-agent. Nawet jeśli wyciągnięcie kluczy będzie w
pewien sposób trudne, jest to w obrębie umiejętności profesjonalnych
włamywaczy. A kradzież klucza prywatnego powinna zmotywować nas do
przedsięwzięcia kroków w celu zabezpieczenia się przed podobnymi zdarzeniami.
Aby określić strategię ochrony naszych prywatnych kluczy, przede wszystkim
musimy zaklasyfikować maszyny, do których mamy dostęp, do jednej z dwóch
kategorii. Jeśli dany host jest dobrze zabezpieczony, powinien zostać uważany
za zaufany. Jeśli maszyna jest używana przez wielu użytkowników albo mamy
jakiekolwiek inne wątpliwości dotyczące jej zabezpieczeń, wtedy powinna zostać
uważana jako niezaufana. Chroniąc nasze klucze prywatne, nigdy nie powinniśmy
uruchamiać ssh-agent oraz keychain na niezaufanych maszynach.
Jednakże tworzy to pewne problemy. Jeśli nie można uruchomić ssh-agent na
niezaufanych hostach, jak założyć bezpieczne, pozbawione hasła połączenie ssh z
tych systemów? Odpowiedź jest tylko jedna: użyć ssh-agent i keychain na
zaufanych hostach lub wykorzystać nowe możliwości uwierzytelniania OpenSSH,
rozszerzając pozbawione hasła uwierzytelnianie do tych niezaufanych.
Uwierzytelnianie działa dzięki zezwoleniu zdalnej sesji ssh do kontaktu z
ssh-agent na zaufanych systemach.
Agent przesyłający uwierzytelnianie
Aby zrozumieć ideę działania przesyłu uwierzytelniania, spójrzmy na hipotetyczną
sytuację, w której użytkownik drobbins posiada zaufany laptop zwany lappy,
zaufany serwer zwany trustbox oraz dwa inne, niezaufane systemy, do których
musi mieć dostęp, zwane odpowiednio nottrust1 i nottrust2. Obecnie używa on
ssh-agent wraz z keychain na każdej z czterech maszyn, jak jest to pokazane
poniżej:
Ilustracja 1.1: ssh-agent używany na zaufanych i niezaufanych maszynach |
 |
Problem pojawia się w momencie, w którym ktoś zyska dostęp superużytkownika na
nottrust1 lub nottrust2. Wtedy osoba ta ma możliwość wyciągnięcia kluczy z
procesów ssh-agent. Aby naprawić to, drobbins zatrzymuje ssh-agent i keychain
na nottrust1 i nottrust2. Aby być jeszcze bardziej ostrożnym, drobbins decyduje
się używać tych programów jedynie na laptopie. Limituje to ujawnienie jego
rozszyfrowanych kluczy, chroniąc go przed ich kradzieżą.
Ilustracja 1.2: ssh-agent uruchomiony jedynie na laptopie; bardziej bezpieczna konfiguracja |
 |
Oczywiście problem w tym, że drobbins może teraz ustanawiać pozbawione hasła
połączenia jedynie z laptopa. Spójrzmy jak umożliwić przesyłanie
uwierzytelniania i obejść ten problem.
Zakładając, że wszystkie boxy mają uruchomione najświeższe wersje OpenSSH, nie
możemy użyć przekazania uwierzytelniania. Pozwala ono zdalnemu procesowi ssh na
kontakt z ssh-agent, który jest uruchomiony na lokalnym, zaufanym hoscie --
raczej jak wymaganie wersji ssh-agent'a, który jest uruchomiony na tej samej
maszynie, z której wychodzi połączenie. Zazwyczaj pozwala to na uruchomienie
naszego zespołu (ssh-agent i keychain) na zwykłym hoscie i znaczy, że wszystkie
połączenia ssh, które pochodzą (bezpośrednio lub pośrednio) z tej maszyny, będą
korzystały z lokalnego ssh-agent.
Aby umożliwić przesyłanie uwierzytelniania, dodamy poniższą linię do pliku
/etc/ssh/ssh_config, zarówno w lappy jak i trustbox. Zauważ, że
jest to plik konfiguracyjny ssh (ssh_config), nie demona sshd
(sshd_config):
Listing 1.1: Dodajemy poniższą linię do /etc/ssh/ssh_config |
ForwardAgent Yes
|
Teraz, drobbins spokojnie może połączyć się ze swojego laptopa do trustbox, a
wtedy do nottrust1, już bez wpisania hasła. Oba procesy ssh łączą się z
ssh-agent uruchomionym na lappy:
Listing 1.2: Tapping lappy |
$ ssh drobbins@trustbox
Last login: Wed Sep 26 13:42:08 2001 from lappy
Welcome to trustbox!
$ ssh drobbins@notrust1
Last login: Tue Sep 25 12:03:40 2001 from trustbox
Welcome to notrust1!
$
|
Jeśli wypróbujesz podobną konfigurację i zauważysz, że przekazywanie nie działa
jak należy, spróbuj ssh -A zamiast ssh, aby wyraźnie umożliwić
przekazanie autoryzacji. Poniżej jest diagram ukazujący drogę naszego logowania
się do trustbox oraz nottrust1 używając przekazywanie uwierzytelniania:
Ilustracja 1.3: Agent w akcji |
 |
Jak można słusznie zauważyć połączenie ssh do trustbox utrzymuje połączenie z
ssh-agent uruchomionym na lappy. Kiedy połączenie ssh zostało ustanowione z
trustbox do nottrust1, ssh utrzymało uwierzytelnione połączenie, skutecznie
rozszerzając łańcuch (chain). To czy łańcuch autoryzacji może zostać
rozszerzony poza nottrust1 zależy od tego, jak /etc/ssh/ssh_config
jest skonfigurowany. Tak długo, jak ssh-agent jest uruchomiony na zaufanym
lappy, każda część łańcucha będzie zdolna do uwierzytelnienia.
Korzyści z połączeń przekierowanych
Przekierowanie uwierzytelniania oferuje wiele korzyści nie omawianych w tym
artykule. Aby przekonać mnie do ważności agenta przekierowania, Charles Karney
podzielił się ze mną informacją o tych trzech korzyściach:
-
Klucz prywatny jest przechowywany jedynie na zaufanej maszynie. Chroni to
przed złośliwymi użytkownikami, którzy zbierają zaszyfrowane klucze, a
następnie próbują złamać szyfr.
-
Ssh-agent jest uruchomiony na zaufanym systemie. To z kolei chroni przed
intruzami, którzy robią zrzut pamięci procesu agenta i wyciągają z niego
odszyfrowany klucz prywatny.
-
Odkąd jedynym wymogiem jest wpisanie hasła na zaufanym hoście, można
wykluczyć logowanie naciśnięć klawiszy i uzyskania tego hasła przez osoby
niepowołane.
Jedynym minusem tego rozwiązania jest to, że nie rozwiązuje ono
problemu z wykonaniem prac cron'a. Jedynym wyjściem wtedy jest wymuszenie od
wszystkich prac uwierzytelnienia. Jeśli to konieczne, prace te mogą użyć ssh aby
na zdalnym systemie stworzyć kopie zapasowe, zsynchronizować wszystkie pliki i
tym podobne.
Teraz, skoro poznaliśmy już agenta przesyłającego uwierzytelnienie połączenia,
wróćmy do ostatnich unowocześnień w samym skrypcie keychain.
Unowocześnienia skryptu keychain i jego funkcjonalność
Dzięki łatkom nadesłanym przez użytkowników, zostało wprowadzonych wiele
znaczących zmian. Kilka łatek znalazło powiązania z funkcjami. Dla przykładu:
przypomnijmy sobie, że keychain tworzył plik ~/.ssh-agent;
nazwa tego pliku została zmieniona na ~/.ssh-agent-[hostname],
przez co keychain działa z NFS, zamontowanym w katalogach domowych, dzięki czemu
dostęp do niego możliwy jest z wielu hostów. W dodatku, plik
~/.ssh-agent-[hostname] jest teraz plikiem
~/.ssh-agent-csh-[hostname], przez co może zostać wykorzystany
przez kompatybilne z csh powłoki. Została również dodana nowa opcja
--nocolor, dzięki czemu w razie potrzeby kolorowanie może zostać
wyłączone
Poprawa kompatybilności z powłokami
Podczas gdy funkcjonalność unowocześnień została poprawiona, głównym
problemem stał się brak współpracy z powłokami. Wiemy, że keychain w
wersji 1.0 wymagał powłoki bash. Jego późniejsze wersje zostały zmienione
tak, aby były kompatybilne z wszystkimi rodzajami powłok. Ta zmiana
zezwoliła keychain pracować "poza maszyną" na każdym systemie Uniksowym,
włączając Linux, BSD, Solaris, IRIX oraz AIX. Przejście na sh i generalnie
współpraca z UNIX była nie lada wyzwaniem i olbrzymim doświadczeniem.
Stworzenie zwykłego skryptu, który może zostać
wykonany na każdej z tych platworm było dosyć skomplikowane. Przede wszystkim
dlatego, że nie mam dostępu do tych wszystkich systemów. Na szczęście jednak,
cała masa użytkowników na całym globie ma, a wielu z nich udzieliło wspaniałego
wsparcia w rozwiązywaniu problemów.
Były dwa rodzaje probemów kompatybilności, które musiały zostać naprawione.
Przede wszystkim, musiałem być pewny, że keychain używa jedynie wbudowanych w
siebie wyrażeń i operatorów, które są w pełni wspierane przez wszystkie powłoki,
włączając popularne darmowe i komercjalne UNIX sh, zsh oraz bash w wersjach 1 i
2. Tu jest kilka wprowadzonych przez użytkowników poprawek, które włączyliśmy do
kodu keychain:
W starszych wersjach powłok nie można użyć ~ w odniesieniu do katalogu domowego.
Dlatego zamiast tego został wprowadzony $HOME:
Listing 1.3: Tworzenie $HOME |
hostname=`uname -n`
pidf=${HOME}/.ssh-agent-${hostname}
cshpidf=${HOME}/.ssh-agent-csh-${hostname}
|
Następnie wszystkie wzmianki o source zostały zmienione na . aby mieć pewność,
że będzie współpracował z /bin/sh w NetBSD:
Listing 1.4: Współpraca z NetBSD |
if [ -f $pidf ]
then
. $pidf
else
SSH_AGENT_PID="NULL"
fi
|
Z czasem zastosowałem również kilka innych poprawek. Jeden bystry autor
skryptów poinformował mnie, że zamiast tworzenia plików poprzez "touch", można
go stworzyć wpisując samą jego nazwę:
Listing 1.5: Tworzenie plików |
> foo
|
Polegając na składni powłoki bardziej niż na postaci binarnej, unikamy funkcji
fork(), więc skrypty stają się odrobinę bardziej wydajne. Polecenie > foo
powinno działać z każdą powłoką. Wygląda jednak na to, że nie jest wspierane
przez ash. Nie powinno to jednak stanowić problemu dla większości użytkowników
odkąd ash jest używany bardziej w sytuacjach awaryjnych jak w codziennej pracy.
Problemy wykonywalności platform
Stworzenie skryptu działającego na różnorakich, unikso-podobnych systemach,
wymaga więcej niż jedynie zastosowanie ubogiej składni sh. Należy pamiętać, że
większość skryptów wywołuje zewnętrzne polecenia, takie jak grep, awk, ps i
inne. Komendy te muszą być wywołane w zgodny z wszelkimi standardami sposób. Dla
przykładu, polecenie echo w większości systemów z rodziny UNIX rozpoznaje opcję
-e, podczas gdy Solaris jej nie rozpoznaje -- drukuje ją tak, jak zwykły
tekst. Zatem, aby keychain współpracował w tym wypadku z Solaris'em, potrzeba:
Listing 1.6: Przechytrzanie Solaris'a |
if [ -z "`echo -e`" ]
then
E="-e"
fi
|
Według powyższego skryptu, zmienna E jest równa -e, jeśli opcja -e jest
wspierana. Wtedy polecenie echo może być wywołane w następujący sposób:
Listing 1.7: Lepsze echo |
echo $E Usage: ${CYAN}${0}${OFF} [ ${GREEN}options${OFF} ] ${CYAN}sshkey${OFF} ...
|
Używając echo $E zamiast echo -e, jest możliwe wyłączenie opcji
-e w razie potrzeby.
pidof, ps
Prawdopodobnie najbardziej znaczącą poprawką w kompatybilności keychain była
zmiana, dzięki której program wykrywa proces uruchomionego ssh-agent. Wcześniej
używałem do tego celu komendy pidof, którą musiałem zastąpić, ponieważ na wielu
systemach nie jest dostępna. Szczerze mówiąc, pidof nie jest najlepszym
roziązaniem, ponieważ wyświetla wszystkie uruchomione w systemie procesy
ssh-agent, podczas gdy my jesteśmy zainteresowani procesami przypisanymi do
danego użytkownika.
Zatem zamiast posługiwać się komendą pidof, przefiltrowaliśmy wyjście polecenia
ps, przez grep i awk. Była to jedna z opcji zgłoszonych przez użytkowników:
Listing 1.8: Potok lepszy niż pidof |
mypids=`ps uxw | grep ssh-agent | grep -v grep | awk '{print $2}'`
|
Powyższy potok przyrówna zmienną mypids do wartości każdego procesu ssh-agent,
którego właścicielem jest dany użytkownik. Komenda grep -v grep jest użyta, by
proces naszego potoku nie stał się częścią listy PID.
Metoda ta jest dobra w zamyśle. W praktyce nie wygląda to dokładnie w ten
sposób. Dla przykładu: polecenie ps uxw działa na systemie Linux, ale już nie
działa na systemie IRIX, a polecenie ps -u username -f działa pod
Linuksem, IRIX-em i Solaris'em, lecz nie pod BSD. Aby obejść tego typu problem,
keychain, zanim wykona potok, sam wykrywa czy systemowe ps działa ze składnią
BSD lub SysV:
Listing 1.9: Wykrycie BSD lub System V |
psopts="FAIL"
ps uxw >/dev/null 2>&1
if [ $? -eq 0 ]
then
psopts="uxw"
else
ps -u `whoami` -f >/dev/null 2>&1
if [ $? -eq 0 ]
then
psopts="-u `whoami` -f"
fi
fi
if [ "$psopts" = "FAIL" ]
then
echo $0: unable to use \"ps\" to scan for ssh-agent processes.
Report KeyChain version and echo system configuration to drobbins@gentoo.org.
exit 1
fi
mypids=`ps $psopts 2>/dev/null | grep "[s]sh-agent" | awk '{print $2}'` > /dev/null 2>&1
|
Aby upewnić się, że pracujemy z należącą do BSD lub System V składnią polecenia
ps, skrypt wykonuje "przebieg próbny": sprawdza dane wyjściowe ps uxw. Jeśli kod
błędu jest równy zero, wiemy, że ps uxw działa, a my możemy odpowiednio ustawić
opcje komendy ps. Natomiast, jeśli ps uxw zwróci kod błędu różny od zera
(wskazując na pracę z opcjami rodem BSD), wykonujemy następny przebieg próbny
poleceniem ps -u `whoami` -f; kolejny raz uzyskujemy dane wyjściowe. W
tym momencie znaleźliśmy jeden z dwóch wariantów opcji dla ps. Jeśli staje się
inaczej, widzimy błąd, a praca zostaje zakończona. Jednak jest wysoce
prawdopodobne, że jedna z dwóch komend zadziała, a zatem wykonamy powyższy
fragment kodu -- potok ps.
Potok zawiera również "perełkę", którą uprzejmie nadesłał do mnie Hans Peter
Verne. Należy zauważyć, że człon grep -v grep nie jest już częścią
potoku; został on usunięty, a grep "ssh-agent" został nadpisany przez
grep "[s]sh-agent". Ta prosta komenda kończy swoją pracę w tym samym
punkcie,
co grep ssh-agent | grep -v grep.
Listing 1.10: Doskonała sztuczka z grep |
mypids=`ps $psopts 2>/dev/null | grep "[s]sh-agent" | awk '{print $2}'` > /dev/null 2>&1
|
Nie prawda, że zdumiewające? Jeśli stwierdzasz, że grep "ssh-agent"
oraz grep "[s]sh-agent" zwrócą identyczne dane wyjściowe, masz rację.
Ale dlaczego generują różne wyniki? Oto jak to działa: używając grep
"[s]sh-agent" zmienia się wynik polecenia grep w liście procesów wyświetlonych
po ps. Ponieważ ciąg "[s]sh-agent" nie jest równoznaczny z wyrażeniem regularnym
"[s]sh-agent", grep nie zwraca własnego procesu. Jeśli wciąż nie potrafisz
zrozumieć zasady działania, pobaw się jakiś czas programem grep. Jestem pewien,
że do tego dojdziesz szybko.
Wniosek
Ta kolumna kończy moje sprawozdanie o OpenSSH. Dowiedziałeś się sporo, więc
możesz zacząć używać OpenSSH do zabezpieczenia swojego systemu. W następnych
miesiącach, podobny wątek zostanie kontunuowany serią "Przewodnik po
zaawansowanych narzędziach systemów plików"
2.
Źródła
|