The simplest k8s User JWT token manager in the whole network

Keywords: Linux Kubernetes curl JSON SSL

Three-step installation of kubernetes cluster

Summary

The token of the kubernetes server account is easy to obtain, but the token of the User is very troublesome. This paper presents a minimal way to generate the User token, which can be obtained by the User with an http request.

What is token mainly used for?

The official dashboard login is required. If you log in using a kubeconfig file without a token in the file, you will fail. Most articles now describe using service account token to log in to dashboard. Yes, but there are problems:
First: When binding roles, specify the type of service account:

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
  labels:
    k8s-app: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount   # This is not the User type.
  name: kubernetes-dashboard
  namespace: kube-system

Secondly, to understand that the kubeconfig parse certificate takes CN as user name, then service account is still two accounts, even if it is the same as CNs. It needs to be bound twice when Binding roles. It's a bit like giving service account to "people". So it's not always appropriate to throw service account token to a developer. Service account token is more often used by programs.

If you want to call https directly, without token, you will:

[root@iZj6cegflzze2l7fpcqoerZ ssl]# curl https://172.31.12.61:6443/api/v1/namespaces/default/pods --insecure
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {

  },
  "status": "Failure",
  "message": "pods is forbidden: User \"system:anonymous\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
  "reason": "Forbidden",
  "details": {
    "kind": "pods"
  },
  "code": 403
}

Because there is no authentication information, anonymous users do not have any privileges.

Adding token is like this:

[root@iZj6cegflzze2l7fpcqoerZ ssl]# curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkNnYzRPVEV5TlRVM0VnWm5hWFJvZFdJIn0.eyJpc3MiOiJodHRwczovL2RleC5leGFtcGxlLmNvbTo4MDgwIiwic3ViIjoiQ2djNE9URXlOVFUzRWdabmFYUm9kV0kiLCJhdWQiOiJleGFtcGxlLWFwcCIsImV4cCI6MTU1MTA5NzkwNiwiaWF0IjoxNTUwNzM3OTA2LCJlbWFpbCI6ImZodGpvYkBob3RtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJncm91cHMiOlsiZGV2Il0sIm5hbWUiOiJmYW51eCJ9.ZqKn461UW0aGtyjyqu2Dc5tiUzC-6eYLag542d3AvklUdZuw8i9XwyaUg_f1OAj0ZsEcOybOe9_PeGMaUYzU0OvlKPY-q2zbQVC-m6u6sQw6ZXx8pi0W8k4wQSJnMaOLddCfurlYufmr8kScDBQlnKapSR0F9mJzvpKkHD-XNshQKWhX3n03g7OfFgb4RuhLjKDNQnoGn7DfBNntibHlF9sPo0jC5JjqTZaGvoGmiRE4PAXwxA-RJifsWDNf_jW8lrDiY4NSO_3O081cia4N1GKht51q9W3eaNMvFDD9hje7abDdZoz9KPi2vc3zvgH7cNv0ExVHKaA0-dwAZgTx4g" -k https://172.31.12.61:6443/api/v1/namespaces/default/pods
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {

  },
  "status": "Failure",
  "message": "pods is forbidden: User \"https://dex.example.com:8080#fanux\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
  "reason": "Forbidden",
  "details": {
    "kind": "pods"
  },
  "code": 403
}

Look, it's still 403, but it already has user information. As long as the user is authorized, it can be accessed normally. How to authorize is described below.

Introduction to token species

There are many ways to generate token, which are mainly divided into three types:

  1. service account token, which is created by service account, is easy to get in secret, but it needs to be distinguished well. User and service account differences
  2. Ordinary token, this kind of token is a common string, usually write an authenticated web hook by oneself, call this hook when k8s authenticates to query whether token is valid, compare low
  3. jwt(josn web token) based on openid, the authentication center puts user information in json, encrypts it with private key, and decrypts it with public key after k8s gets token. As long as the token is decrypted successfully, it is legal and can get user information, so it does not need to request from the authentication center any more.

jwt based on openid is the focus of this paper.

What the community uses more is dex It is a relatively complete implementation, but for those who are not familiar with the technology, it is still a little threshold and easy to go around. There are also some inconvenient problems.
If dependency is complex, we need a real user management program, such as ldap or an auth2 server, which is acceptable. The key is that authentication may need to rely on browsers for jump authorization, which is very embarrassing in many scenarios, such as our scenario does not have at all.
Interface, so token generation becomes a big problem. Secondly, when integrated into other systems, users have already logged in, so it needs a second authorization process to get token, which makes it difficult to design the system because of heavy dependence.
However, if it is not integrated into other systems, such as developing a completed PaaS platform from 0, using dex is still a perfect solution.

So we implemented a simple and crude scheme, completely liberating the process, only the core of care.

Introduction to sealyun fist

What do we want?

input:

{
    "User": "fanux",
    "Group": ["sealyun", "develop"]
}

output:

eyJhbGciOiJSUzI1NiIsImtpZCI6IkNnYzRPVEV5TlRVM0VnWm5hWFJvZFdJIn0.eyJpc3MiOiJodHRwczovL2RleC5leGFtcGxlLmNvbTo4MDgwIiwic3ViIjoiQ2djNE9URXlOVFUzRWdabmFYUm9kV0kiLCJhdWQiOiJleGFtcGxlLWFwcCIsImV4cCI6MTU1MTA5NzkwNiwiaWF0IjoxNTUwNzM3OTA2LCJlbWFpbCI6ImZodGpvYkBob3RtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJncm91cHMiOlsiZGV2Il0sIm5hbWUiOiJmYW51eCJ9.ZqKn461UW0aGtyjyqu2Dc5tiUzC-6eYLag542d3AvklUdZuw8i9XwyaUg_f1OAj0ZsEcOybOe9_PeGMaUYzU0OvlKPY-q2zbQVC-m6u6sQw6ZXx8pi0W8k4wQSJnMaOLddCfurlYufmr8kScDBQlnKapSR0F9mJzvpKkHD-XNshQKWhX3n03g7OfFgb4RuhLjKDNQnoGn7DfBNntibHlF9sPo0jC5JjqTZaGvoGmiRE4PAXwxA-RJifsWDNf_jW8lrDiY4NSO_3O081cia4N1GKht51q9W3eaNMvFDD9hje7abDdZoz9KPi2vc3zvgH7cNv0ExVHKaA0-dwAZgTx4g

End, how simple, don't be so useless.

So in order to achieve the above functions, we developed fist fist's auth module implements the token generation function and jwt function, which are the core of dex.

sealyun fist/auth using tutorials

Installation and deployment

Generate certificates

# mkdir /etc/kubernetes/pki/fist
# cd /etc/kubernetes/pki/fist
# sh gencert.sh # Code in script content

Start fist auth module

kubectl create -f deploy/fist-auth.yaml

Modify k8s apiserver startup parameters

vim /etc/kubernetes/manifests/kube-apiserver.yaml
  - command:
    - kube-apiserver
    - --oidc-issuer-url=https://fist.sealyun.svc.cluster.local:8080
    - --oidc-client-id=example-app
    - --oidc-ca-file=/etc/kubernetes/pki/fist/ca.pem
    - --oidc-username-claim=name
    - --oidc-groups-claim=groups

Getting and using token

Get token

curl https://fist.sealyun.svc.cluster.local:8080/token?user=fanux&group=sealyun,develop --cacert ca.pem

Using token

Direct curl plus bare token see above

Join kubeconfig:

kubectl config set-credentials --token=eyJhbGciOiJSUzI1NiIsImtpZCI6IkNnYzRPVEV5TlRVM0VnWm5hWFJvZFdJIn0.eyJpc3MiOiJodHRwczovL2RleC5leGFtcGxlLmNvbTo4MDgwIiwic3ViIjoiQ2djNE9URXlOVFUzRWdabmFYUm9kV0kiLCJhdWQiOiJleGFtcGxlLWFwcCIsImV4cCI6MTU1MTEwMDI5MywiaWF0IjoxNTUwNzQwMjkzLCJlbWFpbCI6ImZodGpvYkBob3RtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJncm91cHMiOlsiZGV2Il0sIm5hbWUiOiJmYW51eCJ9.OAK4oIYqJszm1EACYW2neXTo738RW9kXFOIN5bOT4Z2CeKAvYqyOVKCWZf04xX45jwT78mATR3uas2YvRooDXlvxaD3K43ls4KBSG-Ofp-ynqlcVTpD3sUDqyux2iieNv4N6IyCv11smrU0lIlkrQC6oyxzTGae1FrJVGc5rHNsIRZHp2WrQvw83uLn_elHgUfSlsOq0cPtVONaAQWMAMi2DX-y5GCNpn1CDvudGJihqsTciPx7bj0AOXyiOznWhV186Ybk-Rgqn8h0eBaQhFMyNpwVt6oIP5pvJQs0uoODeRv6P3I3-AjKyuCllh9KDtlCVvSP4WtMUTfHQN4BigQ  kubernetes-admin

Then the user.client-certifacate-data and client-key-data in the. kube/config file can be deleted, and then the kubectl will be executed:

[root@iZj6cegflzze2l7fpcqoerZ ~]# kubectl get pod
Error from server (Forbidden): pods is forbidden: User "https://dex.example.com:8080#fanux" cannot list resource "pods" in API group "" in the namespace "default"

Explain that the new user has succeeded

To grant authorization

[root@iZj6cegflzze2l7fpcqoerZ ~]# cat rolebind.yaml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: read-secrets-global
subjects:
- kind: User
  name: "https://dex.example.com:8080#fanux" # Name is case sensitive
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin  # Super user gives it to him
  apiGroup: rbac.authorization.k8s.io

Create a role binding:

[root@iZj6cegflzze2l7fpcqoerZ ~]# kubectl  --kubeconfig /etc/kubernetes/admin.conf create  -f rolebind.yaml # Using the administrator's kubeconfig
clusterrolebinding.rbac.authorization.k8s.io/read-secrets-global created
[root@iZj6cegflzze2l7fpcqoerZ ~]# kubectl get pod # Have access to pod
No resources found.

Principle introduction

jwt principle

                       https://fist.sealyun.cluster.local:8080
k8s                                             jwt server
 |   /.well-known/openid-configuration             |
 |------------------------------------------------>|  k8s Through this url Finding some information, the most important thing is to use it for verification. token Address of public key
 |   discover info                                 |
 |<------------------------------------------------|
 |     /keys                                       |
 |------------------------------------------------>|  The last step is to get the address, and this step is to get the public key.
 |     public keys                                 |
 |<------------------------------------------------|
 |                                                 |

discoer info is a json:

{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"revocation_endpoint": "https://oauth2.googleapis.com/revoke",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token",
"none"
],
...

public keys is also a json like:

{
"keys": [
{
"e": "AQAB",
"kty": "RSA",
"alg": "RS256",
"n": "3MdFK4pXPvehMipDL_COfqn6o9soHgSaq_V1o8U_5gTZ-j9DxO9PV7BVncXBgHFctnp3JQ1QTDF7txeHeuLOS4KziRw5r4ohaj2WoOTqXh7lqVMR2YDAcBK46asS177NpkQ1CqHIsy3kNfqhXLwTaKfdlwdA_XUfRbKORWbq0kDxV35egx35nHl5qJ6aP6fcpsnnPvHf7KWO0zkdvwuR-IX79HjqUAEg5UERd5FK4y06PRbxuXHjAgVhHu_sk4reNXNp1HRuTYtQ26DFbVaIjsWb8-nQC8-7FkTjlw9FteAwLVGOm9sTLFp73jAf0pWLh7sJ02pBxZKjsxLO1Lvg7w",
"use": "sig",
"kid": "7c309e3a1c1999cb0404ab7125ee40b7cdbcaf7d"
},
{
"alg": "RS256",
"n": "2K7epoJWl_B68lRUi1txaa0kEuIK4WHiHpi1yC4kPyu48d046yLlrwuvbQMbog2YTOZdVoG1D4zlWKHuVY00O80U1ocFmBl3fKVrUMakvHru0C0mAcEUQo7ItyEX7rpOVYtxlrVk6G8PY4EK61EB-Xe35P0zb2AMZn7Tvm9-tLcccqYlrYBO4SWOwd5uBSqc_WcNJXgnQ-9sYEZ0JUMhKZelEMrpX72hslmduiz-LMsXCnbS7jDGcUuSjHXVLM9tb1SQynx5Xz9xyGeN4rQLnFIKvgwpiqnvLpbMo6grhJwrz67d1X6MwpKtAcqZ2V2v4rQsjbblNH7GzF8ZsfOaqw",
"use": "sig",
"kid": "7d680d8c70d44e947133cbd499ebc1a61c3d5abc",
"e": "AQAB",
"kty": "RSA"
}
]
}

So fist only needs to implement these two URLs and use private key to encrypt user information to generate token.

Create key pairs:

    key, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        log.Fatalf("gen rsa key: %v", err)
    }
    priv = jose.JSONWebKey{
        Key:       key,
        KeyID:     "Cgc4OTEyNTU3EgZnaXRodWI",
        Algorithm: "RS256",
        Use:       "sig",
    }
    pub = jose.JSONWebKey{
        Key:       key.Public(),
        KeyID:     "Cgc4OTEyNTU3EgZnaXRodWI",
        Algorithm: "RS256",
        Use:       "sig",
    }

Private key encryption:

    tok := idTokenClaims{
        Issuer:        "https://dex.example.com:8080",
        Subject:       "Cgc4OTEyNTU3EgZnaXRodWI",
        Audience:      "example-app",
        Expiry:        time.Now().Add(time.Hour * 100).Unix(),
        IssuedAt:      time.Now().Unix(),
        Email:         "fhtjob@hotmail.com",
        EmailVerified: &ev,
        Groups:        []string{"dev"},
        Name:          "fanux",
    }

    payload, err := json.Marshal(&tok)
    if err != nil {
        return
    }

    var idToken string
    if idToken, err = signPayload(&Priv, signingAlg, payload); err != nil {
        return

summary

fist core code is already available, but it needs to be further combed in order to make it easier to use. Please look forward to it. Authentication is only one of its functions. fist positioning is a minimal k8s management platform.

Exploring additive QQ groups: 98488045

Public address:

Posted by Deltran on Thu, 21 Feb 2019 07:18:20 -0800