diff --git a/cmd/libs/go2idl/client-gen/main.go b/cmd/libs/go2idl/client-gen/main.go index 7c5d44487c3..7e38ea7019d 100644 --- a/cmd/libs/go2idl/client-gen/main.go +++ b/cmd/libs/go2idl/client-gen/main.go @@ -32,8 +32,17 @@ import ( ) var ( - test = flag.BoolP("test", "t", false, "set this flag to generate the client code for the testdata") - inputVersions = flag.StringSlice("input", []string{"api/", "extensions/", "autoscaling/", "authentication/", "batch/", "rbac/", "certificates/"}, "group/versions that client-gen will generate clients for. At most one version per group is allowed. Specified in the format \"group1/version1,group2/version2...\". Default to \"api/,extensions/,autoscaling/,batch/,rbac/\"") + test = flag.BoolP("test", "t", false, "set this flag to generate the client code for the testdata") + inputVersions = flag.StringSlice("input", []string{ + "api/", + "authentication/", + "authorization/", + "autoscaling/", + "batch/", + "certificates/", + "extensions/", + "rbac/", + }, "group/versions that client-gen will generate clients for. At most one version per group is allowed. Specified in the format \"group1/version1,group2/version2...\". Default to \"api/,extensions/,autoscaling/,batch/,rbac/\"") includedTypesOverrides = flag.StringSlice("included-types-overrides", []string{}, "list of group/version/type for which client should be generated. By default, client is generated for all types which have genclient=true in types.go. This overrides that. For each groupVersion in this list, only the types mentioned here will be included. The default check of genclient=true will be used for other group versions.") basePath = flag.String("input-base", "k8s.io/kubernetes/pkg/apis", "base path to look for the api group. Default to \"k8s.io/kubernetes/pkg/apis\"") clientsetName = flag.StringP("clientset-name", "n", "internalclientset", "the name of the generated clientset package.") diff --git a/cmd/libs/go2idl/go-to-protobuf/protobuf/cmd.go b/cmd/libs/go2idl/go-to-protobuf/protobuf/cmd.go index 4becf361b65..e70c823b7f7 100644 --- a/cmd/libs/go2idl/go-to-protobuf/protobuf/cmd.go +++ b/cmd/libs/go2idl/go-to-protobuf/protobuf/cmd.go @@ -69,6 +69,7 @@ func New() *Generator { `k8s.io/kubernetes/pkg/apis/policy/v1alpha1`, `k8s.io/kubernetes/pkg/apis/extensions/v1beta1`, `k8s.io/kubernetes/pkg/apis/autoscaling/v1`, + `k8s.io/kubernetes/pkg/apis/authorization/v1beta1`, `k8s.io/kubernetes/pkg/apis/batch/v1`, `k8s.io/kubernetes/pkg/apis/batch/v2alpha1`, `k8s.io/kubernetes/pkg/apis/apps/v1alpha1`, diff --git a/hack/make-rules/test-cmd.sh b/hack/make-rules/test-cmd.sh index cc259f65691..aa095f3fd4d 100755 --- a/hack/make-rules/test-cmd.sh +++ b/hack/make-rules/test-cmd.sh @@ -2328,6 +2328,26 @@ __EOF__ kubectl create -f test/fixtures/pkg/kubectl/cmd/create/tokenreview.json --validate=false + + ######################## + # authorization.k8s.io # + ######################## + + # check remote authorization endpoint, kubectl doesn't actually display the returned object so this isn't super useful + # but it proves that works + kubectl create -f test/fixtures/pkg/kubectl/cmd/create/sar.json --validate=false + + SAR_RESULT_FILE="${KUBE_TEMP}/sar-result.json" + curl -k -H "Content-Type:" http://localhost:8080/apis/authorization.k8s.io/v1beta1/subjectaccessreviews -XPOST -d @test/fixtures/pkg/kubectl/cmd/create/sar.json > "${SAR_RESULT_FILE}" + if grep -q '"allowed": true' "${SAR_RESULT_FILE}"; then + kube::log::status "\"authorization.k8s.io/subjectaccessreviews\" returns as expected: $(cat "${SAR_RESULT_FILE}")" + else + kube::log::status "\"authorization.k8s.io/subjectaccessreviews\" does not return as expected: $(cat "${SAR_RESULT_FILE}")" + exit 1 + fi + rm "${SAR_RESULT_FILE}" + + ##################### # Retrieve multiple # ##################### diff --git a/hack/make-rules/test-integration.sh b/hack/make-rules/test-integration.sh index 5eb764cc3af..c5b04750d80 100755 --- a/hack/make-rules/test-integration.sh +++ b/hack/make-rules/test-integration.sh @@ -27,7 +27,7 @@ source "${KUBE_ROOT}/hack/lib/init.sh" # KUBE_TEST_API_VERSIONS=${KUBE_TEST_API_VERSIONS:-"v1,extensions/v1beta1"} # FIXME: due to current implementation of a test client (see: pkg/api/testapi/testapi.go) # ONLY the last version is tested in each group. -KUBE_TEST_API_VERSIONS=${KUBE_TEST_API_VERSIONS:-"v1,autoscaling/v1,batch/v1,apps/v1alpha1,policy/v1alpha1,extensions/v1beta1,rbac.authorization.k8s.io/v1alpha1,certificates/v1alpha1"} +KUBE_TEST_API_VERSIONS=${KUBE_TEST_API_VERSIONS:-"v1,authorization.k8s.io/v1beta1,autoscaling/v1,batch/v1,apps/v1alpha1,policy/v1alpha1,extensions/v1beta1,rbac.authorization.k8s.io/v1alpha1,certificates/v1alpha1"} # Give integration tests longer to run # TODO: allow a larger value to be passed in diff --git a/hack/make-rules/test.sh b/hack/make-rules/test.sh index 5a292031579..a2a7b413de0 100755 --- a/hack/make-rules/test.sh +++ b/hack/make-rules/test.sh @@ -60,7 +60,7 @@ KUBE_GOVERALLS_BIN=${KUBE_GOVERALLS_BIN:-} # "v1,compute/v1alpha1,experimental/v1alpha2;v1,compute/v2,experimental/v1alpha3" # FIXME: due to current implementation of a test client (see: pkg/api/testapi/testapi.go) # ONLY the last version is tested in each group. -KUBE_TEST_API_VERSIONS=${KUBE_TEST_API_VERSIONS:-"v1,autoscaling/v1,authentication.k8s.io/v1beta1,batch/v1,batch/v2alpha1,extensions/v1beta1,apps/v1alpha1,federation/v1beta1,policy/v1alpha1,rbac.authorization.k8s.io/v1alpha1,certificates/v1alpha1"} +KUBE_TEST_API_VERSIONS=${KUBE_TEST_API_VERSIONS:-"v1,apps/v1alpha1,authentication.k8s.io/v1beta1,authorization.k8s.io/v1beta1,autoscaling/v1,batch/v1,batch/v2alpha1,certificates/v1alpha1,extensions/v1beta1,federation/v1beta1,policy/v1alpha1,rbac.authorization.k8s.io/v1alpha1"} # once we have multiple group supports # Create a junit-style XML test report in this directory if set. KUBE_JUNIT_REPORT_DIR=${KUBE_JUNIT_REPORT_DIR:-} diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 0c8f54d24e2..7edd017fe0f 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -39,7 +39,7 @@ setgen=$(kube::util::find-binary "set-gen") # update- and verify- scripts. ${clientgen} "$@" ${clientgen} -t "$@" -${clientgen} --clientset-name="release_1_4" --input="api/v1,extensions/v1beta1,autoscaling/v1,batch/v1" +${clientgen} --clientset-name="release_1_4" --input="api/v1,authorization/v1beta1,autoscaling/v1,batch/v1,extensions/v1beta1" # Clientgen for federation clientset. ${clientgen} --clientset-name=federation_internalclientset --clientset-path=k8s.io/kubernetes/federation/client/clientset_generated --input="../../federation/apis/federation/","api/","extensions/" --included-types-overrides="api/Service,api/Namespace,extensions/ReplicaSet,api/Secret" "$@" ${clientgen} --clientset-name=federation_release_1_4 --clientset-path=k8s.io/kubernetes/federation/client/clientset_generated --input="../../federation/apis/federation/v1beta1","api/v1","extensions/v1beta1" --included-types-overrides="api/v1/Service,api/v1/Namespace,extensions/v1beta1/ReplicaSet,api/v1/Secret" "$@" diff --git a/hack/update-swagger-spec.sh b/hack/update-swagger-spec.sh index 4c061ceeafb..d309b1b833c 100755 --- a/hack/update-swagger-spec.sh +++ b/hack/update-swagger-spec.sh @@ -74,7 +74,7 @@ APISERVER_PID=$! kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver: " SWAGGER_API_PATH="http://127.0.0.1:${API_PORT}/swaggerapi/" -DEFAULT_GROUP_VERSIONS="v1 authentication.k8s.io/v1beta1 autoscaling/v1 batch/v1 batch/v2alpha1 extensions/v1beta1 apps/v1alpha1 policy/v1alpha1 rbac.authorization.k8s.io/v1alpha1 certificates/v1alpha1" +DEFAULT_GROUP_VERSIONS="v1 apps/v1alpha1 authentication.k8s.io/v1beta1 authorization.k8s.io/v1beta1 autoscaling/v1 batch/v1 batch/v2alpha1 extensions/v1beta1 certificates/v1alpha1 policy/v1alpha1 rbac.authorization.k8s.io/v1alpha1" VERSIONS=${VERSIONS:-$DEFAULT_GROUP_VERSIONS} kube::log::status "Updating " ${SWAGGER_ROOT_DIR} diff --git a/pkg/api/testapi/testapi.go b/pkg/api/testapi/testapi.go index 24ff1fb06a0..7cd00d2b770 100644 --- a/pkg/api/testapi/testapi.go +++ b/pkg/api/testapi/testapi.go @@ -43,6 +43,7 @@ import ( _ "k8s.io/kubernetes/pkg/api/install" _ "k8s.io/kubernetes/pkg/apis/apps/install" _ "k8s.io/kubernetes/pkg/apis/authentication/install" + _ "k8s.io/kubernetes/pkg/apis/authorization/install" _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" _ "k8s.io/kubernetes/pkg/apis/batch/install" _ "k8s.io/kubernetes/pkg/apis/certificates/install" diff --git a/pkg/apis/authorization/doc.go b/pkg/apis/authorization/doc.go index 4cf28cad849..a6c011cbf61 100644 --- a/pkg/apis/authorization/doc.go +++ b/pkg/apis/authorization/doc.go @@ -16,4 +16,5 @@ limitations under the License. // +k8s:deepcopy-gen=package,register +// +groupName=authorization.k8s.io package authorization // import "k8s.io/kubernetes/pkg/apis/authorization" diff --git a/pkg/apis/authorization/types.go b/pkg/apis/authorization/types.go index afa2c4273c7..33ad58c3539 100644 --- a/pkg/apis/authorization/types.go +++ b/pkg/apis/authorization/types.go @@ -17,13 +17,19 @@ limitations under the License. package authorization import ( + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" ) +// +genclient=true +// +nonNamespaced=true +// +noMethods=true + // SubjectAccessReview checks whether or not a user or group can perform an action. Not filling in a // spec.namespace means "in all namespaces". type SubjectAccessReview struct { unversioned.TypeMeta + api.ObjectMeta // Spec holds information about the request being evaluated Spec SubjectAccessReviewSpec @@ -37,6 +43,7 @@ type SubjectAccessReview struct { // to check whether they can perform an action type SelfSubjectAccessReview struct { unversioned.TypeMeta + api.ObjectMeta // Spec holds information about the request being evaluated. Spec SelfSubjectAccessReviewSpec @@ -50,6 +57,7 @@ type SelfSubjectAccessReview struct { // checking. type LocalSubjectAccessReview struct { unversioned.TypeMeta + api.ObjectMeta // Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace // you made the request against. If empty, it is defaulted. @@ -103,9 +111,13 @@ type SubjectAccessReviewSpec struct { Groups []string // Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer // it needs a reflection here. - Extra map[string][]string + Extra map[string]ExtraValue } +// ExtraValue masks the value so protobuf can generate +// +protobuf.nullable=true +type ExtraValue []string + // SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAttributes // and NonResourceAttributes must be set type SelfSubjectAccessReviewSpec struct { @@ -121,4 +133,8 @@ type SubjectAccessReviewStatus struct { Allowed bool // Reason is optional. It indicates why a request was allowed or denied. Reason string + // EvaluationError is an indication that some error occurred during the authorization check. + // It is entirely possible to get an error and be able to continue determine authorization status in spite of it. + // For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request. + EvaluationError string } diff --git a/pkg/apis/authorization/v1beta1/doc.go b/pkg/apis/authorization/v1beta1/doc.go index f5c4cbc2a1f..690ab217add 100644 --- a/pkg/apis/authorization/v1beta1/doc.go +++ b/pkg/apis/authorization/v1beta1/doc.go @@ -17,4 +17,5 @@ limitations under the License. // +k8s:deepcopy-gen=package,register // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/authorization +// +groupName=authorization.k8s.io package v1beta1 // import "k8s.io/kubernetes/pkg/apis/authorization/v1beta1" diff --git a/pkg/apis/authorization/v1beta1/register.go b/pkg/apis/authorization/v1beta1/register.go index fe99bee3fa1..c54d20d6dba 100644 --- a/pkg/apis/authorization/v1beta1/register.go +++ b/pkg/apis/authorization/v1beta1/register.go @@ -18,7 +18,9 @@ package v1beta1 import ( "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/runtime" + versionedwatch "k8s.io/kubernetes/pkg/watch/versioned" ) // GroupName is the group name use in this package @@ -37,10 +39,15 @@ func AddToScheme(scheme *runtime.Scheme) { // Adds the list of known types to api.Scheme. func addKnownTypes(scheme *runtime.Scheme) { scheme.AddKnownTypes(SchemeGroupVersion, + &v1.ListOptions{}, + &v1.DeleteOptions{}, + &SelfSubjectAccessReview{}, &SubjectAccessReview{}, &LocalSubjectAccessReview{}, ) + + versionedwatch.AddToGroupVersion(scheme, SchemeGroupVersion) } func (obj *LocalSubjectAccessReview) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/pkg/apis/authorization/v1beta1/types.go b/pkg/apis/authorization/v1beta1/types.go index 70c1282a755..558a1e5750b 100644 --- a/pkg/apis/authorization/v1beta1/types.go +++ b/pkg/apis/authorization/v1beta1/types.go @@ -17,18 +17,26 @@ limitations under the License. package v1beta1 import ( + "fmt" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" ) +// +genclient=true +// +nonNamespaced=true +// +noMethods=true + // SubjectAccessReview checks whether or not a user or group can perform an action. type SubjectAccessReview struct { unversioned.TypeMeta `json:",inline"` + v1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Spec holds information about the request being evaluated - Spec SubjectAccessReviewSpec `json:"spec"` + Spec SubjectAccessReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` // Status is filled in by the server and indicates whether the request is allowed or not - Status SubjectAccessReviewStatus `json:"status,omitempty"` + Status SubjectAccessReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } // SelfSubjectAccessReview checks whether or the current user can perform an action. Not filling in a @@ -36,12 +44,13 @@ type SubjectAccessReview struct { // to check whether they can perform an action type SelfSubjectAccessReview struct { unversioned.TypeMeta `json:",inline"` + v1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Spec holds information about the request being evaluated. user and groups must be empty - Spec SelfSubjectAccessReviewSpec `json:"spec"` + Spec SelfSubjectAccessReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` // Status is filled in by the server and indicates whether the request is allowed or not - Status SubjectAccessReviewStatus `json:"status,omitempty"` + Status SubjectAccessReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } // LocalSubjectAccessReview checks whether or not a user or group can perform an action in a given namespace. @@ -49,13 +58,14 @@ type SelfSubjectAccessReview struct { // checking. type LocalSubjectAccessReview struct { unversioned.TypeMeta `json:",inline"` + v1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Spec holds information about the request being evaluated. spec.namespace must be equal to the namespace // you made the request against. If empty, it is defaulted. - Spec SubjectAccessReviewSpec `json:"spec"` + Spec SubjectAccessReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` // Status is filled in by the server and indicates whether the request is allowed or not - Status SubjectAccessReviewStatus `json:"status,omitempty"` + Status SubjectAccessReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } // ResourceAttributes includes the authorization attributes available for resource requests to the Authorizer interface @@ -64,60 +74,73 @@ type ResourceAttributes struct { // "" (empty) is defaulted for LocalSubjectAccessReviews // "" (empty) is empty for cluster-scoped resources // "" (empty) means "all" for namespace scoped resources from a SubjectAccessReview or SelfSubjectAccessReview - Namespace string `json:"namespace,omitempty"` + Namespace string `json:"namespace,omitempty" protobuf:"bytes,1,opt,name=namespace"` // Verb is a kubernetes resource API verb, like: get, list, watch, create, update, delete, proxy. "*" means all. - Verb string `json:"verb,omitempty"` + Verb string `json:"verb,omitempty" protobuf:"bytes,2,opt,name=verb"` // Group is the API Group of the Resource. "*" means all. - Group string `json:"group,omitempty"` + Group string `json:"group,omitempty" protobuf:"bytes,3,opt,name=group"` // Version is the API Version of the Resource. "*" means all. - Version string `json:"version,omitempty"` + Version string `json:"version,omitempty" protobuf:"bytes,4,opt,name=version"` // Resource is one of the existing resource types. "*" means all. - Resource string `json:"resource,omitempty"` + Resource string `json:"resource,omitempty" protobuf:"bytes,5,opt,name=resource"` // Subresource is one of the existing resource types. "" means none. - Subresource string `json:"subresource,omitempty"` + Subresource string `json:"subresource,omitempty" protobuf:"bytes,6,opt,name=subresource"` // Name is the name of the resource being requested for a "get" or deleted for a "delete". "" (empty) means all. - Name string `json:"name,omitempty"` + Name string `json:"name,omitempty" protobuf:"bytes,7,opt,name=name"` } // NonResourceAttributes includes the authorization attributes available for non-resource requests to the Authorizer interface type NonResourceAttributes struct { // Path is the URL path of the request - Path string `json:"path,omitempty"` + Path string `json:"path,omitempty" protobuf:"bytes,1,opt,name=path"` // Verb is the standard HTTP verb - Verb string `json:"verb,omitempty"` + Verb string `json:"verb,omitempty" protobuf:"bytes,2,opt,name=verb"` } // SubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes // and NonResourceAuthorizationAttributes must be set type SubjectAccessReviewSpec struct { // ResourceAuthorizationAttributes describes information for a resource access request - ResourceAttributes *ResourceAttributes `json:"resourceAttributes,omitempty"` + ResourceAttributes *ResourceAttributes `json:"resourceAttributes,omitempty" protobuf:"bytes,1,opt,name=resourceAttributes"` // NonResourceAttributes describes information for a non-resource access request - NonResourceAttributes *NonResourceAttributes `json:"nonResourceAttributes,omitempty"` + NonResourceAttributes *NonResourceAttributes `json:"nonResourceAttributes,omitempty" protobuf:"bytes,2,opt,name=nonResourceAttributes"` // User is the user you're testing for. // If you specify "User" but not "Group", then is it interpreted as "What if User were not a member of any groups - User string `json:"user,omitempty"` + User string `json:"user,omitempty" protobuf:"bytes,3,opt,name=verb"` // Groups is the groups you're testing for. - Groups []string `json:"group,omitempty"` + Groups []string `json:"group,omitempty" protobuf:"bytes,4,rep,name=group"` // Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer // it needs a reflection here. - Extra map[string][]string `json:"extra,omitempty"` + Extra map[string]ExtraValue `json:"extra,omitempty" protobuf:"bytes,5,rep,name=extra"` +} + +// ExtraValue masks the value so protobuf can generate +// +protobuf.nullable=true +// +protobuf.options.(gogoproto.goproto_stringer)=false +type ExtraValue []string + +func (t ExtraValue) String() string { + return fmt.Sprintf("%v", []string(t)) } // SelfSubjectAccessReviewSpec is a description of the access request. Exactly one of ResourceAuthorizationAttributes // and NonResourceAuthorizationAttributes must be set type SelfSubjectAccessReviewSpec struct { // ResourceAuthorizationAttributes describes information for a resource access request - ResourceAttributes *ResourceAttributes `json:"resourceAttributes,omitempty"` + ResourceAttributes *ResourceAttributes `json:"resourceAttributes,omitempty" protobuf:"bytes,1,opt,name=resourceAttributes"` // NonResourceAttributes describes information for a non-resource access request - NonResourceAttributes *NonResourceAttributes `json:"nonResourceAttributes,omitempty"` + NonResourceAttributes *NonResourceAttributes `json:"nonResourceAttributes,omitempty" protobuf:"bytes,2,opt,name=nonResourceAttributes"` } // SubjectAccessReviewStatus type SubjectAccessReviewStatus struct { // Allowed is required. True if the action would be allowed, false otherwise. - Allowed bool `json:"allowed"` + Allowed bool `json:"allowed" protobuf:"varint,1,opt,name=allowed"` // Reason is optional. It indicates why a request was allowed or denied. - Reason string `json:"reason,omitempty"` + Reason string `json:"reason,omitempty" protobuf:"bytes,2,opt,name=reason"` + // EvaluationError is an indication that some error occurred during the authorization check. + // It is entirely possible to get an error and be able to continue determine authorization status in spite of it. + // For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request. + EvaluationError string `json:"evaluationError,omitempty" protobuf:"bytes,3,opt,name=evaluationError"` } diff --git a/pkg/apis/authorization/v1beta1/zz_generated.conversion.go b/pkg/apis/authorization/v1beta1/zz_generated.conversion.go index 18d11380bfa..4b142f7f79e 100644 --- a/pkg/apis/authorization/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/authorization/v1beta1/zz_generated.conversion.go @@ -54,6 +54,10 @@ func autoConvert_v1beta1_LocalSubjectAccessReview_To_authorization_LocalSubjectA if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { return err } + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil { + return err + } if err := Convert_v1beta1_SubjectAccessReviewSpec_To_authorization_SubjectAccessReviewSpec(&in.Spec, &out.Spec, s); err != nil { return err } @@ -71,6 +75,10 @@ func autoConvert_authorization_LocalSubjectAccessReview_To_v1beta1_LocalSubjectA if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { return err } + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil { + return err + } if err := Convert_authorization_SubjectAccessReviewSpec_To_v1beta1_SubjectAccessReviewSpec(&in.Spec, &out.Spec, s); err != nil { return err } @@ -138,6 +146,10 @@ func autoConvert_v1beta1_SelfSubjectAccessReview_To_authorization_SelfSubjectAcc if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { return err } + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil { + return err + } if err := Convert_v1beta1_SelfSubjectAccessReviewSpec_To_authorization_SelfSubjectAccessReviewSpec(&in.Spec, &out.Spec, s); err != nil { return err } @@ -155,6 +167,10 @@ func autoConvert_authorization_SelfSubjectAccessReview_To_v1beta1_SelfSubjectAcc if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { return err } + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil { + return err + } if err := Convert_authorization_SelfSubjectAccessReviewSpec_To_v1beta1_SelfSubjectAccessReviewSpec(&in.Spec, &out.Spec, s); err != nil { return err } @@ -224,6 +240,10 @@ func autoConvert_v1beta1_SubjectAccessReview_To_authorization_SubjectAccessRevie if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { return err } + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil { + return err + } if err := Convert_v1beta1_SubjectAccessReviewSpec_To_authorization_SubjectAccessReviewSpec(&in.Spec, &out.Spec, s); err != nil { return err } @@ -241,6 +261,10 @@ func autoConvert_authorization_SubjectAccessReview_To_v1beta1_SubjectAccessRevie if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { return err } + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil { + return err + } if err := Convert_authorization_SubjectAccessReviewSpec_To_v1beta1_SubjectAccessReviewSpec(&in.Spec, &out.Spec, s); err != nil { return err } @@ -275,7 +299,20 @@ func autoConvert_v1beta1_SubjectAccessReviewSpec_To_authorization_SubjectAccessR } out.User = in.User out.Groups = in.Groups - out.Extra = in.Extra + if in.Extra != nil { + in, out := &in.Extra, &out.Extra + *out = make(map[string]authorization.ExtraValue, len(*in)) + for key, val := range *in { + newVal := new(authorization.ExtraValue) + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&val, newVal, 0); err != nil { + return err + } + (*out)[key] = *newVal + } + } else { + out.Extra = nil + } return nil } @@ -304,7 +341,20 @@ func autoConvert_authorization_SubjectAccessReviewSpec_To_v1beta1_SubjectAccessR } out.User = in.User out.Groups = in.Groups - out.Extra = in.Extra + if in.Extra != nil { + in, out := &in.Extra, &out.Extra + *out = make(map[string]ExtraValue, len(*in)) + for key, val := range *in { + newVal := new(ExtraValue) + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&val, newVal, 0); err != nil { + return err + } + (*out)[key] = *newVal + } + } else { + out.Extra = nil + } return nil } @@ -315,6 +365,7 @@ func Convert_authorization_SubjectAccessReviewSpec_To_v1beta1_SubjectAccessRevie func autoConvert_v1beta1_SubjectAccessReviewStatus_To_authorization_SubjectAccessReviewStatus(in *SubjectAccessReviewStatus, out *authorization.SubjectAccessReviewStatus, s conversion.Scope) error { out.Allowed = in.Allowed out.Reason = in.Reason + out.EvaluationError = in.EvaluationError return nil } @@ -325,6 +376,7 @@ func Convert_v1beta1_SubjectAccessReviewStatus_To_authorization_SubjectAccessRev func autoConvert_authorization_SubjectAccessReviewStatus_To_v1beta1_SubjectAccessReviewStatus(in *authorization.SubjectAccessReviewStatus, out *SubjectAccessReviewStatus, s conversion.Scope) error { out.Allowed = in.Allowed out.Reason = in.Reason + out.EvaluationError = in.EvaluationError return nil } diff --git a/pkg/apis/authorization/v1beta1/zz_generated.deepcopy.go b/pkg/apis/authorization/v1beta1/zz_generated.deepcopy.go index af3dcdb8d83..b99e35a3696 100644 --- a/pkg/apis/authorization/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/authorization/v1beta1/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ package v1beta1 import ( api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" conversion "k8s.io/kubernetes/pkg/conversion" reflect "reflect" ) @@ -47,6 +48,9 @@ func DeepCopy_v1beta1_LocalSubjectAccessReview(in interface{}, out interface{}, in := in.(*LocalSubjectAccessReview) out := out.(*LocalSubjectAccessReview) out.TypeMeta = in.TypeMeta + if err := v1.DeepCopy_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } if err := DeepCopy_v1beta1_SubjectAccessReviewSpec(&in.Spec, &out.Spec, c); err != nil { return err } @@ -85,6 +89,9 @@ func DeepCopy_v1beta1_SelfSubjectAccessReview(in interface{}, out interface{}, c in := in.(*SelfSubjectAccessReview) out := out.(*SelfSubjectAccessReview) out.TypeMeta = in.TypeMeta + if err := v1.DeepCopy_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } if err := DeepCopy_v1beta1_SelfSubjectAccessReviewSpec(&in.Spec, &out.Spec, c); err != nil { return err } @@ -120,6 +127,9 @@ func DeepCopy_v1beta1_SubjectAccessReview(in interface{}, out interface{}, c *co in := in.(*SubjectAccessReview) out := out.(*SubjectAccessReview) out.TypeMeta = in.TypeMeta + if err := v1.DeepCopy_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } if err := DeepCopy_v1beta1_SubjectAccessReviewSpec(&in.Spec, &out.Spec, c); err != nil { return err } @@ -156,12 +166,12 @@ func DeepCopy_v1beta1_SubjectAccessReviewSpec(in interface{}, out interface{}, c } if in.Extra != nil { in, out := &in.Extra, &out.Extra - *out = make(map[string][]string) + *out = make(map[string]ExtraValue) for key, val := range *in { if newVal, err := c.DeepCopy(&val); err != nil { return err } else { - (*out)[key] = *newVal.(*[]string) + (*out)[key] = *newVal.(*ExtraValue) } } } else { @@ -177,6 +187,7 @@ func DeepCopy_v1beta1_SubjectAccessReviewStatus(in interface{}, out interface{}, out := out.(*SubjectAccessReviewStatus) out.Allowed = in.Allowed out.Reason = in.Reason + out.EvaluationError = in.EvaluationError return nil } } diff --git a/pkg/apis/authorization/validation/validation.go b/pkg/apis/authorization/validation/validation.go index 7565ac4d8c7..9b6298872c3 100644 --- a/pkg/apis/authorization/validation/validation.go +++ b/pkg/apis/authorization/validation/validation.go @@ -17,6 +17,7 @@ limitations under the License. package validation import ( + "k8s.io/kubernetes/pkg/api" authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" "k8s.io/kubernetes/pkg/util/validation/field" ) @@ -50,15 +51,24 @@ func ValidateSelfSubjectAccessReviewSpec(spec authorizationapi.SelfSubjectAccess func ValidateSubjectAccessReview(sar *authorizationapi.SubjectAccessReview) field.ErrorList { allErrs := ValidateSubjectAccessReviewSpec(sar.Spec, field.NewPath("spec")) + if !api.Semantic.DeepEqual(api.ObjectMeta{}, sar.ObjectMeta) { + allErrs = append(allErrs, field.Invalid(field.NewPath("metadata"), sar.ObjectMeta, `must be empty`)) + } return allErrs } func ValidateSelfSubjectAccessReview(sar *authorizationapi.SelfSubjectAccessReview) field.ErrorList { allErrs := ValidateSelfSubjectAccessReviewSpec(sar.Spec, field.NewPath("spec")) + if !api.Semantic.DeepEqual(api.ObjectMeta{}, sar.ObjectMeta) { + allErrs = append(allErrs, field.Invalid(field.NewPath("metadata"), sar.ObjectMeta, `must be empty`)) + } return allErrs } func ValidateLocalSubjectAccessReview(sar *authorizationapi.LocalSubjectAccessReview) field.ErrorList { allErrs := ValidateSubjectAccessReviewSpec(sar.Spec, field.NewPath("spec")) + if !api.Semantic.DeepEqual(api.ObjectMeta{}, sar.ObjectMeta) { + allErrs = append(allErrs, field.Invalid(field.NewPath("metadata"), sar.ObjectMeta, `must be empty`)) + } return allErrs } diff --git a/pkg/apis/authorization/zz_generated.deepcopy.go b/pkg/apis/authorization/zz_generated.deepcopy.go index b66c33a4e2b..22fc7f3759f 100644 --- a/pkg/apis/authorization/zz_generated.deepcopy.go +++ b/pkg/apis/authorization/zz_generated.deepcopy.go @@ -47,6 +47,9 @@ func DeepCopy_authorization_LocalSubjectAccessReview(in interface{}, out interfa in := in.(*LocalSubjectAccessReview) out := out.(*LocalSubjectAccessReview) out.TypeMeta = in.TypeMeta + if err := api.DeepCopy_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } if err := DeepCopy_authorization_SubjectAccessReviewSpec(&in.Spec, &out.Spec, c); err != nil { return err } @@ -85,6 +88,9 @@ func DeepCopy_authorization_SelfSubjectAccessReview(in interface{}, out interfac in := in.(*SelfSubjectAccessReview) out := out.(*SelfSubjectAccessReview) out.TypeMeta = in.TypeMeta + if err := api.DeepCopy_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } if err := DeepCopy_authorization_SelfSubjectAccessReviewSpec(&in.Spec, &out.Spec, c); err != nil { return err } @@ -120,6 +126,9 @@ func DeepCopy_authorization_SubjectAccessReview(in interface{}, out interface{}, in := in.(*SubjectAccessReview) out := out.(*SubjectAccessReview) out.TypeMeta = in.TypeMeta + if err := api.DeepCopy_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } if err := DeepCopy_authorization_SubjectAccessReviewSpec(&in.Spec, &out.Spec, c); err != nil { return err } @@ -156,12 +165,12 @@ func DeepCopy_authorization_SubjectAccessReviewSpec(in interface{}, out interfac } if in.Extra != nil { in, out := &in.Extra, &out.Extra - *out = make(map[string][]string) + *out = make(map[string]ExtraValue) for key, val := range *in { if newVal, err := c.DeepCopy(&val); err != nil { return err } else { - (*out)[key] = *newVal.(*[]string) + (*out)[key] = *newVal.(*ExtraValue) } } } else { @@ -177,6 +186,7 @@ func DeepCopy_authorization_SubjectAccessReviewStatus(in interface{}, out interf out := out.(*SubjectAccessReviewStatus) out.Allowed = in.Allowed out.Reason = in.Reason + out.EvaluationError = in.EvaluationError return nil } } diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_generated_expansion.go new file mode 100644 index 00000000000..54eed3652d4 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/fake/fake_generated_expansion.go @@ -0,0 +1,28 @@ +/* +Copyright 2016 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 fake + +import ( + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" + + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeSubjectAccessReviews) Create(sar *authorizationapi.SubjectAccessReview) (result *authorizationapi.SubjectAccessReview, err error) { + obj, err := c.Fake.Invokes(core.NewRootCreateAction(authorizationapi.SchemeGroupVersion.WithResource("subjectaccessreviews"), sar), &authorizationapi.SubjectAccessReview{}) + return obj.(*authorizationapi.SubjectAccessReview), err +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/generated_expansion.go new file mode 100644 index 00000000000..16a170ece21 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned/generated_expansion.go @@ -0,0 +1,36 @@ +/* +Copyright 2016 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 unversioned + +import ( + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" +) + +// The PodExpansion interface allows manually adding extra methods to the PodInterface. +type SubjectAccessReviewExpansion interface { + Create(sar *authorizationapi.SubjectAccessReview) (result *authorizationapi.SubjectAccessReview, err error) +} + +func (c *subjectAccessReviews) Create(sar *authorizationapi.SubjectAccessReview) (result *authorizationapi.SubjectAccessReview, err error) { + result = &authorizationapi.SubjectAccessReview{} + err = c.client.Post(). + Resource("subjectaccessreviews"). + Body(sar). + Do(). + Into(result) + return +} diff --git a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_generated_expansion.go b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_generated_expansion.go new file mode 100644 index 00000000000..88df166ef58 --- /dev/null +++ b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/fake/fake_generated_expansion.go @@ -0,0 +1,28 @@ +/* +Copyright 2016 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 fake + +import ( + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization/v1beta1" + + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeSubjectAccessReviews) Create(sar *authorizationapi.SubjectAccessReview) (result *authorizationapi.SubjectAccessReview, err error) { + obj, err := c.Fake.Invokes(core.NewRootCreateAction(authorizationapi.SchemeGroupVersion.WithResource("subjectaccessreviews"), sar), &authorizationapi.SubjectAccessReview{}) + return obj.(*authorizationapi.SubjectAccessReview), err +} diff --git a/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/generated_expansion.go b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/generated_expansion.go new file mode 100644 index 00000000000..ce0a6a929f9 --- /dev/null +++ b/pkg/client/clientset_generated/release_1_4/typed/authorization/v1beta1/generated_expansion.go @@ -0,0 +1,36 @@ +/* +Copyright 2016 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 v1beta1 + +import ( + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization/v1beta1" +) + +// The PodExpansion interface allows manually adding extra methods to the PodInterface. +type SubjectAccessReviewExpansion interface { + Create(sar *authorizationapi.SubjectAccessReview) (result *authorizationapi.SubjectAccessReview, err error) +} + +func (c *subjectAccessReviews) Create(sar *authorizationapi.SubjectAccessReview) (result *authorizationapi.SubjectAccessReview, err error) { + result = &authorizationapi.SubjectAccessReview{} + err = c.client.Post(). + Resource("subjectaccessreviews"). + Body(sar). + Do(). + Into(result) + return +} diff --git a/pkg/client/unversioned/adapters/internalclientset/clientset_adaption.go b/pkg/client/unversioned/adapters/internalclientset/clientset_adaption.go index 1b2262b0932..5b01b4c053d 100644 --- a/pkg/client/unversioned/adapters/internalclientset/clientset_adaption.go +++ b/pkg/client/unversioned/adapters/internalclientset/clientset_adaption.go @@ -19,6 +19,7 @@ package internalclientset import ( "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" unversionedauthentication "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authentication/unversioned" + unversionedauthorization "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned" unversionedautoscaling "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/autoscaling/unversioned" unversionedbatch "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/unversioned" unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" @@ -47,6 +48,11 @@ func FromUnversionedClient(c *unversioned.Client) *internalclientset.Clientset { } else { clientset.BatchClient = unversionedbatch.New(nil) } + if c != nil && c.AuthorizationClient != nil { + clientset.AuthorizationClient = unversionedauthorization.New(c.AuthorizationClient.RESTClient) + } else { + clientset.AuthorizationClient = unversionedauthorization.New(nil) + } if c != nil && c.AutoscalingClient != nil { clientset.AutoscalingClient = unversionedautoscaling.New(c.AutoscalingClient.RESTClient) } else { diff --git a/pkg/client/unversioned/authorization.go b/pkg/client/unversioned/authorization.go new file mode 100644 index 00000000000..beba40dca7c --- /dev/null +++ b/pkg/client/unversioned/authorization.go @@ -0,0 +1,77 @@ +/* +Copyright 2016 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 unversioned + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/authorization" + "k8s.io/kubernetes/pkg/client/restclient" +) + +type AuthorizationInterface interface { + SubjectAccessReviewsInterface +} + +// AuthorizationClient is used to interact with Kubernetes authorization features. +type AuthorizationClient struct { + *restclient.RESTClient +} + +func (c *AuthorizationClient) SubjectAccessReviews() SubjectAccessReviewInterface { + return newSubjectAccessReviews(c) +} + +func NewAuthorization(c *restclient.Config) (*AuthorizationClient, error) { + config := *c + if err := setAuthorizationDefaults(&config); err != nil { + return nil, err + } + client, err := restclient.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &AuthorizationClient{client}, nil +} + +func NewAuthorizationOrDie(c *restclient.Config) *AuthorizationClient { + client, err := NewAuthorization(c) + if err != nil { + panic(err) + } + return client +} + +func setAuthorizationDefaults(config *restclient.Config) error { + // if authorization group is not registered, return an error + g, err := registered.Group(authorization.GroupName) + if err != nil { + return err + } + config.APIPath = defaultAPIPath + if config.UserAgent == "" { + config.UserAgent = restclient.DefaultKubernetesUserAgent() + } + // TODO: Unconditionally set the config.Version, until we fix the config. + //if config.Version == "" { + copyGroupVersion := g.GroupVersion + config.GroupVersion = ©GroupVersion + //} + + config.NegotiatedSerializer = api.Codecs + return nil +} diff --git a/pkg/client/unversioned/client.go b/pkg/client/unversioned/client.go index c87c7ba2bb9..09d0515b1a3 100644 --- a/pkg/client/unversioned/client.go +++ b/pkg/client/unversioned/client.go @@ -45,6 +45,7 @@ type Interface interface { ComponentStatusesInterface ConfigMapsNamespacer Apps() AppsInterface + Authorization() AuthorizationInterface Autoscaling() AutoscalingInterface Authentication() AuthenticationInterface Batch() BatchInterface @@ -120,6 +121,7 @@ func (c *Client) ConfigMaps(namespace string) ConfigMapsInterface { // Client is the implementation of a Kubernetes client. type Client struct { *restclient.RESTClient + *AuthorizationClient *AutoscalingClient *AuthenticationClient *BatchClient @@ -153,6 +155,10 @@ func IsTimeout(err error) bool { return false } +func (c *Client) Authorization() AuthorizationInterface { + return c.AuthorizationClient +} + func (c *Client) Autoscaling() AutoscalingInterface { return c.AutoscalingClient } diff --git a/pkg/client/unversioned/helper.go b/pkg/client/unversioned/helper.go index e0af69e294e..2d5caf32207 100644 --- a/pkg/client/unversioned/helper.go +++ b/pkg/client/unversioned/helper.go @@ -24,6 +24,7 @@ import ( "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/authentication" + "k8s.io/kubernetes/pkg/apis/authorization" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/certificates" @@ -63,6 +64,15 @@ func New(c *restclient.Config) (*Client, error) { return nil, err } + var authorizationClient *AuthorizationClient + if registered.IsRegistered(authorization.GroupName) { + authorizationConfig := *c + authorizationClient, err = NewAuthorization(&authorizationConfig) + if err != nil { + return nil, err + } + } + var autoscalingClient *AutoscalingClient if registered.IsRegistered(autoscaling.GroupName) { autoscalingConfig := *c @@ -137,6 +147,7 @@ func New(c *restclient.Config) (*Client, error) { RESTClient: client, AppsClient: appsClient, AuthenticationClient: authenticationClient, + AuthorizationClient: authorizationClient, AutoscalingClient: autoscalingClient, BatchClient: batchClient, CertificatesClient: certsClient, diff --git a/pkg/client/unversioned/subjectaccessreview.go b/pkg/client/unversioned/subjectaccessreview.go new file mode 100644 index 00000000000..b7a2c59e67a --- /dev/null +++ b/pkg/client/unversioned/subjectaccessreview.go @@ -0,0 +1,45 @@ +/* +Copyright 2015 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 unversioned + +import ( + "k8s.io/kubernetes/pkg/apis/authorization" +) + +type SubjectAccessReviewsInterface interface { + SubjectAccessReviews() SubjectAccessReviewInterface +} + +type SubjectAccessReviewInterface interface { + Create(subjectAccessReview *authorization.SubjectAccessReview) (*authorization.SubjectAccessReview, error) +} + +type subjectAccessReviews struct { + client *AuthorizationClient +} + +func newSubjectAccessReviews(c *AuthorizationClient) *subjectAccessReviews { + return &subjectAccessReviews{ + client: c, + } +} + +func (c *subjectAccessReviews) Create(subjectAccessReview *authorization.SubjectAccessReview) (result *authorization.SubjectAccessReview, err error) { + result = &authorization.SubjectAccessReview{} + err = c.client.Post().Resource("subjectAccessReviews").Body(subjectAccessReview).Do().Into(result) + return +} diff --git a/pkg/client/unversioned/testclient/fake_subjectaccessreviews.go b/pkg/client/unversioned/testclient/fake_subjectaccessreviews.go new file mode 100644 index 00000000000..54f779dbda2 --- /dev/null +++ b/pkg/client/unversioned/testclient/fake_subjectaccessreviews.go @@ -0,0 +1,36 @@ +/* +Copyright 2015 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 testclient + +import ( + "k8s.io/kubernetes/pkg/apis/authorization" +) + +// FakeSubjectAccessReviews implements SubjectAccessReviewInterface. Meant to be embedded into a struct to get a default +// implementation. This makes faking out just the methods you want to test easier. +type FakeSubjectAccessReviews struct { + Fake *FakeAuthorization +} + +func (c *FakeSubjectAccessReviews) Create(a *authorization.SubjectAccessReview) (*authorization.SubjectAccessReview, error) { + obj, err := c.Fake.Invokes(NewRootCreateAction("subjectaccessreviews", a), a) + if obj == nil { + return nil, err + } + + return obj.(*authorization.SubjectAccessReview), err +} diff --git a/pkg/client/unversioned/testclient/testclient.go b/pkg/client/unversioned/testclient/testclient.go index 3ca670f8641..418a726b7e3 100644 --- a/pkg/client/unversioned/testclient/testclient.go +++ b/pkg/client/unversioned/testclient/testclient.go @@ -281,6 +281,10 @@ func (c *Fake) Apps() client.AppsInterface { return &FakeApps{c} } +func (c *Fake) Authorization() client.AuthorizationInterface { + return &FakeAuthorization{c} +} + func (c *Fake) Autoscaling() client.AutoscalingInterface { return &FakeAutoscaling{c} } @@ -344,6 +348,19 @@ func (c *FakeApps) PetSets(namespace string) client.PetSetInterface { return &FakePetSets{Fake: c, Namespace: namespace} } +// NewSimpleFakeAuthorization returns a client that will respond with the provided objects +func NewSimpleFakeAuthorization(objects ...runtime.Object) *FakeAuthorization { + return &FakeAuthorization{Fake: NewSimpleFake(objects...)} +} + +type FakeAuthorization struct { + *Fake +} + +func (c *FakeAuthorization) SubjectAccessReviews() client.SubjectAccessReviewInterface { + return &FakeSubjectAccessReviews{Fake: c} +} + // NewSimpleFakeAutoscaling returns a client that will respond with the provided objects func NewSimpleFakeAutoscaling(objects ...runtime.Object) *FakeAutoscaling { return &FakeAutoscaling{Fake: NewSimpleFake(objects...)} diff --git a/pkg/controller/garbagecollector/garbagecollector.go b/pkg/controller/garbagecollector/garbagecollector.go index 3c2857380ed..55a3bf3b513 100644 --- a/pkg/controller/garbagecollector/garbagecollector.go +++ b/pkg/controller/garbagecollector/garbagecollector.go @@ -503,11 +503,12 @@ func monitorFor(p *Propagator, clientPool dynamic.ClientPool, resource unversion } var ignoredResources = map[unversioned.GroupVersionResource]struct{}{ - unversioned.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "replicationcontrollers"}: {}, - unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "bindings"}: {}, - unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "componentstatuses"}: {}, - unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "events"}: {}, - unversioned.GroupVersionResource{Group: "authentication.k8s.io", Version: "v1beta1", Resource: "tokenreviews"}: {}, + unversioned.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "replicationcontrollers"}: {}, + unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "bindings"}: {}, + unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "componentstatuses"}: {}, + unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "events"}: {}, + unversioned.GroupVersionResource{Group: "authentication.k8s.io", Version: "v1beta1", Resource: "tokenreviews"}: {}, + unversioned.GroupVersionResource{Group: "authorization.k8s.io", Version: "v1beta1", Resource: "subjectaccessreviews"}: {}, } func NewGarbageCollector(clientPool dynamic.ClientPool, resources []unversioned.GroupVersionResource) (*GarbageCollector, error) { diff --git a/pkg/master/master.go b/pkg/master/master.go index 244d21647ee..fc870b9992e 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -35,6 +35,8 @@ import ( "k8s.io/kubernetes/pkg/apimachinery/registered" appsapi "k8s.io/kubernetes/pkg/apis/apps/v1alpha1" authenticationv1beta1 "k8s.io/kubernetes/pkg/apis/authentication/v1beta1" + "k8s.io/kubernetes/pkg/apis/authorization" + authorizationapiv1beta1 "k8s.io/kubernetes/pkg/apis/authorization/v1beta1" "k8s.io/kubernetes/pkg/apis/autoscaling" autoscalingapiv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1" "k8s.io/kubernetes/pkg/apis/batch" @@ -202,6 +204,7 @@ func New(c *Config) (*Master, error) { c.RESTStorageProviders[policy.GroupName] = PolicyRESTStorageProvider{} c.RESTStorageProviders[rbac.GroupName] = RBACRESTStorageProvider{AuthorizerRBACSuperUser: c.AuthorizerRBACSuperUser} c.RESTStorageProviders[authenticationv1beta1.GroupName] = AuthenticationRESTStorageProvider{Authenticator: c.Authenticator} + c.RESTStorageProviders[authorization.GroupName] = AuthorizationRESTStorageProvider{Authorizer: c.Authorizer} m.InstallAPIs(c) // TODO: Attempt clean shutdown? @@ -762,6 +765,7 @@ func DefaultAPIResourceConfigSource() *genericapiserver.ResourceConfig { policyapiv1alpha1.SchemeGroupVersion, rbacapi.SchemeGroupVersion, certificatesapiv1alpha1.SchemeGroupVersion, + authorizationapiv1beta1.SchemeGroupVersion, ) // all extensions resources except these are disabled by default diff --git a/pkg/master/storage_authorization.go b/pkg/master/storage_authorization.go new file mode 100644 index 00000000000..0ddb4f7a50c --- /dev/null +++ b/pkg/master/storage_authorization.go @@ -0,0 +1,58 @@ +/* +Copyright 2016 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 master + +import ( + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/authorization" + authorizationv1beta1 "k8s.io/kubernetes/pkg/apis/authorization/v1beta1" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/genericapiserver" + "k8s.io/kubernetes/pkg/registry/authorization/subjectaccessreview" +) + +type AuthorizationRESTStorageProvider struct { + Authorizer authorizer.Authorizer +} + +var _ RESTStorageProvider = &AuthorizationRESTStorageProvider{} + +func (p AuthorizationRESTStorageProvider) NewRESTStorage(apiResourceConfigSource genericapiserver.APIResourceConfigSource, restOptionsGetter RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { + if p.Authorizer == nil { + return genericapiserver.APIGroupInfo{}, false + } + + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(authorization.GroupName) + + if apiResourceConfigSource.AnyResourcesForVersionEnabled(authorizationv1beta1.SchemeGroupVersion) { + apiGroupInfo.VersionedResourcesStorageMap[authorizationv1beta1.SchemeGroupVersion.Version] = p.v1beta1Storage(apiResourceConfigSource, restOptionsGetter) + apiGroupInfo.GroupMeta.GroupVersion = authorizationv1beta1.SchemeGroupVersion + } + + return apiGroupInfo, true +} + +func (p AuthorizationRESTStorageProvider) v1beta1Storage(apiResourceConfigSource genericapiserver.APIResourceConfigSource, restOptionsGetter RESTOptionsGetter) map[string]rest.Storage { + version := authorizationv1beta1.SchemeGroupVersion + + storage := map[string]rest.Storage{} + if apiResourceConfigSource.ResourceEnabled(version.WithResource("subjectaccessreviews")) { + storage["subjectaccessreviews"] = subjectaccessreview.NewREST(p.Authorizer) + } + + return storage +} diff --git a/pkg/registry/authorization/subjectaccessreview/rest.go b/pkg/registry/authorization/subjectaccessreview/rest.go new file mode 100644 index 00000000000..1c293e5335c --- /dev/null +++ b/pkg/registry/authorization/subjectaccessreview/rest.go @@ -0,0 +1,89 @@ +/* +Copyright 2016 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 subjectaccessreview + +import ( + "fmt" + + kapi "k8s.io/kubernetes/pkg/api" + kapierrors "k8s.io/kubernetes/pkg/api/errors" + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" + authorizationvalidation "k8s.io/kubernetes/pkg/apis/authorization/validation" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/auth/user" + authorizationutil "k8s.io/kubernetes/pkg/registry/authorization/util" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + authorizer authorizer.Authorizer +} + +func NewREST(authorizer authorizer.Authorizer) *REST { + return &REST{authorizer} +} + +func (r *REST) New() runtime.Object { + return &authorizationapi.SubjectAccessReview{} +} + +func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { + subjectAccessReview, ok := obj.(*authorizationapi.SubjectAccessReview) + if !ok { + return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a SubjectAccessReview: %#v", obj)) + } + if errs := authorizationvalidation.ValidateSubjectAccessReview(subjectAccessReview); len(errs) > 0 { + return nil, kapierrors.NewInvalid(authorizationapi.Kind(subjectAccessReview.Kind), "", errs) + } + + userToCheck := &user.DefaultInfo{ + Name: subjectAccessReview.Spec.User, + Groups: subjectAccessReview.Spec.Groups, + Extra: convertToUserInfoExtra(subjectAccessReview.Spec.Extra), + } + + var authorizationAttributes authorizer.AttributesRecord + if subjectAccessReview.Spec.ResourceAttributes != nil { + authorizationAttributes = authorizationutil.ResourceAttributesFrom(userToCheck, *subjectAccessReview.Spec.ResourceAttributes) + } else { + authorizationAttributes = authorizationutil.NonResourceAttributesFrom(userToCheck, *subjectAccessReview.Spec.NonResourceAttributes) + } + + allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) + + subjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{ + Allowed: allowed, + Reason: reason, + } + if evaluationErr != nil { + subjectAccessReview.Status.EvaluationError = evaluationErr.Error() + } + + return subjectAccessReview, nil +} + +func convertToUserInfoExtra(extra map[string]authorizationapi.ExtraValue) map[string][]string { + if extra == nil { + return nil + } + ret := map[string][]string{} + for k, v := range extra { + ret[k] = []string(v) + } + + return ret +} diff --git a/pkg/registry/authorization/util/helpers.go b/pkg/registry/authorization/util/helpers.go new file mode 100644 index 00000000000..45b54a4ab49 --- /dev/null +++ b/pkg/registry/authorization/util/helpers.go @@ -0,0 +1,44 @@ +/* +Copyright 2016 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 util + +import ( + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/auth/user" +) + +// ResourceAttributesFrom combines the API object information and the user.Info from the context to build a full authorizer.AttributesRecord for resource access +func ResourceAttributesFrom(user user.Info, in authorizationapi.ResourceAttributes) authorizer.AttributesRecord { + return authorizer.AttributesRecord{ + User: user, + Verb: in.Verb, + Namespace: in.Namespace, + APIGroup: in.Group, + Resource: in.Resource, + ResourceRequest: true, + } +} + +// NonResourceAttributesFrom combines the API object information and the user.Info from the context to build a full authorizer.AttributesRecord for non resource access +func NonResourceAttributesFrom(user user.Info, in authorizationapi.NonResourceAttributes) authorizer.AttributesRecord { + return authorizer.AttributesRecord{ + User: user, + ResourceRequest: false, + Path: in.Path, + } +} diff --git a/plugin/pkg/auth/authorizer/webhook/webhook.go b/plugin/pkg/auth/authorizer/webhook/webhook.go index 1b4b463e2d4..95645b88dd1 100644 --- a/plugin/pkg/auth/authorizer/webhook/webhook.go +++ b/plugin/pkg/auth/authorizer/webhook/webhook.go @@ -133,7 +133,7 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bo r.Spec = v1beta1.SubjectAccessReviewSpec{ User: user.GetName(), Groups: user.GetGroups(), - Extra: user.GetExtra(), + Extra: convertToSARExtra(user.GetExtra()), } } @@ -186,3 +186,15 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bo } return r.Status.Allowed, r.Status.Reason, nil } + +func convertToSARExtra(extra map[string][]string) map[string]v1beta1.ExtraValue { + if extra == nil { + return nil + } + ret := map[string]v1beta1.ExtraValue{} + for k, v := range extra { + ret[k] = v1beta1.ExtraValue(v) + } + + return ret +} diff --git a/test/fixtures/pkg/kubectl/cmd/create/sar.json b/test/fixtures/pkg/kubectl/cmd/create/sar.json new file mode 100644 index 00000000000..4b2a0c29bd5 --- /dev/null +++ b/test/fixtures/pkg/kubectl/cmd/create/sar.json @@ -0,0 +1,16 @@ +{ + "apiVersion": "authorization.k8s.io/v1beta1", + "kind": "SubjectAccessReview", + "spec": { + "user": "bob", + "groups": [ + "the-group" + ], + "resourceAttributes": { + "namespace": "ns", + "verb": "create", + "group": "autoscaling", + "resource": "horizontalpodautoscalers" + } + } +} \ No newline at end of file diff --git a/test/integration/auth/accessreview_test.go b/test/integration/auth/accessreview_test.go new file mode 100644 index 00000000000..6b76e0fc9d9 --- /dev/null +++ b/test/integration/auth/accessreview_test.go @@ -0,0 +1,159 @@ +// +build integration,!no-etcd + +/* +Copyright 2016 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 auth + +import ( + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/auth/user" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/master" + "k8s.io/kubernetes/plugin/pkg/admission/admit" + "k8s.io/kubernetes/test/integration/framework" +) + +// Inject into master an authorizer that uses user info. +// TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented. +type sarAuthorizer struct{} + +func (sarAuthorizer) Authorize(a authorizer.Attributes) (bool, string, error) { + if a.GetUser().GetName() == "dave" { + return false, "no", errors.New("I'm sorry, Dave") + } + + return true, "you're not dave", nil +} + +func alwaysAlice(req *http.Request) (user.Info, bool, error) { + return &user.DefaultInfo{ + Name: "alice", + }, true, nil +} + +func TestSubjectAccessReview(t *testing.T) { + // Set up a master + var m *master.Master + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + m.Handler.ServeHTTP(w, req) + })) + defer s.Close() + + masterConfig := framework.NewIntegrationTestMasterConfig() + masterConfig.Authenticator = authenticator.RequestFunc(alwaysAlice) + masterConfig.Authorizer = sarAuthorizer{} + masterConfig.AdmissionControl = admit.NewAlwaysAdmit() + m, err := master.New(masterConfig) + if err != nil { + t.Fatalf("error in bringing up the master: %v", err) + } + + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + + tests := []struct { + name string + sar *authorizationapi.SubjectAccessReview + expectedError string + expectedStatus authorizationapi.SubjectAccessReviewStatus + }{ + { + name: "simple allow", + sar: &authorizationapi.SubjectAccessReview{ + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{ + Verb: "list", + Group: api.GroupName, + Version: "v1", + Resource: "pods", + }, + User: "alice", + }, + }, + expectedStatus: authorizationapi.SubjectAccessReviewStatus{ + Allowed: true, + Reason: "you're not dave", + }, + }, + { + name: "simple deny", + sar: &authorizationapi.SubjectAccessReview{ + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{ + Verb: "list", + Group: api.GroupName, + Version: "v1", + Resource: "pods", + }, + User: "dave", + }, + }, + expectedStatus: authorizationapi.SubjectAccessReviewStatus{ + Allowed: false, + Reason: "no", + EvaluationError: "I'm sorry, Dave", + }, + }, + { + name: "simple error", + sar: &authorizationapi.SubjectAccessReview{ + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{ + Verb: "list", + Group: api.GroupName, + Version: "v1", + Resource: "pods", + }, + }, + }, + expectedError: "at least one of user or group must be specified", + }, + } + + for _, test := range tests { + response, err := clientset.Authorization().SubjectAccessReviews().Create(test.sar) + switch { + case err == nil && len(test.expectedError) == 0: + + case err != nil && strings.Contains(err.Error(), test.expectedError): + continue + + case err != nil && len(test.expectedError) != 0: + t.Errorf("%s: unexpected error: %v", test.name, err) + continue + default: + t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err) + continue + } + if response.Status != test.expectedStatus { + t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status) + continue + } + + } + +}