Wybierz region
pl
  • PL
  • EN
Wydrukuj

Shell Scripting w przykładach: jak w szybki sposób wygenerować certyfikaty SSL dla serwerów

Administrator to człowiek szanujący swój czas i z reguły pragmatyczny. Mam tutaj na myśli definicję pragmatyzmu odnoszącą się do tego, że podejmowane działania mają przynosić korzyść. Właśnie po to wymyślono skryptowanie – już nie jest ważne w czym, chociaż my w tym cyklu jak sama nazwa wskazuje rozmawiamy o skryptach powłoki.

Efekt ma być taki, że jeżeli coś jest powtarzalne i do tego żeby to zrobić trzeba się naklepać (co zajmuje cenny czas, a jeszcze przy korzystaniu z klawiatury potencjalnie można gdzieś się pomylić), należy to oskryptować.

Jak odgrażałem się w artykule: „#AdminCases czyli gotowa solucja: jak stworzyć certyfikaty SSL dla serwera bez udziału komercyjnego CA, aby przeglądarki uważały, że jestem zaufany”, to co zostało tam pokazane jako żmudne wprowadzanie poleceń poprzez tłuczenie w klawisze, można  zawrzeć w skrypcie Shell’owym. Nie dość, że napisanie takowego będzie banalnie proste, to jeszcze wprowadzając dane, niezbędne do tego co jest tematem naszego przykładu, nie popełnimy błędu, a o ten zawsze jest łatwo.

Zatem do dzieła.

W tym przypadku będę tworzył skrypt na jednym z Linuxów z rodziny Ubuntu, ale to naprawdę nie ma znaczenia, grunt, że skorzystam z powłoki Bash zatem shebang line będzie wyglądała w ten sposób:

#!/bin/bash
Teraz musimy się zastanowić nad tym, jakie dane będą nam niezbędne na wejściu, no i druga strona medalu, czyli co może zostać zapisane jako atrybuty stałe. We wspomnianym artykule z cyklu #AdminCases wpisywaliśmy wszystko „z palca”, a tutaj chcemy to zautomatyzować wszystko co można uznać za stałe dane, które powinny rzeczywiście takimi się stać.

Co potrzebujemy wiedzieć, co jest niezbędne do uruchomienia, no i co będziemy robić w dalszej części (oczywiście w telegraficznym skrócie):

- zakładamy, że mamy wygenerowany nasz CA, ponieważ do podpisania będziemy potrzebowali klucza prywatnego oraz wygenerowanego certyfikatu,

- potrzebujemy klucza serwera oraz żądania podpisu certyfikatu,

- w przypadku generowania żądania wymagane było wypełnienie kilku pól zatem niezbędne jest wiedzieć jakimi danymi owe pola wypełnić,

- do żądania również niezbędny jest plik z dodatkowymi ustawieniami gdzie potrzebujemy nazwy i ip serwera.

Dane, które muszą znaleźć się na wejściu skryptu to w naszym przypadku nazwa oraz adres ip serwera, a to dlatego, że serwerów w projekcie możemy mieć multum, a ten skrypt ma dla każdego dla którego sobie życzymy wygenerować certyfikat SSL i go podpisać własnym CA. Stałymi danymi – zatem tymi, które umieścimy w treści programu - będą pola o których wypełnienia żąda polecenie openssl po jego uruchomieniu.

Zastanówmy się jak byłoby najlogiczniej wprowadzić zmienne zawierające nazwę serwera oraz jego ip adres. Chciałbym, aby skrypt był uniwersalny, co w tym przypadku rozumiem jako opcję  podania na wejściu 2 zmiennym w postaci: komenda zmienna_1 zmienna _2, a jeżeli tego nie uczynimy to aby skrypt wymusił na nas wprowadzenie tychże danych. Do tego będziemy potrzebowali warunku, który sprawdzi czy na wejściu mamy 2 atrybuty. Skorzystamy więc ze zmiennej $#, która to wartość parametrów wejściowych przechowuje oraz z dobrodziejstwa polecenia test, który służy właśnie do porównywania w  Shell’u. Stara funkcjonalność np. wykorzystywana w Born lub Korn Shell to ta, która charakteryzuje się notacją z pojedynczym nawiasem kwadratowym, a owa funkcjonalność miała swoje ułomności, o których wspomnę w dalszej części wskazując kilka różnic w stosunku do nowej. My skorzystamy zapisu nowego z podwójnymi nawiasami kwadratowymi. Zatem nasz kod sprawdzający będzie wyglądał w ten sposób:

if [[ $# -lt 2 ]]

then

echo "Server name: "

read NAME

echo "Server ip: "

read IP

else

NAME=$1

IP=$2

fi

No i co się tutaj dzieje: za pomocą polecenia if sprawdzamy ilość atrybutów i jeżeli ta jest mniejsza od 2 (-lt) to wymuszamy wprowadzenie argumentów nazwy oraz ip serwera w innym przypadku (czyli użytkownik podał 2 atrybuty) przypisujemy wprowadzone dane do zmiennych.

Dalej będziemy już tworzyć wszystko to co było opisane przytaczanym przeze mnie artykule innymi słowy będziemy wykorzystywać polecenie openssl:

- tworzymy klucz serwera – tutaj skorzystamy z danych przypisanych do zmiennej $NAME

openssl genrsa -out "$NAME.key" 2048
- generujemy żądanie podpisania certyfikatu – i tutaj musimy się na chwilę zatrzymać, ponieważ w artykule wprowadzaliśmy wszystko z ręki, a w przypadku skryptu byłoby to bez sensu, bo wtedy po co nam skrypt skoro niczego by nie usprawniał tylko wywoływał po kolei polecenia, a my nadal wszystkie niezbędne dane musielibyśmy wprowadzać. Dlatego do wymienionego w artykule polecenia dodamy atrybut -subj gdzie wszystkie dane zostaną uzupełnione za pomocą jednego stringu, co  zapobiegnie wprowadzaniu ręcznemu.

openssl req -new -key "$NAME.key" -out "$NAME.csr" -subj "/C=PL/ST=Narnia/L=Pingwinowo/O=Nielot Company/OU=Garbage Department/CN=$NAME"
- tworzymy plik z dodatkowymi ustawieniami – zamiast tworzyć go ręcznie utworzymy zawartość dynamicznie wprowadzając niezbędne dane. Skorzystamy z funkcjonalności przekierowań, aby utworzyć plik o nazwie wskazanej przez zmienną $NAME z rozszerzeniem .ext i zawartością w postaci wielowierszowego stringu. W wersji z artykułu z cyklu #AdminCases w tym pliku z dodatkowymi ustawieniami serwera mieliśmy definicję zmiennej subjectAltName ze wskazaniem na @alt_names oraz definicje alt_names w postaci nazwy serwera oraz ip. Tutaj jeden z moich szacownych kolegów zwrócił uwagę, że dobrze byłoby ustawić dodatkowe atrybuty związane z certyfikacją zatem umieszczam takowe w postaci definicji authorityKeyIdentifier, basicConstraints oraz keyUsage. Można również użyć atrybutu  extendedKeyUsage rozszerzające informacje o przeznaczeniu klucza np. extendedKeyUsage = critical, serverAuth

cat >"$NAME.ext" <<-EOF

authorityKeyIdentifier=keyid,issuer

basicConstraints=CA:FALSE

keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment

subjectAltName = @alt_names

[alt_names]

DNS.1 = $NAME

IP.1 = $IP

EOF

- podpisujemy certyfikat używając własnego CA – jednak, aby to zrobić musimy mieć pewność, że klucz prywatny oraz certyfikat CA egzystują, w katalogu w którym się znajdujemy więc znowu musimy użyć instrukcji warunkowej. I tutaj możemy zobaczyć różnicę notacji, jaka istnieje w starej i nowej formie, czyli używając [ ] lub [[ ]].

W starej formie taki if wyglądałby następująco:

if [ -f test.pem -a -f testkey.pem ]
W nowej formule będzie wyglądał:

if [[ -f test.pem && -f testkey.pem ]]
Dodatkowo w nowej funkcjonalności nie ma problemów z pustymi stringami lub stringami zawierającymi spacje, na które stara opcja była uczulona i trzeba było taką zmienną umieszczać w cudzysłowie. Nowa funkcjonalność przyniosła również możliwość dopasowania zmiennej wzoru np.

if [[ $ANSWER = y* ]]
Jest jeszcze kilka innych ciekawych możliwości, więc zapraszam do pogłębiania wiedzy i poszukiwań tych przez Was nieodkrytych.

Wracając do tematu, to jeżeli istnieją sprawdzane pliki uruchamiamy polecenie openssl podpisujące certyfikat, jeżeli nie to wyrzucamy błąd z informacją o braku certyfikatów.

if [[ -f test.pem && -f testkey.pem ]]

then

openssl x509 -req -in $NAME.csr -CA test.pem -CAkey testkey.pem -CAcreateserial -out $NAME.crt -days 825 -sha256 -extfile $NAME.ext

else

echo "Brak certyfikatow CA"

exit 99

fi

No i szczęśliwie dotarliśmy do końca naszego skryptu. Kupa radości z przewagą radości. Pewnie chcecie zobaczyć go w całej okazałości (dla tych leniwych, którzy lubią kopiować, zamiast napisać) zatem proszę bardzo:

#!/bin/bash

if [[ $# -lt 2 ]]

then

echo "Server name: "

read NAME

echo "Server ip: "

read IP

else

NAME=$1

IP=$2

fi

openssl genrsa -out "$NAME.key" 2048

openssl req -new -key "$NAME.key" -out "$NAME.csr" -subj "/C=PL/ST=Narnia/L=Pingwinowo/O=Nielot Company/OU=Garbage Department/CN=$NAME"

cat >"$NAME.ext" <<-EOF

authorityKeyIdentifier=keyid,issuer

basicConstraints=CA:FALSE

keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment

subjectAltName = @alt_names

[alt_names]

DNS.1 = $NAME

IP.1 = $IP

EOF

if [[ -f test.pem && -f testkey.pem ]]

then

openssl x509 -req -in $NAME.csr -CA test.pem -CAkey testkey.pem -CAcreateserial -out $NAME.crt -days 825 -sha256 -extfile $NAME.ext

else

echo "Brak certyfikatow CA"

exit 99

fi

Pozostaje nam uruchomić nasz skrypcik (oczywiście, jeżeli nadaliście mu uprawnienia execute).

Jak już wspomniałem wcześniej są na to 2 sposoby:

- polecenie z atrybutem nazwa i ip – zacznie od razu generować certyfikaty

- polecenie bez atrybutów – pojawi się monit o padanie nazwy serwera, a następnie ip

Pierwszy sposób możemy wykorzystać chociażby do hurtowego wygenerowania certyfikatów na podstawie listy. Żeby nie mącić i nie przedłużać postaram się to zwizualizować w innym artykule może dodatkowo pokazując w jaki sposób zrobić obsługę błędów, która to zawsze jest jedną z najważniejszych zagadnień programowania.

Przystąpmy do uruchomienia według sposobu numer 2. Umieszczam tutaj to co pojawiło się na ekranie, czyli prośba o wprowadzenie danych, a gdy te zostaną one uzupełnione to powstaje plik z certyfikatem serwera.

[email protected]:~/test_cert$ ./create_cert_for_server

Server name:

server1

Server ip:

192.168.0.100

Generating RSA private key, 2048 bit long modulus (2 primes)

..........................................................................................................+++++

.............................................+++++

e is 65537 (0x010001)

Signature ok

subject=C = PL, ST = Narnia, L = Pingwinowo, O = Nielot Company, OU = Garbage Department, CN = server1

Getting CA Private Key

Po zakończeniu listing zawartości katalogu bieżącego powinien wyglądać mniej więcej tak:

[email protected]:~/test_cert$ ls -l

total 32

-rwxr-xr-x 1 aciek aciek  817 Feb 28 17:45 create_cert_for_server

-rw-rw-r-- 1 aciek aciek 1700 Feb 28 18:29 server1.crt

-rw-rw-r-- 1 aciek aciek 1029 Feb 28 18:29 server1.csr

-rw-rw-r-- 1 aciek aciek  218 Feb 28 18:29 server1.ext

-rw------- 1 aciek aciek 1679 Feb 28 18:29 server1.key

-rw------- 1 aciek aciek 3272 Feb 28 00:33 testkey.pem

-rw-rw-r-- 1 aciek aciek 2000 Feb 28 00:33 test.pem

-rw-rw-r-- 1 aciek aciek   41 Feb 28 18:29 test.srl 

Wystarczy teraz zaimplementować taki certyfikat na serwerze i możemy w najlepsze cieszyć się połączeniami SSL – oczywiście, aby ten nie monitował w przeglądarce braku zaufania muszą zostać spełnione warunki, które zostały opisane w „#AdminCases czyli gotowa solucja: jak stworzyć certyfikaty SSL dla serwera bez udziału komercyjnego CA, aby przeglądarki uważały, że jestem zaufany”.

Od teraz w kilka sekund możemy stworzyć sobie certyfikat dla dowolnego serwera, dla którego potrzebujemy takowego, wprowadzając tylko 2 atrybuty, więc prawdopodobieństwo błędu jest znacznie ograniczone. Proste prawda no i bez udziwnień co wpisuje się we wspomnianą w pierwszym artykule z cyklu Shell Scripting w przykładach regułę KISS, której polskim odpowiednikiem jest BUZI , a rozwinięcie tego akronimu znajdźcie sobie sami ;)

Do zobaczenia i niech Shell będzie z Wami ;)


Adam Paszkiewicz

Adam aka aciek, Ekspert ds. Technologii z Asseco Warszawa. Z technologiami związany od okresu dojrzewania (a ten był dawno temu) ale gotować i żeglować też potrafi. Rzeczy niemożliwe realizuje od ręki, na cuda trzeba chwilę zaczekać. Uważa, że w życiu jak i w pracy dobrze mieć kupę radości ... z przewagą radości.


Wydrukuj