Periodically recreate tls_private_key

Is there a way to periodically recreate resources like tls_private_key? e.g. every 90 days.
I tried the following…

resource "time_rotating" "this" {
  rotation_minutes = 1
}

resource "tls_private_key" "this" {
  algorithm   = "ECDSA"
  ecdsa_curve = "P384"
  depends_on  = [time_rotating.this]
}

time_rotating.this is getting re-created, but not tls_private_key.this.
The rotation_minutes is set to 1 in the example only to make testing easier.

Hi @schiermi,

The depends_on argument in Terraform only affects the order of operations Terraform would’ve performed anyway, and so it can never cause Terraform to perform an additional operation that wasn’t required for other reasons.

In your case, it seems like the problem here is that nothing in the configuration of tls_private_key.this changes in response to the time_rotating resource’s timestamp and so Terraform can see that if it were changing both of those resources at once then it should work on time_rotating.this first, but there’s never any situation where it’s necessary to update or replace tls_private_key.this after it’s initially created because its configuration is constant.

This is a pretty tricky situation because tls_private_key doesn’t include any arguments that explicitly relate to time, and so there’s no natural way to write out that a private key has a limited timespan… that tends to come from the certificates created from the key.

However, it could work to exploit some details of how time_rotating works to still get the effect you wanted. Specifically, I believe (though I’ve not verified) that time_rotating works by making the existing object “vanish” and thus need recreating once the time interval expires, at which point all of its exported time-related attributes will return to being (known after apply) for that plan, because the time it finally records will come from the apply step instead.

This means that we can potentially cheat a little bit by tricking Terraform into thinking there’s some possibility that the algorithm argument will change even though it will actually always still stabilize back to ECDSA again:

resource "tls_private_key" "this" {
  algorithm   = time_rotating.this.id != "" ? "ECDSA" : "invalid"
  ecdsa_curve = "P384"
}

The final value of id should always be a valid timestamp and thus never an empty string, so in practice that conditional can only ever return true, but Terraform itself doesn’t know what that id value represents and so it will see that it has an unknown value and thus conservatively conclude that it doesn’t know yet whether it’ll be empty or not, and thus algorithm itself will become (known after apply), which should then prompt the hashicorp/tls provider to plan to replace that object.

There are two main drawbacks to this approach:

  • Firstly, the configuration I shared about is not intuitive and is only understandable if you know some details of how Terraform works internally. Therefore if I were to employ this trick I’d be sure to write a detailed comment alongside that argument explaining what’s going on, so that future maintainers can understand what it’s doing and not remove the conditional thinking that it’s redundant.

  • Secondly, it does mean that on initial creation and on each rotation the Terraform plan will show algorithm = (known after apply), thus obscuring which algorithm this key will use and making the plan less useful.

    For someone who already knows how tls_private_key works it’s possible to infer from context that ecdsa_curve wouldn’t be valid if algorithm were "RSA", but I think it’s fair to say that the less uncertainty the better when it comes to security-sensitive things like private keys.

With that said, I don’t have a better suggestion to meet your use case within the Terraform language itself. My only other suggestion would be to handle this rotation with some automation outside of Terraform, by arranging for something to run terraform plan -replace=tls_private_key.this every 90 days and then apply that plan to proactively cycle the key. Of course, along with the additional overhead of having some other process in place to do this, there’s the risk that the system will stop working and not be noticed and thus leave the stale key active longer than it ought to have been.

3 Likes

Thank you very much for the solution and the extensive description for it. Your approach is pretty clever. Highly appreciated!

Yes, it works thanks!

I think the workaround described above is not required anymore thanks to lifecycle.replace_triggered_by.

Edit: Ok, you still need another workaround: time_rotating doesn't work with lifecycle replace_triggered_by · Issue #118 · hashicorp/terraform-provider-time · GitHub

You can create 2 keys:
resource “tls_private_key” “this” {
algorithm = “ECDSA”
ecdsa_curve = “P384”
depends_on = [time_rotating.this]
}

resource “tls_private_key” “this2” {
algorithm = “ECDSA”
ecdsa_curve = “P384”
depends_on = [time_rotating.this]
}

One is to use, then one is to rotate later, then use the other back.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.