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

Create a SSO SAML Signing Certificate #823

Open
sseekamp0 opened this issue Jun 13, 2022 · 27 comments
Open

Create a SSO SAML Signing Certificate #823

sseekamp0 opened this issue Jun 13, 2022 · 27 comments

Comments

@sseekamp0
Copy link

sseekamp0 commented Jun 13, 2022

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritise this request
  • Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritise the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Description

I would like to open a new feature request to enable creation of a SSO Signing Certificate. This functionality currently exists via the API, but seems to be missing in the terraform provider.

API Reference

Azure AD service principle certificate only provides importing a certificate. This request would be for creating a SSO certificate.

New or Affected Resource(s)

  • azuread_service_principal_certificate

References

@rajish
Copy link

rajish commented Jun 22, 2022

It is possible to create the certificate, but apparently, the activation has to be done manually or using Azure CLI.
Also for some unknown reason the azuread_service_principal.this.saml_metadata_url is always null when trying to read it.

My code so far:

data "azuread_client_config" "current" {}

resource "random_uuid" "oauth2_permission_scope" {
  keepers = {
    domain_name = var.saml_identifier_uri
  }
}

resource "random_uuid" "app_role_user" {
  keepers = {
    domain_name = var.saml_identifier_uri
  }
}

resource "random_uuid" "app_role_msiam_access" {
  keepers = {
    domain_name = var.saml_identifier_uri
  }
}

resource "azuread_application" "this" {
  display_name = var.display_name
  owners       = [data.azuread_client_config.current.object_id]
  identifier_uris = [var.saml_identifier_uri]
  api {
    oauth2_permission_scope {
      id = random_uuid.oauth2_permission_scope.id
      admin_consent_description = "Allow the application to access ${var.display_name} on behalf of the signed-in user."
      admin_consent_display_name = "Access ${var.display_name}"
      user_consent_description = "Allow the application to access ${var.display_name} on behalf of the signed-in user."
      user_consent_display_name = "Access ${var.display_name}"
      enabled = true
      type = "User"
      value = "user_impersonation"
    }
  }

  app_role {
    allowed_member_types = ["User"]
    description          = "User"
    display_name         = "User"
    id                   = random_uuid.app_role_user.id
  }
  app_role {
    allowed_member_types = ["User"]
    description          = "msiam_access"
    display_name         = "msiam_access"
    id                   = random_uuid.app_role_msiam_access.id
  }

  optional_claims {
    saml2_token {
      essential = true
      name = "email"
      additional_properties = ["sam_account_name"]
    }
  }

  web {
    homepage_url = "https://${var.app_fqdn}"
    logout_url = "https://${var.app_fqdn}"
    redirect_uris = [
      "https://${var.app_fqdn}/saml/acs"
    ]

    implicit_grant {
      access_token_issuance_enabled = false
      id_token_issuance_enabled     = true
    }
  }
}

resource "azuread_service_principal" "this" {
  application_id               = azuread_application.this.application_id
  app_role_assignment_required = false
  owners                       = [data.azuread_client_config.current.object_id]

  feature_tags {
    enterprise = true
    gallery = true
    custom_single_sign_on = true
  }

  preferred_single_sign_on_mode = "saml"

  notification_email_addresses  = var.notification_emails
}

resource "tls_private_key" "this" {
  algorithm = "RSA"
  rsa_bits  = 2048
}

resource "tls_self_signed_cert" "this" {
  allowed_uses          = ["client_auth", "server_auth"]
  key_algorithm         = "RSA"
  private_key_pem       = tls_private_key.this.private_key_pem
  validity_period_hours = 4321
  subject {
    common_name = azuread_application.this.display_name
    organization = var.organisation_name
  }
}

resource "azuread_service_principal_certificate" "this" {
  service_principal_id = azuread_service_principal.this.id
  type                  = "AsymmetricX509Cert"
  value                 = tls_self_signed_cert.this.cert_pem
  end_date_relative     = "4320h"
}

resource "null_resource" "manual_certificate_approve" {
  provisioner "local-exec" {
    command = "echo '\n\n-= Please activate the SSO certificate  THEN RUN `touch /tmp/ididit`; I WILL WAIT HERE =-\n\n'; while ! test -f /tmp/ididit; do sleep 1; done"
  }
  depends_on = [azuread_service_principal_certificate.this]
}

resource "azuread_claims_mapping_policy" "this" {
  definition   = [
    jsonencode(
      {
        ClaimsMappingPolicy = {
          ClaimsSchema = [
            {
              ID            = "employeeid"
              JwtClaimType  = "name"
              SamlClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
              Source        = "user"
            },
            {
              ID            = "mail"
              JwtClaimType  = "mail"
              SamlClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
              Source        = "user"
            },
            {
              ID            = "groups"
              JwtClaimType  = "groups"
              SamlClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups"
              source        = "user"
            }
          ]
          IncludeBasicClaimSet = "true"
          Version              = 1
        }
      }
    ),
  ]
  display_name = "${var.display_name}_cmp"
}

resource "azuread_service_principal_claims_mapping_policy_assignment" "this" {
  claims_mapping_policy_id = azuread_claims_mapping_policy.this.id
  service_principal_id     = azuread_service_principal.this.id
}

data "azuread_service_principal" "this" {
  object_id = azuread_service_principal.this.id
}

data "http" "idp_metadata" {
  url = data.azuread_service_principal.this.saml_metadata_url
  request_headers = {
    Accept = "application/xml"
  }
  depends_on = [
    azuread_service_principal.this,
    azuread_service_principal_certificate.this
  ]
}

@NEViLLLLL
Copy link

I tried the certificate from the computer and generated with tls_self_signed_cert. It really needs to be activated manually on the SSO page. But it does not show up in "Federation Metadata". If you manually import the PFX certificate, it is displayed in "Federation Metadata".

I tried @rajish code. The certificate did not show up in "Federation Metadata"

@rajish
Copy link

rajish commented Jun 23, 2022

Regarding the metadata link, there's a workaround that conforms the Azure Graph documentation:

data "http" "idp_metadata" {
  url = "https://login.microsoftonline.com/${data.azuread_client_config.current.tenant_id}/federationmetadata/2007-06/federationmetadata.xml?appid=${azuread_application.this.application_id}"
  request_headers = {
    Accept = "application/xml"
  }
  depends_on = [
    azuread_service_principal.this,
    azuread_service_principal_certificate.this
  ]
}

But the certificate activation is a pain for two reasons, see the commented out code:

resource "null_resource" "manual_certificate_approve" {
  provisioner "local-exec" {
    command = "echo '\n\n-= Please activate the SSO certificate  THEN RUN `touch /tmp/ididit`; I WILL WAIT HERE =-\n\n'; while ! test -f /tmp/ididit; do sleep 1; done"
    # TODO no thumbprint here
    # command = "az ad sp update --id ${azuread_application.this.application_id} --set preferredTokenSigningKeyThumbprint=${tls_self_signed_cert.this.thumbprint}"
  }
  depends_on = [azuread_service_principal_certificate.this]
}
  1. There's no way to retrieve the thumprint from the certificate.
  2. Even when I tried running the command in a terminal with manually pasted values I get an error:
This command or command group has been migrated to Microsoft Graph API. Please carefully review all breaking changes introduced during this migration: https://docs.microsoft.com/cli/azure/microsoft-graph-migration
The command failed with an unexpected error. Here is the traceback:
'GraphClient' object has no attribute 'service_principals'
Traceback (most recent call last):
  File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/knack/cli.py", line 231, in invoke
    cmd_result = self.invocation.execute(args)
  File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 663, in execute
    raise ex
  File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 726, in _run_jobs_serially
    results.append(self._run_job(expanded_arg, cmd_copy))
  File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 718, in _run_job
    return cmd_copy.exception_handler(ex)
  File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/command_modules/role/commands.py", line 54, in graph_err_handler
    raise ex
  File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 697, in _run_job
    result = cmd_copy(params)
  File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 333, in __call__
    return self.handler(*args, **kwargs)
  File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/command_operation.py", line 240, in handler
    result = cached_put(self.cmd, setter, **setterargs)
  File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 452, in cached_put
    return _put_operation()
  File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 446, in _put_operation
    result = operation(**kwargs)
  File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/command_modules/role/custom.py", line 988, in patch_service_principal
    object_id = _resolve_service_principal(graph_client.service_principals, identifier)
AttributeError: 'GraphClient' object has no attribute 'service_principals'
To open an issue, please run: 'az feedback'

@brodster2
Copy link

@rajish, not sure if this is a recent change from Microsoft but when I call this endpoint, and point to an existing service principal I've created with Terraform (that doesn't have a cert) it generates a certificate and activates it. Not sure if this is new or just the way my app registration and service principal are configured? the script I used to call the endpoint https://gist.github.com/brodster2/16dfc11cdb55e4a84e3903dfab9f4bf4

@dcopestake
Copy link

dcopestake commented Jul 15, 2022

I tried the certificate from the computer and generated with tls_self_signed_cert. It really needs to be activated manually on the SSO page. But it does not show up in "Federation Metadata". If you manually import the PFX certificate, it is displayed in "Federation Metadata".

I tried @rajish code. The certificate did not show up in "Federation Metadata"

I'm actually having exactly the same issue, did you ever find a resolution?

Basically if I provide my own cert via a azuread_service_principal_certificate resource for some reason it just doesn't show up in the App Federation Metadata, but if I deactivate and add a new one that's been automatically generated via the Azure Portal that cert does show up?!

@vschum
Copy link

vschum commented Jul 29, 2022

Using the snippet from @rajish I was able to activate a token by using openssl to generate the thumbprint.

resource "null_resource" "manual_certificate_approve" {
  provisioner "local-exec" {
    command = "echo \"${tls_self_signed_cert.example.cert_pem}\" > /tmp/${tls_self_signed_cert.example.id}.pem"
    interpreter = ["/bin/bash", "-c"]
  }
  provisioner "local-exec" {
    command = "az ad sp update --id ${azuread_application.example.application_id} --set preferredTokenSigningKeyThumbprint=$(openssl x509 -in /tmp/${tls_self_signed_cert.example.id}.pem -noout -fingerprint | grep -oE '[:0-9A-F]{59}' | sed -e 's/://g')"
  }
  provisioner "local-exec" {
    command = "rm -rf /tmp/${tls_self_signed_cert.example.id}.pem"
    interpreter = ["/bin/bash", "-c"]
  }
  depends_on = [azuread_service_principal_certificate.example]
}

@Annihilatopia
Copy link

Hey @vschum which az cli version did you run the exec command on? Doesn't seem to work on

{
  "azure-cli": "2.39.0",
  "azure-cli-core": "2.39.0",
  "azure-cli-telemetry": "1.0.6",
  "extensions": {}
}

@vschum
Copy link

vschum commented Aug 3, 2022

Worked for me with the following version.

{
  "azure-cli": "2.38.0",
  "azure-cli-core": "2.38.0",
  "azure-cli-telemetry": "1.0.6",
  "extensions": {}
}

@Annihilatopia
Copy link

That is strange, I've downgraded az cli to 2.38.0 and I'm still getting a empty list response when trying to update preferredTokenSigningKeyThumbprint.

Couldn't find 'preferredTokenSigningKeyThumbprint=<REDACTED>' in ''. Available options: []

@sherifkayad
Copy link

@vschum I tried your solution and still the cert needed to be activated manually ,, does anyone have another solution maybe?

@anwickes
Copy link

"az ad sp update --id ${azuread_application.example.application_id} --set preferredTokenSigningKeyThumbprint=$(openssl x509 -in /tmp/${tls_self_signed_cert.example.id}.pem -noout -fingerprint | grep -oE '[:0-9A-F]{59}' | sed -e 's/://g')"

i'm also getting the following error when using the above code.

The command failed with an unexpected error. Here is the traceback: 'GraphClient' object has no attribute 'service_principals' Traceback (most recent call last): File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/knack/cli.py", line 231, in invoke cmd_result = self.invocation.execute(args) File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 663, in execute raise ex File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 726, in _run_jobs_serially results.append(self._run_job(expanded_arg, cmd_copy)) File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 718, in _run_job return cmd_copy.exception_handler(ex) File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/command_modules/role/commands.py", line 54, in graph_err_handler raise ex File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 697, in _run_job result = cmd_copy(params) File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 333, in __call__ return self.handler(*args, **kwargs) File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/command_operation.py", line 240, in handler result = cached_put(self.cmd, setter, **setterargs) File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 452, in cached_put return _put_operation() File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 446, in _put_operation result = operation(**kwargs) File "/usr/local/Cellar/azure-cli/2.37.0/libexec/lib/python3.10/site-packages/azure/cli/command_modules/role/custom.py", line 988, in patch_service_principal object_id = _resolve_service_principal(graph_client.service_principals, identifier) AttributeError: 'GraphClient' object has no attribute 'service_principals'

@anwickes
Copy link

anwickes commented Aug 17, 2022

This is working for me. Be interested to see if others are able to use it too.

resource "azuread_service_principal_certificate" "cert" {
  service_principal_id          = azuread_service_principal.app.id
  type                          = "AsymmetricX509Cert"
  value                         = file("sp.pem")
  end_date                      = var.sp_cert_end_date
}

resource "null_resource" "activate_saml_cert" {
  triggers = {
    value = azuread_service_principal_certificate.cert[0].value
    end_date = azuread_service_principal_certificate.cert[0].end_date
  }

  provisioner "local-exec" {
    command = <<EOT
    az login --service-principal --tenant $ARM_TENANT_ID -u $ARM_CLIENT_ID -p cert.pem --allow-no-subscriptions --output none
    thumbprint=$(openssl x509 -in sp.pem -noout -fingerprint | sed 's/SHA1 Fingerprint=//g; s/://g')
    az rest --method PATCH --uri 'https://graph.microsoft.com/v1.0/servicePrincipals/${azuread_service_principal.app.object_id}' --body "{'preferredTokenSigningKeyThumbprint':'$thumbprint'}" --headers Content-Type=application/json
    EOT
  }
}

@cruikshj
Copy link

This is working for me. Be interested to see if others are able to use it too.

resource "azuread_service_principal_certificate" "cert" {
  service_principal_id          = azuread_service_principal.app.id
  type                          = "AsymmetricX509Cert"
  value                         = file("sp.pem")
  end_date                      = var.sp_cert_end_date
}

resource "null_resource" "activate_saml_cert" {
  triggers = {
    value = azuread_service_principal_certificate.cert[0].value
    end_date = azuread_service_principal_certificate.cert[0].end_date
  }

  provisioner "local-exec" {
    command = <<EOT
    az login --service-principal --tenant $ARM_TENANT_ID -u $ARM_CLIENT_ID -p cert.pem --allow-no-subscriptions --output none
    thumbprint=$(openssl x509 -in sp.pem -noout -fingerprint | sed 's/SHA1 Fingerprint=//g; s/://g')
    az rest --method PATCH --uri 'https://graph.microsoft.com/v1.0/servicePrincipals/${azuread_service_principal.app.object_id}' --body "{'preferredTokenSigningKeyThumbprint':'$thumbprint'}" --headers Content-Type=application/json
    EOT
  }
}

This sort of worked for me. While it seems to have activated my cert according to the Azure Portal, the cert is still not configured completely it seems. I receive the error "AADSTS500031: Cannot find signing certificate configured." when trying to test SSO, and the metadata does not contain the cert.

@cruikshj
Copy link

I compared what is created by the UI and what is created by the azuread_service_principal_certificate resource. The difference is that the UI creates a certificate and adds it twice to the principal, once with the usage value of "Verify" and the other with "Sign". The resource only creates the "Verify" credential. I suspect a part of the problem is the assumption this resource provider is making here:

Usage: msgraph.KeyCredentialUsageVerify,
. It is hard coded to only create credentials with "Verify" as the usage.

@anwickes
Copy link

Wow, great find John. Anyone with the skill to add the 2nd API call to the resource creation?

@cruikshj
Copy link

I wonder if it is enough to make Usage configurable on azuread_service_principal_certificate.

@anwickes
Copy link

Bit of a novice on how this stuff all sits together but i'm assuming you would need to issue the API call twice upon resource creation? Once for the "verify", another for "sign"?

@matt-tyler
Copy link

matt-tyler commented Nov 28, 2022

I've had a bit of poke into this - the existing resource provider for certificates is available here -

-> https://github.com/hashicorp/terraform-provider-azuread/blob/main/internal/services/serviceprincipals/service_principal_certificate_resource.go

The last update to this was on Nov 12, 2021, but the underlying client it uses to controls resource was only updated to have addTokenSigningCertificate and set the thumbprint (although Im not sure setting the thumbprint is required - i think this might be done automatically by the aforementioned API call and this is somewhat validate by @brodster2) in February. See below for the commit.

-> manicminer/hamilton@221ac23

The existing resource provider uses a more involved method of making direct calls to the key endpoint (which addTokenSigningCertificate effectively wraps) and then doesn't set the thumbprint.

The two approaches are actually outlined in step 4 of the below of the documentation

It should be enough to modify the resource creation routine to use addTokenSigningCertificate instead - the existing routines to may not need to change - e.g. deletion would still involve needing to look the key up by the key ID.

@manicminer I can look into prepping a PR to fix this if you would like? I'm currently fixing this in a private provider and once I've 100% confirmed this fixes behavior I can look into preparing an appropriate change?

EDIT:

This is effectively covered by the following issues & draft pull request

#732
#741

@karol-treeline
Copy link

karol-treeline commented Dec 14, 2022

hey hey 👋

thanks for your input, @cruikshj I followed your steps and I got the same result, my metadata file doesn't contain the certificate.

What is the status of this issue?

tagur87 added a commit to tagur87/terraform-provider-azuread that referenced this issue 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 hashicorp#732
And part of hashicorp#823
tagur87 added a commit to tagur87/terraform-provider-azuread that referenced this issue 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 hashicorp#732
And part of hashicorp#823
tagur87 added a commit to tagur87/terraform-provider-azuread that referenced this issue Jan 11, 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 hashicorp#732
And part of hashicorp#823
tagur87 added a commit to tagur87/terraform-provider-azuread that referenced this issue Jan 12, 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 hashicorp#732
And part of hashicorp#823
tagur87 added a commit to tagur87/terraform-provider-azuread that referenced this issue Jan 12, 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 hashicorp#732
And part of hashicorp#823
tagur87 added a commit to tagur87/terraform-provider-azuread that referenced this issue Jan 12, 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 hashicorp#732
And part of hashicorp#823
@tagur87
Copy link
Contributor

tagur87 commented Jan 19, 2023

@jallaix
Copy link

jallaix commented Jan 19, 2023

@skgsergio
Copy link

Thanks for your work @tagur87, we've started this week creating an AD App for SAML singing and we needed this.

We've done this for including the rotation, in case it is useful for someone else:

resource "time_rotating" "saml-certificate" {
  rotation_years = 3
}

resource "azuread_service_principal_token_signing_certificate" "saml-certificate" {
  service_principal_id = azuread_service_principal.app.id
  display_name         = "CN=${var.app_name} 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
  }
}

@ojc97
Copy link

ojc97 commented May 31, 2023

I have been using the azuread_service_principal_token_signing_certificate but i cannot figure out how to set the Signing Option. Its default is Sign SAML Response, but i need to set it to Sign SAML Response and Assertion but cannot see any terraform option to do it.

Has anyone been successful with this?

Thanks

@valentinahermann
Copy link

any updates on this issue? We are facing the same problem. Thx.

@rkosyk
Copy link

rkosyk commented Jul 4, 2024

@ojc97 were you able to address this?

@Matioski
Copy link

Matioski commented Oct 25, 2024

I have been using the azuread_service_principal_token_signing_certificate but i cannot figure out how to set the Signing Option. Its default is Sign SAML Response, but i need to set it to Sign SAML Response and Assertion but cannot see any terraform option to do it.

Has anyone been successful with this?

Thanks

Hello, I was able to address this via the tokenIssuancePolicies:

resource "azuread_service_principal_token_signing_certificate" "saml_signing_cert" {
  for_each = { for app in local.apps_map : app.app_name => app if app.type == "saml" }

  service_principal_id = azuread_service_principal.sp[each.key].id
  display_name         = "CN=${each.value.app_name} SSO Certificate"
  end_date             = time_rotating.saml_certificate.rotation_rfc3339

  provisioner "local-exec" {
    command = <<EOT
    az rest --method GET --uri https://graph.microsoft.com/v1.0/servicePrincipals/${azuread_service_principal.sp[each.key].object_id}/tokenIssuancePolicies \
    | jq -r '.value[0].id' > ./tmp/${each.key}_policy.txt
    EOT
  }

  provisioner "local-exec" {
    command = <<EOT
    az rest --method PATCH --uri https://graph.microsoft.com/v1.0/policies/tokenIssuancePolicies/$(cat ./tmp/${each.key}_policy.txt) \
    --headers 'Content-Type=application/json' \
    --body   '{
     "definition": [
        "{\n  \"TokenIssuancePolicy\": {\n    \"Version\": 1,\n    \"SigningAlgorithm\": \"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\",\n    \"TokenResponseSigningPolicy\": \"ResponseAndToken\",\n    \"SamlTokenVersion\": \"2.0\"\n  }\n}"
      ]
    }'
    EOT
  }
}

but the easiest way is actually to create the App resource using the template ID for the non-gallery apps, this will set the policy correctly automatically.
The template id to set is:
template_id = "8adf8e6e-67b2-4cf2-a259-e3dc5476c621"

@Matioski
Copy link

Matioski commented Oct 25, 2024

Thanks for your work @tagur87, we've started this week creating an AD App for SAML singing and we needed this.

We've done this for including the rotation, in case it is useful for someone else:

resource "time_rotating" "saml-certificate" {
  rotation_years = 3
}

resource "azuread_service_principal_token_signing_certificate" "saml-certificate" {
  service_principal_id = azuread_service_principal.app.id
  display_name         = "CN=${var.app_name} 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
  }
}

For me an issue remains as this is not updating: preferredTokenSigningKeyEndDateTime. Has anyone succeeded in that? Thanks

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

No branches or pull requests