DNS dynamique avec l'api de Scaleway et de python
Posted on mer. 27 octobre 2021 in network
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.
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é:
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.
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
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 dans une variable d'environement.
$ export ONLINE_API_KEY='xxx'
$ acme.sh --issue --dns dns_online -d 0w.tf -d "*.0w.tf" --reloadcmd "nginx -s reload" --server letsencrypt --force