From 0cf78b960a168cb068960a83d07bd5c9d6979efa Mon Sep 17 00:00:00 2001 From: Jeremy Colby Date: Thu, 27 Jun 2019 14:27:57 -0700 Subject: [PATCH 01/10] Init commit (Version 1.1.0) --- .../CloudflareITGlue/CloudflareITGlue.psd1 | 134 ++++++++++++++++++ .../CloudflareITGlue/CloudflareITGlue.psm1 | 11 ++ .../Private/CloudflareWebRequest.ps1 | 55 +++++++ .../Private/CloudflareZoneData.ps1 | 115 +++++++++++++++ .../Private/ITGlueWebRequest.ps1 | 51 +++++++ .../Public/CloudflareITGlueAPIAuth.ps1 | 77 ++++++++++ .../New-CloudflareITGlueFlexAssetType.ps1 | 106 ++++++++++++++ .../Sync-CloudflareITGlueFlexibleAssets.ps1 | 56 ++++++++ Cloudflare ITGlue Powershell Module/readme.md | 111 +++++++++++++++ 9 files changed, 716 insertions(+) create mode 100644 Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psd1 create mode 100644 Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psm1 create mode 100644 Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareWebRequest.ps1 create mode 100644 Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 create mode 100644 Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/ITGlueWebRequest.ps1 create mode 100644 Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/CloudflareITGlueAPIAuth.ps1 create mode 100644 Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/New-CloudflareITGlueFlexAssetType.ps1 create mode 100644 Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/Sync-CloudflareITGlueFlexibleAssets.ps1 create mode 100644 Cloudflare ITGlue Powershell Module/readme.md diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psd1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psd1 new file mode 100644 index 0000000..b0b4828 --- /dev/null +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psd1 @@ -0,0 +1,134 @@ +# +# Module manifest for module 'CloudflareITGlue' +# +# Generated by: Jeremy Colby +# +# Generated on: 10/30/2018 +# + +@{ + + # Script module or binary module file associated with this manifest. + RootModule = 'CloudflareITGlue.psm1' + + # Version number of this module. + ModuleVersion = '1.1.0' + + # Supported PSEditions + # CompatiblePSEditions = @() + + # ID used to uniquely identify this module + GUID = '55a62423-f6e4-4548-ba2a-7387a32ff6d3' + + # Author of this module + Author = 'Jeremy Colby' + + # Company or vendor of this module + CompanyName = '' + + # Copyright statement for this module + Copyright = '(c) 2018 Jeremy Colby. All rights reserved.' + + # Description of the functionality provided by this module + # Description = '' + + # Minimum version of the Windows PowerShell engine required by this module + PowerShellVersion = '5.0' #New-TemporaryFile seems like only incompatiblity with 4.0 + + # Name of the Windows PowerShell host required by this module + # PowerShellHostName = '' + + # Minimum version of the Windows PowerShell host required by this module + # PowerShellHostVersion = '' + + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' + + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # CLRVersion = '' + + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' + + # Modules that must be imported into the global environment prior to importing this module + # RequiredModules = @() + + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() + + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() + + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() + + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() + + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + NestedModules = 'Private\CloudflareWebRequest.ps1', + 'Private\CloudflareZoneData.ps1', + 'Private\ITGlueWebRequest.ps1', + 'Public\CloudflareITGlueAPIAuth.ps1', + 'Public\New-CloudflareITGlueFlexAssetType.ps1', + 'Public\Sync-CloudflareITGlueFlexibleAssets.ps1' + + + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = 'Add-CloudflareITGlueAPIAuth', + 'Get-CloudflareITGlueAPIAuth', + 'Remove-CloudflareITGlueAPIAuth', + 'New-CloudflareITGlueFlexAssetType', + 'Sync-CloudflareITGlueFlexibleAssets' + + + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = @() + + # Variables to export from this module + VariablesToExport = @() + + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = @() + + # DSC resources to export from this module + # DscResourcesToExport = @() + + # List of all modules packaged with this module + # ModuleList = @() + + # List of all files packaged with this module + # FileList = @() + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + } # End of PSData hashtable + + } # End of PrivateData hashtable + + # HelpInfo URI of this module + # HelpInfoURI = '' + + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' + +} + diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psm1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psm1 new file mode 100644 index 0000000..6ff9e82 --- /dev/null +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psm1 @@ -0,0 +1,11 @@ +$ModuleBase = Get-Module CloudflareITGlue -ListAvailable | ForEach-Object ModuleBase + +if (Test-Path "$ModuleBase\$env:username.auth") { + Write-Host "CloudflareITGlue: Auth detected for $env:username" -ForegroundColor Green + $Auth = Import-Csv "$ModuleBase\$env:username.auth" + $Global:CloudflareAPIEmail = $Auth.CloudflareEmail + $Global:CloudflareAPIKey = ($Auth.CloudflareAPIKey | ConvertTo-SecureString) + $Global:ITGlueAPIKey = ($Auth.ITGlueAPIKey | ConvertTo-SecureString) +} + +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareWebRequest.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareWebRequest.ps1 new file mode 100644 index 0000000..04b556b --- /dev/null +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareWebRequest.ps1 @@ -0,0 +1,55 @@ +function New-CloudflareWebRequest { + param( + [Parameter(Mandatory = $true)][string]$Endpoint, + [ValidateSet('GET', 'POST', 'PUT', 'PATCH', 'DELETE')][string]$Method = 'GET', + [string]$Body = $null, + [int]$ResultsPerPage = 50, + [int]$PageNumber = 1 + ) + + if ($CloudflareAPIKey) { + try { + $APIKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($CloudflareAPIKey)) + } + catch { + Write-Warning 'New-CloudflareWebRequest: Unable to decrypt auth info' + Write-Warning 'Run Add-CloudflareITGlueAPIAuth to re-add' + break + } + } + else { + Write-Warning 'Run Add-CloudflareITGlueAPIAuth to add authorization info' + break + } + + $RequestParams = @{ + Uri = 'https://api.cloudflare.com/client/v4/' + $Endpoint + "?per_page=$ResultsPerPage&page=$PageNumber" + Method = $Method + Headers = @{ + 'X-Auth-Key' = $APIKey + 'X-Auth-Email' = $CloudflareAPIEmail + 'Content-Type' = 'application/json' + } + } + if ($Body) {$RequestParams.Body = $Body} + + try { + Start-Sleep -Milliseconds 275 + $Request = Invoke-RestMethod @RequestParams + Start-Sleep -Milliseconds 275 + #RateLimit = 1200 requests/5 min but still getting gateway timeouts @ 300ms+ + + if ($PageNumber -lt $Request.result_info.total_pages) { + $PageNumber++ + New-CloudflareWebRequest -Endpoint $Endpoint -ResultsPerPage $ResultsPerPage -PageNumber $PageNumber + } + $APIKey = $null + $RequestParams = $null + return $Request + } + catch { + Write-Warning "Something went wrong with Cloudflare request:`n$_" + $APIKey = $null + $RequestParams = $null + } +} diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 new file mode 100644 index 0000000..f644de7 --- /dev/null +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 @@ -0,0 +1,115 @@ +function Get-CloudflareZoneData { + param( + [Parameter(Mandatory = $true)][string]$ZoneId, + [Parameter(Mandatory = $true)][pscustomobject]$ITGMatch + ) + + $Timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-M-d HH:mm:ss") + $AccountId = New-CloudflareWebRequest -Endpoint 'accounts' | ForEach-Object result | ForEach-Object id + $ZoneInfo = New-CloudflareWebRequest -Endpoint "zones/$ZoneId" + $ZoneRecords = New-CloudflareWebRequest -Endpoint "zones/$ZoneId/dns_records" + if ($ZoneRecords.result_info.count -eq 0) { + Write-Host "$($ZoneInfo.result.name): Empty Zone Detected" -ForegroundColor Yellow + break + } + $ZoneFileData = New-CloudflareWebRequest -Endpoint "zones/$ZoneId/dns_records/export" + $ZoneFileData = $ZoneFileData.Replace( + "@ 3600 IN SOA $($ZoneInfo.result.name). root.$($ZoneInfo.result.name). (", + "@ 3600 IN SOA $($ZoneInfo.result.name_servers[0]). $((($CloudflareAPIEmail -split '@')[0]).Replace('.','\.') + '.'+ ($CloudflareAPIEmail -split '@')[1]). (" + ) + $ZoneFileData = $ZoneFileData.Replace( + ";; NS Records (YOU MUST CHANGE THIS)`n$($ZoneInfo.result.name). 1 IN NS " + 'REPLACE&ME$WITH^YOUR@NAMESERVER.', + ";; NS Records`n$($ZoneInfo.result.name). 1 IN NS $($ZoneInfo.result.name_servers[0]).`n$($ZoneInfo.result.name). 1 IN NS $($ZoneInfo.result.name_servers[1])." + ) + $ZoneFileData = $ZoneFileData.Replace( + ';; -- update the SOA record with the correct authoritative name server', + ";; -- update the SOA record with the correct authoritative name server`n;; ** CloudflareITGlue Module: Updated $($Timestamp)" + ) + $ZoneFileData = $ZoneFileData.Replace( + ';; -- update the SOA record with the contact e-mail address information', + ";; -- update the SOA record with the contact e-mail address information`n;; ** CloudflareITGlue Module: Updated $($Timestamp)" + ) + $ZoneFileData = $ZoneFileData.Replace( + ';; -- update the NS record(s) with the authoritative name servers for this domain.', + ";; -- update the NS record(s) with the authoritative name servers for this domain.`n;; ** CloudflareITGlue Module: Updated $($Timestamp)" + ) + $RecordsHtml = + '
+

+ + Open in Cloudflare +

+ + + + + + + + + + + ' + + $(foreach ($Record in $ZoneRecords.result) { + " + + + + + + + + " + }) + + ' +
TypeNameValuePriorityTTLProxiedModified
$($Record.type)$($Record.name)$($Record.content)$($Record.priority)$(if ($Record.ttl -eq 1){'Auto'}else{$Record.ttl})$($Record.proxied)$(($Record.modified_on.Replace('T', ' ') -split '\.')[0])
+
' + + $ZoneData = [ordered]@{ + Name = $ZoneInfo.result.name + SyncDate = $Timestamp + CfNameServers = $ZoneInfo.result.name_servers + Status = $ZoneInfo.result.status + ZoneFileData = $ZoneFileData + RecordsHtml = $RecordsHtml + ITGOrg = $Match.OrgMatchId + DomainTracker = $Match.DomainTrackerId + } + $ZoneData +} + +function Get-CloudflareZoneDataArray { + $ZoneDataArray = @() + $AllZones = New-CloudflareWebRequest -Endpoint 'zones' + [pscustomobject]$ITGDomains = New-ITGlueWebRequest -Endpoint 'domains' | ForEach-Object data + $Progress = 0 + + foreach ($Zone in $AllZones.result) { + Write-Progress -Activity 'CloudflareAPI' -Status 'Getting Zone Data' -CurrentOperation $Zone.name -PercentComplete ($Progress / ($AllZones.result | Measure-Object | ForEach-Object count) * 100) -Id 1 + + $ITGMatches = @() + foreach ($ITGDomain in $ITGDomains) { + if ($Zone.name.ToLower() -eq $ITGDomain.attributes.name.ToLower()) { + $Match = @{ + OrgMatchId = $ITGDomain.attributes.'organization-id' + DomainTrackerId = $ITGDomain.id + } + $ITGMatches += [pscustomobject]$Match + } + } + if($ITGMatches){ + foreach ($Match in $ITGMatches) { + $ZoneData = Get-CloudflareZoneData -ZoneId $Zone.id -ITGMatch $Match + if ($ZoneData) { + $ZoneDataArray += [pscustomobject]$ZoneData + } + } + } + else{ + Write-Host "$($Zone.name): Add to domain tracker" -ForegroundColor Yellow + } + $Progress++ + } + Write-Progress -Activity 'CloudflareAPI' -Status 'Complete' -PercentComplete 100 -Id 1 + $ZoneDataArray +} diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/ITGlueWebRequest.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/ITGlueWebRequest.ps1 new file mode 100644 index 0000000..91a64c8 --- /dev/null +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/ITGlueWebRequest.ps1 @@ -0,0 +1,51 @@ +function New-ITGlueWebRequest { + param( + [Parameter(Mandatory = $true)][string]$Endpoint, + [ValidateSet('GET', 'POST', 'PATCH', 'DELETE')][string]$Method = 'GET', + [string]$Body = $null, + [int]$ResultsPerPage = 50, + [int]$PageNumber = 1 + ) + + if ($ITGlueAPIKey) { + try { + $APIKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ITGlueAPIKey)) + } + catch { + Write-Warning 'New-ITGlueWebRequest: Unable to decrypt auth info' + Write-Warning 'Run Add-CloudflareITGlueAPIAuth to re-add' + break + } + } + else { + Write-Warning 'Run Add-CloudflareITGlueAPIAuth to add authorization info' + break + } + + $RequestParams = @{ + Uri = 'https://api.itglue.com/' + $Endpoint + "?page[size]=$ResultsPerPage&page[number]=$PageNumber" + Method = $Method + Headers = @{ + 'x-api-key' = $APIKey + 'Content-Type' = 'application/vnd.api+json' + } + } + if ($Body) {$RequestParams.Body = $Body} + + try { + $Request = Invoke-RestMethod @RequestParams + + if ($PageNumber -lt $Request.meta.'total-pages') { + $PageNumber++ + New-ITGlueWebRequest -Endpoint $Endpoint -Body $Body -ResultsPerPage $ResultsPerPage -PageNumber $PageNumber + } + $APIKey = $null + $RequestParams = $null + return $Request + } + catch { + Write-Warning "Something went wrong with ITGlue request:`n$_" + $APIKey = $null + $RequestParams = $null + } +} diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/CloudflareITGlueAPIAuth.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/CloudflareITGlueAPIAuth.ps1 new file mode 100644 index 0000000..e858210 --- /dev/null +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/CloudflareITGlueAPIAuth.ps1 @@ -0,0 +1,77 @@ +function Add-CloudflareITGlueAPIAuth { + if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) { + Write-Host 'Add/removing API Auth require admin access' -ForegroundColor Yellow + } + else { + [pscredential]$CloudflareCredentials = $Host.UI.PromptForCredential('Cloudflare API Authentication', "User name: Cloudflare Email`r`nPassword: Cloudflare API Key", '', '') + [pscredential]$ITGCredentials = $Host.UI.PromptForCredential('ITGlue API Authentication', 'Password: ITGlue API Key', 'ITGlue', '') + $Global:CloudflareAPIEmail = $CloudflareCredentials.username + $Global:CloudflareAPIKey = $CloudflareCredentials.Password + $Global:ITGlueAPIKey = $ITGCredentials.Password + + if (!$CloudflareAPIEmail -or !$CloudflareAPIKey -or !$ITGlueAPIKey) { + Write-Host 'Cancelled' -ForegroundColor Yellow + break + } + if ($CloudflareAPIEmail -notmatch "\A[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z") { + Write-Host 'Invalid email address format' -ForegroundColor Yellow + break + } + if (!$CloudflareCredentials.GetNetworkCredential().Password -or !$ITGCredentials.GetNetworkCredential().Password) { + Write-Warning 'API key(s) not entered' + break + } + $Credentials = @{ + CloudflareEmail = $CloudflareAPIEmail + CloudflareAPIKey = ($CloudflareAPIKey | ConvertFrom-SecureString) + ITGlueAPIKey = ($ITGlueAPIKey | ConvertFrom-SecureString) + } + $Auth = @() + $Auth += [pscustomobject]$Credentials + $ModuleBase = Get-Module CloudflareITGlue | ForEach-Object ModuleBase + + $Auth | Export-Csv "$ModuleBase\$env:username.auth" -NoTypeInformation -Force + } +} + +function Get-CloudflareITGlueAPIAuth { + if (Test-Path "$ModuleBase\$env:username.auth") { + Write-Host 'Auth file detected' -ForegroundColor Green + $Auth = Import-Csv "$ModuleBase\$env:username.auth" + + try { + $cfkey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($($Auth.CloudflareAPIKey | ConvertTo-SecureString))) + $itgkey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($($Auth.ITGlueAPIKey | ConvertTo-SecureString))) + $cfkeyhalf = [int]($cfkey | Measure-Object -Character | ForEach-Object Characters) / 2 + $itgkeyhalf = [int]($itgkey | Measure-Object -Character | ForEach-Object Characters) / 2 + Write-Host "Cloudflare Email: $($Auth.CloudflareEmail)" + Write-Host "Cloudflare API Key: $($cfkey.Substring(0,$cfkeyhalf))********************" -ErrorAction Ignore + Write-Host "ITGlue API Key: $($itgkey.Substring(0,$itgkeyhalf))********************`n" -ErrorAction Ignore + $cfkey = $null + $itgkey = $null + } + catch { + Write-Warning 'Invalid format or unable to decrypt' + Write-Warning 'Run Add-CloudflareITGlueAPIAuth to re-add auth info for the current account' + $cfkey = $null + $itgkey = $null + } + } + else { + Write-Host "No auth detected for $env:username" -ForegroundColor Yellow + } +} + +function Remove-CloudflareITGlueAPIAuth { + if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) { + Write-Host 'Add/removing API Auth require admin access' -ForegroundColor Yellow + } + else { + if (Test-Path "$ModuleBase\$env:username.auth") { + Remove-Item "$ModuleBase\$env:username.auth" -Force + } + else { + Write-Host 'Not added' -ForegroundColor Yellow + } + } +} diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/New-CloudflareITGlueFlexAssetType.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/New-CloudflareITGlueFlexAssetType.ps1 new file mode 100644 index 0000000..8c8a789 --- /dev/null +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/New-CloudflareITGlueFlexAssetType.ps1 @@ -0,0 +1,106 @@ +function New-CloudflareITGlueFlexAssetType { + param( + [string]$Name = 'Cloudflare DNS' + ) + + $Body = @{ + Data = @{ + type = 'flexible_asset_types' + attributes = @{ + name = $Name + description = 'DNS Zones from Cloudflare.' + icon = 'cloud' + enabled = $true + show_in_menu = $false + } + + relationships = @{ + flexible_asset_fields = @{ + data = @( + @{ + type = 'flexible_asset_fields' + attributes = @{ + order = 1 + name = 'Name' + kind = 'Text' + hint = 'Name of the DNS Zone' + required = $true + show_in_list = $true + use_for_title = $true + } + }, + @{ + type = 'flexible_asset_fields' + attributes = @{ + order = 2 + name = 'Last Sync' + kind = 'Text' + hint = 'When zone last synced (UTC)' + required = $true + show_in_list = $false + } + }, + @{ + type = 'flexible_asset_fields' + attributes = @{ + order = 3 + name = 'Nameservers' + kind = 'Textbox' + hint = 'Cloudflare provided nameservers for this zone' + required = $true + show_in_list = $false + } + }, + @{ + type = 'flexible_asset_fields' + attributes = @{ + order = 4 + name = 'Status' + kind = 'Text' + hint = 'Status of the Cloudflare Zone' + required = $false + show_in_list = $true + } + }, + @{ + type = 'flexible_asset_fields' + attributes = @{ + order = 5 + name = 'Zone File' + kind = 'Upload' + hint = 'Exported zone file in BIND format. You can upload this to Cloudflare. UTF-8 Encoded, use notepad++ for better viewing.' + required = $false + show_in_list = $false + } + }, + @{ + type = 'flexible_asset_fields' + attributes = @{ + order = 6 + name = 'Domain Tracker' + kind = 'Tag' + hint = 'Tagged in Domain Tracker' + tag_type = 'Domains' + required = $false + show_in_list = $false + } + }, + @{ + type = 'flexible_asset_fields' + attributes = @{ + order = 7 + name = 'DNS Records' + kind = 'Textbox' + hint = 'Table of DNS records in the zone' + required = $true + show_in_list = $false + } + } + ) + } + } + } + } + $Body = $Body | ConvertTo-Json -Depth 6 + New-ITGlueWebRequest -Endpoint 'flexible_asset_types' -Method 'POST' -Body $Body +} \ No newline at end of file diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/Sync-CloudflareITGlueFlexibleAssets.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/Sync-CloudflareITGlueFlexibleAssets.ps1 new file mode 100644 index 0000000..7290421 --- /dev/null +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/Sync-CloudflareITGlueFlexibleAssets.ps1 @@ -0,0 +1,56 @@ +function Sync-CloudflareITGlueFlexibleAssets { + param( + [string]$FlexAssetType = 'Cloudflare DNS' + ) + + $Progress = 0 + $ZoneDataArray = Get-CloudflareZoneDataArray + $FlexAssetTypeId = New-ITGlueWebRequest -Endpoint 'flexible_asset_types' -Method 'GET' | ForEach-Object data | Where-Object {$_.attributes.name -eq $FlexAssetType} | ForEach-Object id + + foreach ($ZoneData in $ZoneDataArray) { + Write-Progress -Activity 'ITGlueAPI' -Status 'Syncing Flexible Assets' -CurrentOperation $ZoneData.name -PercentComplete ($Progress / ($ZoneDataArray | Measure-Object | foreach-object count) * 100) -Id 2 + + $TempFile = New-TemporaryFile + $ZoneData.ZoneFileData | Out-file $TempFile -Force -Encoding utf8 + $Base64ZoneFile = ([System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($TempFile))) + Remove-Item $TempFile -Force + $Body = @{ + data = @{ + 'type' = 'flexible-assets' + 'attributes' = @{ + 'organization-id' = $ZoneData.ITGOrg + 'flexible-asset-type-id' = $FlexAssetTypeId + 'traits' = @{ + 'name' = $ZoneData.Name + 'last-sync' = $ZoneData.SyncDate + 'nameservers' = $ZoneData.CfNameServers -join '
' + 'status' = $ZoneData.Status + 'zone-file' = @{ + 'content' = $Base64ZoneFile + 'file_name' = "$($ZoneData.Name).txt" + } + 'dns-records' = $ZoneData.RecordsHtml + 'domain-tracker' = $ZoneData.DomainTracker + } + } + } + } + $Body = $Body | ConvertTo-Json -Depth 4 + $FlexAssets = New-ITGlueWebRequest -Endpoint "flexible_assets?filter[flexible_asset_type_id]=$FlexAssetTypeId&filter[organization_id]=$($ZoneData.ITGOrg)" -Method 'GET' | ForEach-Object data + $PatchId = $null + + foreach ($FlexAsset in $FlexAssets) { + if ($FlexAsset.attributes.traits.name -eq $ZoneData.name) { + $PatchId = $FlexAsset.id + } + } + if ($PatchId) { + New-ITGlueWebRequest -Endpoint "flexible_assets/$PatchId" -Method 'PATCH' -Body $Body + } + else { + New-ITGlueWebRequest -Endpoint 'flexible_assets' -Method 'POST' -Body $Body + } + $Progress++ + } + Write-Progress -Activity 'ITGlueAPI' -Status 'Syncing Flexible Assets' -PercentComplete 100 -Id 2 +} diff --git a/Cloudflare ITGlue Powershell Module/readme.md b/Cloudflare ITGlue Powershell Module/readme.md new file mode 100644 index 0000000..109d813 --- /dev/null +++ b/Cloudflare ITGlue Powershell Module/readme.md @@ -0,0 +1,111 @@ +# CloudflareITGlue Powershell Module + +## What does this do + +- Sync Cloudflare DNS Zones to ITGlue Client Organizations as Flex Assets + +![screenshot](https://user-images.githubusercontent.com/43423017/48573233-6e7f4700-e8c0-11e8-8dd1-793e06620e96.png) + +>**Name:** Name of the Cloudflare DNS Zone +>**Last Sync:** Timestamp when flex asset is created/updated +>**Nameservers:** Nameservers designated by Cloudflare +>**Status:** Status of the Cloudflare DNS Zone +>**Zone File:** BIND format zone file +>**Domain Tracker:** Domain Tracker Tag +>**DNS Records:** Table of all DNS records in the zone and a link to the zone page in Cloudflare +>**Revisions:** Flex assets contain revision history by nature + +## How it works + +- Configure +- Schedule the sync command to run at a desired interval + +## Configuration + +[Installing the module](#installing-the-module) +[API Authorization](#api-authorization) +[Creating the ITGlue Flex Asset Type](#creating-the-itglue-flex-asset-type) + +### Installing the module + +Copy the CloudflareITGlue module folder into the Powershell module directory +>`C:\Program Files\WindowsPowerShell\Modules\CloudflareITGlue` + +### API Authorization + +#### Obtain API keys + +- Cloudflare + - Login to Cloudflare. + - Go to **My Profile**. + - Scroll down to **API Keys** and locate _Global API Key_. + - Select **API Key**. + +- ITGlue + - Login to ITGlue. + - Select the **Account** tab. + - Select the **API Keys** tab. + - Click the **+** symbol to add a new API key. + - **Enter Name**. + - Select **Generate API Key** + +#### Add authorization + +```powershell +Add-CloudflareITGlueAPIAuth +``` + +>This will prompt you for your API keys and Cloudflare email. The API keys will be encrypted with your user account and stored in the module directory. Requires elevated permissions for file creation. + +#### Viewing/Removing authorization info + +```powershell +Get-CloudflareITGlueAPIAuth +Remove-CloudflareITGlueAPIAuth +``` + +>Use these to view/delete the auth that's been entered. +>API keys are not shown in full. Removal requires elevated permissions to delete file. + +### Creating the ITGlue Flex Asset Type + +```powershell +New-CloudflareITGlueFlexAssetType +``` + +>This will create a new Flex Asset Type in ITGlue called **Cloudflare DNS**. +>Customize your ITGlue sidebar in the **Account > Settings > General > Customize Sidebar** section. +>If you need to use a different name there is an optional parameter: +>`New-CloudflareITGlueFlexAssetType -Name 'My Cloudflare DNS'` + +## Usage + +```powershell +Sync-CloudflareITGlueFlexibleAssets +``` + +>This command will match Cloudflare zones to ITGlue orgs using the Domain Tracker then sync the zones as flex assets to their respective organizations. +>Cloudflare zones that are not in the Domain Tracker will be output to the console. +>If you used a custom name for the flex asset type, you'll also need to pass it to the sync command via the optional FlexAssetType parameter: +>`Sync-CloudflareITGlueFlexibleAssets -FlexAssetType 'My Cloudflare DNS'` + +Set this up to run at an interval of your choosing however you like. + +- Heres a quick Powershell script you can use to create a scheduled task: + +>```powershell +>$Action = New-ScheduledTaskAction -Execute 'Powershell.exe' ` +> -Argument '-NoProfile -WindowStyle Hidden -Command "& Sync-CloudflareITGlueFlexibleAssets"' +>$Trigger = New-ScheduledTaskTrigger -Daily -At 8am +>$Principal = New-ScheduledTaskPrincipal -UserID '%username%' -LogonType S4U +>Register-ScheduledTask -TaskName 'Sync zones' -Action $Action -Trigger $Trigger -Principal $Principal +># Be sure you've added auth info for %username% +>``` + +## References + +[Invoke-RestMethod Documentation](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod/) +[ITGlue API Documentation](https://api.itglue.com/developer/) +[Cloudflare API Documentation](https://api.cloudflare.com/) +>On Cloudflare Rate Limiting: "The Cloudflare API sets a maximum of 1,200 requests in a five minute period." +>You may still see the odd gateway timeout even though the rate limit is accounted for. From 4253afd6424becf2505107aa2e1ccb1aa30fd848 Mon Sep 17 00:00:00 2001 From: Jeremy Colby Date: Thu, 27 Jun 2019 14:29:37 -0700 Subject: [PATCH 02/10] 1.2.0 --- .../CloudflareITGlue/CloudflareITGlue.psd1 | 31 ++++--- .../CloudflareITGlue/CloudflareITGlue.psm1 | 2 + .../Private/CloudflareWebRequest.ps1 | 11 ++- .../Private/CloudflareZoneData.ps1 | 45 ++++++---- .../Private/ITGlueWebRequest.ps1 | 7 +- .../New-CloudflareITGlueFlexAssetType.ps1 | 14 +-- .../Public/CloudflareITGlueAPIAuth.ps1 | 66 +++++++------- .../Sync-CloudflareITGlueFlexibleAssets.ps1 | 86 +++++++++++++++---- 8 files changed, 167 insertions(+), 95 deletions(-) rename Cloudflare ITGlue Powershell Module/CloudflareITGlue/{Public => Private}/New-CloudflareITGlueFlexAssetType.ps1 (85%) diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psd1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psd1 index b0b4828..5343f25 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psd1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psd1 @@ -3,7 +3,7 @@ # # Generated by: Jeremy Colby # -# Generated on: 10/30/2018 +# Generated on: 06/27/2019 # @{ @@ -12,8 +12,20 @@ RootModule = 'CloudflareITGlue.psm1' # Version number of this module. - ModuleVersion = '1.1.0' - + ModuleVersion = '1.2.0' + # 1.0: Matching zones to ITGlue orgs via txt record mechanism + # 1.1: Matching zones to ITGlue orgs via Domain tracker + minor improvements + # 1.2: Full logging functionality + # Cloudflare's zone file export format changed, modified to account for this + # - Also, re Cloudflare zone file import/upload - it is normal for Cloudflare to show error when reading the SOA record from the zone file + # - All other records are still imported normally, This happens with an unmodified exported zone files as well - SOA is a backend setting in Cloudflare + # Files with the same name in ITGlue on a flex asset are not unique, revision history would only show the newest file, + # - Zone files now have unique filename via utc timestamp, revision history keeps copies of each file + # Running the sync command automatically creates the flex asset type if it does not exist + # Related items tagging + # Lowered Cloudflare request buffer + # Formatting + minor improvements + # Supported PSEditions # CompatiblePSEditions = @() @@ -24,16 +36,16 @@ Author = 'Jeremy Colby' # Company or vendor of this module - CompanyName = '' + # CompanyName = '' # Copyright statement for this module - Copyright = '(c) 2018 Jeremy Colby. All rights reserved.' + Copyright = '(c) 2019 Jeremy Colby. All rights reserved.' # Description of the functionality provided by this module - # Description = '' + Description = 'Sync Cloudflare DNS Zones to ITGlue Client Organizations as Flex Assets' # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '5.0' #New-TemporaryFile seems like only incompatiblity with 4.0 + PowerShellVersion = '5.0' # New-TemporaryFile seems like only incompatiblity with 4.0 # Name of the Windows PowerShell host required by this module # PowerShellHostName = '' @@ -69,19 +81,16 @@ NestedModules = 'Private\CloudflareWebRequest.ps1', 'Private\CloudflareZoneData.ps1', 'Private\ITGlueWebRequest.ps1', + 'Private\New-CloudflareITGlueFlexAssetType.ps1', 'Public\CloudflareITGlueAPIAuth.ps1', - 'Public\New-CloudflareITGlueFlexAssetType.ps1', 'Public\Sync-CloudflareITGlueFlexibleAssets.ps1' - # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = 'Add-CloudflareITGlueAPIAuth', 'Get-CloudflareITGlueAPIAuth', 'Remove-CloudflareITGlueAPIAuth', - 'New-CloudflareITGlueFlexAssetType', 'Sync-CloudflareITGlueFlexibleAssets' - # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psm1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psm1 index 6ff9e82..64eac4a 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psm1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/CloudflareITGlue.psm1 @@ -8,4 +8,6 @@ if (Test-Path "$ModuleBase\$env:username.auth") { $Global:ITGlueAPIKey = ($Auth.ITGlueAPIKey | ConvertTo-SecureString) } +$Global:CFITGLog = $null + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareWebRequest.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareWebRequest.ps1 index 04b556b..edaaad7 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareWebRequest.ps1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareWebRequest.ps1 @@ -31,13 +31,12 @@ function New-CloudflareWebRequest { 'Content-Type' = 'application/json' } } - if ($Body) {$RequestParams.Body = $Body} + if ($Body) { $RequestParams.Body = $Body } try { - Start-Sleep -Milliseconds 275 $Request = Invoke-RestMethod @RequestParams - Start-Sleep -Milliseconds 275 - #RateLimit = 1200 requests/5 min but still getting gateway timeouts @ 300ms+ + Start-Sleep -Milliseconds 325 + # RateLimit: 1200/5 min if ($PageNumber -lt $Request.result_info.total_pages) { $PageNumber++ @@ -49,6 +48,10 @@ function New-CloudflareWebRequest { } catch { Write-Warning "Something went wrong with Cloudflare request:`n$_" + if ($CFITGLog) { + "[CF Request: $Endpoint]$(Get-Date -Format G): $_" | Out-File $CFITGLog -Append + } + $APIKey = $null $RequestParams = $null } diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 index f644de7..33e2971 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 @@ -4,34 +4,41 @@ function Get-CloudflareZoneData { [Parameter(Mandatory = $true)][pscustomobject]$ITGMatch ) - $Timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-M-d HH:mm:ss") + $DateUTC = (Get-Date).ToUniversalTime().ToString('yyyy-M-d') $AccountId = New-CloudflareWebRequest -Endpoint 'accounts' | ForEach-Object result | ForEach-Object id $ZoneInfo = New-CloudflareWebRequest -Endpoint "zones/$ZoneId" $ZoneRecords = New-CloudflareWebRequest -Endpoint "zones/$ZoneId/dns_records" if ($ZoneRecords.result_info.count -eq 0) { Write-Host "$($ZoneInfo.result.name): Empty Zone Detected" -ForegroundColor Yellow + if ($CFITGLog) { + "[CF]$(Get-Date -Format G): Empty Zone Detected - $($ZoneInfo.result.name)" | Out-File $CFITGLog -Append + } break } $ZoneFileData = New-CloudflareWebRequest -Endpoint "zones/$ZoneId/dns_records/export" $ZoneFileData = $ZoneFileData.Replace( - "@ 3600 IN SOA $($ZoneInfo.result.name). root.$($ZoneInfo.result.name). (", - "@ 3600 IN SOA $($ZoneInfo.result.name_servers[0]). $((($CloudflareAPIEmail -split '@')[0]).Replace('.','\.') + '.'+ ($CloudflareAPIEmail -split '@')[1]). (" + ';; SOA Record', + "`$ORIGIN $($ZoneInfo.result.name).`n`n;; SOA Record" + ) + $ZoneFileData = $ZoneFileData.Replace( + "SOA`t$($ZoneInfo.result.name). root.$($ZoneInfo.result.name).", + "SOA`t$($ZoneInfo.result.name_servers[0]). $((($CloudflareAPIEmail -split '@')[0]).Replace('.','\.') + '.'+ ($CloudflareAPIEmail -split '@')[1])." ) $ZoneFileData = $ZoneFileData.Replace( - ";; NS Records (YOU MUST CHANGE THIS)`n$($ZoneInfo.result.name). 1 IN NS " + 'REPLACE&ME$WITH^YOUR@NAMESERVER.', - ";; NS Records`n$($ZoneInfo.result.name). 1 IN NS $($ZoneInfo.result.name_servers[0]).`n$($ZoneInfo.result.name). 1 IN NS $($ZoneInfo.result.name_servers[1])." + ';; A Records', + ";; NS Records`n$($ZoneInfo.result.name). 1 IN NS $($ZoneInfo.result.name_servers[0]).`n$($ZoneInfo.result.name). 1 IN NS $($ZoneInfo.result.name_servers[1]).`n`n;; A Records" ) $ZoneFileData = $ZoneFileData.Replace( ';; -- update the SOA record with the correct authoritative name server', - ";; -- update the SOA record with the correct authoritative name server`n;; ** CloudflareITGlue Module: Updated $($Timestamp)" + ";; -- update the SOA record with the correct authoritative name server`n;; ** CloudflareITGlue Module: Updated $($DateUTC)" ) $ZoneFileData = $ZoneFileData.Replace( ';; -- update the SOA record with the contact e-mail address information', - ";; -- update the SOA record with the contact e-mail address information`n;; ** CloudflareITGlue Module: Updated $($Timestamp)" + ";; -- update the SOA record with the contact e-mail address information`n;; ** CloudflareITGlue Module: Updated $($DateUTC)" ) $ZoneFileData = $ZoneFileData.Replace( ';; -- update the NS record(s) with the authoritative name servers for this domain.', - ";; -- update the NS record(s) with the authoritative name servers for this domain.`n;; ** CloudflareITGlue Module: Updated $($Timestamp)" + ";; -- update the NS record(s) with the authoritative name servers for this domain.`n;; ** CloudflareITGlue Module: Updated $($DateUTC)`n;; ** CloudflareITGlue Module: Added `$ORIGIN directive" ) $RecordsHtml = '
@@ -50,8 +57,8 @@ function Get-CloudflareZoneData { Modified ' + - $(foreach ($Record in $ZoneRecords.result) { - " + $(foreach ($Record in $ZoneRecords.result) { + " $($Record.type) $($Record.name) $($Record.content) @@ -60,14 +67,14 @@ function Get-CloudflareZoneData { $($Record.proxied) $(($Record.modified_on.Replace('T', ' ') -split '\.')[0]) " - }) + - ' + }) + + '
' $ZoneData = [ordered]@{ Name = $ZoneInfo.result.name - SyncDate = $Timestamp + SyncDate = $DateUTC CfNameServers = $ZoneInfo.result.name_servers Status = $ZoneInfo.result.status ZoneFileData = $ZoneFileData @@ -79,11 +86,12 @@ function Get-CloudflareZoneData { } function Get-CloudflareZoneDataArray { + $Progress = 0 + Write-Progress -Activity 'CloudflareAPI' -Status 'Getting Zone Data' -PercentComplete 0 -Id 1 $ZoneDataArray = @() $AllZones = New-CloudflareWebRequest -Endpoint 'zones' [pscustomobject]$ITGDomains = New-ITGlueWebRequest -Endpoint 'domains' | ForEach-Object data - $Progress = 0 - + foreach ($Zone in $AllZones.result) { Write-Progress -Activity 'CloudflareAPI' -Status 'Getting Zone Data' -CurrentOperation $Zone.name -PercentComplete ($Progress / ($AllZones.result | Measure-Object | ForEach-Object count) * 100) -Id 1 @@ -97,7 +105,7 @@ function Get-CloudflareZoneDataArray { $ITGMatches += [pscustomobject]$Match } } - if($ITGMatches){ + if ($ITGMatches) { foreach ($Match in $ITGMatches) { $ZoneData = Get-CloudflareZoneData -ZoneId $Zone.id -ITGMatch $Match if ($ZoneData) { @@ -105,8 +113,11 @@ function Get-CloudflareZoneDataArray { } } } - else{ + else { Write-Host "$($Zone.name): Add to domain tracker" -ForegroundColor Yellow + if ($CFITGLog) { + "[CFITG]$(Get-Date -Format G): $($Zone.name) - Add to ITG domain tracker" | Out-File $CFITGLog -Append + } } $Progress++ } diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/ITGlueWebRequest.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/ITGlueWebRequest.ps1 index 91a64c8..6ea3a37 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/ITGlueWebRequest.ps1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/ITGlueWebRequest.ps1 @@ -30,10 +30,11 @@ function New-ITGlueWebRequest { 'Content-Type' = 'application/vnd.api+json' } } - if ($Body) {$RequestParams.Body = $Body} + if ($Body) { $RequestParams.Body = $Body } try { $Request = Invoke-RestMethod @RequestParams + # RateLimit: 10k/day if ($PageNumber -lt $Request.meta.'total-pages') { $PageNumber++ @@ -45,6 +46,10 @@ function New-ITGlueWebRequest { } catch { Write-Warning "Something went wrong with ITGlue request:`n$_" + if ($CFITGLog) { + "[ITG Request: $Endpoint]$(Get-Date -Format G): $_" | Out-File $CFITGLog -Append + } + $APIKey = $null $RequestParams = $null } diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/New-CloudflareITGlueFlexAssetType.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/New-CloudflareITGlueFlexAssetType.ps1 similarity index 85% rename from Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/New-CloudflareITGlueFlexAssetType.ps1 rename to Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/New-CloudflareITGlueFlexAssetType.ps1 index 8c8a789..706848a 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/New-CloudflareITGlueFlexAssetType.ps1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/New-CloudflareITGlueFlexAssetType.ps1 @@ -68,7 +68,7 @@ function New-CloudflareITGlueFlexAssetType { order = 5 name = 'Zone File' kind = 'Upload' - hint = 'Exported zone file in BIND format. You can upload this to Cloudflare. UTF-8 Encoded, use notepad++ for better viewing.' + hint = 'Exported zone file in BIND format. You can upload this to Cloudflare.' required = $false show_in_list = $false } @@ -77,18 +77,6 @@ function New-CloudflareITGlueFlexAssetType { type = 'flexible_asset_fields' attributes = @{ order = 6 - name = 'Domain Tracker' - kind = 'Tag' - hint = 'Tagged in Domain Tracker' - tag_type = 'Domains' - required = $false - show_in_list = $false - } - }, - @{ - type = 'flexible_asset_fields' - attributes = @{ - order = 7 name = 'DNS Records' kind = 'Textbox' hint = 'Table of DNS records in the zone' diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/CloudflareITGlueAPIAuth.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/CloudflareITGlueAPIAuth.ps1 index e858210..d6fe2c5 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/CloudflareITGlueAPIAuth.ps1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/CloudflareITGlueAPIAuth.ps1 @@ -1,37 +1,36 @@ function Add-CloudflareITGlueAPIAuth { if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) { Write-Host 'Add/removing API Auth require admin access' -ForegroundColor Yellow + break } - else { - [pscredential]$CloudflareCredentials = $Host.UI.PromptForCredential('Cloudflare API Authentication', "User name: Cloudflare Email`r`nPassword: Cloudflare API Key", '', '') - [pscredential]$ITGCredentials = $Host.UI.PromptForCredential('ITGlue API Authentication', 'Password: ITGlue API Key', 'ITGlue', '') - $Global:CloudflareAPIEmail = $CloudflareCredentials.username - $Global:CloudflareAPIKey = $CloudflareCredentials.Password - $Global:ITGlueAPIKey = $ITGCredentials.Password + [pscredential]$CloudflareCredentials = $Host.UI.PromptForCredential('Cloudflare API Authentication', "User name: Cloudflare Email`r`nPassword: Cloudflare API Key", '', '') + [pscredential]$ITGCredentials = $Host.UI.PromptForCredential('ITGlue API Authentication', 'Password: ITGlue API Key', 'ITGlue', '') + $Global:CloudflareAPIEmail = $CloudflareCredentials.username + $Global:CloudflareAPIKey = $CloudflareCredentials.Password + $Global:ITGlueAPIKey = $ITGCredentials.Password - if (!$CloudflareAPIEmail -or !$CloudflareAPIKey -or !$ITGlueAPIKey) { - Write-Host 'Cancelled' -ForegroundColor Yellow - break - } - if ($CloudflareAPIEmail -notmatch "\A[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z") { - Write-Host 'Invalid email address format' -ForegroundColor Yellow - break - } - if (!$CloudflareCredentials.GetNetworkCredential().Password -or !$ITGCredentials.GetNetworkCredential().Password) { - Write-Warning 'API key(s) not entered' - break - } - $Credentials = @{ - CloudflareEmail = $CloudflareAPIEmail - CloudflareAPIKey = ($CloudflareAPIKey | ConvertFrom-SecureString) - ITGlueAPIKey = ($ITGlueAPIKey | ConvertFrom-SecureString) - } - $Auth = @() - $Auth += [pscustomobject]$Credentials - $ModuleBase = Get-Module CloudflareITGlue | ForEach-Object ModuleBase - - $Auth | Export-Csv "$ModuleBase\$env:username.auth" -NoTypeInformation -Force + if (!$CloudflareAPIEmail -or !$CloudflareAPIKey -or !$ITGlueAPIKey) { + Write-Host 'Cancelled' -ForegroundColor Yellow + break + } + if ($CloudflareAPIEmail -notmatch "\A[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z") { + Write-Host 'Invalid email address format' -ForegroundColor Yellow + break } + if (!$CloudflareCredentials.GetNetworkCredential().Password -or !$ITGCredentials.GetNetworkCredential().Password) { + Write-Warning 'API key(s) not entered' + break + } + $Credentials = @{ + CloudflareEmail = $CloudflareAPIEmail + CloudflareAPIKey = ($CloudflareAPIKey | ConvertFrom-SecureString) + ITGlueAPIKey = ($ITGlueAPIKey | ConvertFrom-SecureString) + } + $Auth = @() + $Auth += [pscustomobject]$Credentials + $ModuleBase = Get-Module CloudflareITGlue | ForEach-Object ModuleBase + + $Auth | Export-Csv "$ModuleBase\$env:username.auth" -NoTypeInformation -Force } function Get-CloudflareITGlueAPIAuth { @@ -65,13 +64,12 @@ function Get-CloudflareITGlueAPIAuth { function Remove-CloudflareITGlueAPIAuth { if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) { Write-Host 'Add/removing API Auth require admin access' -ForegroundColor Yellow + break + } + if (Test-Path "$ModuleBase\$env:username.auth") { + Remove-Item "$ModuleBase\$env:username.auth" -Force } else { - if (Test-Path "$ModuleBase\$env:username.auth") { - Remove-Item "$ModuleBase\$env:username.auth" -Force - } - else { - Write-Host 'Not added' -ForegroundColor Yellow - } + Write-Host 'Not added' -ForegroundColor Yellow } } diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/Sync-CloudflareITGlueFlexibleAssets.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/Sync-CloudflareITGlueFlexibleAssets.ps1 index 7290421..2d96f3b 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/Sync-CloudflareITGlueFlexibleAssets.ps1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Public/Sync-CloudflareITGlueFlexibleAssets.ps1 @@ -1,17 +1,38 @@ function Sync-CloudflareITGlueFlexibleAssets { param( - [string]$FlexAssetType = 'Cloudflare DNS' + [string]$FlexAssetType = 'Cloudflare DNS', + [string]$Log ) - $Progress = 0 + + if ($Log) { + if (Test-Path $Log) { + $Global:CFITGLog = $Log + } + else { + New-Item -ItemType File -Path $Log -ErrorAction Ignore | Out-Null + if (Test-Path $Log) { + $Global:CFITGLog = $Log + } + else { + Write-Warning "Unable to create log file: $Log - Invalid path or access denied" + return + } + } + } + $ZoneDataArray = Get-CloudflareZoneDataArray - $FlexAssetTypeId = New-ITGlueWebRequest -Endpoint 'flexible_asset_types' -Method 'GET' | ForEach-Object data | Where-Object {$_.attributes.name -eq $FlexAssetType} | ForEach-Object id + $FlexAssetTypeId = New-ITGlueWebRequest -Endpoint 'flexible_asset_types' -Method 'GET' | ForEach-Object data | Where-Object { $_.attributes.name -eq $FlexAssetType } | ForEach-Object id + if (!$FlexAssetTypeId) { + New-CloudflareITGlueFlexAssetType -Name $FlexAssetType | Out-Null + $FlexAssetTypeId = New-ITGlueWebRequest -Endpoint 'flexible_asset_types' -Method 'GET' | ForEach-Object data | Where-Object { $_.attributes.name -eq $FlexAssetType } | ForEach-Object id + } foreach ($ZoneData in $ZoneDataArray) { - Write-Progress -Activity 'ITGlueAPI' -Status 'Syncing Flexible Assets' -CurrentOperation $ZoneData.name -PercentComplete ($Progress / ($ZoneDataArray | Measure-Object | foreach-object count) * 100) -Id 2 + Write-Progress -Activity 'ITGlueAPI' -Status 'Syncing Flexible Assets' -CurrentOperation $ZoneData.name -PercentComplete ($Progress / ($ZoneDataArray | Measure-Object | ForEach-Object count) * 100) -Id 2 $TempFile = New-TemporaryFile - $ZoneData.ZoneFileData | Out-file $TempFile -Force -Encoding utf8 + $ZoneData.ZoneFileData | Out-file $TempFile -Force -Encoding ascii $Base64ZoneFile = ([System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($TempFile))) Remove-Item $TempFile -Force $Body = @{ @@ -21,16 +42,15 @@ function Sync-CloudflareITGlueFlexibleAssets { 'organization-id' = $ZoneData.ITGOrg 'flexible-asset-type-id' = $FlexAssetTypeId 'traits' = @{ - 'name' = $ZoneData.Name - 'last-sync' = $ZoneData.SyncDate - 'nameservers' = $ZoneData.CfNameServers -join '
' - 'status' = $ZoneData.Status - 'zone-file' = @{ + 'name' = $ZoneData.Name + 'last-sync' = $ZoneData.SyncDate + 'nameservers' = $ZoneData.CfNameServers -join '
' + 'status' = $ZoneData.Status + 'zone-file' = @{ 'content' = $Base64ZoneFile - 'file_name' = "$($ZoneData.Name).txt" + 'file_name' = "$($ZoneData.Name)_$((Get-Date).ToUniversalTime() | Get-Date -Format "yyyy-MM-ddTHHmmssK").txt" } - 'dns-records' = $ZoneData.RecordsHtml - 'domain-tracker' = $ZoneData.DomainTracker + 'dns-records' = $ZoneData.RecordsHtml } } } @@ -45,11 +65,47 @@ function Sync-CloudflareITGlueFlexibleAssets { } } if ($PatchId) { - New-ITGlueWebRequest -Endpoint "flexible_assets/$PatchId" -Method 'PATCH' -Body $Body + try { + $FlexAssetId = New-ITGlueWebRequest -Endpoint "flexible_assets/$PatchId" -Method 'PATCH' -Body $Body + if ($CFITGLog) { + "[ITG]$(Get-Date -Format G): Updating $($ZoneData.Name)" | Out-File $CFITGLog -Append + } + } + catch { + Write-Warning "Something went wrong updating $($ZoneData.Name)`n$_" + if ($CFITGLog) { + "[ITG]$(Get-Date -Format G): Something went wrong updating $($ZoneData.Name)`n$_" | Out-File $CFITGLog -Append + } + continue + } } else { - New-ITGlueWebRequest -Endpoint 'flexible_assets' -Method 'POST' -Body $Body + try { + $FlexAssetId = New-ITGlueWebRequest -Endpoint 'flexible_assets' -Method 'POST' -Body $Body + if ($CFITGLog) { + "[ITG]$(Get-Date -Format G): Creating $($ZoneData.Name)" | Out-File $CFITGLog -Append + } + } + catch { + Write-Warning "Something went wrong creating $($ZoneData.Name)`n$_" + if ($CFITGLog) { + "[ITG]$(Get-Date -Format G): Something went wrong creating $($ZoneData.Name)`n$_" | Out-File $CFITGLog -Append + } + continue + } } + $TagBody = @{ + data = @{ + type = 'related_items' + attributes = @{ + 'destination_id' = $ZoneData.DomainTracker + 'destination_type' = 'Domain' + } + } + } + $TagBody = $TagBody | ConvertTo-Json -Depth 4 + New-ITGlueWebRequest -Endpoint "flexible_assets/$($FlexAssetId.data.id)/relationships/related_items" -Method POST -Body $TagBody | Out-Null + $Progress++ } Write-Progress -Activity 'ITGlueAPI' -Status 'Syncing Flexible Assets' -PercentComplete 100 -Id 2 From 7fae31cfb9dd354145edfe2f506ffb1691fe0943 Mon Sep 17 00:00:00 2001 From: Jeremy Colby Date: Thu, 27 Jun 2019 14:30:51 -0700 Subject: [PATCH 03/10] 1.2.0 --- Cloudflare ITGlue Powershell Module/readme.md | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/Cloudflare ITGlue Powershell Module/readme.md b/Cloudflare ITGlue Powershell Module/readme.md index 109d813..2ab5808 100644 --- a/Cloudflare ITGlue Powershell Module/readme.md +++ b/Cloudflare ITGlue Powershell Module/readme.md @@ -4,16 +4,16 @@ - Sync Cloudflare DNS Zones to ITGlue Client Organizations as Flex Assets -![screenshot](https://user-images.githubusercontent.com/43423017/48573233-6e7f4700-e8c0-11e8-8dd1-793e06620e96.png) +![screenshot](https://user-images.githubusercontent.com/43423017/60233728-61630700-9856-11e9-899c-54178c746463.png) >**Name:** Name of the Cloudflare DNS Zone ->**Last Sync:** Timestamp when flex asset is created/updated +>**Last Sync:** UTC datestamp >**Nameservers:** Nameservers designated by Cloudflare >**Status:** Status of the Cloudflare DNS Zone >**Zone File:** BIND format zone file ->**Domain Tracker:** Domain Tracker Tag >**DNS Records:** Table of all DNS records in the zone and a link to the zone page in Cloudflare ->**Revisions:** Flex assets contain revision history by nature +>**Related Items:** Domain Tracker Tag +>**Revisions:** Flex assets contain revision history by nature (Cloudflare does not!) ## How it works @@ -24,7 +24,6 @@ [Installing the module](#installing-the-module) [API Authorization](#api-authorization) -[Creating the ITGlue Flex Asset Type](#creating-the-itglue-flex-asset-type) ### Installing the module @@ -67,26 +66,20 @@ Remove-CloudflareITGlueAPIAuth >Use these to view/delete the auth that's been entered. >API keys are not shown in full. Removal requires elevated permissions to delete file. -### Creating the ITGlue Flex Asset Type - -```powershell -New-CloudflareITGlueFlexAssetType -``` - ->This will create a new Flex Asset Type in ITGlue called **Cloudflare DNS**. ->Customize your ITGlue sidebar in the **Account > Settings > General > Customize Sidebar** section. ->If you need to use a different name there is an optional parameter: ->`New-CloudflareITGlueFlexAssetType -Name 'My Cloudflare DNS'` - ## Usage ```powershell Sync-CloudflareITGlueFlexibleAssets ``` ->This command will match Cloudflare zones to ITGlue orgs using the Domain Tracker then sync the zones as flex assets to their respective organizations. ->Cloudflare zones that are not in the Domain Tracker will be output to the console. ->If you used a custom name for the flex asset type, you'll also need to pass it to the sync command via the optional FlexAssetType parameter: +>This command will create a new flex asset type in ITGlue called Cloudflare DNS. +>It will then match Cloudflare zones to ITGlue orgs using the Domain Tracker and sync the zones to their respective ITGlue organizations. +>Cloudflare zones that are not in the Domain Tracker will be output to the console and log file. +> +>There is optional logging functionality: +>`Sync-CloudflareITGlueFlexibleAssets -Log 'C:\Temp\cfitg.log'` +> +>You can use a custom name for the flex asset type via the optional FlexAssetType parameter: >`Sync-CloudflareITGlueFlexibleAssets -FlexAssetType 'My Cloudflare DNS'` Set this up to run at an interval of your choosing however you like. @@ -95,11 +88,11 @@ Set this up to run at an interval of your choosing however you like. >```powershell >$Action = New-ScheduledTaskAction -Execute 'Powershell.exe' ` -> -Argument '-NoProfile -WindowStyle Hidden -Command "& Sync-CloudflareITGlueFlexibleAssets"' +> -Argument '-NoProfile -WindowStyle Hidden -Command "& Sync-CloudflareITGlueFlexibleAssets -Log C:\Temp\cfitg.log"' >$Trigger = New-ScheduledTaskTrigger -Daily -At 8am ->$Principal = New-ScheduledTaskPrincipal -UserID '%username%' -LogonType S4U +>$Principal = New-ScheduledTaskPrincipal -UserID '%USERNAME%' -LogonType S4U >Register-ScheduledTask -TaskName 'Sync zones' -Action $Action -Trigger $Trigger -Principal $Principal -># Be sure you've added auth info for %username% +># Be sure you've added auth info for %USERNAME% >``` ## References @@ -107,5 +100,8 @@ Set this up to run at an interval of your choosing however you like. [Invoke-RestMethod Documentation](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod/) [ITGlue API Documentation](https://api.itglue.com/developer/) [Cloudflare API Documentation](https://api.cloudflare.com/) ->On Cloudflare Rate Limiting: "The Cloudflare API sets a maximum of 1,200 requests in a five minute period." ->You may still see the odd gateway timeout even though the rate limit is accounted for. + +### ITGlue Contest Submission Info + +[IT Glue's API Contest](https://www.itglue.com/api-contest/) +Submitted by: Jeremy Colby, [Nucleus Networks](https://yournucleus.ca/), June 27 2019 From 99275038e7c28941ba57e15678366ba05c2f9c26 Mon Sep 17 00:00:00 2001 From: jeremycolby <43423017+jeremycolby@users.noreply.github.com> Date: Thu, 27 Jun 2019 21:31:53 -0700 Subject: [PATCH 04/10] 1.2.0 Update readme --- Cloudflare ITGlue Powershell Module/readme.md | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/Cloudflare ITGlue Powershell Module/readme.md b/Cloudflare ITGlue Powershell Module/readme.md index 2ab5808..5a516b6 100644 --- a/Cloudflare ITGlue Powershell Module/readme.md +++ b/Cloudflare ITGlue Powershell Module/readme.md @@ -15,22 +15,19 @@ >**Related Items:** Domain Tracker Tag >**Revisions:** Flex assets contain revision history by nature (Cloudflare does not!) -## How it works - -- Configure -- Schedule the sync command to run at a desired interval +- [Installing the module](#Installing-the-module) +- [API authorization](#API-Authorization) +- [Usage](#Usage) +- [Version info](#Version-History) ## Configuration -[Installing the module](#installing-the-module) -[API Authorization](#api-authorization) - ### Installing the module -Copy the CloudflareITGlue module folder into the Powershell module directory +Copy the CloudflareITGlue module folder into the Powershell module directory, default path: >`C:\Program Files\WindowsPowerShell\Modules\CloudflareITGlue` -### API Authorization +### API authorization #### Obtain API keys @@ -83,8 +80,7 @@ Sync-CloudflareITGlueFlexibleAssets >`Sync-CloudflareITGlueFlexibleAssets -FlexAssetType 'My Cloudflare DNS'` Set this up to run at an interval of your choosing however you like. - -- Heres a quick Powershell script you can use to create a scheduled task: +Heres a quick Powershell script you can use to create a scheduled task: >```powershell >$Action = New-ScheduledTaskAction -Execute 'Powershell.exe' ` @@ -95,13 +91,29 @@ Set this up to run at an interval of your choosing however you like. ># Be sure you've added auth info for %USERNAME% >``` +## Version info + +- 1.0 + - Dns zones are matched to ITGlue orgs via custom txt record mechanism +- 1.1 + - Dns zones are matched to ITGlue orgs automatically via Domain tracker +- 1.2 + - Full logging functionality + - Files with the same name in ITGlue on a flex asset do not appear to be unique, revision history only shows the latest file, Zone files now have a unique filename via utc timestamp and revision history now keeps copies of each file + - Running the sync command automatically creates the flex asset type if it does not exist + - Related items tagging + - Lowered Cloudflare request buffer + - Re: Zone file export format + - Cloudflare export format changed, modified to account for this + - Upon import/upload, it is normal for Cloudflare to show an error when reading the SOA record. All records are imported correctly and the SOA is not configurable by Cloudflare. The same behavior happens with an unmodified zone file export + ## References [Invoke-RestMethod Documentation](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod/) [ITGlue API Documentation](https://api.itglue.com/developer/) [Cloudflare API Documentation](https://api.cloudflare.com/) -### ITGlue Contest Submission Info +### ITGlue contest submission info [IT Glue's API Contest](https://www.itglue.com/api-contest/) Submitted by: Jeremy Colby, [Nucleus Networks](https://yournucleus.ca/), June 27 2019 From 8e3bfd0c9420a2e719ca5d9695e668b4b3629dbe Mon Sep 17 00:00:00 2001 From: jeremycolby <43423017+jeremycolby@users.noreply.github.com> Date: Thu, 27 Jun 2019 22:04:49 -0700 Subject: [PATCH 05/10] 1.2.0 Formatting --- .../Private/New-CloudflareITGlueFlexAssetType.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/New-CloudflareITGlueFlexAssetType.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/New-CloudflareITGlueFlexAssetType.ps1 index 706848a..c0bef1d 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/New-CloudflareITGlueFlexAssetType.ps1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/New-CloudflareITGlueFlexAssetType.ps1 @@ -91,4 +91,4 @@ function New-CloudflareITGlueFlexAssetType { } $Body = $Body | ConvertTo-Json -Depth 6 New-ITGlueWebRequest -Endpoint 'flexible_asset_types' -Method 'POST' -Body $Body -} \ No newline at end of file +} From c3aadbb9617718600d5b342a748cc85a769bf4be Mon Sep 17 00:00:00 2001 From: jeremycolby <43423017+jeremycolby@users.noreply.github.com> Date: Thu, 27 Jun 2019 22:09:08 -0700 Subject: [PATCH 06/10] 1.2.0 Formatting --- .../Private/CloudflareZoneData.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 index 33e2971..01f2123 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 @@ -59,14 +59,14 @@ function Get-CloudflareZoneData { ' + $(foreach ($Record in $ZoneRecords.result) { " - $($Record.type) - $($Record.name) - $($Record.content) - $($Record.priority) - $(if ($Record.ttl -eq 1){'Auto'}else{$Record.ttl}) - $($Record.proxied) - $(($Record.modified_on.Replace('T', ' ') -split '\.')[0]) - " + $($Record.type) + $($Record.name) + $($Record.content) + $($Record.priority) + $(if ($Record.ttl -eq 1){'Auto'}else{$Record.ttl}) + $($Record.proxied) + $(($Record.modified_on.Replace('T', ' ') -split '\.')[0]) + " }) + ' From c82ba4e135d184cec1c051acfe099104ca68b2b7 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Thu, 27 Jun 2019 23:50:21 -0700 Subject: [PATCH 07/10] 1.2.0 Formatting --- .../Private/CloudflareZoneData.ps1 | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 index 01f2123..8071ac7 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareZoneData.ps1 @@ -43,9 +43,8 @@ function Get-CloudflareZoneData { $RecordsHtml = '

- - Open in Cloudflare -

+ Open in Cloudflare

+ @@ -57,18 +56,18 @@ function Get-CloudflareZoneData { ' + - $(foreach ($Record in $ZoneRecords.result) { - " - - - - - - - - " - }) + - ' + $(foreach ($Record in $ZoneRecords.result) { + " + + + + + + + + " + }) + + '
TypeModified
$($Record.type)$($Record.name)$($Record.content)$($Record.priority)$(if ($Record.ttl -eq 1){'Auto'}else{$Record.ttl})$($Record.proxied)$(($Record.modified_on.Replace('T', ' ') -split '\.')[0])
$($Record.type)$($Record.name)$($Record.content)$($Record.priority)$(if ($Record.ttl -eq 1){'Auto'}else{$Record.ttl})$($Record.proxied)$(($Record.modified_on.Replace('T', ' ') -split '\.')[0])
' From 208540dfe3d966c3f55b7bfed8f71e21e46836c3 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Fri, 28 Jun 2019 00:11:20 -0700 Subject: [PATCH 08/10] 1.2.0 Update readme --- Cloudflare ITGlue Powershell Module/readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cloudflare ITGlue Powershell Module/readme.md b/Cloudflare ITGlue Powershell Module/readme.md index 5a516b6..6c0a0a6 100644 --- a/Cloudflare ITGlue Powershell Module/readme.md +++ b/Cloudflare ITGlue Powershell Module/readme.md @@ -72,6 +72,7 @@ Sync-CloudflareITGlueFlexibleAssets >This command will create a new flex asset type in ITGlue called Cloudflare DNS. >It will then match Cloudflare zones to ITGlue orgs using the Domain Tracker and sync the zones to their respective ITGlue organizations. >Cloudflare zones that are not in the Domain Tracker will be output to the console and log file. +>Set this up to run at an interval of your choosing however you like. > >There is optional logging functionality: >`Sync-CloudflareITGlueFlexibleAssets -Log 'C:\Temp\cfitg.log'` @@ -79,8 +80,7 @@ Sync-CloudflareITGlueFlexibleAssets >You can use a custom name for the flex asset type via the optional FlexAssetType parameter: >`Sync-CloudflareITGlueFlexibleAssets -FlexAssetType 'My Cloudflare DNS'` -Set this up to run at an interval of your choosing however you like. -Heres a quick Powershell script you can use to create a scheduled task: +- Heres a quick Powershell script you can use to create a scheduled task: >```powershell >$Action = New-ScheduledTaskAction -Execute 'Powershell.exe' ` @@ -116,4 +116,4 @@ Heres a quick Powershell script you can use to create a scheduled task: ### ITGlue contest submission info [IT Glue's API Contest](https://www.itglue.com/api-contest/) -Submitted by: Jeremy Colby, [Nucleus Networks](https://yournucleus.ca/), June 27 2019 +Submitted by: Jeremy Colby, [Nucleus Networks](https://yournucleus.ca/), June 28 2019 From 75cb632d67cba5127b08e93085a8f4bf4d752cca Mon Sep 17 00:00:00 2001 From: Jeremy Date: Fri, 28 Jun 2019 21:51:30 -0700 Subject: [PATCH 09/10] 1.2.0 write to log if no auth --- .../CloudflareITGlue/Private/CloudflareWebRequest.ps1 | 9 +++++++-- .../CloudflareITGlue/Private/ITGlueWebRequest.ps1 | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareWebRequest.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareWebRequest.ps1 index edaaad7..b881ffa 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareWebRequest.ps1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/CloudflareWebRequest.ps1 @@ -12,13 +12,18 @@ function New-CloudflareWebRequest { $APIKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($CloudflareAPIKey)) } catch { - Write-Warning 'New-CloudflareWebRequest: Unable to decrypt auth info' - Write-Warning 'Run Add-CloudflareITGlueAPIAuth to re-add' + Write-Warning 'Unable to decrypt auth info, run Add-CloudflareITGlueAPIAuth to re-add' + if ($CFITGLog) { + "[CF Request]$(Get-Date -Format G): Unable to decrypt auth info, run Add-CloudflareITGlueAPIAuth to re-add" | Out-File $CFITGLog -Append + } break } } else { Write-Warning 'Run Add-CloudflareITGlueAPIAuth to add authorization info' + if ($CFITGLog) { + "[CF Request]$(Get-Date -Format G): Run Add-CloudflareITGlueAPIAuth to add authorization info" | Out-File $CFITGLog -Append + } break } diff --git a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/ITGlueWebRequest.ps1 b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/ITGlueWebRequest.ps1 index 6ea3a37..353dea5 100644 --- a/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/ITGlueWebRequest.ps1 +++ b/Cloudflare ITGlue Powershell Module/CloudflareITGlue/Private/ITGlueWebRequest.ps1 @@ -12,13 +12,18 @@ function New-ITGlueWebRequest { $APIKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ITGlueAPIKey)) } catch { - Write-Warning 'New-ITGlueWebRequest: Unable to decrypt auth info' - Write-Warning 'Run Add-CloudflareITGlueAPIAuth to re-add' + Write-Warning 'Unable to decrypt auth info, run Add-CloudflareITGlueAPIAuth to re-add' + if ($CFITGLog) { + "[ITG Request]$(Get-Date -Format G): Unable to decrypt auth info, run Add-CloudflareITGlueAPIAuth to re-add" | Out-File $CFITGLog -Append + } break } } else { Write-Warning 'Run Add-CloudflareITGlueAPIAuth to add authorization info' + if ($CFITGLog) { + "[ITG Request]$(Get-Date -Format G): Run Add-CloudflareITGlueAPIAuth to add authorization info" | Out-File $CFITGLog -Append + } break } From bb86be0f39295463edeec36724dc661d7c661b74 Mon Sep 17 00:00:00 2001 From: jeremycolby <43423017+jeremycolby@users.noreply.github.com> Date: Fri, 28 Jun 2019 22:02:31 -0700 Subject: [PATCH 10/10] 1.2.0 update readme --- Cloudflare ITGlue Powershell Module/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cloudflare ITGlue Powershell Module/readme.md b/Cloudflare ITGlue Powershell Module/readme.md index 6c0a0a6..2bc7dbf 100644 --- a/Cloudflare ITGlue Powershell Module/readme.md +++ b/Cloudflare ITGlue Powershell Module/readme.md @@ -116,4 +116,4 @@ Sync-CloudflareITGlueFlexibleAssets ### ITGlue contest submission info [IT Glue's API Contest](https://www.itglue.com/api-contest/) -Submitted by: Jeremy Colby, [Nucleus Networks](https://yournucleus.ca/), June 28 2019 +Submitted by: Jeremy Colby <>, [Nucleus Networks](https://yournucleus.ca/), June 28 2019