mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 03:41:45 +00:00
add --as-group option to cli
The usecase of this change: When a super user grant some RBAC permissions to a group, he can use --as-group to test whether the group get the permissions. Note that now we support as-groups, as-user-extra in kubeconfig file after this change.
This commit is contained in:
parent
4807e6cadf
commit
e541defd49
@ -28,6 +28,7 @@ KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../..
|
|||||||
ETCD_HOST=${ETCD_HOST:-127.0.0.1}
|
ETCD_HOST=${ETCD_HOST:-127.0.0.1}
|
||||||
ETCD_PORT=${ETCD_PORT:-2379}
|
ETCD_PORT=${ETCD_PORT:-2379}
|
||||||
API_PORT=${API_PORT:-8080}
|
API_PORT=${API_PORT:-8080}
|
||||||
|
SECURE_API_PORT=${SECURE_API_PORT:-6443}
|
||||||
API_HOST=${API_HOST:-127.0.0.1}
|
API_HOST=${API_HOST:-127.0.0.1}
|
||||||
KUBELET_PORT=${KUBELET_PORT:-10250}
|
KUBELET_PORT=${KUBELET_PORT:-10250}
|
||||||
KUBELET_HEALTHZ_PORT=${KUBELET_HEALTHZ_PORT:-10248}
|
KUBELET_HEALTHZ_PORT=${KUBELET_HEALTHZ_PORT:-10248}
|
||||||
@ -2855,8 +2856,14 @@ runTests() {
|
|||||||
kube_flags=(
|
kube_flags=(
|
||||||
-s "http://127.0.0.1:${API_PORT}"
|
-s "http://127.0.0.1:${API_PORT}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
kube_flags_with_token=(
|
||||||
|
-s "https://127.0.0.1:${SECURE_API_PORT}" --token=admin/system:masters --insecure-skip-tls-verify=true
|
||||||
|
)
|
||||||
|
|
||||||
if [[ -z "${ALLOW_SKEW:-}" ]]; then
|
if [[ -z "${ALLOW_SKEW:-}" ]]; then
|
||||||
kube_flags+=("--match-server-version")
|
kube_flags+=("--match-server-version")
|
||||||
|
kube_flags_with_token+=("--match-server-version")
|
||||||
fi
|
fi
|
||||||
if kube::test::if_supports_resource "${nodes}" ; then
|
if kube::test::if_supports_resource "${nodes}" ; then
|
||||||
[ "$(kubectl get nodes -o go-template='{{ .apiVersion }}' "${kube_flags[@]}")" == "v1" ]
|
[ "$(kubectl get nodes -o go-template='{{ .apiVersion }}' "${kube_flags[@]}")" == "v1" ]
|
||||||
@ -3762,5 +3769,25 @@ __EOF__
|
|||||||
output_message=$(! KUBECTL_PLUGINS_PATH=test/fixtures/pkg/kubectl/plugins/ kubectl plugin error 2>&1)
|
output_message=$(! KUBECTL_PLUGINS_PATH=test/fixtures/pkg/kubectl/plugins/ kubectl plugin error 2>&1)
|
||||||
kube::test::if_has_string "${output_message}" 'error: exit status 1'
|
kube::test::if_has_string "${output_message}" 'error: exit status 1'
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Impersonation #
|
||||||
|
#################
|
||||||
|
output_message=$(! kubectl get pods "${kube_flags_with_token[@]}" --as-group=foo 2>&1)
|
||||||
|
kube::test::if_has_string "${output_message}" 'without impersonating a user'
|
||||||
|
|
||||||
|
if kube::test::if_supports_resource "${csr}" ; then
|
||||||
|
# --as
|
||||||
|
kubectl create -f hack/testdata/csr.yml "${kube_flags_with_token[@]}" --as=user1
|
||||||
|
kube::test::get_object_assert 'csr/foo' '{{.spec.username}}' 'user1'
|
||||||
|
kube::test::get_object_assert 'csr/foo' '{{range .spec.groups}}{{.}}{{end}}' 'system:authenticated'
|
||||||
|
kubectl delete -f hack/testdata/csr.yml "${kube_flags_with_token[@]}"
|
||||||
|
|
||||||
|
# --as-group
|
||||||
|
kubectl create -f hack/testdata/csr.yml "${kube_flags_with_token[@]}" --as=user1 --as-group=group2 --as-group=group1 --as-group=,,,chameleon
|
||||||
|
kube::test::get_object_assert 'csr/foo' '{{len .spec.groups}}' '3'
|
||||||
|
kube::test::get_object_assert 'csr/foo' '{{range .spec.groups}}{{.}} {{end}}' 'group2 group1 ,,,chameleon '
|
||||||
|
kubectl delete -f hack/testdata/csr.yml "${kube_flags_with_token[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
kube::test::clear_all
|
kube::test::clear_all
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ function run_kube_apiserver() {
|
|||||||
--public-address-override="127.0.0.1" \
|
--public-address-override="127.0.0.1" \
|
||||||
--port="${API_PORT}" \
|
--port="${API_PORT}" \
|
||||||
--authorization-mode="${AUTHORIZATION_MODE}" \
|
--authorization-mode="${AUTHORIZATION_MODE}" \
|
||||||
|
--secure-port="${SECURE_API_PORT}" \
|
||||||
--admission-control="${ADMISSION_CONTROL}" \
|
--admission-control="${ADMISSION_CONTROL}" \
|
||||||
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \
|
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \
|
||||||
--public-address-override="127.0.0.1" \
|
--public-address-override="127.0.0.1" \
|
||||||
@ -51,7 +52,8 @@ function run_kube_apiserver() {
|
|||||||
--runtime-config=api/v1 \
|
--runtime-config=api/v1 \
|
||||||
--storage-media-type="${KUBE_TEST_API_STORAGE_TYPE-}" \
|
--storage-media-type="${KUBE_TEST_API_STORAGE_TYPE-}" \
|
||||||
--cert-dir="${TMPDIR:-/tmp/}" \
|
--cert-dir="${TMPDIR:-/tmp/}" \
|
||||||
--service-cluster-ip-range="10.0.0.0/24" 1>&2 &
|
--service-cluster-ip-range="10.0.0.0/24" \
|
||||||
|
--insecure-allow-any-token 1>&2 &
|
||||||
APISERVER_PID=$!
|
APISERVER_PID=$!
|
||||||
|
|
||||||
kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver"
|
kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver"
|
||||||
|
@ -38,10 +38,12 @@ function run_federation_apiserver() {
|
|||||||
|
|
||||||
"${KUBE_OUTPUT_HOSTBIN}/federation-apiserver" \
|
"${KUBE_OUTPUT_HOSTBIN}/federation-apiserver" \
|
||||||
--insecure-port="${API_PORT}" \
|
--insecure-port="${API_PORT}" \
|
||||||
|
--secure-port="${SECURE_API_PORT}" \
|
||||||
--admission-control="${ADMISSION_CONTROL}" \
|
--admission-control="${ADMISSION_CONTROL}" \
|
||||||
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \
|
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \
|
||||||
--storage-media-type="${KUBE_TEST_API_STORAGE_TYPE-}" \
|
--storage-media-type="${KUBE_TEST_API_STORAGE_TYPE-}" \
|
||||||
--cert-dir="${TMPDIR:-/tmp/}" 1>&2 &
|
--cert-dir="${TMPDIR:-/tmp/}" \
|
||||||
|
--insecure-allow-any-token 1>&2 &
|
||||||
APISERVER_PID=$!
|
APISERVER_PID=$!
|
||||||
|
|
||||||
kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver"
|
kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver"
|
||||||
|
@ -109,6 +109,12 @@ type AuthInfo struct {
|
|||||||
// Impersonate is the username to act-as.
|
// Impersonate is the username to act-as.
|
||||||
// +optional
|
// +optional
|
||||||
Impersonate string `json:"act-as,omitempty"`
|
Impersonate string `json:"act-as,omitempty"`
|
||||||
|
// ImpersonateGroups is the groups to imperonate.
|
||||||
|
// +optional
|
||||||
|
ImpersonateGroups []string `json:"act-as-groups,omitempty"`
|
||||||
|
// ImpersonateUserExtra contains additional information for impersonated user.
|
||||||
|
// +optional
|
||||||
|
ImpersonateUserExtra map[string][]string `json:"act-as-user-extra,omitempty"`
|
||||||
// Username is the username for basic authentication to the kubernetes cluster.
|
// Username is the username for basic authentication to the kubernetes cluster.
|
||||||
// +optional
|
// +optional
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
@ -172,7 +178,10 @@ func NewCluster() *Cluster {
|
|||||||
// NewAuthInfo is a convenience function that returns a new AuthInfo
|
// NewAuthInfo is a convenience function that returns a new AuthInfo
|
||||||
// object with non-nil maps
|
// object with non-nil maps
|
||||||
func NewAuthInfo() *AuthInfo {
|
func NewAuthInfo() *AuthInfo {
|
||||||
return &AuthInfo{Extensions: make(map[string]runtime.Object)}
|
return &AuthInfo{
|
||||||
|
Extensions: make(map[string]runtime.Object),
|
||||||
|
ImpersonateUserExtra: make(map[string][]string),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPreferences is a convenience function that returns a new
|
// NewPreferences is a convenience function that returns a new
|
||||||
|
@ -103,6 +103,12 @@ type AuthInfo struct {
|
|||||||
// Impersonate is the username to imperonate. The name matches the flag.
|
// Impersonate is the username to imperonate. The name matches the flag.
|
||||||
// +optional
|
// +optional
|
||||||
Impersonate string `json:"as,omitempty"`
|
Impersonate string `json:"as,omitempty"`
|
||||||
|
// ImpersonateGroups is the groups to imperonate.
|
||||||
|
// +optional
|
||||||
|
ImpersonateGroups []string `json:"as-groups,omitempty"`
|
||||||
|
// ImpersonateUserExtra contains additional information for impersonated user.
|
||||||
|
// +optional
|
||||||
|
ImpersonateUserExtra map[string][]string `json:"as-user-extra,omitempty"`
|
||||||
// Username is the username for basic authentication to the kubernetes cluster.
|
// Username is the username for basic authentication to the kubernetes cluster.
|
||||||
// +optional
|
// +optional
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
|
@ -146,7 +146,11 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
|
|||||||
clientConfig.Host = u.String()
|
clientConfig.Host = u.String()
|
||||||
}
|
}
|
||||||
if len(configAuthInfo.Impersonate) > 0 {
|
if len(configAuthInfo.Impersonate) > 0 {
|
||||||
clientConfig.Impersonate = restclient.ImpersonationConfig{UserName: configAuthInfo.Impersonate}
|
clientConfig.Impersonate = restclient.ImpersonationConfig{
|
||||||
|
UserName: configAuthInfo.Impersonate,
|
||||||
|
Groups: configAuthInfo.ImpersonateGroups,
|
||||||
|
Extra: configAuthInfo.ImpersonateUserExtra,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// only try to read the auth information if we are secure
|
// only try to read the auth information if we are secure
|
||||||
@ -217,7 +221,11 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
|
|||||||
mergedConfig.BearerToken = string(tokenBytes)
|
mergedConfig.BearerToken = string(tokenBytes)
|
||||||
}
|
}
|
||||||
if len(configAuthInfo.Impersonate) > 0 {
|
if len(configAuthInfo.Impersonate) > 0 {
|
||||||
mergedConfig.Impersonate = restclient.ImpersonationConfig{UserName: configAuthInfo.Impersonate}
|
mergedConfig.Impersonate = restclient.ImpersonationConfig{
|
||||||
|
UserName: configAuthInfo.Impersonate,
|
||||||
|
Groups: configAuthInfo.ImpersonateGroups,
|
||||||
|
Extra: configAuthInfo.ImpersonateUserExtra,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
|
if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
|
||||||
mergedConfig.CertFile = configAuthInfo.ClientCertificate
|
mergedConfig.CertFile = configAuthInfo.ClientCertificate
|
||||||
|
@ -52,6 +52,7 @@ type AuthOverrideFlags struct {
|
|||||||
ClientKey FlagInfo
|
ClientKey FlagInfo
|
||||||
Token FlagInfo
|
Token FlagInfo
|
||||||
Impersonate FlagInfo
|
Impersonate FlagInfo
|
||||||
|
ImpersonateGroups FlagInfo
|
||||||
Username FlagInfo
|
Username FlagInfo
|
||||||
Password FlagInfo
|
Password FlagInfo
|
||||||
}
|
}
|
||||||
@ -100,6 +101,19 @@ func (f FlagInfo) BindStringFlag(flags *pflag.FlagSet, target *string) FlagInfo
|
|||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindStringSliceFlag binds the flag based on the provided info. If LongName == "", nothing is registered
|
||||||
|
func (f FlagInfo) BindStringArrayFlag(flags *pflag.FlagSet, target *[]string) FlagInfo {
|
||||||
|
// you can't register a flag without a long name
|
||||||
|
if len(f.LongName) > 0 {
|
||||||
|
sliceVal := []string{}
|
||||||
|
if len(f.Default) > 0 {
|
||||||
|
sliceVal = []string{f.Default}
|
||||||
|
}
|
||||||
|
flags.StringArrayVarP(target, f.LongName, f.ShortName, sliceVal, f.Description)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
// BindBoolFlag binds the flag based on the provided info. If LongName == "", nothing is registered
|
// BindBoolFlag binds the flag based on the provided info. If LongName == "", nothing is registered
|
||||||
func (f FlagInfo) BindBoolFlag(flags *pflag.FlagSet, target *bool) FlagInfo {
|
func (f FlagInfo) BindBoolFlag(flags *pflag.FlagSet, target *bool) FlagInfo {
|
||||||
// you can't register a flag without a long name
|
// you can't register a flag without a long name
|
||||||
@ -116,22 +130,23 @@ func (f FlagInfo) BindBoolFlag(flags *pflag.FlagSet, target *bool) FlagInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FlagClusterName = "cluster"
|
FlagClusterName = "cluster"
|
||||||
FlagAuthInfoName = "user"
|
FlagAuthInfoName = "user"
|
||||||
FlagContext = "context"
|
FlagContext = "context"
|
||||||
FlagNamespace = "namespace"
|
FlagNamespace = "namespace"
|
||||||
FlagAPIServer = "server"
|
FlagAPIServer = "server"
|
||||||
FlagAPIVersion = "api-version"
|
FlagAPIVersion = "api-version"
|
||||||
FlagInsecure = "insecure-skip-tls-verify"
|
FlagInsecure = "insecure-skip-tls-verify"
|
||||||
FlagCertFile = "client-certificate"
|
FlagCertFile = "client-certificate"
|
||||||
FlagKeyFile = "client-key"
|
FlagKeyFile = "client-key"
|
||||||
FlagCAFile = "certificate-authority"
|
FlagCAFile = "certificate-authority"
|
||||||
FlagEmbedCerts = "embed-certs"
|
FlagEmbedCerts = "embed-certs"
|
||||||
FlagBearerToken = "token"
|
FlagBearerToken = "token"
|
||||||
FlagImpersonate = "as"
|
FlagImpersonate = "as"
|
||||||
FlagUsername = "username"
|
FlagImpersonateGroup = "as-group"
|
||||||
FlagPassword = "password"
|
FlagUsername = "username"
|
||||||
FlagTimeout = "request-timeout"
|
FlagPassword = "password"
|
||||||
|
FlagTimeout = "request-timeout"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RecommendedConfigOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
// RecommendedConfigOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
||||||
@ -153,6 +168,7 @@ func RecommendedAuthOverrideFlags(prefix string) AuthOverrideFlags {
|
|||||||
ClientKey: FlagInfo{prefix + FlagKeyFile, "", "", "Path to a client key file for TLS"},
|
ClientKey: FlagInfo{prefix + FlagKeyFile, "", "", "Path to a client key file for TLS"},
|
||||||
Token: FlagInfo{prefix + FlagBearerToken, "", "", "Bearer token for authentication to the API server"},
|
Token: FlagInfo{prefix + FlagBearerToken, "", "", "Bearer token for authentication to the API server"},
|
||||||
Impersonate: FlagInfo{prefix + FlagImpersonate, "", "", "Username to impersonate for the operation"},
|
Impersonate: FlagInfo{prefix + FlagImpersonate, "", "", "Username to impersonate for the operation"},
|
||||||
|
ImpersonateGroups: FlagInfo{prefix + FlagImpersonateGroup, "", "", "Group to impersonate for the operation, this flag can be repeated to specify multiple groups."},
|
||||||
Username: FlagInfo{prefix + FlagUsername, "", "", "Username for basic authentication to the API server"},
|
Username: FlagInfo{prefix + FlagUsername, "", "", "Username for basic authentication to the API server"},
|
||||||
Password: FlagInfo{prefix + FlagPassword, "", "", "Password for basic authentication to the API server"},
|
Password: FlagInfo{prefix + FlagPassword, "", "", "Password for basic authentication to the API server"},
|
||||||
}
|
}
|
||||||
@ -192,6 +208,7 @@ func BindAuthInfoFlags(authInfo *clientcmdapi.AuthInfo, flags *pflag.FlagSet, fl
|
|||||||
flagNames.ClientKey.BindStringFlag(flags, &authInfo.ClientKey).AddSecretAnnotation(flags)
|
flagNames.ClientKey.BindStringFlag(flags, &authInfo.ClientKey).AddSecretAnnotation(flags)
|
||||||
flagNames.Token.BindStringFlag(flags, &authInfo.Token).AddSecretAnnotation(flags)
|
flagNames.Token.BindStringFlag(flags, &authInfo.Token).AddSecretAnnotation(flags)
|
||||||
flagNames.Impersonate.BindStringFlag(flags, &authInfo.Impersonate).AddSecretAnnotation(flags)
|
flagNames.Impersonate.BindStringFlag(flags, &authInfo.Impersonate).AddSecretAnnotation(flags)
|
||||||
|
flagNames.ImpersonateGroups.BindStringArrayFlag(flags, &authInfo.ImpersonateGroups).AddSecretAnnotation(flags)
|
||||||
flagNames.Username.BindStringFlag(flags, &authInfo.Username).AddSecretAnnotation(flags)
|
flagNames.Username.BindStringFlag(flags, &authInfo.Username).AddSecretAnnotation(flags)
|
||||||
flagNames.Password.BindStringFlag(flags, &authInfo.Password).AddSecretAnnotation(flags)
|
flagNames.Password.BindStringFlag(flags, &authInfo.Password).AddSecretAnnotation(flags)
|
||||||
}
|
}
|
||||||
|
@ -242,6 +242,10 @@ func validateAuthInfo(authInfoName string, authInfo clientcmdapi.AuthInfo) []err
|
|||||||
validationErrors = append(validationErrors, fmt.Errorf("more than one authentication method found for %v; found %v, only one is allowed", authInfoName, methods))
|
validationErrors = append(validationErrors, fmt.Errorf("more than one authentication method found for %v; found %v, only one is allowed", authInfoName, methods))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImpersonateGroups or ImpersonateUserExtra should be requested with a user
|
||||||
|
if (len(authInfo.ImpersonateGroups) > 0 || len(authInfo.ImpersonateUserExtra) > 0) && (len(authInfo.Impersonate) == 0) {
|
||||||
|
validationErrors = append(validationErrors, fmt.Errorf("requesting groups or user-extra for %v without impersonating a user", authInfoName))
|
||||||
|
}
|
||||||
return validationErrors
|
return validationErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user