IdentityCommand.UAP.psm1
|
function ConvertTo-UAPFilterString { <# .SYNOPSIS Assembles an Access Control Policies filter expression. .DESCRIPTION Formats filter criteria into the expression syntax the Access Control Policies service accepts. Values for the same field are combined with `or` inside their own parentheses, and the resulting groups are combined with `and`: (((policyTags eq 'a') or (policyTags eq 'b')) and (targetCategory eq 'VM')) Every field compares with `eq`, apart from `identities`, which supports only `contains`. Values are single quoted, as the service documents them. The expression is not url encoded - Add-QueryString encodes the query string as a whole. .PARAMETER Filter A hashtable of field names to the values to match. A field with several values matches any of them. Supported fields are policyTags, identities, targetCategory, status and locationType. .EXAMPLE ConvertTo-UAPFilterString -Filter @{ targetCategory = 'VM' } Outputs: (targetCategory eq 'VM') .EXAMPLE ConvertTo-UAPFilterString -Filter ([ordered]@{ policyTags = @('a', 'b'); targetCategory = 'VM' }) Outputs: (((policyTags eq 'a') or (policyTags eq 'b')) and (targetCategory eq 'VM')) .OUTPUTS String #> [CmdletBinding()] [OutputType([string])] param( [parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [System.Collections.IDictionary]$Filter ) $SupportedField = @('policyTags', 'identities', 'targetCategory', 'status', 'locationType') #Every field compares with eq; identities is documented as supporting only contains $FieldOperator = @{ identities = 'contains' } $Groups = [System.Collections.Generic.List[string]]::new() foreach ($Field in $Filter.Keys) { if ($Field -notin $SupportedField) { throw "'$Field' is not a filterable field of the Access Control Policies service. Valid fields are: $($SupportedField -join ', ')" } $Values = @($Filter[$Field]) | Where-Object { -not [string]::IsNullOrEmpty("$_") } if ($Values.Count -eq 0) { continue } $Operator = if ($FieldOperator.ContainsKey($Field)) { $FieldOperator[$Field] } else { 'eq' } $Clauses = @($Values | ForEach-Object { "($(ConvertTo-FilterClause -Field $Field -Operator $Operator -Value $_ -QuoteValue -QuoteCharacter "'"))" }) $Groups.Add($(if ($Clauses.Count -gt 1) { "($($Clauses -join ' or '))" } else { $Clauses[0] })) } if ($Groups.Count -eq 0) { return } if ($Groups.Count -eq 1) { return $Groups[0] } "($($Groups -join ' and '))" } function Remove-UAPReadOnlyProperty { <# .SYNOPSIS Strips the read-only properties from a retrieved access policy. .DESCRIPTION A policy retrieved with GET carries properties the service populates and marks read-only - who created it, the display names resolved for each target, the status code behind its status, and the invalid resources behind an error. Set-UAPPolicy re-sends the retrieved policy as the fallback for whatever the caller did not supply, so those properties are removed first rather than echoed back. The policy's status is kept, since a policy is suspended and reactivated through it; the read-only detail alongside it is not. .PARAMETER Policy The policy as returned by Get-UAPPolicy. .EXAMPLE $Existing = Get-UAPPolicy -policyId $policyId | Remove-UAPReadOnlyProperty .OUTPUTS The policy, without its read-only properties. #> [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function returns a filtered copy of its input and does not change state')] [CmdletBinding()] param( [parameter( Mandatory = $true, ValueFromPipeline = $true )] [psobject]$Policy ) begin { #Read-only on the policy itself. delegationClassification is marked read-only by the schema, #though the service's own edit examples send it back. $PolicyProperty = @('delegationClassification', 'invalidResources') #Read-only on the metadata. The examples name the update stamp inconsistently, so both spellings go. $MetadataProperty = @('createdBy', 'updatedBy', 'updatedOn') #Display names and types the service resolves for each target from the ids given $TargetProperty = @( 'roleName' 'roleType' 'rolePackage' 'workspaceName' 'clusterName' 'namespaceName' 'groupName' 'groupType' 'directoryName' 'description' ) $Strip = { param($Object, $Names) if ($null -eq $Object) { return $null } $Kept = [ordered]@{ } foreach ($Property in $Object.PSObject.Properties) { if ($Property.Name -notin $Names) { $Kept[$Property.Name] = $Property.Value } } [pscustomobject]$Kept } }#begin process { $Clean = & $Strip $Policy $PolicyProperty if ($null -ne $Clean.metadata) { $Metadata = & $Strip $Clean.metadata $MetadataProperty if ($null -ne $Metadata.status) { $Metadata.status = [pscustomobject]@{ status = $Metadata.status.status } } $Clean.metadata = $Metadata } if ($null -ne $Clean.targets) { #Cloud console policies nest their targets, clusters and groups hold a bare array, and #VM/DB targets are keyed objects whose entries are not per-target records if ($Clean.targets -is [System.Collections.IEnumerable] -and $Clean.targets -isnot [string]) { $Clean.targets = @($Clean.targets | ForEach-Object { & $Strip $_ $TargetProperty }) } elseif ($null -ne $Clean.targets.targets) { $Clean.targets = [pscustomobject]@{ targets = @($Clean.targets.targets | ForEach-Object { & $Strip $_ $TargetProperty }) } } } $Clean }#process end { }#end } function Resolve-UAPTargetShape { <# .SYNOPSIS Places policy targets in the shape their target category expects. .DESCRIPTION The service carries targets differently for each target category, so the builders emit plain target entries and this helper wraps them once, in one place: Cloud Console { "targets": [ ... ] } Clusters [ ... ] Groups [ ... ] DB { "FQDN/IP": { "instances": [ ... ] } } VM { "AWS": { ... } } - already shaped by New-UAPVirtualMachineTargetDefinition Targets which are already in their final shape are returned unaltered, so a hand-built payload passes through untouched. .PARAMETER targetCategory The policy's target category. .PARAMETER targets The targets to shape, as returned by the New-UAP*TargetDefinition builders. .EXAMPLE Resolve-UAPTargetShape -targetCategory 'Cloud Console' -targets $Targets .OUTPUTS The targets, wrapped as the target category requires. #> [CmdletBinding()] param( [parameter(Mandatory = $true)] [string]$targetCategory, [parameter(Mandatory = $true)] [AllowNull()] $targets ) switch ($targetCategory) { 'Cloud Console' { #Every documented cloud console example nests the array under a targets property, which #the schema does not show. The examples are consistent across create, edit and get. if ($targets.PSObject.Properties.Name -contains 'targets') { $targets } else { [ordered]@{ targets = @($targets) } } break } 'DB' { if ($targets.PSObject.Properties.Name -contains 'FQDN/IP') { $targets } else { [ordered]@{ 'FQDN/IP' = [ordered]@{ instances = @($targets) } } } break } { $_ -in 'Clusters', 'Groups' } { @($targets) break } default { #VM targets are keyed by location, and the builder emits that object whole $targets } } } # .ExternalHelp IdentityCommand.UAP-help.xml function Connect-UAPTenant { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Subdomain')] param( #subdomain [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Subdomain')] [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'SubdomainCredential')] [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'SubdomainSAML')] [ValidateNotNullOrEmpty()] [Alias('subdomain')] [String]$tenant_subdomain, #tenant_url [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'URL')] [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'URLCredential')] [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'URLSAML')] [ValidateNotNullOrEmpty()] [Alias('uap_url')] [String]$tenant_url, #Credential used to authenticate to CyberArk Identity. Authentication is performed even if an active IdentityCommand session is found, replacing it [parameter(Mandatory = $true, ParameterSetName = 'SubdomainCredential')] [parameter(Mandatory = $true, ParameterSetName = 'URLCredential')] [ValidateNotNullOrEmpty()] [PSCredential]$Credential, #Authenticate as a service user via New-IDPlatformToken (OAuth client_credentials) instead of the interactive New-IDSession [parameter(ParameterSetName = 'SubdomainCredential')] [parameter(ParameterSetName = 'URLCredential')] [Switch]$PlatformToken, #SAML assertion used to authenticate to CyberArk Identity. Authentication is performed even if an active IdentityCommand session is found, replacing it [parameter(Mandatory = $true, ParameterSetName = 'SubdomainSAML')] [parameter(Mandatory = $true, ParameterSetName = 'URLSAML')] [ValidateNotNullOrEmpty()] [String]$SAMLResponse ) begin { $IDSession = Get-IDSession $HaveSession = $null -ne $IDSession.tenant_url $AuthRequested = $PSBoundParameters.ContainsKey('Credential') -or $PSBoundParameters.ContainsKey('SAMLResponse') if ($HaveSession -and $AuthRequested) { Write-Verbose 'Authentication parameters were supplied; authenticating and replacing the existing IdentityCommand session' } }#begin process { $UsingSubdomain = $PSCmdlet.ParameterSetName -like 'Subdomain*' #Resolve service URLs from platform discovery when a subdomain was supplied, or when #authentication is required and the CyberArk Identity URL must be discovered. $ServiceUrl = $null if ($UsingSubdomain) { $ServiceUrl = Resolve-ServiceUrl -Service uap -Subdomain $tenant_subdomain $tenant_url = $ServiceUrl.ServiceUrl } else { #Ensure URL is in expected format - remove trailing slash if provided in Url $tenant_url = $tenant_url -replace '/$', '' if ($AuthRequested) { $ServiceUrl = Resolve-ServiceUrl -Service uap -Url $tenant_url } } if ($AuthRequested -or (-not $HaveSession)) { if (-not $AuthRequested) { throw 'Authenticate with New-IDSession or New-IDPlatformToken, or supply -Credential, and try again' } $IdentityUrl = $ServiceUrl.IdentityUrl if ($PSCmdlet.ShouldProcess($IdentityUrl, 'Authenticate to CyberArk Identity')) { if ($PSCmdlet.ParameterSetName -like '*SAML') { $null = New-IDSession -tenant_url $IdentityUrl -SAMLResponse $SAMLResponse } elseif ($PlatformToken) { $null = New-IDPlatformToken -tenant_url $IdentityUrl -Credential $Credential } else { $null = New-IDSession -tenant_url $IdentityUrl -Credential $Credential } $IDSession = Get-IDSession } } #Make the CyberArk Identity Session available in the IdentityCommand.UAP scope foreach ($key in $IDSession.keys) { if ($null -ne $IDSession[$key]) { $ISPSSSession[$key] = $IDSession[$key] } } #Set the Access Requests URL in the session data $ISPSSSession.tenant_url = $tenant_url }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function Get-UAPModuleData { [CmdletBinding()] param() begin { }#begin process { #Calculate the time elapsed since the start of the session and include in return data if ($null -ne $ISPSSSession.StartTime) { $ISPSSSession.ElapsedTime = '{0:HH:mm:ss}' -f ([datetime]$($(Get-Date) - $($ISPSSSession.StartTime)).Ticks) } else { $ISPSSSession.ElapsedTime = $null } #Deep Copy the $ISPSSSession session object and return as IdCmd Session type. Get-SessionClone -InputObject $ISPSSSession | Add-CustomType -Type IdCmd.Session }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function Get-UAPPolicy { [CmdletBinding(DefaultParameterSetName = 'byFilterCriteria')] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byId' )] [ValidateNotNullOrEmpty()] [Alias('id')] [String]$policyId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byQuery' )] [ValidateNotNullOrEmpty()] [String]$filter, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateNotNullOrEmpty()] [String[]]$policyTags, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateNotNullOrEmpty()] [String[]]$identities, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateSet('Cloud Console', 'VM', 'DB', 'Clusters', 'Groups')] [String[]]$targetCategory, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateSet('Active', 'Suspended', 'Expired', 'Validating', 'Error', 'Warning')] [String[]]$status, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateSet('AWS', 'Azure', 'GCP', 'FQDN/IP')] [String[]]$locationType, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byQuery' )] [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateRange(0, 50)] [int]$limit, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byQuery' )] [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateSet('createdBy')] [String]$sort, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byQuery' )] [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'byFilterCriteria' )] [ValidateLength(1, 200)] [String]$q ) begin { $FilterField = @('policyTags', 'identities', 'targetCategory', 'status', 'locationType') }#begin process { if ($PSCmdlet.ParameterSetName -eq 'byId') { $URI = "$($ISPSSSession.tenant_url)/api/policies/$policyId" #Send Request Invoke-IDRestMethod -Uri $URI -Method GET } else { $URI = "$($ISPSSSession.tenant_url)/api/policies" $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove $FilterField if ($PSCmdlet.ParameterSetName -eq 'byFilterCriteria') { $Criteria = [ordered]@{ } foreach ($Field in $FilterField) { if ($PSBoundParameters.ContainsKey($Field)) { $Criteria[$Field] = $PSBoundParameters[$Field] } } if ($Criteria.Count -gt 0) { $boundParameters['filter'] = ConvertTo-UAPFilterString -Filter $Criteria } } $URI = Add-QueryString -URI $URI -Parameter $boundParameters #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method GET if ($null -ne $result) { Get-PagedResult -InitialResult $result -URI $URI -Style Cursor -ResultProperty 'results' -CursorRequestKey 'nextToken' -CursorResponseKey 'nextToken' } } }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function New-UAPCloudConsoleTargetDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function builds a definition object and does not change state')] [CmdletBinding()] [OutputType('IdCmd.UAP.Definition.CloudConsoleTarget')] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$roleId, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$workspaceId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$orgId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$workspaceType, [parameter(Mandatory = $false)] [psobject[]]$TargetDefinition ) begin { }#begin process { $Target = $PSBoundParameters | Get-Parameter -ParametersToRemove TargetDefinition $Output = @($TargetDefinition) + @([pscustomobject]$Target) | Where-Object { $null -ne $_ } $Output | Add-CustomType -Type 'IdCmd.UAP.Definition.CloudConsoleTarget' }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function New-UAPClusterTargetDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function builds a definition object and does not change state')] [CmdletBinding()] [OutputType('IdCmd.UAP.Definition.ClusterTarget')] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$roleId, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$workspaceId, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$clusterId, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('cluster', 'namespace')] [String]$scope, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$namespaceId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$orgId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$workspaceType, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$region, [parameter(Mandatory = $false)] [psobject[]]$TargetDefinition ) begin { }#begin process { if (($scope -eq 'namespace') -and (-not $PSBoundParameters.ContainsKey('namespaceId'))) { throw 'namespaceId is required when scope is namespace' } $Target = $PSBoundParameters | Get-Parameter -ParametersToRemove TargetDefinition $Output = @($TargetDefinition) + @([pscustomobject]$Target) | Where-Object { $null -ne $_ } $Output | Add-CustomType -Type 'IdCmd.UAP.Definition.ClusterTarget' }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function New-UAPConditionDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function builds a definition object and does not change state')] [CmdletBinding()] [OutputType('IdCmd.UAP.Definition.Conditions')] param( [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateRange(0, 6)] [int[]]$daysOfTheWeek, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$fromHour, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$toHour, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateRange(1, 24)] [int]$maxSessionDuration, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateRange(1, 120)] [int]$idleTime, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [switch]$accessApprovalRequired, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateCount(1, 5)] [psobject[]]$approvers ) begin { }#begin process { $Conditions = [ordered]@{ } if ($PSBoundParameters.ContainsKey('daysOfTheWeek') -or $PSBoundParameters.ContainsKey('fromHour') -or $PSBoundParameters.ContainsKey('toHour')) { if ($accessApprovalRequired) { throw 'Omit the access window when access approval is required - the service rejects a payload carrying both' } $AccessWindow = [ordered]@{ } if ($PSBoundParameters.ContainsKey('daysOfTheWeek')) { $AccessWindow['daysOfTheWeek'] = @($daysOfTheWeek) } if ($PSBoundParameters.ContainsKey('fromHour')) { $AccessWindow['fromHour'] = $fromHour } if ($PSBoundParameters.ContainsKey('toHour')) { $AccessWindow['toHour'] = $toHour } $Conditions['accessWindow'] = $AccessWindow } if ($PSBoundParameters.ContainsKey('maxSessionDuration')) { $Conditions['maxSessionDuration'] = $maxSessionDuration } if ($PSBoundParameters.ContainsKey('idleTime')) { $Conditions['idleTime'] = $idleTime } if ($PSBoundParameters.ContainsKey('accessApprovalRequired')) { $AccessApproval = [ordered]@{ required = $accessApprovalRequired.IsPresent } if ($PSBoundParameters.ContainsKey('approvers')) { $AccessApproval['approvers'] = @($approvers) } $Conditions['accessApproval'] = $AccessApproval } [pscustomobject]$Conditions | Add-CustomType -Type 'IdCmd.UAP.Definition.Conditions' }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function New-UAPDatabaseTargetDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function builds a definition object and does not change state')] [CmdletBinding()] [OutputType('IdCmd.UAP.Definition.DatabaseTarget')] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 256)] [String]$instanceName, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 32)] [String]$instanceType, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 36)] [String]$instanceId, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('ldap_auth', 'db_auth', 'oracle_auth', 'mongo_auth', 'sqlserver_auth', 'rds_iam_user_auth')] [String]$authenticationMethod, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [hashtable]$profile, [parameter(Mandatory = $false)] [psobject[]]$TargetDefinition ) begin { }#begin process { $Target = $PSBoundParameters | Get-Parameter -ParametersToRemove TargetDefinition $Output = @($TargetDefinition) + @([pscustomobject]$Target) | Where-Object { $null -ne $_ } $Output | Add-CustomType -Type 'IdCmd.UAP.Definition.DatabaseTarget' }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function New-UAPGroupTargetDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function builds a definition object and does not change state')] [CmdletBinding()] [OutputType('IdCmd.UAP.Definition.GroupTarget')] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$groupId, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$directoryId, [parameter(Mandatory = $false)] [psobject[]]$TargetDefinition ) begin { }#begin process { $Target = $PSBoundParameters | Get-Parameter -ParametersToRemove TargetDefinition $Output = @($TargetDefinition) + @([pscustomobject]$Target) | Where-Object { $null -ne $_ } $Output | Add-CustomType -Type 'IdCmd.UAP.Definition.GroupTarget' }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function New-UAPPolicy { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 200)] [String]$name, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 200)] [String]$description, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('Cloud Console', 'VM', 'DB', 'Clusters', 'Groups')] [String]$targetCategory, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('AWS', 'Azure', 'GCP', 'FQDN/IP')] [String]$locationType, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('Recurring', 'OnDemand')] [String]$policyType, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateCount(1, 20)] [String[]]$policyTags, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 50)] [String]$timeZone, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [datetime]$fromTime, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [datetime]$toTime, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('Active', 'Suspended')] [String]$status, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [psobject[]]$principals, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [psobject]$conditions, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [psobject]$targets, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [psobject]$behavior, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('direct', 'proxy')] [String]$connectionMethod ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/policies" $Metadata = [ordered]@{ name = $name } if ($PSBoundParameters.ContainsKey('description')) { $Metadata['description'] = $description } if ($PSBoundParameters.ContainsKey('status')) { $Metadata['status'] = @{ status = $status } } if ($PSBoundParameters.ContainsKey('policyTags')) { $Metadata['policyTags'] = @($policyTags) } if ($PSBoundParameters.ContainsKey('fromTime') -or $PSBoundParameters.ContainsKey('toTime')) { $TimeFrame = [ordered]@{ } #The service documents the timeframe as yyyy-MM-ddTHH:mm:ss if ($PSBoundParameters.ContainsKey('fromTime')) { $TimeFrame['fromTime'] = $fromTime.ToString('yyyy-MM-ddTHH:mm:ss') } if ($PSBoundParameters.ContainsKey('toTime')) { $TimeFrame['toTime'] = $toTime.ToString('yyyy-MM-ddTHH:mm:ss') } $Metadata['timeFrame'] = $TimeFrame } if ($PSBoundParameters.ContainsKey('timeZone')) { $Metadata['timeZone'] = $timeZone } $Entitlement = [ordered]@{ targetCategory = $targetCategory; locationType = $locationType } if ($PSBoundParameters.ContainsKey('policyType')) { $Entitlement['policyType'] = $policyType } $Metadata['policyEntitlement'] = $Entitlement $Policy = [ordered]@{ metadata = $Metadata principals = @($principals) targets = $(Resolve-UAPTargetShape -targetCategory $targetCategory -targets $targets) } if ($PSBoundParameters.ContainsKey('conditions')) { $Policy['conditions'] = $conditions } if ($PSBoundParameters.ContainsKey('behavior')) { $Policy['behavior'] = $behavior } if ($PSBoundParameters.ContainsKey('connectionMethod')) { $Policy['connectionMethod'] = $connectionMethod } $body = $Policy | ConvertTo-Json -Depth 12 if ($PSCmdlet.ShouldProcess($name, 'Create Access Policy')) { #Send Request Invoke-IDRestMethod -Uri $URI -Method POST -Body $body } }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function New-UAPPrincipalDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function builds a definition object and does not change state')] [CmdletBinding()] [OutputType('IdCmd.UAP.Definition.Principal')] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 40)] [String]$id, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 512)] [String]$name, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('USER', 'ROLE', 'GROUP')] [String]$type, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 256)] [String]$sourceDirectoryName, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$sourceDirectoryId, [parameter(Mandatory = $false)] [psobject[]]$PrincipalDefinition ) begin { }#begin process { if (($type -ne 'ROLE') -and (-not ($PSBoundParameters.ContainsKey('sourceDirectoryName') -and $PSBoundParameters.ContainsKey('sourceDirectoryId')))) { throw 'sourceDirectoryName and sourceDirectoryId are required unless the principal type is ROLE' } $Principal = $PSBoundParameters | Get-Parameter -ParametersToRemove PrincipalDefinition $Output = @($PrincipalDefinition) + @([pscustomobject]$Principal) | Where-Object { $null -ne $_ } $Output | Add-CustomType -Type 'IdCmd.UAP.Definition.Principal' }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function New-UAPVirtualMachineBehaviorDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function builds a definition object and does not change state')] [CmdletBinding()] [OutputType('IdCmd.UAP.Definition.VirtualMachineBehavior')] param( [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String]$sshUsername, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [String[]]$rdpAssignGroups, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [switch]$enableEphemeralUserReconnect ) begin { }#begin process { $ConnectAs = [ordered]@{ } if ($PSBoundParameters.ContainsKey('sshUsername')) { $ConnectAs['ssh'] = @{ username = $sshUsername } } if ($PSBoundParameters.ContainsKey('rdpAssignGroups') -or $PSBoundParameters.ContainsKey('enableEphemeralUserReconnect')) { $EphemeralUser = [ordered]@{ } if ($PSBoundParameters.ContainsKey('rdpAssignGroups')) { $EphemeralUser['assignGroups'] = @($rdpAssignGroups) } $EphemeralUser['enableEphemeralUserReconnect'] = $enableEphemeralUserReconnect.IsPresent $ConnectAs['rdp'] = @{ localEphemeralUser = $EphemeralUser } } [pscustomobject]@{ connectAs = $ConnectAs } | Add-CustomType -Type 'IdCmd.UAP.Definition.VirtualMachineBehavior' }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function New-UAPVirtualMachineTargetDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function builds a definition object and does not change state')] [CmdletBinding(DefaultParameterSetName = 'AWS')] [OutputType('IdCmd.UAP.Definition.VirtualMachineTarget')] param( [parameter(Mandatory = $true, ParameterSetName = 'AWS')] [switch]$AWS, [parameter(Mandatory = $true, ParameterSetName = 'Azure')] [switch]$Azure, [parameter(Mandatory = $true, ParameterSetName = 'GCP')] [switch]$GCP, [parameter(Mandatory = $true, ParameterSetName = 'FQDNIP')] [switch]$FQDNIP, [parameter(Mandatory = $false, ParameterSetName = 'AWS')] [parameter(Mandatory = $false, ParameterSetName = 'Azure')] [parameter(Mandatory = $false, ParameterSetName = 'GCP')] [String[]]$regions, [parameter(Mandatory = $false, ParameterSetName = 'AWS')] [parameter(Mandatory = $false, ParameterSetName = 'Azure')] [hashtable[]]$tags, [parameter(Mandatory = $false, ParameterSetName = 'GCP')] [hashtable[]]$labels, [parameter(Mandatory = $false, ParameterSetName = 'AWS')] [parameter(Mandatory = $false, ParameterSetName = 'GCP')] [String[]]$vpcIds, [parameter(Mandatory = $false, ParameterSetName = 'AWS')] [String[]]$accountIds, [parameter(Mandatory = $false, ParameterSetName = 'Azure')] [String[]]$vnetIds, [parameter(Mandatory = $false, ParameterSetName = 'Azure')] [String[]]$resourceGroups, [parameter(Mandatory = $false, ParameterSetName = 'Azure')] [String[]]$subscriptions, [parameter(Mandatory = $false, ParameterSetName = 'GCP')] [String[]]$projects, [parameter(Mandatory = $false, ParameterSetName = 'FQDNIP')] [hashtable[]]$fqdnRules, [parameter(Mandatory = $false, ParameterSetName = 'FQDNIP')] [hashtable[]]$ipRules, [parameter(Mandatory = $false)] [psobject]$TargetDefinition ) begin { }#begin process { $Location = if ($PSCmdlet.ParameterSetName -eq 'FQDNIP') { 'FQDN/IP' } else { $PSCmdlet.ParameterSetName } $Target = $PSBoundParameters | Get-Parameter -ParametersToRemove AWS, Azure, GCP, FQDNIP, TargetDefinition #Each location is a property of a single targets object, so an existing definition is #carried forward rather than replaced $Output = [ordered]@{ } if ($PSBoundParameters.ContainsKey('TargetDefinition')) { foreach ($Property in $TargetDefinition.PSObject.Properties) { $Output[$Property.Name] = $Property.Value } } $Output[$Location] = $Target [pscustomobject]$Output | Add-CustomType -Type 'IdCmd.UAP.Definition.VirtualMachineTarget' }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function Remove-UAPPolicy { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [Alias('id')] [String]$policyId ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/policies/$policyId" if ($PSCmdlet.ShouldProcess($policyId, 'Delete Access Policy')) { #Send Request Invoke-IDRestMethod -Uri $URI -Method DELETE } }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function Set-UAPPolicy { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [Alias('id')] [String]$policyId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 200)] [String]$name, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 200)] [String]$description, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('Cloud Console', 'VM', 'DB', 'Clusters', 'Groups')] [String]$targetCategory, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('AWS', 'Azure', 'GCP', 'FQDN/IP')] [String]$locationType, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('Recurring', 'OnDemand')] [String]$policyType, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateCount(1, 20)] [String[]]$policyTags, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 50)] [String]$timeZone, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [datetime]$fromTime, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [datetime]$toTime, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('Active', 'Suspended')] [String]$status, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [psobject[]]$principals, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [psobject]$conditions, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [psobject]$targets, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [psobject]$behavior, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('direct', 'proxy')] [String]$connectionMethod ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/policies/$policyId" #The service replaces the policy with the payload sent, so the current policy provides #whatever the caller did not supply. Its read-only properties are dropped rather than echoed. $Existing = Get-UAPPolicy -policyId $policyId | Remove-UAPReadOnlyProperty if ($null -eq $Existing) { throw "Policy '$policyId' was not found, so there is no policy to update" } $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove policyId #Metadata: the flat properties merge directly against the current policy's metadata $Metadata = Merge-Parameter -Template ([ordered]@{ policyId = $policyId name = $null description = $null policyTags = $null timeZone = $null }) -BoundParameter $boundParameters -Fallback $Existing.metadata $Metadata['policyId'] = $policyId #Status travels as an object, and only its status value is writable $CurrentStatus = if ($PSBoundParameters.ContainsKey('status')) { $status } else { $Existing.metadata.status.status } if (-not [string]::IsNullOrEmpty($CurrentStatus)) { $Metadata['status'] = [ordered]@{ status = $CurrentStatus } } #The timeframe is a nested object, so each end falls back to the current policy's own value $TimeFrame = [ordered]@{ } foreach ($Bound in @{ Name = 'fromTime'; Value = $fromTime }, @{ Name = 'toTime'; Value = $toTime }) { if ($PSBoundParameters.ContainsKey($Bound.Name)) { #The service documents the timeframe as yyyy-MM-ddTHH:mm:ss $TimeFrame[$Bound.Name] = $Bound.Value.ToString('yyyy-MM-ddTHH:mm:ss') } elseif ($null -ne $Existing.metadata.timeFrame.$($Bound.Name)) { $TimeFrame[$Bound.Name] = $Existing.metadata.timeFrame.$($Bound.Name) } } if ($TimeFrame.Count -gt 0) { $Metadata['timeFrame'] = $TimeFrame } $Metadata['policyEntitlement'] = Merge-Parameter -Template ([ordered]@{ targetCategory = $null locationType = $null policyType = $null }) -BoundParameter $boundParameters -Fallback $Existing.metadata.policyEntitlement #Top level properties merge against the policy itself $Policy = Merge-Parameter -Template ([ordered]@{ principals = $null conditions = $null behavior = $null connectionMethod = $null }) -BoundParameter $boundParameters -Fallback $Existing $Policy.Insert(0, 'metadata', $Metadata) #Targets retrieved from the service are already in their final shape; targets supplied by the #caller come from a builder and are shaped here $Category = $Metadata['policyEntitlement']['targetCategory'] $PolicyTargets = if ($PSBoundParameters.ContainsKey('targets')) { $targets } else { $Existing.targets } $Policy['targets'] = Resolve-UAPTargetShape -targetCategory $Category -targets $PolicyTargets #Drop anything neither supplied nor held by the current policy foreach ($Key in @($Policy.Keys)) { if ($null -eq $Policy[$Key]) { $Policy.Remove($Key) } } foreach ($Key in @($Metadata.Keys)) { if ($null -eq $Metadata[$Key]) { $Metadata.Remove($Key) } } $body = $Policy | ConvertTo-Json -Depth 12 if ($PSCmdlet.ShouldProcess($policyId, 'Update Access Policy')) { #Send Request Invoke-IDRestMethod -Uri $URI -Method PUT -Body $body } }#process end { }#end } # .ExternalHelp IdentityCommand.UAP-help.xml function Test-UAPPolicy { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateNotNullOrEmpty()] [Alias('id')] [String]$policyId ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/policies/$policyId/validate" if ($PSCmdlet.ShouldProcess($policyId, 'Validate Access Policy')) { #Send Request Invoke-IDRestMethod -Uri $URI -Method POST } }#process end { }#end } #Copy IdentityCommand's private helpers into this module: this module's functions call them, and #the argument completer registrations below do so at import time. #Each copy is created from the function definition, so it runs in this module's scope and uses this #module's $ISPSSSession, whether IdentityCommand loaded from source or from its combined psm1. #Resolve a single IdentityCommand module: with more than one version loaded, Get-Module returns #an array. $Module = Get-Module -Name IdentityCommand | Sort-Object Version -Descending | Select-Object -First 1 if ($null -eq $Module) { throw 'The IdentityCommand module is not loaded. Import IdentityCommand and try again.' } & $Module { Get-ChildItem -Path Function: } | Where-Object { $_.ModuleName -eq $Module.Name -and -not $Module.ExportedFunctions.ContainsKey($_.Name) } | ForEach-Object { . ([scriptblock]::Create("function $($_.Name) {$($_.Definition)}")) } #region Registration #Policies are identified by an opaque id, so label the completion with the policy name. Register-ArgumentCompleter -ParameterName 'policyId' -ScriptBlock ( Get-ArgumentCompleter -RetrievalCommand 'Get-UAPPolicy' -ValueProperty 'policyId', 'id' -LabelProperty 'name' ) -CommandName @( 'Get-UAPPolicy' 'Remove-UAPPolicy' 'Set-UAPPolicy' 'Test-UAPPolicy' ) #Tags are free text, so offer those already in use on the tenant rather than a fixed set. Register-ArgumentCompleter -ParameterName 'policyTags' -ScriptBlock ( Get-ArgumentCompleter -RetrievalCommand 'Get-UAPPolicy' -ValueProperty 'policyTags' ) -CommandName @( 'Get-UAPPolicy' 'New-UAPPolicy' 'Set-UAPPolicy' ) #endregion Registration # Script scope session object for session data $ISPSSSession = [ordered]@{ tenant_url = $null User = $null TenantId = $null SessionId = $null WebSession = $null StartTime = $null ElapsedTime = $null LastCommand = $null LastCommandTime = $null LastCommandResults = $null LastError = $null LastErrorTime = $null } | Add-CustomType -Type IdCmd.Session New-Variable -Name ISPSSSession -Value $ISPSSSession -Scope Script -Force |