(re)Formatting x509 Certificates in Terraform

Hey all, is there a recommended method of formatting or reformatting an x509 Certificate within Terraform?

I’m currently automating the relationship between Azure AD (IdP) and Okta " Identity Providers" configuration. In the Okta docs a certificate must be generated manually in an Azure Enterprise App; that certificate is later downloaded from Azure, then uploaded to the Okta " Identity Providers" config.

I can create this certificate with Terraform:

resource "azuread_service_principal_token_signing_certificate" "saml_signing_cert" {
  service_principal_id = azuread_service_principal.okta_sp.id
  display_name         = "CN=${var.myCo} SSO Certificate"
  end_date             = time_rotating.saml_certificate.rotation_rfc3339

  provisioner "local-exec" {
    command = <<-SHELL
      az ad sp update --id ${self.service_principal_id} \
        --set preferredTokenSigningKeyThumbprint=${self.thumbprint}
    SHELL
  }
}

Then create an idp_signing_key in Okta to receive it.

resource "okta_idp_saml_key" "idp_signing_key" {
  x5c = ["azuread_service_principal_token_signing_certificate.saml_signing_cert.value"]
}

At the end of the apply, this error is displayed:

Error: failed to create identity provider signing key: the API returned an error: Api validation failed: JsonWebKey. Causes: errorSummary: The IDP certificate JWK has an invalid x5c.

I’ve validated the contents of the cert created in the Azure AD Enterprise App by downloading it and comparing it to azuread_service_principal_token_signing_certificate.saml_signing_cert.value output; they are the same.

I believe the difference is that Okta is expecting a standard x509 format:

-----BEGIN CERTIFICATE-----
MIIC8DCCAdigAxIBAgIQLR20Xyb3sI5O6/Ga+l6L6jANBgkqjkiG9w0BAQsFADA0MTIwMAYDVQQD
...
-----END CERTIFICATE-----

Basically, a BEGIN/END CERTIFICATE declaration with content that breaks to new lines after 78 characters.

In this case, it appears that the value in the state is just a long string:

terraform show -json | grep MIIC8DCCAdigAxIBAgIQLR20Xyb3sI5O6

There are no BEGIN/END CERTIFICATE declarations with breaks to new lines after 78 characters. So, the question becomes:

Does Terraform have a library that converts this certificate back into the expected format?

Hi @todd-dsm,

The typical way we address problems like this in Terraform is to have a standard/conventional representation of any kind of value that tends to cross provider boundaries. In this case it’s conventional in Terraform to use a PEM representation for certificates and other related objects like private keys. This is a similar principle to how timestamps in Terraform should always be represented as RFC 3339 strings, so that timestamps from one provider can be sent to another without the visual noise of format conversion directly in the Terraform configuration.

Unfortunately it seems that the azuread_service_principal_token_signing_certificate resource type is not following that convention. The way I’d expect to solve this is for that resource type to offer the standard PEM encoding as one of its attributes so that this would’ve worked the way you expected it to work, and so it might be worth opening a feature request for that in the provider’s repository if there isn’t one already.

In the meantime, I see in the documentation that this value attribute is explicitly documented as being a PEM string without the PEM envelope:

value - The certificate data, which is PEM encoded but does not include the header -----BEGIN CERTIFICATE-----\n or the footer \n-----END CERTIFICATE-----.

Based on that documentation, it should be safe to template in the PEM envelope markers yourself, if this is indeed “PEM encoded but without the header”:

  resource "okta_idp_saml_key" "idp_signing_key" {
  x5c = [
    <<-EOT
    -----BEGIN CERTIFICATE-----
    ${azuread_service_principal_token_signing_certificate.saml_signing_cert.value}
    -----END CERTIFICATE-----
    EOT
  ]
}

Hopefully a future version of this provider can offer an additional value_pem attribute that comes with these extra markers already present so that the string is compatible with what other providers will typically expect, but until then this should hopefully get the effect you wanted.

1 Like

That got most of the way there but still failing. I’m not sure if these observations are an issue but here are the latest outputs from the test cert:

  1. It appears there is a 2-character indent on all lines
    a. but, it’s even with the Here Doc (<<-)
  2. There are no line breaks in the string
  3. There’s an extra return between the END... and EOT.

NOTE: when digging through the plan output (terraform show -json | grep my-cert), all line endings look pretty decent:

"my-cert":{"sensitive":true,"value":["-----BEGIN CERTIFICATE-----\nMIICxjCCAa6gAwIBAgIIQBVfexW8+...\n-----END CERTIFICATE-----\n"],

Number 3 still seems unnecessary.

Not sure if any of these are relevant but this is the here’s the accompanying error message:

Api validation failed: JsonWebKey. Causes: errorSummary: The IDP certificate JWK has an invalid x5c.

If you could lend guidance for this last bit that would be much appreciated.

The RFE (“Request for Enhancement”) is in. TIA

Closing this one out. I had so much cruft that when I destroyed everything and rebuilt the problem went away. Certainly not a data problem.

Thanks for following up, @todd-dsm! I’m glad it worked out in the end.

I realize that you no longer need the answers to the details about the formatting that resulted from your template but I’m going to address it anyway just in case someone else finds this topic in future and has similar questions.

This PEM format is described in RFC 7468, which includes both a general part describing how the base64 data between the markers can be formatted and then specific parts for the different possible begin/end labels for different kinds of data.

In Section 2 “General Considerations” the specification states:

Furthermore, parsers SHOULD ignore whitespace and other non-base64 characters and MUST handle different newline conventions.

So that takes care of your first and second points, about extra indentation and the presence or absence of newline characters.

The BEGIN and END lines are there specifically to allow isolating just the base64 data from any surrounding text, so extra newline characters – or indeed, any other text lines at all – before or after those should not typically be a problem for a compliant parser of this format. Having all of the base64 characters on a single line is technically non-conformant for a generator of this format – the spec requires wrapping at 64 characters – but it also tells parsers to be liberal in what they accept.

The nice thing about this PEM encoding is that it was originally designed for being embedded directly inside the body of an email message, where email clients tend to do things such as add indentation markers when replying, add extra line breaks in unfortunate places, etc. So the format is designed to be quite resilient, and compliant parsers tend to be quite forgiving in what they’ll accept. (though the spec does warn that there are parsers that are not so liberal or compliant.)

Hopefully if a full PEM attribute were added to the provider alongside the base64-bytes-only attribute then it would follow the PEM spec more closely, and thus be compatible with more parsers. But it does at least seem like the parser used by Okta is liberal enough for our purposes here.


In addition to that, I’d note that the indentation shown in your screenshot is not actually part of the string, but is instead part of Terraform’s UI rendering of the string. Terraform is indenting it to help you see that it’s nested inside the tolist([ ... ]) container. The - character in <<-EOT in the example I shared before tells Terraform to strip off any leading whitespace that all of the lines have in common, and the UI intentionally uses a similar syntax in the hope that its meaning will seem familiar. As you saw in the raw data beneath the UI, those leading spaces aren’t present in the real string.

Good stuff; I did leave a link in their issue back to this post so everyone can find these details :grinning:

Thanks again!

Had the same issue, here is a solution if you still need to split the content;

variable "certificate" {
  default = "MIIC6DCCAdCgAwIBAgII........."
}

output "pem" {
  value = "-----BEGIN CERTIFICATE-----${join("",  [for i, v in split("", var.certificate) : i % 64 == 0 ? "\n${v}" : v ])}\n-----END CERTIFICATE-----"
}
1 Like