在我们继续测试 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/如若转载,请注明原文地址: