def __init__(self, cfg): if cfg: self.cfg = cfg creds = auth(self.cfg) try: if 'project' in cfg['dns']: self.dns = dns.Client(project=self.cfg['dns']['project'], credentials=creds) else: self.dns = dns.Client(credentials=creds) except Exception as e: click.echo("Login failed: [{}]".format(e)) raise click.Abort()
def update_dns(zone_name: str, dns_name: str, ttl: int = 60, force_update: bool = False, project_id: Optional[str] = None): """ Updates a GCP Cloud DNS zone with the host's current IP as it appears from the internet :param zone_name: the name of the zone in your GCP project :param dns_name: the DNS name, e.g. `www.example.com`, to update :param ttl: the ttl of the new record :param force_update: if True, the records will be updated even if they are not different :param project_id: the GCP project id :return: the applied change set """ if not dns_name.endswith("."): dns_name = "%s." % dns_name if len(resolve_addresses(dns_name).symmetric_difference( my_ip())) == 0 and not force_update: return if project_id: client = dns.Client(project_id) else: client = dns.Client() zone = client.zone(zone_name) addresses = my_ip() ipv4_addresses = set( filter(lambda _ip: len(_ip.split(".")) == 4, addresses)) ipv6_addresses = addresses - ipv4_addresses changes = zone.changes() for record in zone.list_resource_record_sets(): if record.name == dns_name and record.record_type in ("A", "AAAA"): changes.delete_record_set(record) if len(ipv4_addresses) > 0: changes.add_record_set( zone.resource_record_set(dns_name, "A", ttl, list(ipv4_addresses))) if len(ipv6_addresses) > 0: changes.add_record_set( zone.resource_record_set(dns_name, "AAAA", ttl, list(ipv6_addresses))) changes.create() return changes
def update_domain(): client = dns.Client(project=config['project_id']) zone = client.zone(config['zone_name'], config['dns_name']) ip = ipgetter.myip(); changes = zone.changes() records = zone.list_resource_record_sets() for record in records: if record.name == config['dns_name']: if record.record_type == config['record_type'] and record.ttl == config['record_ttl'] and record.rrdatas[0] == ip: #no update needed print('No update needed for ' + config['dns_name'] + ' in zone ' + config['zone_name'] + ' rrdata: ' + record.rrdatas[0]) return else: #delete out of date record bfore adding new print('Delete out of date record set ' + config['dns_name'] + ' in zone ' + config['zone_name'] + ' rrdata: ' + record.rrdatas[0]) record_delete = zone.resource_record_set(record.name, record.record_type, record.ttl, record.rrdatas) changes.delete_record_set(record_delete) break print('Add record set ' + config['dns_name'] + ' in zone ' + config['zone_name'] + ' rrdata: ' + ip) record_set = zone.resource_record_set(config['dns_name'], config['record_type'], config['record_ttl'], [ip,]) changes.add_record_set(record_set) changes.create() # API request while changes.status != 'done': print('Waiting for changes for ' + config['dns_name'] + ' in zone ' + config['zone_name'] + ' rrdata: ' + ip) time.sleep(10) # or whatever interval is appropriate changes.reload() # API request print('Change ' + config['dns_name'] + ' in zone ' + config['zone_name'] + ' rrdata: ' + ip + ' updated')
def delete_dns(self, name, domain): """ :param name: :param domain: :return: """ project = self.project zone = self.zone client = dns.Client(project) domain_name = domain.replace('.', '-') zones = [z for z in client.list_zones() if z.name == domain_name] if not zones: return else: zone = zones[0] # zone = client.zone(domain_name) # # if not zone.exists(): # return entry = "%s.%s." % (name, domain) changes = zone.changes() records = [record for record in zone.list_resource_record_sets() if entry in record.name] if records: for record in records: record_set = zone.resource_record_set(record.name, record.record_type, record.ttl, record.rrdatas) changes.delete_record_set(record_set) changes.create() return {'result': 'success'}
def list_changes(project_id, zone_name): client = dns.Client(project=project_id) zone = client.zone(zone_name) changes = zone.list_changes() return [(change.started, change.status) for change in changes]
async def nfo(self, ctx, sub, ident): """Create a DNS entry from an NFO name sub = DNS prefix, the part before .scgc.xyz ident = NFO Prefix/Identifier, the part before .game.nfoservers.com""" if checks.role_or_permissions( ctx, lambda r: r.name.lower() in ('monkey', 'server manager', 'owner')) is False: return False ip = await self.resolver.query("{}.game.nfoservers.com".format(ident), 'A') client = dns.Client(project=self.settings["project"][0], credentials=self.creds) zone = client.zone(self.settings['zone'][0], self.settings['domain'][0]) record_set = zone.resource_record_set( '{}.{}.'.format(sub, self.settings['domain'][0]), 'A', 60 * 60, [ip[0].host]) changes = zone.changes() changes.add_record_set(record_set) changes.create() while changes.status != 'done': asyncio.sleep(60) changes.reload() self.unk[sub] = {'dns': sub, 'nfo': ident, 'port': 0, 'name': 'unk'} dataIO.save_json(os.path.join("data", "dns", "unk.json"), self.unk) self.settings['last'] = int(datetime.datetime.utcnow().timestamp()) dataIO.save_json(os.path.join("data", "dns", "settings.json"), self.settings) await self.bot.say( "DNS Entry Created. Don't forget to update the server list, !serverlist edit {}" .format(sub))
async def a(self, ctx, sub, ip): """Create or Modify a DNS A Record sub = DNS prefix, the part before .scgc.xyz ip = IP the record should point to""" if checks.role_or_permissions( ctx, lambda r: r.name.lower() in ('monkey', 'server manager', 'owner')) is False: return False client = dns.Client(project=self.settings["project"][0], credentials=self.creds) zone = client.zone(self.settings['zone'][0], self.settings['domain'][0]) record_set = zone.resource_record_set( '{}.{}.'.format(sub, self.settings['domain'][0]), 'A', 60 * 60 * 2, [ip]) changes = zone.changes() changes.add_record_set(record_set) changes.create() while changes.status != 'done': asyncio.sleep(60) changes.reload() self.unk[sub] = {'dns': sub, 'nfo': ip, 'port': 0, 'name': 'unk'} dataIO.save_json(os.path.join("data", "dns", "unk.json"), self.unk) self.settings['last'] = int(datetime.datetime.utcnow().timestamp()) dataIO.save_json(os.path.join("data", "dns", "settings.json"), self.settings) await self.bot.say( "DNS Entry Created. Don't forget to update the server list if needed, !serverlist edit {}" .format(sub))
def update_dns(hostname, myip): client = dns.Client(project=project_id) zone = client.zone(dns_zone) record_old = None if not zone.exists(): print(f"Zone with name: {dns_zone} does not exist!") return False if hostname == DNS_HOSTNAME: hostname = hostname + "." else: print(f"Provided Hostname: {hostname} does not equal {DNS_HOSTNAME}!") return False resource_record_sets = zone.list_resource_record_sets() for record in resource_record_sets: if record.name == hostname and record.record_type == 'A': record_old = record if record_old.rrdatas[0] == myip: print( f"Record {hostname} with IP {myip} already exists -> skipping" ) return True else: print( f"Record {hostname} does exists with different IP -> updating" ) record_new = zone.resource_record_set(hostname, 'A', dns_ttl, [ myip, ]) changes = zone.changes() if not record_old is None: changes.delete_record_set(record_old) changes.add_record_set(record_new) changes.create() return True
def list_resource_records(project_id, zone_name): client = dns.Client(project=project_id) zone = client.zone(zone_name) records = zone.list_resource_record_sets() return [(record.name, record.record_type, record.ttl, record.rrdatas) for record in records]
def get_dns_zone(dns_project, dns_zone, dns_domain): zone = dns.Client(project=dns_project).zone(dns_zone, dns_name=dns_domain) if zone.exists(): print('Zone {} exists.'.format(dns_zone)) else: print('Zone {} does not exist.'.format(dns_zone)) raise NameError('Zone {} does not exist.'.format(dns_zone)) return zone
def client(): client = dns.Client(PROJECT) yield client # Delete anything created during the test. for zone in client.list_zones(): zone.delete()
def create_zone(project_id, name, dns_name, description): client = dns.Client(project=project_id) zone = client.zone( name, # examplezonename dns_name=dns_name, # example.com. description=description) zone.create() return zone
def client(cloud_config): client = dns.Client(cloud_config.project) yield client # Delete anything created during the test. for zone in client.list_zones()[0]: zone.delete()
def reserve_dns(self, name, nets=[], domain=None, ip=None, alias=[], force=False): """ :param name: :param nets: :param domain: :param ip: :param alias: :param force: :return: """ net = nets[0] project = self.project zone = self.zone client = dns.Client(project) domain_name = domain.replace('.', '-') common.pprint("Assuming Domain name is %s..." % domain_name, color='green') zones = [z for z in client.list_zones() if z.name == domain_name] if not zones: common.pprint("Domain %s not found" % domain_name, color='red') return {'result': 'failure', 'reason': "Domain not found"} else: zone = zones[0] # zone = client.zone(domain_name) # if not zone.exists(): # common.pprint("Domain %s not found" % domain_name, color='red') # return {'result': 'failure', 'reason': "Domain not found"} entry = "%s.%s." % (name, domain) if ip is None: if isinstance(net, dict): ip = net.get('ip') if ip is None: counter = 0 while counter != 100: ip = self.ip(name) if ip is None: time.sleep(5) print("Waiting 5 seconds to grab ip and create DNS record...") counter += 10 else: break if ip is None: print("Couldn't assign DNS") return changes = zone.changes() record_set = zone.resource_record_set(entry, 'A', 300, [ip]) changes.add_record_set(record_set) if alias: for a in alias: if a == '*': new = '*.%s.%s.' % (name, domain) record_set = zone.resource_record_set(new, 'A', 300, [ip]) else: new = '%s.%s.' % (a, domain) if '.' not in a else '%s.' % a record_set = zone.resource_record_set(new, 'CNAME', 300, [entry]) changes.add_record_set(record_set) changes.create() return {'result': 'success'}
def test_quota(): client = dns.Client() quotas = client.quotas() # check that kind is properly stripped from the resource assert "kind" not in quotas for keyspec in quotas["whitelistedKeySpecs"]: assert "kind" not in keyspec
def get_zone(zone_name, project_id=None): project_id = project_id or os.environ['PROJECT_ID'] client = dns.Client(project=project_id) zone = client.zone(zone_name) if not zone.exists(): raise DNSException(zone_name + " does not exist") return zone
def client_conn(project_id=None): """Create a connection with Google DNS API :param project_id: a project_id of Google Cloud Platform :returns: an object connection of Google DNS """ client = dns.Client(project=project_id) return client
def create_zone(): client = dns.Client(project=config['project_id']) zone = client.zone(config['zone_name'], config['dns_name'], description='Created by rpi-google-cloud-dynamic-dns') if not zone.exists(): # API request zone.create() # API request while not zone(exists): print('Waiting for creation of domain ' + config['dns_name'] + ' in zone ' + config['zone_name'] + ' to complete') time.sleep(10) # or whatever interval is appropriate
def get_zone(project_id, name): client = dns.Client(project=project_id) zone = client.zone(name=name) try: zone.reload() return zone except NotFound: return None
def client(): client = dns.Client(PROJECT) yield client # Delete anything created during the test. for zone in client.list_zones(): try: zone.delete() except NotFound: # May have been in process pass
def __init__(self): print('Initializing...') self.ip = self.get_stored_ip() try: self.config = yaml.load(open(config_path), Loader=yaml.Loader) self.auth = json.load(open(auth_path)) except SyntaxError: exit() # TODO: validate config: # zones: list(dict(name: str, TTL: int, records: list(str, ends-with-dot))] self.client = dns.Client(project=self.auth['project_id'])
def list_resource_records(project_id, zone_name): client = dns.Client(project=project_id) zone = client.zone(zone_name) records, page_token = zone.list_resource_record_sets() while page_token is not None: next_batch, page_token = zone.list_resource_record_sets( page_token=page_token) records.extend(next_batch) return [(record.name, record.record_type, record.ttl, record.rrdatas) for record in records]
def add_acme_challenge(self, zone_name, domain, acme_challenge_token): rrset_name = f'{acme_challenge_record}.{domain}.' print( f'Adding ACME challenge token {acme_challenge_token} to recordset \"{rrset_name}\"...' ) client = dns.Client() zone = client.zone(name=zone_name) records = zone.list_resource_record_sets() acme_rrset = None for r in records: if r.name == rrset_name: print('Existing recordset is...') self._print_rrset(r) acme_rrset = r changes = zone.changes() if acme_rrset is not None: changes.delete_record_set(acme_rrset) if len(acme_rrset.rrdatas ) == 1 and acme_rrset.rrdatas[0] == init_rrdata: new_rrdata = [f'\"{acme_challenge_token}\"'] else: new_rrdata = copy.deepcopy(acme_rrset.rrdatas) new_rrdata.append(f'\"{acme_challenge_token}\"') else: new_rrdata = [f'\"{acme_challenge_token}\"'] record_set = zone.resource_record_set(rrset_name, 'TXT', ttl, new_rrdata) changes.add_record_set(record_set) changes.create() # API request self._wait_for_changes_to_done(changes) print('Change is done.') records = zone.list_resource_record_sets() for r in records: if r.name == rrset_name: print('Existing recordset is...') self._print_rrset(r) if acme_rrset is not None: print( f'Waiting for {acme_rrset.ttl} secs to let cache TTL expire...' ) time.sleep(acme_rrset.ttl) else: print(f'No existing recordset {rrset_name} confirmed.')
def __init__(self, id, project=None, credentials_file=None, *args, **kwargs): if credentials_file: self.gcloud_client = dns.Client.from_service_account_json( credentials_file, project=project) else: self.gcloud_client = dns.Client(project=project) # Logger self.log = getLogger('GoogleCloudProvider[{}]'.format(id)) self.id = id self._gcloud_zones = {} super(GoogleCloudProvider, self).__init__(id, *args, **kwargs)
def cleanup_acme_challenge(self, zone_name, domain): rrset_name = f'{acme_challenge_record}.{domain}.' print( f'Cleaning up ACME challenge token from recordset \"{rrset_name}\"...' ) client = dns.Client() zone = client.zone(name=zone_name) records = zone.list_resource_record_sets() acme_rrset = None for r in records: if r.name == rrset_name: print('Existing recordset is...') self._print_rrset(r) acme_rrset = r changes = zone.changes() if acme_rrset is not None: changes.delete_record_set(acme_rrset) record_set = zone.resource_record_set(rrset_name, 'TXT', ttl, [init_rrdata]) changes.add_record_set(record_set) changes.create() # API request self._wait_for_changes_to_done(changes) print('Change is done.') records = zone.list_resource_record_sets() for r in records: if r.name == rrset_name: print('Existing recordset is...') self._print_rrset(r) if acme_rrset is not None: print( f'Waiting for {acme_rrset.ttl} secs to let cache TTL expire...' ) time.sleep(acme_rrset.ttl) else: print(f'No existing recordset {rrset_name} confirmed.')
def list_zones(project_id): client = dns.Client(project=project_id) zones = client.list_zones() return [zone.name for zone in zones]
def main(): # You can provide the config file as the first parameter if len(sys.argv) == 2: config_file = sys.argv[1] elif len(sys.argv) > 2: print("Usage: python gcp_ddns.py [path_to_config_file.yaml]") return 1 else: config_file = "ddns-config.yaml" # Read YAML configuration file and set initial parameters for logfile and api key with open(config_file, 'r') as stream: try: config = yaml.safe_load(stream) print(config) if 'api-key' in config: api_key = config['api-key'] else: print(f"api_key must be defined in {config_file}") exit(1) if 'logfile' in config: logfile = config['logfile'] else: print(f"logfile must be defined in {config_file}") exit(1) # iterate through our required config parameters and each host entry in the config file # check that all requisite parameters are included in the file before proceeding. except yaml.YAMLError: print( f"There was an error loading configuration file: {config_file}" ) exit(1) # ensure that the provided credential file exists if not os.path.isfile(api_key): print( "Credential file not found. By default this program checks for ddns-api-key.json in this directory." ) print( "You can specify the path to the credentials as an argument to this script. " ) print("Usage: python gcp_ddns.py [path_to_config_file.json]") return 1 logging.basicConfig( level=logging.DEBUG, filename=logfile, filemode="w", format="%(asctime)s - %(levelname)s - %(message)s", ) # set OS environ for google authentication os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = api_key # setup our objects that will be used to query the Google API # N.B. cache_discover if false. This prevents google module exceptions # This is not a performance critical script, so shouldn't be a problem. service = discovery.build("dns", "v1", cache_discovery=False) # this is the program's main loop. Exit with ctl-c while True: try: for count, config_host in enumerate(config['hosts'], start=1): for key in CONFIG_PARAMS: if key not in config_host: print( f"{key} not found in config file {config_file}. Please ensure it is." ) exit(1) project = config_host["project_id"] managed_zone = config_host["managed_zone"] domain = config_host["domain"] host = config_host["host"] ttl = config_host["ttl"] interval = config_host["interval"] # confirm that the last character of host is a '.'. This is a google requirement if host[-1] != ".": print( f"The host entry in the configuration file must end with a '.', e.g. www.example.com. " ) return 1 # this is where we build our resource record set and what we will use to call the api # further down in the script. request = service.resourceRecordSets().list( project=project, managedZone=managed_zone, name=host) # Use Google's dns.Client to create client object and zone object # Note: Client() will pull the credentials from the os.environ from above try: client = dns.Client(project=project) except authexc.DefaultCredentialsError: logging.error( "Provided credentials failed. Please ensure you have correct credentials." ) return 1 except authexc.GoogleAuthError: logging.error( "Provided credentials failed. Please ensure you have correct credentials." ) return 1 # this is the object which will be sent to Google and queried by us. zone = client.zone(managed_zone, domain) # http get request to fetch our public IP address from ipify.org response = get("https://api.ipify.org?format=json") # check that we got a valid response. If not, sleep for interval and go to the top of the loop if response.status_code != 200: logging.error( f"API request unsuccessful. Expected HTTP 200, got {response.status_code}" ) time.sleep(interval) # no point going further if we didn't get a valid response, # but we also want to try again later, should there be a temporary server issue with ipify.org continue # this is our public IP address. ip = response.json()["ip"] # build the record set based on our configuration file record_set = { "name": host, "type": "A", "ttl": ttl, "rrdatas": [ip] } # attempt to get the DNS information of our host from Google try: response = request.execute() # API call except errors.HttpError as e: logging.error( f"Access forbidden. You most likely have a configuration error. Full error: {e}" ) return 1 except corexc.Forbidden as e: logging.error( f"Access forbidden. You most likely have a configuration error. Full error: {e}" ) return 1 # ensure that we got a valid response if response is not None and len(response["rrsets"]) > 0: rrset = response["rrsets"][0] google_ip = rrset["rrdatas"][0] google_host = rrset["name"] google_ttl = rrset["ttl"] google_type = rrset["type"] logging.debug( f"config_h: {host} current_ip: {ip} g_host: {rrset['name']} g_ip: {google_ip}" ) # ensure that the record we received has the same name as the record we want to create if google_host == host: logging.info( "Config file host and google host record match") if google_ip == ip: logging.info( f"IP and Host information match. Nothing to do here. " ) else: # host record exists, but IPs are different. We need to update the record in the cloud. # To do this, we must first delete the current record, then create a new record del_record_set = { "name": host, "type": google_type, "ttl": google_ttl, "rrdatas": [google_ip], } logging.debug(f"Deleting record {del_record_set}") if not dns_change(zone, del_record_set, "delete"): logging.error( f"Failed to delete record set {del_record_set}" ) logging.debug(f"Creating record {record_set}") if not dns_change(zone, record_set, "create"): logging.error( f"Failed to create record set {record_set}" ) else: # for whatever reason, the record returned from google doesn't match the host # we have configured in our config file. Exit and log logging.error( "Configured hostname doesn't match hostname returned from google. No actions taken" ) else: # response to our request returned no results, so we'll create a DNS record logging.info( f"No record found. Creating a new record: {record_set}" ) if not dns_change(zone, record_set, "create"): logging.error( f"Failed to create record set {record_set}") # only go to sleep if we have cycled through all hosts if count == len(config['hosts']): logging.info(f"Going to sleep for {interval} seconds ") time.sleep(interval) except KeyboardInterrupt: print("\nCtl-c received. Goodbye!") break return 0
conf = yaml.load(conf_file, Loader=yaml.SafeLoader) if "ipv4" in conf["sources"]: ipv4_address = get_ipv4_address(conf["sources"]["ipv4"]) print("Discovered IPv4 address: {}".format(ipv4_address)) else: ipv4_address = None if "ipv6" in conf["sources"]: ipv6_prefix = get_ipv6_prefix(conf["sources"]["ipv6"]) print("Discovered IPv6 prefix: {}".format(ipv6_prefix)) else: ipv6_prefix = None gcp_credentials = service_account.Credentials.from_service_account_file( conf["gcp"]["credentials_file"]) gcp_client = dns.Client(project=conf["gcp"]["project"], credentials=gcp_credentials) # Create DnsRecords dns_records = [] for dns_record in conf["dns_records"]: if dns_record["ipv4"] and ipv4_address: dns_records.append( DnsRecord(dns_record["hostname"], ipv4_address, "A", conf["global"]["ttl"], gcp_client)) if dns_record["ipv6"] and ipv6_prefix: ipv6_address = calculate_ipv6_address(ipv6_prefix, dns_record) dns_records.append( DnsRecord(dns_record["hostname"], ipv6_address, "AAAA", conf["global"]["ttl"], gcp_client)) # Update
import flask from flask import request, jsonify # app = flask.Flask(__name__) # app.config["DEBUG"] = True # Grab our configuration cfg = config.cfg # Configure the client & zone if (len(cfg.gcpAuthKeyJsonFile) == 0): credentials, project = google.auth.default() else: credentials = service_account.Credentials.from_service_account_file(cfg.gcpAuthKeyJsonFile) client = dns.Client(project=cfg.gcpProject, credentials=credentials) zone = client.zone(cfg.gcpDnsZoneName, cfg.gcpDnsDomain) records = "" changes = zone.changes() def page_not_found(e): logging.error("The resource could not be found.") return "<h1>404</h1><p>The resource could not be found.</p>", 404 def page_unauthorized(e): logging.error("You are not authorized to access this resource.") return "<h1>401</h1><p>You are not authorized to access this resource.</p>", 401 def main(request): logging.info("Update request started.")
def delete_zone(project_id, name): client = dns.Client(project=project_id) zone = client.zone(name) zone.delete()