Skip to content

Commit 1ae6e66

Browse files
authored
Merge pull request #14721 from clarketm/gencred-cli
Add a "gencred" CLI w/ kubeconfig generation support
2 parents a94a48b + d5888df commit 1ae6e66

File tree

21 files changed

+1013
-270
lines changed

21 files changed

+1013
-270
lines changed

BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ filegroup(
100100
"//experiment:all-srcs",
101101
"//gcsweb/cmd/gcsweb:all-srcs",
102102
"//gcsweb/pkg/version:all-srcs",
103+
"//gencred:all-srcs",
103104
"//ghproxy:all-srcs",
104105
"//gopherage:all-srcs",
105106
"//greenhouse:all-srcs",
@@ -121,7 +122,6 @@ filegroup(
121122
"//metrics:all-srcs",
122123
"//pkg/benchmarkjunit:all-srcs",
123124
"//pkg/flagutil:all-srcs",
124-
"//pkg/gencred:all-srcs",
125125
"//pkg/genyaml:all-srcs",
126126
"//pkg/ghclient:all-srcs",
127127
"//pkg/io:all-srcs",

gencred/BUILD.bazel

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
2+
3+
filegroup(
4+
name = "package-srcs",
5+
srcs = glob(["**"]),
6+
tags = ["automanaged"],
7+
visibility = ["//visibility:private"],
8+
)
9+
10+
filegroup(
11+
name = "all-srcs",
12+
srcs = [
13+
":package-srcs",
14+
"//gencred/cmd/gencred:all-srcs",
15+
"//gencred/pkg/certificate:all-srcs",
16+
"//gencred/pkg/kubeconfig:all-srcs",
17+
"//gencred/pkg/serviceaccount:all-srcs",
18+
"//gencred/pkg/util:all-srcs",
19+
],
20+
tags = ["automanaged"],
21+
visibility = ["//visibility:public"],
22+
)
23+
24+
go_library(
25+
name = "go_default_library",
26+
srcs = ["main.go"],
27+
importpath = "k8s.io/test-infra/gencred",
28+
visibility = ["//visibility:private"],
29+
deps = ["//gencred/cmd/gencred:go_default_library"],
30+
)
31+
32+
go_binary(
33+
name = "gencred",
34+
embed = [":go_default_library"],
35+
visibility = ["//visibility:public"],
36+
)

gencred/OWNERS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# See the OWNERS docs at https://go.k8s.io/owners
2+
3+
approvers:
4+
- clarketm
5+
- fejta

gencred/README.md

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# Gencred
2+
3+
## Description
4+
5+
`gencred` is a simple tool used to generate cluster auth credentials (w/ [**cluster-admin**](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) permissions) for authenticating to a Kubernetes cluster.
6+
> **NOTE:** since `gencred` creates credentials with `cluster-admin` level access, the kube context used **must** also be bound to the `cluster-admin` role.
7+
8+
## Usage
9+
10+
### Script
11+
12+
Run using Bazel:
13+
14+
```console
15+
$ bazel run //gencred -- <options>
16+
```
17+
18+
Run using Golang:
19+
20+
```console
21+
$ go run k8s.io/test-infra/gencred <options>
22+
```
23+
24+
The following is a list of supported options for the `gencred` CLI. All options are *optional*.
25+
> If a `--context` is not specified, then the *current kubeconfig context* is used.
26+
27+
```console
28+
-c, --certificate Authorize with a client certificate and key.
29+
--context string The name of the kubeconfig context to use.
30+
-n, --name string Context name for the kubeconfig entry. (default "build")
31+
-o, --output string Output path for generated kubeconfig file. (default "/dev/stdout")
32+
--overwrite Overwrite (rather than merge) output file if exists.
33+
-s, --serviceaccount Authorize with a service account. (default true)
34+
```
35+
36+
Create a kubeconfig entry with context name `mycluster` using `serviceaccount` authorization and output to a file `config.yaml`.
37+
> `serviceaccount` authorization is the *default* if neither `-s, --serviceaccount` nor `-c, --certificate` is specified.
38+
39+
```console
40+
$ gencred --name mycluster --output ./config.yaml --serviceaccount
41+
```
42+
43+
The kubeconfig contents will be `output` to `./config.yaml`:
44+
45+
```yaml
46+
apiVersion: v1
47+
clusters:
48+
- cluster:
49+
certificate-authority-data: fake-ca-data
50+
server: https://1.2.3.4
51+
name: mycluster
52+
contexts:
53+
- context:
54+
cluster: mycluster
55+
user: mycluster
56+
name: mycluster
57+
current-context: mycluster
58+
kind: Config
59+
preferences: {}
60+
users:
61+
- name: mycluster
62+
user:
63+
token: fake-token
64+
```
65+
66+
Create a kubeconfig entry with **default** context name `build` using `certificate` authorization and output to the **default** `stdout`.
67+
68+
```console
69+
$ gencred --certificate
70+
```
71+
72+
The kubeconfig contents will be `output` to `stdout`:
73+
74+
```yaml
75+
apiVersion: v1
76+
clusters:
77+
- cluster:
78+
certificate-authority-data: fake-ca-data
79+
server: https://1.2.3.4
80+
name: build
81+
contexts:
82+
- context:
83+
cluster: build
84+
user: build
85+
name: build
86+
current-context: build
87+
kind: Config
88+
preferences: {}
89+
users:
90+
- name: build
91+
user:
92+
client-certificate-data: fake-cert-data
93+
client-key-data: fake-key-data
94+
```
95+
96+
Specify an alternative kube `context` to generate credentials for.
97+
98+
```console
99+
$ gencred --context gke_project_us-west1-a_prow
100+
```
101+
102+
Specify the `--overwrite` flag to *replace* the `output` file if it exists.
103+
104+
```console
105+
$ gencred --output ./existing.yaml --overwrite
106+
```
107+
108+
Omit the `--overwrite` flag to *merge* the `output` file if it exists.
109+
> Entries from the *existing* file take precedence on conflicts.
110+
111+
```console
112+
$ gencred --name oldcluster --output ./existing.yaml
113+
$ gencred --name newcluster --output ./existing.yaml
114+
```
115+
116+
The kubeconfig contents will be `output` to `./existing.yaml`:
117+
118+
```yaml
119+
apiVersion: v1
120+
clusters:
121+
- cluster:
122+
certificate-authority-data: fake-ca-data
123+
server: https://1.2.3.4
124+
name: oldcluster
125+
- cluster:
126+
certificate-authority-data: fake-ca-data
127+
server: https://1.2.3.4
128+
name: newcluster
129+
contexts:
130+
- context:
131+
cluster: oldcluster
132+
user: oldcluster
133+
name: oldcluster
134+
- context:
135+
cluster: newcluster
136+
user: newcluster
137+
name: newcluster
138+
users:
139+
- name: oldcluster
140+
user:
141+
client-certificate-data: fake-cert-data
142+
client-key-data: fake-key-data
143+
- name: newcluster
144+
user:
145+
client-certificate-data: fake-cert-data
146+
client-key-data: fake-key-data
147+
```
148+
149+
### Library
150+
151+
#### Generate a service account token for a cluster.
152+
✅ **PREFERRED** method.
153+
154+
```go
155+
// Import serviceaccount
156+
import "k8s.io/test-infra/gencred/pkg/serviceaccount"
157+
158+
//...
159+
160+
// Create a Kubernetes clientset for interacting with the cluster.
161+
// In this case we are simply using the `current-context` defined in our local `~/.kube/config`.
162+
homedir, _ := os.UserHomeDir()
163+
kubeconfig := filepath.Join(homedir, ".kube", "config")
164+
config, _ := clientcmd.BuildConfigFromFlags("", kubeconfig)
165+
clientset, _ := kubernetes.NewForConfig(config)
166+
167+
// Generate a service account token, as well as return the certificate authority that issued the token.
168+
token, caPEM, err := serviceaccount.CreateClusterServiceAccountCredentials(clientset)
169+
```
170+
171+
`token` will contain the **service account access token** and `caPEM` will contain the **server certificate authority**.
172+
173+
```go
174+
import "encoding/base64"
175+
176+
//...
177+
178+
// Cast the `token` to a string to use in a kubeconfig.
179+
accessToken := string(token)
180+
// Base64 encode the `caPEM` to use in a kubeconfig.
181+
ca := base64.StdEncoding.EncodeToString(caPEM)
182+
183+
fmt.Println("token:", accessToken)
184+
fmt.Println("ca:", ca)
185+
```
186+
187+
```text
188+
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3Mit...
189+
ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURER...
190+
```
191+
192+
#### Generate a client key and certificate for a cluster.
193+
**DEPRECATED** method.
194+
195+
```go
196+
// Import certificate
197+
import "k8s.io/test-infra/gencred/pkg/certificate"
198+
199+
//...
200+
201+
// Create a Kubernetes clientset for interacting with the cluster.
202+
// In this case we are simply using the `current-context` defined in our local `~/.kube/config`.
203+
homedir, _ := os.UserHomeDir()
204+
kubeconfig := filepath.Join(homedir, ".kube", "config")
205+
config, _ := clientcmd.BuildConfigFromFlags("", kubeconfig)
206+
clientset, _ := kubernetes.NewForConfig(config)
207+
208+
// Generate a client key and certificate, as well as return the certificate authority that issued the certificate.
209+
certPEM, keyPEM, caPEM, err := certificate.CreateClusterCertificateCredentials(clientset)
210+
```
211+
212+
`certPEM` will contain the **client certificate**, `keyPEM` will contain the **client key**, and `caPEM` will contain the **server certificate authority**.
213+
214+
```go
215+
import "encoding/base64"
216+
217+
//...
218+
219+
// Base64 encode the `certPEM`, `keyPEM`, and `caPEM` to use in a kubeconfig.
220+
cert := base64.StdEncoding.EncodeToString(certPEM)
221+
key := base64.StdEncoding.EncodeToString(keyPEM)
222+
ca := base64.StdEncoding.EncodeToString(caPEM)
223+
224+
fmt.Println("cert:", cert)
225+
fmt.Println("key:", key)
226+
fmt.Println("ca:", ca)
227+
```
228+
229+
```text
230+
cert: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1...
231+
key: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNL...
232+
ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURER...
233+
```
234+
235+
#### Caveats to using client certificates:
236+
* The use of [x509 client certificate](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certs) with [super-user](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) privileges for cluster authentication/authorization has several drawbacks:
237+
- Certificates **cannot** be revoked ([kubernetes/kubernetes#60917](https://github.com/kubernetes/kubernetes/issues/60917))
238+
- Authorization roles are essentially *global* and thus **cannot** be tweaked at the node level.
239+
- Unless setup with near expiry and explicit rotation, certificates are *long-lived* and **increase** the risk of exposure.
240+
241+
* Client certificate authentication will be deprecated in future versions of Prow ([kubernetes/test-infra#13972](https://github.com/kubernetes/test-infra/issues/13972)).

gencred/cmd/gencred/BUILD.bazel

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = ["main.go"],
6+
importpath = "k8s.io/test-infra/gencred/cmd/gencred",
7+
visibility = ["//visibility:public"],
8+
deps = [
9+
"//gencred/pkg/certificate:go_default_library",
10+
"//gencred/pkg/serviceaccount:go_default_library",
11+
"//gencred/pkg/util:go_default_library",
12+
"@com_github_spf13_pflag//:go_default_library",
13+
"@io_k8s_apimachinery//pkg/runtime:go_default_library",
14+
"@io_k8s_client_go//kubernetes:go_default_library",
15+
"@io_k8s_client_go//plugin/pkg/client/auth:go_default_library",
16+
"@io_k8s_client_go//tools/clientcmd:go_default_library",
17+
"@io_k8s_client_go//tools/clientcmd/api/latest:go_default_library",
18+
"@io_k8s_sigs_yaml//:go_default_library",
19+
],
20+
)
21+
22+
filegroup(
23+
name = "package-srcs",
24+
srcs = glob(["**"]),
25+
tags = ["automanaged"],
26+
visibility = ["//visibility:private"],
27+
)
28+
29+
filegroup(
30+
name = "all-srcs",
31+
srcs = [":package-srcs"],
32+
tags = ["automanaged"],
33+
visibility = ["//visibility:public"],
34+
)

0 commit comments

Comments
 (0)