Day 12-DevOpSec 正夯,没做 security check 的 module 不要用

本篇延续 Terragrunt 的功能,介绍

  • 一款安全性扫描工具: tfsec
  • terragrunt hook
  • terragrunt multiple workspaces
  • terragrunt dependency

课程内容与代码会放在 Github 上: https://github.com/chechiachang/terraform-30-days

赛後文章会整理放到个人的部落格上 http://chechia.net/

追踪粉专可以收到文章的主动推播

https://ithelp.ithome.com.tw/upload/images/20210901/20120327NvpHVr2QC0.jpg


tfsec

工程师应该注意 infrastructure 的安全性,然而并非人人都是资安专业背景,不一定都能捉到设定上资安风险。这时就要依赖外部的检查资料库,根据常见的安全性错误进行检查。tfsec 是一个很好的免费开源工具,针对 terraform 的 .tf 档案,针对 plan 直接进行分析,挑出安全性错误。底下介绍如何搭配 terragrunt 使用 tfsec。

Install tfsec

sudo port install tfsec
sudo brew install tfsec

tfsec --version
0.57.0

Run

找寻 root module 直接运行 tfsec

  • 然而由於我们有使用 terragrunt 做一层 wrapper,在执行 terragrunt 直接执行 tfsec 的话会缺乏许多参数跟档案
  • 会扫描到 .terragrunt-cache 的档案,这些档案是外部下载的 module
  • 使用 --exclude-downloeaded 也只会 exclude .terraform
tfsec .
tfsec --exclude-downloaded-modules

为了避免以上的问题,可以使用 terragrunt before hook。这里我们随意拿一个 root module 作为范例

# azure/foundation/compute_network/terragrunt.hcl

terraform {
  ...
  before_hook "tfsec" {
    commands     = ["apply", "plan"]
    execute      = ["tfsec", "."]
  }
}

实际的效果

# azure/foundation/compute_network/terragrunt.hcl

cd azure/foundation/compute_network

terragrunt apply

Initializing modules...

Initializing the backend...

Initializing provider plugins...

Terraform has been successfully initialized!

INFO[0010] Executing hook: before_hook                   prefix=[/Users/che-chia/my-workspace/terraform-30-days/azure/foundation/compute_network]

  times
  ------------------------------------------
  disk i/o             2.952161ms
  parsing HCL          29.157µs
  evaluating values    1.307087ms
  running checks       1.486024ms

  counts
  ------------------------------------------
  files loaded         7
  blocks               8
  evaluated blocks     26
  modules              1
  module blocks        18

  results
  ------------------------------------------
  critical             0
  high                 0
  medium               0
  low                  0
  ignored              0

No problems detected!

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

只要每一次 apply 前,都会进行 tfsec

Move hooks to parent directory

如果希望所有

  • Review: 已具我们前几章的 terragrunt 设定操作 root module 时都会 terragrunt 向上寻找 import {},并 import 上层的 terrgrunt.hcl。我们可以将 hook 写在上层 terragrunt.hcl,所有底下的 root module 都会生效。

使用注意 hook 与 git pre-commit hook,会需要时间,影响开发效率,可以依据团队的状况做调整

  • 例如频繁开发的功能,还需要大量的 plan 除错,就不需要每次 plan 都检查
  • 如果不放在 terraform hook 里,而是移到 CI/ CD 上执行 tfsec,可以保持开发进度
    • 坏处就是在 CI / CD 上扫出安全性漏洞的话,就要拉回来重新执行 PR
  • 放不放 hook?放 terragrunt hook,或是 pre-commit hook,或是 CI 中检查,团队需要多加沟通,多尝试,持续调整,才能达到团队最佳效益。

范例是 after hook,只要每一次 apply 完成後,做一次 tfsec,让工程师测试时就可以检查安全性问题

# azure/terragrunt.hcl

terraform {
  ...
  after_hook "tfsec" {
    commands     = ["apply", "plan"]
    execute      = ["tfsec", "."]
  }
}

tfsec checks

tfsec 检查清单可以到 tfsec.dev 查阅。所有的检查内容都有附上原因,风险说明,问题范例以及改进范例。例如 azure storage 设定风险

tfsec 就是社群维护的安全守则,善用 tfsec 可以实现安全最佳实践,也同时提升自己的资安知识。

ignore warnings

当扫出问题时,我们不一定能马上解决,也许是排时程稍後再修理,也许是被其他因素影响,暂时无法改正。然而每次扫瞄 tfsec 还是会跳出警告。收到警告,但大家又不会马上修改,就会无谓的警告,浪费团队的精神能量,消耗无谓的注意力。tfsec 提供 ignored 标记

  • 扫描发现问题
  • 把已知问题纪录 Issue Tracking 系统(Github Issue 或 Jira)
  • 在 .tf 程序码中可以做标记,打上 Issue number / url 以供双向追踪

Terragrunt multple workspaces

在 Terraform 中,我们会 change directory 到一个一个 root module 中去执行 init, plan, apply 等工作。当 root module 数量很多的时候,这件事就变得很复杂。这时可以利用 Terragrunt 提供同时多 workspace 执行 的功能,一次控制多个 root module。例如以 azure/dev 为例,资料夹树状结构如下

tree azure/dev
.
├── env.tfvars
├── japanwest
└── southeastasia
    ├── container_registry
    │   └── terragrunt.hcl
    └── env.tfvars
cd foundation

terragrunt run-all init
#terragrunt run-all init --reconfigure

INFO[0000] Stack at /Users/che-chia/my-workspace/terraform-30-days/azure/foundation:
  => Module /Users/che-chia/my-workspace/terraform-30-days/azure/foundation/compute_network (excluded: false, dependencies: [])
  => Module /Users/che-chia/my-workspace/terraform-30-days/azure/foundation/service_principal (excluded: false, dependencies: [])
  => Module /Users/che-chia/my-workspace/terraform-30-days/azure/foundation/southeastasia/terraform_backend (excluded: false, dependencies: [])
Initializing modules...

Initializing the backend...

Initializing the backend...

Initializing the backend...

也可以进行 run-all plan

  • 每个 module 都会执行 plan
terragrunt run-all plan

INFO[0000] Stack at /Users/che-chia/my-workspace/terraform-30-days/azure/dev:
  => Module /Users/che-chia/my-workspace/terraform-30-days/azure/dev/japanwest/container_registry (excluded: false, dependencies: [])
  => Module /Users/che-chia/my-workspace/terraform-30-days/azure/dev/southeastasia/container_registry (excluded: false, dependencies: [])

...

Plan: 1 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + registry_login_server = (known after apply)

...

  Plan: 1 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + registry_login_server = (known after apply)

然而,run-all 搭配 apply,要特别注意

  • 这边是先跳出确认,让你盲眼 apply,在显示 plan 时 -auto-approve
  • 这边是先跳出确认,让你盲眼 apply,在显示 plan 时 -auto-approve
  • 这边是先跳出确认,让你盲眼 apply,在显示 plan 时 -auto-approve
  • 工作流程与 terraform 有所不同,需要使用者自行 run-all plan 检视变更
  • 注意不要不小心 apply 错误的 .tf 档案
terragrunt run-all apply

INFO[0000] Stack at /Users/che-chia/my-workspace/terraform-30-days/azure/dev:
  => Module /Users/che-chia/my-workspace/terraform-30-days/azure/dev/japanwest/container_registry (excluded: false, dependencies: [])
  => Module /Users/che-chia/my-workspace/terraform-30-days/azure/dev/southeastasia/container_registry (excluded: false, dependencies: [])
Are you sure you want to run 'terragrunt apply' in each folder of the stack described above? (y/n)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

Terragrunt module dependency

有些 module 其实是有依赖性,需要依照先後顺序 apply 到公有云上,後面的 module 才能够正常 provision。例如 compute VM 其实依赖 compute network 的 subnet id,才能把 compute VM 的 ip 分配到 subnet 上。实际的例子请见 azure/dev/southeastasia/chechia_net/compute

# azure/dev/southeastasia/chechia_net/compute/terragrunt.hcl

dependency "network"{
  config_path = find_in_parent_folders("azure/foundation/compute_network")
}

inputs = {
  ...
  vnet_subnet_id      = dependency.network.outputs.vnet_subnets[0] # dev-1
}

inputs 中有个参数是 vnet_subnet_id 意思是这台 VM 应该使用指定的 subnet

这一个先後的动作,就是元件的依赖性

  • 依赖性不只在於 provision 时有先後顺序,所有的改动都应注意是否会影响下游依赖的服务,有可能不小心改了 network,network 正常,但是底下 VM 却有功能损坏。所谓牵一发而动全身
  • 透过 portal 操作,我们自然就使用工人智慧,手动的维持依赖上游元件的稳定

使用 terraform ,terraform 有提供 module depends_on 的 meta-argument,让 .tf 中可以描述 module 与 module 之间的关系

terragrunt 中提供的 dependency,进一步将这层依赖性,推广到 root module 之间也能建立依赖性,Terragrunt modules dependency,具体是有什麽差异:

  • 在 terragrunt.hcl 中 dependency{} 宣吿有依赖别的 module,并把 config_path 指向
    • Review: 这边使用 terragrunt built-in function find_in_parent_folders 来寻找
  • 在 inputs = {} 中,使用 dependency 的 attibute 来取得 azure/foundation/compute_network module 中的 output
    • 使用 output 来作为 input,vnet_subnet_id
    • 前提是 azure/foundation/compute_network module 要能 output 需要的值

建立起这层依赖後对於 terragrunt 工作流程的影响是

  • plan chechia_net/compute 前,会先去取得 foundation/compute_network state 中的 output
    • 一方面确认 network 的状态
    • 也将 output 跨越 root module 传递
  • 如果取得 foundation/compute_network state 中有发现问题,则 chechia_net/compute 的 plan 与 apply 会终止
    • 这符合预期,上游依赖的服务有问题,依赖的服务再加上去往往无法正常运作

Pros & Cons

Pros

  • 明确宣告 module 之间的依赖性
  • 动态取得其他 root module 的最新参数,而不是 hard-code 在 input
    • 对於可能远端变动的 output 非常好用

Cons

  • 运算每次 plan 前要先去取得其他 module 的 state,数量多时 plan 就会拖慢
  • 提升稳固性,限制人为更改的程度,某方面也牺牲改动的弹性

实务建议:dependency 请适量使用,不用全部有关的依赖都套用上去,也不要都不用

  • 重要的核心依赖(例如:一出问题底下就爆炸的)依赖可以加在 dependency 中,每次 apply 多做检查
  • 不重要但是相关的依赖,可以直接使用 hard-code 写在 inputs 中
    • 虽然违反 clean code,但却维持住 root module 间的 loose coupling,彼此改动不会被限制住

References


<<:  Day-1 简介与参赛动机

>>:  Day 4 - TiDB架构说明

Day16 - 完成爬虫功能

在完成基础的表单画面後,接着需要将之前完成的爬虫功能整合至网站。 考量功能的独立性、扩充性和使用便利...

Day 17: 人工智慧在音乐领域的应用 (AI作曲-基因演算法一)

我们在Day 7的时候曾经介绍过基於达尔文物竞天择适者生存的演化式计算,那麽今天开始我们就来详细的介...

Day7 最近邻居法(K-Nearest Neighbors)

最近邻居法是什麽? 简称KNN,讲人话就是在现有历史资料的基础上,对於想预测的新资料,直接比对特徵最...

用 Python 畅玩 Line bot - 10:File message, Location message

File message 应用 如果想写一个分析使用者传送的文章或是文件档的 Line bot,可以...

[Day17]-应用模组2

时间time模组 使用前要先import time Time()可以传回自1970/1/1以来的秒...