Skip to content

Conversation

mvanholsteijn
Copy link

Adds the properties private_key_pem_wo and private_key_pem_wo_version to support the use of ephemeral private keys for self signed certificates, keeping the private key out of the state file.

Related Issue

#645

Description

Added support for a write-only private_key_pem as described in https://developer.hashicorp.com/terraform/plugin/framework/resources/write-only-arguments

Rollback Plan

  • If a change needs to be reverted, we will roll out an update to the code within 7 days.

Changes to Security Controls

nope.

…em write only attributes

supports hashicorp#645 Write-only attribute support for TLS managed resources
@mvanholsteijn mvanholsteijn requested a review from a team as a code owner August 16, 2025 13:03
@bschaatsbergen
Copy link

Hey @deniseyu and @austinvalle, tagging you for visibility. Do you see a path for getting this merged? The new write-only argument introduced here is useful for securely creating self-signed certificates—without it, the private key would be exposed in the state.

The beauty of this write-only argument is that it enables a pattern similar to the ephemeral random_password: being able to persist the private key' attribute result directly in a secret manager.

ephemeral tls_private_key "demo" {
  algorithm = "RSA"
  rsa_bits  = 2048
}

resource "tls_self_signed_cert" "demo" {
  private_key_pem_wo = ephemeral.tls_private_key.demo.private_key_pem
  private_key_pem_wo_version = 1

  subject {
    common_name  = "example.com"
    organization = "ACME Examples, Inc"
  }

  validity_period_hours = 24 * 30

  allowed_uses = [
    "key_encipherment",
    "digital_signature",
    "server_auth",
  ]
}

resource "vault_kv_secret_v2" "demo_private_key" {
  mount               = vault_mount.kvv2.path
  name                = "demo_private_key"
  cas                 = 1
  delete_all_versions = true
  data_json_wo = jsonencode(
    {
      private_key = ephemeral.tls_private_key.demo.private_key_pem,
    }
  )
  data_json_wo_version = tls_self_signed_cert.demo.private_key_pem_wo_version
}

Thanks!

@mvanholsteijn
Copy link
Author

mvanholsteijn commented Aug 16, 2025

I actually need the write only properties to create a google service account key by uploading the public key and storing the service account key with the private key directly in the secret manager.

resource "google_service_account" "demo" {
  account_id   = "demo123"
  display_name = "Demo of public key service accounts"
  project      = var.project
}

resource "google_project_iam_member" "demo" {
  for_each = toset(["roles/compute.viewer"])
  project  = var.project
  member   = google_service_account.demo.member
  role     = each.value
}

resource "google_service_account_key" "demo" {
  service_account_id = google_service_account.demo.id
  public_key_data    = base64encode(tls_self_signed_cert.demo.cert_pem)
}

ephemeral "tls_private_key" "demo" {
  algorithm = "RSA"
  rsa_bits  = 2048
}

resource "tls_self_signed_cert" "demo" {
  private_key_pem_wo         = ephemeral.tls_private_key.demo.private_key_pem
  private_key_pem_wo_version = 1

  subject {
    common_name  = google_service_account.demo.email
    organization = "ACME, Inc"
  }

  validity_period_hours = 24 * 30

  allowed_uses = [
    "key_encipherment",
    "digital_signature",
    "server_auth",
  ]
}

resource "google_secret_manager_secret" "demo" {
  secret_id = format("%s-key", google_service_account.demo.account_id)
  replication {
    auto {

    }
  }
  project = var.project
}

resource "google_secret_manager_secret_version" "demo" {
  secret                 = google_secret_manager_secret.demo.id
  secret_data_wo_version = tls_self_signed_cert.demo.private_key_pem_wo_version
  secret_data_wo = jsonencode(
    {
      "type"                      = "service_account"
      project_id                  = google_service_account.demo.project
      private_key_id              = google_service_account_key.demo.name
      private_key                 = ephemeral.tls_private_key.demo.private_key_pem
      client_email                = google_service_account.demo.email
      client_id                   = google_service_account.demo.unique_id,
      auth_uri                    = "https://accounts.google.com/o/oauth2/auth"
      token_uri                   = "https://oauth2.googleapis.com/token"
      auth_provider_x509_cert_url = "https://www.googleapis.com/oauth2/v1/certs"
      client_x509_cert_url        = format("https://www.googleapis.com/robot/v1/metadata/x509/%s", urlencode(google_service_account.demo.email))
      universe_domain             = "googleapis.com"
    }
  )
}

Of course, for refreshing the certificate, the private key should be stored in a google secret manager secret and retrieved via an ephemeral google_secret_manager_secret_version.

@mvanholsteijn
Copy link
Author

mvanholsteijn commented Aug 17, 2025

Once this one is approved, I have the PRs for tls_cert_request and tls_locally_signed_cert ready which are pretty similar.

https://github.com/mvanholsteijn/terraform-provider-tls/tree/feat/tls-cert-request/support-write-only-private-key
https://github.com/mvanholsteijn/terraform-provider-tls/tree/feat/tls-locally-signed-cert/support-write-only-private-key

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants