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:
- 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
- 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
- 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: