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:
Cao Shufeng 2017-03-23 23:18:53 -04:00
parent 4807e6cadf
commit e541defd49
8 changed files with 96 additions and 21 deletions

View File

@ -28,6 +28,7 @@ KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../..
ETCD_HOST=${ETCD_HOST:-127.0.0.1}
ETCD_PORT=${ETCD_PORT:-2379}
API_PORT=${API_PORT:-8080}
SECURE_API_PORT=${SECURE_API_PORT:-6443}
API_HOST=${API_HOST:-127.0.0.1}
KUBELET_PORT=${KUBELET_PORT:-10250}
KUBELET_HEALTHZ_PORT=${KUBELET_HEALTHZ_PORT:-10248}
@ -2855,8 +2856,14 @@ runTests() {
kube_flags=(
-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
kube_flags+=("--match-server-version")
kube_flags_with_token+=("--match-server-version")
fi
if kube::test::if_supports_resource "${nodes}" ; then
[ "$(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)
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
}

View File

@ -44,6 +44,7 @@ function run_kube_apiserver() {
--public-address-override="127.0.0.1" \
--port="${API_PORT}" \
--authorization-mode="${AUTHORIZATION_MODE}" \
--secure-port="${SECURE_API_PORT}" \
--admission-control="${ADMISSION_CONTROL}" \
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \
--public-address-override="127.0.0.1" \
@ -51,7 +52,8 @@ function run_kube_apiserver() {
--runtime-config=api/v1 \
--storage-media-type="${KUBE_TEST_API_STORAGE_TYPE-}" \
--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=$!
kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver"

View File

@ -38,10 +38,12 @@ function run_federation_apiserver() {
"${KUBE_OUTPUT_HOSTBIN}/federation-apiserver" \
--insecure-port="${API_PORT}" \
--secure-port="${SECURE_API_PORT}" \
--admission-control="${ADMISSION_CONTROL}" \
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \
--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=$!
kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver"

View File

@ -109,6 +109,12 @@ type AuthInfo struct {
// Impersonate is the username to act-as.
// +optional
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.
// +optional
Username string `json:"username,omitempty"`
@ -172,7 +178,10 @@ func NewCluster() *Cluster {
// NewAuthInfo is a convenience function that returns a new AuthInfo
// object with non-nil maps
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

View File

@ -103,6 +103,12 @@ type AuthInfo struct {
// Impersonate is the username to imperonate. The name matches the flag.
// +optional
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.
// +optional
Username string `json:"username,omitempty"`

View File

@ -146,7 +146,11 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
clientConfig.Host = u.String()
}
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
@ -217,7 +221,11 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
mergedConfig.BearerToken = string(tokenBytes)
}
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 {
mergedConfig.CertFile = configAuthInfo.ClientCertificate

View File

@ -52,6 +52,7 @@ type AuthOverrideFlags struct {
ClientKey FlagInfo
Token FlagInfo
Impersonate FlagInfo
ImpersonateGroups FlagInfo
Username FlagInfo
Password FlagInfo
}
@ -100,6 +101,19 @@ func (f FlagInfo) BindStringFlag(flags *pflag.FlagSet, target *string) FlagInfo
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
func (f FlagInfo) BindBoolFlag(flags *pflag.FlagSet, target *bool) FlagInfo {
// 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 (
FlagClusterName = "cluster"
FlagAuthInfoName = "user"
FlagContext = "context"
FlagNamespace = "namespace"
FlagAPIServer = "server"
FlagAPIVersion = "api-version"
FlagInsecure = "insecure-skip-tls-verify"
FlagCertFile = "client-certificate"
FlagKeyFile = "client-key"
FlagCAFile = "certificate-authority"
FlagEmbedCerts = "embed-certs"
FlagBearerToken = "token"
FlagImpersonate = "as"
FlagUsername = "username"
FlagPassword = "password"
FlagTimeout = "request-timeout"
FlagClusterName = "cluster"
FlagAuthInfoName = "user"
FlagContext = "context"
FlagNamespace = "namespace"
FlagAPIServer = "server"
FlagAPIVersion = "api-version"
FlagInsecure = "insecure-skip-tls-verify"
FlagCertFile = "client-certificate"
FlagKeyFile = "client-key"
FlagCAFile = "certificate-authority"
FlagEmbedCerts = "embed-certs"
FlagBearerToken = "token"
FlagImpersonate = "as"
FlagImpersonateGroup = "as-group"
FlagUsername = "username"
FlagPassword = "password"
FlagTimeout = "request-timeout"
)
// 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"},
Token: FlagInfo{prefix + FlagBearerToken, "", "", "Bearer token for authentication to the API server"},
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"},
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.Token.BindStringFlag(flags, &authInfo.Token).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.Password.BindStringFlag(flags, &authInfo.Password).AddSecretAnnotation(flags)
}

View File

@ -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))
}
// 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
}