terraform-provider-azuread icon indicating copy to clipboard operation
terraform-provider-azuread copied to clipboard

How to create a SAML-enabled service principal that's linked to the application registration

Open MarkDordoy opened this issue 5 years ago • 27 comments

Hello

I want to define multiple saml based applications in azure AD Enterprise apps. From my understanding i can use tags on the service principal creation which will produce the single sign on options (Disabled, SAML, Password based, Linked). However i want to be able to create this with SAML selected.

I guess i have two questions from this:

  1. Can i create an Enterprise Application (Service principal) defined as SAML based
  2. How can i configure the SAML details, to be more specific, how can i change the User Attributes & Claims mappings.

Here is my current example which gets me so far:

resource "azuread_application" "poc_enterprise_app" {
    name                        = "mdordoy-test-app"
    homepage                    = "https://localhost:44308/"
    identifier_uris             = ["https://mydomain.onmicrosoft.com/mdordoy-test-app"]
    reply_urls                 = ["https://localhost:44308/"]
    available_to_other_tenants  = false
    oauth2_allow_implicit_flow  = false
    type                        = "webapp/api"
}

resource "azuread_service_principal" "poc_enterprise_app_sp" {
  application_id                = azuread_application.poc_enterprise_app.application_id
  app_role_assignment_required  = false

    tags = ["WindowsAzureActiveDirectoryIntegratedApp", "WindowsAzureActiveDirectoryCustomSingleSignOnApplication", "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1"]

}

Im currently running: Terraform v0.12.13

  • provider.azuread v0.6.0

I'll happily do a PR to update the documentation for this if you provide detail. If this is not implemented yet please let me know. If you can point me towards the right API i'm happy to write something to make this happen.

Thanks

MarkDordoy avatar Nov 06 '19 13:11 MarkDordoy

@MarkDordoy Were you able to make any progress on this? Looking to do the same.

nickadams675 avatar Apr 30 '20 04:04 nickadams675

Hi @MarkDordoy, the configuration you posted is the full extent to which you can currently configure a service principal with the provider. We are presently limited in different ways by SDK and API support, however we are planning to migrate to the Microsoft Graph API which will enable wider capabilities for us.

If anyone subscribed to this issue is able to look over the API documentation for AAD Graph and MS Graph, specifically for service principals, to help determine what aspects are supported by both APIs, that would be super helpful and appreciated.

AAD Graph link: https://docs.microsoft.com/en-gb/previous-versions/azure/ad/graph/api/entity-and-complex-type-reference#serviceprincipal-entity MS Graph Link: https://docs.microsoft.com/en-us/graph/api/resources/serviceprincipal?view=graph-rest-beta

manicminer avatar Jul 01 '20 21:07 manicminer

https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/application-saml-sso-configure-api

From: https://github.com/terraform-providers/terraform-provider-azuread/issues/215#issuecomment-655003738

manicminer avatar Jul 07 '20 18:07 manicminer

@manicminer would it be possible for me to call the graph API based on the current setup of this provider?

Ive successfully got this working using a powershell script to go and enable SSO post terraform run. I'd be happy to extend this provider (well try to) to also make that call based on a new tf parameter in the Service principal resource, but i'm not even sure if i can why it is using the Active Directory graph.

MarkDordoy avatar Jul 24 '20 16:07 MarkDordoy

@MarkDordoy currently no, but we're working on it and when we have an implementation for MS Graph we'll ping all waiting issues. If you have any working PS or other implementations please feel free to post them here as it may help for context when it gets implemented.

manicminer avatar Jul 24 '20 20:07 manicminer

@manicminer and @manicminer Here is my Terraform module for SAML with "Grant Admin Consent" workflow as well as adding the SAML attributes. Please note I have two GitHub Issues open with the Graph Dev team for the missing API functions:

az-cli missing parameter SAML Identifier missing on Service Principal via API create

resource "azuread_application" "this" {
  //https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/configure-single-sign-on-non-gallery-applications
  name                       = var.app_name
  identifier_uris            = [var.identifier_uri]
  reply_urls                 = var.reply_url
  available_to_other_tenants = false
  oauth2_allow_implicit_flow = true
  type                       = var.app_type
  group_membership_claims    = var.group_claims
  owners                     = var.owners
  required_resource_access {
    //https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/grant-admin-consent
    resource_app_id = "00000003-0000-0000-c000-000000000000"
    resource_access {
      id   = "5f8c59db-677d-491f-a6b8-5f174b11ec1d"
      type = "Scope"
    }
    resource_access {
      id   = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"
      type = "Scope"
    }
  }
  app_role {
    allowed_member_types = [
      "User"
    ]

    description  = "User"
    display_name = "User"
    is_enabled   = true
  }
  app_role {
    allowed_member_types = [
      "User"
    ]

    description  = "msiam_access"
    display_name = "msiam_access"
    is_enabled   = true
  }
  // We need to wait because Azure Graph API returns a 200 before its call-able #eventualconsistancy...
  provisioner "local-exec" {
    command = "sleep 20"
  }
  //https://github.com/Azure/azure-cli/issues/7579
  //Add metadata URL
  // provisioner "local-exec" {
  //   command = "az ad app update --id ${self.application_id} --set samlMetadataUrl=${var.saml_metadata_url}"
  //   }
  // We need to wait because Azure Graph API returns a 200 before its call-able #eventualconsistancy...
  // provisioner "local-exec" {
  //   command = "sleep 5"
  // }
  //https://github.com/Azure/azure-cli/issues/12946
  //https://github.com/Azure/azure-cli/issues/11534
  //https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims
  //Optional Claims for tokens
  provisioner "local-exec" {
    command = "az rest --method PATCH --uri 'https://graph.microsoft.com/v1.0/applications/${self.object_id}' --body '{\"optionalClaims\": {\"saml2Token\": [{\"name\": \"groups\", \"additionalProperties\": [\"sam_account_name\"]}]}}'"
  }
}

resource "azuread_service_principal" "this" {
  //https://github.com/Azure/azure-cli/issues/9250
  application_id             = azuread_application.this.application_id
  tags = [
    "WindowsAzureActiveDirectoryIntegratedApp",
    "WindowsAzureActiveDirectoryCustomSingleSignOnApplication",
    "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1"
  ]
  // We need to wait because Azure Graph API returns a 200 before its call-able #eventualconsistancy...
  provisioner "local-exec" {
    command = "sleep 20"
  }
  provisioner "local-exec" {
    command = "az ad sp update --id ${azuread_application.this.application_id} --set preferredSingleSignOnMode='saml'"
  }
  depends_on = [
     azuread_application.this
   ]
}

resource "null_resource" "grant_admin_constent" {
    count = var.admin_consent ? 1 : 0
     // https://docs.microsoft.com/en-us/cli/azure/ad/app/permission?view=azure-cli-latest#code-try-3
  provisioner "local-exec" {
    command = "sleep 20"
  }
  provisioner "local-exec" {
    command = "az ad app permission admin-consent --id ${azuread_application.this.application_id}"
  }
  depends_on = [
    azuread_service_principal.this
  ]
}

data "http" "idp_metadata" {
  url = var.idp_metadata_url
}

resource "vault_generic_secret" "vault-azure-sso-component" {
  path = "sites/${var.vault_site_name}/components/azure-sso"

  data_json = <<EOF
{
    "application_id":   "${azuread_application.this.application_id}",
    "description":      "This key is managed by terrafom, do not change/add/remove any values, see terraform module: azure-sso-app",
    "identifier_uri":   "${var.identifier_uri}"
}
EOF
//  "idp_metadata_file":   "${data.http.idp_metadata.body}"
  depends_on = [
    azuread_application.this
  ]
}

nickadams675 avatar Jul 24 '20 20:07 nickadams675

@nickadams675 Thanks for the post, I did consider using the local-exec but you dont get state stored with these calls.

@manicminer Do you have any timeline as to when we can expect the provider to be switched to the Microsoft Graph?

MarkDordoy avatar Jul 28 '20 15:07 MarkDordoy

@MarkDordoy It's something we're actively working on, but we don't have a timeline at this stage

manicminer avatar Jul 28 '20 22:07 manicminer

@MarkDordoy can you post the script you're using with ms graph api?

drdamour avatar Aug 27 '20 22:08 drdamour

@nickadams675 i saw both yoru issues got close to be rerouted, did you come up with a solution?

drdamour avatar Sep 15 '20 16:09 drdamour

The script i used to get me around this issue while this provider gets updated to the AzureAD Graph was the following (I cannot post all the solution but happy to share the functions i wrote to get this part working)

First i created a class for the Graph API Auth:

Class GraphAPIToken {
	[ValidateNotNullOrEmpty()]
	[string]$Grant_Type

	[ValidateNotNullOrEmpty()]
	[string]$Scope

	[ValidateNotNullOrEmpty()]
	[string]$Client_Id

	[ValidateNotNullOrEmpty()]
	[string]$Client_Secret

	[ValidateNotNullOrEmpty()]
	[string]$Tenant_Id

	[ValidateNotNullOrEmpty()]
	[datetime]$Token_Expiry

	[ValidateNotNullOrEmpty()]
	[string]$Token
	

	GraphAPIToken ([string]$grant_Type, [string]$scope, [string]$client_Id, [string]$client_secret, [string]$tenantId) {
		$this.Client_Id = $client_Id
		$this.Client_Secret = $client_secret
		$this.Scope = $scope
		$this.Grant_Type = $grant_Type
		$this.Tenant_Id = $tenantId
		$this.RequestToken()
	}

	[void]RequestToken() {
		
		try {
			$ReqTokenBody = @{
				Grant_Type    = $this.grant_Type
				Scope         = $this.scope
				client_Id     = $this.Client_Id
				Client_Secret = $this.Client_Secret
			} 

			$response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$($this.Tenant_Id)/oauth2/v2.0/token" -Method POST -Body $ReqTokenBody -UseBasicParsing
			$this.Token = $response.access_token
			$this.Token_Expiry = (Get-Date).AddSeconds($response.expires_in - 300)
		}
		catch {
			throw "Unable to query https://login.microsoftonline.com/$($this.Tenant_Id)/oauth2/v2.0/token and receive a token"
		}
	}
	
	[string]GetToken() {

		if ($this.Token_Expiry -lt (get-date)) {
			$this.RequestToken()
		}

		return $this.Token
	}
}

I invoked the class object by using this:

$script:graphAPIToken = [GraphAPIToken]::new("client_credentials", 'https://graph.microsoft.com/.default', $azureADCred.clientId, $azureADCred.clientSecret, $azureADCred.tenantId)

Then i made graph calls using this function which was called from my main script:

Function Invoke-SAMLEnablement {
	[cmdletbinding(SupportsShouldProcess = $true)]
	param(
		[Parameter(Mandatory)]
		[Microsoft.Open.AzureAD.Model.ServicePrincipal]$spn
	)

	$accessToken = $script:graphAPIToken.GetToken()
	$header = @{Authorization = "Bearer $($accessToken)" }
    
	try {
		$spnobject = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$($spn.ObjectId)" -headers $header -UseBasicParsing
	}
	catch {
		Write-Log "Error occuring trying to make graph call. $_" -MessageLevel ERROR
	}
	
	if ($null -eq $spnobject.preferredSingleSignOnMode) {
		if ($PSCmdlet.ShouldProcess("SPN $($spn.DisplayName) has preferredSingleSignOnMode set to null, setting to saml")) {
			write-log "SPN $($spn.DisplayName) has preferredSingleSignOnMode set to null, setting to saml" -MessageLevel INFO
			
			$patch = @{"preferredSingleSignOnMode" = "saml" }
			try {
				Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$($spn.ObjectId)" -headers $header -Method Patch -Body ($patch | ConvertTo-Json) -ContentType application/json -UseBasicParsing
			}
			catch {
				Write-log " Failed to update perferred SSO Mode for enteprise app $($spn.DisplayName), StatusCode: $($_.Exception.Response.StatusCode.value__)" -MessageLevel ERROR
				write-log "$_" -MessageLevel ERROR
			}
		}
	}
}

My end solution was terraform creating the app registration and SPN, then a powershell script than ran in a nomad job (think a cron job) that would go and enable the SAML endpoint, check on things like conditional accces policies and add them, then finally flatten our AD groups (as azure hates nesting) and apply those to the ACL of the enterprise app. It would also diff the groups and always made sure they are in sync.

Hope this helps.

MarkDordoy avatar Sep 16 '20 09:09 MarkDordoy

Thanks for the workarounds... Are there any updates on progress with the terraform provider?

dwrusse avatar May 06 '21 20:05 dwrusse

@dwrusse Please subscribe to #323 for updates, thanks!

manicminer avatar May 13 '21 12:05 manicminer

@manicminer hi, now that microsoft graph support has been added to the provider, would you have any hint here on how to consume it to be able to resolve this issue (SAML settings on entreprise apps). Thanks a lot.

Leooo avatar Jun 21 '21 11:06 Leooo

@Leooo There are a few features requested in this issue which are all due to be added in v2.0. There are also SAML configurations which cannot be added outside the Azure Portal, but I don't believe those have been discussed here (see #395)

manicminer avatar Jun 21 '21 12:06 manicminer

Maybe I'm a bit early, but I'm looking at how to create a SAML-enabled service principal in the v2 microsoft graph branch.

Looking at the MS docs it would look like there is no direct way to set the preferredSingleSignOnMode property on the service principal to saml - as they are using a PATCH request on the resource instead. Is this correct?

I hope not, but otherwise I would expect to see a preferred_single_sign_on_mode attribute in the service principal v2 version which is not there yet

Leooo avatar Jun 28 '21 09:06 Leooo

@Leooo We'll be supporting that attribute but it isn't yet added to the v2 branch

manicminer avatar Jun 28 '21 09:06 manicminer

Hi issue subscribers!

As there are a few different pieces of configuration discussed in this thread, I wanted to clarify the specific needs in respect of configuring a SAML enabled service principal. As I understand from the earlier comments, these are:

  • Setting saml2Token optional claims on the app registration, with additional properties
  • Setting the preferredSingleSignOnMode for the service principal
  • Setting some magic tags on the service principal

To the best of my knowledge, the last item is possible today using the tags property in azuread_service_principal. The first two are due to be enabled in v2.0.0.

Is there anything else? I realise this is not a trivial ask, and of course we can only offer whatever the API supports, but I wouldn't want to close this issue prematurely.

manicminer avatar Jul 13 '21 11:07 manicminer

I'm not a specialist (learning as I go), but in my case the goal is to setup SAML SSO - so this doc is a good starting point.

Maybe those are already existing, but I see there the need to:

  • [x] set preferredSingleSignOnMode (you already mentioned it)
  • [x] set redirectUris / identifierUris (I think already possible)
  • [ ] add appRoles ?
  • [ ] configure ClaimsMappingPolicy ?
  • [ ] assign a ClaimsMappingPolicy to a servicePrincipal?
  • [ ] create a signing certificate ?
  • [ ] activate the custom signin key? preferredTokenSigningKeyThumbprint

Not sure if those are absolutely needed or have already been implemented tbh. Happy to test any branch you have.

Leooo avatar Jul 13 '21 11:07 Leooo

Thanks @Leooo, that's super helpful 👍

manicminer avatar Jul 21 '21 00:07 manicminer

@manicminer hi, do you know when the v2.0.0 will be available to consume or test please? Thanks

Leooo avatar Aug 09 '21 10:08 Leooo

@Leooo It should be out in the next few days

manicminer avatar Aug 09 '21 12:08 manicminer

Assigning this to the Future milestone as there are several pieces to achieving what you can get in the portal. We're shipping features as we're able to, but some of these are going to be blocked upstream until there is API support. We'll try to mention this issue on related PRs.

manicminer avatar Sep 07 '21 18:09 manicminer

Publishing a comment for those who get following error in AWS Cognito:

Error in SAML response processing: Invalid user attributes: email: Attribute is required.

The field "email" is not included in the SAML response, if the Enterprise Application is created via API. Set email explicitly in the section optional_claims:

resource "azuread_application" "this" {
...
  optional_claims {
    saml2_token {
      essential = true
      name = "email"
    }
  }
...
}

Terraform import is not catching this, if an application had been provisioned in the Azure UI.

antonbormotov avatar Sep 22 '21 03:09 antonbormotov

How can we create the saml single sign on certificate with Terraform for Azure AD enterprise app? Is this supported? Now I have to create the signing certificate from UI. Also this parameter is empty saml_metadata_url from the resource.

Satak avatar Jan 17 '22 08:01 Satak

@Satak It is possible: https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/service_principal_certificate However, requires to keep the self-signed certificate somewhere (you don't want to keep it in git) That is why I provisioned it the UI.

antonbormotov avatar Jan 17 '22 08:01 antonbormotov

As of 19th Aug, 2022, what's the status of adding SAML claims and attributes to a service principal? It seems the claims mapping policy resources do this but can't necessarily do everything? ie group claims?

anwickes avatar Aug 19 '22 07:08 anwickes