This commit is contained in:
iFargle
2023-02-27 22:45:12 +09:00
parent bdbb85bcb9
commit 88afde359a
4 changed files with 93 additions and 97 deletions

View File

@@ -1,13 +1,12 @@
# pylint: disable=wrong-import-order
import requests, json, os, logger
import requests, json, os
from cryptography.fernet import Fernet
from datetime import timedelta, date
from dateutil import parser
from flask import Flask
from flask import Flask, logging
app = Flask(__name__, static_url_path="/static")
LOG = app.logger()
##################################################################
# Functions related to HEADSCALE and API KEYS
@@ -58,7 +57,7 @@ def test_api_key(url, api_key):
def expire_key(url, api_key):
payload = {'prefix':str(api_key[0:10])}
json_payload=json.dumps(payload)
LOG.debug("Sending the payload '"+str(json_payload)+"' to the headscale server")
app.logger.debug("Sending the payload '"+str(json_payload)+"' to the headscale server")
response = requests.post(
str(url)+"/api/v1/apikey/expire",
@@ -89,10 +88,10 @@ def renew_api_key(url, api_key):
# If the delta is less than 5 days, renew the key:
if delta < timedelta(days=5):
LOG.warning("Key is about to expire. Delta is "+str(delta))
app.logger.warning("Key is about to expire. Delta is "+str(delta))
payload = {'expiration':str(new_expiration_date)}
json_payload=json.dumps(payload)
LOG.debug("Sending the payload '"+str(json_payload)+"' to the headscale server")
app.logger.debug("Sending the payload '"+str(json_payload)+"' to the headscale server")
response = requests.post(
str(url)+"/api/v1/apikey",
@@ -104,27 +103,27 @@ def renew_api_key(url, api_key):
}
)
new_key = response.json()
LOG.debug("JSON: "+json.dumps(new_key))
LOG.debug("New Key is: "+new_key["apiKey"])
app.logger.debug("JSON: "+json.dumps(new_key))
app.logger.debug("New Key is: "+new_key["apiKey"])
api_key_test = test_api_key(url, new_key["apiKey"])
LOG.debug("Testing the key: "+str(api_key_test))
app.logger.debug("Testing the key: "+str(api_key_test))
# Test if the new key works:
if api_key_test == 200:
LOG.info("The new key is valid and we are writing it to the file")
app.logger.info("The new key is valid and we are writing it to the file")
if not set_api_key(new_key["apiKey"]):
LOG.error("We failed writing the new key!")
app.logger.error("We failed writing the new key!")
return False # Key write failed
LOG.info("Key validated and written. Moving to expire the key.")
app.logger.info("Key validated and written. Moving to expire the key.")
expire_key(url, api_key)
return True # Key updated and validated
else:
LOG.error("Testing the API key failed.")
app.logger.error("Testing the API key failed.")
return False # The API Key test failed
else: return True # No work is required
# Gets information about the current API key
def get_api_key_info(url, api_key):
LOG.info("Getting API key information")
app.logger.info("Getting API key information")
response = requests.get(
str(url)+"/api/v1/apikey",
headers={
@@ -135,12 +134,12 @@ def get_api_key_info(url, api_key):
json_response = response.json()
# Find the current key in the array:
key_prefix = str(api_key[0:10])
LOG.info("Looking for valid API Key...")
app.logger.info("Looking for valid API Key...")
for key in json_response["apiKeys"]:
if key_prefix == key["prefix"]:
LOG.info("Key found.")
app.logger.info("Key found.")
return key
LOG.error("Could not find a valid key in Headscale. Need a new API key.")
app.logger.error("Could not find a valid key in Headscale. Need a new API key.")
return "Key not found"
##################################################################
@@ -149,7 +148,7 @@ def get_api_key_info(url, api_key):
# register a new machine
def register_machine(url, api_key, machine_key, user):
LOG.info("Registering machine %s to user %s", str(machine_key), str(user))
app.logger.info("Registering machine %s to user %s", str(machine_key), str(user))
response = requests.post(
str(url)+"/api/v1/machine/register?user="+str(user)+"&key="+str(machine_key),
headers={
@@ -162,7 +161,7 @@ def register_machine(url, api_key, machine_key, user):
# Sets the machines tags
def set_machine_tags(url, api_key, machine_id, tags_list):
LOG.info("Setting machine_id %s tag %s", str(machine_id), str(tags_list))
app.logger.info("Setting machine_id %s tag %s", str(machine_id), str(tags_list))
response = requests.post(
str(url)+"/api/v1/machine/"+str(machine_id)+"/tags",
data=tags_list,
@@ -176,7 +175,7 @@ def set_machine_tags(url, api_key, machine_id, tags_list):
# Moves machine_id to user "new_user"
def move_user(url, api_key, machine_id, new_user):
LOG.info("Moving machine_id %s to user %s", str(machine_id), str(new_user))
app.logger.info("Moving machine_id %s to user %s", str(machine_id), str(new_user))
response = requests.post(
str(url)+"/api/v1/machine/"+str(machine_id)+"/user?user="+str(new_user),
headers={
@@ -190,13 +189,13 @@ def update_route(url, api_key, route_id, current_state):
action = ""
if current_state == "True": action = "disable"
if current_state == "False": action = "enable"
LOG.info("Updating Route %s: Action: %s", str(route_id), str(action))
app.logger.info("Updating Route %s: Action: %s", str(route_id), str(action))
# Debug
LOG.debug("URL: "+str(url))
LOG.debug("Route ID: "+str(route_id))
LOG.debug("Current State: "+str(current_state))
LOG.debug("Action to take: "+str(action))
app.logger.debug("URL: "+str(url))
app.logger.debug("Route ID: "+str(route_id))
app.logger.debug("Current State: "+str(current_state))
app.logger.debug("Action to take: "+str(action))
response = requests.post(
str(url)+"/api/v1/routes/"+str(route_id)+"/"+str(action),
@@ -209,7 +208,7 @@ def update_route(url, api_key, route_id, current_state):
# Get all machines on the Headscale network
def get_machines(url, api_key):
LOG.info("Getting machine information")
app.logger.info("Getting machine information")
response = requests.get(
str(url)+"/api/v1/machine",
headers={
@@ -221,7 +220,7 @@ def get_machines(url, api_key):
# Get machine with "machine_id" on the Headscale network
def get_machine_info(url, api_key, machine_id):
LOG.info("Getting information for machine ID %s", str(machine_id))
app.logger.info("Getting information for machine ID %s", str(machine_id))
response = requests.get(
str(url)+"/api/v1/machine/"+str(machine_id),
headers={
@@ -233,7 +232,7 @@ def get_machine_info(url, api_key, machine_id):
# Delete a machine from Headscale
def delete_machine(url, api_key, machine_id):
LOG.info("Deleting machine %s", str(machine_id))
app.logger.info("Deleting machine %s", str(machine_id))
response = requests.delete(
str(url)+"/api/v1/machine/"+str(machine_id),
headers={
@@ -243,14 +242,14 @@ def delete_machine(url, api_key, machine_id):
)
status = "True" if response.status_code == 200 else "False"
if response.status_code == 200:
LOG.info("Machine deleted.")
app.logger.info("Machine deleted.")
else:
LOG.error("Deleting machine failed! %s", str(response.json()))
app.logger.error("Deleting machine failed! %s", str(response.json()))
return {"status": status, "body": response.json()}
# Rename "machine_id" with name "new_name"
def rename_machine(url, api_key, machine_id, new_name):
LOG.info("Renaming machine %s", str(machine_id))
app.logger.info("Renaming machine %s", str(machine_id))
response = requests.post(
str(url)+"/api/v1/machine/"+str(machine_id)+"/rename/"+str(new_name),
headers={
@@ -260,14 +259,14 @@ def rename_machine(url, api_key, machine_id, new_name):
)
status = "True" if response.status_code == 200 else "False"
if response.status_code == 200:
LOG.info("Machine renamed")
app.logger.info("Machine renamed")
else:
LOG.error("Machine rename failed! %s", str(response.json()))
app.logger.error("Machine rename failed! %s", str(response.json()))
return {"status": status, "body": response.json()}
# Gets routes for the passed machine_id
def get_machine_routes(url, api_key, machine_id):
LOG.info("Renaming machine %s", str(machine_id))
app.logger.info("Renaming machine %s", str(machine_id))
response = requests.get(
str(url)+"/api/v1/machine/"+str(machine_id)+"/routes",
headers={
@@ -276,14 +275,14 @@ def get_machine_routes(url, api_key, machine_id):
}
)
if response.status_code == 200:
LOG.info("Routes obtained")
app.logger.info("Routes obtained")
else:
LOG.error("Failed to get routes: %s", str(response.json()))
app.logger.error("Failed to get routes: %s", str(response.json()))
return response.json()
# Gets routes for the entire tailnet
def get_routes(url, api_key):
LOG.info("Getting routes")
app.logger.info("Getting routes")
response = requests.get(
str(url)+"/api/v1/routes",
headers={
@@ -299,7 +298,7 @@ def get_routes(url, api_key):
# Get all users in use
def get_users(url, api_key):
LOG.info("Getting Users")
app.logger.info("Getting Users")
response = requests.get(
str(url)+"/api/v1/user",
headers={
@@ -311,7 +310,7 @@ def get_users(url, api_key):
# Rename "old_name" with name "new_name"
def rename_user(url, api_key, old_name, new_name):
LOG.info("Renaming user %s to %s.", str(old_name), str(new_name))
app.logger.info("Renaming user %s to %s.", str(old_name), str(new_name))
response = requests.post(
str(url)+"/api/v1/user/"+str(old_name)+"/rename/"+str(new_name),
headers={
@@ -321,14 +320,14 @@ def rename_user(url, api_key, old_name, new_name):
)
status = "True" if response.status_code == 200 else "False"
if response.status_code == 200:
LOG.info("User renamed.")
app.logger.info("User renamed.")
else:
LOG.error("Renaming User failed!")
app.logger.error("Renaming User failed!")
return {"status": status, "body": response.json()}
# Delete a user from Headscale
def delete_user(url, api_key, user_name):
LOG.info("Deleting a User: %s", str(user_name))
app.logger.info("Deleting a User: %s", str(user_name))
response = requests.delete(
str(url)+"/api/v1/user/"+str(user_name),
headers={
@@ -338,14 +337,14 @@ def delete_user(url, api_key, user_name):
)
status = "True" if response.status_code == 200 else "False"
if response.status_code == 200:
LOG.info("User deleted.")
app.logger.info("User deleted.")
else:
LOG.error("Deleting User failed!")
app.logger.error("Deleting User failed!")
return {"status": status, "body": response.json()}
# Add a user from Headscale
def add_user(url, api_key, data):
LOG.info("Adding user: %s", str(data))
app.logger.info("Adding user: %s", str(data))
response = requests.post(
str(url)+"/api/v1/user",
data=data,
@@ -357,9 +356,9 @@ def add_user(url, api_key, data):
)
status = "True" if response.status_code == 200 else "False"
if response.status_code == 200:
LOG.info("User added.")
app.logger.info("User added.")
else:
LOG.error("Adding User failed!")
app.logger.error("Adding User failed!")
return {"status": status, "body": response.json()}
##################################################################
@@ -368,7 +367,7 @@ def add_user(url, api_key, data):
# Get all PreAuth keys associated with a user "user_name"
def get_preauth_keys(url, api_key, user_name):
LOG.info("Getting PreAuth Keys in User %s", str(user_name))
app.logger.info("Getting PreAuth Keys in User %s", str(user_name))
response = requests.get(
str(url)+"/api/v1/preauthkey?user="+str(user_name),
headers={
@@ -381,7 +380,7 @@ def get_preauth_keys(url, api_key, user_name):
# Add a preauth key to the user "user_name" given the booleans "ephemeral"
# and "reusable" with the expiration date "date" contained in the JSON payload "data"
def add_preauth_key(url, api_key, data):
LOG.info("Adding PreAuth Key: %s", str(data))
app.logger.info("Adding PreAuth Key: %s", str(data))
response = requests.post(
str(url)+"/api/v1/preauthkey",
data=data,
@@ -393,14 +392,14 @@ def add_preauth_key(url, api_key, data):
)
status = "True" if response.status_code == 200 else "False"
if response.status_code == 200:
LOG.info("PreAuth Key added.")
app.logger.info("PreAuth Key added.")
else:
LOG.error("Adding PreAuth Key failed!")
app.logger.error("Adding PreAuth Key failed!")
return {"status": status, "body": response.json()}
# Expire a pre-auth key. data is {"user": "string", "key": "string"}
def expire_preauth_key(url, api_key, data):
LOG.info("Expiring PreAuth Key...")
app.logger.info("Expiring PreAuth Key...")
response = requests.post(
str(url)+"/api/v1/preauthkey/expire",
data=data,
@@ -411,6 +410,6 @@ def expire_preauth_key(url, api_key, data):
}
)
status = "True" if response.status_code == 200 else "False"
LOG.debug("expire_preauth_key - Return: "+str(response.json()))
LOG.debug("expire_preauth_key - Status: "+str(status))
app.logger.debug("expire_preauth_key - Return: "+str(response.json()))
app.logger.debug("expire_preauth_key - Status: "+str(status))
return {"status": status, "body": response.json()}

View File

@@ -1,10 +1,9 @@
# pylint: disable=wrong-import-order
import os, headscale, requests, logger
from flask import Flask
import os, headscale, requests
from flask import Flask, logging
app = Flask(__name__, static_url_path="/static")
LOG = app.logger()
def pretty_print_duration(duration, delta_type=""):
""" Prints a duration in human-readable formats """
@@ -52,13 +51,13 @@ def key_check():
# Test the API key. If the test fails, return a failure.
# AKA, if headscale returns Unauthorized, fail:
LOG.info("Testing API key validity.")
app.logger.info("Testing API key validity.")
status = headscale.test_api_key(url, api_key)
if status != 200:
LOG.info("Got a non-200 response from Headscale. Test failed (Response: %i)", status)
app.logger.info("Got a non-200 response from Headscale. Test failed (Response: %i)", status)
return False
else:
LOG.info("Key check passed.")
app.logger.info("Key check passed.")
# Check if the key needs to be renewed
headscale.renew_api_key(url, api_key)
return True
@@ -152,20 +151,20 @@ def access_checks():
server_reachable = True
else:
checks_passed = False
LOG.error("Headscale URL: Response 200: FAILED")
app.logger.error("Headscale URL: Response 200: FAILED")
# Check: /data is rwx for 1000:1000:
if os.access('/data/', os.R_OK): data_readable = True
else:
LOG.error("/data READ: FAILED")
app.logger.error("/data READ: FAILED")
checks_passed = False
if os.access('/data/', os.W_OK): data_writable = True
else:
LOG.error("/data WRITE: FAILED")
app.logger.error("/data WRITE: FAILED")
checks_passed = False
if os.access('/data/', os.X_OK): data_executable = True
else:
LOG.error("/data EXEC: FAILED")
app.logger.error("/data EXEC: FAILED")
checks_passed = False
# Check: /data/key.txt exists and is rw:
@@ -173,29 +172,29 @@ def access_checks():
file_exists = True
if os.access('/data/key.txt', os.R_OK): file_readable = True
else:
LOG.error("/data/key.txt READ: FAILED")
app.logger.error("/data/key.txt READ: FAILED")
checks_passed = False
if os.access('/data/key.txt', os.W_OK): file_writable = True
else:
LOG.error("/data/key.txt WRITE: FAILED")
app.logger.error("/data/key.txt WRITE: FAILED")
checks_passed = False
else: LOG.error("/data/key.txt EXIST: FAILED - NO ERROR")
else: app.logger.error("/data/key.txt EXIST: FAILED - NO ERROR")
# Check: /etc/headscale/config.yaml is readable:
if os.access('/etc/headscale/config.yaml', os.R_OK): config_readable = True
elif os.access('/etc/headscale/config.yml', os.R_OK): config_readable = True
else:
LOG.error("/etc/headscale/config.y(a)ml: READ: FAILED")
app.logger.error("/etc/headscale/config.y(a)ml: READ: FAILED")
checks_passed = False
if checks_passed:
LOG.error("All startup checks passed.")
app.logger.error("All startup checks passed.")
return "Pass"
message_html = ""
# Generate the message:
if not server_reachable:
LOG.error("Server is unreachable")
app.logger.error("Server is unreachable")
message = """
<p>Your headscale server is either unreachable or not properly configured.
Please ensure your configuration is correct (Check for 200 status on
@@ -205,7 +204,7 @@ def access_checks():
message_html += format_message("Error", "Headscale unreachable", message)
if not config_readable:
LOG.error("Headscale configuration is not readable")
app.logger.error("Headscale configuration is not readable")
message = """
<p>/etc/headscale/config.yaml not readable. Please ensure your
headscale configuration file resides in /etc/headscale and
@@ -215,7 +214,7 @@ def access_checks():
message_html += format_message("Error", "/etc/headscale/config.yaml not readable", message)
if not data_writable:
LOG.error("/data folder is not writable")
app.logger.error("/data folder is not writable")
message = """
<p>/data is not writable. Please ensure your
permissions are correct. /data mount should be writable
@@ -225,7 +224,7 @@ def access_checks():
message_html += format_message("Error", "/data not writable", message)
if not data_readable:
LOG.error("/data folder is not readable")
app.logger.error("/data folder is not readable")
message = """
<p>/data is not readable. Please ensure your
permissions are correct. /data mount should be readable
@@ -235,7 +234,7 @@ def access_checks():
message_html += format_message("Error", "/data not readable", message)
if not data_executable:
LOG.error("/data folder is not readable")
app.logger.error("/data folder is not readable")
message = """
<p>/data is not executable. Please ensure your
permissions are correct. /data mount should be readable
@@ -249,7 +248,7 @@ def access_checks():
# If it doesn't exist, we assume the user hasn't created it yet.
# Just redirect to the settings page to enter an API Key
if not file_writable:
LOG.error("/data/key.txt is not writable")
app.logger.error("/data/key.txt is not writable")
message = """
<p>/data/key.txt is not writable. Please ensure your
permissions are correct. /data mount should be writable
@@ -259,7 +258,7 @@ def access_checks():
message_html += format_message("Error", "/data/key.txt not writable", message)
if not file_readable:
LOG.error("/data/key.txt is not readable")
app.logger.error("/data/key.txt is not readable")
message = """
<p>/data/key.txt is not readable. Please ensure your
permissions are correct. /data mount should be readable

View File

@@ -1,14 +1,13 @@
# pylint: disable=line-too-long, wrong-import-order
import headscale, helper, pytz, os, yaml, logger
from flask import Flask, Markup, render_template
import headscale, helper, pytz, os, yaml
from flask import Flask, Markup, render_template, logging
from datetime import datetime
from dateutil import parser
from concurrent.futures import ALL_COMPLETED, wait
from flask_executor import Executor
app = Flask(__name__, static_url_path="/static")
LOG = app.logger()
executor = Executor(app)
def render_overview():
@@ -245,7 +244,7 @@ def thread_machine_content(machine, machine_content, idx):
<p><div>
"""
for route in pulled_routes["routes"]:
# LOG.warning("Route: ["+str(route['machine']['name'])+"] id: "+str(route['id'])+" / prefix: "+str(route['prefix'])+" enabled?: "+str(route['enabled']))
app.logger.debug("Route: ["+str(route['machine']['name'])+"] id: "+str(route['id'])+" / prefix: "+str(route['prefix'])+" enabled?: "+str(route['enabled']))
# Check if the route is enabled:
route_enabled = "red"
route_tooltip = 'enable'
@@ -325,7 +324,7 @@ def thread_machine_content(machine, machine_content, idx):
expiry_time = str(expiry_local.strftime('%m/%Y'))+" "+str(timezone)+" ("+str(expiry_print)+")"
else:
expiry_time = str(expiry_local.strftime('%A %m/%d/%Y, %H:%M:%S'))+" "+str(timezone)+" ("+str(expiry_print)+")"
LOG.error("Machine: "+machine["name"]+" expires: "+str(expiry_local.strftime('%Y'))+" / "+str(expiry_delta.days))
app.logger.debug("Machine: "+machine["name"]+" expires: "+str(expiry_local.strftime('%Y'))+" / "+str(expiry_delta.days))
expiring_soon = True if int(expiry_delta.days) < 14 and int(expiry_delta.days) > 0 else False
# Get the first 10 characters of the PreAuth Key:
@@ -367,7 +366,7 @@ def thread_machine_content(machine, machine_content, idx):
expiration_badge = Markup(expiration_badge),
machine_tags = Markup(tags),
)))
LOG.warning("Finished thread for machine "+machine["givenName"]+" index "+str(idx))
app.logger.warning("Finished thread for machine "+machine["givenName"]+" index "+str(idx))
# Render the cards for the machines page:
def render_machines_cards():
@@ -381,14 +380,14 @@ def render_machines_cards():
iterable = []
machine_content = {}
for i in range (0, num_threads):
LOG.error("Appending iterable: "+str(i))
app.logger.error("Appending iterable: "+str(i))
iterable.append(i)
# Flask-Executor Method:
LOG.warning("Starting futures")
app.logger.warning("Starting futures")
futures = [executor.submit(thread_machine_content, machines_list["machines"][idx], machine_content, idx) for idx in iterable]
# Wait for the executor to finish all jobs:
wait(futures, return_when=ALL_COMPLETED)
LOG.warning("Finished futures")
app.logger.warning("Finished futures")
# DEBUG: Do in a forloop:
# for idx in iterable: thread_machine_content(machines_list["machines"][idx], machine_content, idx)

View File

@@ -1,9 +1,9 @@
# pylint: disable=wrong-import-order
import headscale, helper, json, os, pytz, renderer, secrets, requests, logger
import headscale, helper, json, os, pytz, renderer, secrets, requests
from functools import wraps
from datetime import datetime
from flask import Flask, Markup, redirect, render_template, request, url_for
from flask import Flask, Markup, redirect, render_template, request, url_for, logging
from dateutil import parser
from flask_executor import Executor
from werkzeug.middleware.proxy_fix import ProxyFix
@@ -36,7 +36,6 @@ dictConfig({
})
app = Flask(__name__, static_url_path="/static")
LOG = app.logger()
executor = Executor(app)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)
@@ -49,7 +48,7 @@ if AUTH_TYPE == "oidc":
# https://gist.github.com/thomasdarimont/145dc9aa857b831ff2eff221b79d179a/
# https://www.authelia.com/integration/openid-connect/introduction/
# https://github.com/steinarvk/flask_oidc_demo
LOG.info("Loading OIDC libraries and configuring app...")
app.loggerinfo("Loading OIDC libraries and configuring app...")
DOMAIN_NAME = os.environ["DOMAIN_NAME"]
BASE_PATH = os.environ["SCRIPT_NAME"] if os.environ["SCRIPT_NAME"] != "/" else ""
@@ -60,7 +59,7 @@ if AUTH_TYPE == "oidc":
# Construct client_secrets.json:
response = requests.get(str(OIDC_AUTH_URL))
oidc_info = response.json()
LOG.debug("JSON Dumps for OIDC_INFO: "+json.dumps(oidc_info))
app.logger.debug("JSON Dumps for OIDC_INFO: "+json.dumps(oidc_info))
client_secrets = """{
"web": {
@@ -80,10 +79,10 @@ if AUTH_TYPE == "oidc":
with open("/app/instance/secrets.json", "w+") as secrets_json:
secrets_json.write(client_secrets)
LOG.debug("Client Secrets: ")
app.logger.debug("Client Secrets: ")
with open("/app/instance/secrets.json", "r+") as secrets_json:
LOG.debug("/app/instances/secrets.json:")
LOG.debug(secrets_json.read())
app.logger.debug("/app/instances/secrets.json:")
app.logger.debug(secrets_json.read())
app.config.update({
'SECRET_KEY': secrets.token_urlsafe(32),
@@ -102,7 +101,7 @@ if AUTH_TYPE == "oidc":
elif AUTH_TYPE == "basic":
# https://flask-basicauth.readthedocs.io/en/latest/
LOG.info("Loading basic auth libraries and configuring app...")
app.loggerinfo("Loading basic auth libraries and configuring app...")
from flask_basicauth import BasicAuth
app.config['BASIC_AUTH_USERNAME'] = os.environ["BASIC_AUTH_USER"].replace('"', '')
@@ -289,9 +288,9 @@ def test_key_page():
if status != 200: return "Unauthenticated"
renewed = headscale.renew_api_key(url, api_key)
LOG.warning("The below statement will be TRUE if the key has been renewed, ")
LOG.warning("or DOES NOT need renewal. False in all other cases")
LOG.warning("Renewed: "+str(renewed))
app.loggerwarning("The below statement will be TRUE if the key has been renewed, ")
app.loggerwarning("or DOES NOT need renewal. False in all other cases")
app.loggerwarning("Renewed: "+str(renewed))
# The key works, let's renew it if it needs it. If it does, re-read the api_key from the file:
if renewed: api_key = headscale.get_api_key()