<# .SYNOPSIS Create the management site for access to certs, logs, NSG whitelists/blacklists/etc. .DESCRIPTION Create the management site for access to certs, logs, NSG whitelists/blacklists/etc. Environment variables ARCUS_STATICFOLDERPATH, ARCUS_CUSTUNIQUE, ARCUS_SHAREDFOLDERPATH, ARCUS_LOGFOLDERPATH TODO: set insecure protocols to listen to localhost only TODO: remediate customers who got the AdminSite the week of 190701 - disable the folder scans, flatten dir structure - gghsca, vitamin shop, almost #> param( [Parameter(Mandatory=$False)][string]$serverName = "localhost", [Parameter(Mandatory=$False)][int]$eftAdminPort = 1100, [Parameter(Mandatory=$False)][string]$eftAdminName = "", [Parameter(Mandatory=$False)][int]$authMethod = 1, [Parameter(Mandatory=$False)][string]$eftAdminPassword = "", [Parameter(Mandatory=$True)][string]$customerSiteName = "", # name of the site where mgmt users will live -- we'll create the user template and group there [Parameter(Mandatory=$False)][int]$httpPort = 6996, # Requirement 373920: place admin health check on obscure port on management site [switch]$deleteFolderMonitors ) #----------------------------------------------------------------- # LogWrite function #----------------------------------------------------------------- Function LogWrite ($logstring, $logtype = 'INFORMATION') { Write-Output $logstring $env:ARCUS_DEPLOYMENTLOG = "C:\eft-pkgs\DeployatronLogMgmtSite.log" $timestamp = Get-Date -Format g Add-content $env:ARCUS_DEPLOYMENTLOG -value "$timestamp-Phase2-$logtype-$logstring" if ($logtype -eq "ERROR") { $EventId = 50001 }else { $EventId = 50000 } Write-EventLog -LogName Application -EventId 50000 -Source Deployatron -EntryType $logtype -Message $logstring } Function FindSite([string]$mySiteName) { $oSites = $oServer.Sites(); $mySite = $null for ( $i = 0; $i -lt $oSites.Count(); $i++ ) { $mySite = $oSites.Item($i); if ($mySite.Name -eq $mySiteName ) { #Write-Host "site name is " $oSite.Name break; } else { $mySite = $null } } return $mySite } $bAutoStart = $True # don't start the site by default if we're dealing with HTTP and it hasn't been limited to localhost $bCreateCerts = $False # vanilla user doesn't have access to configure share to house certs $bBasicMgmtOnly = $True # basic is certs and logs ; others include setting up for NSGs, etc.? $adminSitePort = 6995 $bHttpsAccess = $False $bConnected = $False $oServer = new-object -comobject 'SFTPCOMInterface.CIServer' $nConnectTrials = 0 # TODO: DWM: handle inability to log into due to other node having the admin lock - impose max retries? while ( ! $bConnected ) { try { $oServer.ConnectEx("localhost", 1100, 1, $env:ARCUS_EFTADMINNAME, $env:ARCUS_EFTADMINPASS) $bConnected = $True LogWrite "Logged into EFT Server to build mgmt site" break; } catch { LogWrite "Caught an exception staring site:" "ERROR" LogWrite "Exception Type: $($_.Exception.message)" "ERROR" LogWrite "Exception Message: $($_.Exception.Message)" "ERROR" Start-Sleep -s 2 $nConnectTrials++ if ($nConnectTrials -gt 5) { LogWrite "failed: ConnectEx - service up? Admin Lock? .... exiting" "ERROR" Return # exit the script block, but not the entire script } } } $adminsitename = "ArcusManagement" $oSite = FindSite $adminsitename # # create the Arcus Admin site if it doesn't already exist ... this is where event rules will reside # if ( $null -eq $oSite) { # notify LogWrite "failed to find site $adminsitename ... creating"; $message = @{"Phase" = "2"; "starttime" = $(Get-Date -Format g); "hostname" = $(hostname); "resourcegroup" = $env:ARCUS_RESOURCEGROUP; "subscriptionid" = $env:ARCUS_SUBSCRIPTIONID; "arcusenv" = $env:ARCUS_ENVIRONMENT; "status" = "failed to find site $adminsitename ... creating" "custunique" = $env:ARCUS_CUSTUNIQUE } Send-DeploymentQueueMessage $message LogWrite "Wrote message to queue $($message | Out-String)" $adminsiteroot = "$env:ARCUS_SHAREDFOLDERPATH\InetPub\EFTRoot\ManagementSite" $adminauddata = "$ConfigFolderPath\ManagementSite.aud" $oSites = $oServer.Sites() $oSites.AddLocalSite($adminsitename, $adminsiteroot, $adminauddata, 0, $adminSitePort, $True, $True, $bAutoStart, $True) $oServer.ApplyChanges() # # find the just-created admin site # $oSite = FindSite $adminsitename if (-not $oSite) { LogWrite "failed to find created site $adminsitename" "ERROR" Exit } else { LogWrite "found (newly created) site $adminsitename" "INFO" } } # # get the customer site # $customerSite = FindSite $customerSiteName #$env:ARCUS_CUSTUNIQUE if (-not $customerSite) { LogWrite ("failed to find customer site {0}" -f $customerSiteName) "ERROR" } else { LogWrite "found customer site" "INFO" } # # more site configuration # $oSite.SetFTPAccess($False) $oSite.SetHTTPSAccess($False) $oSite.SetHTTPSPort($adminSitePort) $oSite.BanIPForInvalidLoginAttempts = $True $oSite.BanIPForInvalidLoginAttemptsWithExistingUsername = $True $oSite.AllowChangePassword = $True $oSite.InvalidLoginAttemptsCountToBanIP = 10 $oSite.ForcePasswordResetOnInitialLogin = $False $oSite.RequireStrongPasswords = $True $bRestart = $oSite.SetHttpPort($httpPort) # 373920: place admin health check on obscure port on management site $oSite.SetHTTPAccess($True) | Out-Null LogWrite "set mgmt site protocol ports and parameters" try { $oSite.Stop() } catch { LogWrite "exception stopping site"; } if ($bAutoStart) { $oSite.Start() } # # create the management user template in the original site if it doesn't already exit # $adminSTName = "ArcusMgmt_UserTemplate" $clientSettings = $customerSite.GetSettingsLevelSettings($adminSTName) if (-not $clientSettings) { $adminST = $customerSite.CreateSettingsLevel($adminSTName, "Arcus Management Settings Template", 0) $clientSettings = $customerSite.GetSettingsLevelSettings($adminSTName) if ($clientSettings) { if ($bSimpleHomeFolder) { $clientSettings.SetHomeDirString("ArcusManagementVFS") } else { $clientSettings.SetHomeDirString("ArcusManagementVFS") # TODO } $ClientSettings.SetHomeDir(1) $ClientSettings.SetRequireStrongPasswords(1) $oServer.ApplyChanges() LogWrite "created SettingsLevel $adminSTName on customer site" } else { LogWrite "GetSettingsLevelSettings $adminSTName failed" "ERROR" } } else { LogWrite "SettingsLevel $adminSTName already exists" } # # create a permissions group # try { $permGroups = $customerSite.GetPermissionGroups(); $permGroupExists = $False; for ($i = 0; $i -lt $permGroups.Count; $i++) { if ($permGroups[$i] -eq "ArcusManagementGroup") { $permGroupExists = $True break; } } if (-not $permGroupExists) { $customerSite.CreatePermissionGroup("ArcusManagementGroup"); LogWrite "CreatePermissionGroup ArcusManagementGroup on customer site" } else { LogWrite "permissions group ArcusManagementGroup already exists on customer site" } } catch { LogWrite "failed: CreatePermissionGroup ArcusManagementGroup on customer site" "ERROR" } # # create a VFS link from the customer site to the arcus admin site # Requirement 367654: Create folder for self service files # TODO: permissions # try { $customerSite.CreatePhysicalFolder("ArcusManagementVFS") } catch { # will throw an MX exception if the folder already exists } $staticConfigFolder = $env:ARCUS_STATICFOLDERPATH $logFolder = $env:ARCUS_LOGFOLDERPATH $siteRootFolder = ("\\{0}.file{1}gsbdata\InetPub\EFTRoot" -f $env:ARCUS_USERDATASTORAGEACCOUNT, $env:ARCUS_AZUREENDPOINT) -replace '/', '\' # link to first site, what about others $managementSiteFolder = "{0}/AdminSite" -f $env:ARCUS_CONFIGFOLDERPATH #$AzureShare $userDataFolder = "{0}/" -f $env:ARCUS_SHAREDFOLDERPATH $adminFolders = @{ "ARMDB-Dumps" = @{ "Target" = "$managementSiteFolder/ARMDB-Dumps" ; "Writeable" = $True ; "Deleteable" = $True } ; "Certs" = @{ "Target" = "$staticConfigFolder" ; "Writeable" = $True ; "Deleteable" = $False } ; "Logs" = @{ "Target" = "$logFolder" ; "Writeable" = $False ; "Deleteable" = $False } ; "Admin" = @{ "Target" = "$env:ARCUS_ADMINFOLDERPATH" ; "Writeable" = $True ; "Deleteable" = $True } ; "Site-Root" = @{ "Target" = "$siteRootFolder" ; "Writeable" = $True ; "Deleteable" = $True } ; "Backups" = @{ "Target" = "$managementSiteFolder/Backups" ; "Writeable" = $True ; "Deleteable" = $True } ; "Restore" = @{ "Target" = "$managementSiteFolder/Backups/Restore" ; "Child" = $True } ; "AdminUsers" = @{ "Target" = "$managementSiteFolder/AdminUsers" ; "Writeable" = $True ; "Deleteable" = $True } ; "Settings" = @{ "Target" = "$managementSiteFolder/Settings" ; "Writeable" = $True ; "Deleteable" = $True } ; "Custom" = @{ "Target" = "$managementSiteFolder/Custom" ; "Writeable" = $True ; "Deleteable" = $True } ; "Custom/Email" = @{ "Target" = "$managementSiteFolder/Custom/Email" ; "Child" = $True } ; "Custom/Logo" = @{ "Target" = "$managementSiteFolder/Custom/OAI" ; "Child" = $True } ; "Custom/WTCMessage" = @{ "Target" = "$managementSiteFolder/Custom/WTCMessage" ; "Child" = $True } ; "Downloads" = @{ "Target" = "$managementSiteFolder/Downloads" ; "Writeable" = $False ; "Deleteable" = $True } ; # not in list, but already implemented "RestoredFiles" = @{ "Target" = "$siteRootFolder/RestoredFiles" ; "Writeable" = $True ; "Deleteable" = $True } ; "BulkUserExport" = @{ "Target" = "$managementSiteFolder/BulkUserExport" ; "Writeable" = $True ; "Deleteable" = $True } ; "BulkUserImport" = @{ "Target" = "$managementSiteFolder/BulkUserImport" ; "Writeable" = $True ; "Deleteable" = $True } ; "EventRules" = @{ "Target" = "$managementSiteFolder/EventRules" ; "Writeable" = $True ; "Deleteable" = $True } ; "EventRules/AWE" = @{ "Target" = "$managementSiteFolder/EventRules/AWE" ; "Child" = $True } ; "BastionSettings" = @{ "Target" = "$managementSiteFolder/BastionSettings" ; "Writeable" = $True ; "Deleteable" = $True } ; } try { mkdir $managementSiteFolder LogWrite "mkdir $managementSiteFolder" } catch { LogWrite "failed: mkdir $managementSiteFolder" "ERROR" } # TODO: remove Backups/Restore from the user data share if it exists. The old folder existing is just an inconvenience if the restores # are going to the user data share to avoid volume shadow copies. (Actually, Product doesn't think any production environments got deployed # this way, so this is less of a priority ... and if we do that, we'd also have to adjust the folder monitor, too) foreach ($folderAlias in $adminFolders.Keys) { $f = $adminFolders[$folderAlias] # create the target folder - certs and logs target already created (though maybe logs haven't been for older deployments) if ($f -ne "Certs" -or $f -ne "Logs") { #$oSite.CreatePhysicalFolder($f.Target) # this complains that -- Could not obtain the specified folder from the VFS tree. LogWrite ("creating target folder {0}" -f $f.Target) try { if (-not (Test-Path $f.Target)) { mkdir $f.Target | Out-Null # this requires the (config, logs?) shares to be mounted with appropriate permissions ; if already exits, no harm LogWrite ("mkdir {0}" -f $f.Target) } else { LogWrite ("target folder {0} already exists" -f $f.Target) } } catch { LogWrite ("error performing mkdir {0}" -f $f.Target) "ERROR" } } if ($f.Child) { # target VFS link is to a parent folder continue; } # TODO: maybe better to delete to ensure we have the desired permissions and configuration? #vfList = $oSite.GetVirtualFolderList("") #Write-Output $vfList # TODO: need to detect if folder already exists -- recreating it disturbs permissions $bVirtualFolderExists = $null $virtualFolder = "/ArcusManagementVFS/{0}" -f $folderAlias try { $bVirtualFolderExists = $customerSite.IsFolderVirtual($virtualFolder) LogWrite ("$virtualFolder IsFolderVirtual: $bVirtualFolderExists") } catch { } if ($bVirtualFolderExists) { LogWrite "$folderAlias already exists as a virtual folder ... skipping" continue } LogWrite ("creating management site virtual folder {0} with target {1}" -f $folderAlias, $f.Target) $customerSite.CreateVirtualFolder($virtualFolder, $f.Target) # file permissions ; TODO: delete $oPermission = $customerSite.GetBlankPermission("/ArcusManagementVFS/{0}" -f $folderAlias, "ArcusManagementGroup") #"ArcusManagement_UserTemplate") #"All Users") $oPermission.FileDownload = $True $oPermission.DirShowInList = $True $oPermission.DirList = $True if ($f.Writeable) { $oPermission.FileUpload = $True } if ($f.Deleteable) { $oPermission.FileDelete = $True } $customerSite.SetPermission($oPermission, $True) } $oServer.ApplyChanges() LogWrite "done configuring management site" # TODO: DWM: write status to deployment queue for success visibility # # create event rules (TODO: DWM -- should we consider .psm1 modules for better testing, standalone invocation?) # TODO: make error checking synchronous ... what is the COM equivalent of checking the "If Error Stop Processing?" # Function CreateMgmtSiteAction($osite, [string]$name, [int32]$processTimeout, [string]$adminSitePathToMonitor, [string]$parentPath, [string]$inputParamName = "-jsonFile", [string]$conditionSuffix = "json") { $scriptsDir = "{0}\Scripts64e3e669f861" -f $env:ARCUS_CONFIGFOLDERPATH # # create custom command to process NSG rules # LogWrite "creating $name CustomCommand" try { $oCommandSettings = $oSite.GetCommandSettings($name) } catch { } if ($oCommandSettings) { $ccParams = $oCommandSettings.Parameters; if($ccParams -match '\\gsbshared\\Scripts\\') { $ccParams = $ccParams -replace '\\gsbshared\\Scripts\\', '\gsbshared\Scripts64e3e669f861\' $oCommandSettings.Parameters = $ccParams; $oServer.ApplyChanges(); LogWrite "Command $name already exists ... correcting Scripts path" } else { LogWrite "Command $name already exists ... skipping" } } else { $oCommandSettings = $oSite.CreateCommand($name) $oCommandSettings.Name = $name $oCommandSettings.Executable = "C:\Windows\sysnative\WindowsPowerShell\v1.0\powershell.exe" $oCommandSettings.Parameters = "-ExecutionPolicy Bypass -File $scriptsDir\$name.ps1 $inputParamName %1%" # script signing an issue? prevents script from living on config share $oCommandSettings.IsEnabled = $True $oCommandSettings.RedirectOutputToLog = $False if ($processTimeout -gt 0) { $oCommandSettings.EnableProcessTimeOut = $True $oCommandSettings.ProcessTimeOut = $processTimeout } else { $oCommandSettings.EnableProcessTimeOut = $False } $oServer.ApplyChanges() LogWrite "DONE: creating $name CustomCommand" } # # create the FolderMonitor rule with a Virtual Path condition of *\ARMDB-Dumps\*.json ... invoke the FailedLoginReports command above # $cFolderMonitor = 0x1005; $eventRules = $oSite.EventRules($cFolderMonitor); $folderMonitorName = "{0}FolderMonitor" -f $name $existingFM = $eventRules.Find($folderMonitorName) if ($existingFM) { if($deleteFolderMonitors) { for($i = 0; $i -lt $eventRules.Count(); $i++) { $erParams = $eventRules.Item($i).GetParams() LogWrite "looking to delete folder monitor: $folderMonitorName ; comparing with $($erParams.Name)" if($erParams.Name -eq $folderMonitorName) { $eventRules.Delete($i) LogWrite "deleting folder monitor $folderMonitorName" break } } } else { LogWrite "folder monitor $folderMonitorName already exists ... returning" return } } $folderToMonitor = ("{0}\{1}" -f $parentPath, $adminSitePathToMonitor) mkdir $folderToMonitor -ErrorAction Ignore # this may fail if vanilla user doesn't have access to share $eventRuleParams = New-Object -ComObject 'SFTPComInterface.CIFolderMonitorEventRuleParams'; $eventRuleParams.Name = $folderMonitorName; $eventRuleParams.Enabled = $true; $eventRuleParams.Description = "{0}FolderMonitor" -f $name; $eventRuleParams.Path = $folderToMonitor # enable this in the bootstrap script which will have access (running as the service account), but disable when running # as a vanilla user because they will not have access #if(-not (Test-Path $eventRuleParams.Path)) { # LogWrite ("Folder Monitor target folder {0} does not exist ... skipping" -f $eventRuleParams.Path) # return #} # disabling scans for now. Peraton seemed to require them, but scan can re-process files unless Archiving is in play, and archiving # complicates the handoff with the powershell script (file is archived before the script processes it?) $eventRuleParams.UsePeriodicDirectoryPoll = $False #$True # $eventRuleParams.PollIntervalSeconds = 60 # $eventRuleParams.PollInterval = 60 # $eventRuleParams.PollIntervalType = 0 # seconds LogWrite ("creating {0}FolderMonitor FolderMonitor event with path of {1}" -f $name, $eventRuleParams.Path) $eventRule = $eventRules.Add($eventRules.Count(), $eventRuleParams); LogWrite ("DONE: creating {0}FolderMonitor FolderMonitor event" -f $name) LogWrite "creating the if file added condition" $folderOperationProperty = 5008 # Folder operation $conditionEqual = 0x01; # equal to $varConditionValue = "added" $bNot = $True $ifFileAdded = $eventRule.AddIfStatement(0, $folderOperationProperty, $conditionEqual, $varConditionValue, $bNot); $ifFileAddedStatements = $ifFileAdded.IfSection; $stopAction = New-Object -ComObject 'SFTPComInterface.CIStopActionParams'; $ifFileAddedStatements.Add(0, $stopAction) | Out-Null LogWrite "DONE: creating the if file added condition" LogWrite "creating the if file path condition" # having problems creating single match to handle multiple suffixes (array of matches doesn't work?), so create separate IF for each suffix foreach($suffix in ($conditionSuffix -split ",")) { $fileNameEventProperty = 5001 # 5001 is physical path, 5005 is file name; $conditionMatch = 0x10; $varConditionValue = "*\{0}\*.{1}" -f $adminSitePathToMonitor, $suffix $bNot = $False $ifMatch = $eventRule.AddIfStatement(1, $fileNameEventProperty, $conditionMatch, $varConditionValue, $bNot); $ifMatchStatements = $ifMatch.IfSection; LogWrite "DONE: creating the if file path condition" LogWrite "creating the command action to invoke the powershell script" $customCommandActionParams = New-Object -ComObject 'SFTPComInterface.CICommandActionParams' $customCommandActionParams.Command = $name $customCommandActionParams.Parameters = "%FS.PATH%" $customCommandActionParams.WorkingFolder = "C:\" $customCommandAction = $ifMatchStatements.Add(0, $customCommandActionParams); } LogWrite "DONE: creating the command action to invoke the powershell script" LogWrite "applying changes" $oServer.ApplyChanges() LogWrite "DONE: applying changes" } CreateMgmtSiteAction -osite $oSite -name "ArmReports" -processTimeout 1200 -adminSitePathToMonitor "ARMDB-Dumps" -parentPath "$($env:ARCUS_CONFIGFOLDERPATH)\AdminSite" CreateMgmtSiteAction -osite $oSite -name "UpdateBackupPolicy" -processTimeout 60 -adminSitePathToMonitor "Backups" -parentPath "$($env:ARCUS_CONFIGFOLDERPATH)\AdminSite" CreateMgmtSiteAction -osite $oSite -name "ScanRestorePoints" -processTimeout 60 -adminSitePathToMonitor "Restore" -parentPath $env:ARCUS_SHAREDFOLDERPATH # user data share to avoid volume shadow copies CreateMgmtSiteAction -osite $oSite -name "DisableAdmin" -processTimeout 60 -adminSitePathToMonitor "AdminUsers" -parentPath "$($env:ARCUS_CONFIGFOLDERPATH)\AdminSite" # TODO: limit folder monitor to disable-admin.json, archive $currentVersion = $env:ARCUS_CURRENTEFTVERSION $suffices = @{} if($currentVersion.StartsWith("7")) { $suffices['PGP'] = 'asc' $suffices['SSH'] = 'pub' } else { $suffices['PGP'] = 'json' # used to acccept asc directly, but now need json to specify site $suffices['SSH'] = 'json' } CreateMgmtSiteAction -osite $oSite -name "ImportPgpKey" -processTimeout 60 -adminSitePathToMonitor "PgpKeyImports" ` -inputParamName "-keyFile" -parentPath "$($env:ARCUS_CONFIGFOLDERPATH)\Config_Static" -conditionSuffix $suffices['PGP'] CreateMgmtSiteAction -osite $oSite -name "ImportSshPubKey" -processTimeout 60 -adminSitePathToMonitor "SshKeyImports" ` -inputParamName "-keyFile" -parentPath "$($env:ARCUS_CONFIGFOLDERPATH)\Config_Static" -conditionSuffix $suffices['SSH'] CreateMgmtSiteAction -osite $oSite -name "ImportSslCert" -processTimeout 60 -adminSitePathToMonitor "SslCertImports" ` -inputParamName "-jsonFile" -parentPath "$($env:ARCUS_CONFIGFOLDERPATH)\Config_Static" -conditionSuffix "json" CreateMgmtSiteAction -osite $oSite -name "ExportPgpKey" -processTimeout 60 -adminSitePathToMonitor "PgpKeyExports" ` -inputParamName "-jsonFile" -parentPath "$($env:ARCUS_CONFIGFOLDERPATH)\Config_Static" -conditionSuffix "json" CreateMgmtSiteAction -osite $oSite -name "ExportSshPubKey" -processTimeout 60 -adminSitePathToMonitor "SshKeyExports" ` -inputParamName "-jsonFile" -parentPath "$($env:ARCUS_CONFIGFOLDERPATH)\Config_Static" -conditionSuffix "json" CreateMgmtSiteAction -osite $oSite -name "ExportSslCert" -processTimeout 60 -adminSitePathToMonitor "SslCertExports" ` -inputParamName "-jsonFile" -parentPath "$($env:ARCUS_CONFIGFOLDERPATH)\Config_Static" -conditionSuffix "json" CreateMgmtSiteAction -osite $oSite -name "EFT.UserBulk.Import" -processTimeout 1800 -adminSitePathToMonitor "BulkUserImport" ` -inputParamName "-UserCsvlist" -parentPath "$($env:ARCUS_CONFIGFOLDERPATH)\AdminSite" -conditionSuffix "csv" CreateMgmtSiteAction -osite $oSite -name "EFT.UserBulk.Export" -processTimeout 1800 -adminSitePathToMonitor "BulkUserExport" ` -inputParamName "-jsonFile" -parentPath "$($env:ARCUS_CONFIGFOLDERPATH)\AdminSite" -conditionSuffix "json" CreateMgmtSiteAction -osite $oSite -name "BastionWhitelistIPs" -processTimeout 1800 -adminSitePathToMonitor "BastionSettings" ` -inputParamName "-jsonFile" -parentPath "$($env:ARCUS_CONFIGFOLDERPATH)\AdminSite" -conditionSuffix "json"