121 lines
5.0 KiB
Python
121 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
# ------------------------------------------------------------
|
|
# Copyright 2021 The Dapr Authors
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ------------------------------------------------------------
|
|
|
|
import argparse
|
|
import ipaddress
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import urllib.request
|
|
|
|
def parseArgs():
|
|
abscwd = os.path.abspath(os.getcwd())
|
|
arg_parser = argparse.ArgumentParser(description='Generates the IP ranges based on CIDRs for GitHub Actions from meta API.')
|
|
arg_parser.add_argument('--outpath', type=str, default=abscwd, help='Optional. Full path to write the JSON output to.')
|
|
arg_parser.add_argument('--sqlserver', type=str, help='Name of the Azure SQL server to update firewall rules of. Required for deployment.')
|
|
arg_parser.add_argument('--resource-group', type=str, help='Resouce group containing the target Azure SQL server. Required for deployment.')
|
|
arg_parser.add_argument('--no-deployment', action='store_true', help='Specify this flag to generate the ARM template without deploying it.')
|
|
args = arg_parser.parse_args()
|
|
|
|
if not args.no_deployment:
|
|
is_missing_args = False
|
|
if not args.sqlserver:
|
|
print('ERROR: the following argument is required: --sqlserver')
|
|
is_missing_args = True
|
|
if not args.resource_group:
|
|
print('ERROR: the following argument is required: --resource-group')
|
|
is_missing_args = True
|
|
if is_missing_args:
|
|
arg_parser.print_help()
|
|
sys.exit(-1)
|
|
|
|
print('Arguments parsed: {}'.format(args))
|
|
return args
|
|
|
|
def getResponse(url):
|
|
operUrl = urllib.request.urlopen(url)
|
|
if(operUrl.getcode()==200):
|
|
data = operUrl.read()
|
|
jsonData = json.loads(data)
|
|
else:
|
|
print('ERROR: failed to receive data', operUrl.getcode())
|
|
return jsonData
|
|
|
|
def writeAllowedIPRangesJSON(outpath):
|
|
url = 'https://api.github.com/meta'
|
|
jsonData = getResponse(url)
|
|
|
|
ipRanges = []
|
|
prevStart = ''
|
|
prevEnd = ''
|
|
|
|
# Iterate the public IP CIDRs used to run GitHub Actions, and convert them
|
|
# into IP ranges for test SQL server firewall access.
|
|
for cidr in jsonData['actions']:
|
|
net = ipaddress.ip_network(cidr)
|
|
# SQL server firewall only supports up to 128 firewall rules.
|
|
# As a first cut, exclude all IPv6 addresses.
|
|
if net.version == 4:
|
|
start = net[0]
|
|
end = net[-1]
|
|
# print(f'{cidr} --> [{start}, {end}]')
|
|
|
|
if prevStart == '':
|
|
prevStart = start
|
|
if prevEnd == '':
|
|
prevEnd = end
|
|
elif prevEnd + 65536 > start:
|
|
# If the current IP range is within the granularity of a /16
|
|
# subnet mask to the previous range, coalesce them into one.
|
|
# This is necessary to get the number of rules down to ~100.
|
|
prevEnd = end
|
|
else:
|
|
ipRange = [str(prevStart), str(prevEnd)]
|
|
ipRanges.append(ipRange)
|
|
prevStart = start
|
|
prevEnd = end
|
|
|
|
if prevStart != '' and prevEnd != '':
|
|
ipRange = [str(prevStart), str(prevEnd)]
|
|
ipRanges.append(ipRange)
|
|
|
|
with open(outpath, 'w') as outfile:
|
|
json.dump(ipRanges, outfile)
|
|
|
|
def main():
|
|
args = parseArgs()
|
|
|
|
# Get the GitHub IP Ranges to use as firewall allow-rules from the GitHub meta API
|
|
ipRangesFileName = os.path.join(args.outpath, 'github-ipranges.json')
|
|
writeAllowedIPRangesJSON(ipRangesFileName)
|
|
print(f'INFO: GitHub Actions public IP range rules written {ipRangesFileName}')
|
|
|
|
# Generate the ARM template from bicep to update Azure SQL server firewall rules
|
|
subprocess.call(['az', 'bicep', 'install'])
|
|
firewallTemplateName = os.path.join(args.outpath, 'update-sql-firewall-rules.json')
|
|
subprocess.call(['az', 'bicep', 'build', '--file', 'conf-test-azure-sqlserver-firewall.bicep', '--outfile', firewallTemplateName])
|
|
print(f'INFO: ARM template to update SQL Server firewall rules written to {firewallTemplateName}')
|
|
|
|
# Update the Azure SQL server firewall rules
|
|
if args.no_deployment:
|
|
print(f'INFO: --no-deployment specified, skipping update of SQL server {firewallTemplateName}')
|
|
else:
|
|
subprocess.call(['az', 'deployment', 'group', 'create', '--name', 'UpdateSQLFirewallRules', '--template-file', firewallTemplateName, '--resource-group', args.resource_group, '--parameters', f'sqlServerName={args.sqlserver}', '--parameters', f'ipRanges=@{ipRangesFileName}'])
|
|
|
|
sys.exit(0)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|