在我們繼續測試 Ansible 角色之前,有個值得一提的事情是在你的開發工作流程中使用 Molecule 的另一個優勢。我一直發現,使用 Ansible 開發的痛點之一是,為了讓事情如預期的那樣運作,你必須進行大量的調整,然後清理環境,並重複這些調整操作。謝天謝地,Molecule 公開了 molecule converge 命令,它允許你在你的 Docker 容器上應用一個 Ansible 角色,而不需要運行任何測試。這意味著你可以不斷地將你的角色應用於容器,以確保在開發過程中容器是按預期的方式執行。為了檢查你是否在正確的軌道上,molecule login 允許你檢查容器,如果你犯了一個錯誤,你可以通過 molecule destroy 清理。
現在我們已經掌握了 Ansible 角色的處理方法,讓我們使用 InSpec 確保實際部署到 AWS 時一切如我們所期望的那樣。
InSpec
InSpec 是來自 Chef 創建者的一個非常棒的工具,它允許我們測試已部署的基礎設施以獲得所需的狀態。從可用的CIS基準測試,到驗證環境中的漏洞是否已經修補,InSpec 有許多用途。對於我們的例子來說,我們希望使用 InSpec 來確保我們部署的基礎設施滿足一些簡單的要求:
1. 我們是否只公開 HTTP 和 HTTPS 埠?
2. 我們的管理 IP 能否使用 SSH 連接?
3. NGINX 是否已經啟動並運行在這兩個重定向器上?
4. 我們的反向代理配置是否適用於我們的重定向器?
確定了這些簡單的測試用例之後,讓我們創建一組 InSpec 測試來驗證我們部署的基礎結構是否符合我們的期望。
首先,我們需要初始化我們的 AWS 測試,我們可以這樣做:
inspec init profile --platform aws aws_tests
這將創建一個模板,布局看起來像下面這樣:
.
├── README.md
├── attributes.yml
├── controls
│ └── example.rb
├── inspec.lock
└── inspec.yml
在這篇文章中,我們將致力於 example.rb (重命名為某個合理的名稱) ,以介紹一些由 Terraform 構建的 AWS 環境的測試。
如果我們專注於確保我們的重定向器是在線的,並且我們的安全組只公開暴露 HTTP/S,而 公開暴露 SSH ,那麼我們最終會得到一組測試用例,比如:
title "AWS"
describe aws_ec2_instance(name: 'Redirector-LongHaul') do
it { should exist }
end
describe aws_ec2_instance(name: 'Redirector-ShortHaul') do
it { should exist }
end
describe aws_security_group(group_name: 'redirector-sg') do
it { should exist }
it { should allow_in(port: 80, ipv4_range: '0.0.0.0/0') }
it { should allow_in(port: 443, ipv4_range: '0.0.0.0/0') }
it { should allow_in(port: 22, ipv4_range: '1.2.3.4/32') }
end
確保你的 AWS 配置文件是通過 AWS configure 命令或通過環境變量進行配置的,我們可以使用我們的默認配置文件運行 InSpec 測試:
cd test-aws; inspec exec . -t aws://
如果一切順利的話,我們應該看到我們收到了來自 InSpec 的確認信息,一切正常:
但是我們的重定向器配置怎麼辦,我們怎麼知道我們的 Ansible 角色被實際應用了呢?同樣,這也很容易檢查,我們可以創建一個模板,可以使用下面的命令:
inspec init profile redirectors
然後添加一些測試用例,類似於我們上面的 Molecule 測試:
title "Service Config"
describe service('nginx') do
it { should be_installed }
it { should be_enabled }
it { should be_running }
end
describe service('ssh') do
it { should be_installed }
it { should be_enabled }
it { should be_running }
end
describe file('/etc/nginx/conf.d/default.conf') do
its('content') { should match %r{proxy_pass } }
end
然後通過以下方式運行:
inspec exec . -t ssh://ubuntu@HOST -i ./keys/remotekey
這裡我們有一個 NGINX 配置文件的例子,但它與我們預期的測試不匹配。經過一個簡單快速的修改後,就可以修復 Ansible 的角色或 InSpec 測試,執行後,我們可以看到所有檢查項的結果:
太棒了,那麼讓我們來確認一下我們目前所擁有的... 我們有 Terraform 腳本,它可以創建我們的基礎設施。我們有可用於重定向器的劇本和角色。我們有 Molecule 測試,讓我們能夠快速開發和驗證我們的角色,最後我們有 InSpec 測試,以確保我們的基礎設施完全按照我們的期望創建。
接下來是相當有趣的一部分... ... 接下來的內容可以保持我們未來基礎設施發展的良好狀態。
在 CI 管道中將所有東西粘合在一起
因此,我們現在有了解決這個難題的所有細節,並且我們相信,我們的角色和基礎設施將按照預期的方向發展。但是,我們如何確保每次有人對這些組件中的任何一個進行更改時,在下一次我們啟動或重新啟動環境時,一切都能順利運行呢?在開發世界中,持續集成已經成為保持代碼在可控範圍內的一種有用實踐。通過強制將測試推送到 Git 伺服器上運行,我們就可以確保在下次部署時將代碼更改合併到 Master 中不會導致中斷。
為了創建我們的 CI 管道,我們將使用 Gitlab,這是我目前選擇的託管 Git 伺服器。Gitlab 除了是託管 Git 代碼倉庫的好地方,它還具有 CI/CD 平臺的功能,允許我們通過 Gitlab Runners 執行測試、構建和部署。如果你更喜歡使用 Github,請查看 James 發表的 Github 操作 這篇文章,了解他們的 CI/CD 平臺。
那麼 CI 到底是什麼呢?CI 的意思是「持續集成「,即通過大量的自動化測試,不斷將開發人員的更改合併到一個穩定的分支中,以確保 push 的 commit 沒有破壞任何東西。其想法是,通過在合併期間使用測試來保持質量水平,你可以在任何給定時間將穩定分支連續地部署到生產環境中,因為你知道它應該始終按預期的方式運行(這是對「持續部署」的簡化描述。
為了將測試的每個階段聯繫起來,我們需要在 gitlab-ci.yml 中描述每個階段。該文件位於項目的根目錄中。這將允許我們構建我們的管道,我們可以使用它來進行 Git 測試,推送到一個臨時環境,並通過一系列的驗證步驟來確保我們的基礎設施看起來正如我們所希望的那樣。
我們管道的每個階段將被定義為:
· 測試-通過 Molecule 測試 Ansible 角色
· 測試- Terraform HCL 驗證
· 階段-部署到測試環境
· 提供-提供已部署 Ansible 的伺服器
· 驗證-測試環境的 InSpec 驗證
· 清理-拆除基礎設施
讓我們將每個階段分解,並在 gitlab-ci.yml 文件中進行表示。從我們各個管道階段的定義開始:
stages:
- test
- stage
- provision
- validate
- cleanup
在這裡,我們定義了管道的每個階段,它們可以與上面定義的步驟相匹配。接下來我們需要告訴 Gitlab 在每個階段該做什麼,從 Terraform 開始:
terraform_validate:
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
stage: test
script:
- cd terraform
- terraform init
- terraform validate
這裡的想法很簡單,如果基本的 Terraform 配置沒有檢查出來,我們不想進入管道的附加步驟,所以我們在繼續之前運行一個簡單的驗證步驟。
一旦這個階段完成後,我們就可以開始為我們的 Ansible 角色運行 Molecule 測試:
molecule_tests:
image: docker:latest
stage: test
before_script:
- apk update && apk add --no-cache docker
python3-dev py3-pip docker gcc git curl build-base
autoconf automake py3-cryptography linux-headers
musl-dev libffi-dev openssl-dev openssh
- docker info
- python3 --version
script:
- pip3 install ansible molecule docker
- cd ansible/roles/ubuntu-nginx
- molecule test
在這裡,我們將使用 docker,通過使用最新的鏡像和「Docker-in-Docker」服務,能夠使 Molecule 啟動額外的需要運行 Molecule 測試的容器。值得注意的是,在這個階段,我們正在安裝的 Molecule 框架是在管道的階段中執行。我不建議在你自己的管道中這樣做,因為這裡已經演示了執行 Molecule 需要什麼。實際上,你可以託管一個 Docker 鏡像,其中包含所有預先配置的內容,以加速測試。
一旦我們的管道達到這個階段,我們已經驗證了我們的 Terraform 文件在語法上是正確的,並且我們的 Ansible 角色通過了每個創建的測試,所以接下來我們將把我們的基礎設施部署到 AWS,作為一個臨時環境進行進一步的測試:
deploy_stage:
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
stage: stage
script:
- cd terraform
- terraform init
- terraform apply --auto-approve
artifacts:
paths:
- terraform/terraform.tfstate
expire_in: 1 day
與我們之前的 Terraform 階段類似,我們只是簡單地使用 Hashicorp 的 Terraform Docker 鏡像來提供 Terraform 工具,但是在 Terraform 運行之後,我們希望將狀態文件作為一個工件來保存。工件允許我們從一個階段公開文件,同時提供將創建的文件傳遞到管道的後續階段的能力,這意味著在這種情況下,我們可以傳遞 Terraform tfstate 文件以便以後進行清理。
現在我們已經在我們的階段環境中部署了我們的重定向器,我們需要使用 Ansible 提供它們:
provision_stage:
stage: provision
when: delayed
start_in: 30 seconds
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
before_script:
- apk add ansible
- wget https://github.com/adammck/terraform-inventory/releases/download/v0.9/terraform-inventory_0.9_linux_amd64.zip -O /tmp/terraform-inventory_0.9_linux_amd64.zip
- unzip /tmp/terraform-inventory_0.9_linux_amd64.zip -d /usr/bin/; chmod 700 /usr/bin/terraform-inventory
script:
- cd terraform; terraform init; cd ..
- cd ansible; chmod 600 .
- chmod 600 ../terraform/keys/terraformkey
- ANSIBLE_HOST_KEY_CHECKING=False TF_STATE=../terraform ansible-playbook --inventory-file=/usr/bin/terraform-inventory -u ubuntu --private-key ../terraform/keys/terraformkey site.yml
同樣,你通常會創建一個 Docker 鏡像來加速這個階段,並根據需要添加「terraform-inventory」工具。
一旦我們的重定向器準備好了以後,我們就可以在 AWS 的分段環境中運行我們之前精心設計的 InSpec 測試:
inspec_tests:
stage: validate
image:
name: chef/inspec:4.18.51
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/bundle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
before_script:
- apk add jq
script:
- inspec --chef-license=accept-silent
- cd inspec
- inspec exec redirectors -t "ssh://ubuntu@$(jq '.redirector_ips.value[0]' -r ../terraform/output.json)「-i ../terraform/keys/terraformkey
- inspec exec redirectors -t "ssh://ubuntu@$(jq '.redirector_ips.value[1]' -r ../terraform/output.json)「-i ../terraform/keys/terraformkey
最後,一旦所有的事情都完成了,我們需要自己清理完成之後的環境:
cleanup:
when: always
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
stage: cleanup
script:
- cd terraform
- terraform init
- terraform destroy --auto-approve
你可能會注意到這裡的 when:always 語句。這僅僅意味著即使前一個階段失敗了,這個階段也將運行。這使我們有機會清理分段環境,在測試失敗的情況下,可以避免支付不必要的 AWS 費用。
當我們把 gitlab-ci.yml 文件放在一起後,我們會得到下面這樣的文件:
image: docker:latest
services:
- docker:dind
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- terraform/.terraform
stages:
- test
- stage
- provision
- validate
- cleanup
terraform_validate:
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
stage: test
script:
- cd terraform
- terraform init
- terraform validate
molecule_tests:
image: docker:latest
stage: test
before_script:
- apk update && apk add --no-cache docker
python3-dev py3-pip docker gcc git curl build-base
autoconf automake py3-cryptography linux-headers
musl-dev libffi-dev openssl-dev openssh
- docker info
- python3 --version
script:
- pip3 install ansible molecule docker
- cd ansible/roles/ubuntu-nginx
- molecule test
deploy_stage:
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
stage: stage
script:
- cd terraform
- terraform init
- terraform apply --auto-approve
- terraform output --json > output.json
artifacts:
paths:
- terraform/terraform.tfstate
- terraform/output.json
expire_in: 1 day
provision_stage:
when: delayed
start_in: 30 seconds
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
stage: provision
before_script:
- apk add ansible
- wget https://github.com/adammck/terraform-inventory/releases/download/v0.9/terraform-inventory_0.9_linux_amd64.zip -O /tmp/terraform-inventory_0.9_linux_amd64.zip
- unzip /tmp/terraform-inventory_0.9_linux_amd64.zip -d /usr/bin/; chmod 700 /usr/bin/terraform-inventory
script:
- cd terraform; terraform init; cd ..
- cd ansible; chmod 600 .
- chmod 600 ../terraform/keys/terraformkey
- ANSIBLE_HOST_KEY_CHECKING=False TF_STATE=../terraform ansible-playbook --inventory-file=/usr/bin/terraform-inventory -u ubuntu --private-key ../terraform/keys/terraformkey site.yml
inspec_tests:
stage: validate
image:
name: chef/inspec:4.18.51
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/bundle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
before_script:
- apk add jq
script:
- inspec --chef-license=accept-silent
- cd inspec
- inspec exec test-aws -t aws://
- inspec exec redirectors -t "ssh://ubuntu@$(jq '.redirector_ips.value[0]' -r ../terraform/output.json)「-i ../terraform/keys/terraformkey
- inspec exec redirectors -t "ssh://ubuntu@$(jq '.redirector_ips.value[1]' -r ../terraform/output.json)「-i ../terraform/keys/terraformkey
cleanup_stage:
when: always
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
stage: cleanup
script:
- cd terraform
- terraform init
- terraform destroy --auto-approve
一旦投入到我們的項目中,我們就可以看到我們現在擁有了可用的管道,它將在每次 push 時執行。希望一切順利,一旦有人對你的基礎設施進行了 commit,你將會受到一個明確的指示,表明一切正常:
這就是我們如何使用測試和 CI 管道來保持我們的 RedTeam 基礎設施處於良好狀態的一個例子。希望這篇文章的內容能夠對你有所幫助,希望在不久的將來能有更多關於這個主題的文章。
本文參考自:https://blog.xpnsec.com/testing-redteam-infra/