随着RedTeaming行业的发展,我们对构建可靠环境的需求也越来越高。至关重要的是要拥有维护健壮的基础架构的能力,该基础架构要保证一旦出现问题就可以重新创建,更重要的是,我们需要确保环境在部署时不会出现问题。
今天,我开始发布一系列文章中的第一篇,我们将采用DevOps团队流行的一些做法,并希望使用这些技术来帮助我们构建稳定且经过测试的基础架构。在本文中,我将快速回顾一下如何在代码中定义RedTeam基础架构,这些代码放在Git仓库中。但是,更重要的是,我们将继续研究环境不断变化和复杂性增加的测试方法,并逐步介绍如何将CI管道引入混合流程以帮助实现此测试的自动化。
长期以来,典型的RedTeam基础架构部署的构建都使用redirectors的概念进行了标准化。最基本的C2部署如下所示:
到目前为止,我们都了解了在选择C2框架之前使用重定向器的优势,使我们能够通过团队服务器控制客户敏感数据,并掩盖了真正的流量来源,同时允许快速重新创建这些一次性重定向器而无需需要拆除后端服务器。
那么,这一切如何管理?多年来,我已经看到了许多种不同的方式来部署环境。最常见的设置是手动部署每个组件的位置。对于AWS用户,这意味着需要通过管理控制台来创建EC2实例,通过SSH进入,为反向隧道配置SSH等。当然,一旦BlueTeam找到重定向器,实例就会被拆除,然后我就要重新配置新实例,依此类推…
这显然很烦人且容易出错,因此诞生自动化RedTeam基础架构部署的想法。我将从aws或az shell命令包装到脚本中,再到通过配置文件使用Terraform管理环境。
但是,在开始测试基础架构之前,我们实际上需要一些基础架构进行测试。
首先,我们将专注于创建容纳我们资源的基础结构的过程。可能许多人都熟悉Terraform,它是Hashicorp的工具,Terraform允许您在云服务(例如AWS,Azure和DigitalOcean)上创建,更新和销毁基础架构。基础结构在HCL脚本中进行了描述,并在进行了设计之后,我们将其交给Terraform及其提供者来“ 实现 ”。
出于本文的目的,我们将创建一个非常简单的环境,其中包含2个充当重定向器的EC2实例。用于执行此操作的Terraform脚本通常如下所示:
provider "aws" {
region = "eu-west-2"
}
variable "INSTANCE_NAMES" {
default = {
"0" = "Redirector-ShortHaul"
"1" = "Redirector-LongHaul"
}
}
variable "PUBLIC_KEY" {
default = "./keys/terraformkey.pub"
}
variable "MANAGEMENT_IP" {
default = "1.2.3.4/32"
}
resource "aws_key_pair" "terraformkey" {
key_name = "${terraform.workspace}-terraform-key"
public_key = file("${var.PUBLIC_KEY}")
}
resource "aws_security_group" "redirector-sg" {
name = "redirector-sg"
# Allow HTTP inbound
ingress {
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
from_port = 80
to_port = 80
}
# Allow HTTPS inbound
ingress {
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
from_port = 443
to_port = 443
}
# Allow management from our management IP
ingress {
protocol = "tcp"
cidr_blocks = ["${var.MANAGEMENT_IP}"]
from_port = 22
to_port = 22
}
# Allow global outbound
egress {
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
from_port = 0
to_port = 0
}
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"]
}
resource "aws_instance" "redirector" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
count = length(var.INSTANCE_NAMES)
key_name = aws_key_pair.terraformkey.key_name
vpc_security_group_ids = [
"${aws_security_group.redirector-sg.id}",
]
tags = {
Name = "${var.INSTANCE_NAMES[count.index]}"
JobName = "${terraform.workspace}"
}
}
output "redirector_ips" {
description = "Public IP addresses of created redirectors"
value = aws_instance.redirector.*.public_ip
}
为了确保我们可以运行基础架构的多个实例并且不会与其他参与资源冲突,我们通常通过以下方式创建新的Terraform工作空间:
terraform workspace new InsecureBank
但是在部署之前,让我们通过执行terraform plan来测试一切是否正常:
一旦我们对所有内容进行了检查,就可以移至terraform apply命令:
Terraform完成后,我们可以查询AWS并验证是否已创建服务器:
aws ec2 describe-instances --query 'Reservations[].Instances[].[Tags[?Key==`Name`].Value,InstanceType,PublicIpAddress]' --output json
接下来我们需要配置它们。这里不需要使用SSH,无需手动配置任何内容。相反,让我们看一下如何使这一步骤自动化。
尽管有诸如Chef和Puppet之类的其他工具,但是Ansible仍然是我最喜欢的工具之一
因此,我们知道现在有2台在线服务器要配置。我们也知道它们的功能将是相同的,这意味着一个写得很好的Ansible脚本应该能够应用于每个环境。
让我们从创建工作区开始。我发现以下布局对此类项目很有用:
.
├── ansible.cfg
├── playbooks
│ └── 10_example.yml
├── roles
│ ├── requirements.yml
│ └── ubuntu-nginx
│ ├── ...
└── site.yml
这种布局的想法是提供存储我们的脚本和角色的位置,同时包括一个Ansible配置文件,该文件将提供存储规则的位置。
我尝试坚持创建Ansible角色来封装相似的配置区域,例如,对于这样的项目,我们将为以下区域创建单独的角色:
加强基础安装
安装Nginx以公开HTTP(S)C2
配置SSH以从我们的团队服务器进行访问
添加任何日志记录功能
显然,您可以根据需要自定义重定向器,但是在此示例中,我们将重点放在Web服务器角色上。我们的角色将非常简单,并涉及以下任务:
确保添加了所需的用户和组
安装NGINX
通过配置文件配置NGINX以进行反向代理
综上所述,我们的Ansible角色YAML可能看起来像这样:
---
- name: Ensure group "nginx" exists
group:
name: nginx
state: present
- name: Add the user nginx
user:
name: nginx
shell: /bin/nologin
groups: nginx
- name: Install NGINX
apt:
name: nginx
state: present
update_cache: yes
- name: Add NGINX configuration from template
template:
src: default.conf.j2
dest: /etc/nginx/conf.d/default.conf
owner: root
group: root
mode: 0644
notify: restart nginx
在这里,我们采取了一些简单的步骤来添加我们的nginx用户,安装NGINX并添加从模板制作的配置文件。我们可以使用脚本来应用它,例如:
---
- hosts: redirector*
become: yes
gather_facts: no
pre_tasks:
- raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
- setup: # aka gather_facts
roles:
- redirector-nginx
构建角色和脚本后,通过terraform-inventory工具可以轻松地将Ansible角色应用于重定向器的过程,该工具使我们能够解析Terraform状态文件并动态构建Ansible库存。例如,通过上面的Ansible项目,我们可以使用以下方法来应用角色:
TF_STATE=../terraform ansible-playbook --inventory-file=/usr/local/bin/terraform-inventory -u ubuntu --private-key ../terraform/keys/terraformkey site.yml
我们将要测试两个方面。首先将是我们的Ansible角色,负责实际配置重定向器。第二个将是包含重定向器的AWS基础架构的部署,以及部署后实际AWS重定向器的状态。
建立Ansible可能会很难,对我们来说幸运的是,有工具可以帮助测试Ansible,这也使我们可以从提高的开发速度。为此,我们将使用一个名为Molecule的框架。这个框架已经存在了一段时间,并且实质上允许您隔离地部署Ansible角色(例如,在Docker容器内),并在应用角色后使用TestInfra测试容器的状态。
首先,我们需要使用pip安装Molecule:
# Create our venv
python -m venv env
source ./env/bin/activate
# Install molecule
pip install --user molecule
安装完成后,我们将为Ansible角色构建一些测试。Molecule使用“scenarios”的概念来描述测试用例的**。让我们使用以下方法创建默认场景以测试NGINX角色:
cd rolename; molecule init scenario -r rolename
我们看一下内部,将会看到类似以下的内容:
.
└── default
├── Dockerfile.j2
├── INSTALL.rst
├── molecule.yml
├── playbook.yml
└── tests
├── test_default.py
└── test_default.pyc
对于这种情况,我们的测试将添加到test_default.py Python脚本中,并将使用TestInfra框架。但是在开始之前,我们应该花一些时间通过Molecular.yml文件配置场景。由于我通常偏爱基于Ubuntu的服务器,因此我想确保测试在相似的环境中运行。为此,我们只需将“平台”部分更新为:
platforms:
- name: instance
image: ubuntu:18.04
接下来,让我们创建一些简单的测试来展示此框架的功能。我们知道此角色将部署NGINX,因此,作为一个很好的起点,我们可以进行测试以确保在我们的角色运行后实际安装了NGINX,如下所示:
def test_nginx_installed(host):
nginx = host.package("nginx")
assert nginx.is_installed
我们还想确保在应用角色时NGINX实际上正在运行:
def test_nginx_running(host):
nginx = host.service("nginx")
assert nginx.is_running
带有反向代理信息的自定义配置又如何呢?好吧,让我们添加一些检查以确保一旦应用角色,我们的配置实际上就存在:
def test_nginx_configuration(host):
passwd = host.file("/etc/nginx/conf.d/default.conf")
assert passwd.contains("proxy_pass")
assert passwd.user == "nginx"
assert passwd.group == "nginx"
assert passwd.mode == 0o644
将此添加到我们的Molecule测试用例后,让我们开始快速测试以确保所有功能都适用:
molecule test
现在,在我们的情况下,我们可能会看到以下内容:
这意味着测试失败了,因为我们的NGINX配置文件是root拥有的,并且我们的测试正在寻找nginx的用户,所以让我们更新测试以反映这一点:
def test_nginx_configuration(host):
passwd = host.file("/etc/nginx/conf.d/default.conf")
assert passwd.contains("proxy_pass")
assert passwd.user == "root"
assert passwd.group == "root"
assert passwd.mode == 0o644
希望当我们重新运行测试时,我们将看到如下所示:
在我们继续测试Ansible角色之前,值得一提的是在开发工作流程中使用Molecule的另一个优势。我发现,使用Ansible开发的痛苦之一是您必须进行许多调整才能使工作按预期进行,然后清理环境并重复进行。幸运的是,Molecule公开了Molecular converge命令,该命令使您可以在不运行任何测试的情况下将Ansible角色应用于Docker容器。这意味着您可以继续将角色应用于容器,以确保其在开发过程中按预期进行。
InSpec是Chef的创造者提供的一个很棒的工具,它使我们能够根据所需状态测试已部署的基础架构。从可用的CIS基准测试到验证某个环境中已修补了漏洞,InSpec有许多用途。我们想使用InSpec来确保我们部署的基础架构满足许多简单的要求:
1.我们只公开HTTP和HTTPS端口吗?
2.SSH是否可用于我们的管理IP?
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 configure命令或环境变量配置了您的AWS配置文件,并且我们可以使用我们的默认配置文件运行InSpec测试,其中包括:
cd test-aws; inspec exec . -t aws://
希望一切顺利,我们应该看到我们收到了InSpec的确认,确认所有内容都输出:
但是我们的重定向器配置又如何呢?我们怎么知道我们的Ansible角色实际上已被应用?这很容易检查,我们可以使用以下方法创建模板:
inspec init profile redirectors
并添加一些测试用例,类似于上面的测试:
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脚本。我们有Ansible脚本和角色,可以提供重定向程序。我们进行了分子测试,以使我们能够快速开发和验证我们的角色,最后,我们进行了InSpec测试,以确保完全按照我们的期望来创建基础架构。
我们现在可以解决所有问题,并且我们相信我们的角色和基础架构将按期望的方式工作。但是,我们如何确保每次有人对这些组件中的任何一个进行更改时,下次我们启动或重新启动环境时,一切都能顺利运行?在开发环境中,CI已成为控制代码的有用实践。通过强制在推送到Git服务器时运行测试,我们可以确保将代码更改合并到Master中不会在下次部署时造成中断。
为了创建我们的CI管道,我们将使用Gitlab,这是我当前选择的托管Git服务器。Gitlab不仅是托管我们的Git仓库的好地方,而且还充当CI / CD平台,使我们能够通过Gitlab Runners执行测试,构建和部署。
那么CI到底是什么?CI的意思是“持续集成”,这是一种通过大量自动化测试不断将开发人员所做的更改合并到稳定分支中的实践,以确保推送的提交不会破坏任何内容。这个想法是,通过使用测试在合并期间保持质量水平。
为了将测试的每个阶段联系在一起,我们需要在gitlab-ci.yml文件中描述每个阶段,该文件位于项目的根目录中。这将使我们能够构建流水线,可用于通过Git推送进行测试,过渡到暂存环境,并通过一系列验证步骤来确保基础结构完全符合我们的期望。
我们管道的每个阶段将定义为:
测试–通过Molecule测试Ansible
测试– Terraform HCL验证
阶段–部署到测试环境
预配–使用Ansible预配已部署的服务器
验证–测试环境的InSpec验证
清理–拆除基础设施
让我们从定义各个管道阶段开始,将每个阶段分解为如何在gitlab-ci.yml文件中表示:
stages:
- test
- stage
- provision
- validate
- cleanup
在这里,我们定义了管道的每个阶段,我们可以将它们匹配到上面定义的步骤。接下来,我们需要从Terraform开始,告诉Gitlab在每个阶段做什么:
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_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:latest映像以及“ 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
提交到项目后,我们将看到现在可以使用我们的管道,该管道将在每次推送时执行。一旦有人向您的基础架构提交提交,您将收到一个消息,表明一切都已正常:
*参考来源:mdsec,FB小编周大涛编译,转载请注明来自FreeBuf.COM