ps/Modules/Alkami.DevOps.SystemEngineering/Public/New-ServerlessServiceAccountPair.ps1
2023-05-30 22:51:22 -07:00

215 lines
9.3 KiB
PowerShell

function New-ServerlessServiceAccountPair {
<#
.SYNOPSIS
Top-level function to create serverless service accounts.
.DESCRIPTION
Top-level function to create serverless service accounts. Initializes data structures and invokes
individual handling functions to create an Active Directory user account pair and an AWS Secrets
Manager secret containing the authentication parameters for the users.
.PARAMETER Cred
[PSCredential] A credential object for the FH user that has permissions to create new accounts
on the domain.
.PARAMETER ServiceName
[string] The truncated identifier for the service name. Must be nine characters or less.
.PARAMETER Environment
[string] The target environment for the user accounts.
.PARAMETER ProfileName
[string] The AWS profile to use during user creation.
.PARAMETER TicketNumber
[string] The Jira ticket identifier requesting the new accounts.
.PARAMETER ServiceAccountIamRoleArn
[string] The AWS IAM role ARN that should be granted access to the AWS Secrets Manager secret.
.PARAMETER Region
[string] The AWS region to use during user creation. If not provided, defaults to 'us-east-1'
.PARAMETER ReplicationRegion
[string] The target AWS region for replicated AWS Secrets Manager secrets.
To disable replication, set this value to null or empty. If not provided, defaults to 'us-west-2'.
.PARAMETER SecretAccessExtraArns
[string[]] An array of AWS ARNs allowed to access the created secret in addition to the defaults.
.PARAMETER RotationSchedule
[string] A scheduling expression for password rotation. Refer to https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotate-secrets_schedule.html.
To disable rotation, set this value to null or empty.
If not provided, the default scheduling expression will rotate passwords at 1400 UTC on the first Tuesday of the month.
.PARAMETER RotationWindow
[string] A window duration for password rotation. Refer to https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotate-secrets_schedule.html.
If not provided, the default rotation window is two hours.
.INPUTS
None. You cannot pipe objects to New-ServerlessServiceAccountPair.
.OUTPUTS
[PSObject] New-ServerlessServiceAccountPair returns an object with the following top-level members:
[PSCredential[]] Credentials : Contains the usernames and passwords created by this function.
[string[]] SecretArns : Contains the ARN(s) of the AWS Secrets Manager secret(s) created by this function.
.EXAMPLE
New-ServerlessServiceAccountPair -Cred (Get-AlkamiCredential) `
-ServiceName 'Example' `
-Environment 'Prod' `
-ProfileName 'temp-prod' `
-TicketNumber 'SYSENG-1234' `
-ServiceAccountIamRoleArn 'arn:aws:iam::000000000000:role/example-services-accounts-role' `
-Region 'us-east-1' `
-ReplicationRegion 'us-west-2' `
-SecretAccessExtraArns @( 'ExampleArn1' )
SecretArns Credentials
---------- -----------
{ExampleSecretArn1, ExampleSecretArn2} {System.Management.Automation.PSCredential, System.Management.Automation.PSCredential}
#>
[OutputType([PSObject])]
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[PSCredential] $Cred,
[Parameter(Mandatory = $true)]
[ValidateLength(1, 8)]
[string] $ServiceName,
[Parameter(Mandatory = $true)]
[ValidateSet('Dev', 'Qa', 'LoadTest', 'Staging', 'Prod', IgnoreCase = $false)]
[string] $Environment,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string] $ProfileName,
[Parameter(Mandatory = $true)]
[ValidatePattern("^[a-z]+-\d+$")]
[string]$TicketNumber,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string] $ServiceAccountIamRoleArn,
[Parameter(Mandatory = $false)]
[ValidateScript({$_ -in (Get-AWSRegion).region})]
[string] $Region = 'us-east-1',
[Parameter(Mandatory = $false)]
[ValidateScript({([String]::IsNullOrEmpty($_) -or ($_ -in (Get-AWSRegion).region))})]
[string] $ReplicationRegion = 'us-west-2',
[Parameter(Mandatory = $false)]
[string[]] $SecretAccessExtraArns = $null,
[Parameter(Mandatory = $false)]
[string] $RotationSchedule = 'cron(0 14 ? * 3#1 *)',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string] $RotationWindow = '2h'
)
$logLead = Get-LogLeadName
$usernamePrefix = "${Environment}${ServiceName}Svc"
$environmentTagValue = ( "{0}shared" -f $Environment.ToLower() )
$userOuPathCommon = "/ServiceAccounts/${Environment}"
$secretName = "${userOuPathCommon}/${usernamePrefix}"
$secretDescription = "Serverless service account secret for $usernamePrefix"
$accessArns = @($ServiceAccountIamRoleArn)
$result = New-Object PSObject -Property @{
Credentials = @()
SecretArns = @()
}
# Dump out a list of the decisions we made if the user wants to see them.
Write-Verbose "$logLead : Username prefix evaluated to be '$usernamePrefix'."
Write-Verbose "$logLead : Environment tag evaluated to be '$environmentTagValue'."
Write-Verbose "$logLead : User OU Path that will be used by Active Directory and AWS Secrets code evaluated to be '$userOuPathCommon'."
Write-Verbose "$logLead : Secret name evaluated to be '$secretName'."
Write-Verbose "$logLead : Secret description evaluated to be '$secretDescription'."
# Generate two usernames and passwords for multi-user rotation strategy.
for ($i = 0; $i -lt 2; $i++) {
$result.Credentials += ( Get-AlkamiCredential -UserName ( "{0}{1}" -f $usernamePrefix, [char]([byte][char]'A' + $i)) `
-Password (New-SecurePassword -PasswordLength 20 -ProfileName $ProfileName -Region $Region ))
}
try {
# Create users in AD.
New-ServerlessServiceAccountActiveDirectoryUserPair -Cred $Cred `
-UserDataList $result.Credentials `
-UserOuPathCommon $userOuPathCommon `
-Environment $Environment `
-TicketNumber $TicketNumber
} catch {
Write-Error "$logLead : Creation of Active Directory users failed. Error encountered was: [$PSItem]"
return $result
}
try {
# Create secret
$result.SecretArns = New-ServerlessServiceAccountSecret -SecretName $secretName `
-UserDataList $result.Credentials `
-EnvironmentTag $environmentTagValue `
-ProfileName $ProfileName `
-Region $Region `
-ReplicationRegion $ReplicationRegion `
-Description $secretDescription
} catch {
Write-Error "$logLead : Creation of AWS secret failed. Error encountered was: [$PSItem]"
return $result
}
try {
New-ServerlessServiceAccountIamPolicy -RoleArn $ServiceAccountIamRoleArn `
-ProfileName $ProfileName `
-Region $Region `
-SecretArns $result.SecretArns
} catch {
Write-Warning "$logLead : Creation of IAM policy failed. Error encountered was: [$PSItem]"
}
try {
$accessArns += $SecretAccessExtraArns
$accessArns += ( Get-YeatsLambdaIamRoleArn -EnvironmentTag $environmentTagValue -ProfileName $ProfileName )
$accessArns = $accessArns | Where-Object { $false -eq [string]::IsNullOrWhitespace($_) }
# Create the resource policy on the secret in AWS.
Write-AlkamiSecretResourcePolicy -SecretName $secretName -ProfileName $ProfileName -Region $Region -SecretAccessExtraArns $accessArns
# Enable rotation on the secret (if the operator didn't turn off that feature).
if ( $false -eq [string]::IsNullOrWhitespace( $RotationSchedule )) {
$rotationLambdaArn = Get-YeatsRotationLambdaArn -EnvironmentTag $environmentTagValue -ProfileName $ProfileName -Region $Region
if ( $null -ne $rotationLambdaArn ) {
# Note that we disable immediate rotation of the secret because we just created the accounts -- the passwords
# do not require rotation at this time.
Invoke-SECSecretRotation -SecretId $result.SecretArns[0] -RotateImmediately $false -RotationLambdaArn $rotationLambdaArn `
-RotationRules_ScheduleExpression $RotationSchedule -RotationRules_Duration $RotationWindow `
-ProfileName $ProfileName -Region $Region
}
}
} catch {
Write-Warning "$logLead : Configuration of AWS secret failed. Error encountered was: [$PSItem]"
}
return $result
}