. $PSScriptRoot\..\..\Load-PesterModules.ps1 $here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.' $global:functionPath = Join-Path -Path $here -ChildPath $sut InModuleScope -ModuleName Alkami.DevOps.SystemEngineering -ScriptBlock { Write-Host "InModuleScope - Overriding SUT: $global:functionPath" Import-Module $global:functionPath -Force $inScopeModule = "Alkami.DevOps.SystemEngineering" Describe "New-ServerlessServiceAccountPair" { Mock -CommandName Get-LogLeadName -ModuleName $inScopeModule -MockWith { return 'New-ServerlessServiceAccountPair.tests' } Mock -CommandName Get-AWSRegion -ModuleName $inScopeModule -MockWith { return @( @{ 'Region' = 'us-east-1' }, @{ 'Region' = 'us-west-2' } ) } Mock -CommandName New-SecurePassword -ModuleName $inScopeModule -MockWith { return 'TestPassword' } Mock -CommandName New-ServerlessServiceAccountActiveDirectoryUserPair -ModuleName $inScopeModule -MockWith {} Mock -CommandName New-ServerlessServiceAccountSecret -ModuleName $inScopeModule -MockWith { return @('TestSecretArn1', 'TestSecretArn2') } Mock -CommandName New-ServerlessServiceAccountIamPolicy -ModuleName $inScopeModule -MockWith {} Mock -CommandName Get-YeatsLambdaIamRoleArn -ModuleName $inScopeModule -MockWith { return 'TestYeatsRoleArn' } Mock -CommandName Get-YeatsRotationLambdaArn -ModuleName $inScopeModule -MockWith { return 'TestYeatsLambdaArn' } Mock -CommandName Write-AlkamiSecretResourcePolicy -ModuleName $inScopeModule -MockWith {} Mock -CommandName Invoke-SECSecretRotation -ModuleName $inScopeModule -MockWith {} Mock -CommandName Write-Error -ModuleName $inScopeModule -MockWith {} Mock -CommandName Write-Warning -ModuleName $inScopeModule -MockWith {} Mock -CommandName Get-AlkamiCredential -ModuleName $inScopeModule -MockWith { return ( New-Object 'Management.Automation.PsCredential' $UserName, ( ConvertTo-SecureString -AsPlainText -Force -String "$Password" )) } $testCredential = New-Object 'Management.Automation.PsCredential' 'Test', ( ConvertTo-SecureString -AsPlainText -Force -String 'Test' ) $validEnvironment = (Get-Command New-ServerlessServiceAccountPair).Parameters.Environment.Attributes.ValidValues[0] Context "Parameter Validation" { It "Throws if Service Name Length Is Too Short" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName '' } | Should -Throw } It "Throws if Service Name Length Is Too Long" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'ThisIsAServiceThatWeShouldDeployEverywhere' } | Should -Throw } It "Throws if Environment is Not in Supported List" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment 'Test' } | Should -Throw } It "Throws if Environment is in Supported List With Different Casing" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validValue.ToLower() } | Should -Throw } It "Throws if Profile Name is Null" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName $null } | Should -Throw } It "Throws if Profile Name is Empty" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName '' } | Should -Throw } It "Throws if Ticket Number Does Not Match Regex" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test!' } | Should -Throw } It "Throws if Role ARN is Null" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn $null } | Should -Throw } It "Throws if Role ARN is Empty" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn '' } | Should -Throw } It "Throws if Region is Not in Supported List" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'Test' } | Should -Throw } It "Throws if Replication Region is Not in Supported List" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' -ReplicationRegion 'Test' } | Should -Throw } It "Throws if Rotation Window is Null" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' -RotationWindow $null } | Should -Throw } It "Throws if Rotation Window is Empty" { { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' -RotationWindow '' } | Should -Throw } } Context "Logic" { It "Creates Two User Passwords" { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName New-SecurePassword -Times 2 -Exactly -Scope It } It "Proxies Provided Arguments to Active Directory Function" { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName New-ServerlessServiceAccountActiveDirectoryUserPair -Times 1 -Exactly -Scope It ` -ParameterFilter { (($Cred.UserName -ceq 'Test') -and ($Environment -ceq $validEnvironment) -and ` ($TicketNumber -ceq 'Test-123')) } } It "Aborts Processing if Active Directory Function Throws" { Mock -CommandName New-ServerlessServiceAccountActiveDirectoryUserPair -ModuleName $inScopeModule -MockWith { throw "Test" } New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName New-ServerlessServiceAccountActiveDirectoryUserPair -Times 1 -Exactly -Scope It Assert-MockCalled -ModuleName $inScopeModule -CommandName New-ServerlessServiceAccountSecret -Times 0 -Exactly -Scope It Assert-MockCalled -ModuleName $inScopeModule -CommandName Write-Error -Times 1 -Exactly -Scope It ` -ParameterFilter { $Message -match 'Creation of Active Directory users failed. Error encountered was' } # Revert to top-level mock. Mock -CommandName New-ServerlessServiceAccountActiveDirectoryUserPair -ModuleName $inScopeModule -MockWith {} } It "Proxies Provided Arguments to AWS Secret Function" { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' -ReplicationRegion 'us-east-1' | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName New-ServerlessServiceAccountSecret -Times 1 -Exactly -Scope It ` -ParameterFilter { (($ProfileName -ceq 'temp-test') -and ($Region -ceq 'us-east-1') -and ` ($ReplicationRegion -ceq 'us-east-1')) } } It "Aborts Processing if Secret Creation Function Throws" { Mock -CommandName New-ServerlessServiceAccountSecret -ModuleName $inScopeModule -MockWith { throw "Test" } New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName New-ServerlessServiceAccountSecret -Times 1 -Exactly -Scope It Assert-MockCalled -ModuleName $inScopeModule -CommandName New-ServerlessServiceAccountIamPolicy -Times 0 -Exactly -Scope It Assert-MockCalled -ModuleName $inScopeModule -CommandName Write-Error -Times 1 -Exactly -Scope It ` -ParameterFilter { $Message -match 'Creation of AWS secret failed. Error encountered was' } # Revert mock back to top-level. Mock -CommandName New-ServerlessServiceAccountSecret -ModuleName $inScopeModule -MockWith { return @('TestSecretArn1', 'TestSecretArn2') } } It "Proxies Provided Arguments to AWS IAM Policy Function" { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName New-ServerlessServiceAccountIamPolicy -Times 1 -Exactly -Scope It ` -ParameterFilter { (($ProfileName -ceq 'temp-test') -and ($Region -ceq 'us-east-1') -and ($RoleArn -ceq 'TestRoleArn')) } } It "Continues Processing With Warning if AWS IAM Policy Creation Function Throws" { Mock -CommandName New-ServerlessServiceAccountIamPolicy -ModuleName $inScopeModule -MockWith { throw "Test" } New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName New-ServerlessServiceAccountIamPolicy -Times 1 -Exactly -Scope It Assert-MockCalled -ModuleName $inScopeModule -CommandName Get-YeatsLambdaIamRoleArn -Times 1 -Exactly -Scope It Assert-MockCalled -ModuleName $inScopeModule -CommandName Write-Warning -Times 1 -Exactly -Scope It ` -ParameterFilter { $Message -match 'Creation of IAM policy failed. Error encountered was' } # Revert mock back to top-level. Mock -CommandName New-ServerlessServiceAccountIamPolicy -ModuleName $inScopeModule -MockWith {} } It "Grants Supplied IAM Role Access to Created AWS Secret in Resource Policy" { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName Write-AlkamiSecretResourcePolicy -Times 1 -Exactly -Scope It ` -ParameterFilter { $SecretAccessExtraArns -contains 'TestRoleArn' } } It "Grants Yeats Lambda IAM Role Access to Created AWS Secret in Resource Policy" { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName Get-YeatsLambdaIamRoleArn -Times 1 -Exactly -Scope It Assert-MockCalled -ModuleName $inScopeModule -CommandName Write-AlkamiSecretResourcePolicy -Times 1 -Exactly -Scope It ` -ParameterFilter { $SecretAccessExtraArns -contains 'TestYeatsRoleArn' } } It "Grants Provided Extra ARNs Access to Created AWS Secret in Resource Policy" { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' -SecretAccessExtraArns @('TestExtraArn') | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName Write-AlkamiSecretResourcePolicy -Times 1 -Exactly -Scope It ` -ParameterFilter { $SecretAccessExtraArns -contains 'TestExtraArn' } } It "Skips Secret Rotation Configuration if User Explicitly Disabled Feature" { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' -RotationSchedule $null | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName Get-YeatsRotationLambdaArn -Times 0 -Exactly -Scope It Assert-MockCalled -ModuleName $inScopeModule -CommandName Invoke-SECSecretRotation -Times 0 -Exactly -Scope It } It "Skips Secret Rotation Configuration if Yeats Rotation Lambda Is Not Found" { Mock -CommandName Get-YeatsRotationLambdaArn -ModuleName $inScopeModule -MockWith { return $null } New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName Get-YeatsRotationLambdaArn -Times 1 -Exactly -Scope It Assert-MockCalled -ModuleName $inScopeModule -CommandName Invoke-SECSecretRotation -Times 0 -Exactly -Scope It # Revert mock back to top-level. Mock -CommandName Get-YeatsRotationLambdaArn -ModuleName $inScopeModule -MockWith { return 'TestYeatsLambdaArn' } } It "Applies Secret Rotation Configuration if Yeats Rotation Lambda Is Found" { New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName Get-YeatsRotationLambdaArn -Times 1 -Exactly -Scope It Assert-MockCalled -ModuleName $inScopeModule -CommandName Invoke-SECSecretRotation -Times 1 -Exactly -Scope It ` -ParameterFilter { $RotationLambdaArn -eq 'TestYeatsLambdaArn' } } It "Writes Warning if Configuration of AWS Secret Throws" { Mock -CommandName Write-AlkamiSecretResourcePolicy -ModuleName $inScopeModule -MockWith { throw "Test" } New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' | Out-Null Assert-MockCalled -ModuleName $inScopeModule -CommandName Write-AlkamiSecretResourcePolicy -Times 1 -Exactly -Scope It Assert-MockCalled -ModuleName $inScopeModule -CommandName Write-Warning -Times 1 -Exactly -Scope It ` -ParameterFilter { $Message -match 'Configuration of AWS secret failed. Error encountered was' } # Revert mock back to top-level. Mock -CommandName Write-AlkamiSecretResourcePolicy -ModuleName $inScopeModule -MockWith {} } } Context "Output" { It "Returns a PSObject" { (Get-Command New-ServerlessServiceAccountPair).OutputType.Type.ToString() | Should -BeExactly "System.Management.Automation.PSObject" } It "Returned Object Contains Usernames and Passwords for Created Users" { $result = New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' $result.Credentials | Should -HaveCount 2 $result.Credentials[0].UserName | Should -BeExactly "${validEnvironment}TestSvcSvcA" ( Get-PasswordFromCredential $result.Credentials[0] ) | Should -BeExactly "TestPassword" $result.Credentials[1].UserName | Should -BeExactly "${validEnvironment}TestSvcSvcB" ( Get-PasswordFromCredential $result.Credentials[1] ) | Should -BeExactly "TestPassword" } It "Returned Object Contains Secret ARNs" { $result = New-ServerlessServiceAccountPair -Cred $testCredential -ServiceName 'TestSvc' -Environment $validEnvironment -ProfileName 'temp-test' ` -TicketNumber 'Test-123' -ServiceAccountIamRoleArn 'TestRoleArn' -Region 'us-east-1' $result.SecretArns | Should -HaveCount 2 $result.SecretArns[0] | Should -BeExactly "TestSecretArn1" $result.SecretArns[1] | Should -BeExactly "TestSecretArn2" } } } }