diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..18735a5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,200 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+x64/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+
+# Roslyn cache directories
+*.ide/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+#NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding addin-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+## TODO: Comment the next line if you want to checkin your
+## web deploy settings but do note that will include unencrypted
+## passwords
+*.pubxml
+*profile.arm.json
+
+# NuGet Packages Directory
+packages/*
+## TODO: If the tool you use requires repositories.config
+## uncomment the next line
+#!packages/repositories.config
+
+# Enable "build/" folder in the NuGet Packages folder since
+# NuGet packages use it for MSBuild targets.
+# This line needs to be after the ignore of the build folder
+# (and the packages folder if the line above has been uncommented)
+!packages/build/
+
+# Windows Azure Build Output
+csx/
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+sql/
+*.Cache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# LightSwitch generated files
+GeneratedArtifacts/
+_Pvt_Extensions/
+ModelManifest.xml
+
+# Added by OneITVSO
+*.dll
+*.exe
+/.vs/VSWorkspaceState.json
+/.vs/slnx.sqlite
+
+
+.vs/
+**/packages/*
diff --git a/AzTS_Extended/.gitignore b/AzTS_Extended/.gitignore
new file mode 100644
index 0000000..f5cacdf
--- /dev/null
+++ b/AzTS_Extended/.gitignore
@@ -0,0 +1,197 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+x64/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+
+# Roslyn cache directories
+*.ide/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+#NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding addin-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+## TODO: Comment the next line if you want to checkin your
+## web deploy settings but do note that will include unencrypted
+## passwords
+*.pubxml
+*profile.arm.json
+
+# NuGet Packages Directory
+packages/*
+## TODO: If the tool you use requires repositories.config
+## uncomment the next line
+#!packages/repositories.config
+
+# Enable "build/" folder in the NuGet Packages folder since
+# NuGet packages use it for MSBuild targets.
+# This line needs to be after the ignore of the build folder
+# (and the packages folder if the line above has been uncommented)
+!packages/build/
+
+# Windows Azure Build Output
+csx/
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+sql/
+*.Cache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# LightSwitch generated files
+GeneratedArtifacts/
+_Pvt_Extensions/
+ModelManifest.xml
+
+# Added by OneITVSO
+*.dll
+*.exe
+/.vs/VSWorkspaceState.json
+/.vs/slnx.sqlite
+
diff --git a/AzTS_Extended/AzTS_Extended.csproj b/AzTS_Extended/AzTS_Extended.csproj
new file mode 100644
index 0000000..a197206
--- /dev/null
+++ b/AzTS_Extended/AzTS_Extended.csproj
@@ -0,0 +1,39 @@
+
+
+ Library
+ true
+ Start-AzSKTSScan
+ ./nupkg
+ netcoreapp3.1
+ v3
+ AnyCPU;x64
+
+
+
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+ Never
+
+
+ Always
+
+
+
diff --git a/AzTS_Extended/AzTS_Extended.sln b/AzTS_Extended/AzTS_Extended.sln
new file mode 100644
index 0000000..43d5acd
--- /dev/null
+++ b/AzTS_Extended/AzTS_Extended.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31624.102
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzTS_Extended", "AzTS_Extended.csproj", "{E5C68E6E-D6AE-4748-9C25-D101E5341630}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E5C68E6E-D6AE-4748-9C25-D101E5341630}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E5C68E6E-D6AE-4748-9C25-D101E5341630}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E5C68E6E-D6AE-4748-9C25-D101E5341630}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E5C68E6E-D6AE-4748-9C25-D101E5341630}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {861BB5CF-3C37-4336-8888-4371C14738BB}
+ EndGlobalSection
+EndGlobal
diff --git a/AzTS_Extended/Configurations/appsettings.json b/AzTS_Extended/Configurations/appsettings.json
new file mode 100644
index 0000000..8004d2d
--- /dev/null
+++ b/AzTS_Extended/Configurations/appsettings.json
@@ -0,0 +1,165 @@
+{
+ "AppMetadata": {
+ "AppCurrentVersion": "1.0.0"
+ },
+ "AuthNSettings": {
+ "ScannerIdentityConnectionString": "",
+ "InternalIdentityConnectionString": ""
+ },
+ "AuthzSettings": {
+ "IsPIMEnabled": false,
+ "IsRGPIMEnabled": false,
+ "MaxDegreeOfParallelismForSubPIMProcess": 50,
+ "MaxDegreeOfParallelismForRGPIMProcess": 4
+ },
+ "AzureStorageSettings": {
+ "ResourceId": "",
+ "ContainerName": "azskatsscanresult",
+ "QueueName": "subjobqueue",
+ "FileNames": {
+ "SubscriptionMetadata": "{0}/Subscriptions/SubMetaData/{1}_SubMetaData.json",
+ "SubscriptionRBAC": "{0}/Subscriptions/RBAC/{1}_RBAC.json",
+ "ResourceInventory": "{0}/Subscriptions/ResourceInventory/{1}_ResourceInventory.json",
+ "PolicyAssessments": "{0}/Subscriptions/SSAssessmentDetails/{1}_SSAssessmentDetails.json",
+ "ControlEvaluation": "{0}/Subscriptions/ControlResults/{1}_ControlResults.json",
+ "ControlExtensions": "Ext/{0}.ext.json",
+ "PolicyStates": "{0}/Subscriptions/PolicyStateResults/{1}_PolicyStateResults.json"
+ },
+ "FeatureContainerName": {
+ "PolicyExtension": "orgpolicy"
+ }
+ },
+ "EndpointMapping": {
+ "AzureCloud": {
+ "AzureManagement": "https://management.azure.com/",
+ "GraphAPI": "https://graph.microsoft.com",
+ "AzureADGraphAPI": "https://graph.windows.net",
+ "LogAnalytics": "https://api.loganalytics.io/",
+ "LogAnalyticsDataCollector": "https://{0}.ods.opinsights.azure.com",
+ "ApplicationInsights": "https://management.azure.com",
+ "ManagementCore": "https://management.core.windows.net/",
+ "StorageEndpointSuffix": "core.windows.net",
+ "AzureSQL": "https://database.windows.net/",
+ "KuduConsole": "https://{0}.scm.azurewebsites.net"
+ },
+ "AzureGovernmentCloud": {
+ "AzureManagement": "https://management.usgovcloudapi.net/",
+ "GraphAPI": "https://graph.microsoft.us",
+ "AzureADGraphAPI": "https://graph.windows.net",
+ "LogAnalytics": "https://api.loganalytics.us/",
+ "LogAnalyticsDataCollector": "https://{0}.ods.opinsights.azure.us",
+ "ApplicationInsights": "https://management.usgovcloudapi.net/",
+ "ManagementCore": "https://management.core.usgovcloudapi.net/",
+ "StorageEndpointSuffix": "core.usgovcloudapi.net",
+ "AzureSQL": "https://database.usgovcloudapi.net/",
+ "KuduConsole": "https://{0}.scm.azurewebsites.us/"
+ }
+ },
+ "HttpClientConfig": {
+ "MaxRetries": 3,
+ "RetryHttpStatusCodes": [ 429, 408, 502, 504 ],
+ "RetryStepInSecs": 6
+ },
+ "LAConfigurations": {
+ "WorkspaceId": "",
+ "ResourceId": "",
+ "LATypes": {
+ "PolicySummary": "AzSK_PolicySummaryInfo",
+ "SubscriptionRBAC": "AzSK_RBAC",
+ "ADGraph": "AzSK_AADObjectInv",
+ "PolicyAssignments": "AzSK_PolicyAssignmentsInv",
+ "PolicyAssessments": "AzSK_SSAssessmentInv",
+ "PolicyStates": "AzSK_PolicyStateResults",
+ "ControlEvaluation": "AzSK_ControlResults",
+ "ProcessedEvent": "AzSK_ProcessedSubscriptions",
+ "Exceptions": "AzSK_RTExceptions",
+ "PerformanceMetrics": "AzSK_PerformanceMetrics",
+ "ResourceInventory": "AzSK_ResourceInvInfo",
+ "SecureScoreAssessmentSummary": "AzSK_SSAssessmentSummaryInv"
+ },
+ "QueryFiles": {
+ },
+ "Functions": {
+
+ },
+ "BatchSize": 500
+ },
+ "ARGConfigurations": {
+ "QueryFiles": {
+ "PolicyStateDataForAllAssignments": "Arg_Policy_GetPolicyStatesForAllAssignments.json",
+ "PolicyStateDataForSelectedAssignments": "Arg_Policy_GetPolicyStatesForSelectedAssignments.json",
+ "GetASCSecureScore": "Arg_Policy_GetSecureScore.json",
+ "GetASCSecureScoreControls": "Arg_Policy_GetSecureScoreControls.json"
+ },
+ "PolicyStateSettings": {
+ "MaxDegreeOfParallelism": 10,
+ "BatchSize": 100,
+ "FetchAll": false
+ },
+ "SecureScoreSettings": {
+ "MaxDegreeOfParallelism": 5,
+ "BatchSize": 1000,
+ "FetchAll": true
+ },
+ "MaxRetries": 3,
+ "QuotaResetsInSecs": 5,
+ "RetryStatusCodes": [ "TooManyRequests" ]
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Trace",
+ "System.Net.Http": "None"
+ }
+ },
+ "WebJobConfigurations": {
+ "ForceFetch": false,
+ "MaxExecutionTimeInUTCHour": 14,
+ "CloudEnvironmentName": "AzureCloud"
+ },
+ "FeatureManagement": {
+ "All": true,
+ "ManagementGroups": true,
+ "ADGraph": true,
+ "Subscriptions": true,
+ "PolicyDefinitions": true,
+ "PolicyStates": false,
+ "BaselineControls": true,
+ "ControlScanException": true,
+ "OrgPolicy": false,
+ "ScanExternalControl": false,
+ "ControlResultRetention": false
+ },
+ "ManagementGroupConfigurations": {
+ "ManagementGroupId": "",
+ "HierarchyTraverseLimit": 7
+ },
+ "GraphConfigurations": {
+ "BatchSize": 1000,
+ "IsFeatureEnabled": false
+ },
+ "WorkItemProcessorSettings": {
+ "BatchSize": 30000,
+ "MinThreshold": 1,
+ "MessageMaxTimeToLive": "03:00:00",
+ "CacheClearingOn": true,
+ "VerificationResultRetentionPeriodInDays": 7
+ },
+ "RepositorySettings": {
+ "SQLDatabase": {
+ "ResourceEvaluationLimit": 5000
+ },
+ "Storage": {
+ "StorageContainerFetchLimit": 5000
+ },
+ "VirtualMachineScaleSet": {
+ "VirtualMachineInstancesLimit": 50
+ },
+ "LogicApps": {
+ "LogicAppsReturnObjectsLimit": 50000
+ }
+ },
+ "AzureSQLSettings": {
+ "IsFeatureEnabled": false
+ }
+}
\ No newline at end of file
diff --git a/AzTS_Extended/ControlConfigurationExt/FeatureNameExt_Template.json b/AzTS_Extended/ControlConfigurationExt/FeatureNameExt_Template.json
new file mode 100644
index 0000000..7ce9a79
--- /dev/null
+++ b/AzTS_Extended/ControlConfigurationExt/FeatureNameExt_Template.json
@@ -0,0 +1,13 @@
+{
+ "FeatureName": "", // Represents Feature/Service name
+ "Controls": [
+ {
+ "ControlID": "", // Represents human friendly control Id. The format used is Azure___
+ "Id": "", // Represents internal ID and should be unique. Since the ControlID can be modified, this internal ID ensures that we have a unique link to all the control results evaluation.
+ "Automated": "Yes", // Indicates whether the given control is Manual/Automated.
+ "MethodName": "", // Represents the Control method that is responsible to evaluate this control. It should be present inside the feature SVT associated with this control.
+ "DisplayName": "", // Represents human friendly name for the control.
+ "Enabled": false // Defines whether the control is enabled or not.
+ }
+ ]
+}
diff --git a/AzTS_Extended/ControlEvaluator/FeatureNameControlEvaluatorExt_Template.cs b/AzTS_Extended/ControlEvaluator/FeatureNameControlEvaluatorExt_Template.cs
new file mode 100644
index 0000000..a3d4331
--- /dev/null
+++ b/AzTS_Extended/ControlEvaluator/FeatureNameControlEvaluatorExt_Template.cs
@@ -0,0 +1,17 @@
+namespace AzTS_Extended.ControlEvaluator
+{
+ using Microsoft.AzSK.ATS.Extensions.Authorization;
+ using Microsoft.AzSK.ATS.Extensions.Models;
+ using Microsoft.AzSK.ATS.ProcessSubscriptions.Models;
+ using Microsoft.AzSK.ATS.ProcessSubscriptions.Processors.ControlProcessors.ServiceControlEvaluators;
+ using Newtonsoft.Json.Linq;
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ class FeatureNameEvaluatorExt : BaseControlEvaluator
+ {
+
+ }
+}
diff --git a/AzTS_Extended/ControlEvaluator/SubscriptionCoreEvaluatorExt.cs b/AzTS_Extended/ControlEvaluator/SubscriptionCoreEvaluatorExt.cs
new file mode 100644
index 0000000..7a1bb91
--- /dev/null
+++ b/AzTS_Extended/ControlEvaluator/SubscriptionCoreEvaluatorExt.cs
@@ -0,0 +1,63 @@
+namespace AzTS_Extended.ControlEvaluator
+{
+ using Microsoft.AzSK.ATS.Extensions.Authorization;
+ using Microsoft.AzSK.ATS.Extensions.Models;
+ using Microsoft.AzSK.ATS.ProcessSubscriptions.Models;
+ using Microsoft.AzSK.ATS.ProcessSubscriptions.Processors.ControlProcessors.ServiceControlEvaluators;
+ using Newtonsoft.Json.Linq;
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ class SubscriptionCoreEvaluatorExt : SubscriptionCoreEvaluator
+ {
+
+ public ControlResult CheckCoAdminCount(ControlResult cr)
+ {
+
+ // 1. This is where the code logic is placed
+ // 2. ControlResult input to this function, which needs to be updated with the verification Result (Passed/Failed/Verify/Manual/Error) based on the control logic
+ // 3. Messages that you add to ControlResult variable will be displayed in the detailed log automatically.
+
+ // Note the syntax of how to fetch value from Control Settings from the JSON.
+ int noOfClassicAdminsLimit = cr.ControlDetails.ControlSettings?["NoOfClassicAdminsLimit"]?.Value() ?? 2;
+ string classicAdminAccountsString = "No classic admin accounts found.";
+ int classicAdminAccountsCount = 0;
+
+ // NOTE: While fetching RBAC result, we make three API calls - PIM, ARM, Classic. We are *not* handling partial result scenario if error occurred while fetching any of these RBAC result.
+ // If no RBAC is found, mark status as Verify because sufficient data is not available for evaluation.
+ if (this.RBACList?.Any() == false)
+ {
+ cr.VerificationResult = VerificationResultStatus.Verify;
+ cr.StatusReason = "No RBAC result found for this subscription.";
+ cr.ConsiderForCompliance = false;
+ return cr;
+ }
+ else
+ {
+ List classicAdminAccounts = new List();
+ classicAdminAccounts = RBACList.AsParallel().Where(rbacItem => rbacItem.RoleName.ToLower().Contains("coadministrator") || rbacItem.RoleName.ToLower().Contains("serviceadministrator")).ToList();
+
+ // First start with default value, override this if classic admin account is found.
+ if (classicAdminAccounts != null && classicAdminAccounts.Any())
+ {
+ classicAdminAccountsCount = classicAdminAccounts.Count;
+ classicAdminAccountsString = string.Join(",", classicAdminAccounts.Select(a => a.ToStringClassicAssignment()).ToList());
+ }
+
+ // Start with failed state, mark control as Passed if all required conditions are met
+ cr.StatusReason = $"No. of classic administrators found: [{classicAdminAccountsCount}]. Principal name results based on RBAC inv: [{String.Join(", ", classicAdminAccounts.Select(a => a.PrincipalName))}]";
+ cr.VerificationResult = VerificationResultStatus.Failed;
+
+ // Classic admin accounts count does not exceed the limit.
+ if (classicAdminAccountsCount <= noOfClassicAdminsLimit)
+ {
+ cr.VerificationResult = VerificationResultStatus.Passed;
+ }
+ }
+
+ return cr;
+ }
+ }
+}
diff --git a/AzTS_Extended/Processor.cs b/AzTS_Extended/Processor.cs
new file mode 100644
index 0000000..5a5e03c
--- /dev/null
+++ b/AzTS_Extended/Processor.cs
@@ -0,0 +1,42 @@
+namespace AzTS_Extended
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Linq;
+ using Microsoft.AzSK.ATS.Extensions;
+ using Microsoft.AzSK.ATS.Extensions.Models;
+ using Microsoft.AzSK.ATS.ProcessSubscriptions.Models;
+ using Microsoft.AzSK.ATS.ProcessSubscriptions.Processors;
+ using Microsoft.Azure.WebJobs;
+ using Microsoft.Extensions.Logging;
+ using Newtonsoft.Json;
+
+ public class Processor
+ {
+ private SubscriptionItemProcessor _subscriptionItemProcessor;
+
+ public Processor(
+ SubscriptionItemProcessor subscriptionItemProcessor)
+ {
+ _subscriptionItemProcessor = subscriptionItemProcessor;
+ }
+
+ // This function will get triggered/executed when a new message is written
+ // on an Azure Queue called queue.
+ [FunctionName("SubWorkItemProcessor")]
+ //public void Run([QueueTrigger("%queueTriggerName%")] string workItem, ILogger log)
+
+ public void Run([TimerTrigger("0 */60 0-12 * * *", RunOnStartup = true)] TimerInfo timer, ILogger log)
+ {
+ // **Remember**: Before deploying, comment the `line 31` and `line 34` and comment out the `line 29` to make the *Run* function as queue-triggered instead of timer-triggered which is done for local testing purposes.
+ string workItem = "{\"SubscriptionId\":\"\"}";
+ var workItemObject = JsonConvert.DeserializeObject(workItem);
+ workItemObject.IsRBACProcessed = false;
+ _subscriptionItemProcessor._log = log;
+ var controlResults = _subscriptionItemProcessor.ProcessSubscriptionItems(workItemObject);
+
+ }
+ }
+}
diff --git a/AzTS_Extended/Startup.cs b/AzTS_Extended/Startup.cs
new file mode 100644
index 0000000..4d1ab93
--- /dev/null
+++ b/AzTS_Extended/Startup.cs
@@ -0,0 +1,144 @@
+using Microsoft.Azure.Functions.Extensions.DependencyInjection;
+
+[assembly: FunctionsStartup(typeof(AzTS_Extended.Startup))]
+
+namespace AzTS_Extended
+{
+ using System;
+ using System.IO;
+ using System.Net.Http;
+ using Microsoft.AzSK.ATS.EndpointProvider;
+ using Microsoft.AzSK.ATS.Extensions;
+ using Microsoft.AzSK.ATS.Extensions.Authentication;
+ using Microsoft.AzSK.ATS.Extensions.Authorization;
+ using Microsoft.AzSK.ATS.Extensions.ConfigurationProvider;
+ using Microsoft.AzSK.ATS.Extensions.EventProcessor;
+ using Microsoft.AzSK.ATS.Extensions.ExceptionProvider;
+ using Microsoft.AzSK.ATS.Extensions.Graph;
+ using Microsoft.AzSK.ATS.Extensions.HttpHelper;
+ using Microsoft.AzSK.ATS.Extensions.Models;
+ using Microsoft.AzSK.ATS.Extensions.PolicyStateHelper;
+ using Microsoft.AzSK.ATS.Extensions.Storage;
+ using Microsoft.AzSK.ATS.Extensions.SubscriptionHelper;
+ using Microsoft.AzSK.ATS.ProcessSubscriptions.Processors;
+ using Microsoft.AzSK.ATS.ProcessSubscriptions.Repositories;
+ using Microsoft.AzSK.ATS.ProcessSubscriptions.Repositories.ResourceRepositories;
+ using Microsoft.AzSK.ATS.ProcessSubscriptions.Repositories.SubscriptionRespositories;
+ using Microsoft.AzSK.ATS.WorkItemProcessor.Processors.ControlProcessors;
+ using Microsoft.AzSK.ATS.WorkItemProcessor.Repositories;
+ using Microsoft.AzSK.ATS.WorkItemProcessor.Repositories.SubscriptionRespositories;
+ using Microsoft.Extensions.Configuration;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.FeatureManagement;
+ using Polly.Registry;
+
+ class Startup : FunctionsStartup
+ {
+ private static IPolicyRegistry _policyRegistry;
+ private IConfiguration _configuration;
+
+ ///
+ /// Configuration load for function apps.
+ ///
+ /// Function config builder instance.
+ public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
+ {
+ FunctionsHostBuilderContext context = builder.GetContext();
+
+ // Get configurations paths
+ string configurationRootPath = Path.Combine(context.ApplicationRootPath, "Configurations");
+ var temp = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
+
+ if (string.IsNullOrWhiteSpace(configurationRootPath))
+ {
+ throw new ArgumentException("App setting not found");
+ }
+
+ _configuration = builder.ConfigurationBuilder
+ .AddJsonFile(Path.Combine(configurationRootPath, "appsettings.json"), optional: false, reloadOnChange: true)
+ .AddEnvironmentVariables()
+ .Build();
+ }
+
+ ///
+ /// Configure dependencies for application.
+ ///
+ /// Functions host builder instance.
+ public override void Configure(IFunctionsHostBuilder builder)
+ {
+ var services = builder.Services;
+
+ // Add configurations from app settings
+
+ // Enable inbuilt services
+ services.AddLogging();
+ services.AddApplicationInsightsTelemetry();
+ services.AddFeatureManagement(_configuration);
+
+ // Configurations settings
+ // Notes: Configurations read using the options pattern
+ var webJobConfigurations = _configuration.GetSection(WebJobConfigurations.ConfigName).Get();
+ services.Configure(_configuration.GetSection(string.Concat(EndpointMapping.ConfigName, ":", webJobConfigurations.CloudEnvironmentName)));
+ services.Configure(_configuration.GetSection(LAConfigurations.ConfigName));
+ services.Configure(_configuration.GetSection(GraphConfigurations.ConfigName));
+ //// Notes: Named options pattern used for mapping configurations with strongly typed properties/classes
+ services.Configure(_configuration.GetSection(WebJobConfigurations.ConfigName));
+ services.Configure(_configuration.GetSection(AzureStorageSettings.ConfigName));
+ services.Configure(_configuration.GetSection(AuthNSettings.ConfigName));
+ services.Configure(_configuration.GetSection(AuthzSettings.ConfigName));
+ services.Configure(_configuration.GetSection(HttpClientConfig.ConfigName));
+ services.Configure(_configuration.GetSection(AzureSQLSettings.ConfigName));
+ services.Configure(_configuration.GetSection(AzureControlScanExceptionSettings.ConfigName));
+ services.Configure(_configuration.GetSection(AADClientAppDetails.ConfigName));
+ services.Configure(_configuration.GetSection(AppMetadata.ConfigName));
+ services.Configure(_configuration.GetSection(ARGConfigurations.ConfigName));
+ services.Configure(_configuration.GetSection(RepositorySettings.ConfigName));
+ services.Configure(_configuration.GetSection(RuleEngineSettings.ConfigName));
+
+ // Notes: Named options pattern used for mapping configurations with strongly typed properties/classes
+ services.Configure(_configuration.GetSection(WorkItemProcessorSettings.ConfigName));
+
+ // Helper classes registration
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddScoped();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ // Repository classes registration
+ services.AddSingleton();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddSingleton();
+ services.AddScoped();
+ services.AddHttpClient(HttpClientConfig.HttpClientName).SetHandlerLifetime(TimeSpan.FromHours(2));
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/AzTS_Extended/host.json b/AzTS_Extended/host.json
new file mode 100644
index 0000000..d324292
--- /dev/null
+++ b/AzTS_Extended/host.json
@@ -0,0 +1,22 @@
+{
+ "version": "2.0",
+ "extensions": {
+ "queues": {
+ "visibilityTimeout": "01:00:00"
+ }
+ },
+ "logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Information",
+ "System.Net.Http": "None"
+ },
+ "applicationInsights": {
+ //"samplingExcludedTypes": "Request",
+ "samplingSettings": {
+ "isEnabled": false
+ }
+ }
+ },
+ "functionTimeout": "00:09:00"
+}
\ No newline at end of file
diff --git a/AzTS_Extended/local.settings.json b/AzTS_Extended/local.settings.json
new file mode 100644
index 0000000..0291c08
--- /dev/null
+++ b/AzTS_Extended/local.settings.json
@@ -0,0 +1,12 @@
+{
+ "IsEncrypted": false,
+ "Values": {
+ "ASPNETCORE_ENVIRONMENT": "Local",
+ "AzureWebJobsStorage": "UseDevelopmentStorage=true",
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet",
+ "APPINSIGHTS_INSTRUMENTATIONKEY": "",
+ "AzureStorageSettings__ResourceId": "",
+ "LAConfigurations__WorkspaceId": "",
+ "LAConfigurations__ResourceId": ""
+ }
+}
\ No newline at end of file
diff --git a/FeatureCoverage/Feature/AppService.md b/FeatureCoverage/Feature/AppService.md
new file mode 100644
index 0000000..be7e8fb
--- /dev/null
+++ b/FeatureCoverage/Feature/AppService.md
@@ -0,0 +1,81 @@
+# AppService
+
+## Resource Type
+Microsoft.Web/sites
+
+## Reference
+Refer to [this document](https://github.com/azsk/AzTS-docs/blob/main/Control%20coverage/Feature/AppService.md) for meta-data and evaluation logic of the existing controls of AppService service.
+
+Certain Azure Policy or ARM APIs are used for evaluation. You can find below the mapping between the properties fetched from ARM APIs and the property name that can be used in the control evaluation while modifying/creating methods in FeatureNameEvaluatorExt class in the FeatureNameControlEvaluatorExt.cs. The properties fetched from ARM APIs are stored in JObject under the name CustomFields. There can be multiple CustomFields such as CustomFields1, CustomFields2, etc with different types of properties. The below mapping will guide you to write the control methods while extending the controls.
+
+## Properties
+
+**appService.CustomField1:**
+
+| Property Name | API Property | Type |
+|---|---|---|
+|HostNames|properties.hostNames|List|
+|HostNameSslStates|properties.hostNameSslStates|string|
+|AppServicePlan.SkuDescription.Capacity|properties.AppServicePlan.SkuDescription.Capacity|int|
+|AppServicePlan.Name|properties.AppServicePlan.Name|string|
+|HttpsEnabled|properties.httpsOnly|bool|
+|ManagedServiceIdentityType|properties.identity.type|string|
+
+**appService.CustomField2:**
+
+| Property Name | API Property | Type |
+|---|---|---|
+| RemoteDebuggingEnabled | properties.RemoteDebuggingEnabled | bool |
+|WebSocketEnabled |properties.webSocketsEnabled|bool|
+|AlwaysOn|properties.alwaysOn|bool|
+|HttpLoggingEnabled|properties.httpLoggingEnabled|bool|
+|DetailedErrorLoggingEnabled|properties.detailedErrorLoggingEnabled|bool|
+|RequestTracingEnabled|properties.requestTracingEnabled|bool|
+|Cors|properties.cors|CorsSettings Model|
+|MinTLSVersion|properties.minTlsVersion|string|
+|IpSecurityRestrictions|properties.ipSecurityRestrictions|List(IpRule) where IpRule is defined model|
+|ScmIpSecurityRestrictions|properties.scmIpSecurityRestrictions|List(IpRule) where IpRule is defined mode|
+|ScmIpSecurityRestrictionsUseMain|properties.scmIpSecurityRestrictionsUseMain|bool|
+
+**appService.CustomField4:**
+AppSvc.CF4 contains all the deployment slots
+It can be accessed using the following:
+``` CS
+List deploymentSlots = JsonConvert.DeserializeObject>(appService.CustomField4);
+```
+
diff --git a/FeatureCoverage/Feature/Storage.md b/FeatureCoverage/Feature/Storage.md
new file mode 100644
index 0000000..233bb5d
--- /dev/null
+++ b/FeatureCoverage/Feature/Storage.md
@@ -0,0 +1,60 @@
+# Storage
+
+## Resource Type
+Microsoft.Storage/storageAccounts
+
+## Reference
+Refer to [this document](https://github.com/azsk/AzTS-docs/blob/main/Control%20coverage/Feature/Storage.md) for meta-data and evaluation logic of the existing controls of Storage service.
+
+Certain Azure Policy or ARM APIs are used for evaluation. You can find below the mapping between the properties fetched from ARM APIs and the property name that can be used in the control evaluation while modifying/creating methods in FeatureNameEvaluatorExt class in the FeatureNameControlEvaluatorExt.cs. The properties fetched from ARM APIs are stored in JObject under the name CustomFields. There can be multiple CustomFields such as CustomFields1, CustomFields2, etc with different types of properties. The below mapping will guide you to write the control methods while extending the controls.
+
+## Properties
+
+**Storage.CustomField1:**
+
+| Property Name | API Property | Type |
+|---|---|---|
+| HttpsEnabled | properties.supportsHttpsTrafficOnly | bool |
+| Kind |kind|string|
+|ProvisioningState|properties.provisioningState|string|
+|AllowBlobPublicAccess|properties.allowBlobPublicAccess|bool|
+|NetworkRuleSet|properties.networkAcls.defaultAction|string|
+
+## Example
+
+``` CS
+public class StorageControlEvaluator : BaseControlEvaluator
+{
+ public void CheckStorageNetworkAccess(Resource storage, ControlResult cr)
+ {
+ // We first check if CustomField1 is not NULL or empty
+ if (!string.IsNullOrEmpty(storage.CustomField1))
+ {
+
+ cr.VerificationResult = VerificationResultStatus.Failed;
+
+ // CustomField1 has details about which protocol is supported by Storage for traffic
+ // Loading the JObject from a string that contains JSON.
+ var stgDetails = JObject.Parse(storage.CustomField1);
+ // Note how we use the property NetworkRuleSet here from the extracted JObject stgDetails.
+ string strNetworkRuleSet = stgDetails["NetworkRuleSet"].Value();
+
+ if (strNetworkRuleSet.Equals("Deny", StringComparison.OrdinalIgnoreCase))
+ {
+ cr.StatusReason = $"Firewall and Virtual Network restrictions are defined for this storage";
+ cr.VerificationResult = VerificationResultStatus.Passed;
+ }
+ else
+ {
+ cr.StatusReason = $"No Firewall and Virtual Network restrictions are defined for this storage";
+ cr.VerificationResult = VerificationResultStatus.Failed;
+ }
+ }
+
+ // 'Else' block not required since CustomField1 is never expected to be null
+ }
+ .
+ .
+ .
+}
+```
diff --git a/FeatureCoverage/Feature/SubscriptionCore.md b/FeatureCoverage/Feature/SubscriptionCore.md
new file mode 100644
index 0000000..254cebe
--- /dev/null
+++ b/FeatureCoverage/Feature/SubscriptionCore.md
@@ -0,0 +1,66 @@
+# Subscription
+
+## Resource Type
+SubscriptionCore
+
+## Reference
+Refer to [this document](https://github.com/azsk/AzTS-docs/blob/main/Control%20coverage/Feature/SubscriptionCore.md) for meta-data and evaluation logic of the existing controls of Subscription service.
+
+Certain Azure Policy or ARM APIs are used for evaluation. You can find below the mapping between the properties fetched from ARM APIs and the property name that can be used in the control evaluation while modifying/creating methods in FeatureNameEvaluatorExt class in the FeatureNameControlEvaluatorExt.cs. The properties fetched from ARM APIs are stored in JObject under the name CustomFields. There can be multiple CustomFields such as CustomFields1, CustomFields2, etc with different types of properties. The below mapping will guide you to write the control methods while extending the controls.
+
+## Properties
+
+**Subscription.CustomField2:**
+
+| Property Name | API Property | Type |
+|---|---|---|
+| autoProvision | properties.autoProvision | string |
+|pricingTier|properties.pricingTier|string|
+
+**Subscription.CustomField3:**
+
+| Property Name | API Property | Type |
+|---|---|---|
+
+
+**Subscription.CustomField4:**
+
+| Property Name | API Property | Type |
+|---|---|---|
+
+
+## Example
+
+``` CS
+public ControlResult CheckAutoProvisioningForSecurity(ControlResult cr)
+{
+ // We first check if CustomField1 is not NULL or empty
+ if (!string.IsNullOrWhiteSpace(this.Subscription.CustomField2)) //// CF2 contains security center details
+ {
+ // Notice how we deserializes the JSON to the specified model i.e. SecurityCenterModel
+ var securityCenterDetails = JsonConvert.DeserializeObject(this.Subscription.CustomField2);
+
+ if (!securityCenterDetails.IsProviderRegistered)
+ {
+ cr.VerificationResult = VerificationResultStatus.Failed;
+ cr.StatusReason = "Security center provider not registered.";
+ return cr;
+ }
+
+ cr.VerificationResult = VerificationResultStatus.Failed;
+ cr.StatusReason = $"Auto Provisioning setting is disabled for subscription. Provisioning Status:[{securityCenterDetails.AutoProvision}]";
+
+ if (securityCenterDetails.AutoProvision.Equals("on", StringComparison.OrdinalIgnoreCase))
+ {
+ cr.VerificationResult = VerificationResultStatus.Passed;
+ cr.StatusReason = $"Auto Provisioning setting is enabled for subscription.";
+ }
+ }
+
+ return cr;
+}
+ .
+ .
+ .
+}
+```
diff --git a/FeatureCoverage/README.md b/FeatureCoverage/README.md
new file mode 100644
index 0000000..3a32298
--- /dev/null
+++ b/FeatureCoverage/README.md
@@ -0,0 +1,12 @@
+## Security features covered by Azure Tenant Security (AzTS)
+
+This page displays security features that are implemented in AzTS. Features table listed under provide details about properties used within the existing control methods in form of CustomFields and how they can utilized in new custom methods. This is for the purpose to guide users to implement new control for the respective features.
+
+### Azure Services supported by AzTS
+
+|Feature Name|Resource Type|
+|---|---|
+|[AppService](Feature/AppService.md)|Microsoft.Web/sites|
+|[Storage](Feature/Storage.md)|Microsoft.Storage/storageAccounts|
+|[Subscription](Feature/SubscriptionCore.md)|
+
diff --git a/README.md b/README.md
index 978cb9e..c9650ed 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,275 @@
-# AzTS-Samples
\ No newline at end of file
+> The Azure Tenant Security Solution (AzTS) can be used to obtain visibility to cloud subscriptions and resource configuration across multiple subscriptions in an enterprise environment.
+The AzTS is a logical progression of DevOps Kit which helps us move closer to an implementation of cloud security compliance solution using native security capabilities in Azure platform that are available today. Functionally, it is similar to running AzSK Continuous Assurance (CA) in central-scan mode.
+
+You can know more about AzTS [here](https://github.com/azsk/AzTS-docs).
+
+# [Org Policy Customization](README.md#org-policy-customization)
+
+### [Overview](README.md#Overview)
+ - [When and why should I set up org policy?](Readme.md#when-and-why-should-i-setup-org-policy)
+
+### [Setting up org policy](README.md#setting-up-org-policy)
+
+
+### [Modifying and customizing org policy](README.md#modifying-and-customizing-org-policy)
+ - [Structure](Readme.md#structure)
+ - [Know more about controls](Readme.md#know-more-about-controls)
+ - [Steps to extend a control](Readme.md#steps-to-extend-a-control)
+### [Common Application of Org Policy Customization](README.common-application-of-org-policy-customization)
+
+------------------------------------------------------
+
+## [Overview](README.md#Overview)
+
+#### When and why should I setup org policy
+
+When you run any scan from the AzTS, it relies on JSON-based policy files to determine various parameters that affect the behavior of the scan it is about to run. These policy files are downloaded 'on the fly' from a policy server. When you run the public version of the AzTS, the policy files are accessed from a CDN endpoint that is managed by the AzTS team. Thus, whenever you run a scan from a vanilla installation, AzTS accesses the CDN endpoint to get the latest policy configuration and runs the scan using it.
+
+The JSON inside the policy files dictate the behavior of the security scan. This includes things such as:
+ - Which set of controls to evaluate?
+ - What control set to use as a baseline?
+ - What settings/values to use for individual controls?
+ - What messages to display for recommendations? Etc.
+
+Note that the policy files needed for security scans are accessed from the last updated deployed AzTS package.
+
+While the out-of-box files in the package may be good for limited use, in many contexts you may want to "customize" the behavior of the security scans for your environment. You may want to do things such as:
+(a) enable/disable some controls,
+(b) change control settings to better match specific security policies within your org,
+(c) change various messages,
+(d) modify existing control logic
+(e) add additional filter criteria for certain regulatory requirements that teams in your org can leverage, etc.
+(f) add new controls to existing service
+
+When faced with such a need, you need a way to create and manage a dedicated policy endpoint customized to the needs of your environment. The organization policy customization setup feature helps you do that in an automated fashion.
+
+In this document, we will look at how to setup an organization-specific policy endpoint, how to make changes
+to and manage the policy files and how to accomplish various common org-specific policy/behavior customizations
+for the AzTS.
+
+## [Setting up org policy](README.md#setting-up-org-policy)
+
+In this section, we will walk through the steps of setting up organization-specific policy customizable AzTS Scanner locally.
+
+> **Note**: You would require at least 'Reader' level access on Subscription and 'Contributor' level access to the LA Workspace, Storage, etc.
+
+Let's Start!
+
+1. Clone [this](https://github.com/azsk/AzTS-Samples) GitHub repository in a new Visual Studio.
+2. Go to AzTS_Extended folder and load the AzTS_Extended.sln.
+3. Files to update:
+ * In local.settings.json file:
+ ```JSON
+ {
+ "IsEncrypted": false,
+ "Values": {
+ "ASPNETCORE_ENVIRONMENT": "Local",
+ "AzureWebJobsStorage": "UseDevelopmentStorage=true",
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet",
+ "APPINSIGHTS_INSTRUMENTATIONKEY": "",
+ "AzureStorageSettings__ResourceId": "",
+ "LAConfigurations__WorkspaceId": "",
+ "LAConfigurations__ResourceId": ""
+ }
+ }
+ ```
+ 1. Application insights collect telemetry data from connected apps and provides Live Metrics, Log Analytics, etc. It has an instrumentation key which we need to configure into our function app i.e. APPINSIGHTS_INSTRUMENTATIONKEY and with this key app insights grab data from our app. Add instrumentation key for Application Insights by entering "APPINSIGHTS_INSTRUMENTATIONKEY"
+ 2. Storage Account and Log Analytic Workspace are used to store the scan events, inventory, subscription scan progress details and results.
+ 1. Add 'ResourceId' of the Storage Account,
+ 2. Add 'WorkspaceId' and 'ResourceId' of the LA Workspace
+ * Mention the ID of the subscription to be scanned in Processor.cs, (line 33)
+4. Build and Run
+
+This will install the required [NuGet packages](https://www.nuget.org/packages/Microsoft.AzTS.Azure.Scanner/). It will import the dependencies and dynamic linked libraries of the AzTS Scanner to the user's solution along with the following templates:
+
+| Template File | Description
+| ---- | ---- |
+| FeatureNameExt.json
[under the ControlConfigurationExt folder] | This file contains the setting of controls of a specific feature. A few meta-data are required for a control to be scanned which are mentioned in detail further ahead.
+| FeatureNameControlEvaluatorExt.cs
[under the ControlEvaluator folder] | This file is used to override the base control evaluation method.
+
+Next, we will look into how to modify an existing control or add a new control through this setup.
+
+## [Modifying and customizing org policy](README.md#modifying-and-customizing-org-policy)
+
+The typical workflow for all control changes will remain same and will involve the following basic steps:
+1. Make modifications to the existing control metadata (Json files).
+2. Add or Modify control methods in respective control evaluator files.
+3. Build and Run
+
+### [Structure](README.md#structure)
+Before we get started with extending the toolkit, let's understand the structure of the built solution repository.
+
+ ├───AzTS_Extended
+ ├───Connected Services
+ ├───Dependencies
+ ├───Properties
+ ├───ConfigurationProvider
+ │ ├───ControlConfigurations
+ │ └───RoleDefinitionConfigurations
+ ├───Configurations
+ │ ├───LAQueries
+ ├───ControlConfigurationExt
+ ├───ControlEvaluator
+
+
+### [Know more about controls](Readme.md#know-more-about-controls)
+
+All our controls inherit from a base class called BaseControlEvaluator which will take care of all the required plumbing from the control evaluation code. Every control will have a corresponding feature json file under the configurations folder. For example, Storage.cs (in the control evaluator folder) has a corresponding Storage.json file under configurations folder. These controls json have a bunch of configuration parameters, that can be controlled by a policy owner, for instance, you can change the recommendation, modify the description of the control suiting your org, change the severity, etc.
+
+Below is the typical schema for each control inside the feature json
+
+```JSON
+{
+ "ControlID": "Azure_Storage_NetSec_Restrict_Network_Access", //Human friendly control Id. The format used is Azure___
+ "Description": "Ensure that Firewall and Virtual Network access is granted to a minimal set of trusted origins", //Description for the control, which is rendered in all the reports it generates (CSV, AI telemetry, emails etc.).
+ "Id": "AzureStorage260", //This is internal ID and should be unique. Since the ControlID can be modified, this internal ID ensures that we have a unique link to all the control results evaluation.
+ "ControlSeverity": "Medium", //Represents the severity of the Control.
+ "Automated": "Yes", //Indicates whether the given control is Manual/Automated.
+ "MethodName": "CheckStorageNetworkAccess", // Represents the Control method that is responsible to evaluate this control. It should be present inside the feature SVT associated with this control.
+ "DisplayName": "Ensure that Firewall and Virtual Network access is granted to a minimal set of trusted origins", // Represents human friendly name for the control.
+ "Recommendation": "Go to Azure Portal --> your Storage service --> Settings --> Firewalls and virtual networks --> Selected Network. Provide the specific IP address and Virtual Network details that should be allowed to access storage account.", //Recommendation typically provides the precise instructions on how to fix this control.
+ "Tags": [
+ "SDL",
+ "TCP",
+ "Automated",
+ "NetSec",
+ "Baseline"
+ ], // You can decorate your control with different set of tags, that can be used as filters in scan commands.
+ "Enabled": true , //Defines whether the control is enabled or not.
+Â Â Â Â "Rationale": "Restricting access using firewall/virtual network config reduces network exposure of a storage account by limiting access only to expected range/set of clients. Note that this depends on the overall service architecture and may not be possible to implement in all scenarios." //Provides the intent of this control.
+}
+```
+
+After Schema of the control json, let us look at the corresponding feature
+
+``` CS
+public class StorageControlEvaluator : BaseControlEvaluator
+{
+ public void CheckStorageNetworkAccess(Resource storage, ControlResult cr)
+ {
+ // 1. This is where the code logic is placed
+ // 2. ControlResult input to this function, which needs to be updated with the verification Result (Passed/Failed/Verify/Manual/Error) based on the control logic
+ // 3. Messages that you add to ControlResult variable will be displayed in the detailed log automatically.
+
+ if (!string.IsNullOrEmpty(storage.CustomField1))
+ {
+ // Start with failed state, mark control as Passed if all required conditions are met
+ cr.VerificationResult = VerificationResultStatus.Failed;
+ cr.ScanSource = ScanResourceType.Reader.ToString();
+
+ // CustomField1 has details about which protocol is supported by Storage for traffic
+ var stgDetails = JObject.Parse(storage.CustomField1);
+ string strNetworkRuleSet = stgDetails["NetworkRuleSet"].Value();
+
+ if (strNetworkRuleSet.Equals("Deny", StringComparison.OrdinalIgnoreCase))
+ {
+ // Firewall and Virtual Network restrictions are defined for this storage
+ cr.StatusReason = $"Firewall and Virtual Network restrictions are defined for this storage";
+ cr.VerificationResult = VerificationResultStatus.Passed;
+ }
+ else
+ {
+ // No Firewall and Virtual Network restrictions are defined for this storage
+ cr.StatusReason = $"No Firewall and Virtual Network restrictions are defined for this storage";
+ cr.VerificationResult = VerificationResultStatus.Failed;
+ }
+ }
+
+ // 'Else' block not required since CustomField1 is never expected to be null
+ }
+ .
+ .
+ .
+}
+```
+
+
+### [Steps to extend a control](Readme.md#steps-to-extend-a-control)
+1. [**Control JSON:**](README.md#control-json)
+ 1. Copy _FeatureNameExt.json_ file and rename it accordingly. For example: StorageExt.json
+ 2. Fill the parameters according to the feature. For example:
+ ``` JSON
+ {
+ "FeatureName": "Storage",
+ "Reference": "aka.ms/azsktcp/storage", // you can find this from the FeatureName.json as well
+ "IsMaintenanceMode": false,
+ }
+ ```
+ 3. Add the control json with all parameters given in template. The following meta-data are required for a control to be scanned:
+ ``` JSON
+ "Controls": [
+ {
+ "ControlID": "",
+ "Id": "",
+ "Automated": "Yes",
+ "MethodName": "",
+ "DisplayName": "",
+ "Enabled": false
+ }
+ ]
+ ```
+ 1. For **Id** above:
+ * If it is an existing control that you wish to modify, then use the same ID as used previously.
+ * If it is a new control, then follow a convention of a FeatureName followed by a *four* digit ID number. For example, "Storage1005" can be the ID for a new control implemented in Storage feature.
+ 2. For **ControlID** above: Initial part of the control ID is pre-populated based on the service/feature and security domain you choose for the control (Azure_FeatureName_SecurityDomain_XXX). Please don't use spaces between words instead use underscore '_' to separate words in control ID. To see some of the examples of existing control IDs please check out this [list](https://github.com/azsk/AzTS-docs/tree/main/Control%20coverage#azure-services-supported-by-azts).
+ 3. Keep **Enabled** switch to 'Yes' to scan a control.
+ 4. **DisplayName** is the user friendly name for the control.
+
+
+ > *Note*: You can provide additional details/optional settings for the control as listed below.
+
+ |Settings| Description| Examples|
+ |-------------|------|---------|
+ |Automated| Whether the control is manual or automated| e.g. Yes/No (keep it Yes for policy based controls)|
+ |Description| A basic description on what the control is about| e.g. App Service must only be accessible over HTTPS. |
+ | Category| Generic security specification of the control.| e.g. Encrypt data in transit |
+ |Tags| Labels that denote the control being of a specific type or belonging to a specific domain | For e.g. Baseline, Automated etc.|
+ |Control Severity| The severity of the control| e.g. High: Should be remediated as soon as possible. Medium: Should be considered for remediation. Low: Remediation should be prioritized after high and medium.|
+ |Control Requirements| Prerequisites for the control.| e.g. Monitoring and auditing must be enabled and correctly configured according to prescribed organizational guidance|
+ |Rationale| Logical intention for the added control | e.g. Auditing enables log collection of important system events pertinent to security. Regular monitoring of audit logs can help to detect any suspicious and malicious activity early and respond in a timely manner.|
+ |Recommendations| Steps or guidance on how to remediate non-compliant resources | e.g. Refer https://azure.microsoft.com/en-in/documentation/articles/key-vault-get-started/ for configuring Key Vault and storing secrets |
+ |Custom Tags| Tags can be used for filtering and referring controls in the future while reporting| e.g. Production, Phase2 etc. |
+ |Control Settings| Settings specific to the control to be provided for the scan | e.g. Required TLS version for all App services in your tenant (Note: For policy based contols this should be empty) |
+ |Comments | These comments show up in the changelog for the feature. | e.g. Added new policy based control for App Service |
+
+2. [**Control Evaluator:**](README.md#control-evaluator)
+ 1. Copy _FeatureNameControlEvaluatorExt.cs_ and rename it accordingly. For example: StorageControlEvaluatorExt.cs
+ 2. Change the FeatureNameEvaluatorExt and FeatureNameControlEvaluator according to the baseControlEvaluator name (line 13) as shown below.
+ ``` CS
+ // class FeatureNameEvaluatorExt : FeatureNameControlEvaluator
+ class StorageEvaluatorExt : StorageControlEvaluator
+ {
+ // Add control methods here
+ }
+ ```
+ 3. Add the control method according to the [feature documentation](FeatureCoverage/README.md).
+
+## [Common Application of Org Policy Customization](README.common-application-of-org-policy-customization)
+
+1. Customizing/Changing the default Control Metadata
+ - You will be able to achieve the following scenarios using this feature:
+ 1. Update any of the properties of the control metadata according to your organization. The set of properties that you can modify ranges from ControlScanSource (i.e. change from ASCorReader to Reader) to Tags/CustomTags (i.e. add or remove a certain set of tags for a control).
+ 2. Update the baselines for your organization by modifying the tags/custom tags.
+ 3. Modify the ASC properties of a control.
+ "AssessmentProperties": {
+ "AssessmentNames": [
+ "1c5de8e1-f68d-6a17-e0d2-ec259c42768c"
+ ],
+ }
+2. Customizing the Control Method:
+ - You can update the control logic of any existing or new custom control. The user-defined method will get overlayed on the default control logic.
+3. Adding a new Control for existing feature:
+ - Users can add a new custom control of existing features by custom Azure Policy or ASC Assessment. Both can be done leveraging this Org policy customization feature.
+ - On a high level, Users can do so by following the below mentioned steps:
+ 1. Add control metadata of the control in FeatureNameExt.json file.
+ 2. If the ControlScanSource is ASC based then add the AssessmentProperties as shown below:
+ "AssessmentProperties": {
+ "AssessmentNames": [
+ ""
+ ],
+ }
+ 3. If the ControlScanSource is Reader based then implement the control logic in the corresponding Method in the FeatureNameControlEvaluatorExt.cs.
+
+4. Adding a new control of new feature (Coming Soon...)
+