mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 22:46:12 +00:00
exec credential provider: wire in cluster info
Signed-off-by: Monis Khan <mok@vmware.com>
This commit is contained in:
parent
5f2ebe4bbc
commit
f97422c8bd
@ -246,7 +246,7 @@ func restConfigFromKubeconfig(configAuthInfo *clientcmdapi.AuthInfo) (*rest.Conf
|
|||||||
config.Password = configAuthInfo.Password
|
config.Password = configAuthInfo.Password
|
||||||
}
|
}
|
||||||
if configAuthInfo.Exec != nil {
|
if configAuthInfo.Exec != nil {
|
||||||
config.ExecProvider = configAuthInfo.Exec.DeepCopy()
|
config.Exec.ExecProvider = configAuthInfo.Exec.DeepCopy()
|
||||||
}
|
}
|
||||||
if configAuthInfo.AuthProvider != nil {
|
if configAuthInfo.AuthProvider != nil {
|
||||||
return nil, fmt.Errorf("auth provider not supported")
|
return nil, fmt.Errorf("auth provider not supported")
|
||||||
|
@ -18,6 +18,7 @@ package clientauthentication
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
@ -49,6 +50,10 @@ type ExecCredentialSpec struct {
|
|||||||
// interactive prompt.
|
// interactive prompt.
|
||||||
// +optional
|
// +optional
|
||||||
Interactive bool
|
Interactive bool
|
||||||
|
|
||||||
|
// Cluster contains information to allow an exec plugin to communicate
|
||||||
|
// with the kubernetes cluster being authenticated to.
|
||||||
|
Cluster Cluster
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecCredentialStatus holds credentials for the transport to use.
|
// ExecCredentialStatus holds credentials for the transport to use.
|
||||||
@ -75,3 +80,42 @@ type Response struct {
|
|||||||
// Code is the HTTP status code returned by the server.
|
// Code is the HTTP status code returned by the server.
|
||||||
Code int32
|
Code int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cluster contains information to allow an exec plugin to communicate
|
||||||
|
// with the kubernetes cluster being authenticated to.
|
||||||
|
type Cluster struct {
|
||||||
|
// Server is the address of the kubernetes cluster (https://hostname:port).
|
||||||
|
Server string
|
||||||
|
// ServerName is passed to the server for SNI and is used in the client to check server
|
||||||
|
// certificates against. If ServerName is empty, the hostname used to contact the
|
||||||
|
// server is used.
|
||||||
|
// +optional
|
||||||
|
ServerName string
|
||||||
|
// CAData contains PEM-encoded certificate authority certificates.
|
||||||
|
// If empty, system roots should be used.
|
||||||
|
// +listType=atomic
|
||||||
|
// +optional
|
||||||
|
CAData []byte
|
||||||
|
// Config holds additional config data that is specific to the exec
|
||||||
|
// plugin with regards to the cluster being authenticated to.
|
||||||
|
//
|
||||||
|
// This data is sourced from the clientcmd Cluster object's extensions[exec] field:
|
||||||
|
//
|
||||||
|
// clusters:
|
||||||
|
// - name: my-cluster
|
||||||
|
// cluster:
|
||||||
|
// ...
|
||||||
|
// extensions:
|
||||||
|
// - name: exec # reserved extension name for per cluster exec config
|
||||||
|
// extension:
|
||||||
|
// audience: 06e3fbd18de8 # arbitrary config
|
||||||
|
//
|
||||||
|
// In some environments, the user config may be exactly the same across many clusters
|
||||||
|
// (i.e. call this exec plugin) minus some details that are specific to each cluster
|
||||||
|
// such as the audience. This field allows the per cluster config to be directly
|
||||||
|
// specified with the cluster info. Using this field to store secret data is not
|
||||||
|
// recommended as one of the prime benefits of exec plugins is that no secrets need
|
||||||
|
// to be stored directly in the kubeconfig.
|
||||||
|
// +optional
|
||||||
|
Config runtime.Object
|
||||||
|
}
|
||||||
|
@ -18,17 +18,17 @@ package v1beta1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
// ExecCredentials is used by exec-based plugins to communicate credentials to
|
// ExecCredential is used by exec-based plugins to communicate credentials to
|
||||||
// HTTP transports.
|
// HTTP transports.
|
||||||
type ExecCredential struct {
|
type ExecCredential struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
// Spec holds information passed to the plugin by the transport. This contains
|
// Spec holds information passed to the plugin by the transport.
|
||||||
// request and runtime specific information, such as if the session is interactive.
|
|
||||||
Spec ExecCredentialSpec `json:"spec,omitempty"`
|
Spec ExecCredentialSpec `json:"spec,omitempty"`
|
||||||
|
|
||||||
// Status is filled in by the plugin and holds the credentials that the transport
|
// Status is filled in by the plugin and holds the credentials that the transport
|
||||||
@ -37,9 +37,13 @@ type ExecCredential struct {
|
|||||||
Status *ExecCredentialStatus `json:"status,omitempty"`
|
Status *ExecCredentialStatus `json:"status,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecCredenitalSpec holds request and runtime specific information provided by
|
// ExecCredentialSpec holds request and runtime specific information provided by
|
||||||
// the transport.
|
// the transport.
|
||||||
type ExecCredentialSpec struct{}
|
type ExecCredentialSpec struct {
|
||||||
|
// Cluster contains information to allow an exec plugin to communicate
|
||||||
|
// with the kubernetes cluster being authenticated to.
|
||||||
|
Cluster Cluster `json:"cluster"`
|
||||||
|
}
|
||||||
|
|
||||||
// ExecCredentialStatus holds credentials for the transport to use.
|
// ExecCredentialStatus holds credentials for the transport to use.
|
||||||
//
|
//
|
||||||
@ -57,3 +61,42 @@ type ExecCredentialStatus struct {
|
|||||||
// PEM-encoded private key for the above certificate.
|
// PEM-encoded private key for the above certificate.
|
||||||
ClientKeyData string `json:"clientKeyData,omitempty"`
|
ClientKeyData string `json:"clientKeyData,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cluster contains information to allow an exec plugin to communicate
|
||||||
|
// with the kubernetes cluster being authenticated to.
|
||||||
|
type Cluster struct {
|
||||||
|
// Server is the address of the kubernetes cluster (https://hostname:port).
|
||||||
|
Server string `json:"server"`
|
||||||
|
// ServerName is passed to the server for SNI and is used in the client to check server
|
||||||
|
// certificates against. If ServerName is empty, the hostname used to contact the
|
||||||
|
// server is used.
|
||||||
|
// +optional
|
||||||
|
ServerName string `json:"serverName,omitempty"`
|
||||||
|
// CAData contains PEM-encoded certificate authority certificates.
|
||||||
|
// If empty, system roots should be used.
|
||||||
|
// +listType=atomic
|
||||||
|
// +optional
|
||||||
|
CAData []byte `json:"caData,omitempty"`
|
||||||
|
// Config holds additional config data that is specific to the exec
|
||||||
|
// plugin with regards to the cluster being authenticated to.
|
||||||
|
//
|
||||||
|
// This data is sourced from the clientcmd Cluster object's extensions[exec] field:
|
||||||
|
//
|
||||||
|
// clusters:
|
||||||
|
// - name: my-cluster
|
||||||
|
// cluster:
|
||||||
|
// ...
|
||||||
|
// extensions:
|
||||||
|
// - name: exec # reserved extension name for per cluster exec config
|
||||||
|
// extension:
|
||||||
|
// audience: 06e3fbd18de8 # arbitrary config
|
||||||
|
//
|
||||||
|
// In some environments, the user config may be exactly the same across many clusters
|
||||||
|
// (i.e. call this exec plugin) minus some details that are specific to each cluster
|
||||||
|
// such as the audience. This field allows the per cluster config to be directly
|
||||||
|
// specified with the cluster info. Using this field to store secret data is not
|
||||||
|
// recommended as one of the prime benefits of exec plugins is that no secrets need
|
||||||
|
// to be stored directly in the kubeconfig.
|
||||||
|
// +optional
|
||||||
|
Config runtime.RawExtension `json:"config,omitempty"`
|
||||||
|
}
|
||||||
|
@ -87,8 +87,15 @@ func newCache() *cache {
|
|||||||
|
|
||||||
var spewConfig = &spew.ConfigState{DisableMethods: true, Indent: " "}
|
var spewConfig = &spew.ConfigState{DisableMethods: true, Indent: " "}
|
||||||
|
|
||||||
func cacheKey(c *api.ExecConfig) string {
|
func cacheKey(conf *api.ExecConfig, cluster clientauthentication.Cluster) string {
|
||||||
return spewConfig.Sprint(c)
|
key := struct {
|
||||||
|
conf *api.ExecConfig
|
||||||
|
cluster clientauthentication.Cluster
|
||||||
|
}{
|
||||||
|
conf: conf,
|
||||||
|
cluster: cluster,
|
||||||
|
}
|
||||||
|
return spewConfig.Sprint(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
type cache struct {
|
type cache struct {
|
||||||
@ -155,12 +162,12 @@ func (s *sometimes) Do(f func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthenticator returns an exec-based plugin for providing client credentials.
|
// GetAuthenticator returns an exec-based plugin for providing client credentials.
|
||||||
func GetAuthenticator(config *api.ExecConfig) (*Authenticator, error) {
|
func GetAuthenticator(config *api.ExecConfig, cluster clientauthentication.Cluster) (*Authenticator, error) {
|
||||||
return newAuthenticator(globalCache, config)
|
return newAuthenticator(globalCache, config, cluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAuthenticator(c *cache, config *api.ExecConfig) (*Authenticator, error) {
|
func newAuthenticator(c *cache, config *api.ExecConfig, cluster clientauthentication.Cluster) (*Authenticator, error) {
|
||||||
key := cacheKey(config)
|
key := cacheKey(config, cluster)
|
||||||
if a, ok := c.get(key); ok {
|
if a, ok := c.get(key); ok {
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
@ -171,9 +178,10 @@ func newAuthenticator(c *cache, config *api.ExecConfig) (*Authenticator, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
a := &Authenticator{
|
a := &Authenticator{
|
||||||
cmd: config.Command,
|
cmd: config.Command,
|
||||||
args: config.Args,
|
args: config.Args,
|
||||||
group: gv,
|
group: gv,
|
||||||
|
cluster: cluster,
|
||||||
|
|
||||||
installHint: config.InstallHint,
|
installHint: config.InstallHint,
|
||||||
sometimes: &sometimes{
|
sometimes: &sometimes{
|
||||||
@ -200,10 +208,11 @@ func newAuthenticator(c *cache, config *api.ExecConfig) (*Authenticator, error)
|
|||||||
// The plugin input and output are defined by the API group client.authentication.k8s.io.
|
// The plugin input and output are defined by the API group client.authentication.k8s.io.
|
||||||
type Authenticator struct {
|
type Authenticator struct {
|
||||||
// Set by the config
|
// Set by the config
|
||||||
cmd string
|
cmd string
|
||||||
args []string
|
args []string
|
||||||
group schema.GroupVersion
|
group schema.GroupVersion
|
||||||
env []string
|
env []string
|
||||||
|
cluster clientauthentication.Cluster
|
||||||
|
|
||||||
// Used to avoid log spew by rate limiting install hint printing. We didn't do
|
// Used to avoid log spew by rate limiting install hint printing. We didn't do
|
||||||
// this by interval based rate limiting alone since that way may have prevented
|
// this by interval based rate limiting alone since that way may have prevented
|
||||||
@ -365,21 +374,16 @@ func (a *Authenticator) refreshCredsLocked(r *clientauthentication.Response) err
|
|||||||
Spec: clientauthentication.ExecCredentialSpec{
|
Spec: clientauthentication.ExecCredentialSpec{
|
||||||
Response: r,
|
Response: r,
|
||||||
Interactive: a.interactive,
|
Interactive: a.interactive,
|
||||||
|
Cluster: a.cluster,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
env := append(a.environ(), a.env...)
|
env := append(a.environ(), a.env...)
|
||||||
if a.group == v1alpha1.SchemeGroupVersion {
|
data, err := runtime.Encode(codecs.LegacyCodec(a.group), cred)
|
||||||
// Input spec disabled for beta due to lack of use. Possibly re-enable this later if
|
if err != nil {
|
||||||
// someone wants it back.
|
return fmt.Errorf("encode ExecCredentials: %v", err)
|
||||||
//
|
|
||||||
// See: https://github.com/kubernetes/kubernetes/issues/61796
|
|
||||||
data, err := runtime.Encode(codecs.LegacyCodec(a.group), cred)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("encode ExecCredentials: %v", err)
|
|
||||||
}
|
|
||||||
env = append(env, fmt.Sprintf("%s=%s", execInfoEnv, data))
|
|
||||||
}
|
}
|
||||||
|
env = append(env, fmt.Sprintf("%s=%s", execInfoEnv, data))
|
||||||
|
|
||||||
stdout := &bytes.Buffer{}
|
stdout := &bytes.Buffer{}
|
||||||
cmd := exec.Command(a.cmd, a.args...)
|
cmd := exec.Command(a.cmd, a.args...)
|
||||||
|
@ -117,6 +117,21 @@ func TestCacheKey(t *testing.T) {
|
|||||||
},
|
},
|
||||||
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
||||||
}
|
}
|
||||||
|
c1c := clientauthentication.Cluster{
|
||||||
|
Server: "foo",
|
||||||
|
ServerName: "bar",
|
||||||
|
CAData: []byte("baz"),
|
||||||
|
Config: &runtime.Unknown{
|
||||||
|
TypeMeta: runtime.TypeMeta{
|
||||||
|
APIVersion: "",
|
||||||
|
Kind: "",
|
||||||
|
},
|
||||||
|
Raw: []byte(`{"apiVersion":"group/v1","kind":"PluginConfig","spec":{"audience":"snorlax"}}`),
|
||||||
|
ContentEncoding: "",
|
||||||
|
ContentType: "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
c2 := &api.ExecConfig{
|
c2 := &api.ExecConfig{
|
||||||
Command: "foo-bar",
|
Command: "foo-bar",
|
||||||
Args: []string{"1", "2"},
|
Args: []string{"1", "2"},
|
||||||
@ -127,6 +142,21 @@ func TestCacheKey(t *testing.T) {
|
|||||||
},
|
},
|
||||||
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
||||||
}
|
}
|
||||||
|
c2c := clientauthentication.Cluster{
|
||||||
|
Server: "foo",
|
||||||
|
ServerName: "bar",
|
||||||
|
CAData: []byte("baz"),
|
||||||
|
Config: &runtime.Unknown{
|
||||||
|
TypeMeta: runtime.TypeMeta{
|
||||||
|
APIVersion: "",
|
||||||
|
Kind: "",
|
||||||
|
},
|
||||||
|
Raw: []byte(`{"apiVersion":"group/v1","kind":"PluginConfig","spec":{"audience":"snorlax"}}`),
|
||||||
|
ContentEncoding: "",
|
||||||
|
ContentType: "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
c3 := &api.ExecConfig{
|
c3 := &api.ExecConfig{
|
||||||
Command: "foo-bar",
|
Command: "foo-bar",
|
||||||
Args: []string{"1", "2"},
|
Args: []string{"1", "2"},
|
||||||
@ -136,9 +166,49 @@ func TestCacheKey(t *testing.T) {
|
|||||||
},
|
},
|
||||||
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
||||||
}
|
}
|
||||||
key1 := cacheKey(c1)
|
c3c := clientauthentication.Cluster{
|
||||||
key2 := cacheKey(c2)
|
Server: "foo",
|
||||||
key3 := cacheKey(c3)
|
ServerName: "bar",
|
||||||
|
CAData: []byte("baz"),
|
||||||
|
Config: &runtime.Unknown{
|
||||||
|
TypeMeta: runtime.TypeMeta{
|
||||||
|
APIVersion: "",
|
||||||
|
Kind: "",
|
||||||
|
},
|
||||||
|
Raw: []byte(`{"apiVersion":"group/v1","kind":"PluginConfig","spec":{"audience":"snorlax"}}`),
|
||||||
|
ContentEncoding: "",
|
||||||
|
ContentType: "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c4 := &api.ExecConfig{
|
||||||
|
Command: "foo-bar",
|
||||||
|
Args: []string{"1", "2"},
|
||||||
|
Env: []api.ExecEnvVar{
|
||||||
|
{Name: "3", Value: "4"},
|
||||||
|
{Name: "5", Value: "6"},
|
||||||
|
},
|
||||||
|
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
||||||
|
}
|
||||||
|
c4c := clientauthentication.Cluster{
|
||||||
|
Server: "foo",
|
||||||
|
ServerName: "bar",
|
||||||
|
CAData: []byte("baz"),
|
||||||
|
Config: &runtime.Unknown{
|
||||||
|
TypeMeta: runtime.TypeMeta{
|
||||||
|
APIVersion: "",
|
||||||
|
Kind: "",
|
||||||
|
},
|
||||||
|
Raw: []byte(`{"apiVersion":"group/v1","kind":"PluginConfig","spec":{"audience":"panda"}}`),
|
||||||
|
ContentEncoding: "",
|
||||||
|
ContentType: "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
key1 := cacheKey(c1, c1c)
|
||||||
|
key2 := cacheKey(c2, c2c)
|
||||||
|
key3 := cacheKey(c3, c3c)
|
||||||
|
key4 := cacheKey(c4, c4c)
|
||||||
if key1 != key2 {
|
if key1 != key2 {
|
||||||
t.Error("key1 and key2 didn't match")
|
t.Error("key1 and key2 didn't match")
|
||||||
}
|
}
|
||||||
@ -148,6 +218,9 @@ func TestCacheKey(t *testing.T) {
|
|||||||
if key2 == key3 {
|
if key2 == key3 {
|
||||||
t.Error("key2 and key3 matched")
|
t.Error("key2 and key3 matched")
|
||||||
}
|
}
|
||||||
|
if key3 == key4 {
|
||||||
|
t.Error("key3 and key4 matched")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func compJSON(t *testing.T, got, want []byte) {
|
func compJSON(t *testing.T, got, want []byte) {
|
||||||
@ -173,6 +246,7 @@ func TestRefreshCreds(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
config api.ExecConfig
|
config api.ExecConfig
|
||||||
exitCode int
|
exitCode int
|
||||||
|
cluster clientauthentication.Cluster
|
||||||
output string
|
output string
|
||||||
interactive bool
|
interactive bool
|
||||||
response *clientauthentication.Response
|
response *clientauthentication.Response
|
||||||
@ -393,6 +467,16 @@ func TestRefreshCreds(t *testing.T) {
|
|||||||
config: api.ExecConfig{
|
config: api.ExecConfig{
|
||||||
APIVersion: "client.authentication.k8s.io/v1beta1",
|
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||||
},
|
},
|
||||||
|
wantInput: `{
|
||||||
|
"kind": "ExecCredential",
|
||||||
|
"apiVersion": "client.authentication.k8s.io/v1beta1",
|
||||||
|
"spec": {
|
||||||
|
"cluster": {
|
||||||
|
"server": "",
|
||||||
|
"config": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
output: `{
|
output: `{
|
||||||
"kind": "ExecCredential",
|
"kind": "ExecCredential",
|
||||||
"apiVersion": "client.authentication.k8s.io/v1beta1",
|
"apiVersion": "client.authentication.k8s.io/v1beta1",
|
||||||
@ -407,6 +491,16 @@ func TestRefreshCreds(t *testing.T) {
|
|||||||
config: api.ExecConfig{
|
config: api.ExecConfig{
|
||||||
APIVersion: "client.authentication.k8s.io/v1beta1",
|
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||||
},
|
},
|
||||||
|
wantInput: `{
|
||||||
|
"kind": "ExecCredential",
|
||||||
|
"apiVersion": "client.authentication.k8s.io/v1beta1",
|
||||||
|
"spec": {
|
||||||
|
"cluster": {
|
||||||
|
"server": "",
|
||||||
|
"config": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
output: `{
|
output: `{
|
||||||
"kind": "ExecCredential",
|
"kind": "ExecCredential",
|
||||||
"apiVersion": "client.authentication.k8s.io/v1beta1",
|
"apiVersion": "client.authentication.k8s.io/v1beta1",
|
||||||
@ -473,6 +567,106 @@ func TestRefreshCreds(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
wantErrSubstr: "73",
|
wantErrSubstr: "73",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "alpha-with-cluster-is-ignored",
|
||||||
|
config: api.ExecConfig{
|
||||||
|
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
||||||
|
},
|
||||||
|
cluster: clientauthentication.Cluster{
|
||||||
|
Server: "foo",
|
||||||
|
ServerName: "bar",
|
||||||
|
CAData: []byte("baz"),
|
||||||
|
Config: &runtime.Unknown{
|
||||||
|
TypeMeta: runtime.TypeMeta{
|
||||||
|
APIVersion: "",
|
||||||
|
Kind: "",
|
||||||
|
},
|
||||||
|
Raw: []byte(`{"apiVersion":"group/v1","kind":"PluginConfig","spec":{"audience":"panda"}}`),
|
||||||
|
ContentEncoding: "",
|
||||||
|
ContentType: "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
response: &clientauthentication.Response{
|
||||||
|
Header: map[string][]string{
|
||||||
|
"WWW-Authenticate": {`Basic realm="Access to the staging site", charset="UTF-8"`},
|
||||||
|
},
|
||||||
|
Code: 401,
|
||||||
|
},
|
||||||
|
wantInput: `{
|
||||||
|
"kind":"ExecCredential",
|
||||||
|
"apiVersion":"client.authentication.k8s.io/v1alpha1",
|
||||||
|
"spec": {
|
||||||
|
"response": {
|
||||||
|
"header": {
|
||||||
|
"WWW-Authenticate": [
|
||||||
|
"Basic realm=\"Access to the staging site\", charset=\"UTF-8\""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"code": 401
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
output: `{
|
||||||
|
"kind": "ExecCredential",
|
||||||
|
"apiVersion": "client.authentication.k8s.io/v1alpha1",
|
||||||
|
"status": {
|
||||||
|
"token": "foo-bar"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
wantCreds: credentials{token: "foo-bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "beta-with-cluster-is-serialized",
|
||||||
|
config: api.ExecConfig{
|
||||||
|
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||||
|
},
|
||||||
|
cluster: clientauthentication.Cluster{
|
||||||
|
Server: "foo",
|
||||||
|
ServerName: "bar",
|
||||||
|
CAData: []byte("baz"),
|
||||||
|
Config: &runtime.Unknown{
|
||||||
|
TypeMeta: runtime.TypeMeta{
|
||||||
|
APIVersion: "",
|
||||||
|
Kind: "",
|
||||||
|
},
|
||||||
|
Raw: []byte(`{"apiVersion":"group/v1","kind":"PluginConfig","spec":{"audience":"snorlax"}}`),
|
||||||
|
ContentEncoding: "",
|
||||||
|
ContentType: "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
response: &clientauthentication.Response{
|
||||||
|
Header: map[string][]string{
|
||||||
|
"WWW-Authenticate": {`Basic realm="Access to the staging site", charset="UTF-8"`},
|
||||||
|
},
|
||||||
|
Code: 401,
|
||||||
|
},
|
||||||
|
wantInput: `{
|
||||||
|
"kind":"ExecCredential",
|
||||||
|
"apiVersion":"client.authentication.k8s.io/v1beta1",
|
||||||
|
"spec": {
|
||||||
|
"cluster": {
|
||||||
|
"server": "foo",
|
||||||
|
"serverName": "bar",
|
||||||
|
"caData": "YmF6",
|
||||||
|
"config": {
|
||||||
|
"apiVersion": "group/v1",
|
||||||
|
"kind": "PluginConfig",
|
||||||
|
"spec": {
|
||||||
|
"audience": "snorlax"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
output: `{
|
||||||
|
"kind": "ExecCredential",
|
||||||
|
"apiVersion": "client.authentication.k8s.io/v1beta1",
|
||||||
|
"status": {
|
||||||
|
"token": "foo-bar"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
wantCreds: credentials{token: "foo-bar"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -491,7 +685,7 @@ func TestRefreshCreds(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
a, err := newAuthenticator(newCache(), &c)
|
a, err := newAuthenticator(newCache(), &c, test.cluster)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -569,7 +763,7 @@ func TestRoundTripper(t *testing.T) {
|
|||||||
Command: "./testdata/test-plugin.sh",
|
Command: "./testdata/test-plugin.sh",
|
||||||
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
||||||
}
|
}
|
||||||
a, err := newAuthenticator(newCache(), &c)
|
a, err := newAuthenticator(newCache(), &c, clientauthentication.Cluster{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -694,7 +888,7 @@ func TestTLSCredentials(t *testing.T) {
|
|||||||
a, err := newAuthenticator(newCache(), &api.ExecConfig{
|
a, err := newAuthenticator(newCache(), &api.ExecConfig{
|
||||||
Command: "./testdata/test-plugin.sh",
|
Command: "./testdata/test-plugin.sh",
|
||||||
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
||||||
})
|
}, clientauthentication.Cluster{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -784,7 +978,7 @@ func TestConcurrentUpdateTransportConfig(t *testing.T) {
|
|||||||
Command: "./testdata/test-plugin.sh",
|
Command: "./testdata/test-plugin.sh",
|
||||||
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
||||||
}
|
}
|
||||||
a, err := newAuthenticator(newCache(), &c)
|
a, err := newAuthenticator(newCache(), &c, clientauthentication.Cluster{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ type Config struct {
|
|||||||
AuthConfigPersister AuthProviderConfigPersister
|
AuthConfigPersister AuthProviderConfigPersister
|
||||||
|
|
||||||
// Exec-based authentication provider.
|
// Exec-based authentication provider.
|
||||||
ExecProvider *clientcmdapi.ExecConfig
|
Exec Exec
|
||||||
|
|
||||||
// TLSClientConfig contains settings to enable transport layer security
|
// TLSClientConfig contains settings to enable transport layer security
|
||||||
TLSClientConfig
|
TLSClientConfig
|
||||||
@ -160,6 +160,15 @@ func (sanitizedAuthConfigPersister) String() string {
|
|||||||
return "rest.AuthProviderConfigPersister(--- REDACTED ---)"
|
return "rest.AuthProviderConfigPersister(--- REDACTED ---)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sanitizedObject struct{ runtime.Object }
|
||||||
|
|
||||||
|
func (sanitizedObject) GoString() string {
|
||||||
|
return "runtime.Object(--- REDACTED ---)"
|
||||||
|
}
|
||||||
|
func (sanitizedObject) String() string {
|
||||||
|
return "runtime.Object(--- REDACTED ---)"
|
||||||
|
}
|
||||||
|
|
||||||
// GoString implements fmt.GoStringer and sanitizes sensitive fields of Config
|
// GoString implements fmt.GoStringer and sanitizes sensitive fields of Config
|
||||||
// to prevent accidental leaking via logs.
|
// to prevent accidental leaking via logs.
|
||||||
func (c *Config) GoString() string {
|
func (c *Config) GoString() string {
|
||||||
@ -183,10 +192,40 @@ func (c *Config) String() string {
|
|||||||
if cc.AuthConfigPersister != nil {
|
if cc.AuthConfigPersister != nil {
|
||||||
cc.AuthConfigPersister = sanitizedAuthConfigPersister{cc.AuthConfigPersister}
|
cc.AuthConfigPersister = sanitizedAuthConfigPersister{cc.AuthConfigPersister}
|
||||||
}
|
}
|
||||||
|
if cc.Exec.Config != nil {
|
||||||
|
cc.Exec.Config = sanitizedObject{Object: cc.Exec.Config}
|
||||||
|
}
|
||||||
return fmt.Sprintf("%#v", cc)
|
return fmt.Sprintf("%#v", cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exec plugin authentication provider.
|
||||||
|
type Exec struct {
|
||||||
|
// ExecProvider provides the config needed to execute the exec plugin.
|
||||||
|
ExecProvider *clientcmdapi.ExecConfig
|
||||||
|
|
||||||
|
// Config holds additional config data that is specific to the exec
|
||||||
|
// plugin with regards to the cluster being authenticated to.
|
||||||
|
//
|
||||||
|
// This data is sourced from the clientcmd Cluster object's extensions[exec] field:
|
||||||
|
//
|
||||||
|
// clusters:
|
||||||
|
// - name: my-cluster
|
||||||
|
// cluster:
|
||||||
|
// ...
|
||||||
|
// extensions:
|
||||||
|
// - name: exec # reserved extension name for per cluster exec config
|
||||||
|
// extension:
|
||||||
|
// audience: 06e3fbd18de8 # arbitrary config
|
||||||
|
//
|
||||||
|
// In some environments, the user config may be exactly the same across many clusters
|
||||||
|
// (i.e. call this exec plugin) minus some details that are specific to each cluster
|
||||||
|
// such as the audience. This field allows the per cluster config to be directly
|
||||||
|
// specified with the cluster info. Using this field to store secret data is not
|
||||||
|
// recommended as one of the prime benefits of exec plugins is that no secrets need
|
||||||
|
// to be stored directly in the kubeconfig.
|
||||||
|
Config runtime.Object
|
||||||
|
}
|
||||||
|
|
||||||
// ImpersonationConfig has all the available impersonation options
|
// ImpersonationConfig has all the available impersonation options
|
||||||
type ImpersonationConfig struct {
|
type ImpersonationConfig struct {
|
||||||
// UserName is the username to impersonate on each request.
|
// UserName is the username to impersonate on each request.
|
||||||
@ -603,7 +642,10 @@ func CopyConfig(config *Config) *Config {
|
|||||||
},
|
},
|
||||||
AuthProvider: config.AuthProvider,
|
AuthProvider: config.AuthProvider,
|
||||||
AuthConfigPersister: config.AuthConfigPersister,
|
AuthConfigPersister: config.AuthConfigPersister,
|
||||||
ExecProvider: config.ExecProvider,
|
Exec: Exec{
|
||||||
|
ExecProvider: config.Exec.ExecProvider,
|
||||||
|
Config: config.Exec.Config,
|
||||||
|
},
|
||||||
TLSClientConfig: TLSClientConfig{
|
TLSClientConfig: TLSClientConfig{
|
||||||
Insecure: config.TLSClientConfig.Insecure,
|
Insecure: config.TLSClientConfig.Insecure,
|
||||||
ServerName: config.TLSClientConfig.ServerName,
|
ServerName: config.TLSClientConfig.ServerName,
|
||||||
|
@ -337,6 +337,11 @@ func TestAnonymousConfig(t *testing.T) {
|
|||||||
func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
|
func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
|
||||||
*r = fakeProxyFunc
|
*r = fakeProxyFunc
|
||||||
},
|
},
|
||||||
|
func(r *runtime.Object, f fuzz.Continue) {
|
||||||
|
unknown := &runtime.Unknown{}
|
||||||
|
f.Fuzz(unknown)
|
||||||
|
*r = unknown
|
||||||
|
},
|
||||||
)
|
)
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
original := &Config{}
|
original := &Config{}
|
||||||
@ -353,7 +358,8 @@ func TestAnonymousConfig(t *testing.T) {
|
|||||||
expected.Password = ""
|
expected.Password = ""
|
||||||
expected.AuthProvider = nil
|
expected.AuthProvider = nil
|
||||||
expected.AuthConfigPersister = nil
|
expected.AuthConfigPersister = nil
|
||||||
expected.ExecProvider = nil
|
expected.Exec.ExecProvider = nil
|
||||||
|
expected.Exec.Config = nil
|
||||||
expected.TLSClientConfig.CertData = nil
|
expected.TLSClientConfig.CertData = nil
|
||||||
expected.TLSClientConfig.CertFile = ""
|
expected.TLSClientConfig.CertFile = ""
|
||||||
expected.TLSClientConfig.KeyData = nil
|
expected.TLSClientConfig.KeyData = nil
|
||||||
@ -428,6 +434,11 @@ func TestCopyConfig(t *testing.T) {
|
|||||||
func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
|
func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
|
||||||
*r = fakeProxyFunc
|
*r = fakeProxyFunc
|
||||||
},
|
},
|
||||||
|
func(r *runtime.Object, f fuzz.Continue) {
|
||||||
|
unknown := &runtime.Unknown{}
|
||||||
|
f.Fuzz(unknown)
|
||||||
|
*r = unknown
|
||||||
|
},
|
||||||
)
|
)
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
original := &Config{}
|
original := &Config{}
|
||||||
@ -524,9 +535,12 @@ func TestConfigStringer(t *testing.T) {
|
|||||||
AuthProvider: &clientcmdapi.AuthProviderConfig{
|
AuthProvider: &clientcmdapi.AuthProviderConfig{
|
||||||
Config: map[string]string{"secret": "s3cr3t"},
|
Config: map[string]string{"secret": "s3cr3t"},
|
||||||
},
|
},
|
||||||
ExecProvider: &clientcmdapi.ExecConfig{
|
Exec: Exec{
|
||||||
Args: []string{"secret"},
|
ExecProvider: &clientcmdapi.ExecConfig{
|
||||||
Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
|
Args: []string{"secret"},
|
||||||
|
Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
|
||||||
|
},
|
||||||
|
Config: &runtime.Unknown{Raw: []byte("super secret password")},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectContent: []string{
|
expectContent: []string{
|
||||||
@ -545,6 +559,8 @@ func TestConfigStringer(t *testing.T) {
|
|||||||
formatBytes([]byte("fake key")),
|
formatBytes([]byte("fake key")),
|
||||||
"secret",
|
"secret",
|
||||||
"s3cr3t",
|
"s3cr3t",
|
||||||
|
"super secret password",
|
||||||
|
formatBytes([]byte("super secret password")),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -587,10 +603,13 @@ func TestConfigSprint(t *testing.T) {
|
|||||||
Config: map[string]string{"secret": "s3cr3t"},
|
Config: map[string]string{"secret": "s3cr3t"},
|
||||||
},
|
},
|
||||||
AuthConfigPersister: fakeAuthProviderConfigPersister{},
|
AuthConfigPersister: fakeAuthProviderConfigPersister{},
|
||||||
ExecProvider: &clientcmdapi.ExecConfig{
|
Exec: Exec{
|
||||||
Command: "sudo",
|
ExecProvider: &clientcmdapi.ExecConfig{
|
||||||
Args: []string{"secret"},
|
Command: "sudo",
|
||||||
Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
|
Args: []string{"secret"},
|
||||||
|
Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
|
||||||
|
},
|
||||||
|
Config: &runtime.Unknown{Raw: []byte("super secret password")},
|
||||||
},
|
},
|
||||||
TLSClientConfig: TLSClientConfig{
|
TLSClientConfig: TLSClientConfig{
|
||||||
CertFile: "a.crt",
|
CertFile: "a.crt",
|
||||||
@ -611,7 +630,7 @@ func TestConfigSprint(t *testing.T) {
|
|||||||
Proxy: fakeProxyFunc,
|
Proxy: fakeProxyFunc,
|
||||||
}
|
}
|
||||||
want := fmt.Sprintf(
|
want := fmt.Sprintf(
|
||||||
`&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.AuthProviderConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: ""}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil), NextProtos:[]string{"h2", "http/1.1"}}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), WarningHandler:rest.fakeWarningHandler{}, Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p), Proxy:(func(*http.Request) (*url.URL, error))(%p)}`,
|
`&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), Exec:rest.Exec{ExecProvider:api.AuthProviderConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: ""}, Config:runtime.Object(--- REDACTED ---)}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil), NextProtos:[]string{"h2", "http/1.1"}}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), WarningHandler:rest.fakeWarningHandler{}, Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p), Proxy:(func(*http.Request) (*url.URL, error))(%p)}`,
|
||||||
c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc, fakeProxyFunc,
|
c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc, fakeProxyFunc,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,8 +19,10 @@ package rest
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"k8s.io/client-go/pkg/apis/clientauthentication"
|
||||||
"k8s.io/client-go/plugin/pkg/client/auth/exec"
|
"k8s.io/client-go/plugin/pkg/client/auth/exec"
|
||||||
"k8s.io/client-go/transport"
|
"k8s.io/client-go/transport"
|
||||||
)
|
)
|
||||||
@ -89,12 +91,22 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
|
|||||||
Proxy: c.Proxy,
|
Proxy: c.Proxy,
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.ExecProvider != nil && c.AuthProvider != nil {
|
if c.Exec.ExecProvider != nil && c.AuthProvider != nil {
|
||||||
return nil, errors.New("execProvider and authProvider cannot be used in combination")
|
return nil, errors.New("execProvider and authProvider cannot be used in combination")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.ExecProvider != nil {
|
if c.Exec.ExecProvider != nil {
|
||||||
provider, err := exec.GetAuthenticator(c.ExecProvider)
|
caData, err := dataFromSliceOrFile(c.CAData, c.CAFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load CA bundle for execProvider: %v", err)
|
||||||
|
}
|
||||||
|
cluster := clientauthentication.Cluster{
|
||||||
|
Server: c.Host,
|
||||||
|
ServerName: c.TLSClientConfig.ServerName,
|
||||||
|
CAData: caData,
|
||||||
|
Config: c.Exec.Config,
|
||||||
|
}
|
||||||
|
provider, err := exec.GetAuthenticator(c.Exec.ExecProvider, cluster)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -189,7 +189,7 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
|
|||||||
authInfoName, _ := config.getAuthInfoName()
|
authInfoName, _ := config.getAuthInfoName()
|
||||||
persister = PersisterForUser(config.configAccess, authInfoName)
|
persister = PersisterForUser(config.configAccess, authInfoName)
|
||||||
}
|
}
|
||||||
userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister)
|
userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister, configClusterInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -232,7 +232,7 @@ func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo,
|
|||||||
// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
|
// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
|
||||||
// 3. if there is not enough information to identify the user, load try the ~/.kubernetes_auth file
|
// 3. if there is not enough information to identify the user, load try the ~/.kubernetes_auth file
|
||||||
// 4. if there is not enough information to identify the user, prompt if possible
|
// 4. if there is not enough information to identify the user, prompt if possible
|
||||||
func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister) (*restclient.Config, error) {
|
func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
|
||||||
mergedConfig := &restclient.Config{}
|
mergedConfig := &restclient.Config{}
|
||||||
|
|
||||||
// blindly overwrite existing values based on precedence
|
// blindly overwrite existing values based on precedence
|
||||||
@ -269,8 +269,9 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
|
|||||||
mergedConfig.AuthConfigPersister = persistAuthConfig
|
mergedConfig.AuthConfigPersister = persistAuthConfig
|
||||||
}
|
}
|
||||||
if configAuthInfo.Exec != nil {
|
if configAuthInfo.Exec != nil {
|
||||||
mergedConfig.ExecProvider = configAuthInfo.Exec
|
mergedConfig.Exec.ExecProvider = configAuthInfo.Exec
|
||||||
mergedConfig.ExecProvider.InstallHint = cleanANSIEscapeCodes(mergedConfig.ExecProvider.InstallHint)
|
mergedConfig.ExecProvider.InstallHint = cleanANSIEscapeCodes(mergedConfig.ExecProvider.InstallHint)
|
||||||
|
mergedConfig.Exec.Config = configClusterInfo.Extensions["exec"] // this key is reserved in the extensions list for exec plugin config
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there still isn't enough information to authenticate the user, try prompting
|
// if there still isn't enough information to authenticate the user, try prompting
|
||||||
@ -313,7 +314,7 @@ func canIdentifyUser(config restclient.Config) bool {
|
|||||||
(len(config.CertFile) > 0 || len(config.CertData) > 0) ||
|
(len(config.CertFile) > 0 || len(config.CertData) > 0) ||
|
||||||
len(config.BearerToken) > 0 ||
|
len(config.BearerToken) > 0 ||
|
||||||
config.AuthProvider != nil ||
|
config.AuthProvider != nil ||
|
||||||
config.ExecProvider != nil
|
config.Exec.ExecProvider != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanANSIEscapeCodes takes an arbitrary string and ensures that there are no
|
// cleanANSIEscapeCodes takes an arbitrary string and ensures that there are no
|
||||||
|
@ -23,10 +23,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/imdario/mergo"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMergoSemantics(t *testing.T) {
|
func TestMergoSemantics(t *testing.T) {
|
||||||
@ -834,6 +835,11 @@ apiVersion: v1
|
|||||||
clusters:
|
clusters:
|
||||||
- cluster:
|
- cluster:
|
||||||
server: https://localhost:8080
|
server: https://localhost:8080
|
||||||
|
extensions:
|
||||||
|
- name: exec
|
||||||
|
extension:
|
||||||
|
audience: foo
|
||||||
|
other: bar
|
||||||
name: foo-cluster
|
name: foo-cluster
|
||||||
contexts:
|
contexts:
|
||||||
- context:
|
- context:
|
||||||
@ -865,10 +871,16 @@ users:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(config.ExecProvider.Args, []string{"arg-1", "arg-2"}) {
|
if !reflect.DeepEqual(config.Exec.ExecProvider.Args, []string{"arg-1", "arg-2"}) {
|
||||||
t.Errorf("Got args %v when they should be %v\n", config.ExecProvider.Args, []string{"arg-1", "arg-2"})
|
t.Errorf("Got args %v when they should be %v\n", config.Exec.ExecProvider.Args, []string{"arg-1", "arg-2"})
|
||||||
|
}
|
||||||
|
want := &runtime.Unknown{
|
||||||
|
Raw: []byte(`{"audience":"foo","other":"bar"}`),
|
||||||
|
ContentType: "application/json",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(config.Exec.Config, want) {
|
||||||
|
t.Errorf("Got config %v when it should be %v\n", config.Exec.Config, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCleanANSIEscapeCodes(t *testing.T) {
|
func TestCleanANSIEscapeCodes(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user