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

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

Open
MarkDordoy opened this issue Nov 6, 2019 · 27 comments · Fixed by #481

Comments

@MarkDordoy
Copy link
Contributor

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

@nickadams675
Copy link

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

@manicminer
Copy link
Member

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

@MarkDordoy
Copy link
Contributor Author

@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.

@manicminer
Copy link
Member

@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.

@nickadams675
Copy link

nickadams675 commented Jul 24, 2020

@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
  ]
}

@MarkDordoy
Copy link
Contributor Author

@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?

@manicminer
Copy link
Member

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

@drdamour
Copy link

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

@drdamour
Copy link

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

@MarkDordoy
Copy link
Contributor Author

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.

@dwrusse
Copy link

dwrusse commented May 6, 2021

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

@manicminer
Copy link
Member

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

@manicminer manicminer added this to the v2.0.0 milestone May 27, 2021
@Leooo
Copy link

Leooo commented Jun 21, 2021

@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.

@manicminer
Copy link
Member

@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)

@Leooo
Copy link

Leooo commented Jun 28, 2021

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

@manicminer
Copy link
Member

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

@manicminer
Copy link
Member

manicminer commented Jul 13, 2021

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.

@Leooo
Copy link

Leooo commented Jul 13, 2021

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:

  • set preferredSingleSignOnMode (you already mentioned it)
  • 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.

@manicminer
Copy link
Member

Thanks @Leooo, that's super helpful 👍

@manicminer manicminer reopened this Jul 22, 2021
@manicminer manicminer modified the milestones: v2.0.0, v2.1.0 Jul 26, 2021
@Leooo
Copy link

Leooo commented Aug 9, 2021

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

@manicminer
Copy link
Member

@Leooo It should be out in the next few days

@manicminer manicminer modified the milestones: v2.1.0, v2.2.0, Future Sep 2, 2021
@manicminer
Copy link
Member

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.

@antonbormotov
Copy link

antonbormotov commented Sep 22, 2021

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.

@Satak
Copy link

Satak commented Jan 17, 2022

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.

@antonbormotov
Copy link

antonbormotov commented Jan 17, 2022

@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.

@anwickes
Copy link

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?

tiwood pushed a commit to tiwood/terraform-provider-azuread that referenced this issue Feb 19, 2024
Add missing checks for enabling OIDC auth
tiwood pushed a commit to tiwood/terraform-provider-azuread that referenced this issue Feb 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants