W poprzednich częściach pokazaliśmy jak możemy w naszej prototypowanej aplikacji szybko zbudować pełny pipeline CI/CD. Następnie udało nam się przeprowadzić deployment na chmurach Heroku i Azure, oraz cały kod przedstawić w postaci Terraformowego skryptu o stanie współdzielonym w zewnętrznym magazynie. Moglibyśmy teraz ruszyć o krok dalej, pozwalając naszemu repozytorium nie tylko zarządzać kodem, ale też dbać o aplikowanie zmian w chmurze.
W celu użycia nie-personalnego konta i posiadania pewnej kontroli nad tworzoną aplikacją powinniśmy utworzyć dedykowaną jednostkę usługi (Service Principal). Na potrzeby artykułu możemy traktować ją jako rodzaj niespersonalizowanej tożsamości.
% az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/{SubscribtionID}"
Creating a role assignment under the scope of "/subscriptions/<SubscriptionID>"
Retrying role assignment creation: 1/36
{
"appId": "<UUID>",
"displayName": "azure-cli-2020-11-29-13-22-27",
"name": "http://azure-cli-2020-11-29-13-22-27",
"password": "<PASSWORD>",
"tenant": "<UUID>"
}
Przypisanie jednostce usługi roli „Contributor” pozwoli jej na zarządzanie dostępem do zasobów, a jednocześnie uniemożliwi zmianę w ustawieniach ról AzureRBAC i nadawanie nowych uprawnień.
Następnie możemy przygotować zarys Pipeline’u (.gitlab-ci.yml), którego ogólna idea pozwoli na przygotowanie i przetestowanie zmian w infrastrukturze naszego projektu.
image:
name: hashicorp/terraform:0.13.5
before_script:
- rm -rf .terraform
- terraform --version
- terraform init
stages:
- validate
- plan
- apply
validate:
stage: validate
script:
- terraform validate
plan:
stage: plan
script:
- terraform plan
dependencies:
- validate
apply:
stage: apply
script:
- terraform apply -input=false
only:
- master
dependencies:
- plan
Pierwsze etapy pozwolą na weryfikację i wywołanie planowania zmian, aplikacja nastąpi dopiero na pipeline master. Jako, że będziemy działali w ramach automatycznego zadania, musimy wyłączyć tryb interaktywny poprzez dodanie parametru (-input=false). Dodatkowo powinniśmy uzupełnić wszystkie zmienne środowiskowe wymagane przez aplikację.
Zatem począwszy od zmiennych aplikacyjnych TF_VARS_* możemy rozpocząć przygotowywanie naszych sekretów. Po raz kolejny chciałbym zwrócić uwagę, że wciąż nie zaadresowaliśmy wielu problemów związanych z bezpieczeństwem, a jedynie skupiamy na się na poprawie automatyzacji tworzenia infrastruktury.
Przy próbie wywołania zadania planowania nawet po ustawieniu zmiennych, możemy zobaczyć następujący błąd:
Error: Error building AzureRM Client: Azure CLI Authorization Profile was not found. Please ensure the Azure CLI is installed and then log-in with `az login`.
Nadszedł czas na wykorzystanie uprzednio przygotowanego Service Principal. Provider Azure wymaga pięciu zmiennych:
Zasadniczo wszystkie poza id naszej subskrybcji i ACCESS_KEY znanym z poprzedniego artykułu można pozyskać po utworzeniu roli. ARM_CLIENT_ID to tak naprawdę appId, a ARM_CLIENT_SECRET to otrzymane w odpowiedzi hasło.
Gdy zaaplikujemy wszystkie wspomniane zmiany – nasz Pipeline powinien się zazielenić 🙂
Podsumowanie
Czas chyba odpowiedzieć sobie na dwa pytania. Czy tak naszpikowany zmiennymi pipeline może być w ogóle zarządzalny? Czy to w ogóle bezpieczne?
Cóż – na pewno zmienne aplikacyjne nie powinny być ręcznie konfigurowane poprzez Gitlab’a i być tam przechowywanymi. Powinniśmy korzystać z bezpiecznego składowiska typu Vault i z niego na żądanie pobierać wartości.
Co do ogólnego bezpieczeństwa… Zadania na publicznym GitLab uruchamiane są w Dockerze i nie współdzielą stanu pomiędzy wywołaniami. Pozwalamy jednak zewnętrznemu dostawcy na trzymanie danych naszej jednostki usługi. W planie darmowym, niewiele jest sposobów na obejście tej wady, a więc musielibyśmy posłużyć się integracją z Vault. Wszystko zależy od rozmiaru projektu i naszej determinacji w celu zabezpieczenia go.