Nie potrzebujesz pliku tymczasowego, aby wygenerować klucz OpenVPN
Trzymanie materiału klucza w pamięci unika cyklu życia pliku, a nie wycieku. Oddziel strumień wyjściowy od strumienia błędów i traktuj klucz jako sekret.
Generowanie klucza nie potrzebuje pliku tymczasowego, a jego pominięcie naprawia mniej, niż sugeruje większość poradników. Usuwa cykl życia pliku i wyścig o sprzątanie wokół niego. Pozostawia nietkniętą każdą okazję do wycieku sekretu później. Problem poprawności w tych skryptach zwykle leży gdzie indziej, bo diagnostyka i materiał klucza podróżują różnymi strumieniami.
Plik nigdy nie był tym ujawnieniem, o którym myślałeś
Plik tymczasowy jest konkretnym ryzykiem, gdy jest czytelny dla wszystkich, gdy przetrwa awarię albo gdy po wyjątku pominięty zostanie krok sprzątania. To realne i warto to naprawić. Są też lokalne, widoczne i łatwe do przetestowania. Nazwij każdy z nich we własnym skrypcie, zanim uznasz, że zmiana była kosmetyczna.
Cichsze ryzyka siedzą niżej: klucz wypisany podczas debugowania, wyjątek serializujący cały obiekt wyniku, procedura obsługi awarii zapisująca zrzut pamięci, agregator logów odbierający ścieżkę pliku, a potem zawartość. Unikanie pliku tymczasowego nic nie robi dla żadnego z nich, a wiara w coś przeciwnego jest tym, jak dobrze zamierzony refaktor pozostawia sekret bardziej wystawionym niż wcześniej. Traktuj tę zmianę jako jedną poprawkę w szeregu, a nie jako rozwiązanie.
Co zapisuje klient, gdy nie podano nazwy pliku
Obecne wersje klienta wysyłają wygenerowany klucz na standardowe wyjście, gdy polecenie klucza zostanie podane bez ścieżki wyjściowej. To właśnie to udokumentowane zachowanie czyni podejście w pamięci wykonalnym i usuwa potrzebę starszego triku wskazywania narzędziu ścieżki urządzenia.
import subprocess
proc = subprocess.run(
["openvpn", "--genkey", "tls-crypt"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
key_material = proc.stdout
Przekaż polecenie jako listę. Nie trzeba tu rozwinięcia powłoki, a włączenie powłoki uzależniłoby granicę argumentów od cudzysłowów, których nie kontrolujesz. Użyj zamierzonego pliku wykonywalnego przez ścieżkę absolutną, jeśli skrypt może działać z inną ścieżką wyszukiwania niż oczekujesz.
Sprawdzenie kodu powrotu nie pozwala ścieżce sukcesu kontynuować po nieudanym wywołaniu. Bez niego pusty łańcuch bajtów może płynąć dalej i zawieść dopiero znacznie później, w punkcie, w którym klucz jest używany, a komunikat błędu w ogóle nie wspomina już o generowaniu.
Dwa strumienie, dwa znaczenia
Standardowe wyjście niesie klucz. Standardowy błąd niesie wszystko, co narzędzie chce ci powiedzieć, w tym ostrzeżenia o konfiguracji, wersji i typie klucza. Przekierowanie obu do jednego bufora to błąd, który daje wiarygodnie wyglądający klucz z wszytym w środek zdaniem.
Zachowaj też osobną ścieżkę błędu. Gdy wywołanie zawiedzie, zgłoś polecenie, które zawiodło, i status wyjścia, a potem świadomie zdecyduj, czy któraś część standardowego błędu jest bezpieczna do pokazania. Zrzucenie całego obiektu wyniku do logu to zwykły skrót i może ponieść ze sobą klucz.
Bezpieczny dla danych binarnych odczyt ma znaczenie, jeśli później liczysz skrót materiału lub porównujesz go. Tryb tekstowy z nieoczekiwanym kodowaniem może zmienić bajty, które nigdy nie miały być tekstem, a zmienionego klucza nie odróżnisz od błędnego, dopóki nie zawiedzie uzgadnianie.
Jeśli i tak go przechowujesz, zrób to świadomie
Większość wdrożeń w końcu potrzebuje klucza na dysku, na bramie albo w magazynie sekretów. Zapisz go raz, z restrykcyjnymi uprawnieniami od utworzenia, a nie późniejszym chmod, i uczyń zapis atomowym, aby częściowy plik nigdy nie stał się tym, który usługa czyta przy starcie. Zmiana nazwy ze ścieżki w tym samym katalogu zwykle temu wystarcza. Usuń ścieżkę przygotowania w gałęzi błędu, bo nieudany zapis to dokładnie ta kopia, o której ludzie zapominają.
Ustaw tryb pliku przy jego tworzeniu i utrzymuj też restrykcyjny tryb katalogu. Unikaj każdej ścieżki pod lokalizacją, którą zadanie kopii zapasowej zamiata bez zastanowienia, bo sekret w nocnej kopii zapasowej to sekret w każdej kopii tej kopii. To samo rozumowanie dotyczy współdzielonego wolumenu, który mogą czytać inne usługi.
Wspólny klucz nie jest tożsamością
Generowanie tego materiału nie wydaje certyfikatu, nie rejestruje klienta i nie ustala, kto się łączy. Wspólny klucz chroni kanał między stronami, które już sobie ufają; tożsamość nadal pochodzi z certyfikatu i dowolnego uwierzytelnienia stojącego przed nim.
Typy kluczy również nie są wymienne. Klucz symetryczny do jednej funkcji nie jest gotowym zamiennikiem dla innej, a nowsze wydania dodają własne warianty z różnymi plikami na każdym końcu. Generuj typ, który twoja konfiguracja naprawdę nazywa, a potem użyj go w roli, którą ten typ definiuje.
Przetestuj wynik prawdziwym połączeniem, a nie oglądaniem rozmiaru pliku. Jeśli uzgadnianie się kończy, a logi nie pokazują ostrzeżeń o kluczu, ścieżka generowania jest poprawna; gdyby klucz został obcięty przez połączony strumień, połączenie zawiodłoby w sposób wskazujący na drugą stronę, a nie na skrypt.