Make client indentity by AME cert (#11946) (#12908)

This commit is contained in:
mssonicbld 2022-12-02 13:13:26 +08:00 committed by GitHub
parent 02345963db
commit 7152e84277
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 148 additions and 27 deletions

View File

@ -446,7 +446,7 @@ sudo https_proxy=$https_proxy LANG=C chroot $FILESYSTEM_ROOT pip3 install watchd
{% if include_kubernetes == "y" %} {% if include_kubernetes == "y" %}
# Point to kubelet to /etc/resolv.conf # Point to kubelet to /etc/resolv.conf
# #
echo 'KUBELET_EXTRA_ARGS="--resolv-conf=/etc/resolv.conf"' | sudo tee -a $FILESYSTEM_ROOT/etc/default/kubelet echo 'KUBELET_EXTRA_ARGS="--resolv-conf=/etc/resolv.conf --cgroup-driver=cgroupfs --node-ip=::"' | sudo tee -a $FILESYSTEM_ROOT/etc/default/kubelet
# Copy Flannel conf file into sonic-templates # Copy Flannel conf file into sonic-templates
# #

View File

@ -104,10 +104,12 @@ def log_debug(m):
def log_error(m): def log_error(m):
msg = "{}: {}".format(inspect.stack()[1][3], m)
syslog.syslog(syslog.LOG_ERR, msg) syslog.syslog(syslog.LOG_ERR, msg)
def log_info(m): def log_info(m):
msg = "{}: {}".format(inspect.stack()[1][3], m)
syslog.syslog(syslog.LOG_INFO, msg) syslog.syslog(syslog.LOG_INFO, msg)

View File

@ -13,10 +13,14 @@ import sys
import syslog import syslog
import tempfile import tempfile
import urllib.request import urllib.request
import base64
from urllib.parse import urlparse from urllib.parse import urlparse
import yaml import yaml
import requests
from sonic_py_common import device_info from sonic_py_common import device_info
from jinja2 import Template
from swsscommon import swsscommon
KUBE_ADMIN_CONF = "/etc/sonic/kube_admin.conf" KUBE_ADMIN_CONF = "/etc/sonic/kube_admin.conf"
KUBELET_YAML = "/var/lib/kubelet/config.yaml" KUBELET_YAML = "/var/lib/kubelet/config.yaml"
@ -24,6 +28,9 @@ SERVER_ADMIN_URL = "https://{}/admin.conf"
LOCK_FILE = "/var/lock/kube_join.lock" LOCK_FILE = "/var/lock/kube_join.lock"
FLANNEL_CONF_FILE = "/usr/share/sonic/templates/kube_cni.10-flannel.conflist" FLANNEL_CONF_FILE = "/usr/share/sonic/templates/kube_cni.10-flannel.conflist"
CNI_DIR = "/etc/cni/net.d" CNI_DIR = "/etc/cni/net.d"
K8S_CA_URL = "https://{}:{}/api/v1/namespaces/default/configmaps/kube-root-ca.crt"
AME_CRT = "/etc/sonic/credentials/restapiserver.crt"
AME_KEY = "/etc/sonic/credentials/restapiserver.key"
def log_debug(m): def log_debug(m):
msg = "{}: {}".format(inspect.stack()[1][3], m) msg = "{}: {}".format(inspect.stack()[1][3], m)
@ -77,8 +84,7 @@ def _run_command(cmd, timeout=5):
def kube_read_labels(): def kube_read_labels():
""" Read current labels on node and return as dict. """ """ Read current labels on node and return as dict. """
KUBECTL_GET_CMD = "kubectl --kubeconfig {} get nodes --show-labels |\ KUBECTL_GET_CMD = "kubectl --kubeconfig {} get nodes {} --show-labels |tr -s ' ' | cut -f6 -d' '"
grep {} | tr -s ' ' | cut -f6 -d' '"
labels = {} labels = {}
ret, out, _ = _run_command(KUBECTL_GET_CMD.format( ret, out, _ = _run_command(KUBECTL_GET_CMD.format(
@ -211,6 +217,68 @@ def _download_file(server, port, insecure):
log_debug("{} downloaded".format(KUBE_ADMIN_CONF)) log_debug("{} downloaded".format(KUBE_ADMIN_CONF))
def _gen_cli_kubeconf(server, port, insecure):
"""generate identity which can help authenticate and
authorization to k8s cluster
"""
client_kubeconfig_template = """
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: {{ k8s_ca }}
server: https://{{ vip }}:{{ port }}
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: user
name: user@kubernetes
current-context: user@kubernetes
kind: Config
preferences: {}
users:
- name: user
user:
client-certificate-data: {{ ame_crt }}
client-key-data: {{ ame_key }}
"""
if insecure:
r = requests.get(K8S_CA_URL.format(server, port), cert=(AME_CRT, AME_KEY), verify=False)
else:
r = requests.get(K8S_CA_URL.format(server, port), cert=(AME_CRT, AME_KEY))
if not r.ok:
raise requests.RequestException("Something wrong with AME cert or something wrong about sonic role in k8s cluster")
k8s_ca = r.json()["data"]["ca.crt"]
k8s_ca_b64 = base64.b64encode(k8s_ca.encode("utf-8")).decode("utf-8")
ame_crt_raw = open(AME_CRT, "rb")
ame_crt_b64 = base64.b64encode(ame_crt_raw.read()).decode("utf-8")
ame_key_raw = open(AME_KEY, "rb")
ame_key_b64 = base64.b64encode(ame_key_raw.read()).decode("utf-8")
client_kubeconfig_template_j2 = Template(client_kubeconfig_template)
client_kubeconfig = client_kubeconfig_template_j2.render(
k8s_ca=k8s_ca_b64, vip=server, port=port, ame_crt=ame_crt_b64, ame_key=ame_key_b64)
(h, fname) = tempfile.mkstemp(suffix="_kube_join")
os.write(h, client_kubeconfig.encode("utf-8"))
os.close(h)
log_debug("Downloaded = {}".format(fname))
shutil.copyfile(fname, KUBE_ADMIN_CONF)
log_debug("{} downloaded".format(KUBE_ADMIN_CONF))
def _get_local_ipv6():
try:
config_db = swsscommon.DBConnector("CONFIG_DB", 0)
mgmt_ip_data = swsscommon.Table(config_db, 'MGMT_INTERFACE')
for key in mgmt_ip_data.getKeys():
if key.find(":") >= 0:
return key.split("|")[1].split("/")[0]
raise IOError("IPV6 not find from MGMT_INTERFACE table")
except Exception as e:
raise IOError(str(e))
def _troubleshoot_tips(): def _troubleshoot_tips():
""" log troubleshoot tips which could be handy, """ log troubleshoot tips which could be handy,
when in trouble with join when in trouble with join
@ -264,12 +332,14 @@ def _do_reset(pending_join = False):
def _do_join(server, port, insecure): def _do_join(server, port, insecure):
KUBEADM_JOIN_CMD = "kubeadm join --discovery-file {} --node-name {}" KUBEADM_JOIN_CMD = "kubeadm join --discovery-file {} --node-name {} --apiserver-advertise-address {}"
err = "" err = ""
out = "" out = ""
ret = 0 ret = 0
try: try:
_download_file(server, port, insecure) local_ipv6 = _get_local_ipv6()
#_download_file(server, port, insecure)
_gen_cli_kubeconf(server, port, insecure)
_do_reset(True) _do_reset(True)
_run_command("modprobe br_netfilter") _run_command("modprobe br_netfilter")
# Copy flannel.conf # Copy flannel.conf
@ -279,11 +349,11 @@ def _do_join(server, port, insecure):
if ret == 0: if ret == 0:
(ret, out, err) = _run_command(KUBEADM_JOIN_CMD.format( (ret, out, err) = _run_command(KUBEADM_JOIN_CMD.format(
KUBE_ADMIN_CONF, get_device_name()), timeout=60) KUBE_ADMIN_CONF, get_device_name(), local_ipv6), timeout=60)
log_debug("ret = {}".format(ret)) log_debug("ret = {}".format(ret))
except IOError as e: except IOError as e:
err = "Download failed: {}".format(str(e)) err = "Join failed: {}".format(str(e))
ret = -1 ret = -1
out = "" out = ""

View File

@ -9,6 +9,7 @@ import time
CONFIG_DB_NO = 4 CONFIG_DB_NO = 4
STATE_DB_NO = 6 STATE_DB_NO = 6
FEATURE_TABLE = "FEATURE" FEATURE_TABLE = "FEATURE"
MGMT_INTERFACE_TABLE = "MGMT_INTERFACE"
KUBE_LABEL_TABLE = "KUBE_LABELS" KUBE_LABEL_TABLE = "KUBE_LABELS"
KUBE_LABEL_SET_KEY = "SET" KUBE_LABEL_SET_KEY = "SET"
@ -41,6 +42,7 @@ KUBE_RETURN = "kube_return"
IMAGE_TAG = "image_tag" IMAGE_TAG = "image_tag"
FAIL_LOCK = "fail_lock" FAIL_LOCK = "fail_lock"
DO_JOIN = "do_join" DO_JOIN = "do_join"
REQ = "req"
# subproc key words # subproc key words
@ -643,8 +645,27 @@ def mock_subproc_side_effect(cmd, shell=False, stdout=None, stderr=None):
return mock_proc(cmd, index) return mock_proc(cmd, index)
def set_kube_mock(mock_subproc): class mock_reqget:
def __init__(self):
self.ok = True
def json(self):
return current_test_data.get(REQ, "")
def mock_reqget_side_effect(url, cert, verify=True):
return mock_reqget()
def set_kube_mock(mock_subproc, mock_table=None, mock_conn=None, mock_reqget=None):
mock_subproc.side_effect = mock_subproc_side_effect mock_subproc.side_effect = mock_subproc_side_effect
if mock_table != None:
mock_table.side_effect = table_side_effect
if mock_conn != None:
mock_conn.side_effect = conn_side_effect
if mock_reqget != None:
mock_reqget.side_effect = mock_reqget_side_effect
def create_remote_ctr_config_json(): def create_remote_ctr_config_json():
str_conf = '\ str_conf = '\

View File

@ -15,6 +15,8 @@ import kube_commands
KUBE_ADMIN_CONF = "/tmp/kube_admin.conf" KUBE_ADMIN_CONF = "/tmp/kube_admin.conf"
FLANNEL_CONF_FILE = "/tmp/flannel.conf" FLANNEL_CONF_FILE = "/tmp/flannel.conf"
CNI_DIR = "/tmp/cni/net.d" CNI_DIR = "/tmp/cni/net.d"
AME_CRT = "/tmp/restapiserver.crt"
AME_KEY = "/tmp/restapiserver.key"
# kube_commands test cases # kube_commands test cases
# NOTE: Ensure state-db entry is complete in PRE as we need to # NOTE: Ensure state-db entry is complete in PRE as we need to
@ -25,8 +27,7 @@ read_labels_test_data = {
common_test.DESCR: "read labels", common_test.DESCR: "read labels",
common_test.RETVAL: 0, common_test.RETVAL: 0,
common_test.PROC_CMD: ["\ common_test.PROC_CMD: ["\
kubectl --kubeconfig {} get nodes --show-labels |\ kubectl --kubeconfig {} get nodes none --show-labels |tr -s ' ' | cut -f6 -d' '".format(KUBE_ADMIN_CONF)],
grep none | tr -s ' ' | cut -f6 -d' '".format(KUBE_ADMIN_CONF)],
common_test.PROC_OUT: ["foo=bar,hello=world"], common_test.PROC_OUT: ["foo=bar,hello=world"],
common_test.POST: { common_test.POST: {
"foo": "bar", "foo": "bar",
@ -39,8 +40,7 @@ kubectl --kubeconfig {} get nodes --show-labels |\
common_test.TRIGGER_THROW: True, common_test.TRIGGER_THROW: True,
common_test.RETVAL: -1, common_test.RETVAL: -1,
common_test.PROC_CMD: ["\ common_test.PROC_CMD: ["\
kubectl --kubeconfig {} get nodes --show-labels |\ kubectl --kubeconfig {} get nodes none --show-labels |tr -s ' ' | cut -f6 -d' '".format(KUBE_ADMIN_CONF)],
grep none | tr -s ' ' | cut -f6 -d' '".format(KUBE_ADMIN_CONF)],
common_test.POST: { common_test.POST: {
}, },
common_test.PROC_KILLED: 1 common_test.PROC_KILLED: 1
@ -49,8 +49,7 @@ kubectl --kubeconfig {} get nodes --show-labels |\
common_test.DESCR: "read labels fail", common_test.DESCR: "read labels fail",
common_test.RETVAL: -1, common_test.RETVAL: -1,
common_test.PROC_CMD: ["\ common_test.PROC_CMD: ["\
kubectl --kubeconfig {} get nodes --show-labels |\ kubectl --kubeconfig {} get nodes none --show-labels |tr -s ' ' | cut -f6 -d' '".format(KUBE_ADMIN_CONF)],
grep none | tr -s ' ' | cut -f6 -d' '".format(KUBE_ADMIN_CONF)],
common_test.PROC_OUT: [""], common_test.PROC_OUT: [""],
common_test.PROC_ERR: ["command failed"], common_test.PROC_ERR: ["command failed"],
common_test.POST: { common_test.POST: {
@ -65,8 +64,7 @@ write_labels_test_data = {
common_test.RETVAL: 0, common_test.RETVAL: 0,
common_test.ARGS: { "foo": "bar", "hello": "World!", "test": "ok" }, common_test.ARGS: { "foo": "bar", "hello": "World!", "test": "ok" },
common_test.PROC_CMD: [ common_test.PROC_CMD: [
"kubectl --kubeconfig {} get nodes --show-labels |\ "kubectl --kubeconfig {} get nodes none --show-labels |tr -s ' ' | cut -f6 -d' '".format(KUBE_ADMIN_CONF),
grep none | tr -s ' ' | cut -f6 -d' '".format(KUBE_ADMIN_CONF),
"kubectl --kubeconfig {} label --overwrite nodes none hello-".format( "kubectl --kubeconfig {} label --overwrite nodes none hello-".format(
KUBE_ADMIN_CONF), KUBE_ADMIN_CONF),
"kubectl --kubeconfig {} label --overwrite nodes none hello=World! test=ok".format( "kubectl --kubeconfig {} label --overwrite nodes none hello=World! test=ok".format(
@ -79,8 +77,7 @@ write_labels_test_data = {
common_test.RETVAL: 0, common_test.RETVAL: 0,
common_test.ARGS: { "foo": "bar", "hello": "world" }, common_test.ARGS: { "foo": "bar", "hello": "world" },
common_test.PROC_CMD: [ common_test.PROC_CMD: [
"kubectl --kubeconfig {} get nodes --show-labels |\ "kubectl --kubeconfig {} get nodes none --show-labels |tr -s ' ' | cut -f6 -d' '".format(KUBE_ADMIN_CONF)
grep none | tr -s ' ' | cut -f6 -d' '".format(KUBE_ADMIN_CONF)
], ],
common_test.PROC_OUT: ["foo=bar,hello=world"] common_test.PROC_OUT: ["foo=bar,hello=world"]
}, },
@ -90,8 +87,7 @@ write_labels_test_data = {
common_test.ARGS: { "any": "thing" }, common_test.ARGS: { "any": "thing" },
common_test.RETVAL: -1, common_test.RETVAL: -1,
common_test.PROC_CMD: [ common_test.PROC_CMD: [
"kubectl --kubeconfig {} get nodes --show-labels |\ "kubectl --kubeconfig {} get nodes none --show-labels |tr -s ' ' | cut -f6 -d' '".format(KUBE_ADMIN_CONF)
grep none | tr -s ' ' | cut -f6 -d' '".format(KUBE_ADMIN_CONF)
], ],
common_test.PROC_ERR: ["read failed"] common_test.PROC_ERR: ["read failed"]
} }
@ -114,10 +110,22 @@ none".format(KUBE_ADMIN_CONF),
"mkdir -p {}".format(CNI_DIR), "mkdir -p {}".format(CNI_DIR),
"cp {} {}".format(FLANNEL_CONF_FILE, CNI_DIR), "cp {} {}".format(FLANNEL_CONF_FILE, CNI_DIR),
"systemctl start kubelet", "systemctl start kubelet",
"kubeadm join --discovery-file {} --node-name none".format( "kubeadm join --discovery-file {} --node-name none --apiserver-advertise-address FC00:2::32".format(
KUBE_ADMIN_CONF) KUBE_ADMIN_CONF)
], ],
common_test.PROC_RUN: [True, True] common_test.PROC_RUN: [True, True],
common_test.PRE: {
common_test.CONFIG_DB_NO: {
common_test.MGMT_INTERFACE_TABLE: {
"eth0|FC00:2::32/64": {
"gwaddr": "fc00:2::1"
}
}
}
},
common_test.REQ: {
"data": {"ca.crt": "test"}
}
}, },
1: { 1: {
common_test.DESCR: "Regular secure join", common_test.DESCR: "Regular secure join",
@ -135,10 +143,22 @@ none".format(KUBE_ADMIN_CONF),
"mkdir -p {}".format(CNI_DIR), "mkdir -p {}".format(CNI_DIR),
"cp {} {}".format(FLANNEL_CONF_FILE, CNI_DIR), "cp {} {}".format(FLANNEL_CONF_FILE, CNI_DIR),
"systemctl start kubelet", "systemctl start kubelet",
"kubeadm join --discovery-file {} --node-name none".format( "kubeadm join --discovery-file {} --node-name none --apiserver-advertise-address FC00:2::32".format(
KUBE_ADMIN_CONF) KUBE_ADMIN_CONF)
], ],
common_test.PROC_RUN: [True, True] common_test.PROC_RUN: [True, True],
common_test.PRE: {
common_test.CONFIG_DB_NO: {
common_test.MGMT_INTERFACE_TABLE: {
"eth0|FC00:2::32/64": {
"gwaddr": "fc00:2::1"
}
}
}
},
common_test.REQ: {
"data": {"ca.crt": "test"}
}
}, },
2: { 2: {
common_test.DESCR: "Skip join as already connected", common_test.DESCR: "Skip join as already connected",
@ -228,11 +248,17 @@ clusters:\n\
s.close() s.close()
with open(FLANNEL_CONF_FILE, "w") as s: with open(FLANNEL_CONF_FILE, "w") as s:
s.close() s.close()
with open(AME_CRT, "w") as s:
s.close()
with open(AME_KEY, "w") as s:
s.close()
kube_commands.KUBELET_YAML = kubelet_yaml kube_commands.KUBELET_YAML = kubelet_yaml
kube_commands.CNI_DIR = CNI_DIR kube_commands.CNI_DIR = CNI_DIR
kube_commands.FLANNEL_CONF_FILE = FLANNEL_CONF_FILE kube_commands.FLANNEL_CONF_FILE = FLANNEL_CONF_FILE
kube_commands.SERVER_ADMIN_URL = "file://{}".format(self.admin_conf_file) kube_commands.SERVER_ADMIN_URL = "file://{}".format(self.admin_conf_file)
kube_commands.KUBE_ADMIN_CONF = KUBE_ADMIN_CONF kube_commands.KUBE_ADMIN_CONF = KUBE_ADMIN_CONF
kube_commands.AME_CRT = AME_CRT
kube_commands.AME_KEY = AME_KEY
@patch("kube_commands.subprocess.Popen") @patch("kube_commands.subprocess.Popen")
@ -295,11 +321,13 @@ clusters:\n\
json.dumps(labels, indent=4))) json.dumps(labels, indent=4)))
assert False assert False
@patch("kube_commands.requests.get")
@patch("kube_commands.swsscommon.DBConnector")
@patch("kube_commands.swsscommon.Table")
@patch("kube_commands.subprocess.Popen") @patch("kube_commands.subprocess.Popen")
def test_join(self, mock_subproc): def test_join(self, mock_subproc, mock_table, mock_conn, mock_reqget):
self.init() self.init()
common_test.set_kube_mock(mock_subproc) common_test.set_kube_mock(mock_subproc, mock_table, mock_conn, mock_reqget)
for (i, ct_data) in join_test_data.items(): for (i, ct_data) in join_test_data.items():
lock_file = "" lock_file = ""