DNS dynamique avec l'api de Scaleway et de python ################################################# :date: 2021-10-27 21:00 :modified: 2021-11-01 23:30 :tags: DNS, API, python :category: network :slug: dyndns_online :authors: Milka64 :summary: Comment faire du DNS dynamique avec Scaleway et python :status: draft Cette année j'ai été obligé de changer de FAI, je suis passé chez Orange et j'ai (re)découvert les joies d'une ip dynamique... Étant auto-hebergé, il m'a fallu trouver une solution. Solutions --------- J'ai bien commencé à chercher des solutions de dynDNS mais je me suis vite rendu compte que ça ne me convenait pas (panne à répétition, temps de MAJ, etc ...). Ensuite, je me suis dit que j'allais me monter un bind auto-hebergé, mais par manque de temps, j'ai mis cette solution de côté. Et par le plus grand des hasards en mettant à jour une entrée DNS, je me suis rendu compte que Scaleway avait une API pour gérer ses services. Dyndns.py --------- J'ai donc écrit `ce petit script `_ (qui est utilisable par tous) dont je vais détailler les différentes parties. Il n'y a besoin que du token de l'api (disponible `ici `_) Fonction args ~~~~~~~~~~~~~ Elle parse les argument du scripts, et permets d'avoir un `--help`. .. code-block:: python def get_args(): """ parse agrs """ parser = argparse.ArgumentParser(description="Update dns zone with online.net API") group = parser.add_mutually_exclusive_group() group.add_argument("-v", "--verbose", action="store_true") group.add_argument("-q", "--quiet", action="store_true") parser.add_argument("-c", "--clean", help="clean old unused zones", action="store_true") parser.add_argument("-t", "--token", help="token's API (https://console.online.net/fr/api/access)") parser.add_argument("-u", "--url", help="url to get public ip, default=http://ifconfig.me", default="http://ifconfig.me") parser.add_argument("-r", "--records", help="records to update, comma separated list") parser.add_argument("domain", help="domain to update") return parser.parse_args() Fonction clear ~~~~~~~~~~~~~~ Efface les versions innutilisé: .. code-block:: python def clear_versions(args, session): """ clear inactive versions """ all_versions = session.get("https://api.online.net/api/v1/domain/" + args.domain + "/version").json() versions_to_remove = [x for x in session.get("https://api.online.net/api/v1/domain/" + args.domain + "/version").json() if not x['active']] for version in versions_to_remove: if args.verbose: print("Deleting : " + version['name']) api_session.delete("https://api.online.net/api/v1/domain/" + args.domain + "/version/" + version["uuid_ref"]) Fonction update ~~~~~~~~~~~~~~~ Crée une nouvelle zone, la peuple (avec les entrées de la version active) et l'active. .. code-block:: python def create_new_version(args, records, records_to_update): """ Create new zone version with name YYYYMMDDhhmm """ new_name = datetime.now().strftime("%Y%m%d%H%M") if args.verbose: print("Generating new zone called : " + new_name) url = "https://api.online.net/api/v1/domain/" + args.domain + "/version" data = { 'name' : new_name } res = api_session.post(url, data) if "error" in res.json(): exit(res.json()['error_description']) id_to_update = res.json()["uuid_ref"] url = "https://api.online.net/api/v1/domain/" + args.domain + "/version/" + id_to_update + "/zone" for record in records: if record['name'] in records_to_update: record['data'] = public_ip data = { 'name' : record['name'], 'type' : record['type'], 'ttl' : record['ttl'], 'data' : record['data'], 'priority' : 0, } res = api_session.post(url, data) if "error" in res.json(): exit(res['error_description']) url = "https://api.online.net/api/v1/domain/" + args.domain + "/version/" + id_to_update + "/enable" res = api_session.patch(url) try: res_json = res.json() except: res_json = {} if "error" in res_json: exit(res['error_description']) Main ~~~~ Appelé lors de l'exécution du scipt .. code-block:: python if __name__ == "__main__": ## PARSE AGRS ## args = get_args() ## Check if Token is present if not args.token: exit("token is required") ## Get public IP ## try: public_ip = requests.get(args.url).text except: exit("Can't get public IP") if not public_ip: exit("Can't get public IP") if args.verbose: print("Public IP is : " + public_ip) ## Init api session ## api_session = requests.session() api_session.headers['Authorization'] = 'Bearer ' + args.token ## Check api connection ## try: response = api_session.get("https://api.online.net/api/v1/domain/list") except: exit("Can't connect to api") if "error" in response.json(): exit(response.json()['error_description']) ## Get records and compare IP ## records = api_session.get("https://api.online.net/api/v1/domain/" + args.domain + "/zone").json() if "error" in records: exit(records['error_description']) records_to_update = args.records.split(',') old_ip = [x['data'] for x in records if x['name'] in records_to_update and not public_ip in x["data"]] if not old_ip: exit() else: create_new_version(args, records, records_to_update) ## Clear inactive versions ## if args.clean: clear_versions(args, api_session) BONUS : acme.sh + api online ---------------------------- En me documentant sur l'api d'online, je me suis rendu compte qu'acme.sh permet d'utiliser l'api de Scaleway. Jusqu'à présent je mettais à jour mes certificats à la main tous les trois mois (avec quelques raté à l'occasion...). Il y a juste besoin du token. .. code-block:: console $ acme.sh --issue --dns dns_online -d 0w.tf -d "*.0w.tf" --reloadcmd "nginx -s reload" --server letsencrypt --force