Open sseekamp0 opened 2 years ago
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
]
}
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"
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]
}
thumprint
from the certificate.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'
@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
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?!
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]
}
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": {}
}
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": {}
}
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: []
@vschum I tried your solution and still the cert needed to be activated manually ,, does anyone have another solution maybe?
"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'
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 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.
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: https://github.com/hashicorp/terraform-provider-azuread/blob/cc4d2ced1633c23db9e9750f9a33903760e0928f/internal/helpers/credentials.go#L111. It is hard coded to only create credentials with "Verify" as the usage.
Wow, great find John. Anyone with the skill to add the 2nd API call to the resource creation?
I wonder if it is enough to make Usage
configurable on azuread_service_principal_certificate
.
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"?
I've had a bit of poke into this - the existing resource provider for certificates is available here -
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.
-> https://github.com/manicminer/hamilton/commit/221ac23e6a123c6edb242590f0e9150c5f01e288
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
https://github.com/hashicorp/terraform-provider-azuread/issues/732 https://github.com/hashicorp/terraform-provider-azuread/pull/741
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?
Tested and approved !
No need of https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/service_principal_certificate anymore
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
}
}
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
any updates on this issue? We are facing the same problem. Thx.
@ojc97 were you able to address this?
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"
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
Community Note
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)
References