当前位置: 首页 > 工具软件 > aws-eks-base > 使用案例 >

通过ServiceAccount创建eks集群的kubeconfig文件来绕过IAM鉴权

李兴庆
2023-12-01

目录

应用背景 

准备步骤

了解生成config文件的两种方式

请求 api 的一般步骤

安装 AWS CLI和生成原生config

安装 jq 工具

执行步骤

根据 serviceaccount 生成 kubeconfig

为 SA 绑定 RBAC

测试config以及合并

总结


应用背景 

第一个场景是由于 aws 控制台的 eks 产品并不具备导出 kubeconfig 功能,所以需要我们手动导出,但是该文件在使用的时候需要安装 aws cli 工具并通过输入 ak/sk 访问密钥等信息来通过 iam 认证,而且在使用 kubectl 过程中每次都要走这个校验流程,会增加至少 500ms 的延迟,这对使用者来说非常影响体验。


第二个场景是有时为了需要,我们需要给出一些具有特定集群权限的kubeconfig文件,这时我们可以通过使用serviceAccount来制作具有一定集群权限的kubeconfig

所以我们创建一个 serviceaccount 并配置 RBAC 权限,通过取出这个 SA 的 Secret 里的 User TOKEN 和 CA 证书来配置新的 kubeconfig 文件。

准备步骤

了解生成config文件的两种方式

——UserAccount 和 ServiceAccount

通过 UserAccount 创建的 config 文件需要写入ca证书,客户端证书和 key。config文件的创建需要指定用户名,这个用户名和创建证书时写入的 CommonName(CN)不同,并不需要保持完全一致;在创建 binding 时指定的是用户以及对应的用户名,这个用户名需要和 CN 一致

serviceAccount 创建的 config 文件需要写入 ca 证书,以及 user token,这里同样不需要刻意指定用户名,因为真正将 config 文件与 ServiceAccount 进行关联的是 userToken。user token 是从 serviceaccount 的 secret 中取的,在创建 binding 时指定的是 sa 以及对应的 sa 名称

1.24 版本创建 serviceAccount 默认不会创建包含 ca 证书和 user token 的 secret,需要自行关联

 

请求 api 的一般步骤

k8s默认api server在master节点的6443端口上,我们通过curl命令请求需要经过https tls认证,否则会报没权限,一般有两种认证方式,客户端证书或者token的方式。这两种方式在curl中的参数体现分别是--cert=xxx/client.crt --key=xxx/client.key以及-H "Authorization: Bearer $TOKEN"。除此之后还需要配置服务端证书 --cacert=xxx/ca.crt或者--insecure来跳过服务端校验。

这里大概说一下生成客户端证书的过程,当k8s部署好之后会在/etc/kubenetes/pki目录下生成服务端证书文件ca.crt。我们需要在本地生成客户端私钥client.key

openssl genrsa -out client.key 2048

(如果服务端缺少证书ca.key,用同样的命令生成就可以了) 

然后根据私钥文件带入一定的用户信息,生成请求文件client.csr

openssl req -new -key client.key -out client.csr -subj "CN=xxxx"

(其中CN=Common Name,这部分是用户自定义的信息)

最后再根据k8s的服务端证书生成我们用户的客户端证书

openssl x509 -req -in client.csr -CA /etc/kubenetes/pki/ca.crt -CAkey /etc/kubenetes/pki/ca.key -CAcreateserial -out client.crt -days 365

这还没完,我们现在创建了一个名为xxxx用户的证书,还需要设置上下文。

我们需要把crt贴到config文件中去:

# kubectl config —kubeconfig=/root/.kube/config set-credentials your-user —client-certificate=/root/tls/client.crt —client-key=/root/tls/client.key
User “your-user” set.

这一步把用户设置进去了,执行kubectl命令时会切换成我们的用户。

然后根据用户名和集群名生成context上下文

# kubectl config —kubeconfig=/root/.kube/config set-context your-context —cluster=your-cluster —user=your-user
Context “your-context” created.

然后就可以通过use-context指令来切换上下文。

此外,我们可能还要通过创建role和clusterrole并去binding来为刚生成的useraccount来生成对应的权限。

而token认证的方式简单一点,我们只需要创建service account,也是通过 clusterrole 和 role 去对该serviceaccount的权限进行绑定,生成serviceaccount会自动创建一个secret,我们可以在这个secret内取到服务端ca证书和token密钥,进而进行认证

安装 AWS CLI和生成原生config

首先默认已经通过官方的提供的方法生成一个 kubeconfig,这里不在赘述,具体方法见下链接:

建立 Amazon EKS 的 kubeconfig

执行文档中的命令需要在本地安装 AWS CLI,安装的命令(MacOS)为:

curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /

其他的操作系统的安装方法在下面的链接中获得:

Installing or updating the latest version of the AWS CLI

安装 jq 工具

我们需要安装 jq 工具来对获取到的 secret 内容中的一些json字符串做转换处理写入到config(yaml格式)文件中去。

通过以下命令安装

brew install oniguruma
brew install jq

执行步骤

根据 serviceaccount 生成 kubeconfig

我们可以借用一段开源脚本来帮助我们去生成 kubeconfig,脚本入参是 SA的名称和其对应的命名空间

脚本内容如下 :

#!/bin/bash
set -e
set -o pipefail
 
# Add user to k8s using service account, no RBAC (must create RBAC after this script)
if [[ -z "$1" ]] || [[ -z "$2" ]]; then
 echo "usage: $0 <service_account_name> <namespace>"
 exit 1
fi
 
SERVICE_ACCOUNT_NAME=$1
NAMESPACE="$2"
KUBECFG_FILE_NAME="/tmp/kube/k8s-${SERVICE_ACCOUNT_NAME}-${NAMESPACE}-conf"
TARGET_FOLDER="/tmp/kube"
 
create_target_folder() {
    echo -n "Creating target directory to hold files in ${TARGET_FOLDER}..."
    mkdir -p "${TARGET_FOLDER}"
    printf "done"
}
 
create_service_account() {
    echo -e "\\nCreating a service account in ${NAMESPACE} namespace: ${SERVICE_ACCOUNT_NAME}"
    kubectl create sa "${SERVICE_ACCOUNT_NAME}" --namespace "${NAMESPACE}"
}
 
get_secret_name_from_service_account() {
    echo -e "\\nGetting secret of service account ${SERVICE_ACCOUNT_NAME} on ${NAMESPACE}"
    SECRET_NAME=$(kubectl get sa "${SERVICE_ACCOUNT_NAME}" --namespace="${NAMESPACE}" -o json | jq -r .secrets[].name)
    echo "Secret name: ${SECRET_NAME}"
}
 
extract_ca_crt_from_secret() {
    echo -e -n "\\nExtracting ca.crt from secret..."
    kubectl get secret --namespace "${NAMESPACE}" "${SECRET_NAME}" -o json | jq \
    -r '.data["ca.crt"]' | base64 -D > "${TARGET_FOLDER}/ca.crt"
    printf "done"
}
 
get_user_token_from_secret() {
    echo -e -n "\\nGetting user token from secret..."
    USER_TOKEN=$(kubectl get secret --namespace "${NAMESPACE}" "${SECRET_NAME}" -o json | jq -r '.data["token"]' | base64 -D)
    printf "done"
}
 
set_kube_config_values() {
    context=$(kubectl config current-context)
    echo -e "\\nSetting current context to: $context"
 
    CLUSTER_NAME=$(kubectl config get-contexts "$context" | awk '{print $3}' | tail -n 1)
    echo "Cluster name: ${CLUSTER_NAME}"
 
    ENDPOINT=$(kubectl config view \
    -o jsonpath="{.clusters[?(@.name == \"${CLUSTER_NAME}\")].cluster.server}")
    echo "Endpoint: ${ENDPOINT}"
 
    # Set up the config
    echo -e "\\nPreparing k8s-${SERVICE_ACCOUNT_NAME}-${NAMESPACE}-conf"
    echo -n "Setting a cluster entry in kubeconfig..."
    kubectl config set-cluster "${CLUSTER_NAME}" \
    --kubeconfig="${KUBECFG_FILE_NAME}" \
    --server="${ENDPOINT}" \
    --certificate-authority="${TARGET_FOLDER}/ca.crt" \
    --embed-certs=true
 
    echo -n "Setting token credentials entry in kubeconfig..."
    kubectl config set-credentials \
    "${SERVICE_ACCOUNT_NAME}-${NAMESPACE}-${CLUSTER_NAME}" \
    --kubeconfig="${KUBECFG_FILE_NAME}" \
    --token="${USER_TOKEN}"
 
    echo -n "Setting a context entry in kubeconfig..."
    kubectl config set-context \
    "${SERVICE_ACCOUNT_NAME}-${NAMESPACE}-${CLUSTER_NAME}" \
    --kubeconfig="${KUBECFG_FILE_NAME}" \
    --cluster="${CLUSTER_NAME}" \
    --user="${SERVICE_ACCOUNT_NAME}-${NAMESPACE}-${CLUSTER_NAME}" \
    --namespace="${NAMESPACE}"
 
    echo -n "Setting the current-context in the kubeconfig file..."
    kubectl config use-context "${SERVICE_ACCOUNT_NAME}-${NAMESPACE}-${CLUSTER_NAME}" \
    --kubeconfig="${KUBECFG_FILE_NAME}"
}
 
create_target_folder
# create_service_account
get_secret_name_from_service_account
extract_ca_crt_from_secret
get_user_token_from_secret
set_kube_config_values
 
echo -e "\\nAll done! Test with:"
echo "KUBECONFIG=${KUBECFG_FILE_NAME} kubectl get pods"
echo "you should not have any permissions by default - you have just created the authentication part"
echo "You will need to create RBAC permissions"
KUBECONFIG=${KUBECFG_FILE_NAME} kubectl get pods

这段脚本的出处:

Create a service account and generate a kubeconfig file for it - this will also set the default namespace for the user

因为原生的kubeconfig携带的集群名称比较长,脚本中截取了该集群名称 ${SERVICE_ACCOUNT_NAME}-${NAMESPACE}-${CLUSTER_NAME} 作为 credentials、user 和 context的配置名称,不方便我们切换上下文,如果有需要,该部分也可以自定义字符串,不会影响文件的使用。或者是在生成 config文件后手动去修改。

值得一提的是,serviceaccount 虽然有命名空间的指定,但是仍可为其绑定 clusterrole 赋予集群维度的权限,这是由于:

当创建SA时,TokenController 作为 kube-controller-manager 的一部分运行,并为其创建了相应的服务账号令牌 Secret 以允许访问 API。当创建 Pod 时,隐式地指定了 SA 为 default ,default 的元数据命名空间为 default ,这就是为什么当我们创建一个 Pod 实例而不去指定命名空间时,默认会创建到 default 命名空间下。而如果显式指定了 SA ,则会将该令牌绑定到 Pod 实例上,并将 kube-apiserver作为其受众,以该 SA 去创建 Pod 时,就会打到该 SA 的命名空间下。

执行该脚本,进行 SA 的创建和 kubeconfig 的生成

./kubernetes_add_service_account_kubeconfig.sh eks-admin default

如果操作系统为ubuntu,执行的时候遇到 set: Illegal option -o pipefail,原因和解决方案可以参考:

ubuntu”set Illegal option -o pipefail”

解决方法是将dash切换成bash

为 SA 绑定 RBAC

因为我做 kubconfig 导出仍然是作为系统管理员在本地自用,而不是做权限分发,所以我绑定了系统管理员权限的 clusterrole

SA=eks-admin
kubectl create clusterrolebinding ${SA}-binding --clusterrole=cluster-admin --serviceaccount=default:${SA}

测试config以及合并

KUBECONFIG="/tmp/kube/k8s-eks-default-conf"
kubectl get pods

 如果要和本地config进行合并,进行多集群管理,参考我的另一篇文章

通过将多个 kubeconfig 文件合并为一个来进行K8S多集群统一管理

总结

至此,我们通过创建 SA 的方式生成了包含其 Secret TOKEN的 kubeconfig 的文件,并且通过该文件访问 kube- APIserver 能够有效避开 IAM 鉴权,能够加强我们敲 kuebctl命令的体验,并且通过这种方式,也能够根据不同的 clusterrole & role 去生成不同的 kubeconfig 文件做权限的分发。

参考资料:

建立 Amazon EKS 的 kubeconfig

Installing or updating the latest version of the AWS CLI

Create a service account and generate a kubeconfig file for it - this will also set the default namespace for the user

ubuntu”set Illegal option -o pipefail”

管理服务账号

通过将多个 kubeconfig 文件合并为一个来进行K8S多集群统一管理

 类似资料: