Bezpieczeństwo pracy silnika Docker Engine w dużej mierze zależy od wprowadzonej przez nas konfiguracji. Ta domyślna nie zawiera w sobie zbyt wielu włączonych mechanizmów bezpieczeństwa. Można wręcz powiedzieć, że jedynym z nich jest możliwość konfiguracji usługi za pomocą wiersza poleceń z konsoli. Interfejs API nasłuchujący na interfejsach sieciowych serwera jest domyślnie wyłączony. Nieco więcej mechanizmów bezpieczeństwa mamy wbudowanych w Docker Swarm. Służą one zapewnieniu bezpiecznej komunikacji pomiędzy węzłami tworzącymi klaster oraz przechowywania konfiguracji i logów samego klastra. Jeżeli planujemy używać interfejsu API musimy samodzielnie popracować nad wdrożeniem odpowiednich zabezpieczeń.

Przede wszystkim jednak pamiętajmy, że bezpieczeństwo samego Dockera, a bezpieczeństwo aplikacji uruchomionej w kontenerach to nieco inne rzeczy. Fakt, że aplikacja udostępnia swój interfejs za pomocą szyfrowanego połączenia w standardzie TLS nie ma nic wspólnego z mechanizmami bezpieczeństwa, wdrożonymi lub nie, do ochrony samego silnika Docker Engine oraz jego interfejsu API. Są to dwa rozdzielne zagadnienia, którymi zazwyczaj zajmują się oddzielne grupy inżynierów – zespoły aplikacyjne i programiści odpowiedzialni są za bezpieczeństwo aplikacji uruchomionej w kontenerach i komunikacji pomiędzy kontenerami a światem zewnętrznym, zaś administratorzy infrastruktury odpowiadają za bezpieczeństwo pracy i komunikacji samego silnika konteneryzacji.

Zabezpieczenie API Docker Engine

Jak już wspomniałem na wstępie głównym zabezpieczeniem w Docker Engine jest fakt, że komunikacja z demonem odbywa się za pomocą gniazd systemowych w systemie Unix. Gniazda są lokalne, zatem jedynie osoby, które uzyskają dostęp do konsoli serwera wraz z odpowiednimi uprawnieniami mogą komunikować się z demonem usługi. Aktywacja interfejsu API jest świadomą czynnością administratora. Domyślnie odbywa się ona w sposób nieszyfrowany. Aby zapewnić bezpieczeństwo tego kanału, musimy wdrożyć mechanizmy oparte o standard TLS i infrastrukturę klucza publicznego, jak w typowym web serwerze. Korzystać możemy tutaj z certyfikatów podpisanych przez własne CA, jak i publicznych dostawców. Generując Certificate Signing Request (CSR) pamiętajmy, że połączenie z usługą Docker Engine może nastąpić zarówno przy użyciu adresu IP hosta, jak i nazwy domenowej (FQDN). Dlatego CSR musi zawierać identyfikator subjectAltName, w którym zapiszemy wszystkie adresy IP i powiązane z nimi nazwy rekordów DNS interfejsów, na których usługa Dockera będzie nasłuchiwać.

Aby korzystać z Docker Engine z TLS musimy zmodyfikować sposób uruchamiania usługi. Jako parametry przekazujemy dodatkowo informacje o wygenerowanym kluczu szyfrującym, powiązanym z nim podpisanym certyfikatem oraz certyfikat CA wystawcy podpisującego wygenerowany przez nas certyfikat. Usługa Docker Engine z TLS powinna nasłuchiwać na porcie 2376. Możemy samodzielnie wskazać adresy IP serwera, na których serwis ma oczekiwać połączeń lub jednorazowo wybrać wszystkie interfejsy korzystając z notacji 0.0.0.0.

dockerd –tlsverify –tlscacert=ca.pem –tlscert=server-cert.pem —

tlskey=server-key.pem -H=0.0.0.0:2376

Gdy nawiązywane jest połączenie następuje uwierzytelnienie, za pomocą certyfikatów zarówno klienta przez serwer, jak i serwera przez klienta. W zależności od wymaganego poziomu bezpieczeństwa odmiennie skonfigurujemy poziomy tej wzajemnej autentykacji. Musimy tylko odpowiednio operować parametrami przekazywanymi do wywołania każdego z nich. Użycie flagi –tlsverify wymusza weryfikację podpisanego certyfikatu przy użyciu zaufanego CA. Certyfikat serwera CA musi być dodany do zaufanych certyfikatów systemu operacyjnego. Zazwyczaj są to certyfikaty światowych wystawców. Jeżeli nie chcemy weryfikować podpisu wystawionego certyfikatu, to zamiast parametru –tlsverify powinniśmy zastosować parametr –tls. W takiej sytuacji skonfigurowane certyfikaty będą wykorzystane bez sprawdzenia ich podpisu.

Rola certyfikatów w klastrze Docker Swarm

Certyfikaty znajdziemy też wewnątrz klastra Docker Swarm.  Gdy powołujemy do życia nowy klaster, automatycznie na pierwszym z węzłów zarządzających tworzona jest cała infrastruktura klucza publicznego, za pomocą której zabezpieczona jest wewnętrzna komunikacja pomiędzy węzłami klastra. Generowany jest nowy certyfikat Root CA oraz klucze. Możemy za pomocą parametru –external-ca spowodować, że klaster będzie używał naszego własnego, już utworzonego certyfikatu Root CA. Korzystając z tak utworzonej infrastruktury klucza publicznego (PKI) za każdym razem, gdy do klastra zostanie podłączony nowy węzeł, menadżer będzie automatycznie wystawiał dla niego odpowiedni certyfikat.

Oprócz samych certyfikatów, w momencie powoływania klastra, tworzone są dwa tokeny: worker token oraz manager token. Każdy z nich zawiera skrót głównego certyfikatu oraz losowo wygenerowany klucz. Kiedy chcemy dołączyć do klastra nowy węzeł to funkcja skrótu używana jest, aby zweryfikować certyfikat Root CA klastra. Klucz zaś używany jest przez managera do weryfikacji, czy nowy węzeł ma uprawnienia do podłączenia się do wskazanego klastra.

W przypadku, gdyby stwierdzono naruszenie integralności infrastruktury klucza szyfrującego oraz wygenerowanych certyfikatów lub zauważono naruszenie bezpieczeństwa węzłów zarządzających to administrator może w prosty sposób dokonać rotacji kluczy i certyfikatów. Za pomocą polecenia docker swarm ca –rotate wymusi on ponowne wygenerowanie certyfikatów i kluczy, które automatycznie zostaną rozdystrybuowanie do wszystkich węzłów. Następnie poprzednie certyfikaty i klucze są unieważniane. Cała operacja odbywa się w tle i nie powoduje przerwy w działaniu klastra.