terraform-provider: implement constellation_cluster resource (#2691)

* terraform: move module to legacy-directory

* constellation-lib: refactor service account marshalling

* terraform-provider: normalize Azure image URIs

* constellation-lib: refactor Kubeconfig endpoint rewriting

* terraform-provider: add conversion functions for AWS and GCP

* terraform-provider: implement `constellation_cluster` resource

* terraform-provider: refactor conversion

* terraform-provider: implement image and k8s upgrades

* terraform-provider: fix linter checks

* terraform-provider: refactor to bundle init & upgrade method

* constellation-lib: rewrite Kubeconfig endpoint in init

* terraform-provider: bind logger and dialer constructors to struct

* terraform-provider: move applier to function pointer

* terraform-provider: gcp conversion fixes

Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>

* terraform-provider: fix Azure UAMI input

* terraform-provider: rename Kubeconfig variable

* terraform-provider: tidy

* terraform-provider: regenerate docs

* constellation-lib: provide Kubeconfig in testing initserver

---------

Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>
This commit is contained in:
Moritz Sanft 2023-12-11 15:55:44 +01:00 committed by GitHub
parent 767bac4766
commit 60fc73e0e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1469 additions and 323 deletions

View file

@ -0,0 +1,67 @@
locals {
region = substr(var.zone, 0, length(var.zone) - 1)
}
module "aws_iam" {
source = "../../infrastructure/iam/aws"
name_prefix = var.name_prefix
region = local.region
}
resource "null_resource" "ensure_yq" {
provisioner "local-exec" {
command = <<EOT
../common/install-yq.sh
EOT
}
triggers = {
always_run = timestamp()
}
}
module "fetch_image" {
source = "../common/fetch-image"
csp = "aws"
attestation_variant = var.enable_snp ? "aws-sev-snp" : "aws-nitro-tpm"
region = local.region
image = var.image
depends_on = [module.aws_iam, null_resource.ensure_yq]
}
module "aws" {
source = "../../infrastructure/aws"
name = var.name
node_groups = var.node_groups
iam_instance_profile_worker_nodes = module.aws_iam.worker_nodes_instance_profile
iam_instance_profile_control_plane = module.aws_iam.control_plane_instance_profile
ami = module.fetch_image.image
region = local.region
zone = var.zone
debug = var.debug
enable_snp = var.enable_snp
custom_endpoint = var.custom_endpoint
}
module "constellation" {
source = "../constellation-cluster"
csp = "aws"
debug = var.debug
name = var.name
image = var.image
microservice_version = var.microservice_version
kubernetes_version = var.kubernetes_version
uid = module.aws.uid
clusterEndpoint = module.aws.out_of_cluster_endpoint
inClusterEndpoint = module.aws.in_cluster_endpoint
initSecretHash = module.aws.initSecret
ipCidrNode = module.aws.ip_cidr_nodes
apiServerCertSANs = module.aws.api_server_cert_sans
node_groups = var.node_groups
aws_config = {
region = local.region
zone = var.zone
iam_instance_profile_worker_nodes = module.aws_iam.worker_nodes_instance_profile
iam_instance_profile_control_plane = module.aws_iam.control_plane_instance_profile
}
depends_on = [module.aws, null_resource.ensure_yq]
}

View file

@ -0,0 +1,72 @@
variable "name" {
type = string
description = "Name of the Constellation cluster."
}
variable "image" {
type = string
description = "Node image reference or semantic release version. When not set, the latest default version will be used."
default = "@@CONSTELLATION_VERSION@@"
}
variable "microservice_version" {
type = string
description = "Microservice version. When not set, the latest default version will be used."
default = ""
}
variable "kubernetes_version" {
type = string
description = "Kubernetes version. When not set, the latest default version will be used."
default = ""
}
variable "node_groups" {
type = map(object({
role = string
initial_count = optional(number)
instance_type = string
disk_size = number
disk_type = string
zone = string
}))
description = "A map of node group names to node group configurations."
validation {
condition = can([for group in var.node_groups : group.role == "control-plane" || group.role == "worker"])
error_message = "The role has to be 'control-plane' or 'worker'."
}
}
variable "zone" {
type = string
description = "The AWS availability zone name to create the cluster in."
}
variable "debug" {
type = bool
default = false
description = "DON'T USE IN PRODUCTION: Enable debug mode and allow the use of debug images."
}
variable "enable_snp" {
type = bool
default = true
description = "Enable AMD SEV-SNP."
}
variable "custom_endpoint" {
type = string
default = ""
description = "Custom endpoint (DNS Name) to use for the Constellation API server. If not set, the default endpoint will be used."
}
variable "internal_load_balancer" {
type = bool
default = false
description = "Use an internal load balancer."
}
variable "name_prefix" {
type = string
description = "Prefix for all resources."
}

View file

@ -0,0 +1,67 @@
resource "null_resource" "ensure_yq" {
provisioner "local-exec" {
command = <<EOT
../common/install-yq.sh
EOT
}
triggers = {
always_run = timestamp()
}
}
module "fetch_image" {
source = "../common/fetch-image"
csp = "azure"
attestation_variant = "azure-sev-snp"
image = var.image
depends_on = [null_resource.ensure_yq]
}
module "azure_iam" {
source = "../../infrastructure/iam/azure"
region = var.location
service_principal_name = var.service_principal_name
resource_group_name = var.resource_group_name
}
module "azure" {
source = "../../infrastructure/azure"
name = var.name
user_assigned_identity = module.azure_iam.uami_id
node_groups = var.node_groups
location = var.location
image_id = module.fetch_image.image
debug = var.debug
resource_group = module.azure_iam.base_resource_group
create_maa = var.create_maa
}
module "constellation" {
source = "../constellation-cluster"
csp = "azure"
debug = var.debug
name = var.name
image = var.image
microservice_version = var.microservice_version
kubernetes_version = var.kubernetes_version
uid = module.azure.uid
clusterEndpoint = module.azure.out_of_cluster_endpoint
inClusterEndpoint = module.azure.in_cluster_endpoint
initSecretHash = module.azure.initSecret
ipCidrNode = module.azure.ip_cidr_nodes
apiServerCertSANs = module.azure.api_server_cert_sans
node_groups = var.node_groups
azure_config = {
subscription = module.azure_iam.subscription_id
tenant = module.azure_iam.tenant_id
location = var.location
resourceGroup = module.azure.resource_group
userAssignedIdentity = module.azure_iam.uami_id
deployCSIDriver = var.deploy_csi_driver
secureBoot = var.secure_boot
maaURL = module.azure.attestationURL
networkSecurityGroupName = module.azure.network_security_group_name
loadBalancerName = module.azure.loadbalancer_name
}
depends_on = [null_resource.ensure_yq]
}

View file

@ -0,0 +1,89 @@
variable "name" {
type = string
description = "Name of the Constellation cluster."
}
variable "image" {
type = string
description = "Node image reference or semantic release version. When not set, the latest default version will be used."
default = "@@CONSTELLATION_VERSION@@"
}
variable "microservice_version" {
type = string
description = "Microservice version. When not set, the latest default version will be used."
default = ""
}
variable "kubernetes_version" {
type = string
description = "Kubernetes version. When not set, the latest default version will be used."
default = ""
}
variable "debug" {
type = bool
default = false
description = "DON'T USE IN PRODUCTION: Enable debug mode and allow the use of debug images."
}
variable "custom_endpoint" {
type = string
default = ""
description = "Custom endpoint (DNS Name) to use for the Constellation API server. If not set, the default endpoint will be used."
}
variable "internal_load_balancer" {
type = bool
default = false
description = "Use an internal load balancer."
}
variable "node_groups" {
type = map(object({
role = string
initial_count = optional(number)
instance_type = string
disk_size = number
disk_type = string
zones = optional(list(string))
}))
description = "A map of node group names to node group configurations."
validation {
condition = can([for group in var.node_groups : group.role == "control-plane" || group.role == "worker"])
error_message = "The role has to be 'control-plane' or 'worker'."
}
}
variable "service_principal_name" {
type = string
description = "Name of the service principal used to create the cluster."
}
variable "resource_group_name" {
type = string
description = "Name of the resource group the cluster's resources are created in."
}
variable "location" {
type = string
description = "Azure datacenter region the cluster will be deployed in."
}
variable "deploy_csi_driver" {
type = bool
default = true
description = "Deploy the Azure Disk CSI driver with on-node encryption into the cluster."
}
variable "secure_boot" {
type = bool
default = false
description = "Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob."
}
variable "create_maa" {
type = bool
default = true
description = "Create an MAA for attestation."
}

View file

@ -0,0 +1,48 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/local" {
version = "2.4.0"
hashes = [
"h1:7RnIbO3CFakblTJs7o0mUiY44dc9xGYsLhSNFSNS1Ds=",
"h1:Bs7LAkV/iQTLv72j+cTMrvx2U3KyXrcVHaGbdns1NcE=",
"h1:OMqURhlP2IgLEgUCzSlaKXyb/IbnKForgDSjZF/NY4Y=",
"h1:R97FTYETo88sT2VHfMgkPU3lzCsZLunPftjSI5vfKe8=",
"h1:ZUEYUmm2t4vxwzxy1BvN1wL6SDWrDxfH7pxtzX8c6d0=",
"zh:53604cd29cb92538668fe09565c739358dc53ca56f9f11312b9d7de81e48fab9",
"zh:66a46e9c508716a1c98efbf793092f03d50049fa4a83cd6b2251e9a06aca2acf",
"zh:70a6f6a852dd83768d0778ce9817d81d4b3f073fab8fa570bff92dcb0824f732",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:82a803f2f484c8b766e2e9c32343e9c89b91997b9f8d2697f9f3837f62926b35",
"zh:9708a4e40d6cc4b8afd1352e5186e6e1502f6ae599867c120967aebe9d90ed04",
"zh:973f65ce0d67c585f4ec250c1e634c9b22d9c4288b484ee2a871d7fa1e317406",
"zh:c8fa0f98f9316e4cfef082aa9b785ba16e36ff754d6aba8b456dab9500e671c6",
"zh:cfa5342a5f5188b20db246c73ac823918c189468e1382cb3c48a9c0c08fc5bf7",
"zh:e0e2b477c7e899c63b06b38cd8684a893d834d6d0b5e9b033cedc06dd7ffe9e2",
"zh:f62d7d05ea1ee566f732505200ab38d94315a4add27947a60afa29860822d3fc",
"zh:fa7ce69dde358e172bd719014ad637634bbdabc49363104f4fca759b4b73f2ce",
]
}
provider "registry.terraform.io/hashicorp/null" {
version = "3.2.1"
hashes = [
"h1:FbGfc+muBsC17Ohy5g806iuI1hQc4SIexpYCrQHQd8w=",
"h1:tSj1mL6OQ8ILGqR2mDu7OYYYWf+hoir0pf9KAQ8IzO8=",
"h1:vUW21lLLsKlxtBf0QF7LKJreKxs0CM7YXGzqW1N/ODY=",
"h1:wqgRvlyVIbkCeCQs+5jj6zVuQL0KDxZZtNofGqqlSdI=",
"h1:ydA0/SNRVB1o95btfshvYsmxA+jZFRZcvKzZSB+4S1M=",
"zh:58ed64389620cc7b82f01332e27723856422820cfd302e304b5f6c3436fb9840",
"zh:62a5cc82c3b2ddef7ef3a6f2fedb7b9b3deff4ab7b414938b08e51d6e8be87cb",
"zh:63cff4de03af983175a7e37e52d4bd89d990be256b16b5c7f919aff5ad485aa5",
"zh:74cb22c6700e48486b7cabefa10b33b801dfcab56f1a6ac9b6624531f3d36ea3",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:79e553aff77f1cfa9012a2218b8238dd672ea5e1b2924775ac9ac24d2a75c238",
"zh:a1e06ddda0b5ac48f7e7c7d59e1ab5a4073bbcf876c73c0299e4610ed53859dc",
"zh:c37a97090f1a82222925d45d84483b2aa702ef7ab66532af6cbcfb567818b970",
"zh:e4453fbebf90c53ca3323a92e7ca0f9961427d2f0ce0d2b65523cc04d5d999c2",
"zh:e80a746921946d8b6761e77305b752ad188da60688cfd2059322875d363be5f5",
"zh:fbdb892d9822ed0e4cb60f2fedbdbb556e4da0d88d3b942ae963ed6ff091e48f",
"zh:fca01a623d90d0cad0843102f9b8b9fe0d3ff8244593bd817f126582b52dd694",
]
}

View file

@ -0,0 +1,38 @@
locals {
image_ref = startswith(var.image, "v") ? "ref/-/stream/stable/${var.image}" : var.image
region_filter = var.region != "" ? " and .region == \"${var.region}\"" : "" # apply region filter only if region field exists for the CSP
fetch_image_command = <<EOT
curl -s https://cdn.confidential.cloud/constellation/v2/${local.image_ref}/image/info.json | \
./yq eval '.list[] | select(.csp == "${var.csp}" and .attestationVariant == "${var.attestation_variant}"${local.region_filter}) | .reference' - | tr -d '\n' > "image.txt"
if [ '${var.csp}' = 'azure' ]; then
sed -i 's/CommunityGalleries/communityGalleries/g' image.txt
sed -i 's/Images/images/g' image.txt
sed -i 's/Versions/versions/g' image.txt
fi
EOT
}
resource "null_resource" "fetch_image" {
provisioner "local-exec" {
command = local.fetch_image_command
environment = {
attestation_variant = var.attestation_variant
}
}
provisioner "local-exec" {
when = destroy
command = "rm image.txt"
}
triggers = {
always_run = "${timestamp()}"
}
}
data "local_file" "image" {
filename = "image.txt"
depends_on = [null_resource.fetch_image]
}

View file

@ -0,0 +1,4 @@
output "image" {
description = "The resolved image ID of the CSP."
value = data.local_file.image.content
}

View file

@ -0,0 +1,20 @@
variable "csp" {
description = "The cloud service provider to fetch image data for."
type = string
}
variable "attestation_variant" {
description = "The attestation variant to fetch image data for."
type = string
}
variable "region" {
description = "The region to fetch image data for."
type = string
default = ""
}
variable "image" {
description = "The image reference or semantical release version to fetch image data for."
type = string
}

View file

@ -0,0 +1,43 @@
#!/usr/bin/env bash
version="v4.35.2"
if [[ -f ./yq ]] && ./yq --version | grep -q "${version}"; then
echo "yq is already available and up to date."
exit 0
fi
if [[ -f ./yq ]]; then
echo "yq is already available but not at the required version. Replacing with ${version}."
rm -f yq
fi
echo "Fetching yq ${version}"
os=$(uname -s)
arch=$(uname -m)
url=""
if [[ ${os} == "Darwin" ]]; then
if [[ ${arch} == "arm64" ]]; then
url="https://github.com/mikefarah/yq/releases/download/${version}/yq_darwin_arm64"
elif [[ ${arch} == "x86_64" ]]; then
url="https://github.com/mikefarah/yq/releases/download/${version}/yq_darwin_amd64"
fi
elif [[ ${os} == "Linux" ]]; then
if [[ ${arch} == "x86_64" ]]; then
url="https://github.com/mikefarah/yq/releases/download/${version}/yq_linux_amd64"
elif [[ ${arch} == "arm64" ]]; then
url="https://github.com/mikefarah/yq/releases/download/${version}/yq_linux_arm64"
fi
fi
if [[ -z ${url} ]]; then
echo "os \"${os}\" and/or architecture \"${arch}\" is not supported."
exit 1
else
echo "Downloading yq from ${url}"
curl -o yq -L "${url}"
chmod +x ./yq
./yq --version
if ! ./yq --version | grep -q "${version}"; then # check that yq was installed correctly
echo "Version is incorrect"
exit 1
fi
fi

View file

@ -0,0 +1,25 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/null" {
version = "3.2.1"
hashes = [
"h1:FbGfc+muBsC17Ohy5g806iuI1hQc4SIexpYCrQHQd8w=",
"h1:tSj1mL6OQ8ILGqR2mDu7OYYYWf+hoir0pf9KAQ8IzO8=",
"h1:vUW21lLLsKlxtBf0QF7LKJreKxs0CM7YXGzqW1N/ODY=",
"h1:wqgRvlyVIbkCeCQs+5jj6zVuQL0KDxZZtNofGqqlSdI=",
"h1:ydA0/SNRVB1o95btfshvYsmxA+jZFRZcvKzZSB+4S1M=",
"zh:58ed64389620cc7b82f01332e27723856422820cfd302e304b5f6c3436fb9840",
"zh:62a5cc82c3b2ddef7ef3a6f2fedb7b9b3deff4ab7b414938b08e51d6e8be87cb",
"zh:63cff4de03af983175a7e37e52d4bd89d990be256b16b5c7f919aff5ad485aa5",
"zh:74cb22c6700e48486b7cabefa10b33b801dfcab56f1a6ac9b6624531f3d36ea3",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:79e553aff77f1cfa9012a2218b8238dd672ea5e1b2924775ac9ac24d2a75c238",
"zh:a1e06ddda0b5ac48f7e7c7d59e1ab5a4073bbcf876c73c0299e4610ed53859dc",
"zh:c37a97090f1a82222925d45d84483b2aa702ef7ab66532af6cbcfb567818b970",
"zh:e4453fbebf90c53ca3323a92e7ca0f9961427d2f0ce0d2b65523cc04d5d999c2",
"zh:e80a746921946d8b6761e77305b752ad188da60688cfd2059322875d363be5f5",
"zh:fbdb892d9822ed0e4cb60f2fedbdbb556e4da0d88d3b942ae963ed6ff091e48f",
"zh:fca01a623d90d0cad0843102f9b8b9fe0d3ff8244593bd817f126582b52dd694",
]
}

View file

@ -0,0 +1,34 @@
#!/usr/bin/env bash
if [[ -f ./constellation ]]; then
echo "constellation CLI is already available."
exit 0
fi
os=$(uname -s)
arch=$(uname -m)
version=$1
url=""
echo "Fetching constellation ${version}"
if [[ ${os} == "Darwin" ]]; then
if [[ ${arch} == "arm64" ]]; then
url="https://github.com/edgelesssys/constellation/releases/${version}/download/constellation-darwin-arm64"
elif [[ ${arch} == "x86_64" ]]; then
url="https://github.com/edgelesssys/constellation/releases/${version}/download/constellation-darwin-amd64"
fi
elif [[ ${os} == "Linux" ]]; then
if [[ ${arch} == "x86_64" ]]; then
url="https://github.com/edgelesssys/constellation/releases/${version}/download/constellation-linux-amd64"
elif [[ ${arch} == "arm64" ]]; then
url="https://github.com/edgelesssys/constellation/releases/${version}/download/constellation-linux-arm64"
fi
fi
if [[ -z ${url} ]]; then
echo "os \"${os}\" and/or architecture \"${arch}\" is not supported."
exit 1
else
curl -o constellation -L "${url}"
chmod +x constellation
fi

View file

@ -0,0 +1,191 @@
locals {
yq_node_groups = join("\n", flatten([
for name, group in var.node_groups : [
"./yq eval '.nodeGroups.${name}.role = \"${group.role}\"' -i constellation-conf.yaml",
"./yq eval '.nodeGroups.${name}.zone = \"${group.zone}\"' -i constellation-conf.yaml",
"./yq eval '.nodeGroups.${name}.instanceType = \"${group.instance_type}\"' -i constellation-conf.yaml",
"./yq eval '.nodeGroups.${name}.stateDiskSizeGB = ${group.disk_size}' -i constellation-conf.yaml",
"./yq eval '.nodeGroups.${name}.stateDiskType = \"${group.disk_type}\"' -i constellation-conf.yaml",
"./yq eval '.nodeGroups.${name}.initialCount = ${group.initial_count}' -i constellation-conf.yaml"
]
]))
gcp_sa_file_path = "service_account_file.json"
}
resource "null_resource" "ensure_cli" {
provisioner "local-exec" {
command = <<EOT
${path.module}/install-constellation.sh ${var.constellation_version}
EOT
}
triggers = {
always_run = timestamp()
}
}
// terraform_data resource so that it is run only once
resource "terraform_data" "config_generate" {
provisioner "local-exec" {
command = <<EOT
./constellation config generate ${var.csp}
EOT
}
depends_on = [
null_resource.ensure_cli
]
}
resource "null_resource" "aws_config" {
count = var.aws_config != null ? 1 : 0
provisioner "local-exec" {
command = <<EOT
./yq eval '.provider.aws.region = "${var.aws_config.region}"' -i constellation-conf.yaml
./yq eval '.provider.aws.zone = "${var.aws_config.zone}"' -i constellation-conf.yaml
./yq eval '.provider.aws.iamProfileControlPlane = "${var.aws_config.iam_instance_profile_control_plane}"' -i constellation-conf.yaml
./yq eval '.provider.aws.iamProfileWorkerNodes = "${var.aws_config.iam_instance_profile_worker_nodes}"' -i constellation-conf.yaml
EOT
}
triggers = {
always_run = timestamp()
}
depends_on = [
terraform_data.config_generate
]
}
resource "null_resource" "azure_config" {
count = var.azure_config != null ? 1 : 0
provisioner "local-exec" {
command = <<EOT
./yq eval '.provider.azure.subscription = "${var.azure_config.subscription}"' -i constellation-conf.yaml
./yq eval '.provider.azure.tenant = "${var.azure_config.tenant}"' -i constellation-conf.yaml
./yq eval '.provider.azure.location = "${var.azure_config.location}"' -i constellation-conf.yaml
./yq eval '.provider.azure.resourceGroup = "${var.azure_config.resourceGroup}"' -i constellation-conf.yaml
./yq eval '.provider.azure.userAssignedIdentity = "${var.azure_config.userAssignedIdentity}"' -i constellation-conf.yaml
./yq eval '.provider.azure.deployCSIDriver = ${var.azure_config.deployCSIDriver}' -i constellation-conf.yaml
./yq eval '.provider.azure.secureBoot = ${var.azure_config.secureBoot}' -i constellation-conf.yaml
./yq eval '.infrastructure.azure.resourceGroup = "${var.azure_config.resourceGroup}"' -i constellation-state.yaml
./yq eval '.infrastructure.azure.subscriptionID = "${var.azure_config.subscription}"' -i constellation-state.yaml
./yq eval '.infrastructure.azure.networkSecurityGroupName = "${var.azure_config.networkSecurityGroupName}"' -i constellation-state.yaml
./yq eval '.infrastructure.azure.loadBalancerName = "${var.azure_config.loadBalancerName}"' -i constellation-state.yaml
./yq eval '.infrastructure.azure.userAssignedIdentity = "${var.azure_config.userAssignedIdentity}"' -i constellation-state.yaml
if [ '${var.azure_config.maaURL}' != '' ]; then
./yq eval '.infrastructure.azure.attestationURL = "${var.azure_config.maaURL}"' -i constellation-state.yaml
./constellation maa-patch ${var.azure_config.maaURL}
fi
EOT
}
triggers = {
always_run = timestamp()
}
depends_on = [
terraform_data.config_generate
]
}
resource "null_resource" "service_account_file" {
count = var.gcp_config != null ? 1 : 0
provisioner "local-exec" {
command = <<EOT
echo ${var.gcp_config.serviceAccountKey} | base64 -d > "${local.gcp_sa_file_path}"
EOT
}
provisioner "local-exec" {
when = destroy
command = "rm ${self.triggers.file_path}"
}
triggers = {
always_run = timestamp()
file_path = local.gcp_sa_file_path
}
}
resource "null_resource" "gcp_config" {
count = var.gcp_config != null ? 1 : 0
provisioner "local-exec" {
command = <<EOT
./yq eval '.provider.gcp.project = "${var.gcp_config.project}"' -i constellation-conf.yaml
./yq eval '.provider.gcp.region = "${var.gcp_config.region}"' -i constellation-conf.yaml
./yq eval '.provider.gcp.zone = "${var.gcp_config.zone}"' -i constellation-conf.yaml
./yq eval '.provider.gcp.serviceAccountKeyPath = "${local.gcp_sa_file_path}"' -i constellation-conf.yaml
./yq eval '.infrastructure.gcp.projectID = "${var.gcp_config.project}"' -i constellation-state.yaml
./yq eval '.infrastructure.gcp.ipCidrPod = "${var.gcp_config.ipCidrPod}"' -i constellation-state.yaml
EOT
}
triggers = {
always_run = timestamp()
}
depends_on = [
terraform_data.config_generate, null_resource.service_account_file
]
}
resource "null_resource" "config" {
provisioner "local-exec" {
command = <<EOT
./yq eval '.name = "${var.name}"' -i constellation-conf.yaml
if [ "${var.image}" != "" ]; then
./yq eval '.image = "${var.image}"' -i constellation-conf.yaml
fi
if [ "${var.kubernetes_version}" != "" ]; then
./yq eval '.kubernetesVersion = "${var.kubernetes_version}"' -i constellation-conf.yaml
fi
if [ "${var.microservice_version}" != "" ]; then
./yq eval '.microserviceVersion = "${var.microservice_version}"' -i constellation-conf.yaml
fi
if [ "${var.serviceCidr}" != "" ]; then
./yq eval '.serviceCIDR = "${var.serviceCidr}"' -i constellation-conf.yaml
fi
${local.yq_node_groups}
./constellation config fetch-measurements ${var.debug == true ? "--insecure" : ""}
EOT
}
depends_on = [
null_resource.aws_config, null_resource.gcp_config, null_resource.azure_config
]
triggers = {
always_run = timestamp()
}
}
resource "null_resource" "infra_state" {
provisioner "local-exec" {
command = <<EOT
./yq eval '.infrastructure.uid = "${var.uid}"' -i constellation-state.yaml
./yq eval '.infrastructure.inClusterEndpoint = "${var.inClusterEndpoint}"' -i constellation-state.yaml
./yq eval '.infrastructure.clusterEndpoint = "${var.clusterEndpoint}"' -i constellation-state.yaml
./yq eval '.infrastructure.initSecret = "'"$(echo "${var.initSecretHash}" | tr -d '\n' | hexdump -ve '/1 "%02x"')"'"' -i constellation-state.yaml
./yq eval '.infrastructure.apiServerCertSANs = ${jsonencode(var.apiServerCertSANs)}' -i constellation-state.yaml
./yq eval '.infrastructure.name = "${var.name}"' -i constellation-state.yaml
./yq eval '.infrastructure.ipCidrNode = "${var.ipCidrNode}"' -i constellation-state.yaml
EOT
}
depends_on = [
terraform_data.config_generate
]
triggers = {
always_run = timestamp()
}
}
resource "null_resource" "apply" {
provisioner "local-exec" {
command = "./constellation apply --debug --yes --skip-phases infrastructure"
}
provisioner "local-exec" {
when = destroy
command = "./constellation terminate --yes && rm constellation-conf.yaml constellation-mastersecret.json && rm -r constellation-upgrade"
}
depends_on = [
null_resource.infra_state, null_resource.config, null_resource.ensure_cli
]
triggers = {
always_run = timestamp()
}
}

View file

@ -0,0 +1,133 @@
variable "constellation_version" {
type = string
description = "Constellation CLI version to use."
default = "@@CONSTELLATION_VERSION@@"
}
variable "image" {
type = string
description = "The node image reference or semantic release version."
}
variable "csp" {
type = string
description = "The cloud service provider to use."
validation {
condition = var.csp == "aws" || var.csp == "gcp" || var.csp == "azure"
error_message = "The cloud service provider to use."
}
}
variable "node_groups" {
type = map(object({
role = string
initial_count = optional(number)
instance_type = string
disk_size = number
disk_type = string
zone = optional(string, "") # For AWS, GCP
zones = optional(list(string), []) # For Azure
}))
description = "A map of node group names to node group configurations."
validation {
condition = can([for group in var.node_groups : group.role == "control-plane" || group.role == "worker"])
error_message = "The role has to be 'control-plane' or 'worker'."
}
}
variable "name" {
type = string
description = "Name used in the cluster's named resources / cluster name."
}
variable "uid" {
type = string
description = "The UID of the Constellation."
}
variable "clusterEndpoint" {
type = string
description = "Endpoint of the cluster."
}
variable "inClusterEndpoint" {
type = string
description = "The endpoint the cluster uses to reach itself. This might differ from the ClusterEndpoint in case e.g. an internal load balancer is used."
}
variable "initSecretHash" {
type = string
description = "Init secret hash."
}
variable "ipCidrNode" {
type = string
description = "Node IP CIDR."
}
variable "serviceCidr" {
type = string
default = ""
description = "Kubernetes service CIDR. This is only used during first initialization of Constellation."
}
variable "apiServerCertSANs" {
type = list(string)
description = "List of additional SANs (Subject Alternative Names) for the Kubernetes API server certificate."
}
variable "aws_config" {
type = object({
region = string
zone = string
iam_instance_profile_worker_nodes = string
iam_instance_profile_control_plane = string
})
description = "The cluster config for AWS."
default = null
}
variable "azure_config" {
type = object({
subscription = string
tenant = string
location = string
resourceGroup = string
userAssignedIdentity = string
deployCSIDriver = bool
secureBoot = bool
maaURL = string
networkSecurityGroupName = string
loadBalancerName = string
})
description = "The cluster config for Azure."
default = null
}
variable "gcp_config" {
type = object({
region = string
zone = string
project = string
ipCidrPod = string
serviceAccountKey = string
})
description = "The cluster config for GCP."
default = null
}
variable "kubernetes_version" {
type = string
description = "Kubernetes version."
}
variable "microservice_version" {
type = string
description = "Microservice version."
}
variable "debug" {
type = bool
default = false
description = "DON'T USE IN PRODUCTION: Enable debug mode and allow the use of debug images."
}

View file

@ -0,0 +1,69 @@
locals {
region = substr(var.zone, 0, length(var.zone) - 2)
}
module "gcp_iam" {
source = "../../infrastructure/iam/gcp"
project_id = var.project
service_account_id = var.service_account_id
region = local.region
zone = var.zone
}
resource "null_resource" "ensure_yq" {
provisioner "local-exec" {
command = <<EOT
../common/install-yq.sh
EOT
}
triggers = {
always_run = timestamp()
}
}
module "fetch_image" {
source = "../common/fetch-image"
csp = "gcp"
attestation_variant = "gcp-sev-es"
image = var.image
depends_on = [null_resource.ensure_yq]
}
module "gcp" {
source = "../../infrastructure/gcp"
project = var.project
image_id = module.fetch_image.image
name = var.name
node_groups = var.node_groups
region = local.region
zone = var.zone
debug = var.debug
custom_endpoint = var.custom_endpoint
}
module "constellation" {
source = "../constellation-cluster"
csp = "gcp"
debug = var.debug
name = var.name
image = var.image
microservice_version = var.microservice_version
kubernetes_version = var.kubernetes_version
uid = module.gcp.uid
clusterEndpoint = module.gcp.out_of_cluster_endpoint
inClusterEndpoint = module.gcp.in_cluster_endpoint
initSecretHash = module.gcp.initSecret
ipCidrNode = module.gcp.ip_cidr_nodes
apiServerCertSANs = module.gcp.api_server_cert_sans
node_groups = var.node_groups
gcp_config = {
region = local.region
zone = var.zone
project = var.project
ipCidrPod = module.gcp.ip_cidr_pods
serviceAccountKey = module.gcp_iam.sa_key
}
depends_on = [module.gcp, null_resource.ensure_yq]
}

View file

@ -0,0 +1,72 @@
variable "name" {
type = string
description = "Name of the Constellation cluster."
}
variable "project" {
type = string
description = "The project ID to deploy the cluster to."
}
variable "service_account_id" {
type = string
description = "The service account ID to use for the cluster."
}
variable "image" {
type = string
description = "Node image reference or semantic release version. When not set, the latest default version will be used."
default = "@@CONSTELLATION_VERSION@@"
}
variable "microservice_version" {
type = string
description = "Microservice version. When not set, the latest default version will be used."
default = ""
}
variable "kubernetes_version" {
type = string
description = "Kubernetes version. When not set, the latest default version will be used."
default = ""
}
variable "node_groups" {
type = map(object({
role = string
initial_count = optional(number)
instance_type = string
disk_size = number
disk_type = string
zone = string
}))
description = "A map of node group names to node group configurations."
validation {
condition = can([for group in var.node_groups : group.role == "control-plane" || group.role == "worker"])
error_message = "The role has to be 'control-plane' or 'worker'."
}
}
variable "zone" {
type = string
description = "The availability zone name to create the cluster in."
}
variable "debug" {
type = bool
default = false
description = "DON'T USE IN PRODUCTION: Enable debug mode and allow the use of debug images."
}
variable "custom_endpoint" {
type = string
default = ""
description = "Custom endpoint (DNS Name) to use for the Constellation API server. If not set, the default endpoint will be used."
}
variable "internal_load_balancer" {
type = bool
default = false
description = "Use an internal load balancer."
}