From 27337b2b74c3140cf745a64f7154fe8ff7592258 Mon Sep 17 00:00:00 2001 From: Jyoti Mahapatra <49211422+jyotimahapatra@users.noreply.github.com> Date: Wed, 30 Mar 2022 12:35:31 -0700 Subject: [PATCH] Use the apiversion from KUBERNETES_EXEC_INFO (#439) Use the apiVersion from stdin KUBERNETES_EXEC_INFO --- cmd/aws-iam-authenticator/verify.go | 3 + pkg/token/token.go | 18 ++- pkg/token/token_test.go | 84 ++++++++++++++ .../k8s.io/client-go/tools/auth/exec/exec.go | 104 ++++++++++++++++++ vendor/modules.txt | 1 + 5 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 vendor/k8s.io/client-go/tools/auth/exec/exec.go diff --git a/cmd/aws-iam-authenticator/verify.go b/cmd/aws-iam-authenticator/verify.go index 7ea1ecf4b..81a455f03 100644 --- a/cmd/aws-iam-authenticator/verify.go +++ b/cmd/aws-iam-authenticator/verify.go @@ -21,9 +21,11 @@ import ( "fmt" "os" + "sigs.k8s.io/aws-iam-authenticator/pkg/metrics" "sigs.k8s.io/aws-iam-authenticator/pkg/token" "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/prometheus/client_golang/prometheus" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -70,6 +72,7 @@ var verifyCmd = &cobra.Command{ func init() { rootCmd.AddCommand(verifyCmd) + metrics.InitMetrics(prometheus.DefaultRegisterer) verifyCmd.Flags().StringP("token", "t", "", "Token to verify") verifyCmd.Flags().StringP("output", "o", "", "Output format. Only `json` is supported currently.") viper.BindPFlag("token", verifyCmd.Flags().Lookup("token")) diff --git a/pkg/token/token.go b/pkg/token/token.go index f34e06ead..b6d1fbcdc 100644 --- a/pkg/token/token.go +++ b/pkg/token/token.go @@ -38,6 +38,7 @@ import ( "github.com/aws/aws-sdk-go/service/sts/stsiface" "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/pkg/apis/clientauthentication" clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" "sigs.k8s.io/aws-iam-authenticator/pkg" "sigs.k8s.io/aws-iam-authenticator/pkg/arn" @@ -89,7 +90,9 @@ const ( clusterIDHeader = "x-k8s-aws-id" // Format of the X-Amz-Date header used for expiration // https://golang.org/pkg/time/#pkg-constants - dateHeaderFormat = "20060102T150405Z" + dateHeaderFormat = "20060102T150405Z" + kindExecCredential = "ExecCredential" + execInfoEnvKey = "KUBERNETES_EXEC_INFO" ) // Token is generated and used by Kubernetes client-go to authenticate with a Kubernetes cluster. @@ -338,11 +341,20 @@ func (g generator) GetWithSTS(clusterID string, stsAPI stsiface.STSAPI) (Token, // FormatJSON formats the json to support ExecCredential authentication func (g generator) FormatJSON(token Token) string { + apiVersion := clientauthv1beta1.SchemeGroupVersion.String() + env := os.Getenv(execInfoEnvKey) + if env != "" { + cred := &clientauthentication.ExecCredential{} + if err := json.Unmarshal([]byte(env), cred); err == nil { + apiVersion = cred.APIVersion + } + } + expirationTimestamp := metav1.NewTime(token.Expiration) execInput := &clientauthv1beta1.ExecCredential{ TypeMeta: metav1.TypeMeta{ - APIVersion: "client.authentication.k8s.io/v1beta1", - Kind: "ExecCredential", + APIVersion: apiVersion, + Kind: kindExecCredential, }, Status: &clientauthv1beta1.ExecCredentialStatus{ ExpirationTimestamp: &expirationTimestamp, diff --git a/pkg/token/token_test.go b/pkg/token/token_test.go index a186ca1f1..c8e4e6528 100644 --- a/pkg/token/token_test.go +++ b/pkg/token/token_test.go @@ -10,11 +10,17 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "os" "strings" "testing" "time" "github.com/prometheus/client_golang/prometheus" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/pkg/apis/clientauthentication" + clientauthv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" + clientauthv1alpha1 "k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1" + clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" "sigs.k8s.io/aws-iam-authenticator/pkg/metrics" ) @@ -318,3 +324,81 @@ func TestVerifyCanonicalARN(t *testing.T) { t.Errorf("expected CannonicalARN to be %q but was %q", canonicalARN, identity.CanonicalARN) } } + +func TestFormatJson(t *testing.T) { + cases := []struct { + Name string + EnvKey string + ExpectApiVersion string + IsMalformedEnv bool + }{ + { + Name: "Default", + ExpectApiVersion: clientauthv1beta1.SchemeGroupVersion.String(), + }, + { + Name: "Malformed KUBERNETES_EXEC_INFO", + EnvKey: "KUBERNETES_EXEC_INFO", + IsMalformedEnv: true, + ExpectApiVersion: clientauthv1beta1.SchemeGroupVersion.String(), + }, + { + Name: "KUBERNETES_EXEC_INFO with v1beta1", + EnvKey: "KUBERNETES_EXEC_INFO", + ExpectApiVersion: clientauthv1beta1.SchemeGroupVersion.String(), + }, + { + Name: "KUBERNETES_EXEC_INFO with v1alpha1", + EnvKey: "KUBERNETES_EXEC_INFO", + ExpectApiVersion: clientauthv1alpha1.SchemeGroupVersion.String(), + }, + { + Name: "KUBERNETES_EXEC_INFO with v1", + EnvKey: "KUBERNETES_EXEC_INFO", + ExpectApiVersion: clientauthv1.SchemeGroupVersion.String(), + }, + } + for _, c := range cases { + t.Run(c.Name, func(t *testing.T) { + expiry, _ := time.Parse(time.RFC3339, "2012-11-01T22:08:41+00:00") + token := "token" + g, _ := NewGenerator(true, true) + + if c.EnvKey != "" { + marshal := make([]byte, 0) + if !c.IsMalformedEnv { + marshal, _ = json.Marshal(clientauthentication.ExecCredential{ + TypeMeta: v1.TypeMeta{ + Kind: "ExecCredential", + APIVersion: c.ExpectApiVersion, + }, + }) + } + + os.Setenv(c.EnvKey, string(marshal)) + } + + jsonResponse := g.FormatJSON(Token{Token: token, Expiration: expiry}) + output := &clientauthentication.ExecCredential{} + json.Unmarshal([]byte(jsonResponse), output) + + if output.TypeMeta.Kind != kindExecCredential { + t.Errorf("expected Kind to be %s but was %s", kindExecCredential, output.TypeMeta.Kind) + } + + if output.TypeMeta.APIVersion != c.ExpectApiVersion { + t.Errorf("expected APIVersion to be %s but was %s", c.ExpectApiVersion, output.TypeMeta.APIVersion) + } + + if output.Status.Token != token { + t.Errorf("expected token to be %s but was %s", token, output.Status.Token) + } + + if !output.Status.ExpirationTimestamp.Time.Equal(expiry) { + t.Errorf("expected expiration to be %s but was %s", expiry, output.Status.ExpirationTimestamp) + } + + os.Unsetenv(c.EnvKey) + }) + } +} diff --git a/vendor/k8s.io/client-go/tools/auth/exec/exec.go b/vendor/k8s.io/client-go/tools/auth/exec/exec.go new file mode 100644 index 000000000..87947b0cd --- /dev/null +++ b/vendor/k8s.io/client-go/tools/auth/exec/exec.go @@ -0,0 +1,104 @@ +/* +Copyright 2020 The Kubernetes Authors. + +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 exec contains helper utilities for exec credential plugins. +package exec + +import ( + "errors" + "fmt" + "os" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/client-go/pkg/apis/clientauthentication" + "k8s.io/client-go/pkg/apis/clientauthentication/install" + "k8s.io/client-go/rest" +) + +const execInfoEnv = "KUBERNETES_EXEC_INFO" + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) + +func init() { + install.Install(scheme) +} + +// LoadExecCredentialFromEnv is a helper-wrapper around LoadExecCredential that loads from the +// well-known KUBERNETES_EXEC_INFO environment variable. +// +// When the KUBERNETES_EXEC_INFO environment variable is not set or is empty, then this function +// will immediately return an error. +func LoadExecCredentialFromEnv() (runtime.Object, *rest.Config, error) { + env := os.Getenv(execInfoEnv) + if env == "" { + return nil, nil, errors.New("KUBERNETES_EXEC_INFO env var is unset or empty") + } + return LoadExecCredential([]byte(env)) +} + +// LoadExecCredential loads the configuration needed for an exec plugin to communicate with a +// cluster. +// +// LoadExecCredential expects the provided data to be a serialized client.authentication.k8s.io +// ExecCredential object (of any version). If the provided data is invalid (i.e., it cannot be +// unmarshalled into any known client.authentication.k8s.io ExecCredential version), an error will +// be returned. A successfully unmarshalled ExecCredential will be returned as the first return +// value. +// +// If the provided data is successfully unmarshalled, but it does not contain cluster information +// (i.e., ExecCredential.Spec.Cluster == nil), then an error will be returned. +// +// Note that the returned rest.Config will use anonymous authentication, since the exec plugin has +// not returned credentials for this cluster yet. +func LoadExecCredential(data []byte) (runtime.Object, *rest.Config, error) { + obj, gvk, err := codecs.UniversalDeserializer().Decode(data, nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("decode: %w", err) + } + + expectedGK := schema.GroupKind{ + Group: clientauthentication.SchemeGroupVersion.Group, + Kind: "ExecCredential", + } + if gvk.GroupKind() != expectedGK { + return nil, nil, fmt.Errorf( + "invalid group/kind: wanted %s, got %s", + expectedGK.String(), + gvk.GroupKind().String(), + ) + } + + // Explicitly convert object here so that we can return a nicer error message above for when the + // data represents an invalid type. + var execCredential clientauthentication.ExecCredential + if err := scheme.Convert(obj, &execCredential, nil); err != nil { + return nil, nil, fmt.Errorf("cannot convert to ExecCredential: %w", err) + } + + if execCredential.Spec.Cluster == nil { + return nil, nil, errors.New("ExecCredential does not contain cluster information") + } + + restConfig, err := rest.ExecClusterToConfig(execCredential.Spec.Cluster) + if err != nil { + return nil, nil, fmt.Errorf("cannot create rest.Config: %w", err) + } + + return obj, restConfig, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3b13bdac9..d49ee4cf4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -506,6 +506,7 @@ k8s.io/client-go/rest/fake k8s.io/client-go/rest/watch k8s.io/client-go/testing k8s.io/client-go/tools/auth +k8s.io/client-go/tools/auth/exec k8s.io/client-go/tools/cache k8s.io/client-go/tools/clientcmd k8s.io/client-go/tools/clientcmd/api