tfmodules/kubernetes/ovh/credentials.tf
gitops_test 293e78f363 sync-modules: mirrored modules of release 3.2.0-F-feature-simpl-31671.660.aaad53cc
+++ kubernetes/ovh/credentials.tf
+++ kubernetes/ovh/versions.tf
2026-09-20 22:54:05 +00:00

194 lines
6.2 KiB
HCL

// Consumer and management credentials are generated here, never injected by
// the backend and never delivered through outputs. The OVH admin kubeconfig
// (client certificates) is never delivered either: consumer and management
// each get a scoped ServiceAccount with a NON EXPIRING legacy token, valid
// until a credential renewal is requested (the renewal deletes and recreates
// the ServiceAccount, which revokes every previously issued token at once).
// Vault entries follow the existing contract:
// secret/{provider}/{cloud_account}/{requester_unique_id}/{username} consumer kubeconfig
// secret/{provider}/{cloud_account}/{requester_unique_id}/admin root kubeconfig + admin username
// Only the Vault path of the consumer entry is exposed as an output.
resource "kubernetes_namespace_v1" "access" {
metadata {
name = "${var.name_prefix}-access"
}
depends_on = [ovh_cloud_project_kube_nodepool.this]
}
// Rotation anchor: replacing this resource (any change to credentials_version)
// cascades to the consumer ServiceAccount and its token Secret through
// replace_triggered_by, performing the credential rotation inside Terraform.
// null_resource instead of terraform_data: the Terraform of the tofu
// controller runner (1.3.x) predates the terraform_data resource type.
resource "null_resource" "credentials_version" {
triggers = {
version = var.credentials_version
}
}
// Consumer access: ServiceAccount + non expiring legacy token Secret. The
// binding references the ServiceAccount by name, so it survives the
// delete/recreate performed by the credential rotation. Recreating the
// ServiceAccount changes its UID, which revokes every token issued for it.
resource "kubernetes_service_account_v1" "consumer" {
metadata {
name = var.username
namespace = kubernetes_namespace_v1.access.metadata[0].name
}
lifecycle {
replace_triggered_by = [null_resource.credentials_version]
}
}
resource "kubernetes_cluster_role_binding_v1" "consumer_admin" {
metadata {
name = "${var.username}-admin"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = "cluster-admin"
}
subject {
kind = "ServiceAccount"
name = kubernetes_service_account_v1.consumer.metadata[0].name
namespace = kubernetes_namespace_v1.access.metadata[0].name
}
}
resource "kubernetes_secret_v1" "consumer_token" {
metadata {
name = "${var.username}-token"
namespace = kubernetes_namespace_v1.access.metadata[0].name
annotations = {
"kubernetes.io/service-account.name" = kubernetes_service_account_v1.consumer.metadata[0].name
}
}
type = "kubernetes.io/service-account-token"
wait_for_service_account_token = true
lifecycle {
replace_triggered_by = [null_resource.credentials_version]
}
}
// Management (renewer) access: ServiceAccount + NON EXPIRING legacy token
// Secret, the credential the renewal workflow uses with kubectl on day 2.
// Revocable by deleting the ServiceAccount.
resource "kubernetes_service_account_v1" "renewer" {
metadata {
name = var.admin_username
namespace = kubernetes_namespace_v1.access.metadata[0].name
}
}
resource "kubernetes_cluster_role_binding_v1" "renewer_admin" {
metadata {
name = "${var.admin_username}-admin"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = "cluster-admin"
}
subject {
kind = "ServiceAccount"
name = kubernetes_service_account_v1.renewer.metadata[0].name
namespace = kubernetes_namespace_v1.access.metadata[0].name
}
}
resource "kubernetes_secret_v1" "renewer_token" {
metadata {
name = "${var.admin_username}-token"
namespace = kubernetes_namespace_v1.access.metadata[0].name
annotations = {
"kubernetes.io/service-account.name" = kubernetes_service_account_v1.renewer.metadata[0].name
}
}
type = "kubernetes.io/service-account-token"
wait_for_service_account_token = true
}
// Kubeconfigs assembled with the scoped tokens, never the admin certificates
locals {
cluster_server = ovh_cloud_project_kube.this.kubeconfig_attributes[0].host
cluster_ca = ovh_cloud_project_kube.this.kubeconfig_attributes[0].cluster_ca_certificate
consumer_kubeconfig = yamlencode({
apiVersion = "v1"
kind = "Config"
"current-context" = "simpl"
clusters = [{
name = "simpl"
cluster = {
server = local.cluster_server
"certificate-authority-data" = local.cluster_ca
}
}]
users = [{
name = var.username
user = { token = kubernetes_secret_v1.consumer_token.data["token"] }
}]
contexts = [{
name = "simpl"
context = { cluster = "simpl", user = var.username }
}]
})
renewer_kubeconfig = yamlencode({
apiVersion = "v1"
kind = "Config"
"current-context" = "simpl"
clusters = [{
name = "simpl"
cluster = {
server = local.cluster_server
"certificate-authority-data" = local.cluster_ca
}
}]
users = [{
name = var.admin_username
user = { token = kubernetes_secret_v1.renewer_token.data["token"] }
}]
contexts = [{
name = "simpl"
context = { cluster = "simpl", user = var.admin_username }
}]
})
}
// The kubeconfig is the primary artifact of both entries (the credential
// delivered and later renewed), the remaining keys are companions.
resource "vault_kv_secret_v2" "consumer" {
mount = "secret"
name = "ovh/${var.cloud_account}/${var.requester_unique_id}/${var.username}"
data_json = jsonencode({
kubeconfig = local.consumer_kubeconfig
token = kubernetes_secret_v1.consumer_token.data["token"]
server = local.cluster_server
ca_data = local.cluster_ca
})
}
resource "vault_kv_secret_v2" "admin" {
mount = "secret"
name = "ovh/${var.cloud_account}/${var.requester_unique_id}/admin"
data_json = jsonencode({
username = var.admin_username
kubeconfig = local.renewer_kubeconfig
token = kubernetes_secret_v1.renewer_token.data["token"]
server = local.cluster_server
ca_data = local.cluster_ca
})
}