Skip to content

Commit

Permalink
Add token signing certificate resource
Browse files Browse the repository at this point in the history
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
  • Loading branch information
tagur87 committed Jan 10, 2023
1 parent 141bc09 commit 24d2bf7
Show file tree
Hide file tree
Showing 6 changed files with 565 additions and 0 deletions.
87 changes: 87 additions & 0 deletions docs/resources/service_principal_token_signing_certificate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
subcategory: "Service Principals"
---

# Resource: azuread_service_principal_token_signing_certificate

Manages a token signing certificate associated with a service principal within Azure Active Directory.

## API Permissions

The following API permissions are required in order to use this resource.

When authenticated with a service principal, this resource requires one of the following application roles: `Application.ReadWrite.All` or `Directory.ReadWrite.All`

When authenticated with a user principal, this resource requires one of the following directory roles: `Application Administrator` or `Global Administrator`

## Example Usage

*Using default settings*

```terraform
resource "azuread_application" "example" {
display_name = "example"
}
resource "azuread_service_principal" "example" {
application_id = azuread_application.example.application_id
}
resource "azuread_service_principal_token_signing_certificate" "example" {
service_principal_id = azuread_service_principal.example.id
}
```

*Using custom settings*

```terraform
resource "azuread_application" "example" {
display_name = "example"
}
resource "azuread_service_principal" "example" {
application_id = azuread_application.example.application_id
}
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"
}
```

## Argument Reference

The following arguments are supported:

* `display_name` - (Optional) Specifies a friendly name for the certificate.
Must start with `CN=`. Changing this field forces a new resource to be created.

~> If not specified, it will default to `CN=Microsoft Azure Federated SSO Certificate`.

* `end_date` - (Optional) The end date until which the token signing certificate is valid, formatted as an RFC3339 date string (e.g. `2018-01-01T01:02:03Z`). Changing this field forces a new resource to be created.

* `service_principal_id` - (Required) The object ID of the service principal for which this certificate should be created. Changing this field forces a new resource to be created.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:

* `key_id` - A UUID used to uniquely identify the verify certificate.

* `thumbprint` - A SHA-1 generated thumbprint of the token signing certificate, which can be used to set the preferred signing certificate for a service principal.

* `start_date` - The start date from which the certificate is valid, formatted as an RFC3339 date string (e.g. `2018-01-01T01:02:03Z`).

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

## Import

Token signing certificates can be imported using the object ID of the associated service principal and the key ID of the verify certificate credential, e.g.

```shell
terraform import azuread_service_principal_token_signing_certificate.test 00000000-0000-0000-0000-000000000000/tokenSigningCertificate/11111111-1111-1111-1111-111111111111
```

-> This ID format is unique to Terraform and is composed of the service principal's object ID, the string "tokenSigningCertificate" and the verify certificate's key ID in the format `{ServicePrincipalObjectId}/tokenSigningCertificate/{CertificateKeyId}`.
33 changes: 33 additions & 0 deletions internal/helpers/credentials.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package helpers

import (
"bytes"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
Expand Down Expand Up @@ -40,6 +43,18 @@ func GetKeyCredential(keyCredentials *[]msgraph.KeyCredential, id string) (crede
return
}

func GetVerifyKeyCredentialFromCustomKeyId(keyCredentials *[]msgraph.KeyCredential, id string) (credential *msgraph.KeyCredential) {
if keyCredentials != nil {
for _, cred := range *keyCredentials {
if cred.KeyId != nil && strings.EqualFold(*cred.CustomKeyIdentifier, id) && strings.EqualFold(cred.Usage, msgraph.KeyCredentialUsageVerify) {
credential = &cred
break
}
}
}
return
}

func GetPasswordCredential(passwordCredentials *[]msgraph.PasswordCredential, id string) (credential *msgraph.PasswordCredential) {
if passwordCredentials != nil {
for _, cred := range *passwordCredentials {
Expand All @@ -52,6 +67,24 @@ func GetPasswordCredential(passwordCredentials *[]msgraph.PasswordCredential, id
return
}

func GetTokenSigningCertificateThumbprint(certByte []byte) (string, error) {
block, _ := pem.Decode(certByte)
if block == nil {
return "", fmt.Errorf("Failed to decode certificate block")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return "", fmt.Errorf("failed to parse certificate block data: %+v", err)
}
thumbprint := sha1.Sum(cert.Raw)

var buf bytes.Buffer
for _, f := range thumbprint {
fmt.Fprintf(&buf, "%02X", f)
}
return buf.String(), nil
}

func KeyCredentialForResource(d *schema.ResourceData) (*msgraph.KeyCredential, error) {
keyType := d.Get("type").(string)
value := d.Get("value").(string)
Expand Down
13 changes: 13 additions & 0 deletions internal/services/serviceprincipals/parse/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ func (id CredentialId) String() string {
return id.ObjectId + "/" + id.KeyType + "/" + id.KeyId
}

func SigningCertificateID(idString string) (*CredentialId, error) {
id, err := ObjectSubResourceID(idString, "tokenSigningCertificate")
if err != nil {
return nil, fmt.Errorf("unable to parse signing certificate ID: %v", err)
}

return &CredentialId{
ObjectId: id.objectId,
KeyType: id.Type,
KeyId: id.subId,
}, nil
}

func CertificateID(idString string) (*CredentialId, error) {
id, err := ObjectSubResourceID(idString, "certificate")
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/services/serviceprincipals/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func (r Registration) SupportedResources() map[string]*schema.Resource {
"azuread_service_principal_claims_mapping_policy_assignment": servicePrincipalClaimsMappingPolicyAssignmentResource(),
"azuread_service_principal_delegated_permission_grant": servicePrincipalDelegatedPermissionGrantResource(),
"azuread_service_principal_password": servicePrincipalPasswordResource(),
"azuread_service_principal_token_signing_certificate": servicePrincipalTokenSigningCertificateResource(),
"azuread_synchronization_job": synchronizationJobResource(),
"azuread_synchronization_secret": synchronizationSecretResource(),
}
Expand Down
Loading

0 comments on commit 24d2bf7

Please sign in to comment.