init: copied modules from lawndale-infra

This commit is contained in:
2022-05-26 00:40:29 +02:00
commit 414feb48ee
39 changed files with 1435 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
output "namespace" {
value = var.namespace
}
output "host_path" {
value = local.host_path
}
output "pv_name" {
value = kubernetes_persistent_volume.this.metadata.0.name
}
output "pvc_name" {
value = kubernetes_persistent_volume_claim.this.metadata.0.name
}
output "persistent_volume" {
value = kubernetes_persistent_volume.this
}
output "persistent_volume_claim" {
value = kubernetes_persistent_volume_claim.this
}

View File

@@ -0,0 +1,9 @@
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.11"
}
}
}

View File

@@ -0,0 +1,15 @@
variable "name" {
type = string
description = "The name of the volume"
}
variable "namespace" {
type = string
description = "The namespace for the persistent volume claim"
}
variable "volume_storage_capacity" {
type = string
description = "Size of the persistent volume reported to Kubernetes"
default = "1Gi"
}

View File

@@ -0,0 +1,37 @@
locals {
pv_name = "pv-p9hostpath-${var.name}"
host_path = "/mnt/datastore/${var.name}"
}
resource "kubernetes_persistent_volume" "this" {
metadata {
name = local.pv_name
}
spec {
capacity = {
storage = var.volume_storage_capacity
}
access_modes = ["ReadWriteMany"]
persistent_volume_source {
host_path {
path = local.host_path
}
}
}
}
resource "kubernetes_persistent_volume_claim" "this" {
metadata {
name = var.name
namespace = var.namespace
}
spec {
access_modes = ["ReadWriteMany"]
resources {
requests = {
storage = var.volume_storage_capacity
}
}
volume_name = kubernetes_persistent_volume.this.metadata.0.name
}
}

View File

@@ -0,0 +1,39 @@
resource "kubernetes_config_map" "this" {
metadata {
namespace = var.namespace
name = "kube-flannel-cfg"
labels = {
app = "flannel"
"k8s-app" = "cni"
}
}
data = {
"cni-conf.json" = jsonencode({
name = "cbr0",
cniVersion = "0.3.1",
plugins = [
{
type = "flannel",
delegate = {
hairpinMode = true,
isDefaultGateway = true,
}
},
{
type = "portmap",
capabilities = {
portMappings = true,
}
},
]
})
"net-conf.json" = jsonencode({
Network = var.cluster_cidr,
Backend = {
Type = "vxlan",
VNI = var.vxlan_id,
Port = var.vxlan_port,
}
})
}
}

View File

@@ -0,0 +1,167 @@
resource "kubernetes_daemonset" "this" {
metadata {
name = var.daemonset_name
namespace = var.namespace
labels = {
app = var.daemonset_name
"k8s-app" = "cni"
}
}
spec {
selector {
match_labels = {
app = var.daemonset_name
"k8s-app" = "cni"
}
}
template {
metadata {
labels = {
app = var.daemonset_name
"k8s-app" = "cni"
}
}
spec {
affinity {
node_affinity {
required_during_scheduling_ignored_during_execution {
node_selector_term {
match_expressions {
key = "kubernetes.io/os"
operator = "In"
values = var.node_selector_os
}
}
}
}
}
host_network = true
priority_class_name = "system-node-critical"
toleration {
operator = "Exists"
effect = "NoSchedule"
}
service_account_name = kubernetes_service_account.this.metadata.0.name
init_container {
name = "install-cni-plugin"
image = var.flannel_cni_plugin_image
command = ["cp"]
args = ["-f", "/flannel", "/opt/cni/bin/flannel"]
volume_mount {
name = "cni-plugin"
mount_path = "/opt/cni/bin"
}
}
init_container {
name = "install-cni"
image = var.flannel_image
command = ["cp"]
args = ["-f", "/etc/kube-flannel/cni-conf.json", "/etc/cni/net.d/10-flannel.conflist"]
volume_mount {
name = "cni"
mount_path = "/etc/cni/net.d"
}
volume_mount {
name = "flannel-cfg"
mount_path = "/etc/kube-flannel/"
}
}
container {
name = "kube-flannel"
image = var.flannel_image
command = ["/opt/bin/flanneld"]
args = ["--ip-masq", "--kube-subnet-mgr"]
resources {
requests = {
cpu = "100m"
memory = "50Mi"
}
limits = {
cpu = "100m"
memory = "50Mi"
}
}
security_context {
privileged = false
capabilities {
add = ["NET_ADMIN", "NET_RAW"]
}
}
env {
name = "POD_NAME"
value_from {
field_ref {
field_path = "metadata.name"
}
}
}
env {
name = "POD_NAMESPACE"
value_from {
field_ref {
field_path = "metadata.namespace"
}
}
}
env {
name = "EVENT_QUEUE_DEPTH"
value = "5000"
}
volume_mount {
name = "run"
mount_path = "/run/flannel"
}
volume_mount {
name = "flannel-cfg"
mount_path = "/etc/kube-flannel/"
}
volume_mount {
name = "xtables-lock"
mount_path = "/run/xtables.lock"
}
}
volume {
name = "run"
host_path {
path = "/run/flannel"
}
}
volume {
name = "cni-plugin"
host_path {
path = "/opt/cni/bin"
}
}
volume {
name = "cni"
host_path {
path = "/etc/cni/net.d"
}
}
volume {
name = "flannel-cfg"
config_map {
name = kubernetes_config_map.this.metadata.0.name
}
}
volume {
name = "xtables-lock"
host_path {
path = "/run/xtables.lock"
type = "FileOrCreate"
}
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
output "service_account" {
value = kubernetes_service_account.this
}
output "cluster_role" {
value = kubernetes_cluster_role.this
}
output "cluster_role_binding" {
value = kubernetes_cluster_role_binding.this
}
output "pod_security_policy" {
value = kubernetes_pod_security_policy.this
}
output "daemonset" {
value = kubernetes_daemonset.this
}
output "configmap" {
value = kubernetes_config_map.this
}

View File

@@ -0,0 +1,65 @@
resource "kubernetes_pod_security_policy" "this" {
metadata {
name = "psp.flannel.unprivileged"
annotations = {
"seccomp.security.alpha.kubernetes.io/allowedProfileNames" : "docker/default",
"seccomp.security.alpha.kubernetes.io/defaultProfileName" : "docker/default",
"apparmor.security.beta.kubernetes.io/allowedProfileNames" : "runtime/default",
"apparmor.security.beta.kubernetes.io/defaultProfileName" : "runtime/default",
}
}
spec {
# Privilege Escalation
allow_privilege_escalation = false
default_allow_privilege_escalation = false
privileged = false
volumes = [
"configMap",
"secret",
"emptyDir",
"hostPath",
]
allowed_host_paths {
path_prefix = "/etc/cni/net.d"
}
allowed_host_paths {
path_prefix = "/etc/kube-flannel"
}
allowed_host_paths {
path_prefix = "/run/flannel"
}
read_only_root_filesystem = false
# Users and groups
run_as_user {
rule = "MustRunAsNonRoot"
}
supplemental_groups {
rule = "RunAsAny"
}
fs_group {
rule = "RunAsAny"
}
# Capabilities
allowed_capabilities = ["NET_ADMIN", "NET_RAW"]
default_add_capabilities = []
required_drop_capabilities = []
# Host namespaces
host_pid = false
host_ipc = false
host_network = true
host_ports {
min = 0
max = 65535
}
# SELinux
se_linux {
# SELinux is unused in CaaSP
rule = "RunAsAny"
}
}
}

View File

@@ -0,0 +1,8 @@
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.11"
}
}
}

View File

@@ -0,0 +1,52 @@
resource "kubernetes_cluster_role" "this" {
count = var.create_cluster_role ? 1 : 0
metadata {
name = var.cluster_role_name
}
rule {
api_groups = ["extensions"]
resources = ["podsecuritypolicies"]
verbs = ["use"]
resource_names = [kubernetes_pod_security_policy.this.metadata.0.name]
}
rule {
api_groups = [""]
resources = ["pods"]
verbs = ["get"]
}
rule {
api_groups = [""]
resources = ["nodes"]
verbs = ["list", "watch"]
}
rule {
api_groups = [""]
resources = ["nodes/status"]
verbs = ["patch"]
}
}
resource "kubernetes_service_account" "this" {
metadata {
name = "flannel"
namespace = var.namespace
}
}
resource "kubernetes_cluster_role_binding" "this" {
metadata {
name = "flannel"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = var.cluster_role_name
}
subject {
kind = "ServiceAccount"
name = kubernetes_service_account.this.metadata.0.name
namespace = kubernetes_service_account.this.metadata.0.namespace
}
}

View File

@@ -0,0 +1,52 @@
variable "namespace" {
type = string
description = "namespace for resources"
default = "kube-system"
}
variable "vxlan_id" {
type = number
description = "VXLAN ID (VNI)"
}
variable "vxlan_port" {
type = number
description = "UDP port to use for sending encapsulated packets"
}
variable "flannel_image" {
type = string
default = "rancher/mirrored-flannelcni-flannel:v0.17.0"
}
variable "flannel_cni_plugin_image" {
type = string
default = "rancher/mirrored-flannelcni-flannel-cni-plugin:v1.0.1"
}
variable "cluster_role_name" {
type = string
description = "Name of the cluster role flannel will use"
default = "flannel"
}
variable "create_cluster_role" {
type = bool
description = "Weather or not to create a suitable cluster role or use an already exisiting specified by cluster_role_name variable"
default = true
}
variable "daemonset_name" {
type = string
description = "Name of the daeomonset"
default = "flannel"
}
variable "node_selector_os" {
type = list(string)
description = "Which kubernetes.io/os node-label to match for execution"
default = ["linux"]
}
variable "cluster_cidr" {
type = string
description = "ClusterCIDR"
}

View File

@@ -0,0 +1,26 @@
resource "kubernetes_config_map" "this" {
metadata {
name = "${var.daemonset_name}-config"
namespace = var.namespace
}
data = {
"kube-proxy-config.yaml" = yamlencode(merge({
kind = "KubeProxyConfiguration"
apiVersion = "kubeproxy.config.k8s.io/v1alpha1"
clientConnection = {
kubeconfig = "/kubeconfig/kubeconfig"
}
mode = var.mode
clusterCIDR = var.cluster_cidr
}, var.additional_config))
"kubeconfig.sh" : <<EOM
#!/bin/sh
kubectl --kubeconfig=/kubeconfig/kubeconfig config set-cluster default --certificate-authority=/run/secrets/kubernetes.io/serviceaccount/ca.crt --server=${var.kubernetes_server}
kubectl --kubeconfig=/kubeconfig/kubeconfig config set-credentials kube-proxy --token=$(cat /run/secrets/kubernetes.io/serviceaccount/token)
kubectl --kubeconfig=/kubeconfig/kubeconfig config set-context default --cluster=default --user=kube-proxy
kubectl --kubeconfig=/kubeconfig/kubeconfig config use-context default
EOM
}
}

View File

@@ -0,0 +1,145 @@
resource "kubernetes_daemonset" "this" {
metadata {
name = var.daemonset_name
namespace = var.namespace
labels = {
"k8s-app" = var.daemonset_name
}
}
spec {
selector {
match_labels = {
"k8s-app" = var.daemonset_name
}
}
template {
metadata {
labels = {
"k8s-app" = var.daemonset_name
"prometheus.io/scrape" = "true"
"prometheus.io/port" = "10249"
"prometheus.io/scheme" = "http"
}
}
spec {
affinity {
node_affinity {
required_during_scheduling_ignored_during_execution {
node_selector_term {
match_expressions {
key = "kubernetes.io/os"
operator = "In"
values = ["linux"]
}
}
}
}
}
host_network = true
priority_class_name = "system-node-critical"
service_account_name = kubernetes_service_account.this.metadata.0.name
toleration {
operator = "Exists"
effect = "NoSchedule"
}
volume {
name = "kube-proxy-config"
config_map {
name = kubernetes_config_map.this.metadata.0.name
}
}
volume {
name = "kubeconfig"
empty_dir {
medium = "Memory"
}
}
volume {
name = "lib-modules"
host_path {
path = "/lib/modules"
}
}
init_container {
name = "kubeconfig"
image = "bitnami/kubectl:${var.kubernetes_version}"
command = ["/bin/bash"]
args = ["/kubeconfig.sh"]
volume_mount {
name = "kube-proxy-config"
mount_path = "/kubeconfig.sh"
sub_path = "kubeconfig.sh"
}
volume_mount {
name = "kubeconfig"
mount_path = "/kubeconfig"
}
}
container {
security_context {
privileged = true
}
image = "k8s.gcr.io/kube-proxy:v${var.kubernetes_version}"
command = ["kube-proxy"]
args = [
"--config=/var/lib/kube-proxy/kube-proxy-config.yaml",
]
name = "kube-proxy"
resources {
limits = {
cpu = "100m"
memory = "50Mi"
}
requests = {
cpu = "100m"
memory = "50Mi"
}
}
volume_mount {
name = "kube-proxy-config"
mount_path = "/var/lib/kube-proxy"
}
volume_mount {
name = "lib-modules"
mount_path = "/lib/modules"
}
volume_mount {
name = "kubeconfig"
mount_path = "/kubeconfig"
}
# liveness_probe {
# http_get {
# path = "/"
# port = 80
# http_header {
# name = "X-Custom-Header"
# value = "Awesome"
# }
# }
# initial_delay_seconds = 3
# period_seconds = 3
# }
}
}
}
}
}

View File

@@ -0,0 +1,13 @@
output "daemonset" {
value = kubernetes_daemonset.this
}
output "configmap" {
value = kubernetes_config_map.this
}
output "service_account" {
value = kubernetes_service_account.this
}
output "cluster_role_binding" {
value = kubernetes_cluster_role_binding.this
}

View File

@@ -0,0 +1,8 @@
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.11"
}
}
}

View File

@@ -0,0 +1,22 @@
resource "kubernetes_service_account" "this" {
metadata {
name = "kube-proxy"
namespace = "kube-system"
}
}
resource "kubernetes_cluster_role_binding" "this" {
metadata {
name = "kube-proxy-is-system-node-proxier"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = "system:node-proxier"
}
subject {
kind = "ServiceAccount"
name = kubernetes_service_account.this.metadata.0.name
namespace = "kube-system"
}
}

View File

@@ -0,0 +1,17 @@
resource "kubernetes_service" "prometheus" {
metadata {
name = var.daemonset_name
namespace = var.namespace
}
spec {
selector = {
"k8s-app" = var.daemonset_name
}
port {
port = 10249
target_port = 10249
}
type = "ClusterIP"
}
}

View File

@@ -0,0 +1,34 @@
variable "kubernetes_version" {
type = string
description = "Kubernetes cluster version (eg: 1.23.5)"
}
variable "cluster_cidr" {
type = string
description = "Kubernetes cluster CIDR"
}
variable "kubernetes_server" {
type = string
description = "Kubernetes (master) server address (eg: https://k8s.my.domain:6443/)"
}
variable "mode" {
type = string
default = "iptables"
}
variable "daemonset_name" {
type = string
default = "kube-proxy"
}
variable "additional_config" {
type = map(any)
default = {}
}
variable "namespace" {
type = string
default = "kube-system"
}

13
lawndale-vm-ipam/dns.tf Normal file
View File

@@ -0,0 +1,13 @@
resource "dns_a_record_set" "this" {
zone = "lawndale."
name = "${var.name}.${var.interface}"
addresses = [local.ip_address]
ttl = var.ttl
}
resource "dns_ptr_record" "this" {
zone = "168.192.in-addr.arpa."
name = local.ptr_name
ptr = "${var.name}.${var.interface}.lawndale."
ttl = var.ttl
}

View File

@@ -0,0 +1,18 @@
locals {
interface_ip_map = {
"internal" = "192.168.254"
"nat" = "192.168.253"
}
interface_ptr_map = {
"internal" = "254"
"nat" = "253"
}
ip_address = "${local.interface_ip_map[var.interface]}.${var.id}"
ptr_name = "${var.id}.${local.interface_ip_map[var.interface]}"
lawndale_interface_map = {
"internal" = "brInternal"
"nat" = "brNAT"
}
}

View File

@@ -0,0 +1,35 @@
output "ip_address" {
value = local.ip_address
}
output "fqdn" {
value = "${dns_a_record_set.this.name}.${dns_a_record_set.this.zone}"
}
output "interface" {
value = var.interface
}
output "name" {
value = var.name
}
output "gateway" {
value = "${local.interface_ip_map[var.interface]}.254"
}
output "nameserver" {
value = "${local.interface_ip_map[var.interface]}.254"
}
output "search_domains" {
value = ["${var.interface}.lawndale.", "lawndale."]
}
output "cidr" {
value = 24
}
output "lawndale_interface" {
value = local.lawndale_interface_map[var.interface]
}

View File

@@ -0,0 +1,8 @@
terraform {
required_providers {
dns = {
source = "hashicorp/dns"
version = "~> 3.2"
}
}
}

View File

@@ -0,0 +1,21 @@
variable "id" {
type = number
description = "The ID for the VM that will be transposed into an IP address"
}
variable "name" {
type = string
description = "The domain name for the vm"
}
variable "interface" {
type = string
description = "Which interface should be used. Can be either nat or internal"
}
variable "ttl" {
type = number
description = "TTL value for the new records"
default = 300
}

119
lawndale-vm/compute.tf Normal file
View File

@@ -0,0 +1,119 @@
resource "libvirt_pool" "this" {
count = var.create_root_storage_pool ? 1 : 0
name = local.root_storage_pool
type = "dir"
path = "/vmstore/${var.name}"
}
resource "libvirt_volume" "this" {
name = local.root_storage_volume_name
pool = local.root_storage_pool
size = var.root_storage_volume_size_gb * 1024 * 1024 * 1024
base_volume_name = var.base_image_volume
base_volume_pool = var.base_image_pool
}
resource "macaddress" "this" {
}
module "ipam" {
source = "../../modules/lawndale-vm-ipam"
id = var.id
name = var.name
interface = var.interface
}
resource "libvirt_domain" "this" {
name = "${var.id}-${var.name}"
description = var.description
vcpu = var.vcpu
memory = var.memory_mb
autostart = var.autostart
cloudinit = libvirt_cloudinit_disk.this.id
network_interface {
bridge = module.ipam.lawndale_interface
mac = macaddress.this.address
}
disk {
volume_id = libvirt_volume.this.id
scsi = true
}
# video { type = "virtio" }
# graphics {}
console {
type = "pty"
target_port = 0
target_type = "serial"
}
qemu_agent = true
dynamic "filesystem" {
for_each = var.filesystems
content {
source = filesystem.value.source
target = filesystem.value.target
readonly = filesystem.value.readonly
accessmode = filesystem.value.accessmode
}
}
dynamic "xml" {
for_each = var.xslt == null ? [] : [var.xslt]
content {
xslt = xml.value
}
}
}
resource "libvirt_cloudinit_disk" "this" {
name = "${var.id}-${var.name}-cloudinit"
pool = local.root_storage_pool
meta_data = local.meta_data
network_config = local.network_config
user_data = var.user_data
}
locals {
default_network_config = {
version = 2
ethernets = {
eth = {
match = {
macaddress = macaddress.this.address
}
addresses = [
"${module.ipam.ip_address}/${module.ipam.cidr}"
]
gateway4 = module.ipam.gateway
nameservers = {
search = module.ipam.search_domains
addresses = [
module.ipam.nameserver
]
}
}
}
}
default_meta_data = <<EOM
instance-id: ${var.id}-${var.name}
local-hostname: ${var.name}
EOM
meta_data = var.meta_data != null ? var.meta_data : local.default_meta_data
network_config = var.network_config != null ? var.network_config : jsonencode(local.default_network_config)
}

4
lawndale-vm/locals.tf Normal file
View File

@@ -0,0 +1,4 @@
locals {
root_storage_pool = var.root_storage_pool != "" ? var.root_storage_pool : var.name
root_storage_volume_name = var.root_storage_volume_name != "" ? "${var.root_storage_volume_name}.qcow2" : "${var.name}.qcow2"
}

0
lawndale-vm/outputs.tf Normal file
View File

13
lawndale-vm/providers.tf Normal file
View File

@@ -0,0 +1,13 @@
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "~> 0.6.14"
}
macaddress = {
source = "ivoronin/macaddress"
version = "~> 0.3.0"
}
}
}

112
lawndale-vm/variables.tf Normal file
View File

@@ -0,0 +1,112 @@
variable "id" {
type = number
description = "The lawndale id of the virtual machine"
}
variable "name" {
type = string
description = "The name of the virtual machine (must be a [-_a-z0-9])"
validation {
condition = can(regex("^[-_a-z0-9]+$", var.name))
error_message = "A virtual machine name must be lowercase, and can only contain alphanumeral characters, dashes and underscores."
}
}
variable "vcpu" {
type = number
description = "CPU count"
default = 1
}
variable "memory_mb" {
type = number
description = "VM memory allocation in megabytes"
}
variable "description" {
type = string
description = "(Short) Description for the virtual machine"
}
variable "base_image_pool" {
type = string
description = "Base image storage pool"
}
variable "base_image_volume" {
type = string
description = "Base image storage pool"
}
variable "root_storage_pool" {
type = string
description = "The name of the storage pool. It will default to the VM name"
default = ""
}
variable "create_root_storage_pool" {
type = bool
description = "Create the storage pool as part of the module"
default = true
}
variable "root_storage_volume_size_gb" {
type = number
description = "The size of the storage volume (in gigabytes)"
}
variable "root_storage_volume_name" {
type = string
description = "the name of the storage volume (must be unique in the pool)"
default = ""
}
variable "interface" {
type = string
description = "Network interface to attach the vm on"
}
variable "autostart" {
type = bool
description = "Start the VM at host start?"
default = true
}
variable "user_data" {
type = string
description = "Cloud-init userdata script to run"
}
variable "network_config" {
type = string
description = "Cloud-init network config"
default = null
nullable = true
}
variable "meta_data" {
type = string
description = "Cloud-init meta-data"
default = null
nullable = true
}
variable "filesystems" {
type = list(object({
source = string
target = string
accessmode = string
readonly = bool
}))
description = "9p shared filesystem devices"
default = []
}
variable "xslt" {
type = string
description = "XSLT applied to the domain before sent to libvirt"
default = null
}

View File

@@ -0,0 +1,9 @@
output "role_arn" {
type = string
value = var.create_role ? aws_iam_role.this.arn : null
}
output "policy_arn" {
type = string
value = var.create_policy ? aws_iam_policy.this.arn : null
}

View File

@@ -0,0 +1,59 @@
data "aws_iam_policy_document" "this" {
statement {
effect = "Allow"
actions = [
"s3:ListBucket"
]
resources = [
"arn:aws:s3:::${var.bucket_name}",
]
}
statement {
effect = "Allow"
actions = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
]
resources = [
"arn:aws:s3:::${var.bucket_name}",
]
condition {
test = "StringLike"
variable = "s3:prefix"
values = var.prefixes
}
}
statement {
effect = "Allow"
actions = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
]
resources = [
"arn:aws:dynamodb:*:*:table/${var.dynamodb_table}",
]
}
}
resource "aws_iam_role" "this" {
count = var.create_role ? 1 : 0
name = var.role_name
inline_policy {
name = "Allow access for remote states s3 and dynamo"
policy = data.aws_iam_policy_document.this.json
}
}
resource "aws_iam_policy" "this" {
count = var.create_policy ? 1 : 0
name = var.policy_name
path = var.policy_path
}

View File

@@ -0,0 +1,8 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.9"
}
}
}

View File

@@ -0,0 +1,35 @@
variable "bucket_name" {
type = string
}
variable "dynamo_table" {
type = string
}
variable "prefixes" {
type = list(string)
}
variable "role_name" {
type = string
default = null
}
variable "policy_name" {
type = string
default = null
}
variable "policy_path" {
type = string
default = "/"
}
variable "create_role" {
type = bool
default = false
}
variable "create_policy" {
type = bool
default = false
}

55
remote-state/bucket.tf Normal file
View File

@@ -0,0 +1,55 @@
resource "aws_kms_key" "this" {
description = "Enryption key for S3 remote terraform state"
deletion_window_in_days = 30
}
data "aws_iam_policy_document" "force_secure_transport" {
statement {
sid = "ForceSecureTransport"
actions = ["s3:*"]
effect = "Deny"
resources = [
module.states_bucket.s3_bucket_arn,
"${module.states_bucket.s3_bucket_arn}/*"
]
condition {
test = "Bool"
variable = "aws:SecureTransport"
values = ["false"]
}
principals {
type = "*"
identifiers = ["*"]
}
}
}
module "states_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
bucket = local.bucket_name
versioning = {
enabled = true
}
server_side_encryption_configuration = {
rule = {
apply_server_side_encryption_by_default = {
kms_master_key_id = aws_kms_key.this.arn
sse_algorithm = "aws:kms"
}
}
}
acl = "private"
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
attach_policy = true
policy = data.aws_iam_policy_document.force_secure_transport.json
create_bucket = true
}

23
remote-state/dynamo.tf Normal file
View File

@@ -0,0 +1,23 @@
locals {
dynamodb_lock_key_id = "LockID"
}
resource "aws_dynamodb_table" "lock" {
name = local.table_name
hash_key = local.dynamodb_lock_key_id
billing_mode = var.table_billing_mode
write_capacity = var.table_write_capacity
read_capacity = var.table_read_capacity
attribute {
name = local.dynamodb_lock_key_id
type = "S"
}
server_side_encryption {
enabled = var.table_encryption_at_rest
kms_key_arn = aws_kms_key.this.arn
}
}

14
remote-state/locals.tf Normal file
View File

@@ -0,0 +1,14 @@
data "aws_region" "current" {}
locals {
region_name = data.aws_region.current.name
default_bucket_name = try("${var.name_prefix}-${local.region_name}-states", "")
bucket_name = var.bucket_name_override != null ? var.bucket_name_override : local.default_bucket_name
default_table_name = try("${var.name_prefix}-${local.region_name}-tf-state-locks", "")
table_name = var.table_name_override != null ? var.table_name_override : local.default_table_name
default_terraform_iam_policy_name = "terraforming-${local.table_name}"
terraform_iam_policy_name = local.default_terraform_iam_policy_name
}

19
remote-state/outputs.tf Normal file
View File

@@ -0,0 +1,19 @@
output "kms_key" {
value = aws_kms_key.this
}
output "lock_table" {
value = aws_dynamodb_table.lock
}
output "terraform_policy" {
value = aws_iam_policy.terraform
}
output "bucket_name" {
value = local.bucket_name
}
output "lock_table_name" {
value = local.table_name
}

48
remote-state/policy.tf Normal file
View File

@@ -0,0 +1,48 @@
data "aws_iam_policy_document" "access_state" {
statement {
effect = "Allow"
actions = ["s3:ListBucket", "s3:GetBucketVersioning"]
resources = [module.states_bucket.s3_bucket_arn]
}
statement {
effect = "Allow"
actions = ["s3:GetObject", "s3:PutObject"]
resources = ["${module.states_bucket.s3_bucket_arn}/*"]
}
statement {
effect = "Allow"
actions = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:DescribeTable",
]
resources = [aws_dynamodb_table.lock.arn]
}
statement {
effect = "Allow"
actions = [
"kms:ListKeys"
]
resources = ["*"]
}
statement {
effect = "Allow"
actions = [
"kms:Encrypt",
"kms:Decrypt",
"kms:DescribeKey",
"kms:GenerateDataKey",
]
resources = [aws_kms_key.this.arn]
}
}
resource "aws_iam_policy" "terraform" {
name = local.terraform_iam_policy_name
policy = data.aws_iam_policy_document.access_state.json
}

8
remote-state/provider.tf Normal file
View File

@@ -0,0 +1,8 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.9"
}
}
}

59
remote-state/variables.tf Normal file
View File

@@ -0,0 +1,59 @@
## Naming
variable "name_prefix" {
description = "Resource names prefixed by this string."
type = string
nullable = true
default = null
}
## Bucket options
variable "bucket_name_override" {
description = "Explicit name for the remote state bucket. If not specified, the bucket will be named as {name_prefix}-{region_name}-states"
type = string
nullable = true
default = null
}
## DynamoDB Table for locks
variable "table_name_override" {
description = "Explicit name for the remote state lock DynamoDB table. If not specified, the table will be named as {name_prefix}-{region_name}-tf-state-locks"
type = string
nullable = true
default = null
}
variable "table_encryption_at_rest" {
description = "Wheather or not apply encryption at rest for the DynamoDB."
type = bool
default = false
}
variable "table_billing_mode" {
description = "Table billing mode. Can be PAY_PER_REQUEST or PROVISIONED"
type = string
default = "PAY_PER_REQUEST"
}
variable "table_write_capacity" {
description = "(Optional) The number of write units for the lock table. If the billing_mode is PROVISIONED, this field is required"
type = number
default = 0
}
variable "table_read_capacity" {
description = "(Optional) The number of read units for this table. If the billing_mode is PROVISIONED, this field is required"
type = number
default = 0
}