Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add token signing certificate resource #968

Merged
merged 3 commits into from
Jan 18, 2023

Conversation

tagur87
Copy link
Contributor

@tagur87 tagur87 commented Jan 10, 2023

This adds a new resource called
service_principal_token_signing_certificate that is used to manage the whole lifecycle of token signing certificates used for SAML authentication.

This resource makes use of the AddTokenSigningCertificate function that was added to hamilton previously here:
manicminer/hamilton#158

MS Graphs Docs: https://learn.microsoft.com/en-us/graph/api/serviceprincipal-addtokensigningcertificate?view=graph-rest-1.0&tabs=http

As documented in the docs above, when the AddTokenSigningCertificate function is invoked, 3 individual objects are created...

  • Verify keyCredential (Public Cert)
  • Sign keyCredential (Private Key)
  • passwordCredential (Private Key Password)

When the object is returned, it includes the thumbprint, the public key pem value, and a keyId. However, we found an odd behavior that the keyId that is returned is actually for the Sign keyCredential.

Since the Verify certificate is the one that we acutally care about, we used the customKeyIdentifier, which is the same for all 3 values, to get the Verify keyId, which we then use in building the resource ID.

We additionally had to "calculate" the thumbprint value from the actual value of the Verify cert, as this value is not returned from the API, except after initial creation in the Create step. We did this by getting pem value of the Verify cert by adding the $select=keyCredential odata query to the GET of the service principal. By combining this value with the PEM header/footer, we can calculate the SHA-1 fingerprint, which matches up to the appropriate thumbprint.

Finally, to delete the certificate, we have to PATCH the service principal with all 3 objects mentioned previously removed. To gather this, we used the customKeyIdentifier value in a loop.

Closes #732
And part of #823

@tagur87
Copy link
Contributor Author

tagur87 commented Jan 10, 2023

@manicminer @katbyte - Welcoming feedback on this. I am sure there are some things that need addressed, but it works really well right now for adding the cert.

We just need to figure out how to add the preferred thumbprint to the service principal.

Since it would cause a circular dependency to reference this resource in the service_principal resource, I thought about adding a field to this resource such as preferred_thumbprint = true to set it as preferred...but not sure if there is a way to prevent that from being set in multiple instances of the resource.

This adds a new resource called
`service_principal_token_signing_certificate` that is used to manage the
whole lifecycle of token signing certificates used for SAML
authentication.

This resource makes use of the `AddTokenSigningCertificate` function
that was added to hamilton previously here:
manicminer/hamilton#158

MS Graphs Docs: https://learn.microsoft.com/en-us/graph/api/serviceprincipal-addtokensigningcertificate?view=graph-rest-1.0&tabs=http

As documented in the docs above, when the  `AddTokenSigningCertificate`
function is invoked, 3 individual objects are created...
- Verify `keyCredential` (Public Cert)
- Sign `keyCredential` (Private Key)
- `passwordCredential` (Private Key Password)

When the object is returned, it includes the thumbprint, the public key
pem value, and a `keyId`. However, we found an odd behavior that the
`keyId` that is returned is actually for the Sign `keyCredential`.

Since the Verify certificate is the one that we acutally care about,
we used the `customKeyIdentifier`, which is the same for all 3 values,
to get the Verify `keyId`, which we then use in building the resource
ID.

We additionally had to "calculate" the thumbprint value from the
actual value of the Verify cert, as this value is not returned from the
API, except after initial creation in the Create step.
We did this by getting pem value of the Verify cert by adding the
`$select=keyCredential` odata query to the GET of the service principal.
By combining this value with the PEM header/footer, we can calculate
the SHA-1 fingerprint, which matches up to the appropriate thumbprint.

Finally, to delete the certificate, we have to PATCH the service
principal with all 3 objects mentioned previously removed. To gather
this, we used the `customKeyIdentifier` value in a loop.

Closes hashicorp#732
And part of hashicorp#823
@tagur87
Copy link
Contributor Author

tagur87 commented Jan 12, 2023

Fixed the lint issues.

@kenchan0130
Copy link
Contributor

kenchan0130 commented Jan 17, 2023

We just need to figure out how to add the preferred thumbprint to the service principal.

Since it would cause a circular dependency to reference this resource in the service_principal resource, I thought about adding a field to this resource such as preferred_thumbprint = true to set it as preferred...but not sure if there is a way to prevent that from being set in multiple instances of the resource.

Basically, it seems better for terraform resources to be compliant with Microsoft Graph API.
Therefore, I do not think it is necessary to make the certificate active in the service_principal_token_signing_certificate resource you have created.

I think this is inevitable because the Microsoft Graph API is not well designed.

(Since automatic renewal of certificates temporarily disables login to service providers that do not support IdP metadataURL, perhaps the inability to renew is an appropriate process.)

In practical terms, the following responses are expected

# this is an example
resource "azuread_service_principal_token_signing_certificate" "example" {
  service_principal_id = azuread_service_principal.example.id
  display_name         = "CN=example.com"
  end_date             = "2023-05-01T01:02:03Z"

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

Copy link
Contributor

@manicminer manicminer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @tagur87, thanks for this contribution! My changes below are just some minor tweaks, this otherwise looks great and is good to merge 🎸

In terms of setting the preferred certificate, I think perhaps a third resource is the most future-proof approach right now - particularly as the next major version of the provider will see more of these sorts of virtual resources. Something like:

resource "azuread_service_principal_preferred_token_signing_certificate" "example" {
  service_principal_id = azuread_service_principal.example.object_id
  certificate_id       = azuread_service_principal_token_signing_certificate.example.id
}

Whilst that approach doesn't mitigate being able to create overlapping resources, it does reduce the config to a single place and will help with graphing dependencies in a future with more of these split-up virtual resources.

Since that can be added separately, we can still merge this PR. You're welcome to add that in another PR :)

Test results

Screen Shot 2023-01-18 at 23 04 17

Comment on lines 123 to 124
tf.LockByName(servicePrincipalResourceName, objectId)
defer tf.UnlockByName(servicePrincipalResourceName, objectId)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to move this before any write operations

Suggested change
tf.LockByName(servicePrincipalResourceName, objectId)
defer tf.UnlockByName(servicePrincipalResourceName, objectId)

Comment on lines +218 to +222
thumbprint, err := helpers.GetTokenSigningCertificateThumbprint(
[]byte("-----BEGIN CERTIFICATE-----\n" + *credential.Key + "\n-----END CERTIFICATE-----"))
if err != nil {
return tf.ErrorDiagPathF(err, "id", "parsing tokenSigningCertificate key value with ID %q", id.KeyId)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice workaround :)

@manicminer manicminer merged commit aee0de6 into hashicorp:main Jan 18, 2023
manicminer added a commit that referenced this pull request Jan 18, 2023
@github-actions
Copy link

This functionality has been released in v2.33.0 of the Terraform Provider. Please see the Terraform documentation on provider versioning or reach out if you need any assistance upgrading.

For further feature requests or bug reports with this functionality, please create a new GitHub issue following the template. Thank you!

@github-actions
Copy link

I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 25, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add azuread_service_principal_token_signing_certificate to simplify certificate management
3 participants