NexusIQ.psm1
"Import Microsoft.PowerShell.Commands.WebRequestMethod" | Out-Null filter Invoke-NexusIQAPI { [CmdletBinding()] param ( [Parameter(Mandatory)] [String]$Path, [Microsoft.PowerShell.Commands.WebRequestMethod]$Method = "Get", # Use the api or rest extension [ValidateSet("api","rest","ui")] [string]$RequestType = "api", [Hashtable]$Parameters, $Body, [string]$ContentType, # Optionally where to output the result [string]$OutFile ) $Settings = Get-NexusIQSettings $StringBuilder = [System.Text.StringBuilder]::new("$($Settings.BaseUrl)$RequestType") if ($RequestType -eq "api") { $StringBuilder.Append("/$($Settings.APIVersion.ToString())") | Out-Null } $StringBuilder.Append("/$Path") | Out-Null if ($Parameters) { $Separator = "?" $Parameters.Keys | ForEach-Object { $StringBuilder.Append(("{0}{1}={2}" -f $Separator,$_,[System.Web.HttpUtility]::UrlEncode($Parameters."$_".ToString()))) | Out-Null $Separator = "&" } } $Uri = $StringBuilder.ToString() Write-Verbose "Invoking Url $Uri" $Splat = @{ Uri=$Uri Method=$Method } if ($PSVersionTable.PSVersion.Major -ge 6) { $Splat.Add("Authentication","Basic") $Splat.Add("Credential",$Settings.Credential) } else { $Pair = "$($Settings.Username):$($Settings.Credential.GetNetworkCredential().Password)" $EncodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($Pair)) $Headers = @{ Authorization = "Basic $EncodedCreds" } $Splat.Add("Headers",$Headers) } if ($ContentType) { $Splat.Add("ContentType",$ContentType) } # Either output to a file or save the response to the "Response" variable if ($PSBoundParameters.ContainsKey("Outfile")) { ($Splat.Add("OutFile",$OutFile) ) } else { $Splat.Add("OutVariable","Response") } if ($Body) { $Splat.Add("Body",$Body) } Invoke-RestMethod @Splat | Out-Null if ($Response) { Write-Verbose "Unravel the response so it outputs each item to the pipeline instead of all at once" for ([UInt16]$i = 0; $i -lt $Response.Count; $i++) { $Response[$i] } } } class NexusIQSettings { static [String]$SaveDir = "$env:APPDATA$([System.IO.Path]::DirectorySeparatorChar)NexusIQ" static [String]$SavePath = "$([NexusIQSettings]::SaveDir)$([System.IO.Path]::DirectorySeparatorChar)Auth.xml" # Parameters [String]$BaseUrl [PSCredential]$Credential [NexusIQAPIVersion]$APIVersion NexusIQSettings([PSCredential]$Credential,[uri]$BaseUrl,[NexusIQAPIVersion]$APIVersion) { $this.BaseUrl = $BaseUrl $this.Credential = $Credential $this.APIVersion = $APIVersion } } enum NexusIQAPIVersion { v1 v2 } <# .SYNOPSIS Retrieves all the applications in NexusIQ #> filter Find-NexusIQApplication { [CmdletBinding()] param ( # Name of the application to search for by wildcard. [SupportsWildcards()] [string]$Name ) Write-Verbose "Listing all applications..." $Applications = Invoke-NexusIQAPI -Path "applications" | Select-Object -ExpandProperty applications if ($Name) { $Applications | Where-Object -Property name -Like $Name } else { $Applications } } <# .SYNOPSIS Retrieves the Application in Nexus IQ based on its "Public ID" .EXAMPLE Get-NexusIQApplication -ApplicationId App1Id .EXAMPLE Get-NexusIQApplication -Name App1Name .EXAMPLE Get-NexusIQApplication -Name App1* .NOTES https://help.sonatype.com/iqserver/automating/rest-apis/application-rest-api---v2 #> filter Get-NexusIQApplication { [CmdletBinding(DefaultParameterSetName="Id")] param ( # This is the application ID for the application. In the IQ Server GUI this is represented by the "Application" field. It must be unique., i.e. publicId [Parameter(Mandatory,ParameterSetName="Id",ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias("ApplicationId")] [string[]]$PublicId, # This is the name of the application. In the IQ Server GUI this corresponds to the "Application Name" field. It must be unique. [Parameter(Mandatory,ParameterSetName="Name",Position=0)] [SupportsWildcards()] [string[]]$Name ) if ($PublicId) { foreach ($AppId in $PublicId) { Invoke-NexusIQAPI -Path "applications" -Parameters @{ publicId=$AppId } | Select-Object -ExpandProperty applications -OutVariable Result if (-not $Result) { Write-Error "No application with ID $AppId" } } } elseif ($Name) { $AllApplications = Find-NexusIQApplication foreach ($AppName in $Name) { $AllApplications | Where-Object -Property name -Like $AppName | Select-Object -ExpandProperty publicId | ForEach-Object -Process { Write-Verbose "Found app with name $AppName and id $_" Invoke-NexusIQAPI -Path "applications" -Parameters @{ publicId=$_ } | Select-Object -ExpandProperty applications } } } } <# .SYNOPSIS Retrieves the Application in Nexus IQ based on its "Public ID" .EXAMPLE New-NexusIQApplication -ApplicationId AppId1 -Name "My Special App" -OrganizationName MyOrg .NOTES https://help.sonatype.com/iqserver/automating/rest-apis/application-rest-api---v2 #> filter New-NexusIQApplication { [CmdletBinding()] param ( # This is the application ID for the application. In the IQ Server GUI this is represented by the "Application" field. It must be unique., i.e. publicId [Parameter(Mandatory)] [Alias("ApplicationId")] [string]$PublicId, # This is the name of the application. In the IQ Server GUI this corresponds to the "Application Name" field. It must be unique. [Parameter(Mandatory)] [string]$Name, # Name of the organization to add this application to [Parameter(Mandatory)] [string]$OrganizationName ) $Organization = Get-NexusIQOrganization -Name $OrganizationName Invoke-NexusIQAPI -Path "applications" -Method Post -Body ( [PSCustomObject]@{ publicId = $PublicId name = $Name organizationId = $Organization.id } | ConvertTo-Json ) -ContentType "application/json" } <# .SYNOPSIS Removes an application .EXAMPLE Remove-NexusIQApplication -ApplicationId AppId1 .EXAMPLE Get-NexusIQApplication -ApplicationId AppId1 | Remove-NexusIQApplication .NOTES https://help.sonatype.com/iqserver/automating/rest-apis/application-rest-api---v2 #> filter Remove-NexusIQApplication { [CmdletBinding()] param ( # This is the application ID for the application. In the IQ Server GUI this is represented by the "Application" field. It must be unique., i.e. publicId [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [Alias("ApplicationId")] [string]$PublicId ) # Halt if the application wasn't found $Application = Get-NexusIQApplication -PublicId $PublicId -ErrorAction Stop Invoke-NexusIQAPI -Path "applications/$($Application.id)" -Method Delete } <# .SYNOPSIS Set an Application's properties in Nexus IQ based on its "Public ID" .EXAMPLE Get-NexusIQApplication -PublicId App1Id | Set-NexusIQApplication -PublicId App2Id -Name "This is my renamed app" .NOTES https://help.sonatype.com/iqserver/automating/rest-apis/application-rest-api---v2 #> filter Set-NexusIQApplication { [CmdletBinding()] param ( # This is the application ID for the application. In the IQ Server GUI this is represented by the "Application" field. It must be unique., NOT the public Id [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string]$Id, # The new Public Id to set the application to [string]$PublicId, # The new name [string]$Name ) $IQApp = Invoke-NexusIQAPI -Path "applications/$Id" if ($PublicId) { $IQApp.publicId = $PublicId } if ($Name) { $IQApp.name = $Name } Invoke-NexusIQAPI -Path "applications/$Id" -Body ($IQApp | ConvertTo-Json -Depth 99) -Method Put -ContentType "application/json" } <# .SYNOPSIS Retrieves the saved profile information of the current user, including BaseUrl and the userCode and passCode they are using. No parameters required. #> filter Get-NexusIQSettings { [CmdletBinding()] [OutputType([NexusIQSettings])] param () if (Test-Path -Path ([NexusIQSettings]::SavePath)) { $XML = Import-Clixml -Path ([NexusIQSettings]::SavePath) [NexusIQSettings]::new($XML.Credential,$XML.BaseUrl,$XML.APIVersion) } else { throw "Use Connect-NexusIQ to create a login profile" } } <# .SYNOPSIS Saves the user's Nexus IQ token using a saved PSCredential object stored as a CliXml file with an XML extension. Only works on a per-machine, per-user basis. .PARAMETER Credential PSCredential where the username is the UserCode and the password is the PassCode. This will be passed to Nexus IQ when calling the API. PowerShell 7+ automatically formats the username and password properly using Base-64 encoding and Basic authentication. .PARAMETER BaseUrl The URL of the Nexus IQ website .EXAMPLE Save-NexusIQLogin -BaseUrl https://nexusiq.mycompany.com .EXAMPLE # Reuse an existing profile's base URL and change the credentials $Settings = Get-NexusIQSettings $Settings | Connect-NexusIQ -Credential (Get-Credential) #> filter Connect-NexusIQ { [CmdletBinding()] [Alias("Login-NexusIQ","Save-NexusIQLogin")] param ( [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string]$BaseUrl, [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [PSCredential]$Credential, [Parameter(ValueFromPipeline)] [NexusIQAPIVersion]$APIVersion = "V2" ) if (-not (Test-Path([NexusIQSettings]::SaveDir))) { New-Item -Type Directory -Path ([NexusIQSettings]::SaveDir) | Out-Null } else { Write-Verbose "Profile folder already existed" } $Settings = [NexusIQSettings]::new($Credential,$BaseUrl,$APIVersion) $Settings | Export-CliXml -Path ([NexusIQSettings]::SavePath) -Encoding 'utf8' -Force $Settings } <# .SYNOPSIS Removes the user's login profile .EXAMPLE Disconnect-NexusIQ #> filter Disconnect-NexusIQ { [CmdletBinding()] [Alias("Logout-NexusIQ","Remove-NexusIQLogin")] param () if (Test-Path ([NexusIQSettings]::SaveDir)) { Remove-Item [NexusIQSettings]::SaveDir -Recurse } else { Write-Warning "The profile did not exist in path $([NexusIQSettings]::SaveDir)" } } <# .SYNOPSIS Verifies the user can log into the system. No parameters required. #> filter Test-NexusIQLogin { [CmdletBinding()] [OutputType([bool])] param () try { if (Invoke-NexusIQAPI -Path "userTokens/currentUser/hasToken" -ErrorAction Stop) { $true } } catch { $false } } <# .SYNOPSIS Retrieves an Organization in Nexus IQ .EXAMPLE Get-NexusIQOrganization -Name MyOrg .EXAMPLE "MyOrg1" | Get-NexusIQOrganization .NOTES https://help.sonatype.com/iqserver/automating/rest-apis/organizations-rest-api---v2 #> filter Get-NexusIQOrganization { [CmdletBinding(DefaultParameterSetName="Id")] param ( # Name of the organization [Parameter(ParameterSetName="Name",ValueFromPipeline,Position=0)] [string[]]$Name, # Id of the organization [Parameter(ParameterSetName="Id",ValueFromPipelineByPropertyName)] [string[]]$Id ) if ($Name) { foreach ($OrgName in $Name) { Invoke-NexusIQAPI -Path "organizations" -Parameters @{ organizationName=$OrgName } | Select-Object -ExpandProperty organizations } } elseif ($Id) { foreach ($OrgId in $Id) { Invoke-NexusIQAPI -Path "organizations/$OrgId" } } else { (Invoke-NexusIQAPI -Path "organizations").organizations } } <# .SYNOPSIS Retrieves polices and their associated organizations or applications. Basically makes it easier for the user to look up policies by doing all the heavy lifting under the hood. .EXAMPLE Get-NexusIQPolicy -Type Organization -Name MyOrg # Retrieves all the policies of the specified organization .EXAMPLE Get-NexusIQPolicy -Type Application -Name MyApp1 # Retrieves the policies of the specified application .NOTES https://help.sonatype.com/iqserver/automating/rest-apis/policy-violation-rest-api---v2#PolicyViolationRESTAPIv2-Step1-GetthePolicyIDs #> filter Get-NexusIQPolicy { [CmdletBinding()] param ( # Whether to retrieve an application's policies or an organization's [ValidateSet("Organization","Application")] [string]$Type, # Name of the organization to query for policies [string[]]$Name, # Name of the application to query for policies [Parameter(ParameterSetName="Application Name")] [string[]]$ApplicationName ) switch ($Type) { "Organization" { $Organizations = Get-NexusIQOrganization -Name $Name $Name | Where-Object { $_ -notin $Organizations.name } | ForEach-Object { Write-Error "The organization '$_' was not found" -ErrorAction Stop } Get-NexusIQPolicyId | Where-Object -Property ownerType -EQ "ORGANIZATION" | Where-Object -Property ownerId -In $Organizations.id continue } "Application" { $Applications = Get-NexusIQApplication -Name $Name $Name | Where-Object { $_ -NotIn $Applications.name } | ForEach-Object { Write-Error "The application '$_' was not found" -ErrorAction Stop } Get-NexusIQPolicyId | Where-Object -Property ownerType -EQ "APPLICATION" | Where-Object -Property ownerId -In $Applications.id continue } default { # Just retrieve all of them Get-NexusIQPolicyId } } } <# .SYNOPSIS Retrieves the policy IDs used to retrieve policy violations .EXAMPLE $PolicyInfo = Get-NexusIQPolicyId Get-NexusIQPolicyViolation -PolicyId $PolicyInfo[0].id .NOTES https://help.sonatype.com/iqserver/automating/rest-apis/policy-violation-rest-api---v2#PolicyViolationRESTAPIv2-Step1-GetthePolicyIDs #> filter Get-NexusIQPolicyId { [CmdletBinding()] param () (Invoke-NexusIQAPI -Path "policies").policies } <# .SYNOPSIS The Policy Violation REST APIs allow you to access and extract policy violations gathered during the evaluation of applications. In most cases the desire for getting to this data is to integrate into other tools your company may have. For example you may have a specific dashboard or reporting application that should have this data. .EXAMPLE $PolicyInfo = Get-NexusIQPolicyId | Where-Object -Property name -EQ "Security-High" Get-NexusIQPolicyViolation -PolicyId $PolicyInfo.id .EXAMPLE Get-NexusIQPolicyId | Where-Object -Property threatLevel -gt 5 | Get-NexusIQPolicyViolation .NOTES https://help.sonatype.com/iqserver/automating/rest-apis/policy-violation-rest-api---v2#PolicyViolationRESTAPIv2-Step2-GetthePolicyViolations #> filter Get-NexusIQPolicyViolation { [CmdletBinding()] param ( # Id of the policy to find the violations for [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias("PolicyId")] [guid[]]$Id ) foreach ($PolicyId in $Id) { Invoke-NexusIQAPI -Path "policyViolations" -Parameters @{ p=$PolicyId } } } <# .SYNOPSIS Retrieves reports for all stages of an application #> filter Find-NexusIQReport { [CmdletBinding()] param ( # The unique ID of the application [Parameter(Mandatory)] [Alias("ApplicationId")] [string]$PublicId ) Write-Verbose "Finding application's internal Id with public ID $PublicId" $InternalId = Get-NexusIQApplication @PSBoundParameters | Select-Object -ExpandProperty id Invoke-NexusIQAPI -Path "reports/applications/$InternalId" } <# .SYNOPSIS Retrieves reports for the specified stage of an application .EXAMPLE Get-NexusIQReport -ApplicationId MyApp1 -Stage stage-release .EXAMPLE Get-NexusIQApplication -ApplicationId MyApp1 | Get-NexusIQReport -Stage stage-release .NOTES https://help.sonatype.com/iqserver/automating/rest-apis/application-rest-api---v2#ApplicationRESTAPIv2-Step1-GettheOrganizationID #> filter Get-NexusIQReport { [CmdletBinding()] param ( # The unique ID of the application [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [Alias("ApplicationId")] [string]$PublicId, # Stage the report was run on [Parameter(Mandatory)] [ValidateSet("stage-release","source","build","release")] [string]$Stage ) Find-NexusIQReport -PublicId $PublicId | Where-Object -Property stage -EQ $Stage } <# .SYNOPSIS Downloads a security scan report based on the type specified. It will end up in your default download location. .EXAMPLE $NexusApp = Get-NexusIQApplication -ApplicationId MyAppId -ErrorAction Stop $FileName = "$($NexusApp.publicId.Substring(0,5))_$($NexusApp.name).pdf" $IQReportPath = "$env:OneDrive\Documents\$FileName" # Set it to the app's display name Export-NexusIQReport -ApplicationId MyAppId1 -Stage stage-release -ReportType PDF -OutFile $IQReportPath .EXAMPLE Get-NexusIQApplication -ApplicationId MyAppId | Export-NexusIQReport -Stage stage-release -ReportType PDF -OutFile "$env:TEMP\Report.pdf" #> filter Export-NexusIQReport { [CmdletBinding()] param ( # The unique ID of the application [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [Alias("ApplicationId")] [string]$PublicId, # Stage the report was run on [Parameter(Mandatory)] [ValidateSet("stage-release","source","build","release")] [string]$Stage, # Format to output the data in [Parameter(Mandatory)] [ValidateSet("RAW","PDF")] [string]$ReportType, # Directory to output the reports to [Parameter(Mandatory)] [ValidateScript({ Test-Path (Split-Path $_) })] [string]$OutFile ) $ReportsInfo = Get-NexusIQReport -PublicId $PublicId -Stage $Stage if ($ReportsInfo) { switch ($ReportType) { "PDF" { $BasePath = $ReportsInfo.reportDataUrl -replace "api\/v2\/applications\/","report/" -replace "\/raw","" -replace "\/reports\/","/" Invoke-NexusIQAPI -Path "$BasePath/printReport" -RequestType rest -OutFile $OutFile } "RAW" { $Path = $ReportsInfo.reportDataUrl -replace "api\/v2\/","" Invoke-NexusIQAPI -Path $Path -RequestType api -OutFile $OutFile } } Get-ItemProperty -Path $OutFile } else { Write-Warning "No report was found for App Id '$PublicId' in stage '$Stage'. Have you generated a report?" } } <# .SYNOPSIS Starts a scan and waits for the scan to complete. Doesn't really work right and the documentation seems incorrect. .PARAMETER ApplicationId The application ID for your application .PARAMETER TargetDirectory Working directory to scan files within .PARAMETER Target This is the path to a specific application archive file, a directory containing such archives or the ID of a Docker image. For archives, a number of formats are supported, including jar, war, ear, tar, tar.gz, zip and many others. You can specify multiple scan targets ( directories or files) separated by spaces test/dir/*/*.jar test/*/*.ear .EXAMPLE Invoke-NexusIQScan -ApplicationId AppId1 -TargetDirectory "$env:USERPROFILE\MyRepo" -Target "**/*.dll" .NOTES https://help.sonatype.com/iqserver/integrations/nexus-iq-cli #> filter Invoke-NexusIQScan { [CmdletBinding()] param ( [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [Alias("ApplicationId")] [string]$PublicId, [Parameter(Mandatory)] [Alias("Directory")] [string]$TargetDirectory, [Parameter(Mandatory)] [string]$Target, [ValidateSet("stage-release","source","build","release")] [string]$Stage = "build", [ValidateSet("Windows","Linux","Mac","Cross-platform")] [string]$Platform = "Windows", [string]$Version = "1.161.0-01+630" ) $CliPath = Save-NexusIQCli -Platform $Platform -Version $Version -PassThru $Settings = Get-NexusIQSettings Get-NexusIQApplication -PublicId $PublicId | Out-Null Push-Location Set-Location $TargetDirectory $ParamFileName = "$([NexusIQSettings]::SaveDir)$([System.IO.Path]::DirectorySeparatorChar)cli-params.txt" @" --application-id $PublicId --server-url $($Settings.BaseUrl) --stage $Stage $Target "@.TrimStart() | Out-File -FilePath $ParamFileName if ($Platform -eq "Cross-Platform") { $Java = "$env:ProgramFiles$([System.IO.Path]::DirectorySeparatorChar)Microsoft$([System.IO.Path]::DirectorySeparatorChar)jdk-11.0.12.7-hotspot$([System.IO.Path]::DirectorySeparatorChar)bin$([System.IO.Path]::DirectorySeparatorChar)java.exe" if (-not (Test-Path $Java)) { $Java = "java" } . "$Java" -jar "$CliPath"--authentication "$($Settings.Credential.Username)`:$($Settings.Credential.GetNetworkCredential().Password)" @$ParamFileName } else { . "$CliPath" --authentication "$($Settings.Credential.Username)`:$($Settings.Credential.GetNetworkCredential().Password)" @$ParamFileName } Remove-Item $ParamFileName Pop-Location } filter Save-NexusIQCli { [CmdletBinding()] param ( [ValidateSet("Windows","Linux","Mac","Cross-Platform")] [string]$Platform = "Windows", [ValidateNotNullOrEmpty()] [string]$Version = "1.161.0-01+630", # Tells the function to output the full path to the CLI tool [switch]$PassThru ) $CliName = @{ Windows = "nexus-iq-cli.exe" Linux = "nexus-iq-cli" Mac = "nexus-iq-cli" "Cross-Platform" = "nexus-iq-cli-$Version.jar" -replace "\+.*\.jar",".jar" } $CliPath = "$([NexusIQSettings]::SaveDir)$([System.IO.Path]::DirectorySeparatorChar)$($CliName.Item($Platform))" if (-not (Test-Path $CliPath)) { Write-Verbose "CLI tool wasn't found '$CliPath'. Downloading..." $Links = @{ Windows = "https://download.sonatype.com/clm/scanner/nexus-iq-cli-$Version-windows.zip" Linux = "https://download.sonatype.com/clm/scanner/nexus-iq-cli-$Version-unix.zip" Mac = "https://download.sonatype.com/clm/scanner/nexus-iq-cli-$Version-mac.pkg" "Cross-Platform" = "https://download.sonatype.com/clm/scanner/latest.jar" } $Url = $Links.Item($Platform) $OrigSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol if ($PSVersionTable.PSEdition -ne "Core" -and "Tls12" -notin ([Net.ServicePointManager]::SecurityProtocol)) { [Net.ServicePointManager]::SecurityProtocol=@(([Net.ServicePointManager]::SecurityProtocol),[Net.SecurityProtocolType]::Tls12) } $WebClient = New-Object Net.WebClient # Way faster than Invoke-WebRequest $DefaultProxy = [System.Net.WebRequest]::DefaultWebProxy if ($DefaultProxy -and (-not $DefaultProxy.IsBypassed($Url))) { $WebClient.Proxy = New-Object Net.WebProxy($DefaultProxy.GetProxy($Url).OriginalString, $true) $WebClient.Proxy.UseDefaultCredentials = $true } $ArchivePath = "$env:TEMP$([System.IO.Path]::DirectorySeparatorChar)$([System.IO.Path]::GetFileName($Links.Item($Platform)))" Write-Verbose "Downloading package using URL '$Url'" $WebClient.DownloadFile($Url, $ArchivePath) switch ([System.IO.Path]::GetExtension($ArchivePath)) { ".zip" { [System.IO.Compression.ZipFile]::ExtractToDirectory($ArchivePath,(Split-Path $CliPath)) # Expand-Archive $ArchivePath -Destination "$([NexusIQSettings]::SaveDir)" } ".pkg" { pkgutil --expand-full "$ArchivePath" "$(Split-Path $CliPath)" } ".jar" { Move-Item $ArchivePath -Destination $CliPath } } if ($PSVersionTable.PSVersion -lt 6) { [Net.ServicePointManager]::SecurityProtocol = $OrigSecurityProtocol } } else { Write-Verbose "The executable '$CliPath' was already downloaded" } if ($Platform -ne "Cross-Platform") { if (-not (Test-Path $CliPath)) { Write-Error "Something went wrong and the cli wasn't found"} else { Remove-Item $ArchivePath } } if ($PassThru) { $CliPath } } |