diff --git a/pkg/detectors/kubeconfig/kubeconfig.go b/pkg/detectors/kubeconfig/kubeconfig.go new file mode 100644 index 000000000000..ee24c6a75d24 --- /dev/null +++ b/pkg/detectors/kubeconfig/kubeconfig.go @@ -0,0 +1,244 @@ +package kubeconfig + +import ( + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple" + "io" + "net/http" + "net/url" + "strings" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +type Scanner struct { + client *http.Client + *detectors.CustomMultiPartCredentialProvider +} + +var ( + // Ensure the Scanner satisfies the interface at compile time. + _ interface { + detectors.Detector + detectors.MultiPartCredentialProvider + } = (*Scanner)(nil) + defaultClient = common.SaneHttpClient() + + invalidHosts = simple.NewCache[struct{}]() +) + +func New() Scanner { + s := Scanner{} + s.CustomMultiPartCredentialProvider = detectors.NewCustomMultiPartCredentialProvider(4096) // ???? + return s +} + +// Keywords are used for efficiently pre-filtering chunks. +func (s Scanner) Keywords() []string { + return []string{"current-context"} +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_KubeConfig +} + +func (s Scanner) Description() string { + return "KubeConfig credentials can allow unauthorized access to a Kubernetes cluster." +} + +// FromData will find and optionally verify KubeConfig secrets in a given set of bytes. +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + logCtx := logContext.AddLogger(ctx) + logger := logCtx.Logger().WithName("kubeconfig") + dataStr := string(data) + + // A crude method to differentiate JSON and YAML configs. + var ( + clusters []cluster + errs []error + ) + if jsonpat.MatchString(dataStr) { + clusters, errs = parseJson(dataStr) + } else { + clusters, errs = parseYaml(dataStr) + } + + if len(errs) > 0 { + for _, pErr := range errs { + // Fatal errors + if errors.Is(pErr, noClustersObjectError) || + errors.Is(pErr, noClusterEntriesError) || + errors.Is(pErr, noContextsObjectError) || + errors.Is(pErr, noContextsError) || + errors.Is(pErr, noUsersObjectError) || + errors.Is(pErr, noUsersError) { + return + } + + logger.Error(pErr, "Failed to parse config") + } + } + if len(clusters) == 0 { + return + } + + for _, cluster := range clusters { + r := detectors.Result{ + DetectorType: s.Type(), + Raw: []byte(cluster.Auth.GetValue()), + RawV2: []byte(fmt.Sprintf(`{"server":"%s","user":"%s","auth":"%s"}`, cluster.Server, cluster.GetUser(), cluster.Auth.GetValue())), + ExtraData: map[string]string{ + "Server": cluster.Server, + "User": cluster.User, + "Type": cluster.Auth.Type.String(), + }, + } + + if verify { + if invalidHosts.Exists(cluster.Server) { + logger.Info("Skipping non-resolving server", "server", cluster.Server) + continue + } + + client := s.client + if client == nil { + client = defaultClient + } + patchTransport(client) + + verified, extraData, verificationErr := verifyCluster(logCtx, client, cluster) + r.Verified = verified + for k, v := range extraData { + r.ExtraData[k] = v + } + + if verificationErr != nil { + if strings.Contains(verificationErr.Error(), "no such host") { + invalidHosts.Set(cluster.Server, struct{}{}) + } + r.SetVerificationError(verificationErr) + } + } + + results = append(results, r) + } + + return +} + +func verifyCluster(ctx logContext.Context, client *http.Client, cluster cluster) (bool, map[string]string, error) { + logger := ctx.Logger().WithName("kubeconfig") + + namespacesUrl, err := url.JoinPath(cluster.Server, "/api/v1/namespaces") + if err != nil { + return false, nil, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, namespacesUrl+"?limit=10", nil) + if err != nil { + return false, nil, nil + } + + // https://github.com/kubernetes/kubernetes/blob/e0e6c9633d5f9a388cbf9c7757c789afaec11c34/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go#L171 + // https://github.com/kubernetes/kubernetes/blob/e0e6c9633d5f9a388cbf9c7757c789afaec11c34/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go#L291 + switch cluster.Auth.Type { + case clientKeyAuth: + // Requires mutual TLS auth. + // Need to investigate this. + case passwordAuth: + req.SetBasicAuth(cluster.User, cluster.Auth.Password) + case tokenAuth: + req.Header.Set("Authorization", "Bearer "+cluster.Auth.Token) + default: + // This should never happen. + logger.Info("Skipping authentication for unknown auth type", "type", cluster.Auth.Type.String()) + } + req.Header.Set("Accept", "application/json") + res, err := client.Do(req) + if err != nil { + return false, nil, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + body, err := io.ReadAll(res.Body) + if err != nil { + return false, nil, err + } + + switch res.StatusCode { + case http.StatusOK: + var nsRes namespaceListResponse + if err := json.Unmarshal(body, &nsRes); err != nil { + return false, nil, err + } + + var extraData map[string]string + if len(nsRes.Items) > 0 { + var sb strings.Builder + for i, ns := range nsRes.Items { + if i > 0 { + sb.WriteString(",") + } + sb.WriteString(ns.Metadata.Name) + } + + if nsRes.Metadata.Continue != "" { + sb.WriteString(" (+ more)") + } + + extraData = map[string]string{ + "Namespaces": sb.String(), + } + } + return true, extraData, nil + case http.StatusUnauthorized: + // The secret is determinately not verified (nothing to do) + return false, nil, nil + case http.StatusForbidden: + // The auth was valid but the user lacks permission. + return true, nil, nil + default: + return false, nil, fmt.Errorf("unexpected HTTP response: status=%d, body=%q", res.StatusCode, string(body)) + } +} + +// patchTransport disables TLS certificate validation. +// This is necessary because many k8s clusters use self-signed certificates. +func patchTransport(c *http.Client) { + transport, ok := c.Transport.(*http.Transport) + if !ok || transport == nil { + transport = &http.Transport{} + } + + // Allow self-signed certificates. + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + c.Transport = transport +} + +// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#namespacelist-v1-core +type namespaceListResponse struct { + Items []item `json:"items"` + Metadata listMetadata +} + +type listMetadata struct { + Continue string `json:"continue"` +} + +type item struct { + Metadata itemMetadata `json:"metadata"` +} + +type itemMetadata struct { + Name string `json:"name"` +} diff --git a/pkg/detectors/kubeconfig/kubeconfig_test.go b/pkg/detectors/kubeconfig/kubeconfig_test.go new file mode 100644 index 000000000000..03b5216f2fe3 --- /dev/null +++ b/pkg/detectors/kubeconfig/kubeconfig_test.go @@ -0,0 +1,337 @@ +package kubeconfig + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestKubeConfig_Pattern(t *testing.T) { + d := Scanner{} + ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d}) + tests := []struct { + name string + input string + want []string + }{ + { + name: "JSON config", + input: `{ + "apiVersion": "v1", + "clusters": [ + { + "cluster": { + "server": "https://127.0.0.1:8080" + }, + "name": "a" + }, + { + "cluster": { + "server": "https://127.0.0.1:8081" + }, + "name": "b" + }, + { + "cluster": { + "server": "https://127.0.0.1:8082" + }, + "name": "c" + } + ], + "contexts": [ + { + "context": { + "cluster": "a", + "namespace": "a", + "user": "a" + }, + "name": "a" + }, + { + "context": { + "cluster": "b", + "user": "b" + }, + "name": "b" + }, + { + "context": { + "cluster": "c", + "user": "c" + }, + "name": "c" + } + ], + "current-context": "a", + "kind": "Config", + "users": [ + { + "name": "a", + "user": { + "client-certificate-data": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEF", + "client-key-data": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEF" + } + }, + { + "name": "b", + "user": { + "username": "admin", + "password": "hunter2" + } + }, + { + "name": "c", + "user": { + "token": "s3cr3t" + } + } + ] +}`, + want: []string{ + `{"server":"https://127.0.0.1:8080","user":"a","auth":"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEF"}`, + `{"server":"https://127.0.0.1:8081","user":"admin","auth":"hunter2"}`, + `{"server":"https://127.0.0.1:8082","user":"c","auth":"s3cr3t"}`, + }, + }, + { + name: "YAML config", + input: `apiVersion: v1 +clusters: +- cluster: + server: https://127.0.0.1:8080 + name: a +- cluster: + server: https://127.0.0.1:8081 + name: b +- cluster: + server: https://127.0.0.1:8082 + name: c +contexts: +- context: + cluster: a + user: a + name: a +- context: + cluster: b + user: b + name: b +- context: + cluster: c + user: c + name: c +current-context: a +kind: Config +preferences: {} +users: +- name: a + user: + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEF +- name: b + user: + password: hunter2 + username: admin +- name: c + user: + token: s3cr3t`, + want: []string{ + `{"server":"https://127.0.0.1:8080","user":"a","auth":"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEF"}`, + `{"server":"https://127.0.0.1:8081","user":"admin","auth":"hunter2"}`, + `{"server":"https://127.0.0.1:8082","user":"c","auth":"s3cr3t"}`, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + chunkSpecificDetectors := make(map[ahocorasick.DetectorKey]detectors.Detector, 2) + ahoCorasickCore.PopulateMatchingDetectors(test.input, chunkSpecificDetectors) + if len(chunkSpecificDetectors) == 0 { + t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input) + return + } + + results, err := d.FromData(context.Background(), false, []byte(test.input)) + if err != nil { + t.Errorf("error = %v", err) + return + } + + if len(results) != len(test.want) { + if len(results) == 0 { + t.Errorf("did not receive result") + return + } + t.Logf("expected %d results, received %d", len(test.want), len(results)) + } + + actual := make(map[string]struct{}, len(results)) + for _, r := range results { + if len(r.RawV2) > 0 { + actual[string(r.RawV2)] = struct{}{} + } else { + actual[string(r.Raw)] = struct{}{} + } + } + expected := make(map[string]struct{}, len(test.want)) + for _, v := range test.want { + expected[v] = struct{}{} + } + + if diff := cmp.Diff(expected, actual); diff != "" { + t.Errorf("%s diff: (-want +got)\n%s", test.name, diff) + } + }) + } +} + +func TestKubeConfig_FromChunk(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + secret := testSecrets.MustGetField("KUBECONFIG_YAML") + inactiveSecret := testSecrets.MustGetField("KUBECONFIG_YAML_INACTIVE") + + type args struct { + ctx context.Context + data []byte + verify bool + } + tests := []struct { + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool + }{ + { + name: "found, verified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a kubeconfig secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_KubeConfig, + Verified: true, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, unverified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a kubeconfig secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_KubeConfig, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "not found", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte("You cannot find the secret within"), + verify: true, + }, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, would be verified if not for timeout", + s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a kubeconfig secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_KubeConfig, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found, verified but unexpected api surface", + s: Scanner{client: common.ConstantResponseHttpClient(404, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a kubeconfig secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_KubeConfig, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("Kubeconfig.FromData() error = %v, wantErr %v", err, tt.wantErr) + return + } + for i := range got { + if len(got[i].Raw) == 0 { + t.Fatalf("no raw secret present: \n %+v", got[i]) + } + if (got[i].VerificationError() != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError()) + } + } + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("Kubeconfig.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + } + }) + } +} + +func BenchmarkFromData(benchmark *testing.B) { + ctx := context.Background() + s := Scanner{} + for name, data := range detectors.MustGetBenchmarkData() { + benchmark.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := s.FromData(ctx, false, data) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/pkg/detectors/kubeconfig/parse_common.go b/pkg/detectors/kubeconfig/parse_common.go new file mode 100644 index 000000000000..61e5ce48aff3 --- /dev/null +++ b/pkg/detectors/kubeconfig/parse_common.go @@ -0,0 +1,69 @@ +package kubeconfig + +import ( + "errors" +) + +type cluster struct { + Server string + User string + Auth clusterAuth +} + +func (c cluster) GetUser() string { + if c.Auth.Type == passwordAuth { + return c.Auth.Username + } + return c.User +} + +type clusterAuth struct { + Type authType + ClientKey string + Username string + Password string + Token string +} + +type authType int + +const ( + unknownAuth authType = iota + externalAuth + clientKeyAuth + tokenAuth + passwordAuth +) + +func (t authType) String() string { + return [...]string{ + "UnknownAuth", + "ExternalAuth", + "ClientKeyAuth", + "TokenAuth", + "PasswordAuth", + }[t] +} + +func (c *clusterAuth) GetValue() string { + switch c.Type { + case clientKeyAuth: + return c.ClientKey + case passwordAuth: + return c.Password + case tokenAuth: + return c.Token + default: + return "" + } +} + +var ( + // Parsing errors + noClustersObjectError = errors.New("no 'clusters' object") + noClusterEntriesError = errors.New("no 'cluster' entries found in data") + noContextsObjectError = errors.New("no 'contexts' object") + noContextsError = errors.New("no context entries found in data") + noUsersObjectError = errors.New("no 'users' object") + noUsersError = errors.New("no user entries found in data") +) diff --git a/pkg/detectors/kubeconfig/parse_common_test.go b/pkg/detectors/kubeconfig/parse_common_test.go new file mode 100644 index 000000000000..38293435b3bc --- /dev/null +++ b/pkg/detectors/kubeconfig/parse_common_test.go @@ -0,0 +1,59 @@ +package kubeconfig + +import ( + "sort" + "testing" + + "github.com/google/go-cmp/cmp" +) + +type testCase struct { + name string + input string + want []cluster + wantErrs []error + skip bool +} + +var errComparer = cmp.Comparer(func(x, y error) bool { + if x == nil || y == nil { + return x == nil && y == nil + } + + return x.Error() == y.Error() +}) + +func sortClusters(clusters []cluster) { + sort.Slice(clusters, func(i, j int) bool { + return clusters[i].Server < clusters[j].Server + }) +} + +func runTest(t *testing.T, parseFunc func(string) ([]cluster, []error), tests []testCase) { + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.skip { + t.Skip("Skipping test: not implemented") + } + + actual, errs := parseFunc(test.input) + if len(errs) > 0 { + if len(test.wantErrs) > 0 { + if diff := cmp.Diff(test.wantErrs, errs, errComparer); diff != "" { + t.Errorf("failed to parse config: (-want +got)\n%s", diff) + return + } + } else { + t.Errorf("got unexpected error(s): %v (%T)", errs, errs[0]) + return + } + } + + sortClusters(test.want) + sortClusters(actual) + if diff := cmp.Diff(test.want, actual); diff != "" { + t.Errorf("diff: (-want +got)\n%s", diff) + } + }) + } +} diff --git a/pkg/detectors/kubeconfig/parse_json.go b/pkg/detectors/kubeconfig/parse_json.go new file mode 100644 index 000000000000..ff0422df4783 --- /dev/null +++ b/pkg/detectors/kubeconfig/parse_json.go @@ -0,0 +1,210 @@ +package kubeconfig + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + regexp "github.com/wasilibs/go-re2" +) + +var ( + jsonpat = regexp.MustCompile(`\\*"(?:clusters|contexts|users)\\*"[ \t]*:\s*?\[`) + configPat = regexp.MustCompile(`\{(?:(?:[^{]|\s)*\\*"(?:kind|apiVersion|preferences|clusters|users|contexts|current-context)\\*"[ \t]*:(?:.|\s)*,?)}`) +) + +// parseJsonConfig attempts to parse the KubeConfig format. +// https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/ +func parseJson(data string) ([]cluster, []error) { + configStr := configPat.FindString(data) + if configStr == "" { + return nil, []error{noUsersObjectError} + } + + // It's impossible to match start/end brackets with regex. + // However, `json.NewDecoder` will ignore any extraneous data. + var conf config + if err := json.NewDecoder(strings.NewReader(configStr)).Decode(&conf); err != nil { + // Ignore invalid JSON. + var jsonErr *json.SyntaxError + if errors.As(err, &jsonErr) { + return nil, nil + } + return nil, []error{err} + } + // Ignore empty configs. + // Could mean the data was JSON, but didn't match the config. + if len(conf.Clusters) == 0 && len(conf.Contexts) == 0 && len(conf.AuthInfos) == 0 { + return nil, nil + } + + // Parse clusters. + urlByClusterName := make(map[string]string) + for _, c := range conf.Clusters { + urlByClusterName[c.Name] = c.Cluster.Server + } + + // Parse contexts + // A cluster can be associated with multiple users. + // A cluster/user can have multiple entries for different namespaces. + usersByCluster := make(map[string]map[string]struct{}) + for _, c := range conf.Contexts { + cluster := c.Context.Cluster + if _, ok := usersByCluster[cluster]; !ok { + // If the outer key doesn't exist, initialize the nested map + usersByCluster[cluster] = make(map[string]struct{}) + } + + usersByCluster[cluster][c.Context.User] = struct{}{} + } + + // Parse users + // A cluster can be associated with multiple users. + // A cluster/user can have multiple entries for different namespaces. + authByUser := make(map[string]clusterAuth) + parseErrors := make([]error, 0) + for _, info := range conf.AuthInfos { + var ( + auth clusterAuth + u = info.User + ) + + switch { + case u.ClientKeyData != "": + auth = clusterAuth{ + Type: clientKeyAuth, + ClientKey: u.ClientKeyData, + } + + // The base64 decoder can provide mangled data. + // Ensure that the certificate value starts with `-----BEGIN`, base64-encoded. + if !strings.HasPrefix(auth.ClientKey, "LS0tLS1CRUdJTi") { + continue + } + case u.Password != "": + auth = clusterAuth{ + Type: passwordAuth, + Username: u.Username, + Password: u.Password, + } + case u.Token != "": + auth = clusterAuth{ + Type: tokenAuth, + Token: u.Token, + } + case u.ClientKey != "", u.TokenFile != "", u.AuthProvider != nil, u.Exec != nil: + // External + auth = clusterAuth{Type: externalAuth} + default: + parseErrors = append(parseErrors, fmt.Errorf("user '%s' has unknown auth type", info.Name)) + auth = clusterAuth{Type: unknownAuth} + } + + authByUser[info.Name] = auth + } + + // Assemble the data. + clusters := make([]cluster, 0) + for clusterName, clusterUrl := range urlByClusterName { + users, ok := usersByCluster[clusterName] + if !ok { + // This could indicate that the file was truncated by the chunker. + err := fmt.Errorf("cluster '%s' has no associated users", clusterName) + parseErrors = append(parseErrors, err) + continue + } + + for user := range users { + cluster := cluster{ + Server: clusterUrl, + User: user, + } + + auth, ok := authByUser[user] + if !ok { + // This could indicate that the file was truncated by the chunker. + err := fmt.Errorf("user '%s@%s' has no associated auth info", user, clusterName) + parseErrors = append(parseErrors, err) + continue + } else if auth.Type == unknownAuth || auth.Type == externalAuth { + // Auth info was found for the user, but we can't use it for some reason. + continue + } + + cluster.Auth = auth + clusters = append(clusters, cluster) + } + } + + if len(clusters) > 0 { + return clusters, parseErrors + } else { + return nil, parseErrors + } +} + +type config struct { + Clusters []namedCluster `json:"clusters"` + Contexts []namedContext `json:"contexts"` + AuthInfos []namedAuthInfo `json:"users"` +} + +// https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/#NamedCluster +// https://github.com/kubernetes/kubernetes/blob/4bb434501d9ee5edda6faf52a9d6d32a969ae183/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/types.go#L163 +type namedCluster struct { + Name string `json:"name"` + Cluster kubeCluster `json:"cluster"` +} + +// https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/#Cluster +// https://github.com/kubernetes/kubernetes/blob/4bb434501d9ee5edda6faf52a9d6d32a969ae183/staging/src/k8s.io/client-go/tools/clientcmd/api/types.go#L67 +type kubeCluster struct { + Server string `json:"server"` +} + +// https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/#NamedContext +// https://github.com/kubernetes/kubernetes/blob/4bb434501d9ee5edda6faf52a9d6d32a969ae183/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/types.go#L171 +type namedContext struct { + Name string `json:"name"` + Context clusterContext `json:"context"` +} + +// https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/#Context +// https://github.com/kubernetes/kubernetes/blob/4bb434501d9ee5edda6faf52a9d6d32a969ae183/staging/src/k8s.io/client-go/tools/clientcmd/api/types.go#L159 +type clusterContext struct { + Cluster string `json:"cluster"` + User string `json:"user"` +} + +// https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/#NamedAuthInfo +// https://github.com/kubernetes/kubernetes/blob/4bb434501d9ee5edda6faf52a9d6d32a969ae183/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/types.go#L179 +type namedAuthInfo struct { + Name string `json:"name"` + User authInfo `json:"user"` +} + +// https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/#AuthInfo +// https://github.com/kubernetes/kubernetes/blob/4bb434501d9ee5edda6faf52a9d6d32a969ae183/staging/src/k8s.io/client-go/tools/clientcmd/api/types.go#L107 +type authInfo struct { + ClientCertificate string `json:"client-certificate"` + ClientCertificateData string `json:"client-certificate-data"` + ClientKey string `json:"client-key"` + ClientKeyData string `json:"client-key-data"` + Token string `json:"token"` + TokenFile string `json:"tokenFile"` + Username string `json:"username"` + Password string `json:"password"` + AuthProvider *authProviderConfig `json:"auth-provider"` + Exec *execConfig `json:"exec,omitempty"` +} + +// https://github.com/kubernetes/kubernetes/blob/fad52aedfcc14061cf20370be061789b4f3d97d9/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/types.go#L195 +type authProviderConfig struct { + Name string `json:"name"` +} + +// https://github.com/kubernetes/kubernetes/blob/fad52aedfcc14061cf20370be061789b4f3d97d9/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/types.go#L205 +type execConfig struct { + Command string `json:"command"` +} diff --git a/pkg/detectors/kubeconfig/parse_json_test.go b/pkg/detectors/kubeconfig/parse_json_test.go new file mode 100644 index 000000000000..4b181e301a14 --- /dev/null +++ b/pkg/detectors/kubeconfig/parse_json_test.go @@ -0,0 +1,546 @@ +package kubeconfig + +import ( + "errors" + "testing" +) + +// Happy path: valid, parseable auth. +func Test_Json_ValidAuth(t *testing.T) { + tests := []testCase{ + { + name: "ClientKey auth", + input: `{"k8s":{"name":"kubernetes","config":{ + "apiVersion": "v1", + "clusters": [ + { + "cluster": { + "certificate-authority-data": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1USXdNekE1TURFME1Gb1hEVEk1TVRFek1EQTVNREUwTUZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTU9XCk1ub3FNRzA3YWxxOUFEVXc3bkN6ZXJiUzRRdzhwWWlkVkZYZHRrQ3JKK0xJR21QcXJ1Y1VkL0o2aGpzREhrRVkKbGplT1M2cGxUWXNmSVlWL1JRQmQ2WGZQcVc5OTgrSXdhYmFoTzdlQmx0MVgyanJiWlBUVm4yeXZwcGRCTUp0dgp5OHo3MkYrK1RES0V5VnJoVXF6UUJCTC95bEJDeWFPNnpYbHovYlhXWm1IWXRBTjVNQ3R4ekRGQWhycWl5WGY4Cjk0MkxhbWZmQ1N0VFN2SXo2OE81dVpZSS82RzFOaW9jaTZvMDVibHIzWmY1Q2xnTERkcUV1ckw2a3hTOHY4a3kKMTdIWndSS2NqcDZoMzZ2cUdURlQ2Zm9IdjFzNjd4c3g0U3FacEpEbmVOVDVDZWJRMWdLTytXUVl5dmVoNkpnSgp4My9QRkthR0RKMlRCd3lBNWNzQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFFTlFTQWxtekVac1g4RU82aFFZRmpDTjdLNGwKSDZvd3l2aisvdDBlelkzS0NjUEJMaTJlbFMyeklBVjNic2pDUndSc1h1ekMrTnhmcXhvVkRyekdmOE5pVWFZMgorZFZ5UlNLbXdCYzFmRjdoNTQrT0hmaS9DZVE4RlR3OE9aWkdPaDBBdXYyM1M4THh6SGEwMEZ1YWpLTDFpWTUwCllTVllZU1MrblBBMlM0TTFkTTNXSGdTMmZPdWV2b0djTm14MXdVRENMdnV2NkN0YjhVU0xFbDJCOWpPb1RFVFgKZUZtaEFGNXVmMjJuRWlubHdKeUVzTjY2OXBPaGdiQXZXbDFxdkxhbDlHTjBWR3NYNFhaTVpHSkt4YkVTUmhLNwpxekE5cFlZbnlwRngxd3phN1g2aUdCeWVGeTZDS1FGS3J4dTV0VUhHZGQ5cmhUcU9Td0JoWVVXeEFoMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", + "server": "https://apiserver.node1:6443" + }, + "name": "kubernetes-m" + }, + { + "cluster": { + "certificate-authority-data": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1USXdOREF6TlRBd09Gb1hEVEk1TVRJd01UQXpOVEF3T0Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTFlzCkxBbFZnZE9qWlY4cmIwS1Ewd1ZPTUd0d2tkb29sZnlUTHo0VVM2ZFhzbTREcEYwbk4xU3pJRFVoVE1tWEh0bDEKTEg2YU9KNGdtYmNTOUdtNFVIQnBxYm81YmM5SHplcjh5bEErTTJGKzJGYjVoVHBUYnJKOXJmMWlVN21VNndjQwpjTUFJRzhCNEpiaVUraEdNQSs2bTJPckZ3Ympsck5TcmRsV21kUzNxNDZ1TEtHTzlyVnRYdVEvR0VKanIvQWs4CmFJYmcyNnFGWVdrVzR5WWlsVWlsS3IzZVpzS3R2ZHBkbEVNcTA3SXpnalRrYVVRMEdpR1RYRW1wWFBCcnRYTW4KQUh3bCtCQVF4OTdlVW8wZ3BpNm44WDh2T0lseEs5ZEx5Si9KVnNkOUlzVkh4bzY5TFNabVNrMnNqVFNWZlJhMQpaZ3RHRXJLK0ZuTWlNeUdPNVdVQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFGaW91ZlRnYjE4S0lmT2k3bXNWWmoyakVzUDEKUnp3Z0ZFQ3Voa3lKZC9tWWxNMlI2dUVOb0d4OWpJUi9GeUxvaDB2dmxwZm1iUUdDck1NOTh0L28vYkNTNHRVaApsbTVWemNEclA4VGlDRFR0VWY2NG5kM1QvZENrR1MxZzBPTjNqTUZJaU9vQmhyaW1KbnRHWkI3WHhDbmFIZUpoCi9vMHdsWUhtcnFVbGZ2R1ZpQm9LU3poZnY4cVA4ZUxxbUN3MyszUnIrTGdpQjhaNFkyRkZ0ekxoY0YvZ0MzRy8KY081UUtjWkZDbjhoWlhEZDBBUE1pREJLOE50QWNOYU5IV3grR1RkZ2t4Z1drN2lJQUgrTkVBT0hrRzEyZUhHSApDN0tZcmRIWWxyZnNhRWw4Vnh6U3NYZ0NnQU9UT3IxWXg3WDVodkVnZlRCSTRGbHl0V09xT1pxRHB0TT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", + "server": "https://apiserver.kube.local:6443" + }, + "name": "kubernetes-s" + } + ], + "contexts": [ + { + "context": { + "cluster": "kubernetes-m", + "namespace": "default", + "user": "kubernetes-admin-m" + }, + "name": "kubernetes-admin@kubernetes-m" + }, + { + "context": { + "cluster": "kubernetes-s", + "namespace": "kong", + "user": "kubernetes-admin-s" + }, + "name": "kubernetes-admin@kubernetes-s" + } + ], + "current-context": "kubernetes-admin@kubernetes-s", + "kind": "Config", + "preferences": {}, + "users": [ + { + "name": "kubernetes-admin-m", + "user": { + "client-certificate-data": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJRDlxcnZIUVNCNVF3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T1RFeU1ETXdPVEF4TkRCYUZ3MHlNREV5TURJd09UQXhOREphTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXZBYXFialV3Qkg5cytQWngKMDFlc1ZRdlU1eHlnWWJEdWxhdG9qSVB1TFF6bFY3OEdqcE9YUDlVcW9wQTUwbVJsMTE4WWh5bDdIVTRwMURkaApKbUtDcWU5SWprdTJPamxQTDAzSW9Hc1c0WHNCVUNuMnQ0Uit4alA0QThUOUJrUS94czVLUElJblRyeXRKaXNTCjVPQklvMDNBeEswdlJsbUJPQ3l4dzRrVTZUMCtDZEZzenFTYUxkM3IydzQzOGFSeGkvdkRPNHVnbWFpQnZ1S0wKYk8yOHl1WEFsM1pVTGxnQUxwcHFBUXZTWTluektsbXpYaExlU3hIODRLcjBVNFFiQjlVRHdpdmJGVDRaN2R5TApmdWVmWEJUeXcxRWYvWlhzNlhJQkVMTlMwZVhsZXpUTmsrb1R5N0NieDUyU2dldjF1R0pkVG1KSEVZaGdiazZ2CnFHSlFMd0lEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFIL1ByTWcxaGtkdUJZWEp6elkwb0VPSW05SVZFcFJWNjNYMApPVnNQUHk4OFNtRDQwMUhmV1NrREREdEVqY2kxajFFK1I0ZFRtRktJWFBxbDdxbFVWeWNmMEJucjUxRGVDUDR0CjNYUmI3Y2kxcGhyNVh2bHZ1Tkh6RHdnSWEyOWVPbkxKZ2xYeDNyNXI2RjNKSkIwekxzUXhSTHUwRFk4V3FvVU8KeHExdXdYR0hKdXZhcVVBc1F0UnV2eEF2RzVLRGlwQTZtUjFxZTZWbjAwSTZpMTc3RFBpbWhXZ1dmVHpZNkJkNgo1Zi8rRzQyMVg2dGFiQnp5WGFQb1NGTURpcDRaTkc3ZFQwYlRlNWhkZ0pIVmlzVUd3WkxXR0NKbndlUTR3cGNqCi9uYlNkUzdiNE9xZXpBbnVCa2p4M21nbTE0cjB6eFhvMGNkWjU3eUxzRzhaa2NPSWRXMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", + "client-key-data": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBdkFhcWJqVXdCSDlzK1BaeDAxZXNWUXZVNXh5Z1liRHVsYXRvaklQdUxRemxWNzhHCmpwT1hQOVVxb3BBNTBtUmwxMThZaHlsN0hVNHAxRGRoSm1LQ3FlOUlqa3UyT2psUEwwM0lvR3NXNFhzQlVDbjIKdDRSK3hqUDRBOFQ5QmtRL3hzNUtQSUluVHJ5dEppc1M1T0JJbzAzQXhLMHZSbG1CT0N5eHc0a1U2VDArQ2RGcwp6cVNhTGQzcjJ3NDM4YVJ4aS92RE80dWdtYWlCdnVLTGJPMjh5dVhBbDNaVUxsZ0FMcHBxQVF2U1k5bnpLbG16ClhoTGVTeEg4NEtyMFU0UWJCOVVEd2l2YkZUNFo3ZHlMZnVlZlhCVHl3MUVmL1pYczZYSUJFTE5TMGVYbGV6VE4KaytvVHk3Q2J4NTJTZ2V2MXVHSmRUbUpIRVloZ2JrNnZxR0pRTHdJREFRQUJBb0lCQUFEMng4am8zT1lwQVJZRgpyVyszODFvOFJVc3FDbWgxejhOVXJhU0t5SjNTZ3hxQUVEaUs2U3VhbkMxWkwvSzBNUkY1bTFhV0Q5dUdteEJMCmVHUUovVUdCeUkxeU5lejJma0Z2MUtkOTVSQWk0VTdYNkR2b29mM0NKbk5lZnkyWkMvcW85Qmg3VWxoRS8xNUMKdWtZU0lFMDJDTmI1VEZUQUFMbVpBUkJQazV2ZWdrQ3Y0dFY1Y29rVk01N2pJOU5GS2g0cW1DSVRDazhjZWlDNQp3bDRITjlrK3p6TlpUcDB4ZXZVUkNDWkRiZHhEcjgwZkh5L0dnS1pIcmQ0cXo5NG5GT2liVVl5dTlXZ0IwdzhrCjh6OFBDV2hDQnRvWmtVMTdFOXZlSzN5Vi82ak1BQkNQcm96aWNXVld6cXlSbW9COEQyTXlpcXFjTHRNSDdEbk0KY1FJdkhBRUNnWUVBeUphUVNRLy92N01LaDJjdE1GdTB1Nytkbm5vcWl0bnY5akZaOHMvdTVuck5wSC8yWFVwTAppcE0wTW9rYUwrS054R3dBR1FSaGNkZ2wzdE9iZTRlbnloUzZZV2FGUzRIbzI4dTdUQTZDSy9hN0xkbGZML0RFCnZCSlBMeDNSTWdWZHRUVDJ6VDVvRmVQR3pwQ0ZNa1JpL3d5Zy83aDRiRnB1VTUwN2JRbmE4NkVDZ1lFQTcvZTQKWEp6bjVMNFMyejMzV0FqOTVBVzYzaWFVbWpSMGphOGt0MEJiNGlaek10UVF3UzBlb1dHQXVBRm9aY2xDL1YybApqd0FHYnVaN1Bpc0V4MHlwT001Y0NmbTlXWUdpSlFPbGhZY0pxc3BhY29nYUF3K1RONzAxbVAzeExlZzRTMFp2CmNLb1ZsblB5N3piaU9oUDJ4bm1HQ213Zm5seWd0aXYrMWZQRnNjOENnWUVBcmxyaGxBQ0tKNUZ6UjNzUnRvVWcKTmtvNnNiUXpJbnFKc0kvNVJhd2tWc2JMMVg4OUlKNGh4NVJvdkx5YnZKL0s1citSM2kwR25yUnBScVRjODZWWQozYmppd1NNaUhoNFAwRzNvb2hYQ1pJQ1U5eWVKSzl5MnhWdU01TUdnUTBDUzBaMzJJVFZydUF0RGxlM2RPWEprCk1wcEJuOFl6TnN2c05sWG5mOElmUmNFQ2dZQUxGbEw2VkhXU2FBWFBBMm51TTF3bnNPd1ZYNHIySlA1Tm5ZNEEKdVlTRlNtbUFLN1FxZUw4MWpaKzQ0TGZHSENwd01tZDMxL1IwSTBvR2NVNWpOdk9Lb0Y0NFI4V3I0UVZ3MkY1SgpjUmZOUUZRMWZueFZMOThKY0VDTnRRM3pwUXNVejBoTzJFenZDcVJxMFFwYXpKbFdTajhiTkN1eDBXM0xmUFRsClJjSVltUUtCZ0QzMUt2dDl6emltaGJ6UXVkM3NGUDlOcE40bERTdEM4bnhuTlpVVDV0K3I3WldYZHBmcy9yWEYKZHlWd3NYSlpSb0V2RThLQkFFdDFJN3JQbVR6ajVWVkFuVm9kQXlodE5BZE9VbFJlbFd6UWJBbzRjRGFJNXB2bgpBUjltZmxQZ2V6c25ZVGJIdHREanhpWVJ3bDRwcGVaWWRpNkEwajlKdWNjaHhvOGhmSG0xCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==" + } + }, + { + "name": "kubernetes-admin-s", + "user": { + "client-certificate-data": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJUnRMa2FEQVlRSDR3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T1RFeU1EUXdNelV3TURoYUZ3MHlNREV5TURNd016VXdNVEphTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQTZmMGtsMm13RDd5d0haa24KT3IyRlFGVE9wOVVMREpXNUkyVHRlNmJkTjViNWJzQnhZRlV4dWk3WlFTaUtneW5pN0tsSHh4MHhDc3BrcUtaSQp2bWJqN25OdWlEOGRQRmFyVXdISnJzYmU1OHNnbVRKK2JuWjdHRDFRbFp6bUw4Q0VvaDluYjY3UWJqN0xLK0pUCnQwbVI2d3hyZ1RLMnllY2xoOStWUW1WZGxFeHQ0dEJrWHFYMGo2anU0M3N6ejBjZUZ4ZTQ2TkdxcksrNXFXZGEKc1Uwdm8vK3VwN3hFSFVVdjIxQVNhc083MDg3K09aRU54bm9ETnZwWFRxRXF0SUdhL0VnaE5UV2dab0ZoNU1vKwpzTGlLbEZlK09OTUtiVER0Y2JESE5HOWliVjFsZWt0a2xXbjc4RGxjYlZWU2FxMHIzSkJZRUVhMVBERVB5a2V3CkE0Z2NQd0lEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDVFpja2VUWWJlNmhGZVdMcjYyaXQxajNQMXE4QThHSUpncQpjQ1pGaG5ycXVUMXp6S3hISk80TktHaGZXNjZ3N0g5dlhyU1R1TEtnYUdBMGZDa1RPSnZJa3ZRV0Q1VWNtVUtHCjI3UEI4NVRQUWt3QWh3QkRNQmJhazNSVktFaGRZNXpLcUJ3UWVER3ZmUGsrSkFGQjA2VXIxak5DNXROVzRKc1gKQ25XNDQ2SVpkZm00eGRwR2FDWHFJTDM1T0N4dzJjbnNmeVdSVjlIMWJFcEtobkxsSU1KQ2w2QWhwbUdRMFVWcQpUZlhlV0FCRjBOYlRsTU54K0RBb0Z2Vkwwck4za2NQUEdtMnAxU0N1R3dhRDRuSGNoVE1UYUY4aUkyMm9CcXNDClhsRDJpQTNidG5ISWxGeUIzZWhmUHlZOUxYU0Z6Yk5oQm9QWjFGdDh2a080d1BzYlVzYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", + "client-key-data": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNmYwa2wybXdEN3l3SFprbk9yMkZRRlRPcDlVTERKVzVJMlR0ZTZiZE41YjVic0J4CllGVXh1aTdaUVNpS2d5bmk3S2xIeHgweENzcGtxS1pJdm1iajduTnVpRDhkUEZhclV3SEpyc2JlNThzZ21USisKYm5aN0dEMVFsWnptTDhDRW9oOW5iNjdRYmo3TEsrSlR0MG1SNnd4cmdUSzJ5ZWNsaDkrVlFtVmRsRXh0NHRCawpYcVgwajZqdTQzc3p6MGNlRnhlNDZOR3FySys1cVdkYXNVMHZvLyt1cDd4RUhVVXYyMUFTYXNPNzA4NytPWkVOCnhub0ROdnBYVHFFcXRJR2EvRWdoTlRXZ1pvRmg1TW8rc0xpS2xGZStPTk1LYlREdGNiREhORzlpYlYxbGVrdGsKbFduNzhEbGNiVlZTYXEwcjNKQllFRWExUERFUHlrZXdBNGdjUHdJREFRQUJBb0lCQUZiVHI4TmIzWkJKWlZUbQpZdzlDQ25OUHhRdTBXNUJFOHRsMmQwVitLdktZM0dCRG13NnpMbXUzUExrWUVTWVE0ZnNONmV1eUltT3RyT0tFCktkUTFtL2o4N3BReVQyZjNoVVdkRVRrQTVQQkFpUTB3Rm9ocEFNNkMyaWRhZkhSVnpTSFg0MnNuQklNVVhCSWgKdnd6eGlPc2V4Ym5BbHVHZkcyY3JDVmtGQWsrbnd5amJVN01ORGVobXk2a3cyZWhUUXVqZ0hGamhMdjJRbnpKawoydjh3Z0w1VWh3alRSdVdqd3FzYy95bWFxTnl1SDRERnF5ZGxhRWpGVjF2Zm5TZUNCbFJ5T3ByQWxGdFI5RWJFCm9aMThoZ1RlditJN04wNm1IU05kSlFuSDduaTF2Z1gwR2lYeE4wNGVuY3JWbGFDTm1ueldpcVdoVVhUVld2UGMKSEhWSk9URUNnWUVBOE5IaFdqMWxQUnV6YllxbTdRbWNsR3hQYzF3S1A5a3pvVm0vUTd2ODdYUkZWc29vN2p6SApEK2NzRFFBYnRTeHR4WVRkMGRjVFVvVzI4TkNQMGp3VjZHSERrQ1FVcVhkMzBkSnlYYjc1L0U4ZEJhU2tTcGtQCitzbjBKSGZyMHBPWWRscGxCZlh2SjNQZ2gxT0loSFkyVW5ZbDhSbzNTaUpOL0R3L21MZGVicmNDZ1lFQStMMEgKeXVtc2xhcjI0alFvdmZielcvMTRQcmZaVGJqb3BqamE2S0VxL3B0QmUxWkM3SzhhMHFBaFdDNmZ5Z2JXSStLUwo2dlBEaWh3cWxtTUx2cnlKa3dTMHJ4UXdxMXBOT1BIV2NCQ1RaQWh3bVhqMW1yNkxZQ09QWGM3YUVXMmRmai9lClNMU3Y5Mmp5Y0k0WWQwYTN4R3BrK2M2NDNkU2ZmSVBISXpzTXRya0NnWUVBd1VneWpyTG9KbnV0THlZeGc5NUwKQmZWSWIxWllBNWJZa1kvdXF2YWVzaGEzOEVpaFFWVVdqL1VDcmd5QU1KRlFLVS9TbVREK0dTV3BCdTdkLythcAp6ckZvdksrNHhhdFZSOXFZWUJWL25yb0FtUjdqbmR2cnIyV1h4ZzFhQU5EbGRWaG43TGpQRWVNM09tWVpFL2VzCjhkSlI4WWtSQnpjeFVGa3EwZSthbzFFQ2dZRUF6TEpiY0Z4ZklBaERCaUtnaUx4cXg0QlBiV1hGR2RZYTkzZ0EKaHNMamJBWCtuRzUvd3VIVGRCUTlmS0ZaOUZzdDdQZ3ZxZFVUVFZ3aW5BSkVqeUgvSVpNVTBxUU43V1h4K1BQawpZZkx5S0xkZFdwK1ZsMVJKeE1OZTMzYzBOSFY5ejREbC8vVmFmb1BLU0dCWHVBamxnR21DVWFZU3N0T2dzRXFPCkhlc2hhbGtDZ1lCRWNCNmJCMUlONXJQRGdYejJZcnpuYlZkWHRxbkM1dWx2Qk1zR1oyTWQzbExJUFN6Y2FudXEKeXo3dW5mTjFGeWoxMFJ4RFY4K0tXQlZFTEwyZ1VhbWVSLzd3RlZJN1dTaXkyczVHVDFmRTg1cjBaTW5pZmlRSApjKy9tbWFvbnFSMzNVcXBJb0hyNUJha0FlamRQTkRtZ1Fkb1ZTQ2EzbDU0OWtlMEZrNUxUVlE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=" + } + } + ] +}}}`, + want: []cluster{ + { + Server: "https://apiserver.node1:6443", + User: "kubernetes-admin-m", + Auth: clusterAuth{ + Type: clientKeyAuth, + ClientKey: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBdkFhcWJqVXdCSDlzK1BaeDAxZXNWUXZVNXh5Z1liRHVsYXRvaklQdUxRemxWNzhHCmpwT1hQOVVxb3BBNTBtUmwxMThZaHlsN0hVNHAxRGRoSm1LQ3FlOUlqa3UyT2psUEwwM0lvR3NXNFhzQlVDbjIKdDRSK3hqUDRBOFQ5QmtRL3hzNUtQSUluVHJ5dEppc1M1T0JJbzAzQXhLMHZSbG1CT0N5eHc0a1U2VDArQ2RGcwp6cVNhTGQzcjJ3NDM4YVJ4aS92RE80dWdtYWlCdnVLTGJPMjh5dVhBbDNaVUxsZ0FMcHBxQVF2U1k5bnpLbG16ClhoTGVTeEg4NEtyMFU0UWJCOVVEd2l2YkZUNFo3ZHlMZnVlZlhCVHl3MUVmL1pYczZYSUJFTE5TMGVYbGV6VE4KaytvVHk3Q2J4NTJTZ2V2MXVHSmRUbUpIRVloZ2JrNnZxR0pRTHdJREFRQUJBb0lCQUFEMng4am8zT1lwQVJZRgpyVyszODFvOFJVc3FDbWgxejhOVXJhU0t5SjNTZ3hxQUVEaUs2U3VhbkMxWkwvSzBNUkY1bTFhV0Q5dUdteEJMCmVHUUovVUdCeUkxeU5lejJma0Z2MUtkOTVSQWk0VTdYNkR2b29mM0NKbk5lZnkyWkMvcW85Qmg3VWxoRS8xNUMKdWtZU0lFMDJDTmI1VEZUQUFMbVpBUkJQazV2ZWdrQ3Y0dFY1Y29rVk01N2pJOU5GS2g0cW1DSVRDazhjZWlDNQp3bDRITjlrK3p6TlpUcDB4ZXZVUkNDWkRiZHhEcjgwZkh5L0dnS1pIcmQ0cXo5NG5GT2liVVl5dTlXZ0IwdzhrCjh6OFBDV2hDQnRvWmtVMTdFOXZlSzN5Vi82ak1BQkNQcm96aWNXVld6cXlSbW9COEQyTXlpcXFjTHRNSDdEbk0KY1FJdkhBRUNnWUVBeUphUVNRLy92N01LaDJjdE1GdTB1Nytkbm5vcWl0bnY5akZaOHMvdTVuck5wSC8yWFVwTAppcE0wTW9rYUwrS054R3dBR1FSaGNkZ2wzdE9iZTRlbnloUzZZV2FGUzRIbzI4dTdUQTZDSy9hN0xkbGZML0RFCnZCSlBMeDNSTWdWZHRUVDJ6VDVvRmVQR3pwQ0ZNa1JpL3d5Zy83aDRiRnB1VTUwN2JRbmE4NkVDZ1lFQTcvZTQKWEp6bjVMNFMyejMzV0FqOTVBVzYzaWFVbWpSMGphOGt0MEJiNGlaek10UVF3UzBlb1dHQXVBRm9aY2xDL1YybApqd0FHYnVaN1Bpc0V4MHlwT001Y0NmbTlXWUdpSlFPbGhZY0pxc3BhY29nYUF3K1RONzAxbVAzeExlZzRTMFp2CmNLb1ZsblB5N3piaU9oUDJ4bm1HQ213Zm5seWd0aXYrMWZQRnNjOENnWUVBcmxyaGxBQ0tKNUZ6UjNzUnRvVWcKTmtvNnNiUXpJbnFKc0kvNVJhd2tWc2JMMVg4OUlKNGh4NVJvdkx5YnZKL0s1citSM2kwR25yUnBScVRjODZWWQozYmppd1NNaUhoNFAwRzNvb2hYQ1pJQ1U5eWVKSzl5MnhWdU01TUdnUTBDUzBaMzJJVFZydUF0RGxlM2RPWEprCk1wcEJuOFl6TnN2c05sWG5mOElmUmNFQ2dZQUxGbEw2VkhXU2FBWFBBMm51TTF3bnNPd1ZYNHIySlA1Tm5ZNEEKdVlTRlNtbUFLN1FxZUw4MWpaKzQ0TGZHSENwd01tZDMxL1IwSTBvR2NVNWpOdk9Lb0Y0NFI4V3I0UVZ3MkY1SgpjUmZOUUZRMWZueFZMOThKY0VDTnRRM3pwUXNVejBoTzJFenZDcVJxMFFwYXpKbFdTajhiTkN1eDBXM0xmUFRsClJjSVltUUtCZ0QzMUt2dDl6emltaGJ6UXVkM3NGUDlOcE40bERTdEM4bnhuTlpVVDV0K3I3WldYZHBmcy9yWEYKZHlWd3NYSlpSb0V2RThLQkFFdDFJN3JQbVR6ajVWVkFuVm9kQXlodE5BZE9VbFJlbFd6UWJBbzRjRGFJNXB2bgpBUjltZmxQZ2V6c25ZVGJIdHREanhpWVJ3bDRwcGVaWWRpNkEwajlKdWNjaHhvOGhmSG0xCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==", + }, + }, + { + Server: "https://apiserver.kube.local:6443", + User: "kubernetes-admin-s", + Auth: clusterAuth{ + Type: clientKeyAuth, + ClientKey: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNmYwa2wybXdEN3l3SFprbk9yMkZRRlRPcDlVTERKVzVJMlR0ZTZiZE41YjVic0J4CllGVXh1aTdaUVNpS2d5bmk3S2xIeHgweENzcGtxS1pJdm1iajduTnVpRDhkUEZhclV3SEpyc2JlNThzZ21USisKYm5aN0dEMVFsWnptTDhDRW9oOW5iNjdRYmo3TEsrSlR0MG1SNnd4cmdUSzJ5ZWNsaDkrVlFtVmRsRXh0NHRCawpYcVgwajZqdTQzc3p6MGNlRnhlNDZOR3FySys1cVdkYXNVMHZvLyt1cDd4RUhVVXYyMUFTYXNPNzA4NytPWkVOCnhub0ROdnBYVHFFcXRJR2EvRWdoTlRXZ1pvRmg1TW8rc0xpS2xGZStPTk1LYlREdGNiREhORzlpYlYxbGVrdGsKbFduNzhEbGNiVlZTYXEwcjNKQllFRWExUERFUHlrZXdBNGdjUHdJREFRQUJBb0lCQUZiVHI4TmIzWkJKWlZUbQpZdzlDQ25OUHhRdTBXNUJFOHRsMmQwVitLdktZM0dCRG13NnpMbXUzUExrWUVTWVE0ZnNONmV1eUltT3RyT0tFCktkUTFtL2o4N3BReVQyZjNoVVdkRVRrQTVQQkFpUTB3Rm9ocEFNNkMyaWRhZkhSVnpTSFg0MnNuQklNVVhCSWgKdnd6eGlPc2V4Ym5BbHVHZkcyY3JDVmtGQWsrbnd5amJVN01ORGVobXk2a3cyZWhUUXVqZ0hGamhMdjJRbnpKawoydjh3Z0w1VWh3alRSdVdqd3FzYy95bWFxTnl1SDRERnF5ZGxhRWpGVjF2Zm5TZUNCbFJ5T3ByQWxGdFI5RWJFCm9aMThoZ1RlditJN04wNm1IU05kSlFuSDduaTF2Z1gwR2lYeE4wNGVuY3JWbGFDTm1ueldpcVdoVVhUVld2UGMKSEhWSk9URUNnWUVBOE5IaFdqMWxQUnV6YllxbTdRbWNsR3hQYzF3S1A5a3pvVm0vUTd2ODdYUkZWc29vN2p6SApEK2NzRFFBYnRTeHR4WVRkMGRjVFVvVzI4TkNQMGp3VjZHSERrQ1FVcVhkMzBkSnlYYjc1L0U4ZEJhU2tTcGtQCitzbjBKSGZyMHBPWWRscGxCZlh2SjNQZ2gxT0loSFkyVW5ZbDhSbzNTaUpOL0R3L21MZGVicmNDZ1lFQStMMEgKeXVtc2xhcjI0alFvdmZielcvMTRQcmZaVGJqb3BqamE2S0VxL3B0QmUxWkM3SzhhMHFBaFdDNmZ5Z2JXSStLUwo2dlBEaWh3cWxtTUx2cnlKa3dTMHJ4UXdxMXBOT1BIV2NCQ1RaQWh3bVhqMW1yNkxZQ09QWGM3YUVXMmRmai9lClNMU3Y5Mmp5Y0k0WWQwYTN4R3BrK2M2NDNkU2ZmSVBISXpzTXRya0NnWUVBd1VneWpyTG9KbnV0THlZeGc5NUwKQmZWSWIxWllBNWJZa1kvdXF2YWVzaGEzOEVpaFFWVVdqL1VDcmd5QU1KRlFLVS9TbVREK0dTV3BCdTdkLythcAp6ckZvdksrNHhhdFZSOXFZWUJWL25yb0FtUjdqbmR2cnIyV1h4ZzFhQU5EbGRWaG43TGpQRWVNM09tWVpFL2VzCjhkSlI4WWtSQnpjeFVGa3EwZSthbzFFQ2dZRUF6TEpiY0Z4ZklBaERCaUtnaUx4cXg0QlBiV1hGR2RZYTkzZ0EKaHNMamJBWCtuRzUvd3VIVGRCUTlmS0ZaOUZzdDdQZ3ZxZFVUVFZ3aW5BSkVqeUgvSVpNVTBxUU43V1h4K1BQawpZZkx5S0xkZFdwK1ZsMVJKeE1OZTMzYzBOSFY5ejREbC8vVmFmb1BLU0dCWHVBamxnR21DVWFZU3N0T2dzRXFPCkhlc2hhbGtDZ1lCRWNCNmJCMUlONXJQRGdYejJZcnpuYlZkWHRxbkM1dWx2Qk1zR1oyTWQzbExJUFN6Y2FudXEKeXo3dW5mTjFGeWoxMFJ4RFY4K0tXQlZFTEwyZ1VhbWVSLzd3RlZJN1dTaXkyczVHVDFmRTg1cjBaTW5pZmlRSApjKy9tbWFvbnFSMzNVcXBJb0hyNUJha0FlamRQTkRtZ1Fkb1ZTQ2EzbDU0OWtlMEZrNUxUVlE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=", + }, + }, + }, + }, + { + name: "Token auth", + input: `{ + "apiVersion": "v1", + "clusters": [ + { + "cluster": { + "server": "https://k8s.corp.net:6443" + }, + "name": "default" + } + ], + "contexts": [ + { + "context": { + "cluster": "default", + "namespace": "corpbot", + "user": "corpbot" + }, + "name": "default" + } + ], + "current-context": "default", + "kind": "Config", + "users": [ + { + "name": "corpbot", + "user": { + "token": "yI4ehbwWp0OU6dO0QsT6GTHxQhehC2HEyMEDgxxTmYlV2CynEWBQOTquNn3MXrlT" + } + } + ] +}`, + want: []cluster{ + { + Server: "https://k8s.corp.net:6443", + User: "corpbot", + Auth: clusterAuth{ + Type: tokenAuth, + Token: "yI4ehbwWp0OU6dO0QsT6GTHxQhehC2HEyMEDgxxTmYlV2CynEWBQOTquNn3MXrlT", + }, + }, + }, + }, + { + name: "Password auth", + input: `{ + "apiVersion": "v1", + "kind": "Config", + "preferences": {}, + "clusters": [ + { + "cluster": { + "certificate-authority-data": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJWakNCL3FBREFnRUNBZ0VBTUFvR0NDcUdTTTQ5QkFNQ01DTXhJVEFmQmdOVkJBTU1HR3N6Y3kxelpYSjIKWlhJdFkyRkFNVFU1T0RJNE5qZ3pNekFlRncweU1EQTRNalF4TmpNek5UTmFGdzB6TURBNE1qSXhOak16TlROYQpNQ014SVRBZkJnTlZCQU1NR0dzemN5MXpaWEoyWlhJdFkyRkFNVFU1T0RJNE5qZ3pNekJaTUJNR0J5cUdTTTQ5CkFnRUdDQ3FHU000OUF3RUhBMElBQkp3ZTF2UXR0T1F6c2xaVTNUQkhSWlQzS293UWdHdnY1TnYvZko5NTRpSkkKbG5nbHVyZktWQk9nbDM2bFp2UGJGdzVKTTFpSWYzaEVmVTZxU3crNThSR2pJekFoTUE0R0ExVWREd0VCL3dRRQpBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUVSMG81dm82K1pXCkVkaE1GYXpVWk9CTnhkVk14N0dRTTY1MCsyWFBzZTBJQWlBVkJBWmZUdE94SkN0Q2lZQnp6alhzM29MWWhyZzYKcFVhQ0o3Nyt0VFpycUE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", + "server": "https://127.0.0.1:6443" + }, + "name": "default" + }, + { + "cluster": { + "certificate-authority": "./ca.crt", + "server": "https://192.168.99.100:8443" + }, + "name": "minikube" + } + ], + "contexts": [ + { + "context": { + "cluster": "default", + "user": "default" + }, + "name": "default" + }, + { + "context": { + "cluster": "minikube", + "user": "minikube" + }, + "name": "minikube" + } + ], + "current-context": "default", + "users": [ + { + "name": "default", + "user": { + "password": "f976df38c1bc4ed617413adef8844217", + "username": "admin" + } + }, + { + "name":"minikube", + "user": { + "client-certificate":"/tmp/vm-state-kube/xchg/secrets/cluster-admin.pem", + "client-key":"/tmp/vm-state-kube/xchg/secrets/cluster-admin-key.pem" + } + } + ] +} +`, + want: []cluster{ + { + Server: "https://127.0.0.1:6443", + User: "default", + Auth: clusterAuth{ + Type: passwordAuth, + Username: "admin", + Password: "f976df38c1bc4ed617413adef8844217", + }, + }, + }, + }, + } + runTest(t, parseJson, tests) +} + +func Test_Json_DifferentOrder(t *testing.T) { + tests := []testCase{ + { + name: "clusters>users>contexts", + input: `{ + "preferences": {}, + "clusters": [ + { + "cluster": { + "server": "https://127.0.0.1:6443" + }, + "name": "a" + } + ], + "users": [ + { + "name": "a", + "user": { + "token": "a" + } + } + ], + "contexts": [ + { + "context": { + "cluster": "a", + "user": "a" + }, + "name": "a" + } + ], + "current-context": "a" +}`, + want: []cluster{ + {Server: "https://127.0.0.1:6443", User: "a", Auth: clusterAuth{Type: tokenAuth, Token: "a"}}, + }, + }, + { + name: "users>contexts>clusters", + input: `{ + "apiVersion": "v1", + "users": [ + { + "name": "a", + "user": { + "token": "a" + } + } + ], + "contexts": [ + { + "context": { + "cluster": "a", + "user": "a" + }, + "name": "a" + } + ], + "current-context": "a", + "clusters": [ + { + "cluster": { + "server": "https://127.0.0.1:6443" + }, + "name": "a" + } + ], + "kind": "Config", + "preferences": {} +}`, + want: []cluster{ + {Server: "https://127.0.0.1:6443", User: "a", Auth: clusterAuth{Type: tokenAuth, Token: "a"}}, + }, + }, + } + + runTest(t, parseJson, tests) +} + +// Auth is provided by an external source (file, extension). +func Test_Json_ExternalAuth(t *testing.T) { + tests := []testCase{ + // Certificate is stored in a file. + { + name: "client-certificate", + input: `{ + "apiVersion": "v1", + "kind": "Config", + "preferences": {}, + "clusters": [ + { + "cluster": { + "certificate-authority": "./ca.crt", + "server": "https://192.168.99.100:8443" + }, + "name": "minikube" + } + ], + "contexts": [ + { + "context": { + "cluster": "minikube", + "user": "minikube" + }, + "name": "minikube" + } + ], + "current-context": "default", + "users": [ + { + "name":"minikube", + "user": { + "client-certificate":"/tmp/vm-state-kube/xchg/secrets/cluster-admin.pem", + "client-key":"/tmp/vm-state-kube/xchg/secrets/cluster-admin-key.pem" + } + } + ] +}`, + }, + // Token is stored in a file. + // TODO: Find an example of this. + { + name: "tokenFile", + input: ``, + skip: true, + }, + // Custom plugin to provide credentials. + { + name: "auth-provider", + input: `{ + "apiVersion": "v1", + "clusters": [ + { + "cluster": { + "certificate-authority-data": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUV5VENDQXJHZ0F3SUJBZ0lRTDZFWHZDU3ovcTQ0Um51RlFtYnJPekFOQmdrcWhraUc5dzBCQVFzRkFEQU4KTVFzd0NRWURWUVFERXdKallUQWdGdzB5TVRBeU1qSXhOelExTVRKYUdBOHlNRFV4TURJeE5URTNORFV4TWxvdwpEVEVMTUFrR0ExVUVBeE1DWTJFd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUUNjCnpkNFRvRERwZHdMYWhyb250TGhwRkpsS0lVdTZBVGw1SnN3VnU4ek5pTUVsVWxoTjZmVmdUY2hXMVNDM3NaTHoKWitpSnNUL3l4TWJQN0I3M3F3UC92Wkh2YUNhMldPSDZ5UmRWbDQ3UmhFbU8wbHA5SnlHVGNzd0JXVy9MNnVLeApTVWRoSmI1WExoOEZHVzZDVUZpU0cxNHVCcUM2bzRKMDRRWmt6YWo4djA3YnBYeWhXUWg3Z3FWTU9NRWEwZzdlCm9GbS85QndoNnIvZThzcXc3SkZ4WTFVT25VcVl6WnhCR1NXQ3N5aTZhVDc1SnlOY2ZaUXV6S1A4VnpKNGZFK0cKRmJ1SSt2Q2VqWUMyTnFweU0vV1VaM3BJSXR0dm5RcVk2ekxtSUlKcSt5dk9yb005KytaNjV2NVl4Zzh0aG9LawpmdktjOXl2TDNHdjB4OUhEZ2JXSlhjcG1pR1h2ZEoxaXNSZUw1WFI5b0NWeTJxcTVKV1dOQWFhU1M2OUhjbXpPCjVyYTRRa0F3d0xQaWJkTXJOK0F2bDlwTmhGOHRjYm1DakZlOHIvbStrTEJ4cHYvdXVDL25vK3hEbnA0MGsrckcKQ1NyRkJNTWJwbStCamExSnMwR0trZ2lUcERBVlZyUmt5MjJQdjd4N1k0TjdoUk42R0MyZGtTU0hpV21sTmJESwpNZWFTek1ucVc1bTlhUTcrL2s1cGk0d01WdWZQNUJEM3dJVXh5N0lEdi9RaXVNVXkrNjNYdkVYYlZTeVEyOXVCCkpLY3JVT2s5dDUzd3hENXZUaFpNZGJYR0pYOTVzSXk3bzR3ajNnMWJKS0FvbjlGUEVGYUFvaFFGUWNxY0VyV2wKTGpEQXY2UjBMM3ZJNWlyMEhwdWIvdGRVS3YrOThCRnBzcjRXWXJYVTZ3SURBUUFCb3lNd0lUQU9CZ05WSFE4QgpBZjhFQkFNQ0FxUXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FnRUFYTXZJCkVjTjdxNHFyUTVQYU54dzljVXozdDZUNFpvUVFPcHp5YUVHM3IxK1k2OWVJbzNRenpycENDRmI5OXZFZnVsaGwKeVlnQUw5cmQ5Q0tsMld0TDh6dTkzNmNtS0ZHMXFTczFBTW9ZcC9ZM2RDMTVUUlBxT3UxUmZTa0JOWU8rOE1WcQo4OE5jMTJPQWVZU1BqNUpVdUVTTW9KWWg2Tm1KMC92bitxRmorQUVwcWkzR0p2SG5rRjFYM3JnanlhUGMrZG1TClJxdVZqc3U5K3pSdGdQTzZDd3p3bGtMcWNyQnU1eUZKWGxpeVZZc1FNNVBveXQ4a1drOXE2UGp3dnQ3MUdDbEIKb3M0TU5ETk1Hb3d5bWNwVTUwcDZxMGx4VE80eERzUUV1c1ZtOUIxOVRFZGFVVXQyUW5jUWwrOVFOZXFydVFXNwpyNVdKdWNYUFZDSXEvWXIyK29YdWpqa0w0eU1RMWNickRtcmpYaDI1b04vN0Uvd1VlTVpXL2lXbmZsRXZrc2xXCkk4VHp6S21HbWdhU0h6WitkVmRpRDBWU29DeXc4bnd4M3BaVEdEY0E2YWhVdmU2TTRUOGkveTRVMlY0MFFRc20KczdaVHJoNlJiU2JGWlFTMldxOHduZ09QKzkxVE4yMXp1VUxRTldhem1qWDE2MitIZDNZZTJMYlR4S3YrQzAwYgpCb29ldGRyWTBCK2tqaTIxSFF4aGkrTTRoN3J3ZXhmckFOcWpmSkVhMzBTandZSnREcGdQZ3VmZTdtTU5LZFAyCnV0cmYyMkgwNmNEZi9QTmo4ZjJGOEdKRGFRZFJZTnZYUFlTMVpFOVhkY3Y0d3BVRGF2Tk45akliQW9mdTF3NGwKOFZ6T1VYQXl2N25hOENtQlE0aXVGR081V1BxcGlDVi9lbmJkRE8wPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", + "server": "https://193.0.1.10" + }, + "name": "aks-engine-workshop-cluster-6033eda0" + } + ], + "contexts": [ + { + "context": { + "cluster": "aks-engine-workshop-cluster-6033eda0", + "user": "aks-engine-workshop-cluster-6033eda0-admin" + }, + "name": "aks-engine-workshop-cluster-6033eda0" + } + ], + "current-context": "aks-engine-workshop-cluster-6033eda0", + "kind": "Config", + "users": [ + { + "name": "aks-engine-workshop-cluster-6033eda0-admin", + "user": {"auth-provider":{"name":"azure","config":{"environment":"AzurePublicCloud","tenant-id":"3851f269-b22b-4de6-97d6-aa9fe60fe301","apiserver-id":"3adf37ca-d914-43e9-9b24-8c081e0b3a08","client-id":"70dba699-0fba-4c1d-805e-213acea0a63e"}}} + } + ] +}`, + }, + // Custom command to provide credentials. + // https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/#ExecConfig + { + name: "exec", + input: `{ + "apiVersion": "v1", + "kind": "Config", + "current-context": "terraform", + "clusters": [ + { + "name": "terraform", + "cluster": { + "insecure-skip-tls-verify": true, + "server": "" + } + } + ], + "users": [ + { + "name": "terraform", + "user": { + "exec": { + "apiVersion": "client.authentication.k8s.io/v1beta1", + "command": "aws-iam-authenticator", + "args": [ + "token", + "-i", + "", + "-r", + "" + ] + } + } + } + ], + "contexts": [ + { + "name": "terraform", + "context": { + "cluster": "terraform", + "user": "terraform" + } + } + ] +}`, + }, + } + runTest(t, parseJson, tests) +} + +func Test_Json_UnknownAuth(t *testing.T) { + tests := []testCase{ + { + name: "Missing auth info", + input: `{ + "apiVersion":"v1", + "clusters": + [ + { + "cluster": + { + "certificate-authority":"/tmp/vm-state-kube/xchg/secrets/ca.pem", + "server":"https://127.0.0.1:5443" + }, + "name":"kubenix" + }, + { + "cluster": + { + "certificate-authority":"/tmp/vm-state-kube/xchg/secrets/ca.pem", + "server":"https://127.0.0.1:5443" + }, + "name":"kubenix2" + } + ], + "contexts": + [ + { + "context": + { + "cluster":"kubenix", + "user":"cluster-admin" + }, + "current-context":"kubenix" + }, + { + "context": + { + "cluster":"kubenix2", + "user":"cluster-admin2" + }, + "current-context":"kubenix2" + } + ], + "kind":"Config", + "users": + [ + { + "name": "cluster-admin", + "user": { + "token": "yI4ehbwWp0OU6dO0QsT6GTHxQhehC2HEyMEDgxxTmYlV2CynEWBQOTquNn3MXrlT" + } + } + ] +}`, + want: []cluster{ + { + Server: "https://127.0.0.1:5443", + User: "cluster-admin", + Auth: clusterAuth{ + Type: tokenAuth, + Token: "yI4ehbwWp0OU6dO0QsT6GTHxQhehC2HEyMEDgxxTmYlV2CynEWBQOTquNn3MXrlT", + }, + }, + }, + wantErrs: []error{errors.New("user 'cluster-admin2@kubenix2' has no associated auth info")}, + }, + } + runTest(t, parseJson, tests) +} + +func Test_Json_Unparseable(t *testing.T) { + tests := []testCase{ + { + name: "Empty config", + input: `{ + "apiVersion": "v1", + "kind": "Config", + "preferences": {}, + "clusters": [], + "contexts": [], + "current-context": "default", + "users": [ ] +}`, + wantErrs: []error{noClusterEntriesError}, + }, + + // False positives + { + name: "Invalid JSON", + input: `{ + "apiVersion": "v1", + "clusters": [ + { + "cluster": { + "insecure-skip-tls-verify": True, + "server": apiServer + }, + "name": "sreworks_cluster" + } + ], + "contexts": [ + { + "context": { + "cluster": "sreworks_cluster", + "user": "sreworks_admin" + }, + "name": "t" + } + ], + "current-context": "t", + "kind": "Config", + "preferences": {}, + "users": [ + { + "name": "sreworks_admin", + "user": { + "token": token, + } + } + ] + }`, + wantErrs: []error{}, + }, + { + name: "Placeholder values", + input: `{ "apiVersion": "v1", "clusters": [ { "cluster": { "certificate-authority-data": "xxxxxx==", "server": "https://xxxxx.com" }, "name": "cls-xxxxx" } ], "contexts": [ { "context": { "cluster": "cls-xxxxx", "user": "100014xxxxx" }, "name": "cls-a44yhcxxxxxxxxxx" } ], "current-context": "cls-a4xxxx-context-default", "kind": "Config", "preferences": {}, "users": [ { "name": "100014xxxxx", "user": { "client-certificate-data": "xxxxxx", "client-key-data": "xxxxxx" } } ]} */`, + wantErrs: []error{errors.New("user '100014xxxxx@cls-xxxxx' has no associated auth info")}, + }, + } + + runTest(t, parseJson, tests) +} diff --git a/pkg/detectors/kubeconfig/parse_yaml.go b/pkg/detectors/kubeconfig/parse_yaml.go new file mode 100644 index 000000000000..59af35d022b1 --- /dev/null +++ b/pkg/detectors/kubeconfig/parse_yaml.go @@ -0,0 +1,175 @@ +package kubeconfig + +import ( + "fmt" + "strings" + + regexp "github.com/wasilibs/go-re2" +) + +var ( + // Attempts to match the "clusters" object. + // https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/#NamedCluster + clustersObjectPat = regexp.MustCompile(`clusters:[\s\S]+?(?:(?:kind|apiVersion|preferences|contexts|users|current-context):|\z)`) + clusterEntryPat = regexp.MustCompile(`-[ \t]*cluster:(?:.|\s)+?server:[ \t]*['"]?(https?://[\w.\-:\/]+)['"]?(?:.|\s)+?name:[ \t]*['"]?([\w\-:]+)['"]?`) + // Attempts to match the "contexts" object. + // https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/#NamedContext + contextsObjectPat = regexp.MustCompile(`contexts:[[\s\S]+(?:(?:kind|apiVersion|preferences|clusters|users|current-context):|\z)`) + contextEntryPat = regexp.MustCompile(`-[ \t]*context:(?:.|\s)+?cluster:[ \t]*['"]?([\w\-:]+)['"]?(?:.|\s)+?user:[ \t]*['"]?([\w\-:\/]+)['"]?(?:.|\s)+?name:[ \t]*['"]?([\w\-:\/]+)['"]?`) + // Attempts to match the "users" object. + // https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/#NamedAuthInfo + usersObjectPat = regexp.MustCompile(`users:[\s\S]+?(?:(?:kind|apiVersion|preferences|contexts|clusters|current-context):|\z)`) + userEntryPat = regexp.MustCompile(`-[ \t]*name:[ \t]*['"]?([\w\-:\/]+)['"]?(?:.|\s)+?(client-key(?:-data)?|password|token):[ \t]*['"]?([\w.\-\/]+={0,2})['"]?(?:\s|$)`) + userUsernamePat = func(password string) *regexp.Regexp { + return regexp.MustCompile(fmt.Sprintf(`['"]?password['"]?:[ \t]*['"]?%s['"]?(?:.|\s)+username:[ \t]*['"]?([\w.\-\/]+={0,2})['"]?(?:\s|$)`, password)) + } +) + +func parseYaml(data string) ([]cluster, []error) { + // Parse `clusters` object. + clustersObject := clustersObjectPat.FindString(data) + if clustersObject == "" { + return nil, []error{noClustersObjectError} + } + clusterEntries := clusterEntryPat.FindAllStringSubmatch(clustersObject, -1) + if len(clusterEntries) == 0 { + return nil, []error{noClusterEntriesError} + } + + urlByClusterName := make(map[string]string) + for _, m := range clusterEntries { + server := m[1] + name := m[2] + urlByClusterName[name] = server + } + + // Parse `contexts` object. + contextsObject := contextsObjectPat.FindString(data) + if contextsObject == "" { + return nil, []error{noContextsObjectError} + } + contextEntries := contextEntryPat.FindAllStringSubmatch(contextsObject, -1) + if len(contextEntries) == 0 { + return nil, []error{noContextsError} + } + + // A cluster can be associated with multiple users. + // A cluster/user can have multiple entries for different namespaces. + usersByCluster := make(map[string]map[string]struct{}) + for _, m := range contextEntries { + cluster := m[1] + user := m[2] + if _, ok := usersByCluster[cluster]; !ok { + // If the outer key doesn't exist, initialize the nested map + usersByCluster[cluster] = make(map[string]struct{}) + } + + usersByCluster[cluster][user] = struct{}{} + } + + // Parse `users` object. + usersObject := usersObjectPat.FindString(data) + if usersObject == "" { + return nil, []error{noUsersObjectError} + } + userEntries := userEntryPat.FindAllStringSubmatch(usersObject, -1) + if len(userEntries) == 0 { + return nil, []error{noUsersError} + } + + authByUser := make(map[string]clusterAuth) + parseErrors := make([]error, 0) + for _, m := range userEntries { + var ( + name = m[1] + authType = m[2] + authValue = m[3] + + auth clusterAuth + ) + + switch authType { + case "client-key": + // Path to a file; we can't use this. + auth = clusterAuth{Type: externalAuth} + case "client-key-data": + auth = clusterAuth{ + Type: clientKeyAuth, + ClientKey: authValue, + } + + // The base64 decoder can provide mangled data. + // Ensure that the certificate value starts with `-----BEGIN`, base64-encoded. + if !strings.HasPrefix(authValue, "LS0tLS1CRUdJTi") { + continue + } + case "password": + auth = clusterAuth{ + Type: passwordAuth, + Password: authValue, + } + case "token": + auth = clusterAuth{ + Type: tokenAuth, + Token: authValue, + } + case "auth-provider", "exec": + // Authentication can use a custom `auth-provider`, `exec` command, or other extension. + auth = clusterAuth{Type: externalAuth} + default: + parseErrors = append(parseErrors, fmt.Errorf("user '%s' has unknown auth type: %s", name, authType)) + auth = clusterAuth{Type: unknownAuth} + } + + authByUser[name] = auth + } + + // Assemble the data. + clusters := make([]cluster, 0) + for clusterName, clusterUrl := range urlByClusterName { + users, ok := usersByCluster[clusterName] + if !ok { + // This could indicate that the file was truncated by the chunker. + err := fmt.Errorf("cluster '%s' has no associated users", clusterName) + parseErrors = append(parseErrors, err) + continue + } + + for user := range users { + cluster := cluster{ + Server: clusterUrl, + User: user, + } + + auth, ok := authByUser[user] + if !ok { + // This could indicate that the file was truncated by the chunker. + err := fmt.Errorf("user '%s@%s' has no associated auth info", user, clusterName) + parseErrors = append(parseErrors, err) + continue + } else if auth.Type == unknownAuth || auth.Type == externalAuth { + // Auth info was found for the user, but we can't use it for some reason. + continue + } + + // If the auth is password, there is a distinct `username` field that we need. + if auth.Type == passwordAuth { + usernamePat := userUsernamePat(auth.Password) + matches := usernamePat.FindStringSubmatch(data) + if len(matches) == 0 { + // This could indicate that the file was truncated by the chunker. + err := fmt.Errorf("user '%s@%s' auth info has a `password` but no `username`", user, clusterName) + parseErrors = append(parseErrors, err) + continue + } + + auth.Username = matches[1] + } + + cluster.Auth = auth + clusters = append(clusters, cluster) + } + } + + return clusters, parseErrors +} diff --git a/pkg/detectors/kubeconfig/parse_yaml_test.go b/pkg/detectors/kubeconfig/parse_yaml_test.go new file mode 100644 index 000000000000..bab69b25cfcc --- /dev/null +++ b/pkg/detectors/kubeconfig/parse_yaml_test.go @@ -0,0 +1,319 @@ +package kubeconfig + +import ( + "errors" + "testing" +) + +// ClientKey and token?? +// https://github.com/jakkaj/ravenswood/blob/e31763d7dd8cf59af0c7bacb370e1edcbb31272f/deployments/Scripts/builds/yourbuild/aks_kubeconfig.yaml#L19 +func Test_ParseYamlConfig(t *testing.T) { + tests := []testCase{ + // True Positives + { + name: "ClientKeyData auth", + input: `apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXBPZ0F3SUJBZ0lVTWVuVHNleWtMcGQreWdPZGhzZkZCa3R0UTlNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2JERUxNQWtHQTFVRUJoTUNRMDR4RURBT0JnTlZCQWdUQjBKbGFVcHBibWN4RURBT0JnTlZCQWNUQjBKbAphVXBwYm1jeEV6QVJCZ05WQkFvVENrdDFZbVZ5Ym1WMFpYTXhEekFOQmdOVkJBc1RCbE41YzNSbGJURVRNQkVHCkExVUVBeE1LYTNWaVpYSnVaWFJsY3pBZUZ3MHhPREF4TVRBd09UTTBNREJhRncweU16QXhNRGt3T1RNME1EQmEKTUd3eEN6QUpCZ05WQkFZVEFrTk9NUkF3RGdZRFZRUUlFd2RDWldsS2FXNW5NUkF3RGdZRFZRUUhFd2RDWldsSwphVzVuTVJNd0VRWURWUVFLRXdwTGRXSmxjbTVsZEdWek1ROHdEUVlEVlFRTEV3WlRlWE4wWlcweEV6QVJCZ05WCkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRREkKM09HOE91d0h2ZVIvY3RSZE1naUJRMGc2cXozYmFnNTlDRG1yMURaU2R3bm9TTzZVR0QyanpjQllWVFJOTFFwWQpuc2FBY1pLa3FONmJDdHRiVGdCVkd4ZGltNGQzdXdveS9kcnpTeU9zclVnWUVsZGQwV0p2Nk5xZVUrUGVOd01pCmg1WGxTcFlYbTF1eTBDejFSdFlLbUZxZUFoMTN2c1hwMGptcEQ0NCtQMStIN0dvaWlXSTNMakVCOFNlcVM2LzIKUzVvNXlWdC92VzlUTlRzZ1FRL2o1Q2tNYnRkMGdDVzdGdk14ckpabXZFNi9Ocm9vU3o0WDZVb3M1TkxZVFYwdwpyVDhJbDZ2N3l4d2xHcEZ3dE0wSy9yMGRQN1U0eGRjUVFjQzlkOFRqaE5SMWczNEVaeXBNZ2FybFBVWDYzK1AxCjhWMXExanBScXdGZG9uS1lNSWExQWdNQkFBR2pSVEJETUE0R0ExVWREd0VCL3dRRUF3SUJCakFTQmdOVkhSTUIKQWY4RUNEQUdBUUgvQWdFQ01CMEdBMVVkRGdRV0JCUklROENvNnl4NEh1RXZ3anZlR0NQZXBDUGVpekFOQmdrcQpoa2lHOXcwQkFRc0ZBQU9DQVFFQXZxaGFvUjUzdEZSMmlDdllaWFc0Zmc5Zlh2Y25mUEFxY3pwTEtIbVNjUDdZCjdZWWxJOVF2Vkg1YXVZanU0WjRjVWVsMFErcytUZXp6dGFkRDYvMHJ2K1pVRlB5a0NxQld4NDh1NjRmZ2JlNnIKN25USnJjRXY3Sm54eENSN04vem5YQ25rYStDOE5vMEE2UzJCU1ZHS29rSkZzNmNmMy85OEZLazQyU0ZqM2NsNwpLMytobUtJU0hQMGxWQ2ZwNWVhTHVTcEYzakwvYUdmVk4wb1dLaWJFYWN2QWFhUE5DR29zMkRFV0NsNjNMY2wxCnhoTktsWjlBdDgvN2M0ejRSemJwUDJVaE9FYkRGQlYyRXhhLy9ya0NEdnhDUWtudmtva004TnN0Q1loc2MyWVkKNjNIang0K3hNZjZJNUhlUVBUS2Y3dy9LZVZwbFdLdkVBQVV4MFhYUTVRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + server: https://console-kubernetes.example.com:443 + name: console-kubernetes-example-com:443 +- cluster: + certificate-authority: ./ca.crt + server: https://192.168.99.100:8443 + name: minikube +contexts: +- context: + cluster: console-kubernetes-example-com:443 + user: "system:serviceaccount:default:example/console-kubernetes-example-com:443" + name: default +- context: + cluster: minikube + user: minikube + name: minikube +current-context: default +kind: Config +preferences: {} +users: +- name: "system:serviceaccount:default:example/console-kubernetes-example-com:443" + user: + as-user-extra: {} + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQ3RENDQXRTZ0F3SUJBZ0lVREZGNzY0cVRUeGJsbWpEWEdqZGp0M0RYWnFvd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2JERUxNQWtHQTFVRUJoTUNRMDR4RURBT0JnTlZCQWdUQjBKbGFVcHBibWN4RURBT0JnTlZCQWNUQjBKbAphVXBwYm1jeEV6QVJCZ05WQkFvVENrdDFZbVZ5Ym1WMFpYTXhEekFOQmdOVkJBc1RCbE41YzNSbGJURVRNQkVHCkExVUVBeE1LYTNWaVpYSnVaWFJsY3pBZUZ3MHhPREF4TVRBd09UVXhNREJhRncweU9EQXhNRGd3T1RVeE1EQmEKTUhNeEN6QUpCZ05WQkFZVEFrTk9NUkF3RGdZRFZRUUlFd2RDWldsS2FXNW5NUkF3RGdZRFZRUUhFd2RDWldsSwphVzVuTVJNd0VRWURWUVFLRXdwcmRXSmxjbTVsZEdWek1ROHdEUVlEVlFRTEV3WlRlWE4wWlcweEdqQVlCZ05WCkJBTVRFWE41YzNSbGJUcHJkV0psTFhCeWIzaDVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUIKQ2dLQ0FRRUFwMXpVd1Q3VzNucnlIZEJoS0lvMkYxTXpwWGxYMndXbUZQTlViMUMvQytqMTZxT0pjZlpZLzRxbApqdHl1TjBrWjZWUmN3U3hXZk5rZm04ejd3emJvNW1jd1lEUUgzR0JWdG43aXY5dFhjSG16OHJkVGhKOGdHWERzClVsQVRCc1Y5YWExa21jbHhZbjBCMEh5T1oycnNEdC80TW8vYm1hUWIzT2JyR1pqbUNyVnVnY0xjeGFpSk9ycWEKRDZRT1I4S1FxQ2ZNbThRbzRmeDdjVFY0NlZ0aHVTbklUc001RmRmdUxkU1JQbWxCcE5aeElHU2kwZ0JhK0xOUgpacmlRYnBRSHY5b1NhOTRaVlY4RnVudVdrZUEzOThvWDloZTlwR1dVL0hDeEZNRHlONnRybTlNSlNTeEtQSExaCkF4dllsOHFGL2JRS2FLK3hreFdNSmloN2xUOFprd0lEQVFBQm8zOHdmVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXcKSFFZRFZSMGxCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQndNQ01Bd0dBMVVkRXdFQi93UUNNQUF3SFFZRApWUjBPQkJZRUZBUG9sQUVPVElBWllzbHdrTzFXTTBYNjgvWE5NQjhHQTFVZEl3UVlNQmFBRkVoRHdLanJMSGdlCjRTL0NPOTRZSTk2a0k5NkxNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUI4cUlCdFF0UWdMT3MyVW5LRHNIMlMKUCtYaW5STlFLSGRFdllhd2w3L0FwZ0RkcHVFV09KZmFENnVZQ3NvSFNaMDQwQzg5ckJWOVJ2MXlzT0lsbXVCRApVOTFrZU5hTkkwQlM5dm95VWZlbzJ0VVF6bk85NGpmVWphY1pCSkdIY0g2TTVtZ2ZKTG9JR3NYR0grazc3QkduCnZWQmVMUENUMGtPa3pla0JhZlEwYzA2WElTdGFtKzhpcFJaUDZaZ3E0eXZOc3JPSm9QcTBxSlcvd0FtMjdQM1IKNWdpMUVWM09iL1M3N3IvSGZjTW81RTdiRUR3eWhSVHZtVDFSWmJkNURMeXZwODRvNS9KMXRzVExvL25pZ0VnbgpTdmhSNWVleDIwNXBwZWlWYXEyUCtXTlFweFg3VXlMS3VaSFhqVFNXNzQvc05tcU5Na08wcHdEYTZoQU1XR2ZRCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBcDF6VXdUN1czbnJ5SGRCaEtJbzJGMU16cFhsWDJ3V21GUE5VYjFDL0MrajE2cU9KCmNmWlkvNHFsanR5dU4wa1o2VlJjd1N4V2ZOa2ZtOHo3d3pibzVtY3dZRFFIM0dCVnRuN2l2OXRYY0htejhyZFQKaEo4Z0dYRHNVbEFUQnNWOWFhMWttY2x4WW4wQjBIeU9aMnJzRHQvNE1vL2JtYVFiM09ickdaam1DclZ1Z2NMYwp4YWlKT3JxYUQ2UU9SOEtRcUNmTW04UW80Zng3Y1RWNDZWdGh1U25JVHNNNUZkZnVMZFNSUG1sQnBOWnhJR1NpCjBnQmErTE5SWnJpUWJwUUh2OW9TYTk0WlZWOEZ1bnVXa2VBMzk4b1g5aGU5cEdXVS9IQ3hGTUR5TjZ0cm05TUoKU1N4S1BITFpBeHZZbDhxRi9iUUthSyt4a3hXTUppaDdsVDhaa3dJREFRQUJBb0lCQUZOVWhsNDlvcENkMktWOQpscEt2MW1UZ3VXdGZzcDNML3ppWk8yWTlaeEpRQ1BtdU9ZWXpxWFo3R3htNXlVaWZyalllR2h6WXJhdDJGQ1huCjkwYm90U2ZiSXh3VGJBS1BPTDRvQ1ZDTHJzckMzaFV3c0hYdElQZHA0VkRPcTlxSVJIeDBxQTFtWG4weVRzLzIKNUpTYmlUT1MwcXFpTkM0WXB3TGpPeFhBcW5HVHZDQmd6OEhWMFJwSEgvaGtBVXJkTWlnNTJLeXNQTGtMSlBzUwozLzlpMGpGTnloU0RQVHFZajg3dVkwOS9ROEtLaHRiUm1tUXgzU1hZTUF6SVQyVllxc0hldXBaZzlRSjlRbkhzCngyQXVpdVVFNURTN25IN2pndFhGQlNzMy9qWml2TWtnQzI0SHk5Yi9OdlU4S0lZQlh0WEwvT1pEUXMyNnJ2VloKZUxMT1NKa0NnWUVBdzVZU1lnZDZjcmRKM1gyQnE3V2dveGMwQWpvbXc5aEVnamVzL2lUbEZpb1NFRXlNbGFFbwo5UFJwUzY5MU53YUxBZzRvcmNCdnZLQm9vRTVldERxaXFNQTc3OUF4ZEVlckVJSXJxQlJDUGRXZ29zVTVKdkNsClkxV2QyMTJabmhEc3ptTU4wbjFTS2h0aldjb0o1SU5WN2Q5Q3pya1R1MW1ETTBMdSsweUswMzhDZ1lFQTJ3NzgKWWJZWVNVMmNxeGhtSTdrZ3NESnNtK1RTQUkyYlpwZ29DMmpBZjlMSU5DSjJoRmUyWWxqeW4vSHMwbUI4dFF4dwozMGluRWJCVVA0amRrTS93Tll3dG0wQ1dFQnRrRWRpVkJkYXZlTFNDaGhHdmsrM0lTdDlCaGsrd1E5cUxKODFlCm1kdTZxcmZPdS84M3pSSmdsUVRLSVhFRXBpUGdKc21KSlc3cE0rMENnWUFtUUNaT042b3gzemk1OFg2M3B5akkKWEpSV1R5c2ZxQjhWM0crZnNIV0JGUzg5TXN0WHhCSHZmaEZOdFAzV2loZ0xpZHRZeDhiU2ZBaWFPVmw2SS9HRgowVHFubHU3bEQ5TWJ3bWxwVUxUM3hOekttSW1wM094cmRlWU9iY3JLU0FNWUJmVkJFak5NZXRpK1NhNFBtOFBsClpvRjVUbWJXZ0JZUm8yaDdpeWVuWHdLQmdRQ3FHY2JzOFFPRzJGZVJuRTZqNnJ0eFZwWnpyNGxLbUt0VlRVMjcKSGtwc2QzYXkxUmdHeUQxOXZPZ2FQemZRWE5BNW5rRi9nT0VLb1V1cVVsTUtnZzFhNTFENnYzcEhZNTJmSmZrQwpJYVQ4Szk4MjBFRHdzN0hXUWVxVnF3ZUtpUWVKanJXbzc3RFJwQTFLZW5JUU1mY0JnRWlkRXkreSt5U3h1Y2xmCllmS0FPUUtCZ1FDU05EK1g5SjVOaVBGOTRyWG1CaE9KTVNkelB6eUV2Y1NwTjJ6ZkY5WFEzcUtJVWlaQUdtdmUKelVSUzNlTUt6TmJhaUt5cXZvMEE0c3hlb2czcldPNEN1a0pmRW01NWViNXl3VlRSOXZYeWt3YmdMR08xOHU3TwpYOEJKNzB5QmJYUHN6c2NpWDR5ak9hbWF4eGFDVkxCU2ZDWkhiaGZNVDVnR0FJZTNTV2duQVE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= +- name: minikube + user: + as-user-extra: {} + client-certificate: ./client.crt + client-key: ./client.key +`, + want: []cluster{ + { + Server: "https://console-kubernetes.example.com:443", + User: "system:serviceaccount:default:example/console-kubernetes-example-com:443", + Auth: clusterAuth{ + Type: clientKeyAuth, + ClientKey: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBcDF6VXdUN1czbnJ5SGRCaEtJbzJGMU16cFhsWDJ3V21GUE5VYjFDL0MrajE2cU9KCmNmWlkvNHFsanR5dU4wa1o2VlJjd1N4V2ZOa2ZtOHo3d3pibzVtY3dZRFFIM0dCVnRuN2l2OXRYY0htejhyZFQKaEo4Z0dYRHNVbEFUQnNWOWFhMWttY2x4WW4wQjBIeU9aMnJzRHQvNE1vL2JtYVFiM09ickdaam1DclZ1Z2NMYwp4YWlKT3JxYUQ2UU9SOEtRcUNmTW04UW80Zng3Y1RWNDZWdGh1U25JVHNNNUZkZnVMZFNSUG1sQnBOWnhJR1NpCjBnQmErTE5SWnJpUWJwUUh2OW9TYTk0WlZWOEZ1bnVXa2VBMzk4b1g5aGU5cEdXVS9IQ3hGTUR5TjZ0cm05TUoKU1N4S1BITFpBeHZZbDhxRi9iUUthSyt4a3hXTUppaDdsVDhaa3dJREFRQUJBb0lCQUZOVWhsNDlvcENkMktWOQpscEt2MW1UZ3VXdGZzcDNML3ppWk8yWTlaeEpRQ1BtdU9ZWXpxWFo3R3htNXlVaWZyalllR2h6WXJhdDJGQ1huCjkwYm90U2ZiSXh3VGJBS1BPTDRvQ1ZDTHJzckMzaFV3c0hYdElQZHA0VkRPcTlxSVJIeDBxQTFtWG4weVRzLzIKNUpTYmlUT1MwcXFpTkM0WXB3TGpPeFhBcW5HVHZDQmd6OEhWMFJwSEgvaGtBVXJkTWlnNTJLeXNQTGtMSlBzUwozLzlpMGpGTnloU0RQVHFZajg3dVkwOS9ROEtLaHRiUm1tUXgzU1hZTUF6SVQyVllxc0hldXBaZzlRSjlRbkhzCngyQXVpdVVFNURTN25IN2pndFhGQlNzMy9qWml2TWtnQzI0SHk5Yi9OdlU4S0lZQlh0WEwvT1pEUXMyNnJ2VloKZUxMT1NKa0NnWUVBdzVZU1lnZDZjcmRKM1gyQnE3V2dveGMwQWpvbXc5aEVnamVzL2lUbEZpb1NFRXlNbGFFbwo5UFJwUzY5MU53YUxBZzRvcmNCdnZLQm9vRTVldERxaXFNQTc3OUF4ZEVlckVJSXJxQlJDUGRXZ29zVTVKdkNsClkxV2QyMTJabmhEc3ptTU4wbjFTS2h0aldjb0o1SU5WN2Q5Q3pya1R1MW1ETTBMdSsweUswMzhDZ1lFQTJ3NzgKWWJZWVNVMmNxeGhtSTdrZ3NESnNtK1RTQUkyYlpwZ29DMmpBZjlMSU5DSjJoRmUyWWxqeW4vSHMwbUI4dFF4dwozMGluRWJCVVA0amRrTS93Tll3dG0wQ1dFQnRrRWRpVkJkYXZlTFNDaGhHdmsrM0lTdDlCaGsrd1E5cUxKODFlCm1kdTZxcmZPdS84M3pSSmdsUVRLSVhFRXBpUGdKc21KSlc3cE0rMENnWUFtUUNaT042b3gzemk1OFg2M3B5akkKWEpSV1R5c2ZxQjhWM0crZnNIV0JGUzg5TXN0WHhCSHZmaEZOdFAzV2loZ0xpZHRZeDhiU2ZBaWFPVmw2SS9HRgowVHFubHU3bEQ5TWJ3bWxwVUxUM3hOekttSW1wM094cmRlWU9iY3JLU0FNWUJmVkJFak5NZXRpK1NhNFBtOFBsClpvRjVUbWJXZ0JZUm8yaDdpeWVuWHdLQmdRQ3FHY2JzOFFPRzJGZVJuRTZqNnJ0eFZwWnpyNGxLbUt0VlRVMjcKSGtwc2QzYXkxUmdHeUQxOXZPZ2FQemZRWE5BNW5rRi9nT0VLb1V1cVVsTUtnZzFhNTFENnYzcEhZNTJmSmZrQwpJYVQ4Szk4MjBFRHdzN0hXUWVxVnF3ZUtpUWVKanJXbzc3RFJwQTFLZW5JUU1mY0JnRWlkRXkreSt5U3h1Y2xmCllmS0FPUUtCZ1FDU05EK1g5SjVOaVBGOTRyWG1CaE9KTVNkelB6eUV2Y1NwTjJ6ZkY5WFEzcUtJVWlaQUdtdmUKelVSUzNlTUt6TmJhaUt5cXZvMEE0c3hlb2czcldPNEN1a0pmRW01NWViNXl3VlRSOXZYeWt3YmdMR08xOHU3TwpYOEJKNzB5QmJYUHN6c2NpWDR5ak9hbWF4eGFDVkxCU2ZDWkhiaGZNVDVnR0FJZTNTV2duQVE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=", + }, + }, + }, + }, + { + name: "Password auth", + input: `apiVersion: v1 +kind: Config +preferences: {} +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJWakNCL3FBREFnRUNBZ0VBTUFvR0NDcUdTTTQ5QkFNQ01DTXhJVEFmQmdOVkJBTU1HR3N6Y3kxelpYSjIKWlhJdFkyRkFNVFU1T0RJNE5qZ3pNekFlRncweU1EQTRNalF4TmpNek5UTmFGdzB6TURBNE1qSXhOak16TlROYQpNQ014SVRBZkJnTlZCQU1NR0dzemN5MXpaWEoyWlhJdFkyRkFNVFU1T0RJNE5qZ3pNekJaTUJNR0J5cUdTTTQ5CkFnRUdDQ3FHU000OUF3RUhBMElBQkp3ZTF2UXR0T1F6c2xaVTNUQkhSWlQzS293UWdHdnY1TnYvZko5NTRpSkkKbG5nbHVyZktWQk9nbDM2bFp2UGJGdzVKTTFpSWYzaEVmVTZxU3crNThSR2pJekFoTUE0R0ExVWREd0VCL3dRRQpBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUVSMG81dm82K1pXCkVkaE1GYXpVWk9CTnhkVk14N0dRTTY1MCsyWFBzZTBJQWlBVkJBWmZUdE94SkN0Q2lZQnp6alhzM29MWWhyZzYKcFVhQ0o3Nyt0VFpycUE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + server: https://127.0.0.1:6443 + name: default +contexts: +- context: + cluster: default + user: default + name: default +current-context: default +users: +- name: default + user: + password: f976df38c1bc4ed617413adef8844217 + username: admin`, + want: []cluster{ + { + Server: "https://127.0.0.1:6443", + User: "default", + Auth: clusterAuth{ + Type: passwordAuth, + Username: "admin", + Password: "f976df38c1bc4ed617413adef8844217", + }, + }, + }, + }, + { + name: "Token auth", + input: `apiVersion: v1 +clusters: +- cluster: + insecure-skip-tls-verify: true + server: https://192.168.0.27:8080/r/projects/1a5/kubernetes:6443 + name: Default +- cluster: + certificate-authority: ./ca.crt + server: https://192.168.99.100:8443 + name: minikube +contexts: +- context: + cluster: Default + user: Default + name: Default +- context: + cluster: minikube + user: minikube + name: minikube +current-context: minikube +kind: Config +preferences: {} +users: +- name: Default + user: + token: QmFzaWMgT0VZMk5qQTBOa0pDTlRjNU1UTTFSRUpDT1RFNlEzVlNWWEZIVEhGMU4yUnpTRXhLU0dGbGRWcGplRlJsY0dKVVpISmphRk5FVGtGV1VVdGFkQT09 +- name: minikube + user: + as-user-extra: {} + client-certificate: ./client.crt + client-key: ./client.key`, + want: []cluster{ + { + Server: "https://192.168.0.27:8080/r/projects/1a5/kubernetes:6443", + User: "Default", + Auth: clusterAuth{ + Type: tokenAuth, + Token: "QmFzaWMgT0VZMk5qQTBOa0pDTlRjNU1UTTFSRUpDT1RFNlEzVlNWWEZIVEhGMU4yUnpTRXhLU0dGbGRWcGplRlJsY0dKVVpISmphRk5FVGtGV1VVdGFkQT09", + }, + }, + }, + }, + + // Errors + { + name: "No auth", + input: `apiVersion: v1 +kind: Config +preferences: {} +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJWakNCL3FBREFnRUNBZ0VBTUFvR0NDcUdTTTQ5QkFNQ01DTXhJVEFmQmdOVkJBTU1HR3N6Y3kxelpYSjIKWlhJdFkyRkFNVFU1T0RJNE5qZ3pNekFlRncweU1EQTRNalF4TmpNek5UTmFGdzB6TURBNE1qSXhOak16TlROYQpNQ014SVRBZkJnTlZCQU1NR0dzemN5MXpaWEoyWlhJdFkyRkFNVFU1T0RJNE5qZ3pNekJaTUJNR0J5cUdTTTQ5CkFnRUdDQ3FHU000OUF3RUhBMElBQkp3ZTF2UXR0T1F6c2xaVTNUQkhSWlQzS293UWdHdnY1TnYvZko5NTRpSkkKbG5nbHVyZktWQk9nbDM2bFp2UGJGdzVKTTFpSWYzaEVmVTZxU3crNThSR2pJekFoTUE0R0ExVWREd0VCL3dRRQpBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUVSMG81dm82K1pXCkVkaE1GYXpVWk9CTnhkVk14N0dRTTY1MCsyWFBzZTBJQWlBVkJBWmZUdE94SkN0Q2lZQnp6alhzM29MWWhyZzYKcFVhQ0o3Nyt0VFpycUE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + server: https://127.0.0.1:6443 + name: default +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJWakNCL3FBREFnRUNBZ0VBTUFvR0NDcUdTTTQ5QkFNQ01DTXhJVEFmQmdOVkJBTU1HR3N6Y3kxelpYSjIKWlhJdFkyRkFNVFU1T0RJNE5qZ3pNekFlRncweU1EQTRNalF4TmpNek5UTmFGdzB6TURBNE1qSXhOak16TlROYQpNQ014SVRBZkJnTlZCQU1NR0dzemN5MXpaWEoyWlhJdFkyRkFNVFU1T0RJNE5qZ3pNekJaTUJNR0J5cUdTTTQ5CkFnRUdDQ3FHU000OUF3RUhBMElBQkp3ZTF2UXR0T1F6c2xaVTNUQkhSWlQzS293UWdHdnY1TnYvZko5NTRpSkkKbG5nbHVyZktWQk9nbDM2bFp2UGJGdzVKTTFpSWYzaEVmVTZxU3crNThSR2pJekFoTUE0R0ExVWREd0VCL3dRRQpBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUVSMG81dm82K1pXCkVkaE1GYXpVWk9CTnhkVk14N0dRTTY1MCsyWFBzZTBJQWlBVkJBWmZUdE94SkN0Q2lZQnp6alhzM29MWWhyZzYKcFVhQ0o3Nyt0VFpycUE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + server: https://127.0.0.2:6443 + name: default2 +contexts: +- context: + cluster: default + user: default + name: default +- context: + cluster: default2 + user: default2 + name: default2 +current-context: default +users: +- name: default + user: + password: f976df38c1bc4ed617413adef8844217 + username: admin`, + want: []cluster{ + { + Server: "https://127.0.0.1:6443", + User: "default", + Auth: clusterAuth{ + Type: passwordAuth, + Username: "admin", + Password: "f976df38c1bc4ed617413adef8844217", + }, + }, + }, + wantErrs: []error{errors.New("user 'default2@default2' has no associated auth info")}, + }, + { + name: "Empty config", + input: `clusters: +contexts: +current-context: +kind: Config +preferences: {} +users: `, + wantErrs: []error{noClusterEntriesError}, + }, + + // False positives + // { + // name: "typical", + // input: `apiVersion: v1 + //clusters: + //- cluster: + // certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM2akNDQWRLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJeU1EY3hOREV6TVRjek0xb1hEVE15TURjeE1URXpNakl6TTFvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVZ1CjB3STdVdDd0R3VoR0l1Y1VOM1VWOUJJRzVtalBHVjBGYnZFaTZHL1pTQlJ2YXpkdWNBcjAreG9xQm9qbFppUUsKaFF3RkppVHJxVHdZODRjMmgvSXM5ZVJjMit6V1g5cDZwOVlmdnJzYlBhZjB0elp2cU9RRGRSeGVZUUg0dC9HUQpOSUxOSmJqSU9NSXFNbjhjWVNhbXZYZmovUWJuQWx2QlI3VC9uVzh3VjdyeVJSQjZ2a3RxRkEzaVY0STQveWpHClI5RW4vLzQ1Y0xVU1dIL1hGb1U2a21iaEJQYkJSTHY2WjJHRkZuS29SVTVuc01iaE0vZk9RV3F1emZPUDgxUHoKTzkwczVtQ0w2bnkwOU14VzlOdGV3ZE55UkprTElWSTl5aDJwRjBuNW9vTHNrTC91ODRBdTh2VTc5K0szaDZZWgptT2t2NzBpK0FUY0lBa1BialdNQ0F3RUFBYU5GTUVNd0RnWURWUjBQQVFIL0JBUURBZ0trTUJJR0ExVWRFd0VCCi93UUlNQVlCQWY4Q0FRQXdIUVlEVlIwT0JCWUVGUG9zSG5CeTlIaXhsWi93UmJIVnJKS3IvTEFITUEwR0NTcUcKU0liM0RRRUJDd1VBQTRJQkFRQWZiaVZERFhNWkZnYlhxcVd4QUdmRmVKVkhDL3JKMmZQWjJqVmt0TVZNQS92TAozTGFKc3dTaVJxeTIzNFZTVXRRSE0zZDl4ZUo4MzFqK0locUtndkNVVWpwNGRlL1Q5U284R0dITlB0RWh2ZWF3Cm1jWFBDcHJmZnpweSt5QmpTTTEvRGY0bnlFTmRwZkpWOFlCUU8xNmtaQ2d0d1lTOU1STEZidU5nYUFmZ2VZMmoKbG0ycHFoOHJPWmphUkZLQlZoZHJ5cGJic204V2xqRVlrZVBQOFB2N281QXo4Nm9iYkRqSFhiekY1RHVjOEllSQpidFNCbDl4eUwzbFZzblFSUHlLeG5jc2M5MElHb3NNRWJGRDZYZnkwcWRKVitsL2lwYkU0UmhnSllJM3VUMGdTClN0d0IxNXF5N09OR0NmZy9tem94MlJQSHRhdThmNXltVFlqN3dPNEIKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + // server: https://172.16.16.2:6443 + // name: clstr-mgmt01`, + // want: []string{"https://172.16.16.2:6443", "clstr-mgmt01"}, + // }, + // { + // name: "extra data between 'cluster' and 'server'", + // input: `apiVersion: v1 + //clusters: + //- cluster: + // certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCakNDQWU2Z0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwdGFXNXAKYTNWaVpVTkJNQjRYRFRJeE1Ea3hOakU0TkRNME0xb1hEVE14TURreE5URTRORE0wTTFvd0ZURVRNQkVHQTFVRQpBeE1LYldsdWFXdDFZbVZEUVRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTG1SCk1DSHJheEQ0WmFRYnFWMmM1dXdBR2s1bDFkM3djQkl2b0FIY2tUaVMzY1BKWFJBR3poZmJ0ZmJWYjFIZmh1QncKQyt5dFA2Q2hub0ZWeXI4ZGEwNXNGeTFzS1J0ZFFqdnZMWW0rbHZ2RDMrNUJmemszUCtFMzg1ZVcxTE5VQkRwcgp3azljS2VwR3B5ZUZRNms2RzhERjY3QkJOelFKU1QxcXVVVVFGWGZITE1HRnY1c2F3NlVjaDFXa2IrQUpRN3l5CjBEcTNVNnFWekZwZzJwMTlWbVM5dHpBOTBoTWZlYi8vNEUyUm8wT0tlRktSUVZnSnZIcFZMeU9oZ0xGL2ZxcCsKd1FNU0JQYTIwN3JwaW8xd29aQjNBNWJxbzdBbkpWOWNRSWc5aDVXZzBMS1ZQTUhyUDVnQ1dqd3BPeGRPVXZ0bgpnSDlRVDQ4UDJsKzJDbGo5Q29jQ0F3RUFBYU5oTUY4d0RnWURWUjBQQVFIL0JBUURBZ0trTUIwR0ExVWRKUVFXCk1CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0RBVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVcKQkJRcy9pT3pVK0xpMDV2UXFUVWR6TnhCYXovakR6QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFqSzF6bmoyWgpNbWxOa3NlVml3QnJDdVhJaGdzSDliUXVRMlhjVm1uUStLb0VGMzh1Nk9SU2F5djR6ek5nOVZPT1pmWlk0VlhYCkRMTjZHaTJieDRJK0pXZy9JS25NSzVaL3BpVXBienFtTHlaNWlBdlNiV0lwM0lvakpQOWkrNk8vakpFTHZGN3MKUG1rczBLNzhYRlBpYit2di9UMjVvUGNpTDIyYncvM0RtN1lMMDM4MXlqamkyK3U4cUNHbjZQS0lFekFBM01acApQeCtYU1BRaTgwcEtYUUVlY2R6MVoxSWZzenZJUkt3aHVjeURpUXc3OC9DNTlVQmlmb0NZeWJ2cXpkQytRdlUyCld1a0NzanozY0FwOVRpZEtDYVBhMnloakZRc3REeFVnYWI4RmhYYTJjVVBFanZhdDB1cnZ4bVluT1ZhM0pNbFQKNTNkSFJiMGxUb3NTWmc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + // extensions: + // - extension: + // last-update: Fri, 17 Sep 2021 15:21:30 MDT + // provider: minikube.sigs.k8s.io + // version: v1.23.1 + // name: cluster_info + // server: https://192.168.1.129:51999 + // name: minikube`, + // want: []string{"https://192.168.1.129:51999", "minikube"}, + // }, + // { + // name: "multiple clusters", + // input: `apiVersion: v1 + //clusters: + //- cluster: + // certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM2akNDQWRLZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJeU1EY3hOREV6TVRjek0xb1hEVE15TURjeE1URXpNakl6TTFvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVZ1CjB3STdVdDd0R3VoR0l1Y1VOM1VWOUJJRzVtalBHVjBGYnZFaTZHL1pTQlJ2YXpkdWNBcjAreG9xQm9qbFppUUsKaFF3RkppVHJxVHdZODRjMmgvSXM5ZVJjMit6V1g5cDZwOVlmdnJzYlBhZjB0elp2cU9RRGRSeGVZUUg0dC9HUQpOSUxOSmJqSU9NSXFNbjhjWVNhbXZYZmovUWJuQWx2QlI3VC9uVzh3VjdyeVJSQjZ2a3RxRkEzaVY0STQveWpHClI5RW4vLzQ1Y0xVU1dIL1hGb1U2a21iaEJQYkJSTHY2WjJHRkZuS29SVTVuc01iaE0vZk9RV3F1emZPUDgxUHoKTzkwczVtQ0w2bnkwOU14VzlOdGV3ZE55UkprTElWSTl5aDJwRjBuNW9vTHNrTC91ODRBdTh2VTc5K0szaDZZWgptT2t2NzBpK0FUY0lBa1BialdNQ0F3RUFBYU5GTUVNd0RnWURWUjBQQVFIL0JBUURBZ0trTUJJR0ExVWRFd0VCCi93UUlNQVlCQWY4Q0FRQXdIUVlEVlIwT0JCWUVGUG9zSG5CeTlIaXhsWi93UmJIVnJKS3IvTEFITUEwR0NTcUcKU0liM0RRRUJDd1VBQTRJQkFRQWZiaVZERFhNWkZnYlhxcVd4QUdmRmVKVkhDL3JKMmZQWjJqVmt0TVZNQS92TAozTGFKc3dTaVJxeTIzNFZTVXRRSE0zZDl4ZUo4MzFqK0locUtndkNVVWpwNGRlL1Q5U284R0dITlB0RWh2ZWF3Cm1jWFBDcHJmZnpweSt5QmpTTTEvRGY0bnlFTmRwZkpWOFlCUU8xNmtaQ2d0d1lTOU1STEZidU5nYUFmZ2VZMmoKbG0ycHFoOHJPWmphUkZLQlZoZHJ5cGJic204V2xqRVlrZVBQOFB2N281QXo4Nm9iYkRqSFhiekY1RHVjOEllSQpidFNCbDl4eUwzbFZzblFSUHlLeG5jc2M5MElHb3NNRWJGRDZYZnkwcWRKVitsL2lwYkU0UmhnSllJM3VUMGdTClN0d0IxNXF5N09OR0NmZy9tem94MlJQSHRhdThmNXltVFlqN3dPNEIKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + // server: https://172.16.16.2:6443 + // name: clstr-mgmt01 + //- cluster: + // certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXBPZ0F3SUJBZ0lVTWVuVHNleWtMcGQreWdPZGhzZkZCa3R0UTlNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2JERUxNQWtHQTFVRUJoTUNRMDR4RURBT0JnTlZCQWdUQjBKbGFVcHBibWN4RURBT0JnTlZCQWNUQjBKbAphVXBwYm1jeEV6QVJCZ05WQkFvVENrdDFZbVZ5Ym1WMFpYTXhEekFOQmdOVkJBc1RCbE41YzNSbGJURVRNQkVHCkExVUVBeE1LYTNWaVpYSnVaWFJsY3pBZUZ3MHhPREF4TVRBd09UTTBNREJhRncweU16QXhNRGt3T1RNME1EQmEKTUd3eEN6QUpCZ05WQkFZVEFrTk9NUkF3RGdZRFZRUUlFd2RDWldsS2FXNW5NUkF3RGdZRFZRUUhFd2RDWldsSwphVzVuTVJNd0VRWURWUVFLRXdwTGRXSmxjbTVsZEdWek1ROHdEUVlEVlFRTEV3WlRlWE4wWlcweEV6QVJCZ05WCkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRREkKM09HOE91d0h2ZVIvY3RSZE1naUJRMGc2cXozYmFnNTlDRG1yMURaU2R3bm9TTzZVR0QyanpjQllWVFJOTFFwWQpuc2FBY1pLa3FONmJDdHRiVGdCVkd4ZGltNGQzdXdveS9kcnpTeU9zclVnWUVsZGQwV0p2Nk5xZVUrUGVOd01pCmg1WGxTcFlYbTF1eTBDejFSdFlLbUZxZUFoMTN2c1hwMGptcEQ0NCtQMStIN0dvaWlXSTNMakVCOFNlcVM2LzIKUzVvNXlWdC92VzlUTlRzZ1FRL2o1Q2tNYnRkMGdDVzdGdk14ckpabXZFNi9Ocm9vU3o0WDZVb3M1TkxZVFYwdwpyVDhJbDZ2N3l4d2xHcEZ3dE0wSy9yMGRQN1U0eGRjUVFjQzlkOFRqaE5SMWczNEVaeXBNZ2FybFBVWDYzK1AxCjhWMXExanBScXdGZG9uS1lNSWExQWdNQkFBR2pSVEJETUE0R0ExVWREd0VCL3dRRUF3SUJCakFTQmdOVkhSTUIKQWY4RUNEQUdBUUgvQWdFQ01CMEdBMVVkRGdRV0JCUklROENvNnl4NEh1RXZ3anZlR0NQZXBDUGVpekFOQmdrcQpoa2lHOXcwQkFRc0ZBQU9DQVFFQXZxaGFvUjUzdEZSMmlDdllaWFc0Zmc5Zlh2Y25mUEFxY3pwTEtIbVNjUDdZCjdZWWxJOVF2Vkg1YXVZanU0WjRjVWVsMFErcytUZXp6dGFkRDYvMHJ2K1pVRlB5a0NxQld4NDh1NjRmZ2JlNnIKN25USnJjRXY3Sm54eENSN04vem5YQ25rYStDOE5vMEE2UzJCU1ZHS29rSkZzNmNmMy85OEZLazQyU0ZqM2NsNwpLMytobUtJU0hQMGxWQ2ZwNWVhTHVTcEYzakwvYUdmVk4wb1dLaWJFYWN2QWFhUE5DR29zMkRFV0NsNjNMY2wxCnhoTktsWjlBdDgvN2M0ejRSemJwUDJVaE9FYkRGQlYyRXhhLy9ya0NEdnhDUWtudmtva004TnN0Q1loc2MyWVkKNjNIang0K3hNZjZJNUhlUVBUS2Y3dy9LZVZwbFdLdkVBQVV4MFhYUTVRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + // server: https://172.17.8.101:6443 + // name: kubernetes + //`, + // want: []string{"https://172.16.16.2:6443", "clstr-mgmt01", "https://172.17.8.101:6443", "kubernetes"}, + // }, + // { + // name: "JSON config", + // input: `{ + // "apiVersion": "v1", + // "clusters": [ + // { + // "cluster": { + // "server": "https://k8s-master.tools.wmflabs.org:6443" + // }, + // "name": "default" + // } + // ],`, + // skip: true, + // }, + } + + runTest(t, parseYaml, tests) +} + +func Test_Yaml_DifferentOrder(t *testing.T) { + tests := []testCase{ + { + name: "clusters>users>contexts", + input: `apiVersion: v1 +kind: Config +preferences: {} +clusters: +- cluster: + server: https://127.0.0.1:443 + name: a +users: +- name: a + user: + token: s3cret +current-context: a +contexts: +- context: + cluster: a + user: a + name: a +`, + want: []cluster{ + { + Server: "https://127.0.0.1:443", + User: "a", + Auth: clusterAuth{ + Type: tokenAuth, + Token: "s3cret", + }, + }, + }, + }, + { + name: "users>contexts>clusters", + input: `apiVersion: v1 +kind: Config +users: +- name: a + user: + token: s3cret +contexts: +- context: + cluster: a + user: a + name: a +current-context: a +preferences: {} +clusters: +- cluster: + server: https://127.0.0.1:443 + name: a +`, + want: []cluster{ + { + Server: "https://127.0.0.1:443", + User: "a", + Auth: clusterAuth{ + Type: tokenAuth, + Token: "s3cret", + }, + }, + }, + }, + } + runTest(t, parseYaml, tests) +} diff --git a/pkg/engine/defaults/defaults.go b/pkg/engine/defaults/defaults.go index 5e23a833ae5a..f242d8c58436 100644 --- a/pkg/engine/defaults/defaults.go +++ b/pkg/engine/defaults/defaults.go @@ -393,6 +393,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/knapsackpro" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kontent" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kraken" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kubeconfig" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kucoin" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kylas" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/languagelayer" @@ -1225,6 +1226,7 @@ func buildDetectorList() []detectors.Detector { &knapsackpro.Scanner{}, &kontent.Scanner{}, &kraken.Scanner{}, + kubeconfig.New(), &kucoin.Scanner{}, &kylas.Scanner{}, &languagelayer.Scanner{},