rules/AzureDevOps.Pipelines.Core.Rule.ps1
|
# PSRule rule definitions for Azure DevOps Pipelines definitions # Synopsis: Pipelines should use YAML definitions Rule 'Azure.DevOps.Pipelines.Core.UseYamlDefinition' ` -Ref 'ADO-PL-001' ` -Type 'Azure.DevOps.Pipeline' ` -Tag @{ release = 'GA'} ` -Level Warning { # Description "Pipelines should use YAML definitions instead of classic editor" Reason "The pipeline is using a classic editor definition" Recommend "Consider using YAML definitions for your pipelines" # Links "https://learn.microsoft.com/en-us/azure/devops/organizations/security/security-best-practices?view=azure-devops#definitions" AllOf { $Assert.HasField($TargetObject, "configuration.type", $true) $Assert.HasFieldValue($TargetObject, "configuration.type", "yaml") } } # Synopsis: Pipelines should not have inherited permissions Rule 'Azure.DevOps.Pipelines.Core.InheritedPermissions' ` -Ref 'ADO-PL-002' ` -Type 'Azure.DevOps.Pipeline' ` -If { (Get-AzDevOpsEffectivePermissionAssessment -Evidence $TargetObject.PermissionEvidence -Policy $Configuration.effectivePermissionPolicies.'Azure.DevOps.Pipeline').Outcome -ne 'Unknown' } ` -Tag @{ release = 'GA'; assessment = 'EffectivePermissions'; legacyName = 'InheritedPermissions' } ` -Level Warning { $assessment = Get-AzDevOpsEffectivePermissionAssessment -Evidence $TargetObject.PermissionEvidence -Policy $Configuration.effectivePermissionPolicies.'Azure.DevOps.Pipeline' Reason $assessment.ReasonCode Recommend 'Restrict unauthorized effective privileges or update the approved permission policy.' $Assert.HasFieldValue($assessment, 'Outcome', 'Pass') } # Synopsis: GUI Designer pipelines should not have secrets as plain text variables Rule 'Azure.DevOps.Pipelines.Core.NoPlainTextSecrets' ` -Ref 'ADO-PL-003' ` -Type 'Azure.DevOps.Pipeline' ` -If { $TargetObject.configuration.type -eq 'designerJson' } ` -Tag @{ release = 'GA'} ` -Level Warning { # Description "GUI Designer pipelines should not have secrets as plain text variables" Reason "The pipeline is using a secret as a plain text variable" Recommend "Consider using a secret variable for your pipeline" # Links "https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#secret-variables" AllOf { # Iterate over all properties of the variables object $TargetObject.configuration.designerJson.variables.psobject.Properties | ForEach-Object { # Check if the variable matches the regex patterns if ($null -ne $_.Value.value) { AllOf { $Assert.HasFieldValue($_, "Value.value") # $Assert.NotMatch($_, "value", "^(?=\D*\d)(?=[^a-z]*[a-z])(?=[^A-Z]*[A-Z])(?=(\w*\W|\w*))[0-9\Wa-zA-Z]{7,20}$") $Assert.NotMatch($_, "Value.value", "((P|p)assword|pwd)\s*=\s*\w+;?").Reason('A configuration value matches a plaintext credential pattern.') # SQL conn strings $Assert.NotMatch($_, "Value.value", "(?# To match SQL/MySQL conn strings.)^Data Source=[^;]+;Initial Catalog=[^;]+;User ID=[^;]+;Password=[^;]+;$").Reason('A configuration value matches a plaintext credential pattern.') $Assert.NotMatch($_, "Value.value", "(?# To match SQL/MySQL conn strings.)((U|u)ser(Id)?|uid)\s*=\s*\w+;?").Reason('A configuration value matches a plaintext credential pattern.') # Azure storage keys, connection strings, and SAS $Assert.NotMatch($_, "Value.value", "(?# To match Azure storage keys.)^[A-Za-z0-9/+]{86}==$").Reason('A configuration value matches a plaintext credential pattern.') $Assert.NotMatch($_, "Value.value", "(?# To match Azure storage connection strings.)DefaultEndpointsProtocol=https;AccountName=\w+;AccountKey=[A-Za-z0-9/+]{86}==$").Reason('A configuration value matches a plaintext credential pattern.') $Assert.NotMatch($_, "Value.value", "(?# To match storage SAS.)([^?]*\?sv=)[^&]+(&s[a-z]=[^&]+){4}").Reason('A configuration value matches a plaintext credential pattern.') # Azure DevOps PATs $Assert.NotMatch($_, "Value.value", "(?# To match ADO PATs.)^[A-Za-z0-9]{75}AZDO[A-Za-z0-9]{5}$").Reason('A configuration value matches a plaintext credential pattern.') # Azure Event Hub connection strings $Assert.NotMatch($_, "Value.value", "(?# To match Azure Event Hub connection strings.)Endpoint=sb://[^/]+\.servicebus\.windows\.net/;SharedAccessKeyName=[^;]+;SharedAccessKey=[A-Za-z0-9+/=]{44}==$").Reason('A configuration value matches a plaintext credential pattern.') # Azure Service Bus connection strings $Assert.NotMatch($_, "Value.value", "(?# To match Azure Service Bus connection strings.)Endpoint=sb://[^/]+\.servicebus\.windows\.net/;SharedAccessKeyName=[^;]+;SharedAccessKey=[A-Za-z0-9+/=]{44}==$").Reason('A configuration value matches a plaintext credential pattern.') # Azure Service Bus SAS $Assert.NotMatch($_, "Value.value", "(?# To match Azure Service Bus SAS.)((S|s)hared(A|a)ccess(S|s)ignature|sas)\s*=\s*\w+;?").Reason('A configuration value matches a plaintext credential pattern.') # Azure OpenAI API keys $Assert.NotMatch($_, "Value.value", "(?# To match Azure OpenAI API keys.)^sk-[a-zA-Z0-9]{32}$").Reason('A configuration value matches a plaintext credential pattern.') # AWS access keys $Assert.NotMatch($_, "Value.value", "(?# To match AWS access keys.)^AKIA[0-9A-Z]{16}$").Reason('A configuration value matches a plaintext credential pattern.') # MongoDB connection strings $Assert.NotMatch($_, "Value.value", "(?# To match MongoDB connection strings.)mongodb://[^:]+:[^@]+@[^/]+/[^?]+(\?[^&]+(&[^&]+)*)?$").Reason('A configuration value matches a plaintext credential pattern.') # MongoDB SRV connection strings $Assert.NotMatch($_, "Value.value", "(?# To match MongoDB SRV connection strings.)mongodb\+srv://[^:]+:[^@]+@[^/]+/[^?]+(\?[^&]+(&[^&]+)*)?$").Reason('A configuration value matches a plaintext credential pattern.') # Redis connection strings $Assert.NotMatch($_, "Value.value", "(?# To match Redis connection strings with password.)^redis://:[^@]+@[^:]+:\d+/?$").Reason('A configuration value matches a plaintext credential pattern.') # Redis SSL connection strings $Assert.NotMatch($_, "Value.value", "(?# To match Redis SSL connection strings.)^rediss://[^:]+:[^@]+@[^:]+:\d+/?$").Reason('A configuration value matches a plaintext credential pattern.') # MySQL connection strings $Assert.NotMatch($_, "Value.value", "(?# To match MySQL connection strings.)^Server=[^;]+;Port=\d+;Database=[^;]+;Uid=[^;]+;Pwd=[^;]+;$").Reason('A configuration value matches a plaintext credential pattern.') # PostgreSQL connection strings $Assert.NotMatch($_, "Value.value", "(?# To match PostgreSQL connection strings.)^Server=[^;]+;Port=\d+;Database=[^;]+;User Id=[^;]+;Password=[^;]+;$").Reason('A configuration value matches a plaintext credential pattern.') # PEM files $Assert.NotMatch($_, "Value.value", "(?# To match PEM files.)^-----BEGIN [A-Z ]+-----\r?\n([A-Za-z0-9+/=]{64}\r?\n)*-----END [A-Z ]+-----\r?\n?$").Reason('A configuration value matches a plaintext credential pattern.') } } else { $Assert.Null($_, "Value.value") } } } } # Synposis: Pipelines should not have direct permissions for Project Valid Users Rule 'Azure.DevOps.Pipelines.Core.ProjectValidUsers' ` -Ref 'ADO-PL-004' ` -Type 'Azure.DevOps.Pipeline' ` -If { $null -ne $TargetObject.Acls } ` -Tag @{ release = 'GA' } ` -Level Warning { # Description 'Pipelines should not have direct permissions for Project Valid Users.' Reason 'The pipeline has direct permissions for Project Valid Users.' Recommend 'Do not grant direct permissions for Project Valid Users for the pipeline.' AllOf { # Loop through all the propeties of the first ACL $TargetObject.Acls.acesDictionary.psobject.Properties.GetEnumerator() | ForEach-Object { # Assert the property name does not end with -0-0-0-0-3 wich is the Project Valid Users group SID AnyOf { $Assert.NotMatch($_.Value, "descriptor", "-0-0-0-0-3") $Assert.HasFieldValue($_.Value, "allow", 1) } } } } |