Archive for February 2021
Set-AzWebApp: Case of The Unexplained 409 Conflict When Adding HostName
tldr;
With Azure App Service, you could have the same Custom Domain assigned to multiple App Services at the same time. With one condition though: The same custom domain could not be assigned to two app services if there are in the same Deployment Unit <Scale Unit>.
When you call Set-AzWebApp with an updated Custom Domain Name (HostName), you expect the commandlet Set-AzWebApp to add the HostName to the desired web app. In case of a whatever error, you expect the Set-AzWebApp to throw an exception if the addition of the HostName failed.
Set-AzWebApp meticulously and miserably notifies you with a warning – not an error, a warning in a specific case that goes deep in to the Deployment Units (previously called Scale Units) and the App Service Plan you have chosen.
“
Microsoft.Azure.Management.WebSites.Models.DefaultErrorResponseException:
Operation returned an invalid status code ‘Conflict’
”
Here’s a screenshot of Azure Release Pipelines silently ignoring the warning as it was supposed to (and Set-AzWebApp should have thrown an error in possible senses instead of a warning), and letting the further dependent tasks fail.
With advise from one of my colleagues, when looked at the WebApp to which we were trying to add the host name to, it revealed another detailed error.
"statusMessage": "{\"Code\":\"Conflict\",\"Message\":\"The host name redacted is already assigned to another Azure website: redacted.\",\"Target\":null,\"Details\":[{\"Message\":\"The host name redacted is already assigned to another Azure website: redacted.\"},{\"Code\":\"Conflict\"},{\"ErrorEntity\":{\"ExtendedCode\":\"54004\",\"MessageTemplate\":\"The host name {0} is already assigned to another Azure website: {1}.\",\"Parameters\":[\"redacted\",\"redacted\"],\"Code\":\"Conflict\",\"Message\":\"The host name redacted is already assigned to another Azure website: redacted.\"}}],\"Innererror\":null}",
With Azure App Service, you could have the same Custom Domain assigned to multiple App Services at the same time. With one condition though: The same custom domain could not be assigned to two app services if there are in the same Deployment Unit <Scale Unit>.
Here’s some additional documentation from msdn that details out the condition.
You can migrate an active custom domain in Azure, between subscriptions or within the same subscription. However, such a migration without downtime requires the source app and the target app are assigned the same custom domain at a certain time. Therefore, you need to make sure that the two apps are not deployed to the same deployment unit (internally known as a webspace). A domain name can be assigned to only one app in each deployment unit.
You can find the deployment unit for your app by looking at the domain name of the FTP/S URL
<deployment-unit>.ftp.azurewebsites.windows.net
. Check and make sure the deployment unit is different between the source app and the target app. The deployment unit of an app is determined by the App Service plan it’s in. It’s selected randomly by Azure when you create the plan and can’t be changed. Azure only makes sure two plans are in the same deployment unit when you create them in the same resource group and the same region, but it doesn’t have any logic to make sure plans are in different deployment units. The only way for you to create a plan in a different deployment unit is to keep creating a plan in a new resource group or region until you get a different deployment unit.
Powershell UnitTesting with Pester: Return a Different Mock Response For the Second or Nth Time
Credits: https://github.com/pester/Pester/issues/574
Let’s say you want to update the ‘Custom Domain Name’ for an App Service in Azure through powershell. Because Azure commandlets and API’s could sometimes be flawed, after updating, you want to verify whether the ‘Custom Domain Name’ is added to the App Service.
One way to achieve that is as in the commandlet below:
1. Get-AzWebApp
2. Add the HostName
3. Set-AzWebApp
4. After setting, Get-AzWebApp
5. Verify if the HostName is added
Now, when mocking Get-AzWebApp, because we are calling Get-AzWebApp twice, we should be able to return a different response the second time Get-AzWebApp is called. There is a suggestion to use a $script level or a $global level variable called $mockCounter or $mockcounterwhatevernameforthecounteryouwanttogive here in the the Pester Issues.
Here’s a working example of an actual commandlet, and a unit test.
function Add-RenTWebAppDomainName { <# .SYNOPSIS Adds a custom domain for an App Service. .DESCRIPTION Adds a custom domain for an App Service. .EXAMPLE Add-RenTWebAppDomainName -ResourceGroupName 'RenT-12' -WebAppName "rentapp" -Hostname 'rentapp.renounced.com' .PARAMETER ResourceGroupName The resource group in which the App Service resides. .PARAMETER WebAppName The name of the App Service to which a custom domain name needs to be added. .PARAMETER HostName The custom domain name that needs to be added to the App Service. #> [OutputType()] [Cmdletbinding()] param( [Parameter(Mandatory = $true)] [string]$ResourceGroupName, [Parameter(Mandatory = $true)] [string]$WebAppName, [Parameter(Mandatory = $true)] [string]$HostName ) begin { Write-RenTLogStartCmdlet -CallerInvocation $MyInvocation } process { try { Write-Verbose ("Associating custom domain name '{0}' for the app {1}" -f $HostName, $WebAppName) $WebApp = Get-AzWebApp -Name $WebAppName -ResourceGroupName $ResourceGroupName $HostNames = $WebApp.HostNames $HostNames.Add($HostName) Set-AzWebApp -Name $WebAppName -ResourceGroupName $ResourceGroupName -HostNames $HostNames Write-Verbose ("Verifying if the custom domain name '{0}' for the app {1} is added" -f $HostName, $WebAppName) $WebApp = Get-AzWebApp -Name $WebAppName -ResourceGroupName $ResourceGroupName $HostNames = $WebApp.HostNames if($HostName -in $HostNames){ Write-Verbose ("Successfully associated custom domain name '{0}' for the app {1}" -f $HostName, $WebAppName) } else{ Write-Error ("Could not associate custom domain name '{0}' for the app {1}" -f $HostName, $WebAppName) } } catch { Write-Error $_.Exception.Message } } end { Write-RenTLogEndCmdlet -CallerInvocation $MyInvocation } }
However, when you want to Unit Test the commandlet, you want to return a different response the second time Get-AzWebApp is called. Here’s a sample with 100% code coverage:
. (Join-Path $PSScriptRoot ../../private/logging/Write-RenTLogEndCmdlet.ps1) . (Join-Path $PSScriptRoot ../../private/logging/Write-RenTLogStartCmdlet.ps1) . (Join-Path $PSScriptRoot Add-RenTWebAppDomainName.ps1) Describe "Add-RenTWebAppDomainName" { function Set-AzWebApp {[CmdletBinding(SupportsShouldProcess)] param($Name,$ResourceGroupName,$HostNames) if ($Force -or $PSCmdlet.ShouldProcess("ShouldProcess?")){} return} Context "A HostName is supplied for a WebAppName in a ResourceGroupName" { It "It adds the HostName (Custom Domain Name) to the WebApp." { $script:mockCounter = 0; function Get-AzWebApp {} Mock Get-AzWebApp { $returnValue = $null if ($script:mockCounter -eq 0){ $returnValue = [PSCustomObject]@{ Name = 'WebAppName'; HostNames = [System.Collections.Generic.List[System.Object]]@('HostName1')}; } else{ $returnValue = [PSCustomObject]@{ Name = 'WebAppName'; HostNames = [System.Collections.Generic.List[System.Object]]@('HostName1','HostName2')}; } $script:mockCounter++ return $returnValue } Mock Set-AzWebApp { } Add-RenTWebAppDomainName -ResourceGroupName 'RG' ` -WebAppName 'WebAppName' ` -HostName 'HostName2' ` -ErrorAction Stop Assert-MockCalled Get-AzWebApp -Times 2 -Exactly -Scope It Assert-MockCalled Set-AzWebApp -Times 1 -Exactly -Scope It } It "It runs with errors." { Mock Get-AzWebApp -MockWith { throw "Mocked error" } { Add-RenTWebAppDomainName -ResourceGroupName 'RG' -WebAppName 'WebAppname' -HostName 'HostName' -ErrorAction Stop } | Should Throw "Mocked error" Assert-MockCalled Get-AzWebApp -Times 1 -Exactly -Scope It } It "Fails to add the HostName for the WebApp"{ $script:mockCounter = 0; function Get-AzWebApp {} Mock Get-AzWebApp { $returnValue = $null if ($script:mockCounter -eq 0){ $returnValue = [PSCustomObject]@{ Name = 'WebAppName'; HostNames = [System.Collections.Generic.List[System.Object]]@('HostName1')}; } else{ $returnValue = [PSCustomObject]@{ Name = 'WebAppName'; HostNames = [System.Collections.Generic.List[System.Object]]@('HostName1')}; } $script:mockCounter++ return $returnValue } Mock Set-AzWebApp { } { Add-RenTWebAppDomainName -ResourceGroupName 'RG' ` -WebAppName 'WebAppName' ` -HostName 'HostName2' ` -ErrorAction Stop } | Should Throw "Could not associate custom domain name 'HostName2' for the app WebAppName" Assert-MockCalled Get-AzWebApp -Times 2 -Exactly -Scope It Assert-MockCalled Set-AzWebApp -Times 1 -Exactly -Scope It } } }
Changing the Default Postgres User Identity
The default identify that ships with the package to run postgresql is postgres. Change it to postgresuser
sudo adduser postgresuser
sudo passwd postgresuser
/database/ is the mount for database files, so the default directory for postgres to be changed
Create a directory /pgsql/data under /database/
sudo mkdir /database/pgsql/data -p
Then change the ownership of /database/pgsql/data to the user postgresuser
sudo chown -R postgresuser:postgresuser /database/pgsql/data
Also change the ownership of the default install directory
sudo chown -R postgresuser:postgresuser /var/lib/pgsql
Delete the default postgres user
sudo userdel -r postgres
Look at the default system unit file for postgresql and extend the unit file configuration to override the default postgres user to postgresuser
cat /lib/systemd/system/postgresql.service
Extending systemd service unit files are extensively documented at – https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/sect-managing_services_with_systemd-unit_files#sect-Managing_Services_with_systemd-Unit_File_Modify
Create a directory postgresql.service.d under /etc/systemd/system
sudo mkdir /etc/systemd/system/postgresql.service.d/
Create a file named overrideuser.conf
sudo touch /etc/systemd/system/postgresql.service.d/overrideuser.conf
Edit the overrideuser.conf to provide User and Group information as the newly created postgresuser.
sudo vi /etc/systemd/system/postgresql.service.d/overrideuser.conf
[Service]
User=postgresuser
Group=postgresuser
:wq!
The configuration files from configuration directories in #/etc/systemd/system/ take precedence over the default unit files in #/usr/lib/systemd/system/.
Change the ownership of the /var/run/postgresql where the postgres will create a socket when running
sudo chown -R postgresuser:postgresuser /var/run/postgresql
/var/run/postgresql directory will be erased on every reboot, hence the directory need to be created during reboot with the ownership of the newly created postgresuser.
sudo vi /usr/lib/tmpfiles.d/postgresql.conf
d /var/run/postgresql 0755 postgresuser postgresuser –
:wq!
Failing to correctly modify the ownership and configuration of the /var/run/postgressql might results in errors like below: sudo systemctl status postgresql.service -l # FATAL: could not create lock file #"/var/run/postgresql/.s.PGSQL.5432.lock": Permission denied |
After the above changes, Enable postgresql.service to be run on system start, then reload the systemd daemon, start the postgresql.service, check the status to see if the service is running.
sudo systemctl enable postgresql.service
sudo systemctl daemon-reload
sudo systemctl restart postgresql.service
sudo systemctl status postgresql.service -l
Now postgresql.service should be running under the newly created user identify postgresuser.
Securing postgresql authentication methods
Review the authentication methods:
https://www.postgresql.org/docs/11/auth-methods.html
https://www.postgresql.org/docs/11/auth-password.html
postgresql by default users ident and trust for local networks and the default authentication methods are found at
sudo cat /var/lib/pgsql/data/pg_hba.conf
The password storage mechanism for postgresql users is configured at
sudo cat /var/lib/pgsql/data/postgresql.conf
Starting postgresql11, there is support for SHA-256 based passwords with the configuration scram-sha-256. So, change the default password hashing mechanism for postgresql users at postgresql.conf.
sudo vi /var/lib/pgsql/data/postgresql.conf
password_encryption = ‘scram-sha-256’
:wq!
Change the authentication settings in pg_hba.conf
Change peer to trust and ident to scram-sha-256
sudo vi /var/lib/pgsql/data/pg_hba.conf
# TYPE DATABASE USER ADDRESS METHOD
# "local" is for Unix domain socket connections only
local all all trust
# IPv4 local connections:
host all all 127.0.0.1/32 scram-sha-256
# IPv6 local connections:
host all all ::1/128 scram-sha-256
# Allow replication connections from localhost, by a user with the
# replication privilege.
local replication all peer
host replication all 127.0.0.1/32 ident
host replication all ::1/128 ident
Reload and start service after authentication change.
sudo systemctl status postgresql.service -l
sudo systemctl daemon-reload
sudo systemctl restart postgresql.service
sudo systemctl status postgresql.service -l
Verify authentication changes by logging in to psql and checking version.
su – postgresuser
psql –version
select * from pg_shadow;
List users, and change the password for the database user named postgresuser.
\du+
\password
After password change the ps_shadow table should show a password for the postgresuser starting with scram-sha-256.
select * from pg_shadow;
Exit from pgsql, and postgresuser.
Change the pg_hba.conf to enforce scram-sha-256 for local connections as well.
sudo vi /var/lib/pgsql/data/pg_hba.conf
Change trust to scram-sha-256
# TYPE DATABASE USER ADDRESS METHOD
# "local" is for Unix domain socket connections only
local all all scram-sha-256
# IPv4 local connections:
host all all 127.0.0.1/32 scram-sha-256
# IPv6 local connections:
host all all ::1/128 scram-sha-256
# Allow replication connections from localhost, by a user with the
# replication privilege.
local replication all scram-sha-256
host replication all 127.0.0.1/32 scram-sha-256
host replication all ::1/128 scram-sha-256
:wq!
Reload the daemon, and restart the postgresql.service.
sudo systemctl daemon-reload
sudo systemctl restart postgresql.service
sudo systemctl status postgresql.service -l
As a last change, login to postgres and delete the default user database ‘postgres’.
su – postgresuser
psql
Enter the newly created password for the database identify postgresuser.
List all databases
\l
drop database postgres;
Azure Resources Migration between subscriptions – code snippets
Credits: Vijayaragavan Inbaraj, and Mohammed Siyam
Before migration, review the supported resources at https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/move-support-resources
If the source and the destination subscription are in different tenants, then you’d want to create a ‘temp’ destination subscription, and change the ‘temp’ subscription’s tenant to the new tenant.
Steps:
- Check if quotas in the new subscription are equivalent to the quotas in the old subscription
- Check if the supported "Resource Providers" in the new subscription are in "Registered" state as the old subscription
- Gather a list of Resource Groups to be moved (filter by tags or names or a another filter)
- Gather a list of resources that do not support movement between subscriptions [Move operation support for resources]
- Talk to the team members, resource group owners, product owner and obtain their confirmation for the move
- Communicate possible downtime if any
- Create new empty resource groups with the same name and tags as in the old subscription
- Initiate the move command for one resource group at a time because it is safer to do it in case any errors occur
- After the resources in a resource group are moved, check for the number of resources in the moved Resource Group to see if all the resources are moved without fail
- Create a "Do-Not-Delete" Lock on the new resource groups that should not be deleted
- After verifying, delete the old resource groups
- Communicate
Below are some snippets that would assist in the above migration process.
Note:
1. Adjust the $whatif flags accordingly
2. Use the –Debug flag for Az commandlets to get more information on non-revealing error messages
############################################################################################################ #Snippet to check quotas ############################################################################################################ Set-AzContext -SubscriptionId 'oooooooo-oooo-llll-llll-dddddddddddd' #old-subscription Get-AzNetworkUsage ` -Location westeurope ` | Where-Object {$_.CurrentValue -gt 0} ` | Format-Table ResourceType, CurrentValue, Limit Get-AzStorageUsage ` -Location westeurope ` | Where-Object {$_.CurrentValue -gt 0} Get-AzVMUsage ` -Location westeurope ` | Where-Object {$_.CurrentValue -gt 0}
———–
############################################################################################################ #Snippet to register missing resource providers in the new subscription ############################################################################################################ Set-AzContext -SubscriptionId 'oooooooo-oooo-llll-llll-dddddddddddd' #old-subscription $ExistingResourceProvidersInOldSubscription = Get-AzResourceProvider Set-AzContext -SubscriptionId 'nnnnnnnn-nnnn-eeee-eeee-wwwwwwwwwwww' #new-subscription $ExistingResourceProvidersInNewSubscription = Get-AzResourceProvider foreach($ExistingResourceProviderInOldSubscription in $ExistingResourceProvidersInOldSubscription) { if($ExistingResourceProviderInOldSubscription.RegistrationState -eq 'Registered'){ $providerfound = $false; foreach($ExistingResourceProviderInNewSubscription in $ExistingResourceProvidersInNewSubscription){ if($ExistingResourceProviderInNewSubscription.ProviderNamespace -eq $ExistingResourceProviderInOldSubscription.ProviderNamespace){ $providerfound = $true; if($ExistingResourceProviderInNewSubscription.RegistrationState -eq 'Registered'){ Write-Host '------------------------------' Write-Host $ExistingResourceProviderInOldSubscription.ProviderNamespace 'PRESENT, REGISTERED' Write-Host '------------------------------' } else{ Write-Host $ExistingResourceProviderInOldSubscription.ProviderNamespace 'PRESENT, UNREGISTERED, Registering' Register-AzResourceProvider -ProviderNamespace $ExistingResourceProviderInOldSubscription.ProviderNamespace -WhatIf:$WhatIf } } } if(-Not($providerfound)){ Write-Host $ExistingResourceProviderInOldSubscription.ProviderNamespace 'ABSENT, Registering' Register-AzResourceProvider -ProviderNamespace $ExistingResourceProviderInOldSubscription.ProviderNamespace -WhatIf:$WhatIf } } }
————
############################################################################################################ #Snippet to gather resource groups to be moved ############################################################################################################ #A Class to hold information to be used for discussion later class Resource{ [string]$ResourceGroupName [string]$Name [string]$Moveable [string]$Owner [string]$Comments; Resource( [string]$ResourceGroupName, [string]$Name, [string]$Moveable, [string]$Owner, [string]$Comments ){ $this.ResourceGroupName = $ResourceGroupName $this.Name = $Name $this.Moveable = $Moveable $this.Owner = $Owner $this.Comments = $Comments } } $ResultObject = @([Resource]::new('--', '--', '--', '--', '--')) Set-AzContext -SubscriptionId 'oooooooo-oooo-llll-llll-dddddddddddd' #old-subscription # https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/move-support-resources $NonMovableResourceTypesWithinSameTenant = @('Microsoft.AAD/DomainServices', ` 'Microsoft.ContainerService/managedClusters', ` 'microsoft.insights/activityLogAlerts', ` 'microsoft.insights/metricalerts', ` 'microsoft.visualstudio/account', ` 'Sendgrid.Email/accounts') $StaticRG = Get-AzResourceGroup <# $StaticRG = Get-AzResourceGroup | Where-Object {$_.Tags -and ($_.Tags.GetEnumerator() | Where-Object {($_.Value -eq 'Development') -or ($_.Value -eq 'Production - Static')})} #> foreach ($RG in $StaticRG){ Write-Host $RG.ResourceGroupName $AllResources = Get-AzResource -ResourceGroupName $RG.ResourceGroupName $Name = '' $Moveable = '' $Owner = ''#$Rg.Tags['Owner'] $Comments = '' foreach ($Resource in $AllResources){ $ResourceType = $Resource.ResourceType if($NonMovableResourceTypesWithinSameTenant -contains $Resource.ResourceType){ $Moveable = 'False' $Comments = "Move operation not supported on this Resource Group because -- $ResourceType -- does not support move" } else{ $Moveable = 'True' $Comments = "Move operation supported for: $ResourceType" } $ResultObject += [Resource]::new($RG.ResourceGroupName, $Resource.Name, $Moveable, $Owner, $Comments) } } $ResultObject | ft -AutoSize -Wrap $ResultObject | Export-Csv -Path .\MoveableResources.csv -NoTypeInformation $ResultObject | Where-Object { $_.Moveable -eq 'FALSE'} | ft -AutoSize -Wrap $ResultObject | Group-Object -Property ResourceGroupName $StaticRG | Group-Object -Property ResourceGroupName
——————–
############################################################################################################ #Snippet to create RG ############################################################################################################ Set-AzContext -SubscriptionId 'nnnnnnnn-nnnn-eeee-eeee-wwwwwwwwwwww' #new-subscription foreach ($RG in $StaticRG){ Write-Host $RG.ResourceGroupName New-AzResourceGroup -Name $RG.ResourceGroupName -Location $RG.Location -Tag $RG.Tags -WhatIf:$false -Confirm;$false }
——————-
############################################################################################################ #Snippet to move resources ############################################################################################################ $RGsToBeMoved = @('MyRG') $NewSubscriptionId = 'nnnnnnnn-nnnn-eeee-eeee-wwwwwwwwwwww' #new-subscription Set-AzContext -SubscriptionId 'oooooooo-oooo-llll-llll-dddddddddddd' #old-subscription foreach($RG in $StaticRG){ if($RGsToBeMoved -contains $RG.ResourceGroupName){ $ResourceIdArray = Get-AzResource -ResourceGroupName $RG.ResourceGroupName | Where-Object {!($_.ParentResource)} | Foreach {"$($_.ResourceId)"} Move-AzResource -DestinationResourceGroupName $RG.ResourceGroupName -DestinationSubscriptionId $NewSubscriptionId -ResourceId $ResourceIdArray ` -WhatIf:$true -Verbose -Debug } }
—————————–
############################################################################################################ #Set Delete Lock on RG ############################################################################################################ Set-AzContext -SubscriptionId 'nnnnnnnn-nnnn-eeee-eeee-wwwwwwwwwwww' #new-subscription foreach ($resourcegroup in $StaticRG){ $resourcegroupname = $resourcegroup.ResourceGroupName Write-Host "RG Name $resourcegroupname" if(Get-AzResourceLock -ResourceGroupName $resourcegroupname -AtScope){ Write-Host "Lock Present Already" Set-AzResourceLock -LockName "Do-Not-Delete" -LockLevel CanNotDelete -ResourceGroupName $resourcegroupname -LockNotes 'contact it@renounced' Get-AzResourceLock -ResourceGroupName $resourcegroupname -AtScope | ft -AutoSize -Wrap } else{ Write-Host "Setting the lock" Set-AzResourceLock -LockName "Do-Not-Delete" -LockLevel CanNotDelete -ResourceGroupName $resourcegroupname -LockNotes 'contact it@renounced' #Set-AzResourceLock -LockName "Read-Only" -LockLevel ReadOnly -ResourceGroupName $resourcegroupname } }
————————
############################################################################################################ # Delete RGs ############################################################################################################ Set-AzContext -SubscriptionId 'oooooooo-oooo-llll-llll-dddddddddddd' #old-subscription $StaticRG | Foreach-Object { $resourceGroupName = $_.ResourceGroupName $RG = Get-AzResourceGroup -Name $resourceGroupName -ErrorVariable notPresent -ErrorAction SilentlyContinue if($notPresent){ Write-Host $resourceGroupName ' NOT PRESENT' } else{ $resources = Get-AzResource -ResourceGroupName $resourceGroupName $resourceCount = ($resources).Count Write-Host $resourceGroupName ' Resource Count: ' $resourceCount if ($resourceCount -eq 0){ Write-Host $resourceGroupName ' CAN BE DELETED' $locks = Get-AzResourceLock -ResourceGroupName $resourceGroupName if($locks){ Write-Host 'Locks found! REMOVING LOCKS' $locks | Foreach-Object { Remove-AzResourceLock -LockName $_.Name -ResourceGroupName $resourceGroupName -Force} } Remove-AzResourceGroup -Name $resourceGroupName -Force Write-Host $resourceGroupName ' DELETE ISSUED' } } }
Installing, Managing, and Running SONARQube Community on EC2
Access a pdf version of this content with rich formatting here – https://renouncedthoughts.files.wordpress.com/2021/02/installingmanagingandrunningsonarqubecommunityonec2-v1.3.pdf
Credits: Mohammed Siyam, Dave Kuppers
Contents
Installing the SonarQube Server
Installing Prerequisites
Installing Java
Configuring Java
Installing Postgresql
Configuring Default Postgres Identity
Configuring Postgres User for SonarQube
Configuring Authentication Methods for Postgres
Configuring Data Directory for Postgresql
Configuring Postgres Database and a Postgres User for SonarQube
Downloading and Installing SonarQube server
Creating SonarQube server identity
Configuring SonarQube server
Change System level memory and file system limits for SonarQube Server
Creating systemd files to run ServerQube as a background service
Running a SonarQube Scan with Sonar Runner
Git cloning required respositories
Installing and Configuring node.js to run analysis via sonar-scanner
Installing and Configuring sonar-scanner
Running source code analysis
Updating SonarQube Server
Administering SonarQube Server
Creating and Managing users
Enforcing Authentication
Troubleshooting
Installing the SonarQube Server
Installing Prerequisites
SonarQube has the following prerequisites
1. Java – as the runtime
2. Postgres – as the data store
3. Elastic Search – as the index engine
4. Node.js – required by sonar-scanner to scan javascript code
5. Sonar-scanner – the cli used to scan the source code in a directory
Detailed: https://docs.sonarqube.org/latest/requirements/requirements/
Installing Java
Verify the current JDK version
java -version
Prerequisites mention java version 11, so if java runtime (jre) is version 11, then the prerequisite is met.
However, the current version of java according to the date of this writing is version 14, so install version 14 of openjdk.
Download the latest openjdk from https://openjdk.java.net/install/
tar -xvf openjdk-14.0.1_linux-x64_bin.tar.gz
cd in to jdk-14.0.1
ls to see if bin directory exists
cd in to bin
ls to see if java exists
./java -version
should display the current version, that is 14.
Recursively copy the jdk to /usr/lib/jvm
sudo cp -r jdk-14.0.1 "/usr/lib/jvm"
Verify copy
ls "/usr/lib/jvm/jdk-14.0.1/" -l
Remote the temporary unzip and download directories
rm -rf jdk-14.0.1
rm openjdk-14.0.1_linux-x64_bin.tar.gz
Configuring Java
Adjust the alternatives to set the current version of java (openjdk 14)
alternatives –list
sudo alternatives –install "/usr/bin/java" java "/usr/lib/jvm/jdk-14.0.1/bin/java" 3
sudo alternatives –config java
Choose the java 14 from alternatives
Later, verify
java -version
openjdk version "14.0.1" 2020-04-14
OpenJDK Runtime Environment (build 14.0.1+7)
OpenJDK 64-Bit Server VM (build 14.0.1+7, mixed mode, sharing)
Also set alternatives for jar and javac to the latest openjdk 14, just in case
sudo alternatives –install /usr/bin/jar jar /usr/lib/jvm/jdk-14.0.1/bin/jar 1
sudo alternatives –set jar /usr/lib/jvm/jdk-14.0.1/bin/jar
sudo alternatives –install /usr/bin/javac javac /usr/lib/jvm/jdk-14.0.1/bin/javac 1
sudo alternatives –set javac /usr/lib/jvm/jdk-14.0.1/bin/
Installing Postgresql
The current version of postgres is 12 according to the date of this writing.
The postgres builds from postgres itself is not supported on amazon linux – https://www.postgresql.org/message-id/flat/15768-35c3af8405f5e346%40postgresql.org
Hence, fall back to version 11, as supplied in the amazon linux extras bundle – https://aws.amazon.com/amazon-linux-2/faqs/#Amazon_Linux_Extras
amazon-linux-extras | grep "post"
NOTE: The livepatch extra is in public preview, not meant for production use
5 postgresql9.6 available \
6 postgresql10 available [ =10 =stable ]
41 postgresql11 available [ =11 =stable ]
sudo yum clean metadata
sudo yum install postgresql
sudo yum list postgresql*
sudo yum install postgresql-server
sudo yum install postgresql-contrib
Configuring Default Postgres Identity
The default identify that ships with the package to run postgresql is postgres. Change it to postgresuser
sudo adduser postgresuser
sudo passwd postgresuser
/database/ is the mount for database files, so the default directory for postgres to be changed
Create a directory /pgsql/data under /database/
sudo mkdir /database/pgsql/data -p
Then change the ownership of /database/pgsql/data to the user postgresuser
sudo chown -R postgresuser:postgresuser /database/pgsql/data
Also change the ownership of the default install directory
sudo chown -R postgresuser:postgresuser /var/lib/pgsql
Delete the default postgres user
sudo userdel -r postgres
Look at the default system unit file for postgresql and extend the unit file configuration to override the default postgres user to postgresuser
cat /lib/systemd/system/postgresql.service
Extending systemd service unit files are extensively documented at – https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/sect-managing_services_with_systemd-unit_files#sect-Managing_Services_with_systemd-Unit_File_Modify
Create a directory postgresql.service.d under /etc/systemd/system
sudo mkdir /etc/systemd/system/postgresql.service.d/
Create a file named overrideuser.conf
sudo touch /etc/systemd/system/postgresql.service.d/overrideuser.conf
Edit the overrideuser.conf to provide User and Group information as the newly created postgresuser.
sudo vi /etc/systemd/system/postgresql.service.d/overrideuser.conf
[Service]
User=postgresuser
Group=postgresuser
:wq!
The configuration files from configuration directories in #/etc/systemd/system/ take precedence over the default unit files in #/usr/lib/systemd/system/.
Change the ownership of the /var/run/postgresql where the postgres will create a socket when running
sudo chown -R postgresuser:postgresuser /var/run/postgresql
/var/run/postgresql directory will be erased on every reboot, hence the directory need to be created during reboot with the ownership of the newly created postgresuser.
sudo vi /usr/lib/tmpfiles.d/postgresql.conf
d /var/run/postgresql 0755 postgresuser postgresuser –
:wq!
Failing to correctly modify the ownership and configuration of the /var/run/postgressql might results in errors like below:
sudo systemctl status postgresql.service -l
# FATAL: could not create lock file
#"/var/run/postgresql/.s.PGSQL.5432.lock": Permission denied
|
After the above changes, Enable postgresql.service to be run on system start, then reload the systemd daemon, start the postgresql.service, check the status to see if the service is running.
sudo systemctl enable postgresql.service
sudo systemctl daemon-reload
sudo systemctl restart postgresql.service
sudo systemctl status postgresql.service -l
Now postgresql.service should be running under the newly created user identify postgresuser.
Configuring Postgres User for SonarQube
Login as postgresuser
su – postgresuser
psql –version
should throw a fatal error that “postgresuser” does not exist. This expected behavior us because postgresql creates a template db for every user. So create a create template db for the user postgresuser.
psql
createdb postgresuser
SELECT version();
version
———————————————————————————————————-
PostgreSQL 11.5 on x86_64-koji-linux-gnu, compiled by gcc (GCC) 7.3.1 20180712 (Red Hat 7.3.1-6), 64-bit
(1 row)
exit to logout of psql prompt
exit to logout of postgresuser
Configuring Authentication Methods for Postgres
Review the authentication methods:
https://www.postgresql.org/docs/11/auth-methods.html
https://www.postgresql.org/docs/11/auth-password.html
postgresql by default users ident and trust for local networks and the default authentication methods are found at
sudo cat /var/lib/pgsql/data/pg_hba.conf
The password storage mechanism for postgresql users is configured at
sudo cat /var/lib/pgsql/data/postgresql.conf
Starting postgresql11, there is support for SHA-256 based passwords with the configuration scram-sha-256. So, change the default password hashing mechanism for postgresql users at postgresql.conf.
sudo vi /var/lib/pgsql/data/postgresql.conf
password_encryption = ‘scram-sha-256’
:wq!
Change the authentication settings in pg_hba.conf
Change peer to trust and ident to scram-sha-256
sudo vi /var/lib/pgsql/data/pg_hba.conf
# TYPE DATABASE USER ADDRESS METHOD
# "local" is for Unix domain socket connections only
local all all trust
# IPv4 local connections:
host all all 127.0.0.1/32 scram-sha-256
# IPv6 local connections:
host all all ::1/128 scram-sha-256
# Allow replication connections from localhost, by a user with the
# replication privilege.
local replication all peer
host replication all 127.0.0.1/32 ident
host replication all ::1/128 ident
Reload and start service after authentication change.
sudo systemctl status postgresql.service -l
sudo systemctl daemon-reload
sudo systemctl restart postgresql.service
sudo systemctl status postgresql.service -l
Verify authentication changes by logging in to psql and checking version.
su – postgresuser
psql –version
select * from pg_shadow;
List users, and change the password for the database user named postgresuser.
\du+
\password
After password change the ps_shadow table should show a password for the postgresuser starting with scram-sha-256.
select * from pg_shadow;
Exit from pgsql, and postgresuser.
Change the pg_hba.conf to enforce scram-sha-256 for local connections as well.
sudo vi /var/lib/pgsql/data/pg_hba.conf
Change trust to scram-sha-256
# TYPE DATABASE USER ADDRESS METHOD
# "local" is for Unix domain socket connections only
local all all scram-sha-256
# IPv4 local connections:
host all all 127.0.0.1/32 scram-sha-256
# IPv6 local connections:
host all all ::1/128 scram-sha-256
# Allow replication connections from localhost, by a user with the
# replication privilege.
local replication all scram-sha-256
host replication all 127.0.0.1/32 scram-sha-256
host replication all ::1/128 scram-sha-256
:wq!
Reload the daemon, and restart the postgresql.service.
sudo systemctl daemon-reload
sudo systemctl restart postgresql.service
sudo systemctl status postgresql.service -l
As a last change, login to postgres and delete the default user database ‘postgres’.
su – postgresuser
psql
Enter the newly created password for the database identify postgresuser.
List all databases
\l
drop database postgres;
Continue to change the default data directory for postgresql.
Configuring Data Directory for Postgresql
In the psql prompt, show data_directory displays the current data directory.
show data_directory;
data_directory
———————
/var/lib/pgsql/data
(1 row)
Exit psql and postgresuser.
\q
exit
Stop the postgresql.service
sudo systemctl stop postgresql.service
sudo systemctl status postgresql.service -l
Move the default data directory to /database mount point preserving the permissions and attributes.
sudo rsync -av /var/lib/pgsql /database
Change the default data directory configuration in /var/lib/pgsql/data/postgresql.conf
sudo vi /var/lib/pgsql/data/postgresql.conf
data_directory = ‘/database/pgsql/data’
:wq!
After changing the default data directory. Login to psql as postgresuser and check show data_directory;
su – postgresuser
psql
show data_directory;
data_directory
———————-
/database/pgsql/data
(1 row)
When the default data directory is moved, override the default systemd unit file configuration to enforce /database/pgsql/data.
sudo touch /etc/systemd/system/postgresql.service.d/overridedatadirectory.conf
sudo vi /etc/systemd/system/postgresql.service.d/overridedatadirectory.conf
[Service]
# Location of database directory
Environment=PGDATA=/database/pgsql/data
Environment=PGLOG=/database/pgsql/data/pgstartup.log
:wq!
Reload daemon, and restart the postgresql.service.
sudo systemctl daemon-reload
sudo systemctl restart postgresql.service
sudo systemctl status postgresql.service -l
Configuring Postgres Database and a Postgres User for SonarQube
Create a database named sonarqubedb and a user named sonaruser for SonarQube server.
su – postgresuser
psql
create database sonarqubedb;
create user sonarqubeuser with encrypted password ‘ReferToPasswordManager’;
Grant all privileges on database sonarqubedb to sonarqubeuser.
grant all privileges on database sonarqubedb to sonarqubeuser;
Downloading and Installing SonarQube server
/var/lib/ is the installation directory for sonarqube server. So cd in to /var/lib/
cd /var/lib
Obtain the latest version from https://www.sonarqube.org/downloads/
sudo wget https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-8.3.1.34397.zip
Unzip and move contents to the sonarqube directory
sudo unzip sonarqube-8.3.1.34397.zip
sudo mv sonarqube-8.3.1.34397/* sonarqube
Remove the default unzip directory and the downloaded .zip file source
sudo rm -r sonarqube-8.3.1.34397
sudo rm -r sonarqube-8.3.1.34397.zip
Create sonarqube data directory for elastic search
sudo mkdir /database/sonarqube
sudo mkdir /database/sonarqube/data
sudo mkdir /database/sonarqube/temp
sudo mkdir /database/sonarqube/logs
Creating SonarQube server identity
Create a user named sonaruser and change ownership of the sonarqube data directory to the sonaruser.
sudo useradd sonaruser
sudo passwd sonaruser
sudo chown -R sonaruser:sonaruser /database/sonarqube /var/lib/sonarqube
Configuring SonarQube server
Edit sonarqube configuration file present at /var/lib/sonarqube/conf/sonar.properties for postgresql database and webserver settings. Webserver needs to run on port 8080.
sudo vi /var/lib/sonarqube/conf/sonar.properties
And Change the below properties. The properties are spread across the file from the top till the bottom of the file.
sonar.jdbc.username=sonarqubeuser
sonar.jdbc.password=ReferToPasswordManager
sonar.jdbc.url=jdbc:postgresql://localhost/sonarqubedb
sonar.web.port=8080
sonar.path.logs=/database/sonarqube/logs
sonar.path.data=/database/sonarqube/data
sonar.path.temp=/database/sonarqube/temp
:wq!
Change System level memory and file system limits for SonarQube Server
SonarQube server has prescribed minimum memory and file system limits as described in the prerequisites – https://docs.sonarqube.org/latest/requirements/requirements/.
sysctl -w vm.max_map_count=262144
sysctl -w fs.file-max=65536
ulimit -n 65536
ulimit -u 4096
Review the current system prerequisites using the commands below and adjust the limits accordingly.
sysctl vm.max_map_count
sysctl fs.file-max
ulimit -n
ulimit -u
Adjust the below values only in the minimum configuration is not met by the current operating system.
sudo touch /etc/sysctl.d/99-sonarqube.conf
sudo vi /etc/sysctl.d/99-sonarqube.conf
vm.max_map_count=262144
fs.file-max=35536
:wq!
sudo touch /etc/security/limits.d/99-sonarqube.conf
sudo vi /etc/security/limits.d/99-sonarqube.conf
sonaruser – nofile 65536
sonaruser – nproc 4096
:wq!
After the system limit changes, a reboot is required for settings to take effect. So, reboot
sudo reboot
Creating systemd files to run ServerQube as a background service
Create a file named sonarqube.service under /etc/systemd/system with the contents as described below:
sudo vi /etc/systemd/system/sonarqube.service
[Unit]
Description=SonarQube service
After=syslog.target network.target
[Service]
Type=simple
User=sonaruser
Group=sonaruser
PermissionsStartOnly=true
ExecStart=/bin/nohup java -Xms32m -Xmx32m -Djava.net.preferIPv4Stack=true -jar /var/lib/sonarqube/lib/sonar-application-8.3.1.34397.jar
StandardOutput=syslog
LimitNOFILE=65536
LimitNPROC=4096
TimeoutStartSec=5
Restart=always
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
Please note the ExecStart parameter takes the .jar file with the version number as argument. During upgrades, this systemd unit file needs it’s ExecStart parameter to be updated accordingly with the more recent version of sonarqube.
The above service file is the most suitable way to run sonarqube. However for testing purposes, sometimes if sonarqube needed to be run with the /var/lib/sonarqube/bin/linux-x86-64/sonar.sh file, then the below parameter inside the /var/lib/sonarqube/bin/linux-x86-64/sonar.sh file needs to be changed as sonaruser.
#RUN_AS_USER=sonaruser
|
Enable the service file on start, start the service, and check status to see if sonarqube is up and running.
sudo systemctl enable sonarqube.service
sudo systemctl start sonarqube.service
sudo systemctl status sonarqube.service -l
Verify if sonar is running on localhost:8080 by wget.
sudo wget http://localhost:8080
Running a SonarQube Scan with Sonar Runner
Review the Sonar Runner options at https://docs.sonarqube.org/latest/analysis/overview/
Use sonar-scanner as referenced at https://docs.sonarqube.org/latest/analysis/scan/sonarscanner/
Git cloning required respositories
sudo mkdir -p /database/bitbucket/fantasytravel/
cd /database/bitbucket/fantasytravel/
sudo git clone all or the desired repositories repositories
Installing and Configuring node.js to run analysis via sonar-scanner
Since node.js is not a part of the Amazon Extras, review the installation instructions at https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html
Get the latest version of node.js from
https://github.com/nvm-sh/nvm/blob/master/README.md
sudo wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
. ~/.nvm/nvm.sh
nvm install node
Verify node.js installation by checking the version.
node -e "console.log(‘Running Node.js ‘ + process.version)"
Enable node.js for sudoers by creating a symbolic link at /usr/bin
whereis node
node: /home/do.maran/.nvm/versions/node/v14.2.0/bin/node
sudo ln -s /home/do.maran/.nvm/versions/node/v14.2.0/bin/node /usr/bin/node
Reboot to apply changes
sudo reboot
Installing and Configuring sonar-scanner
Get the latest version of sonar-scanner from https://docs.sonarqube.org/latest/analysis/scan/sonarscanner/
cd /database
sudo unzip sonar-scanner-cli-4.3.0.2102-linux.zip
sudo mv sonar-scanner-4.3.0.2102-linux sonar-scanner
Point sonar-scanner to the running url of sonarqube server. The sonar.host.url needs to be reachable from the sonar-scanner location. So, sonar-scanner can not only be run from the same EC2 instance, but also from anywhere in side the VLAN, VPN where the sonarqube server – https://sonarqube.fantasynetwork.com/ is accessible.
sudo vi /database/sonar-scanner/conf/sonar-scanner.properties
sonar.host.url=http://localhost:8080
:wq!
Make sure sonar-scanner is working by invoking its help.
/database/sonar-scanner/bin/sonar-scanner -h
Remove the downloaded .zip file
sudo rm -fR /database/sonar-scanner-cli-4.3.0.2102-linux.zip
Running source code analysis
From the root directory of a source code, sonar-runner needs to be invoked to run a source code analysis.
sonar-runner requires basic properties such as project key – which is unique in a sonarqube server.
The basic scan properties can be a part of the sonar-project.properties file in the root directory of a source code, or the basic scan properties can also be passed on to sonar-scanner via the command line.
The below command is an example of running sonar-runner for a source code located at avacado-api-syncer.
cd /database/bitbucket/fantasytravel/avacado-api-syncer
/database/sonar-scanner/bin/sonar-scanner -Dsonar.projectKey=avacado-api-syncer-8D2is5jQ -Dsonar.projectName=avacado-api-syncer -Dsonar.login=sonar-runner -Dsonar.password=ReferToPasswordManager >> /database/run-sonar-scanner-for-all-projects.log
sudo if permissions are denied to create the log file named /database/run-sonar-scanner-for-all-projects.log.
sudo bash /database/run-sonar-scanner-for-all-projects.sh
Review the log files at:
cat /database/run-sonar-scanner-for-all-projects.log
Once the analysis is complete, review the results at
https://sonarqube.fantasynetwork.com/
Updating SonarQube Server
Review the recent version at https://www.sonarqube.org/downloads/
Follow the aforementioned steps, but from an update perspective.
After the update, ensure the sonaruser and the file permissions on the required directories are set appropriately as outlined.
Administering SonarQube Server
Creating and Managing users
By default, the administrator credentials are admin/admin. This has been changed, refer to the password manager for updated credentials for the default admin user.
sonar-runner is a user created for the sonar-scanner cli, which also is required to be an administrator.
Refer to the password manager for the credentials.
https://sonarqube.fantasynetwork.com/
Administration è Security èUsers
Enforcing Authentication
By default SonarQube web ui has authentication turned off, which means anyone hitting the url https://sonarqube.fantasynetwork.com/ would be able to view the scan results and many other information.
Disable anonymous authentication by enforcing authentication.
https://sonarqube.fantasynetwork.com/
Administration è Configuration è General settings è Security è Force user authentication.
Troubleshooting
Review systemd start up logs by the status command.
sudo systemctl status postgresql.service -l
sudo systemctl status sonarqube.service -l
Review postgresql logs at /database/pgsql/data/log
Review sonarqube logs at /database/sonarqube/logs
SonarQube has an elasticsearch subsystem for indexing, the logs are stored with the name es.log
SonarQube has an inbuild java based web server, the logs for the web server are stored at web.log
SonarQube has a compute engine, the logs are stored at ce.log
SonarQube access logs are stored at access.log
Success!