def create_admin_kubeconfig(ca, ha_admin_token=None): """ Create a kubeconfig file. The file in stored under credentials named after the admin :param ca: the ca :param ha_admin_token: the ha_cluster_token """ if not ha_admin_token: token = get_token("admin", "basic_auth.csv") if not token: print( "Error, could not locate admin token. Joining cluster failed.") exit(2) else: token = ha_admin_token assert token is not None config_template = "{}/{}".format(snap_path, "client.config.template") config = "{}/credentials/client.config".format(snapdata_path) shutil.copyfile(config, "{}.backup".format(config)) try_set_file_permissions("{}.backup".format(config)) ca_line = ca_one_line(ca) with open(config_template, "r") as tfp: with open(config, "w+") as fp: for _, config_txt in enumerate(tfp): if config_txt.strip().startswith("username:"******"CADATA", ca_line) config_txt = config_txt.replace("NAME", "admin") config_txt = config_txt.replace("AUTHTYPE", "token") config_txt = config_txt.replace("PASSWORD", token) fp.write(config_txt) try_set_file_permissions(config)
def create_x509_kubeconfig(ca, master_ip, api_port, filename, user, path_to_cert, path_to_cert_key): """ Create a kubeconfig file. The file in stored under credentials named after the user :param ca: the ca :param master_ip: the master node IP :param api_port: the API server port :param filename: the name of the config file :param user: the user to use al login :param path_to_cert: path to certificate file :param path_to_cert_key: path to certificate key file """ snap_path = os.environ.get("SNAP") config_template = "{}/{}".format(snap_path, "client-x509.config.template") config = "{}/credentials/{}".format(snapdata_path, filename) shutil.copyfile(config, "{}.backup".format(config)) try_set_file_permissions("{}.backup".format(config)) ca_line = ca_one_line(ca) with open(config_template, "r") as tfp: with open(config, "w+") as fp: config_txt = tfp.read() config_txt = config_txt.replace("CADATA", ca_line) config_txt = config_txt.replace("NAME", user) config_txt = config_txt.replace("PATHTOCERT", path_to_cert) config_txt = config_txt.replace("PATHTOKEYCERT", path_to_cert_key) config_txt = config_txt.replace("127.0.0.1", master_ip) config_txt = config_txt.replace("16443", api_port) fp.write(config_txt) try_set_file_permissions(config)
def get_etcd_client_cert(master_ip, master_port, token): """ Get a signed cert to access etcd :param master_ip: master ip :param master_port: master port :param token: token to contact the master with """ cer_req_file = "{}/certs/server.remote.csr".format(snapdata_path) cmd_cert = ( "{snap}/usr/bin/openssl req -new -sha256 -key {snapdata}/certs/server.key -out {csr} " "-config {snapdata}/certs/csr.conf".format(snap=snap_path, snapdata=snapdata_path, csr=cer_req_file)) subprocess.check_call(cmd_cert.split()) with open(cer_req_file) as fp: csr = fp.read() req_data = {"token": token, "request": csr} # TODO: enable ssl verification signed = requests.post( "https://{}:{}/{}/sign-cert".format(master_ip, master_port, CLUSTER_API), json=req_data, verify=False, ) if signed.status_code != 200: print("Failed to sign certificate. {}".format( signed.json()["error"])) exit(1) info = signed.json() with open(server_cert_file, "w") as cert_fp: cert_fp.write(info["certificate"]) try_set_file_permissions(server_cert_file)
def create_kubeconfig(token, ca, master_ip, api_port, filename, user): """ Create a kubeconfig file. The file in stored under credentials named after the user :param token: the token to be in the kubeconfig :param ca: the ca :param master_ip: the master node IP :param api_port: the API server port :param filename: the name of the config file :param user: the user to use al login """ snap_path = os.environ.get("SNAP") config_template = "{}/{}".format(snap_path, "kubelet.config.template") config = "{}/credentials/{}".format(snapdata_path, filename) shutil.copyfile(config, "{}.backup".format(config)) try_set_file_permissions("{}.backup".format(config)) ca_line = ca_one_line(ca) with open(config_template, "r") as tfp: with open(config, "w+") as fp: config_txt = tfp.read() config_txt = config_txt.replace("CADATA", ca_line) config_txt = config_txt.replace("NAME", user) config_txt = config_txt.replace("TOKEN", token) config_txt = config_txt.replace("127.0.0.1", master_ip) config_txt = config_txt.replace("16443", api_port) fp.write(config_txt) try_set_file_permissions(config)
def rebuild_client_config(): """ Recreate the client config """ token = get_token("admin") if not token: print("Error, could not locate admin token. Resetting the node failed.") exit(2) config_template = "{}/{}".format(snap_path, "client.config.template") config = "{}/credentials/client.config".format(snapdata_path) shutil.copyfile(config, "{}.backup".format(config)) try_set_file_permissions("{}.backup".format(config)) cert_file = "{}/certs/{}".format(snapdata_path, "ca.crt") with open(cert_file) as fp: ca = fp.read() ca_line = base64.b64encode(ca.encode("utf-8")).decode("utf-8") with open(config_template, "r") as tfp: with open(config, "w+") as fp: for _, config_txt in enumerate(tfp): if config_txt.strip().startswith("username:"******"CADATA", ca_line) config_txt = config_txt.replace("NAME", "admin") config_txt = config_txt.replace("AUTHTYPE", "token") config_txt = config_txt.replace("PASSWORD", token) fp.write(config_txt) try_set_file_permissions(config)
def update_apiserver_proxy(master_ip, api_port): """ Update the apiserver-proxy configuration """ lock_path = os.path.expandvars("${SNAP_DATA}/var/lock") lock = "{}/no-apiserver-proxy".format(lock_path) if os.path.exists(lock): os.remove(lock) # add the initial control plane endpoint addresses = [{"address": "{}:{}".format(master_ip, api_port)}] traefik_providers = os.path.expandvars( "${SNAP_DATA}/args/traefik/provider-template.yaml") traefik_providers_out = os.path.expandvars( "${SNAP_DATA}/args/traefik/provider.yaml") with open(traefik_providers) as f: p = yaml.safe_load(f) p["tcp"]["services"]["kube-apiserver"]["loadBalancer"][ "servers"] = addresses with open(traefik_providers_out, "w") as out_file: yaml.dump(p, out_file) try_set_file_permissions(traefik_providers_out) service("restart", "apiserver-proxy")
def set_arg(key, value, file): """ Set an argument to a file :param key: argument name :param value: value :param file: the arguments file """ filename = "{}/args/{}".format(snapdata_path, file) filename_remote = "{}/args/{}.remote".format(snapdata_path, file) done = False with open(filename_remote, "w+") as back_fp: with open(filename, "r+") as fp: for _, line in enumerate(fp): if line.startswith(key): done = True if value is not None: back_fp.write("{}={}\n".format(key, value)) else: back_fp.write("{}".format(line)) if not done and value is not None: back_fp.write("{}={}\n".format(key, value)) shutil.copyfile(filename, "{}.backup".format(filename)) try_set_file_permissions("{}.backup".format(filename)) shutil.copyfile(filename_remote, filename) try_set_file_permissions(filename) os.remove(filename_remote)
def store_remote_ca(ca): """ Store the remote ca :param ca: the CA """ with open(ca_cert_file, "w+") as fp: fp.write(ca) try_set_file_permissions(ca_cert_file)
def store_base_kubelet_args(args_string): """ Create a kubelet args file from the set of args provided :param args_string: the arguments provided """ args_file = "{}/args/kubelet".format(snapdata_path) with open(args_file, "w") as fp: fp.write(args_string) try_set_file_permissions(args_file)
def store_callback_token(token): """ Store the callback token :param token: the callback token """ callback_token_file = "{}/credentials/callback-token.txt".format( snapdata_path) with open(callback_token_file, "w") as fp: fp.write(token) try_set_file_permissions(callback_token_file)
def store_cluster_certs(cluster_cert, cluster_key): """ Store the dqlite cluster certs :param cluster_cert: the cluster certificate :param cluster_key: the cluster certificate key """ with open(cluster_cert_file, "w+") as fp: fp.write(cluster_cert) try_set_file_permissions(cluster_cert_file) with open(cluster_key_file, "w+") as fp: fp.write(cluster_key) try_set_file_permissions(cluster_key_file)
def store_cert(filename, payload): """ Store a certificate :param filename: where to store the certificate :param payload: certificate payload """ file_with_path = "{}/certs/{}".format(snapdata_path, filename) backup_file_with_path = "{}.backup".format(file_with_path) shutil.copyfile(file_with_path, backup_file_with_path) try_set_file_permissions(backup_file_with_path) with open(file_with_path, "w+") as fp: fp.write(payload) try_set_file_permissions(file_with_path)
def generate_callback_token(): """ Generate a token and store it in the callback token file :return: the token """ token = "".join( random.choice(string.ascii_uppercase + string.digits) for _ in range(64)) with open(callback_token_file, "w") as fp: fp.write("{}\n".format(token)) try_set_file_permissions(callback_token_file) return token
def remove_kubelet_token(node): """ Remove a token for a node in the known tokens :param node: the name of the node """ file = "{}/credentials/known_tokens.csv".format(snapdata_path) backup_file = "{}.backup".format(file) token = "system:node:{}".format(node) # That is a critical section. We need to protect it. with open(backup_file, "w") as back_fp: with open(file, "r") as fp: for _, line in enumerate(fp): if token in line: continue back_fp.write("{}".format(line)) try_set_file_permissions(backup_file) shutil.copyfile(backup_file, file)
def replace_admin_token(token): """ Replaces the admin token in the known tokens :param token: the admin token """ file = "{}/credentials/known_tokens.csv".format(snapdata_path) backup_file = "{}.backup".format(file) # That is a critical section. We need to protect it. with open(backup_file, "w") as back_fp: with open(file, "r") as fp: for _, line in enumerate(fp): if 'admin,admin,"system:masters"' in line: continue back_fp.write("{}".format(line)) back_fp.write('{},admin,admin,"system:masters"\n'.format(token)) try_set_file_permissions(backup_file) shutil.copyfile(backup_file, file)
def remove_callback_token(node): """ Remove a callback token :param node: the node """ tmp_file = "{}.tmp".format(callback_tokens_file) if not os.path.isfile(callback_tokens_file): open(callback_tokens_file, "a+") os.chmod(callback_tokens_file, 0o600) with open(tmp_file, "w") as backup_fp: os.chmod(tmp_file, 0o600) with open(callback_tokens_file, "r+") as callback_fp: # Entries are of the format: 'node_hostname:agent_port token' # We need to get the node_hostname part for line in callback_fp: parts = line.split(":") if parts[0] == node: continue else: backup_fp.write(line) try_set_file_permissions(tmp_file) shutil.move(tmp_file, callback_tokens_file)
def get_client_cert(master_ip, master_port, fname, token, username, group=None): """ Get a signed cert. See https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certs :param master_ip: master ip :param master_port: master port :param fname: file name prefix for the certificate :param token: token to contact the master with :param username: the username of the cert's owner :param group: the group the owner belongs to """ info = "/CN={}".format(username) if group: info = "{}/O={}".format(info, group) cer_req_file = "{}/certs/{}.csr".format(snapdata_path, fname) cer_key_file = "{}/certs/{}.key".format(snapdata_path, fname) cer_file = "{}/certs/{}.crt".format(snapdata_path, fname) if not os.path.exists(cer_key_file): cmd_gen_cert_key = "{snap}/usr/bin/openssl genrsa -out {key} 2048".format( snap=snap_path, key=cer_key_file) subprocess.check_call(cmd_gen_cert_key.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) try_set_file_permissions(cer_key_file) cmd_cert = "{snap}/usr/bin/openssl req -new -sha256 -key {key} -out {csr} -subj {info}".format( snap=snap_path, key=cer_key_file, csr=cer_req_file, info=info, ) subprocess.check_call(cmd_cert.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) with open(cer_req_file) as fp: csr = fp.read() req_data = {"token": token, "request": csr} # TODO: enable ssl verification signed = requests.post( "https://{}:{}/{}/sign-cert".format(master_ip, master_port, CLUSTER_API), json=req_data, verify=False, ) if signed.status_code != 200: error = "Failed to sign {} certificate ({}).".format( fname, signed.status_code) try: if "error" in signed.json(): error = "{} {}".format(error, format(signed.json()["error"])) except ValueError: print( "Make sure the cluster you connect to supports joining worker nodes." ) print(error) exit(1) info = signed.json() with open(cer_file, "w") as cert_fp: cert_fp.write(info["certificate"]) try_set_file_permissions(cer_file) return { "certificate_location": cer_file, "certificate_key_location": cer_key_file, }