├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── CONTRIBUTING.md └── Sync.ps1 /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AWS IAM Identity Center Sync Script 2 | 3 | ## Overview 4 | On Demand SCIM provisioning of Azure AD to AWS IAM Identity Center with PowerShell 5 | - This repo is based on the steps outlined in this [article](https://aws.amazon.com/blogs/security/on-demand-scim-provisioning-of-azure-ad-to-aws-sso-with-powershell/) updated June 2023. 6 | 7 | ## June 2023 Update 8 | Made minor updates to the Configure section for Graph API Permissions 9 | 10 | 11 | ## March 2022 Update 12 | There is an updated version of this solution that uses Azure Functions and Keyvault to store the secrets in this [article](https://medium.com/i-love-my-local-farmer-engineering-blog/charting-our-identity-journey-in-aws-part-2-e4a99e6b1de3) and repo is [here](https://github.com/aws-samples/i-love-my-local-farmer/tree/main/IdentityJourney) 13 | 14 | ## Getting Started 15 | 16 | ### Prerequisites 17 | 18 | Configure **AWS IAM Identity Center** with the steps outlined in this [article](https://aws.amazon.com/blogs/aws/the-next-evolution-in-aws-single-sign-on/) 19 | 20 | ### App Registration 21 | 22 | #### Create 23 | 24 | - Navigate to Azure Active Directory 25 | - Open the _App Registrations_ blade 26 | - Choose _New Registration_ 27 | - Name: [_Example: AWS_] 28 | - All other options remain default 29 | - Choose _Register_ 30 | - Open the _Certificates & secrets_ blade 31 | - Choose _New client secret_ 32 | - Choose _Add_ 33 | - Copy the Value shown within Client Secrets 34 | 35 | #### Configure 36 | 37 | - Navigate to Azure Active Directory 38 | - Open the _App Registrations_ blade 39 | - Choose the app created in the previous task 40 | - Open the _API Permissions_ blade 41 | - Choose _Add a permission_ 42 | - Choose _Microsoft Graph_ 43 | - Choose _Application permissions_ 44 | - Scroll down to _Application_ and expand 45 | - Choose _Application.ReadWrite.OwnedBy_ 46 | - Choose _Synchronization.ReadWrite.All_ 47 | - Choose _Add permissions_ 48 | - Choose _Grant admin consent for [Tenant Name]_ 49 | 50 | ### Enterprise Application 51 | 52 | - Navigate to Azure Active Directory 53 | - Choose _Enterprise applications_ blade 54 | - Choose the app created in the previous task 55 | - Choose _Users and groups_ blade 56 | - Choose _Add user_ 57 | - Choose _Users_ 58 | - Choose service account created in previous task 59 | - Choose _Assign_ 60 | 61 | ### Inputs 62 | 63 | Gather the following properties for input into the PowerShell script 64 | 65 | * Navigate to Azure Active Directory 66 | * Choose _App Registrations_ blade 67 | * Choose the app created in the previous task 68 | * Copy the following values: 69 | - Tenant Id 70 | - Application Name 71 | - Application Id 72 | - Client Secret 73 | 74 | 75 | > Note: When copying and pasting in Windows, choose the PowerShell icon, then Edit > Paste. 76 | 77 | 78 | ## License 79 | 80 | This library is licensed under the MIT-0 License. See the LICENSE file. 81 | 82 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /Sync.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 7.0 2 | #region Get 3 | function Get-GraphSynchronizationJobId() { 4 | 5 | [CmdletBinding()] 6 | [OutputType([string])] 7 | param ( 8 | [Parameter(Mandatory = $true)] 9 | [ValidateNotNull()] 10 | [securestring]$AccessToken, 11 | 12 | [Parameter(Mandatory = $true)] 13 | [ValidateNotNull()] 14 | [string]$ServicePrincipalId 15 | ) 16 | 17 | begin { 18 | Write-Verbose -Message ("Initiating function " + $MyInvocation.MyCommand + " begin") 19 | $params = @{ 20 | Method = "GET" 21 | Uri = ("https://graph.microsoft.com/beta/servicePrincipals/" + $servicePrincipalId + "/synchronization/jobs") 22 | Headers = @{ 23 | "Authorization" = ("Bearer " + (ConvertFrom-SecureString -SecureString $accessToken -AsPlainText)) 24 | "Accept" = "application/json" 25 | } 26 | Body = @{ } 27 | } 28 | } 29 | 30 | process { 31 | Write-Verbose -Message ("Initiating function " + $MyInvocation.MyCommand + " process") 32 | try { 33 | $response = Invoke-RestMethod @params 34 | $response = $response.value[0].id 35 | } 36 | catch { 37 | Write-Error -ErrorRecord $_ -ErrorAction Stop 38 | } 39 | } 40 | 41 | end { 42 | Write-Verbose -Message ("Initiating function " + $MyInvocation.MyCommand + " end") 43 | return $response 44 | } 45 | 46 | } 47 | function Get-GraphServicePrincipal() { 48 | 49 | [CmdletBinding()] 50 | [OutputType([string])] 51 | param ( 52 | [Parameter(Mandatory = $true)] 53 | [ValidateNotNull()] 54 | [securestring]$AccessToken, 55 | 56 | [Parameter(Mandatory = $true)] 57 | [ValidateNotNull()] 58 | [string]$DisplayName 59 | ) 60 | 61 | begin { 62 | Write-Verbose -Message ("Initiating function " + $MyInvocation.MyCommand + " begin") 63 | $params = @{ 64 | Method = "GET" 65 | Headers = @{ 66 | "Authorization" = ("Bearer " + (ConvertFrom-SecureString -SecureString $accessToken -AsPlainText)) 67 | "Accept" = "application/json" 68 | } 69 | } 70 | } 71 | 72 | process { 73 | Write-Verbose -Message ("Initiating function " + $MyInvocation.MyCommand + " process") 74 | try { 75 | $response = Invoke-RestMethod @params -Uri "https://graph.microsoft.com/beta/servicePrincipals" 76 | $response.value | ForEach-Object { 77 | if ($_.displayName -like $displayName) { 78 | $objectId = $_.id 79 | } 80 | } 81 | if ($response.'@odata.nextLink') { 82 | Write-Verbose -Message "NextLink Section" 83 | $nextLink = $response.'@odata.nextLink' 84 | while ($nextLink -ne $null) { 85 | $output = Invoke-RestMethod @params -Uri $nextLink 86 | $output.value | ForEach-Object { 87 | if ($_.displayName -like $displayName) { 88 | $objectId = $_.id 89 | } 90 | } 91 | $nextLink = $output.'@odata.nextLink' 92 | 93 | } 94 | } 95 | } 96 | catch { 97 | Write-Error -ErrorRecord $_ -ErrorAction Stop 98 | } 99 | } 100 | 101 | end { 102 | Write-Verbose -Message ("Initiating function " + $MyInvocation.MyCommand + " end") 103 | return $objectId 104 | } 105 | 106 | } 107 | #endregion 108 | 109 | #region New 110 | function New-GraphAccessToken() { 111 | 112 | [CmdletBinding()] 113 | [OutputType([securestring])] 114 | param ( 115 | [Parameter(Mandatory = $true)] 116 | [string]$TenantId, 117 | 118 | [Parameter(Mandatory = $true)] 119 | [string]$ApplicationId, 120 | 121 | [Parameter(Mandatory = $true)] 122 | [securestring]$ClientSecret 123 | ) 124 | 125 | begin { 126 | Write-Verbose -Message ("Initiating function " + $MyInvocation.MyCommand + " begin") 127 | $params = @{ 128 | Method = "POST" 129 | Uri = ("https://login.microsoftonline.com/" + $tenantId + "/oauth2/token") 130 | Headers = @{ 131 | "Content-Type" = "application/x-www-form-urlencoded" 132 | "Accept" = "application/json" 133 | } 134 | Body = @{ 135 | "resource" = "https://graph.microsoft.com" 136 | "grant_type" = "client_credentials" 137 | "client_id" = "$applicationId" 138 | "client_secret" = "$(ConvertFrom-SecureString -SecureString $clientSecret -AsPlainText)" 139 | } 140 | } 141 | } 142 | 143 | process { 144 | Write-Verbose -Message ("Initiating function " + $MyInvocation.MyCommand + " process") 145 | try { 146 | $response = Invoke-RestMethod @params 147 | $accessToken = ConvertTo-SecureString -String "$($response.access_token)" -AsPlainText -Force 148 | } 149 | catch { 150 | Write-Error -ErrorRecord $_ -ErrorAction Stop 151 | } 152 | } 153 | 154 | end { 155 | Write-Verbose -Message ("Initiating function " + $MyInvocation.MyCommand + " end") 156 | return $accessToken 157 | } 158 | 159 | } 160 | #endregion 161 | 162 | #region Start 163 | function Start-GraphSynchronizationJob() { 164 | 165 | [CmdletBinding()] 166 | [OutputType([string])] 167 | param ( 168 | [Parameter(Mandatory = $true)] 169 | [ValidateNotNull()] 170 | [securestring]$AccessToken, 171 | 172 | [Parameter(Mandatory = $true)] 173 | [ValidateNotNull()] 174 | [string]$ServicePrincipalId, 175 | 176 | [Parameter(Mandatory = $true)] 177 | [ValidateNotNull()] 178 | [string]$JobId 179 | ) 180 | 181 | begin { 182 | Write-Verbose -Message ("Initiating function " + $MyInvocation.MyCommand + " begin") 183 | $params = @{ 184 | Method = "POST" 185 | Uri = ("https://graph.microsoft.com/beta/servicePrincipals/" + $servicePrincipalId + "/synchronization/jobs/" + $JobId + "/start") 186 | Headers = @{ 187 | "Authorization" = ("Bearer " + (ConvertFrom-SecureString -SecureString $accessToken -AsPlainText)) 188 | } 189 | } 190 | } 191 | 192 | process { 193 | Write-Verbose -Message ("Initiating function " + $MyInvocation.MyCommand + " process") 194 | try { 195 | $response = Invoke-RestMethod @params 196 | } 197 | catch { 198 | Write-Error -ErrorRecord $_ -ErrorAction Stop 199 | } 200 | } 201 | 202 | end { 203 | Write-Verbose -Message ("Initiating function " + $MyInvocation.MyCommand + " end") 204 | return $response 205 | } 206 | 207 | } 208 | #endregion 209 | 210 | #region Initialization 211 | function Initialization { 212 | 213 | [CmdletBinding()] 214 | param () 215 | 216 | begin { 217 | Clear-Host 218 | Write-Host "AWS Single Sign-On Integration - Sync`n" -ForegroundColor Yellow 219 | 220 | $tenantId = Read-Host -Prompt "Tenant Id" 221 | if ([string]::IsNullOrEmpty($tenantId)) { Write-Error -Message "Tenant Id cannot be blank." -ErrorAction Stop } 222 | 223 | $displayName = Read-Host -Prompt "Display Name" 224 | if ([string]::IsNullOrEmpty($displayName)) { Write-Error -Message "Display Name cannot be blank." -ErrorAction Stop } 225 | 226 | $applicationId = Read-Host -Prompt "App Id" 227 | if ([string]::IsNullOrEmpty($applicationId)) { Write-Error -Message "Application Id cannot be blank." -ErrorAction Stop } 228 | 229 | $clientSecret = Read-Host -Prompt "Client Secret" -AsSecureString 230 | if ([string]::IsNullOrEmpty($clientSecret)) { Write-Error -Message "Client Secret cannot be blank." -ErrorAction Stop } 231 | 232 | Clear-Host 233 | Write-Host "AWS Single Sign-On Integration - Sync Starting" -ForegroundColor Yellow 234 | } 235 | 236 | process { 237 | $accessToken = New-GraphAccessToken -TenantId $tenantId -ApplicationId $applicationId -ClientSecret $clientSecret 238 | $servicePrincipalId = Get-GraphServicePrincipal -AccessToken $accessToken -DisplayName $displayName 239 | $jobId = Get-GraphSynchronizationJobId -AccessToken $accessToken -ServicePrincipalId $servicePrincipalId 240 | Start-GraphSynchronizationJob -AccessToken $accessToken -ServicePrincipalId $servicePrincipalId -JobId $jobId 241 | } 242 | 243 | end { 244 | Write-Host "AWS Single Sign-On Integration - Sync Completed" -ForegroundColor Green 245 | } 246 | 247 | } 248 | #endregion 249 | 250 | Initialization 251 | --------------------------------------------------------------------------------