テスト駆動型インフラストラクチャ

コードとしてのコンプライアンス

by
Joaquín Menchaca

ホアキーン • メンチャカ

私についての情報

About ME

ロケット弁護士

職業:フルタイム忍者

実際の写真

Test Driven
InfraStructure

Compliance as Code

by
Joaquín Menchaca

À propos de moi

About ME

Rocket LAWYER

SENIOR DEVOPS BUILD/RELEASE ENGINEER

aka

Linux Ninja Pants Automation Engineering Mutant

actual photo

AGENDA

  1. Setup
  2. Context
  3. Chef + InSpec
  4. Ansible + InSpec
  5. Bonus

SETUP

ThE CODE

git clone \
  https://github.com/darkn3rd/lisa18_test_driven_infra
cd lisa18_test_driven_infra

# Using Virtual Workstation
vagrant up
vagrant ssh
cd lisa18_test_driven_infra

# Using Host (MacOS X or Linux)
#### Install Requirements

# Using Host (Windows)
#### Install Rrequiremnts
#### Warning: Might not work, had success w/ Chef

CODE

SETUP

The HoST

must be able to run
Docker
or
Vagrant

Minimum ReqUIRED

ChefDK - bundles ruby, test kitchen, inspec

Docker - fastest way to run stuff (virtual virtual machines)

DOCKER

Easiest Path is Docker Desktop

choco install docker-for-windows
brew cask install docker

https://chocolatey.org/

https://brew.sh/

https://www.docker.com/products/docker-desktop

DirecT DOWNLOAD

Package Managers

DOCKER

sudo apt-get update -qq
sudo apt-get install -y apt-transport-https \
 curl ca-certificates software-properties-common

DOCKER_REPO="https://download.docker.com/linux/ubuntu"
curl -fsSL ${DOCKER_REPO}/gpg | \
  sudo apt-key add -
sudo add-apt-repository \
  "deb [arch=amd64] ${DOCKER_REPO} \
  $(lsb_release -cs) \
  stable"

sudo apt-get update -qq
sudo apt-get install -y docker-ce
sudo usermod -aG docker $USER

Debian Package on Ubuntu

DOCKER

CHEFDK

Easiest Way to Get Test Kitchen and InSpec

choco install chefdk
chef gem install kitchen-ansible
chef gem install kitchen-docker
brew tap chef/chef
brew cask install chefdk
chef gem install kitchen-ansible
chef gem install kitchen-docker

https://chocolatey.org/

https://brew.sh/

https://downloads.chef.io/chefdk/

DirecT DOWNLOAD

Package Managers

CHEFDK

VER=3.2.30
PKG=chefdk_${VER}-1_amd64.deb
PREFIX=https://packages.chef.io/files/stable/chefdk/

# Fetch and Install
wget --quiet ${PREFIX}/${VER}/ubuntu/16.04/${PKG}
sudo dpkg -i ${PKG}

# Local ChefDK Ruby Gems
chef gem install kitchen-ansible
chef gem install kitchen-docker

Debian Package on Ubuntu

CHEFDK

CONTEXT

WHY?

WHY?

We Code
SYSTEMS

We Code
PLATFORMS

We Code INFRASTRUCTURE

WHY?

WHY?

  • Disaster Recovery, Rollback
  • Migrate/Replicate New Environments
  • Swap out components, Experiment
  • Rapid (Like Seriously) Development

Destroy and Recreate with Ease
Because Tests

WHEN?

WHEN
You have
an
Artifact?

What?

WHAT TO TEST?

HOW?

ABOUT THE

TOOLS

TEST KITCHEN

Create (Vagrant)→Converge(Chef)→Verify(InSpec)

TEST KITCHEN: Vagrant

---
driver:
  name: vagrant
  provider: hyperv

provisioner:
  name: chef_zero

verifier:
  name: inspec

platforms:
  - name: ubuntu-16.04

suites:
  - name: default

Create (Docker)→Converge(Ansible)→Verify(InSpec)

TEST KITCHEN

---
driver:
  name: docker

provisioner:
  name: ansible_playbook

verifier:
  name: inspec

platforms:
  - name: ubuntu-16.04

suites:
  - name: default

TEST KITCHEN: DOCKER

control 'ez_apache-contract-01' do
  describe port(80) do
    it { should be_listening }
  end
end

control 'ez_mysql-security-conf-01' do
  describe file(mysql_data_path) do
    it { should be_directory }
    it { should be_owned_by 'mysql' }
    it { should be_grouped_into 'mysql' }
  end
end

INSPEC

package 'apache2'

service 'apache2' do
  action %i(enable start)
  supports(
    status: true,
    restart: true,
    reload: true
  )
end

cookbook_file "#{node['docroot']}/index.html" do
  source 'index.html'
  action :create
end

CHEF

- name: "Install Web Service"
  package:
    name: apache2
    state: present

- name: "Start Web Service"
  service:
    name: apache2
    state: started
    enabled: yes

- name: "Copy Content"
  copy:
    src: "{{ role_path }}/files/index.html"
    dest: "{{ docroot }}/index.html"

ANSIBLE

InSPEC

CHEF

CookBOOK STRUCTURE

./chef/cookbooks/ez_mysql
├── attributes
│   └── default.rb
├── Berksfile
├── kitchen.docker.yml
├── kitchen.hyperv.yml
├── kitchen.vbox.yml
├── metadata.rb
├── README.md
├── recipes
│   ├── client.rb
│   ├── database.rb
│   ├── default.rb
│   ├── harden.rb
│   ├── install.rb
│   └── service.rb
├── templates
│   └── security.cnf.erb
└── test -> ../../../inspec/ez_mysql/test

TEST STRUCTURE

./inspec/ez_mysql
└── test
    └── integration
        └── default
            ├── ansible.cfg
            ├── conform_test.rb
            ├── contract_test.rb
            ├── default.yml
            └── security_test.rb

Shared Test for ANY change configuration, e.g.
CAPS (Chef, Ansible, Puppet, Salt Stack), or other automation

THE STEPS

# Setup (tell kitchen to use docker)
export KITCHEN_YAML=kitchen.docker.yml

# Create Environment
kitchen create

# (optional) Login into environment
kitchen login

# Converge to Desired State
kitchen converge

# Verify Environment
kitchen verify

WORKED FINE?

THE STEPS

include_recipe 'ez_mysql::install'
include_recipe 'ez_mysql::service'
# include_recipe 'ez_mysql::database'
include_recipe 'ez_mysql::harden'

THE STEPS

include_recipe 'ez_mysql::install'
include_recipe 'ez_mysql::service'
include_recipe 'ez_mysql::database'
include_recipe 'ez_mysql::harden'

WHEN CODE WORKS

ANSIBLE

InSPEC

ROLE STRUCTURE

./ansible/roles/ez_mysql
├── defaults
│   └── main.yml
├── handlers
│   └── main.yml
├── kitchen.docker.yml
├── kitchen.hyperv.yml
├── kitchen.vbox.yml
├── meta
│   └── main.yml
├── tasks
│   ├── client.yml
│   ├── database.yml
│   ├── harden.yml
│   ├── install.yml
│   ├── main.yml
│   └── service.yml
├── templates
│   └── security.cnf.j2
└─── test -> ../../../inspec/ez_mysql/test

TEST STRUCTURE

./inspec/ez_mysql
└── test
    └── integration
        └── default
            ├── ansible.cfg
            ├── conform_test.rb
            ├── contract_test.rb
            ├── default.yml
            └── security_test.rb

Shared Test for ANY change configuration, e.g.
CAPS (Chef, Ansible, Puppet, Salt Stack), or other automation

THE STEPS

# Setup (tell kitchen to use docker)
export KITCHEN_YAML=kitchen.docker.yml

# Create Environment
kitchen create

# (optional) Login into environment
kitchen login

# Converge to Desired State
kitchen converge

# Verify Environment
kitchen verify

WORKED FINE?

THE STEPS

- include: install.yml
- include: config.yml
- include: service.yml
# - include: harden.yml

THE STEPS

- include: install.yml
- include: config.yml
- include: service.yml
- include: harden.yml

WHEN CODE WORKS

EXTRA STUFF

INRAstructure

variable "username" {
  default = "admin"
}

variable "password" {}
variable "project" {}
variable "region" {}

module "gke" {
  source   = "./gke"
  project  = "${var.project}"
  region   = "${var.region}"
  username = "${var.username}"
  password = "${var.password}"
}

INRAstructure

module "k8s" {
  source   = "./k8s"
  host     = "${module.gke.host}"
  username = "${var.username}"
  password = "${var.password}"

  client_certificate     = "${module.gke.client_certificate}"
  client_key             = "${module.gke.client_key}"
  cluster_ca_certificate = "${module.gke.cluster_ca_certificate}"
}

INRAstructure

password = "$3kr3tl!k3$er!ou$1Y"
project = "lisa18-test-driven-infra"
region = "us-east4"
terraform plan \
  -var-file="kluster.tfvars"
terraform apply \
  -var-file="kluster.tfvars"
kluster.tfvars

console

INRAstructure

name: gcp-profile
title: InSpec Profile
maintainer: Joaquín Menchaca
copyright: Joaquín Menchaca
license: MIT
summary: InSpec GCP Demo
version: 0.1.0

depends:
  - name: gcp-resources
    url: https://github.com/inspec/inspec-gcp/archive/master.tar.gz
supports:
  - platform: gcp
guestbook-profile/inspec.yml

INRAstructure

title 'GCP Guestbook Cluster'

control 'gcp-1' do
  impact 0.7
  title 'Check Guestbook Cluster'

  describe google_container_cluster(
    project: PROJECT_NAME,
    zone: CLUSTER_ZONE,
    name: CLUSTER_NAME
  ) do
    it { should exist }
    its('name') { should eq CLUSTER_NAME }
    its('status') { should eq 'RUNNING' }
    its('master_auth.username') 
     { should eq 'admin' }
    its('network') { should eq "default" }
    its('subnetwork') { should eq "default"}
    its('initial_node_count') { should eq 3 }
  end
end
guestbook-profile/control/cluster.rb

INRAstructure

project_name: lisa18-test-driven-infra
cluster_zone: us-east4-c
cluster_name: guestbook
inspec exec guestbook-profile \
  -t gcp:// --attrs attributes.yml

attributes.yml

console

FINAL NOTES

(for reference)

TESTED PLATFORMS

Mac OS HOST

LINUX GUEST

WINDOWS HOST

☑ Mac/Vagrant (Virtualbox) / TestKitchen (Docker) / Chef
☑ Mac/Vagrant (Virtualbox)/ TestKitchen (Docker) / Ansible
☑ Win/Vagrant (HyperV) / TestKitchen (Docker) / Chef
☐ Win/Vagrant (HyperV)/ TestKitchen (Docker) / Ansible

☑ TestKitchen (HyperV) / Chef
☒ TestKitchen (HyperV) / Ansible
☐ TestKitchen (Docker) / Chef
☐ TestKitchen (Docker) / Ansible

☑ TestKitchen (Virtualbox) / Chef
☑ TestKitchen (Virtualbox) / Ansible
☑ TestKitchen (Docker) / Chef
☑ TestKitchen (Docker) / Ansible

FURTHER READING

TEST KITCHEN RESROUCES

Vagrant

DOCKER

FURTHER READING

INSPEC

KUBERNETES

TERRAFORM

FURTHER READING

INSPEC SCRIPTS

FURTHER READING

OTHER QUESTIONS

THE END

テスト駆動型インフラストラクチャ

By Joaquín Menchaca

テスト駆動型インフラストラクチャ

Presented Live at LISA18

  • 708