Framework/Core/SVT/Services/ServiceFabric.ps1
Set-StrictMode -Version Latest class ServiceFabric : AzSVTBase { hidden [PSObject] $ResourceObject; hidden [string] $ClusterTagValue; hidden [PSObject] $ApplicationList; hidden [string] $DefaultTagName = "clusterName" hidden [string] $CertStoreLocation = "CurrentUser" hidden [string] $CertStoreName = "My" hidden [boolean] $IsSDKAvailable = $false ServiceFabric([string] $subscriptionId, [SVTResource] $svtResource): Base($subscriptionId, $svtResource) { $this.GetResourceObject(); } hidden [PSObject] GetResourceObject() { if (-not $this.ResourceObject) { $this.ResourceObject = Get-AzResource -ResourceGroupName $this.ResourceContext.ResourceGroupName -ResourceType $this.ResourceContext.ResourceType -Name $this.ResourceContext.ResourceName $this.ResourceObject.Tags.GetEnumerator() | Where-Object { $_.Key -eq $this.DefaultTagName } | ForEach-Object {$this.ClusterTagValue = $_.Value } # Check if Service Fabric SDK is installed try { $scanSource = [RemoteReportHelper]::GetScanSource(); if($scanSource -eq [ScanSource]::SpotCheck -and (Get-Command Connect-ServiceFabricCluster -ErrorAction SilentlyContinue)){ $this.IsSDKAvailable = $true }else { $this.IsSDKAvailable = $false } }catch { # No need to break execution # All controls which requires SDK to be present in user machine will be treated as manual controls $this.IsSDKAvailable = $false } if(-not $this.ResourceObject) { throw ([SuppressedException]::new(("Resource '{0}' not found under Resource Group '{1}'" -f ($this.ResourceContext.ResourceName), ($this.ResourceContext.ResourceGroupName)), [SuppressedExceptionType]::InvalidOperation)) } } return $this.ResourceObject; } [ControlItem[]] ApplyServiceFilters([ControlItem[]] $controls) { $result = @(); $VMType = "Windows" #Check VM type if([Helpers]::CheckMember($this.ResourceObject.Properties,"vmImage")){ $VMType = $this.ResourceObject.Properties.vmImage } if($VMType -eq "Linux") { $result += $controls | Where-Object { $_.Tags -contains "Linux" }; } else { $result += $controls | Where-Object { $_.Tags -contains "Windows" };; } return $result; } hidden [ControlResult] CheckSecurityMode([ControlResult] $controlResult) { $isCertificateEnabled = [Helpers]::CheckMember($this.ResourceObject.Properties,"certificate" ) #Validate if primary certificate is enabled on cluster. Presence of certificate property value indicates, security mode is turned on. if($isCertificateEnabled) { $controlResult.AddMessage([VerificationResult]::Passed,"Service Fabric cluster is secured with certificate", $this.ResourceObject.Properties.certificate); } else { $controlResult.AddMessage([VerificationResult]::Failed,"Service Fabric cluster is not secured with certificate"); } return $controlResult; } hidden [ControlResult] CheckClusterCertificateSSL([ControlResult] $controlResult) { $managementEndpointUri = $this.ResourceObject.Properties.managementEndpoint $managementEndpointUriScheme = ([System.Uri]$managementEndpointUri).Scheme #Validate if cluster management endpoint url is SSL enabled if($managementEndpointUriScheme -eq "https") { #Hit web request to management endpoint uri and validate certificate trust level $request = [System.Net.HttpWebRequest]::Create($managementEndpointUri) try { $request.GetResponse().Dispose() $controlResult.AddMessage([VerificationResult]::Passed,"Service Fabric cluster is protected with CA signed certificate"); } catch [System.Net.WebException] { #Trust failure indicates self-signed certificate or domain mismatch certificate present on endpoint if ($_.Exception.Status -eq [System.Net.WebExceptionStatus]::TrustFailure) { $controlResult.AddMessage([VerificationResult]::Verify,"Validate if self-signed certificate is not used for cluster management endpoint protection",$this.ResourceObject.Properties.managementEndpoint); $controlResult.SetStateData("Management endpoint", $this.ResourceObject.Properties.managementEndpoint); } elseif($_.Exception.Message.Contains('403')) { $controlResult.AddMessage([VerificationResult]::Passed,"Service Fabric cluster is protected with CA signed certificate"); } else { $controlResult.AddMessage([VerificationResult]::Manual,"Unable to Validate certificate details. Please verify manually that self-signed certificate is not used for cluster management endpoint protection",$this.ResourceObject.Properties.managementEndpoint); $controlResult.AddMessage($_.Exception.Message); } } } else { $controlResult.AddMessage([VerificationResult]::Failed,"Service Fabric cluster is not protected by SSL") } return $controlResult; } hidden [ControlResult] CheckAADClientAuthentication([ControlResult] $controlResult) { $isAADEnabled = [Helpers]::CheckMember($this.ResourceObject.Properties,"azureActiveDirectory") #Presence of 'AzureActiveDirectory' indicates, AAD authentication is enabled for client authentication if($isAADEnabled) { $controlResult.AddMessage([VerificationResult]::Passed,"AAD is enabled for client authentication",$this.ResourceObject.Properties.azureActiveDirectory ) } else { $controlResult.AddMessage([VerificationResult]::Failed,"AAD is not enabled for client authentication") } return $controlResult } hidden [ControlResult] CheckClusterProtectionLevel([ControlResult] $controlResult) { $fabricSecuritySettings = $this.ResourceObject.Properties.fabricSettings | Where-Object {$_.Name -eq "Security"} #Absence of security settings indicates, secure mode is not enabled on cluster. if($null -ne $fabricSecuritySettings) { $clusterProtectionLevel = $fabricSecuritySettings.parameters | Where-Object { $_.name -eq "ClusterProtectionLevel"} if($null -ne $clusterProtectionLevel -and $clusterProtectionLevel.value -eq "EncryptAndSign") { $controlResult.AddMessage([VerificationResult]::Passed,"Cluster security is ON with 'EncryptAndSign' protection level",$clusterProtectionLevel); } else { $controlResult.AddMessage([VerificationResult]::Failed,"Cluster security is not set with 'EncryptAndSign' protection level. Current protection level is :", $clusterProtectionLevel); $controlResult.SetStateData("Cluster protection level", $clusterProtectionLevel); } } else { $controlResult.AddMessage([VerificationResult]::Failed,"Cluster security is OFF"); } return $controlResult } hidden [ControlResult[]] CheckNSGConfigurations([ControlResult] $controlResult) { $isVerify = $true; $nsgEnabledVNet = @{}; $nsgDisabledVNet = @{}; $virtualNetworkResources = $this.GetLinkedResources("Microsoft.Network/virtualNetworks") if($virtualNetworkResources -ne $null) { #Iterate through all cluster linked VNet resources $virtualNetworkResources |ForEach-Object{ $virtualNetwork=Get-AzVirtualNetwork -ResourceGroupName $_.ResourceGroupName -Name $_.Name $subnetConfig = Get-AzVirtualNetworkSubnetConfig -VirtualNetwork $virtualNetwork #Iterate through Subnet and validate if NSG is configured or not $subnetConfig | ForEach-Object{ $subnetName =$_.Name $isCompliant = ($null -ne $_.NetworkSecurityGroup) #If NSG is enabled on Subnet display all security rules applied if($isCompliant) { $nsgResource = Get-AzResource -ResourceId $_.NetworkSecurityGroup.Id $nsgResourceDetails = Get-AzNetworkSecurityGroup -ResourceGroupName $nsgResource.ResourceGroupName -Name $nsgResource.Name $nsgEnabledVNet.Add($subnetName, $nsgResourceDetails) } #If NSG is not enabled on Subnet, fail the TCP with Subnet details else { $nsgDisabledVNet.Add($subnetName, $_) $isVerify = $false } } } if($nsgEnabledVNet.Keys.Count -gt 0) { $nsgEnabledVNet.Keys | Foreach-Object { $controlResult.AddMessage("Validate NSG security rules applied on subnet '$_'",$nsgEnabledVNet[$_]); } } if($nsgDisabledVNet.Keys.Count -gt 0) { $nsgDisabledVNet.Keys | Foreach-Object { $controlResult.AddMessage("NSG is not configured on subnet '$_'",$nsgDisabledVNet[$_]); } } if($isVerify) { $controlResult.VerificationResult = [VerificationResult]::Verify; } else { $controlResult.VerificationResult = [VerificationResult]::Failed; } $NSGState = New-Object -TypeName PSObject $NSGState | Add-Member -NotePropertyName NSGConfiguredSubnet -NotePropertyValue $nsgEnabledVNet $NSGState | Add-Member -NotePropertyName NSGNotConfiguredSubnet -NotePropertyValue $nsgDisabledVNet $controlResult.SetStateData("NSG security rules applied on subnet", $NSGState); }else{ $controlResult.AddMessage("Not able to fetch details of VNet resources linked with cluster."); $controlResult.AddMessage("Manually verify that NSG is enabled on Subnet."); $controlResult.VerificationResult = [VerificationResult]::Manual; } return $controlResult } hidden [ControlResult[]] CheckVmssDiagnostics([ControlResult] $controlResult) { $isPassed = $true; $diagnosticsEnabledScaleSet = @{}; $diagnosticsDisabledScaleSet = @{}; $vmssResources = $this.GetLinkedResources("Microsoft.Compute/virtualMachineScaleSets") #Iterate through cluster linked vmss resources $vmssResources | ForEach-Object{ $VMScaleSetName = $_.Name $nodeTypeResource = Get-AzVMss -ResourceGroupName $_.ResourceGroupName -VMScaleSetName $VMScaleSetName # Fetch diagnostics settings based on OS if($this.ResourceObject.Properties.vmImage -eq "Linux") { $diagnosticsSettings = $nodeTypeResource.VirtualMachineProfile.ExtensionProfile.Extensions | ? { $_.Type -eq "LinuxDiagnostic" -and $_.Publisher -eq "Microsoft.OSTCExtensions" } } else { $diagnosticsSettings = $nodeTypeResource.VirtualMachineProfile.ExtensionProfile.Extensions | ? { $_.Type -eq "IaaSDiagnostics" -and $_.Publisher -eq "Microsoft.Azure.Diagnostics" } } #Validate if diagnostics is enabled on vmss if($null -ne $diagnosticsSettings ) { $diagnosticsEnabledScaleSet.Add($VMScaleSetName, $diagnosticsSettings) } else { $isPassed = $false; $diagnosticsDisabledScaleSet.Add($VMScaleSetName, $diagnosticsSettings) } } if($diagnosticsEnabledScaleSet.Keys.Count -gt 0) { $diagnosticsEnabledScaleSet.Keys | Foreach-Object { $controlResult.AddMessage("Diagnostics is enabled on Vmss '$_'",$diagnosticsEnabledScaleSet[$_]); } } if($diagnosticsDisabledScaleSet.Keys.Count -gt 0) { $diagnosticsDisabledScaleSet.Keys | Foreach-Object { $controlResult.AddMessage("Diagnostics is disabled on Vmss '$_'",$diagnosticsDisabledScaleSet[$_]); } } if($isPassed) { $controlResult.VerificationResult = [VerificationResult]::Passed; } else { $controlResult.VerificationResult = [VerificationResult]::Failed; $controlResult.SetStateData("Diagnostics is disabled on Vmss", $diagnosticsDisabledScaleSet); } return $controlResult } hidden [ControlResult[]] CheckReverseProxyPort([ControlResult] $controlResult) { # add attestation details $isPassed = $true; $reverseProxyEnabledNode = @{}; $reverseProxyDisabledNode = @(); $reverseProxyExposedNode = @{}; $nodeTypes= $this.ResourceObject.Properties.nodeTypes #Iterate through each node $nodeTypes | ForEach-Object{ if([Helpers]::CheckMember($_,"reverseProxyEndpointPort")) { $reverseProxyEnabledNode.Add($_.name, $_.reverseProxyEndpointPort) }else{ $reverseProxyDisabledNode += $_.name } } # if reverse proxy is not enabled in any node, pass TCP if(($reverseProxyEnabledNode | Measure-Object).Count -gt 0) { $lbWithBackendPorts = @{} $loadBalancerResources = $this.GetLinkedResources("Microsoft.Network/loadBalancers") #Collect all open ports on load balancer $loadBalancerResources | ForEach-Object{ $loadBalancerBackendPorts = @() $loadBalancerResource = Get-AzLoadBalancer -Name $_.Name -ResourceGroupName $_.ResourceGroupName $loadBalancingRules = @($loadBalancerResource.FrontendIpConfigurations | ? { $null -ne $_.PublicIpAddress } | ForEach-Object { $_.LoadBalancingRules }) $loadBalancingRules | ForEach-Object { $loadBalancingRuleId = $_.Id; $loadBalancingRule = $loadBalancerResource.LoadBalancingRules | ? { $_.Id -eq $loadBalancingRuleId } | Select-Object -First 1 $loadBalancerBackendPorts += $loadBalancingRule.BackendPort; }; if($loadBalancerBackendPorts.Count -gt 0) { if([Helpers]::CheckMember($loadBalancerResource.BackendAddressPools,"BackendIpConfigurations") -and ($loadBalancerResource.BackendAddressPools.BackendIpConfigurations | Measure-Object).Count -gt 0){ $backEndIpConfiguration = $loadBalancerResource.BackendAddressPools.BackendIpConfigurations | Select -First 1 $pattern = "providers/Microsoft.Compute/virtualMachineScaleSets/(.*?)/" $result = [regex]::match($backEndIpConfiguration.Id, $pattern) if($result.Success){ $nodeName = $result.Groups[1].Value $lbWithBackendPorts.Add($nodeName, $loadBalancerBackendPorts) } } } } #If no ports open, Pass the TCP if($lbWithBackendPorts.Count -eq 0) { $controlResult.AddMessage("No ports enabled in load balancer.") $controlResultList += $controlResult } #If Ports are open for public in load balancer, check if any reverse proxy port is exposed else { $reverseProxyEnabledNode.Keys | Foreach-Object { $loadBalancerBackendPorts = @() $nodeName = $_ $lbWithBackendPorts.Keys | ForEach-Object{ if($_ -eq $nodeName){ $loadBalancerBackendPorts = $lbWithBackendPorts[$_] } } if($loadBalancerBackendPorts.Count -gt 0 -and $loadBalancerBackendPorts.Contains( [Int32] $reverseProxyEnabledNode[$_])) { $isPassed = $false; $controlResult.AddMessage("Reverse proxy port is publicly exposed for node '$_'"); $reverseProxyExposedNode.Add($_, $reverseProxyEnabledNode[$_]) }else{ $controlResult.AddMessage("Reverse proxy port is not publicly exposed for node '$_'.") } } } }else{ $controlResult.AddMessage("Reverse proxy service is not enabled in cluster.") } if($isPassed) { $controlResult.VerificationResult = [VerificationResult]::Passed; } else { $controlResult.VerificationResult = [VerificationResult]::Failed; $controlResult.SetStateData("Reverse proxy port is publicly exposed", $reverseProxyExposedNode); } return $controlResult } hidden [ControlResult] CheckClusterUpgradeMode([ControlResult] $controlResult) { if([Helpers]::CheckMember($this.ResourceObject.Properties,"upgradeMode") -and $this.ResourceObject.Properties.upgradeMode -eq "Automatic") { $controlResult.AddMessage([VerificationResult]::Passed,"Upgrade mode for cluster is set to automatic." ) } else { $controlResult.AddMessage([VerificationResult]::Failed,"Upgrade mode for cluster is set to manual.") } return $controlResult } hidden [ControlResult[]] CheckStatefulServiceReplicaSetSize([ControlResult] $controlResult) { $isConnectionSuccessful = $false if($this.IsSDKAvailable -eq $true) { #Function to validate authentication and connect with Service Fabric cluster $sfCluster = $null $uri = ([System.Uri]$this.ResourceObject.Properties.managementEndpoint).Host $primaryNodeType = $this.ResourceObject.Properties.nodeTypes | Where-Object { $_.isPrimary -eq $true } $ClusterConnectionUri = $uri +":"+ $primaryNodeType.clientConnectionEndpointPort $isClusterSecure = [Helpers]::CheckMember($this.ResourceObject.Properties,"certificate" ) if($isClusterSecure) { $serviceFabricCertificate = $this.ResourceObject.Properties.certificate $CertThumbprint= $this.ResourceObject.Properties.certificate.thumbprint $serviceFabricAAD = $null if([Helpers]::CheckMember($this.ResourceObject.Properties,"azureActiveDirectory" )) { $serviceFabricAAD =$this.ResourceObject.Properties.azureActiveDirectory } if($null -ne $serviceFabricAAD) { try { $this.PublishCustomMessage("Connecting Service Fabric using AAD...") $sfCluster = Connect-ServiceFabricCluster -ConnectionEndpoint $ClusterConnectionUri -AzureActiveDirectory -ServerCertThumbprint $CertThumbprint #-SecurityToken " $isConnectionSuccessful = $true $this.PublishCustomMessage("Connection using AAD is successful.") } catch { $this.PublishCustomMessage("You may not have permission to connect with cluster", [MessageType]::Warning); } } else { $this.PublishCustomMessage("Validating if cluster certificate present on machine...") $IsCertPresent = (Get-ChildItem -Path "Cert:\$($this.CertStoreLocation)\$($this.CertStoreName)" | Where-Object {$_.Thumbprint -eq $CertThumbprint }| Measure-Object).Count if($IsCertPresent) { try { $this.PublishCustomMessage("Connecting Service Fabric using certificate") $sfCluster = Connect-serviceFabricCluster -ConnectionEndpoint $ClusterConnectionUri -KeepAliveIntervalInSec 10 -X509Credential -ServerCertThumbprint $CertThumbprint -FindType FindByThumbprint -FindValue $CertThumbprint -StoreLocation $this.CertStoreLocation -StoreName $this.CertStoreName $isConnectionSuccessful = $true }catch { $this.PublishCustomMessage("Cannot connect with Service Fabric cluster using cluster certificate. Verify that valid cluster certificate is present in 'CurrentUser' location.", [MessageType]::Warning); } } else { $this.PublishCustomMessage("Cannot connect with Service Fabric cluster due to unavailability of cluster certificate in local machine. Validate cluster certificate is present in 'CurrentUser' location.", [MessageType]::Warning); } } } else { $sfCluster = Connect-serviceFabricCluster -ConnectionEndpoint $ClusterConnectionUri $isConnectionSuccessful = $true $this.PublishCustomMessage("Service Fabric connection is successful"); } try { $this.ApplicationList = Get-ServiceFabricApplication -ErrorAction SilentlyContinue }catch { # No need to break execution, handled in next condition } $isPassed = $true; $complianteServices = @{}; $nonComplianteServices = @{}; #Iterate through the applications present in cluster if($isConnectionSuccessful -eq $false) { $controlResult.AddMessage([VerificationResult]::Manual,"Cannot connect with Service Fabric cluster.") $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; }elseif($this.ApplicationList) { $this.ApplicationList | ForEach-Object{ $serviceFabricApplication = $_ Get-ServiceFabricService -ApplicationName $serviceFabricApplication.ApplicationName | ForEach-Object{ $serviceName = $_.ServiceName $serviceDescription = Get-ServiceFabricServiceDescription -ServiceName $_.ServiceName #Filter application with Stateful service type if($serviceDescription.ServiceKind -eq "Stateful") { #Validate minimum replica and target replica size for each service $isCompliant = !($serviceDescription.MinReplicaSetSize -lt 3 -or $serviceDescription.TargetReplicaSetSize -lt 3) $stateObject = "" | Select-Object "MinReplicaSetSize" ,"TargetReplicaSetSize" $stateObject.MinReplicaSetSize = $serviceDescription.MinReplicaSetSize $stateObject.TargetReplicaSetSize = $serviceDescription.TargetReplicaSetSize if($isCompliant) { $complianteServices.Add($serviceName, $stateObject) } else { $isPassed = $False $nonComplianteServices.Add($serviceName, $stateObject) } } } } if($complianteServices.Keys.Count -gt 0) { $controlResult.AddMessage("Replica set size for below services are complaint"); $complianteServices.Keys | Foreach-Object { $controlResult.AddMessage("Replica set size details for service '$_'",$complianteServices[$_]); } } if($nonComplianteServices.Keys.Count -gt 0) { $controlResult.AddMessage("Replica set size for below services are non-complaint"); $nonComplianteServices.Keys | Foreach-Object { $controlResult.AddMessage("Replica set size details for service '$_'",$nonComplianteServices[$_]); } } if($isPassed) { $controlResult.VerificationResult = [VerificationResult]::Passed; } else { $controlResult.VerificationResult = [VerificationResult]::Failed; $controlResult.SetStateData("Replica set size are non-complaint for", $nonComplianteServices); } } else { $controlResult.AddMessage([VerificationResult]::Passed,"No stateful service found.") } }else{ $scanSource = [RemoteReportHelper]::GetScanSource(); if($scanSource -eq [ScanSource]::SpotCheck) { $controlResult.AddMessage("Service Fabric SDK is not present in user machine. To evaluate this control SDK should be available on user machine.") } $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; $controlResult.VerificationResult = [VerificationResult]::Manual; } return $controlResult; } hidden [ControlResult[]] CheckStatelessServiceInstanceCount([ControlResult] $controlResult) { $isConnectionSuccessful = $false if($this.IsSDKAvailable -eq $true) { #Function to validate authentication and connect with Service Fabric cluster $sfCluster = $null $uri = ([System.Uri]$this.ResourceObject.Properties.managementEndpoint).Host $primaryNodeType = $this.ResourceObject.Properties.nodeTypes | Where-Object { $_.isPrimary -eq $true } $ClusterConnectionUri = $uri +":"+ $primaryNodeType.clientConnectionEndpointPort $isClusterSecure = [Helpers]::CheckMember($this.ResourceObject.Properties,"certificate" ) if($isClusterSecure) { $serviceFabricCertificate = $this.ResourceObject.Properties.certificate $CertThumbprint= $this.ResourceObject.Properties.certificate.thumbprint $serviceFabricAAD = $null if([Helpers]::CheckMember($this.ResourceObject.Properties,"azureActiveDirectory" )) { $serviceFabricAAD =$this.ResourceObject.Properties.azureActiveDirectory } if($null -ne $serviceFabricAAD) { try { $this.PublishCustomMessage("Connecting Service Fabric using AAD...") $sfCluster = Connect-ServiceFabricCluster -ConnectionEndpoint $ClusterConnectionUri -AzureActiveDirectory -ServerCertThumbprint $CertThumbprint #-SecurityToken " $isConnectionSuccessful = $true $this.PublishCustomMessage("Connection using AAD is successful.") } catch { $this.PublishCustomMessage("You may not have permission to connect with cluster", [MessageType]::Warning); } } else { $this.PublishCustomMessage("Validating if cluster certificate present on machine...") $IsCertPresent = (Get-ChildItem -Path "Cert:\$($this.CertStoreLocation)\$($this.CertStoreName)" | Where-Object {$_.Thumbprint -eq $CertThumbprint }| Measure-Object).Count if($IsCertPresent) { try { $this.PublishCustomMessage("Connecting Service Fabric using certificate") $sfCluster = Connect-serviceFabricCluster -ConnectionEndpoint $ClusterConnectionUri -KeepAliveIntervalInSec 10 -X509Credential -ServerCertThumbprint $CertThumbprint -FindType FindByThumbprint -FindValue $CertThumbprint -StoreLocation $this.CertStoreLocation -StoreName $this.CertStoreName $isConnectionSuccessful = $true }catch { $this.PublishCustomMessage("Cannot connect with Service Fabric cluster using cluster certificate. Verify that valid cluster certificate is present in 'CurrentUser' location.", [MessageType]::Warning); } } else { $this.PublishCustomMessage("Cannot connect with Service Fabric cluster due to unavailability of cluster certificate in local machine. Validate cluster certificate is present in 'CurrentUser' location.", [MessageType]::Warning); } } } else { $sfCluster = Connect-serviceFabricCluster -ConnectionEndpoint $ClusterConnectionUri $isConnectionSuccessful = $true $this.PublishCustomMessage("Service Fabric connection is successful"); } try { $this.ApplicationList = Get-ServiceFabricApplication -ErrorAction SilentlyContinue }catch { # No need to break execution, handled in next condition } $isPassed = $true; $complianteServices = @{}; $nonComplianteServices = @{}; if($isConnectionSuccessful -eq $false) { $controlResult.AddMessage([VerificationResult]::Manual,"Cannot connect with Service Fabric cluster.") $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; }elseif($this.ApplicationList) { #Iterate through the applications present in cluster $this.ApplicationList | ForEach-Object{ $serviceFabricApplication = $_ Get-ServiceFabricService -ApplicationName $serviceFabricApplication.ApplicationName | ForEach-Object{ $serviceName = $_.ServiceName $serviceDescription = Get-ServiceFabricServiceDescription -ServiceName $serviceName #Filter application with Stateless service type if($serviceDescription.ServiceKind -eq "Stateless") { #Validate instancecount it -1 (auto) or greater than equal to 3 $isCompliant = ($serviceDescription.InstanceCount -eq -1 -or $serviceDescription.InstanceCount -ge 3) if($isCompliant) { $complianteServices.Add($serviceName, $serviceDescription.InstanceCount) } else { $isPassed = $False $nonComplianteServices.Add($serviceName, $serviceDescription.InstanceCount) } } } } if($complianteServices.Keys.Count -gt 0) { $controlResult.AddMessage("Instance count for below services are complaint"); $complianteServices.Keys | Foreach-Object { $controlResult.AddMessage("Instance count details for service '$_'",$complianteServices[$_]); } } if($nonComplianteServices.Keys.Count -gt 0) { $controlResult.AddMessage("Instance count for below services are non-complaint"); $nonComplianteServices.Keys | Foreach-Object { $controlResult.AddMessage("Instance count details for service '$_'",$nonComplianteServices[$_]); } } if($isPassed) { $controlResult.VerificationResult = [VerificationResult]::Passed; } else { $controlResult.VerificationResult = [VerificationResult]::Failed; $controlResult.SetStateData("Instance count are non-complaint for", $nonComplianteServices); } } else { $controlResult.AddMessage([VerificationResult]::Passed,"No stateless service found.") } }else{ $scanSource = [RemoteReportHelper]::GetScanSource(); if($scanSource -eq [ScanSource]::SpotCheck) { $controlResult.AddMessage("Service Fabric SDK is not present in user machine. To evaluate this control SDK should be available on user machine.") } $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; $controlResult.VerificationResult = [VerificationResult]::Manual; } return $controlResult; } hidden [ControlResult[]] CheckPublicEndpointSSL([ControlResult] $controlResult) { $isConnectionSuccessful = $false if($this.IsSDKAvailable -eq $true) { #Function to validate authentication and connect with Service Fabric cluster $sfCluster = $null $uri = ([System.Uri]$this.ResourceObject.Properties.managementEndpoint).Host $primaryNodeType = $this.ResourceObject.Properties.nodeTypes | Where-Object { $_.isPrimary -eq $true } $ClusterConnectionUri = $uri +":"+ $primaryNodeType.clientConnectionEndpointPort $isClusterSecure = [Helpers]::CheckMember($this.ResourceObject.Properties,"certificate" ) if($isClusterSecure) { $serviceFabricCertificate = $this.ResourceObject.Properties.certificate $CertThumbprint= $this.ResourceObject.Properties.certificate.thumbprint $serviceFabricAAD = $null if([Helpers]::CheckMember($this.ResourceObject.Properties,"azureActiveDirectory" )) { $serviceFabricAAD =$this.ResourceObject.Properties.azureActiveDirectory } if($null -ne $serviceFabricAAD) { try { $this.PublishCustomMessage("Connecting Service Fabric using AAD...") $sfCluster = Connect-ServiceFabricCluster -ConnectionEndpoint $ClusterConnectionUri -AzureActiveDirectory -ServerCertThumbprint $CertThumbprint #-SecurityToken " $isConnectionSuccessful = $true $this.PublishCustomMessage("Connection using AAD is successful.") } catch { $this.PublishCustomMessage("You may not have permission to connect with cluster", [MessageType]::Warning); } } else { $this.PublishCustomMessage("Validating if cluster certificate present on machine...") $IsCertPresent = (Get-ChildItem -Path "Cert:\$($this.CertStoreLocation)\$($this.CertStoreName)" | Where-Object {$_.Thumbprint -eq $CertThumbprint }| Measure-Object).Count if($IsCertPresent) { try { $this.PublishCustomMessage("Connecting Service Fabric using certificate") $sfCluster = Connect-serviceFabricCluster -ConnectionEndpoint $ClusterConnectionUri -KeepAliveIntervalInSec 10 -X509Credential -ServerCertThumbprint $CertThumbprint -FindType FindByThumbprint -FindValue $CertThumbprint -StoreLocation $this.CertStoreLocation -StoreName $this.CertStoreName $isConnectionSuccessful = $true }catch { $this.PublishCustomMessage("Cannot connect with Service Fabric cluster using cluster certificate. Verify that valid cluster certificate is present in 'CurrentUser' location.", [MessageType]::Warning); } } else { $this.PublishCustomMessage("Cannot connect with Service Fabric cluster due to unavailability of cluster certificate in local machine. Validate cluster certificate is present in 'CurrentUser' location.", [MessageType]::Warning); } } } else { $sfCluster = Connect-serviceFabricCluster -ConnectionEndpoint $ClusterConnectionUri $isConnectionSuccessful = $true $this.PublishCustomMessage("Service Fabric connection is successful"); } try { $this.ApplicationList = Get-ServiceFabricApplication -ErrorAction SilentlyContinue }catch { #No need to break execution, handled in next condition } $isManual = $false; $isPassed = $true; $compliantPort = @{}; $nonCompliantPort = @{}; $loadBalancerBackendPorts = @() $loadBalancerResources = $this.GetLinkedResources("Microsoft.Network/loadBalancers") #Collect all open ports on load balancer $loadBalancerResources | ForEach-Object{ $loadBalancerResource = Get-AzLoadBalancer -Name $_.Name -ResourceGroupName $_.ResourceGroupName $loadBalancingRules = @($loadBalancerResource.FrontendIpConfigurations | ? { $null -ne $_.PublicIpAddress } | ForEach-Object { $_.LoadBalancingRules }) $loadBalancingRules | ForEach-Object { $loadBalancingRuleId = $_.Id; $loadBalancingRule = $loadBalancerResource.LoadBalancingRules | ? { $_.Id -eq $loadBalancingRuleId } | Select-Object -First 1 $loadBalancerBackendPorts += $loadBalancingRule.BackendPort; }; } #If no ports open, Pass the TCP if($loadBalancerBackendPorts.Count -eq 0) { $controlResult.AddMessage("No ports enabled.") } #If Ports are open for public in load balancer, map load balancer ports with application endpoint ports and validate if SSL is enabled. else { if($isConnectionSuccessful -eq $false) { $isManual = $true; $controlResult.AddMessage("Cannot connect with Service Fabric cluster.") $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; } elseif($this.ApplicationList) { $controlResult.AddMessage("List of publicly exposed port",$loadBalancerBackendPorts) $this.ApplicationList | ForEach-Object{ $serviceFabricApplication = $_ Get-ServiceFabricServiceType -ApplicationTypeName $serviceFabricApplication.ApplicationTypeName -ApplicationTypeVersion $serviceFabricApplication.ApplicationTypeVersion | ForEach-Object{ $currentService = $_ $serviceManifest = [xml](Get-ServiceFabricServiceManifest -ApplicationTypeName $serviceFabricApplication.ApplicationTypeName -ApplicationTypeVersion $serviceFabricApplication.ApplicationTypeVersion -ServiceManifestName $_.ServiceManifestName) if([Helpers]::CheckMember($serviceManifest.ServiceManifest,"Resources.Endpoints")) { $serviceManifest.ServiceManifest.Resources.Endpoints.ChildNodes | ForEach-Object{ $endpoint = $_ $serviceTypeName = $currentService.ServiceTypeName if(-not [Helpers]::CheckMember($endpoint,"Port")) { #Add message #$childControlResult.AddMessage([VerificationResult]::Passed) } else { if($loadBalancerBackendPorts.Contains([Int32] $endpoint.Port) ) { if([Helpers]::CheckMember($endpoint,"Protocol") -and $endpoint.Protocol -eq "https"){ $compliantPort.Add($serviceFabricApplication.ApplicationName.OriginalString + "/" + $serviceTypeName + "/"+$endpoint.Name, $endpoint.Port) } elseif([Helpers]::CheckMember($endpoint,"Protocol") -and $endpoint.Protocol -eq "http"){ $isPassed = $false; $nonCompliantPort.Add($serviceFabricApplication.ApplicationName.OriginalString + "/" + $serviceTypeName + "/"+$endpoint.Name, $endpoint.Port) } else { $isPassed = $false; $nonCompliantPort.Add($serviceFabricApplication.ApplicationName.OriginalString + "/" + $serviceTypeName + "/"+$endpoint.Name, $endpoint.Port) } } else { $compliantPort.Add($serviceFabricApplication.ApplicationName.OriginalString + "/" + $serviceTypeName + "/"+$endpoint.Name, $endpoint.Port) } } } } } } } else { $controlResult.AddMessage("No service found.") } } if($compliantPort.Keys.Count -gt 0) { $controlResult.AddMessage("Following endpoint(s) are compliant"); $compliantPort.Keys | Foreach-Object { $controlResult.AddMessage("Endpoint: '$_' Port: $($compliantPort[$_])"); } } if($nonCompliantPort.Keys.Count -gt 0) { $controlResult.AddMessage("Following publicly exposed endpoint(s) are not secured using SSL"); $nonCompliantPort.Keys | Foreach-Object { $controlResult.AddMessage("EndPoint: '$_' Port: $($nonCompliantPort[$_])"); } } if($isManual) { $controlResult.VerificationResult = [VerificationResult]::Manual; } elseif($isPassed) { $controlResult.VerificationResult = [VerificationResult]::Passed; } else { $controlResult.VerificationResult = [VerificationResult]::Failed; $controlResult.SetStateData("Following ports are non-complaint", $nonCompliantPort); } }else{ $scanSource = [RemoteReportHelper]::GetScanSource(); if($scanSource -eq [ScanSource]::SpotCheck) { $controlResult.AddMessage("Service Fabric SDK is not present in user machine. To evaluate this control SDK should be available on user machine.") } $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; $controlResult.VerificationResult = [VerificationResult]::Manual; } return $controlResult } [void] CheckClusterAccess() { #Function to validate authentication and connect with Service Fabric cluster $sfCluster = $null $uri = ([System.Uri]$this.ResourceObject.Properties.managementEndpoint).Host $primaryNodeType = $this.ResourceObject.Properties.nodeTypes | Where-Object { $_.isPrimary -eq $true } $ClusterConnectionUri = $uri +":"+ $primaryNodeType.clientConnectionEndpointPort $this.PublishCustomMessage("Connecting with Service Fabric cluster...") $this.PublishCustomMessage("Validating if Service Fabric is secure...") $isClusterSecure = [Helpers]::CheckMember($this.ResourceObject.Properties,"certificate" ) if($isClusterSecure) { $serviceFabricCertificate = $this.ResourceObject.Properties.certificate $this.PublishCustomMessage("Service Fabric is secure") $CertThumbprint= $this.ResourceObject.Properties.certificate.thumbprint $serviceFabricAAD = $null if([Helpers]::CheckMember($this.ResourceObject.Properties,"azureActiveDirectory" )) { $serviceFabricAAD =$this.ResourceObject.Properties.azureActiveDirectory } if($null -ne $serviceFabricAAD) { try { $this.PublishCustomMessage("Connecting Service Fabric using AAD...") $sfCluster = Connect-ServiceFabricCluster -ConnectionEndpoint $ClusterConnectionUri -AzureActiveDirectory -ServerCertThumbprint $CertThumbprint #-SecurityToken " $this.PublishCustomMessage("Connection using AAD is successful.") } catch { throw ([SuppressedException]::new(("You may not have permission to connect with cluster"), [SuppressedExceptionType]::InvalidOperation)) } } else { $this.PublishCustomMessage("Validating if cluster certificate present on machine...") $IsCertPresent = (Get-ChildItem -Path "Cert:\$($this.CertStoreLocation)\$($this.CertStoreName)" | Where-Object {$_.Thumbprint -eq $CertThumbprint }| Measure-Object).Count if($IsCertPresent) { $this.PublishCustomMessage("Connecting Service Fabric using certificate") $sfCluster = Connect-serviceFabricCluster -ConnectionEndpoint $ClusterConnectionUri -KeepAliveIntervalInSec 300 -X509Credential -ServerCertThumbprint $CertThumbprint -FindType FindByThumbprint -FindValue $CertThumbprint -StoreLocation $this.CertStoreLocation -StoreName $this.CertStoreName } else { throw ([SuppressedException]::new(("Cannot connect with Service Fabric due to unavailability of cluster certificate in local machine. Validate cluster certificate is present in 'CurrentUser' location."), [SuppressedExceptionType]::InvalidOperation)) } } } else { $this.PublishCustomMessage("Service Fabric is unsecure"); $sfCluster = Connect-serviceFabricCluster -ConnectionEndpoint $ClusterConnectionUri $this.PublishCustomMessage("Service Fabric connection is successful"); } } [PSObject] GetLinkedResources([string] $resourceType) { return Get-AzResource -TagName $this.DefaultTagName -TagValue $this.ClusterTagValue | Where-Object { ($_.ResourceType -EQ $resourceType) -and ($_.ResourceGroupName -eq $this.ResourceContext.ResourceGroupName) } } } |