summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKubeEdge Bot <48982446+kubeedge-bot@users.noreply.github.com>2023-07-01 02:31:14 +0800
committerGitHub <noreply@github.com>2023-07-01 02:31:14 +0800
commit817cb6738af7a5bec3d3402303158dfb5b035cb9 (patch)
tree1ba1f8f89c949416385a8bee956fa3882e2afd32
parentMerge pull request #4809 from JiaweiGithub/feat/updateversion (diff)
parentreconcile rules (diff)
downloadkubeedge-817cb6738af7a5bec3d3402303158dfb5b035cb9.tar.gz
Merge pull request #4802 from vincentgoat/rbac-offline-sql
authentication and authorization on the edge side
-rw-r--r--build/crds/policy/policy_v1alpha1_serviceaccountaccess.yaml501
-rw-r--r--cloud/cmd/cloudcore/app/server.go2
-rw-r--r--cloud/pkg/cloudhub/dispatcher/message_dispatcher.go5
-rw-r--r--cloud/pkg/common/client/client.go2
-rw-r--r--cloud/pkg/common/messagelayer/context.go8
-rw-r--r--cloud/pkg/common/modules/modules.go3
-rw-r--r--cloud/pkg/dynamiccontroller/application/application.go115
-rw-r--r--cloud/pkg/edgecontroller/controller/upstream.go18
-rw-r--r--cloud/pkg/policycontroller/manager/reconcile.go620
-rw-r--r--cloud/pkg/policycontroller/manager/reconcile_test.go1781
-rw-r--r--cloud/pkg/policycontroller/policycontroller.go104
-rw-r--r--cloud/pkg/policycontroller/policycontroller_test.go38
-rw-r--r--cloud/pkg/synccontroller/objectsync.go6
-rw-r--r--common/constants/default.go11
-rw-r--r--common/types/types.go7
-rw-r--r--edge/pkg/edged/kubeclientbridge/typed/core/v1/serviceaccount_bridge.go5
-rw-r--r--edge/pkg/metamanager/client/metaclient.go17
-rw-r--r--edge/pkg/metamanager/client/nodestatus.go1
-rw-r--r--edge/pkg/metamanager/client/pod.go4
-rw-r--r--edge/pkg/metamanager/client/podstatus.go1
-rw-r--r--edge/pkg/metamanager/client/secret.go4
-rw-r--r--edge/pkg/metamanager/client/serviceaccount.go83
-rw-r--r--edge/pkg/metamanager/client/serviceaccountaccess.go189
-rw-r--r--edge/pkg/metamanager/constants/constants.go9
-rw-r--r--edge/pkg/metamanager/metaserver/auth/auth.go125
-rw-r--r--edge/pkg/metamanager/metaserver/auth/auth_test.go418
-rw-r--r--edge/pkg/metamanager/metaserver/auth/claims.go169
-rw-r--r--edge/pkg/metamanager/metaserver/auth/claims_test.go222
-rw-r--r--edge/pkg/metamanager/metaserver/config/config.go3
-rw-r--r--edge/pkg/metamanager/metaserver/kubernetes/storage/storage.go17
-rw-r--r--edge/pkg/metamanager/metaserver/server.go55
-rw-r--r--edge/pkg/metamanager/process.go332
-rw-r--r--edge/pkg/metamanager/process_test.go333
-rw-r--r--go.mod2
-rwxr-xr-xhack/generate-crds.sh6
-rwxr-xr-xhack/local-up-kubeedge.sh6
-rwxr-xr-xhack/update-codegen.sh2
-rw-r--r--manifests/charts/cloudcore/crds/policy_v1alpha1_serviceaccountaccess.yaml501
-rw-r--r--pkg/apis/componentconfig/edgecore/v1alpha2/default.go11
-rw-r--r--pkg/apis/componentconfig/edgecore/v1alpha2/types.go13
-rw-r--r--pkg/apis/policy/v1alpha1/doc.go21
-rw-r--r--pkg/apis/policy/v1alpha1/register.go61
-rw-r--r--pkg/apis/policy/v1alpha1/serviceaccountaccess_types.go88
-rw-r--r--pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go188
-rw-r--r--pkg/client/clientset/versioned/clientset.go13
-rw-r--r--pkg/client/clientset/versioned/fake/clientset_generated.go7
-rw-r--r--pkg/client/clientset/versioned/fake/register.go2
-rw-r--r--pkg/client/clientset/versioned/scheme/register.go2
-rw-r--r--pkg/client/clientset/versioned/typed/policy/v1alpha1/doc.go20
-rw-r--r--pkg/client/clientset/versioned/typed/policy/v1alpha1/fake/doc.go20
-rw-r--r--pkg/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_policy_client.go40
-rw-r--r--pkg/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_serviceaccountaccess.go142
-rw-r--r--pkg/client/clientset/versioned/typed/policy/v1alpha1/generated_expansion.go21
-rw-r--r--pkg/client/clientset/versioned/typed/policy/v1alpha1/policy_client.go107
-rw-r--r--pkg/client/clientset/versioned/typed/policy/v1alpha1/serviceaccountaccess.go195
-rw-r--r--pkg/client/informers/externalversions/factory.go6
-rw-r--r--pkg/client/informers/externalversions/generic.go5
-rw-r--r--pkg/client/informers/externalversions/policy/interface.go46
-rw-r--r--pkg/client/informers/externalversions/policy/v1alpha1/interface.go45
-rw-r--r--pkg/client/informers/externalversions/policy/v1alpha1/serviceaccountaccess.go90
-rw-r--r--pkg/client/listers/policy/v1alpha1/expansion_generated.go27
-rw-r--r--pkg/client/listers/policy/v1alpha1/serviceaccountaccess.go99
-rw-r--r--pkg/metaserver/application.go9
-rw-r--r--staging/src/github.com/kubeedge/beehive/pkg/core/model/message.go1
-rw-r--r--vendor/k8s.io/component-helpers/auth/rbac/validation/policy_comparator.go173
-rw-r--r--vendor/k8s.io/kubernetes/pkg/registry/rbac/validation/internal_version_adapter.go39
-rw-r--r--vendor/k8s.io/kubernetes/pkg/registry/rbac/validation/policy_compact.go89
-rw-r--r--vendor/k8s.io/kubernetes/pkg/registry/rbac/validation/rule.go368
-rw-r--r--vendor/k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/rbac.go225
-rw-r--r--vendor/k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/subject_locator.go123
-rw-r--r--vendor/modules.txt4
-rw-r--r--vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go765
-rw-r--r--vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/doc.go39
73 files changed, 8379 insertions, 450 deletions
diff --git a/build/crds/policy/policy_v1alpha1_serviceaccountaccess.yaml b/build/crds/policy/policy_v1alpha1_serviceaccountaccess.yaml
new file mode 100644
index 000000000..a6a2c27e0
--- /dev/null
+++ b/build/crds/policy/policy_v1alpha1_serviceaccountaccess.yaml
@@ -0,0 +1,501 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.6.2
+ creationTimestamp: null
+ name: serviceaccountaccesses.policy.kubeedge.io
+spec:
+ group: policy.kubeedge.io
+ names:
+ kind: ServiceAccountAccess
+ listKind: ServiceAccountAccessList
+ plural: serviceaccountaccesses
+ shortNames:
+ - saaccess
+ singular: serviceaccountaccess
+ scope: Namespaced
+ versions:
+ - name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: ServiceAccountAccess is the Schema for the ServiceAccountAccess
+ API
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this representation
+ of an object. Servers should convert recognized schemas to the latest
+ internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST resource this
+ object represents. Servers may infer this from the endpoint the client
+ submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: Spec represents the specification of rbac.
+ properties:
+ accessClusterRoleBinding:
+ description: AccessClusterRoleBinding represents rbac ClusterRoleBinding
+ plus detailed ClusterRole info.
+ items:
+ description: AccessClusterRoleBinding represents rbac ClusterRoleBinding
+ plus detailed ClusterRole info.
+ properties:
+ clusterRoleBinding:
+ description: ClusterRoleBinding represents rbac ClusterRoleBinding.
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of
+ this representation of an object. Servers should convert
+ recognized schemas to the latest internal value, and may
+ reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST
+ resource this object represents. Servers may infer this
+ from the endpoint the client submits requests to. Cannot
+ be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ description: Standard object's metadata.
+ properties:
+ annotations:
+ additionalProperties:
+ type: string
+ type: object
+ finalizers:
+ items:
+ type: string
+ type: array
+ labels:
+ additionalProperties:
+ type: string
+ type: object
+ name:
+ type: string
+ namespace:
+ type: string
+ type: object
+ roleRef:
+ description: RoleRef can only reference a ClusterRole in
+ the global namespace. If the RoleRef cannot be resolved,
+ the Authorizer must return an error.
+ properties:
+ apiGroup:
+ description: APIGroup is the group for the resource
+ being referenced
+ type: string
+ kind:
+ description: Kind is the type of resource being referenced
+ type: string
+ name:
+ description: Name is the name of resource being referenced
+ type: string
+ required:
+ - apiGroup
+ - kind
+ - name
+ type: object
+ subjects:
+ description: Subjects holds references to the objects the
+ role applies to.
+ items:
+ description: Subject contains a reference to the object
+ or user identities a role binding applies to. This
+ can either hold a direct API object reference, or a
+ value for non-objects such as user and group names.
+ properties:
+ apiGroup:
+ description: APIGroup holds the API group of the referenced
+ subject. Defaults to "" for ServiceAccount subjects.
+ Defaults to "rbac.authorization.k8s.io" for User
+ and Group subjects.
+ type: string
+ kind:
+ description: Kind of object being referenced. Values
+ defined by this API group are "User", "Group", and
+ "ServiceAccount". If the Authorizer does not recognized
+ the kind value, the Authorizer should report an
+ error.
+ type: string
+ name:
+ description: Name of the object being referenced.
+ type: string
+ namespace:
+ description: Namespace of the referenced object. If
+ the object kind is non-namespace, such as "User"
+ or "Group", and this value is not empty the Authorizer
+ should report an error.
+ type: string
+ required:
+ - kind
+ - name
+ type: object
+ type: array
+ required:
+ - roleRef
+ type: object
+ rules:
+ description: Rules contains role rules.
+ items:
+ description: PolicyRule holds information that describes a
+ policy rule, but does not contain information about who
+ the rule applies to or which namespace the rule applies
+ to.
+ properties:
+ apiGroups:
+ description: APIGroups is the name of the APIGroup that
+ contains the resources. If multiple API groups are
+ specified, any action requested against one of the enumerated
+ resources in any API group will be allowed.
+ items:
+ type: string
+ type: array
+ nonResourceURLs:
+ description: NonResourceURLs is a set of partial urls
+ that a user should have access to. *s are allowed,
+ but only as the full, final step in the path Since non-resource
+ URLs are not namespaced, this field is only applicable
+ for ClusterRoles referenced from a ClusterRoleBinding.
+ Rules can either apply to API resources (such as "pods"
+ or "secrets") or non-resource URL paths (such as "/api"), but
+ not both.
+ items:
+ type: string
+ type: array
+ resourceNames:
+ description: ResourceNames is an optional white list of
+ names that the rule applies to. An empty set means
+ that everything is allowed.
+ items:
+ type: string
+ type: array
+ resources:
+ description: Resources is a list of resources this rule
+ applies to. '*' represents all resources.
+ items:
+ type: string
+ type: array
+ verbs:
+ description: Verbs is a list of Verbs that apply to ALL
+ the ResourceKinds contained in this rule. '*' represents
+ all verbs.
+ items:
+ type: string
+ type: array
+ required:
+ - verbs
+ type: object
+ type: array
+ type: object
+ type: array
+ accessRoleBinding:
+ description: AccessRoleBinding represents rbac rolebinding plus detailed
+ role info.
+ items:
+ description: AccessRoleBinding represents rbac rolebinding plus
+ detailed role info.
+ properties:
+ roleBinding:
+ description: RoleBinding represents rbac rolebinding.
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of
+ this representation of an object. Servers should convert
+ recognized schemas to the latest internal value, and may
+ reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST
+ resource this object represents. Servers may infer this
+ from the endpoint the client submits requests to. Cannot
+ be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ description: Standard object's metadata.
+ properties:
+ annotations:
+ additionalProperties:
+ type: string
+ type: object
+ finalizers:
+ items:
+ type: string
+ type: array
+ labels:
+ additionalProperties:
+ type: string
+ type: object
+ name:
+ type: string
+ namespace:
+ type: string
+ type: object
+ roleRef:
+ description: RoleRef can reference a Role in the current
+ namespace or a ClusterRole in the global namespace. If
+ the RoleRef cannot be resolved, the Authorizer must return
+ an error.
+ properties:
+ apiGroup:
+ description: APIGroup is the group for the resource
+ being referenced
+ type: string
+ kind:
+ description: Kind is the type of resource being referenced
+ type: string
+ name:
+ description: Name is the name of resource being referenced
+ type: string
+ required:
+ - apiGroup
+ - kind
+ - name
+ type: object
+ subjects:
+ description: Subjects holds references to the objects the
+ role applies to.
+ items:
+ description: Subject contains a reference to the object
+ or user identities a role binding applies to. This
+ can either hold a direct API object reference, or a
+ value for non-objects such as user and group names.
+ properties:
+ apiGroup:
+ description: APIGroup holds the API group of the referenced
+ subject. Defaults to "" for ServiceAccount subjects.
+ Defaults to "rbac.authorization.k8s.io" for User
+ and Group subjects.
+ type: string
+ kind:
+ description: Kind of object being referenced. Values
+ defined by this API group are "User", "Group", and
+ "ServiceAccount". If the Authorizer does not recognized
+ the kind value, the Authorizer should report an
+ error.
+ type: string
+ name:
+ description: Name of the object being referenced.
+ type: string
+ namespace:
+ description: Namespace of the referenced object. If
+ the object kind is non-namespace, such as "User"
+ or "Group", and this value is not empty the Authorizer
+ should report an error.
+ type: string
+ required:
+ - kind
+ - name
+ type: object
+ type: array
+ required:
+ - roleRef
+ type: object
+ rules:
+ description: Rules contains role rules.
+ items:
+ description: PolicyRule holds information that describes a
+ policy rule, but does not contain information about who
+ the rule applies to or which namespace the rule applies
+ to.
+ properties:
+ apiGroups:
+ description: APIGroups is the name of the APIGroup that
+ contains the resources. If multiple API groups are
+ specified, any action requested against one of the enumerated
+ resources in any API group will be allowed.
+ items:
+ type: string
+ type: array
+ nonResourceURLs:
+ description: NonResourceURLs is a set of partial urls
+ that a user should have access to. *s are allowed,
+ but only as the full, final step in the path Since non-resource
+ URLs are not namespaced, this field is only applicable
+ for ClusterRoles referenced from a ClusterRoleBinding.
+ Rules can either apply to API resources (such as "pods"
+ or "secrets") or non-resource URL paths (such as "/api"), but
+ not both.
+ items:
+ type: string
+ type: array
+ resourceNames:
+ description: ResourceNames is an optional white list of
+ names that the rule applies to. An empty set means
+ that everything is allowed.
+ items:
+ type: string
+ type: array
+ resources:
+ description: Resources is a list of resources this rule
+ applies to. '*' represents all resources.
+ items:
+ type: string
+ type: array
+ verbs:
+ description: Verbs is a list of Verbs that apply to ALL
+ the ResourceKinds contained in this rule. '*' represents
+ all verbs.
+ items:
+ type: string
+ type: array
+ required:
+ - verbs
+ type: object
+ type: array
+ type: object
+ type: array
+ serviceAccount:
+ description: ServiceAccount is one-to-one corresponding relations
+ with the serviceaccountaccess.
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this
+ representation of an object. Servers should convert recognized
+ schemas to the latest internal value, and may reject unrecognized
+ values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ automountServiceAccountToken:
+ description: AutomountServiceAccountToken indicates whether pods
+ running as this service account should have an API token automatically
+ mounted. Can be overridden at the pod level.
+ type: boolean
+ imagePullSecrets:
+ description: 'ImagePullSecrets is a list of references to secrets
+ in the same namespace to use for pulling any images in pods
+ that reference this ServiceAccount. ImagePullSecrets are distinct
+ from Secrets because Secrets can be mounted in the pod, but
+ ImagePullSecrets are only accessed by the kubelet. More info:
+ https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod'
+ items:
+ description: LocalObjectReference contains enough information
+ to let you locate the referenced object inside the same namespace.
+ properties:
+ name:
+ description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ TODO: Add other useful fields. apiVersion, kind, uid?'
+ type: string
+ type: object
+ type: array
+ kind:
+ description: 'Kind is a string value representing the REST resource
+ this object represents. Servers may infer this from the endpoint
+ the client submits requests to. Cannot be updated. In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata'
+ properties:
+ annotations:
+ additionalProperties:
+ type: string
+ type: object
+ finalizers:
+ items:
+ type: string
+ type: array
+ labels:
+ additionalProperties:
+ type: string
+ type: object
+ name:
+ type: string
+ namespace:
+ type: string
+ type: object
+ secrets:
+ description: 'Secrets is the list of secrets allowed to be used
+ by pods running using this ServiceAccount. More info: https://kubernetes.io/docs/concepts/configuration/secret'
+ items:
+ description: 'ObjectReference contains enough information to
+ let you inspect or modify the referred object. --- New uses
+ of this type are discouraged because of difficulty describing
+ its usage when embedded in APIs. 1. Ignored fields. It includes
+ many fields which are not generally honored. For instance,
+ ResourceVersion and FieldPath are both very rarely valid in
+ actual usage. 2. Invalid usage help. It is impossible to
+ add specific help for individual usage. In most embedded
+ usages, there are particular restrictions like, "must
+ refer only to types A and B" or "UID not honored" or "name
+ must be restricted". Those cannot be well described when
+ embedded. 3. Inconsistent validation. Because the usages
+ are different, the validation rules are different by usage,
+ which makes it hard for users to predict what will happen. 4.
+ The fields are both imprecise and overly precise. Kind is
+ not a precise mapping to a URL. This can produce ambiguity during
+ interpretation and require a REST mapping. In most cases,
+ the dependency is on the group,resource tuple and the
+ version of the actual struct is irrelevant. 5. We cannot
+ easily change it. Because this type is embedded in many locations,
+ updates to this type will affect numerous schemas. Don''t
+ make new APIs embed an underspecified API type they do not
+ control. Instead of using this type, create a locally provided
+ and used type that is well-focused on your reference. For
+ example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533
+ .'
+ properties:
+ apiVersion:
+ description: API version of the referent.
+ type: string
+ fieldPath:
+ description: 'If referring to a piece of an object instead
+ of an entire object, this string should contain a valid
+ JSON/Go field access statement, such as desiredState.manifest.containers[2].
+ For example, if the object reference is to a container
+ within a pod, this would take on a value like: "spec.containers{name}"
+ (where "name" refers to the name of the container that
+ triggered the event) or if no container name is specified
+ "spec.containers[2]" (container with index 2 in this pod).
+ This syntax is chosen only to have some well-defined way
+ of referencing a part of an object. TODO: this design
+ is not final and this field is subject to change in the
+ future.'
+ type: string
+ kind:
+ description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ name:
+ description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
+ type: string
+ namespace:
+ description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
+ type: string
+ resourceVersion:
+ description: 'Specific resourceVersion to which this reference
+ is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
+ type: string
+ uid:
+ description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
+ type: string
+ type: object
+ type: array
+ type: object
+ serviceAccountUid:
+ description: ServiceAccountUID is the uid of serviceaccount.
+ type: string
+ type: object
+ status:
+ description: Status represents the node list which store the rules.
+ properties:
+ nodeList:
+ description: NodeList represents the node name which store the rules.
+ items:
+ type: string
+ type: array
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
+status:
+ acceptedNames:
+ kind: ""
+ plural: ""
+ conditions: []
+ storedVersions: []
diff --git a/cloud/cmd/cloudcore/app/server.go b/cloud/cmd/cloudcore/app/server.go
index 68da4ae06..e42f6cf3b 100644
--- a/cloud/cmd/cloudcore/app/server.go
+++ b/cloud/cmd/cloudcore/app/server.go
@@ -49,6 +49,7 @@ import (
"github.com/kubeedge/kubeedge/cloud/pkg/dynamiccontroller"
"github.com/kubeedge/kubeedge/cloud/pkg/edgecontroller"
"github.com/kubeedge/kubeedge/cloud/pkg/nodeupgradejobcontroller"
+ "github.com/kubeedge/kubeedge/cloud/pkg/policycontroller"
"github.com/kubeedge/kubeedge/cloud/pkg/router"
"github.com/kubeedge/kubeedge/cloud/pkg/synccontroller"
"github.com/kubeedge/kubeedge/common/constants"
@@ -162,6 +163,7 @@ func registerModules(c *v1alpha1.CloudCoreConfig) {
cloudstream.Register(c.Modules.CloudStream, c.CommonConfig)
router.Register(c.Modules.Router)
dynamiccontroller.Register(c.Modules.DynamicController)
+ policycontroller.Register(client.CrdConfig)
}
func NegotiateTunnelPort() (*int, error) {
diff --git a/cloud/pkg/cloudhub/dispatcher/message_dispatcher.go b/cloud/pkg/cloudhub/dispatcher/message_dispatcher.go
index 1f63c975f..408f05afe 100644
--- a/cloud/pkg/cloudhub/dispatcher/message_dispatcher.go
+++ b/cloud/pkg/cloudhub/dispatcher/message_dispatcher.go
@@ -175,6 +175,9 @@ func (md *messageDispatcher) DispatchUpstream(message *beehivemodel.Message, inf
klog.Errorf("node %s receive message ack err: %v", info.NodeID, err)
}
+ case message.GetOperation() == beehivemodel.ResponseErrorOperation:
+ klog.Errorf("node %s receive message %s error response: %v", info.NodeID, message.GetID(), message.GetContent())
+
case message.GetOperation() == beehivemodel.UploadOperation && message.GetGroup() == modules.UserGroup:
message.Router.Resource = fmt.Sprintf("node/%s/%s", info.NodeID, message.Router.Resource)
beehivecontext.Send(modules.RouterModuleName, *message)
@@ -219,7 +222,7 @@ func (md *messageDispatcher) enqueueAckMessage(nodeID string, msg *beehivemodel.
messageKey, err := common.AckMessageKeyFunc(msg)
if err != nil {
- klog.Errorf("fail to get key for message: %s", msg.String())
+ klog.Errorf("fail to get key for message: %s, err: %v", msg.String(), err)
return
}
diff --git a/cloud/pkg/common/client/client.go b/cloud/pkg/common/client/client.go
index a3daba4d5..4fc9e32bf 100644
--- a/cloud/pkg/common/client/client.go
+++ b/cloud/pkg/common/client/client.go
@@ -43,6 +43,7 @@ var (
authKubeConfig *rest.Config
KubeConfig *rest.Config
+ CrdConfig *rest.Config
)
func InitKubeEdgeClient(config *cloudcoreConfig.KubeAPIConfig) {
@@ -64,6 +65,7 @@ func InitKubeEdgeClient(config *cloudcoreConfig.KubeAPIConfig) {
crdKubeConfig := rest.CopyConfig(kubeConfig)
crdKubeConfig.ContentType = runtime.ContentTypeJSON
+ CrdConfig = crdKubeConfig
crdClient = crdClientset.NewForConfigOrDie(crdKubeConfig)
authKubeConfig, err = clientcmd.BuildConfigFromFlags(kubeConfig.Host, "")
diff --git a/cloud/pkg/common/messagelayer/context.go b/cloud/pkg/common/messagelayer/context.go
index 21cc57009..7aeb2afdc 100644
--- a/cloud/pkg/common/messagelayer/context.go
+++ b/cloud/pkg/common/messagelayer/context.go
@@ -103,3 +103,11 @@ func NodeUpgradeJobControllerMessageLayer() MessageLayer {
ResponseModuleName: modules.CloudHubModuleName,
}
}
+
+func PolicyControllerMessageLayer() MessageLayer {
+ return &ContextMessageLayer{
+ SendModuleName: modules.CloudHubModuleName,
+ ReceiveModuleName: modules.PolicyControllerModuleName,
+ ResponseModuleName: modules.CloudHubModuleName,
+ }
+}
diff --git a/cloud/pkg/common/modules/modules.go b/cloud/pkg/common/modules/modules.go
index 8d9a471e3..76f748251 100644
--- a/cloud/pkg/common/modules/modules.go
+++ b/cloud/pkg/common/modules/modules.go
@@ -7,6 +7,9 @@ const (
EdgeControllerModuleName = "edgecontroller"
EdgeControllerGroupName = "edgecontroller"
+ PolicyControllerModuleName = "policycontroller"
+ PolicyControllerGroupName = "policycontroller"
+
DeviceControllerModuleName = "devicecontroller"
DeviceControllerModuleGroup = "devicecontroller"
diff --git a/cloud/pkg/dynamiccontroller/application/application.go b/cloud/pkg/dynamiccontroller/application/application.go
index 08deca76e..a13882702 100644
--- a/cloud/pkg/dynamiccontroller/application/application.go
+++ b/cloud/pkg/dynamiccontroller/application/application.go
@@ -5,16 +5,12 @@ import (
"fmt"
"strings"
- authorizationv1 "k8s.io/api/authorization/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
- "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/dynamicinformer"
- authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
- "k8s.io/client-go/rest"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
@@ -23,20 +19,19 @@ import (
"github.com/kubeedge/kubeedge/cloud/pkg/common/modules"
"github.com/kubeedge/kubeedge/cloud/pkg/dynamiccontroller/filter"
"github.com/kubeedge/kubeedge/edge/pkg/common/message"
- kefeatures "github.com/kubeedge/kubeedge/pkg/features"
"github.com/kubeedge/kubeedge/pkg/metaserver"
)
type Center struct {
HandlerCenter
messageLayer messagelayer.MessageLayer
- authConfig *rest.Config
+ kubeclient dynamic.Interface
}
func NewApplicationCenter(dynamicSharedInformerFactory dynamicinformer.DynamicSharedInformerFactory) *Center {
a := &Center{
HandlerCenter: NewHandlerCenter(dynamicSharedInformerFactory),
- authConfig: client.GetAuthConfig(),
+ kubeclient: client.GetDynamicClient(),
messageLayer: messagelayer.DynamicControllerMessageLayer(),
}
return a
@@ -70,72 +65,6 @@ func (c *Center) Process(msg model.Message) {
klog.Infof("[metaserver/applicationCenter]successfully to process Application(%+v)", app)
}
-func (c *Center) generateNewConfig(raw string) (*rest.Config, error) {
- parts := strings.SplitN(raw, " ", 3)
- if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" || len(parts[1]) <= 0 {
- return nil, fmt.Errorf("invalid request token format or length: %v", len(parts))
- }
- authConfig := rest.CopyConfig(c.authConfig)
- authConfig.BearerToken = parts[1]
- return authConfig, nil
-}
-
-func (c *Center) createAuthClient(app *metaserver.Application) (authorizationv1client.AuthorizationV1Interface, error) {
- authConfig, err := c.generateNewConfig(app.Token)
- if err != nil {
- return nil, err
- }
- return authorizationv1client.NewForConfigOrDie(authConfig), nil
-}
-
-func (c *Center) createKubeClient(app *metaserver.Application) (dynamic.Interface, error) {
- if !kefeatures.DefaultFeatureGate.Enabled(kefeatures.RequireAuthorization) {
- return client.GetDynamicClient(), nil
- }
- authConfig, err := c.generateNewConfig(app.Token)
- if err != nil {
- return nil, err
- }
- return dynamic.NewForConfigOrDie(authConfig), nil
-}
-
-func (c *Center) authorizeApplication(app *metaserver.Application, gvr schema.GroupVersionResource, namespace string, name string) error {
- if !kefeatures.DefaultFeatureGate.Enabled(kefeatures.RequireAuthorization) {
- return nil
- }
- tmpAuthClient, err := c.createAuthClient(app)
- if err != nil {
- return err
- }
- sar := &authorizationv1.SelfSubjectAccessReview{
- Spec: authorizationv1.SelfSubjectAccessReviewSpec{
- ResourceAttributes: &authorizationv1.ResourceAttributes{
- Namespace: namespace,
- Verb: string(app.Verb),
- Group: gvr.Group,
- Resource: gvr.Resource,
- Name: name,
- Subresource: app.Subresource,
- },
- },
- }
- response, err := tmpAuthClient.SelfSubjectAccessReviews().Create(context.TODO(), sar, metav1.CreateOptions{})
- if err != nil {
- return err
- }
- if response.Status.Allowed {
- return nil
- }
- var errMsg = fmt.Sprintf("resource %v authorize failed.", gvr)
- if len(response.Status.Reason) > 0 {
- errMsg += fmt.Sprintf("reason: %v.", response.Status.Reason)
- }
- if len(response.Status.EvaluationError) > 0 {
- errMsg += fmt.Sprintf("evaluation error: %v.", response.Status.EvaluationError)
- }
- return fmt.Errorf(errMsg)
-}
-
// ProcessApplication processes application by re-translating it to kube-api request with kube client,
// which will be processed and responded by apiserver eventually.
// Specially if app.verb == watch, it transforms app to a listener and register it to HandlerCenter, rather
@@ -144,21 +73,6 @@ func (c *Center) authorizeApplication(app *metaserver.Application, gvr schema.Gr
func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, error) {
app.Status = metaserver.InProcessing
gvr, ns, name := metaserver.ParseKey(app.Key)
- var kubeClient dynamic.Interface
- var err error
- if app.Verb != metaserver.Watch {
- kubeClient, err = c.createKubeClient(app)
- if err != nil {
- klog.Errorf("create kube client error: %v", err)
- return nil, err
- }
- } else {
- err := c.authorizeApplication(app, gvr, ns, name)
- if err != nil {
- klog.Errorf("authorize application error: %v", err)
- return nil, err
- }
- }
switch app.Verb {
case metaserver.List:
@@ -166,7 +80,7 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
if err := app.OptionTo(option); err != nil {
return nil, err
}
- list, err := kubeClient.Resource(gvr).Namespace(ns).List(context.TODO(), *option)
+ list, err := c.kubeclient.Resource(gvr).Namespace(ns).List(context.TODO(), *option)
if err != nil {
return nil, fmt.Errorf("get current list error: %v", err)
}
@@ -186,7 +100,7 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
if err := app.OptionTo(option); err != nil {
return nil, err
}
- retObj, err := kubeClient.Resource(gvr).Namespace(ns).Get(context.TODO(), name, *option)
+ retObj, err := c.kubeclient.Resource(gvr).Namespace(ns).Get(context.TODO(), name, *option)
if err != nil {
return nil, err
}
@@ -203,9 +117,9 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
var retObj interface{}
var err error
if app.Subresource == "" {
- retObj, err = kubeClient.Resource(gvr).Namespace(ns).Create(context.TODO(), obj, *option)
+ retObj, err = c.kubeclient.Resource(gvr).Namespace(ns).Create(context.TODO(), obj, *option)
} else {
- retObj, err = kubeClient.Resource(gvr).Namespace(ns).Create(context.TODO(), obj, *option, app.Subresource)
+ retObj, err = c.kubeclient.Resource(gvr).Namespace(ns).Create(context.TODO(), obj, *option, app.Subresource)
}
if err != nil {
return nil, err
@@ -216,7 +130,7 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
if err := app.OptionTo(&option); err != nil {
return nil, err
}
- if err := kubeClient.Resource(gvr).Namespace(ns).Delete(context.TODO(), name, *option); err != nil {
+ if err := c.kubeclient.Resource(gvr).Namespace(ns).Delete(context.TODO(), name, *option); err != nil {
return nil, err
}
return nil, nil
@@ -232,9 +146,9 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
var retObj interface{}
var err error
if app.Subresource == "" {
- retObj, err = kubeClient.Resource(gvr).Namespace(ns).Update(context.TODO(), obj, *option)
+ retObj, err = c.kubeclient.Resource(gvr).Namespace(ns).Update(context.TODO(), obj, *option)
} else {
- retObj, err = kubeClient.Resource(gvr).Namespace(ns).Update(context.TODO(), obj, *option, app.Subresource)
+ retObj, err = c.kubeclient.Resource(gvr).Namespace(ns).Update(context.TODO(), obj, *option, app.Subresource)
}
if err != nil {
return nil, err
@@ -249,7 +163,7 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
if err := app.ReqBodyTo(obj); err != nil {
return nil, err
}
- retObj, err := kubeClient.Resource(gvr).Namespace(ns).UpdateStatus(context.TODO(), obj, *option)
+ retObj, err := c.kubeclient.Resource(gvr).Namespace(ns).UpdateStatus(context.TODO(), obj, *option)
if err != nil {
return nil, err
}
@@ -259,7 +173,7 @@ func (c *Center) ProcessApplication(app *metaserver.Application) (interface{}, e
if err := app.OptionTo(pi); err != nil {
return nil, err
}
- retObj, err := kubeClient.Resource(gvr).Namespace(ns).Patch(context.TODO(), pi.Name, pi.PatchType, pi.Data, pi.Options, pi.Subresources...)
+ retObj, err := c.kubeclient.Resource(gvr).Namespace(ns).Patch(context.TODO(), pi.Name, pi.PatchType, pi.Data, pi.Options, pi.Subresources...)
if err != nil {
return nil, err
}
@@ -377,13 +291,6 @@ func (c *Center) getWatchDiff(allWatchAppInEdge map[string]metaserver.Applicatio
func (c *Center) processWatchApp(watchApp *metaserver.Application) error {
watchApp.Status = metaserver.InProcessing
- gvr, ns, name := metaserver.ParseKey(watchApp.Key)
-
- err := c.authorizeApplication(watchApp, gvr, ns, name)
- if err != nil {
- return fmt.Errorf("authorize application error: %v", err)
- }
-
listener, err := applicationToListener(watchApp)
if err != nil {
return err
diff --git a/cloud/pkg/edgecontroller/controller/upstream.go b/cloud/pkg/edgecontroller/controller/upstream.go
index 07a133741..338117bcc 100644
--- a/cloud/pkg/edgecontroller/controller/upstream.go
+++ b/cloud/pkg/edgecontroller/controller/upstream.go
@@ -90,12 +90,6 @@ func SortInitContainerStatuses(p *v1.Pod, statuses []v1.ContainerStatus) {
}
}
-// ObjectResp is the object that api-server response
-type ObjectResp struct {
- Object metaV1.Object
- Err error
-}
-
// UpstreamController subscribe messages from edge and sync to k8s api server
type UpstreamController struct {
kubeClient kubernetes.Interface
@@ -848,7 +842,7 @@ func (uc *UpstreamController) registerNode() {
}
resMsg := model.NewMessage(msg.GetID()).
- FillBody(&ObjectResp{Object: resp, Err: err}).
+ FillBody(&edgeapi.ObjectResp{Object: resp, Err: err}).
BuildRouter(modules.EdgeControllerModuleName, constants.GroupResource, msg.GetResource(), model.ResponseOperation)
if err = uc.messageLayer.Response(*resMsg); err != nil {
klog.Warningf("Response message: %s failed, response failed with error: %v", msg.GetID(), err)
@@ -893,7 +887,7 @@ func (uc *UpstreamController) patchNode() {
resMsg := model.NewMessage(msg.GetID()).
SetResourceVersion(node.ResourceVersion).
- FillBody(&ObjectResp{Object: node, Err: err}).
+ FillBody(&edgeapi.ObjectResp{Object: node, Err: err}).
BuildRouter(modules.EdgeControllerModuleName, constants.GroupResource, msg.GetResource(), model.ResponseOperation)
if err = uc.messageLayer.Response(*resMsg); err != nil {
klog.Warningf("Message: %s process failure, response failed with error: %v", msg.GetID(), err)
@@ -1036,7 +1030,7 @@ func (uc *UpstreamController) patchPod() {
resMsg := model.NewMessage(msg.GetID()).
SetResourceVersion(updatedPod.ResourceVersion).
- FillBody(&ObjectResp{Object: updatedPod, Err: err}).
+ FillBody(&edgeapi.ObjectResp{Object: updatedPod, Err: err}).
BuildRouter(modules.EdgeControllerModuleName, constants.GroupResource, msg.GetResource(), model.ResponseOperation)
if err = uc.messageLayer.Response(*resMsg); err != nil {
klog.Errorf("Message: %s process failure, response failed with error: %v", msg.GetID(), err)
@@ -1163,7 +1157,7 @@ func (uc *UpstreamController) createOrUpdateLease() {
}
resMsg := model.NewMessage(msg.GetID()).
- FillBody(&ObjectResp{Object: resp, Err: err}).
+ FillBody(&edgeapi.ObjectResp{Object: resp, Err: err}).
BuildRouter(modules.EdgeControllerModuleName, constants.GroupResource, msg.GetResource(), model.ResponseOperation)
if err = uc.messageLayer.Response(*resMsg); err != nil {
klog.Warningf("Response message: %s failed, response failed with error: %v", msg.GetID(), err)
@@ -1179,7 +1173,7 @@ func (uc *UpstreamController) createOrUpdateLease() {
}
resMsg := model.NewMessage(msg.GetID()).
- FillBody(&ObjectResp{Object: resp, Err: err}).
+ FillBody(&edgeapi.ObjectResp{Object: resp, Err: err}).
BuildRouter(modules.EdgeControllerModuleName, constants.GroupResource, msg.GetResource(), model.ResponseOperation)
if err = uc.messageLayer.Response(*resMsg); err != nil {
klog.Warningf("Response message: %s failed, response failed with error: %v", msg.GetID(), err)
@@ -1220,7 +1214,7 @@ func (uc *UpstreamController) queryLease() {
}
resMsg := model.NewMessage(msg.GetID()).
- FillBody(&ObjectResp{Object: object, Err: err}).
+ FillBody(&edgeapi.ObjectResp{Object: object, Err: err}).
BuildRouter(modules.EdgeControllerModuleName, constants.GroupResource, msg.GetResource(), model.ResponseOperation)
if err = uc.messageLayer.Response(*resMsg); err != nil {
klog.Warningf("Response message: %s failed, response failed with error: %v", msg.GetID(), err)
diff --git a/cloud/pkg/policycontroller/manager/reconcile.go b/cloud/pkg/policycontroller/manager/reconcile.go
new file mode 100644
index 000000000..c0610a184
--- /dev/null
+++ b/cloud/pkg/policycontroller/manager/reconcile.go
@@ -0,0 +1,620 @@
+package controller
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+ "sort"
+
+ corev1 "k8s.io/api/core/v1"
+ rbacv1 "k8s.io/api/rbac/v1"
+ "k8s.io/apimachinery/pkg/api/equality"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/fields"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apiserver/pkg/authentication/serviceaccount"
+ "k8s.io/apiserver/pkg/authentication/user"
+ "k8s.io/klog/v2"
+ controllerruntime "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/builder"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/handler"
+ "sigs.k8s.io/controller-runtime/pkg/predicate"
+ "sigs.k8s.io/controller-runtime/pkg/source"
+
+ "github.com/kubeedge/beehive/pkg/core/model"
+ "github.com/kubeedge/kubeedge/cloud/pkg/common/messagelayer"
+ "github.com/kubeedge/kubeedge/cloud/pkg/common/modules"
+ "github.com/kubeedge/kubeedge/cloud/pkg/edgecontroller/constants"
+ commonconstants "github.com/kubeedge/kubeedge/common/constants"
+ policyv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
+)
+
+type Controller struct {
+ client.Client
+ MessageLayer messagelayer.MessageLayer
+}
+
+func (c *Controller) Reconcile(ctx context.Context, request controllerruntime.Request) (controllerruntime.Result, error) {
+ acc := &policyv1alpha1.ServiceAccountAccess{}
+ if err := c.Client.Get(ctx, request.NamespacedName, acc); err != nil {
+ if apierrors.IsNotFound(err) {
+ return controllerruntime.Result{}, nil
+ }
+ klog.Errorf("failed to get serviceaccountaccess %s/%s, %v", request.Namespace, request.Name, err)
+ return controllerruntime.Result{Requeue: true}, err
+ }
+ if !acc.GetDeletionTimestamp().IsZero() {
+ return controllerruntime.Result{}, nil
+ }
+ return c.syncRules(ctx, acc)
+}
+
+func (c *Controller) filterResource(ctx context.Context, object client.Object) bool {
+ var p = &PolicyMatcher{}
+ matchTarget(ctx, c.Client, object, p.isMatchServiceAccount)
+ klog.V(4).Infof("filter resource %s/%s, %v", object.GetNamespace(), object.GetName(), p.match)
+ return p.match
+}
+
+func isMatchedRoleRef(roleRef rbacv1.RoleRef, bindingNamespace string, object client.Object) bool {
+ if reflect.TypeOf(object).Elem().Name() != roleRef.Kind {
+ return false
+ }
+ if roleRef.Kind == "ClusterRole" {
+ return object.GetName() == roleRef.Name
+ } else if roleRef.Kind == "Role" {
+ return object.GetName() == roleRef.Name && bindingNamespace == object.GetNamespace()
+ }
+ return false
+}
+
+type PolicyMatcher struct {
+ match bool
+}
+
+func (pm *PolicyMatcher) isMatchServiceAccount(acc *policyv1alpha1.ServiceAccountAccess) bool {
+ pm.match = true
+ return false
+}
+
+type PolicyRequestVisitor struct {
+ AuthPolicy []controllerruntime.Request
+}
+
+func (p *PolicyRequestVisitor) matchRuntimeRequest(acc *policyv1alpha1.ServiceAccountAccess) bool {
+ p.AuthPolicy = append(p.AuthPolicy, controllerruntime.Request{NamespacedName: client.ObjectKey{Namespace: acc.Namespace, Name: acc.Name}})
+ return true
+}
+
+func matchTarget(ctx context.Context, cli client.Client, object client.Object, visitor func(*policyv1alpha1.ServiceAccountAccess) bool) {
+ accList := &policyv1alpha1.ServiceAccountAccessList{}
+ if err := cli.List(ctx, accList); err != nil {
+ klog.Errorf("failed to list serviceaccountaccess, %v", err)
+ return
+ }
+
+ crbl := &rbacv1.ClusterRoleBindingList{}
+ if err := cli.List(ctx, crbl); err != nil {
+ klog.Errorf("failed to list clusterrolebindings, %v", err)
+ return
+ }
+
+ for _, am := range accList.Items {
+ userInfo := serviceaccount.UserInfo(am.Spec.ServiceAccount.Namespace, am.Spec.ServiceAccount.Name, string(am.Spec.ServiceAccount.UID))
+ switch obj := object.(type) {
+ case *rbacv1.ClusterRoleBinding:
+ _, applies := appliesTo(userInfo, obj.Subjects, "")
+ if applies && !visitor(&am) {
+ return
+ }
+ case *rbacv1.RoleBinding:
+ _, applies := appliesTo(userInfo, obj.Subjects, obj.Namespace)
+ if applies && !visitor(&am) {
+ return
+ }
+ case *rbacv1.ClusterRole:
+ for _, crb := range crbl.Items {
+ if !isMatchedRoleRef(crb.RoleRef, "", obj) {
+ continue
+ }
+ _, applies := appliesTo(userInfo, crb.Subjects, "")
+ if applies && !visitor(&am) {
+ return
+ }
+ }
+ var roleBindingList = &rbacv1.RoleBindingList{}
+ if err := cli.List(ctx, roleBindingList, &client.ListOptions{Namespace: am.Spec.ServiceAccount.Namespace}); err != nil {
+ klog.Errorf("failed to list rolebindings, %v", err)
+ return
+ }
+ for _, rb := range roleBindingList.Items {
+ if !isMatchedRoleRef(rb.RoleRef, rb.Namespace, obj) {
+ continue
+ }
+ _, applies := appliesTo(userInfo, rb.Subjects, rb.Namespace)
+ if applies && !visitor(&am) {
+ return
+ }
+ }
+ case *rbacv1.Role:
+ var roleBindingList = &rbacv1.RoleBindingList{}
+ if err := cli.List(ctx, roleBindingList, &client.ListOptions{Namespace: am.Spec.ServiceAccount.Namespace}); err != nil {
+ klog.Errorf("failed to list rolebindings, %v", err)
+ return
+ }
+ for _, rb := range roleBindingList.Items {
+ if !isMatchedRoleRef(rb.RoleRef, rb.Namespace, obj) {
+ continue
+ }
+ _, applies := appliesTo(userInfo, rb.Subjects, rb.Namespace)
+ if applies && !visitor(&am) {
+ return
+ }
+ }
+ }
+ }
+}
+
+func (c *Controller) mapRolesFunc(object client.Object) []controllerruntime.Request {
+ var p = PolicyRequestVisitor{}
+ matchTarget(context.Background(), c.Client, object, p.matchRuntimeRequest)
+ klog.V(4).Infof("filter resource %s/%s, %v", object.GetNamespace(), object.GetName(), p.AuthPolicy)
+ return p.AuthPolicy
+}
+
+func newSaAccessObject(sa corev1.ServiceAccount) *policyv1alpha1.ServiceAccountAccess {
+ return &policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: sa.GetName(),
+ Namespace: sa.GetNamespace(),
+ },
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa,
+ },
+ }
+}
+
+func (c *Controller) mapObjectFunc(object client.Object) []controllerruntime.Request {
+ accList := &policyv1alpha1.ServiceAccountAccessList{}
+ if err := c.Client.List(context.Background(), accList, &client.ListOptions{Namespace: object.GetNamespace()}); err != nil {
+ klog.Errorf("failed to list serviceaccountaccess, %v", err)
+ return nil
+ }
+ switch obj := object.(type) {
+ case *corev1.Pod:
+ sa := obj.Spec.ServiceAccountName
+ for _, am := range accList.Items {
+ if am.Spec.ServiceAccount.Name == sa && am.Spec.ServiceAccount.Namespace == object.GetNamespace() {
+ if obj.GetDeletionTimestamp() == nil {
+ // won't reconcile pod update event
+ return []controllerruntime.Request{}
+ }
+ klog.V(4).Infof("reconcile pod deleting %s/%s", obj.Namespace, obj.Name)
+ return []controllerruntime.Request{{NamespacedName: client.ObjectKey{Namespace: am.Namespace, Name: am.Name}}}
+ }
+ }
+ // already deleted serviceaccountaccess
+ if obj.GetDeletionTimestamp() != nil {
+ return []controllerruntime.Request{}
+ }
+ // create serviceaccountaccess if not exist when pod event triggered
+ newSaa := newSaAccessObject(corev1.ServiceAccount{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: sa,
+ Namespace: object.GetNamespace(),
+ },
+ })
+ klog.V(4).Infof("create serviceaccountaccess %s/%s for pod %s/%s", newSaa.Namespace, newSaa.Name, obj.Namespace, obj.Name)
+ if err := c.Client.Create(context.Background(), newSaa); err != nil {
+ klog.Errorf("failed to create serviceaccountaccess, %v", err)
+ return nil
+ }
+ // return empty request to avoid reconcile conflict with serviceaccountaccess resource
+ return []controllerruntime.Request{}
+ case *corev1.ServiceAccount:
+ return []controllerruntime.Request{{NamespacedName: client.ObjectKey{Namespace: object.GetNamespace(), Name: object.GetName()}}}
+ }
+
+ return []controllerruntime.Request{}
+}
+
+func (c *Controller) filterObject(ctx context.Context, object client.Object) bool {
+ switch obj := object.(type) {
+ case *corev1.Pod:
+ node := obj.Spec.NodeName
+ if obj.Spec.ServiceAccountName == "" || node == "" || !isEdgeNode(ctx, c.Client, node) {
+ return false
+ }
+ return true
+ case *corev1.ServiceAccount:
+ accList := &policyv1alpha1.ServiceAccountAccessList{}
+ if err := c.Client.List(ctx, accList, &client.ListOptions{Namespace: object.GetNamespace()}); err != nil {
+ klog.Errorf("failed to list serviceaccountaccess, %v", err)
+ return false
+ }
+ for _, am := range accList.Items {
+ if am.Spec.ServiceAccount.Name == object.GetName() && am.Spec.ServiceAccount.Namespace == object.GetNamespace() {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// SetupWithManager creates a controller and register to controller manager.
+func (c *Controller) SetupWithManager(ctx context.Context, mgr controllerruntime.Manager) error {
+ if err := mgr.GetFieldIndexer().IndexField(ctx, &corev1.Pod{}, "spec.serviceAccountName", func(o client.Object) []string {
+ pod := o.(*corev1.Pod)
+ return []string{pod.Spec.ServiceAccountName}
+ }); err != nil {
+ return fmt.Errorf("failed to set ServiceAccountName field selector for manager, %v", err)
+ }
+ return controllerruntime.NewControllerManagedBy(mgr).
+ For(&policyv1alpha1.ServiceAccountAccess{}).
+ Watches(&source.Kind{Type: &rbacv1.ClusterRoleBinding{}}, handler.EnqueueRequestsFromMapFunc(c.mapRolesFunc), builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
+ return c.filterResource(ctx, object)
+ }))).
+ Watches(&source.Kind{Type: &rbacv1.RoleBinding{}}, handler.EnqueueRequestsFromMapFunc(c.mapRolesFunc), builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
+ return c.filterResource(ctx, object)
+ }))).
+ Watches(&source.Kind{Type: &rbacv1.ClusterRole{}}, handler.EnqueueRequestsFromMapFunc(c.mapRolesFunc), builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
+ return c.filterResource(ctx, object)
+ }))).
+ Watches(&source.Kind{Type: &rbacv1.Role{}}, handler.EnqueueRequestsFromMapFunc(c.mapRolesFunc), builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
+ return c.filterResource(ctx, object)
+ }))).
+ Watches(&source.Kind{Type: &corev1.ServiceAccount{}}, handler.EnqueueRequestsFromMapFunc(c.mapObjectFunc), builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
+ return c.filterObject(ctx, object)
+ }))).
+ Watches(&source.Kind{Type: &corev1.Pod{}}, handler.EnqueueRequestsFromMapFunc(c.mapObjectFunc), builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
+ return c.filterObject(ctx, object)
+ }))).
+ Complete(c)
+}
+
+func isEdgeNode(ctx context.Context, cli client.Client, name string) bool {
+ set := labels.Set{commonconstants.EdgeNodeRoleKey: commonconstants.EdgeNodeRoleValue}
+ selector := labels.SelectorFromSet(set)
+ var edgeNodeList = &corev1.NodeList{}
+ if err := cli.List(ctx, edgeNodeList, &client.ListOptions{LabelSelector: selector}); err != nil {
+ klog.Errorf("failed to list edge nodes, %v", err)
+ return false
+ }
+ for _, node := range edgeNodeList.Items {
+ if node.Name == name {
+ return true
+ }
+ }
+ return false
+}
+
+func getNodeListOfServiceAccountAccess(ctx context.Context, cli client.Client, acc *policyv1alpha1.ServiceAccountAccess) ([]string, error) {
+ var nodeList []string
+ podList := &corev1.PodList{}
+ saNameSelector := fields.OneTermEqualSelector("spec.serviceAccountName", acc.Spec.ServiceAccount.Name)
+ if err := cli.List(ctx, podList, client.MatchingFieldsSelector{Selector: saNameSelector}, &client.ListOptions{Namespace: acc.Namespace}); err != nil {
+ klog.Errorf("failed to list pods through field selector serviceAccountName, %v", err)
+ return nil, err
+ }
+
+ var nodeMap = make(map[string]bool)
+ for _, pod := range podList.Items {
+ if pod.Spec.NodeName == "" {
+ continue
+ }
+ if nodeMap[pod.Spec.NodeName] {
+ continue
+ }
+ if !isEdgeNode(ctx, cli, pod.Spec.NodeName) {
+ continue
+ }
+ nodeMap[pod.Spec.NodeName] = true
+ nodeList = append(nodeList, pod.Spec.NodeName)
+ }
+ return nodeList, nil
+}
+
+func intersectSlice(old, new []string) []string {
+ var intersect = []string{}
+ var oldMap = make(map[string]bool)
+ for _, oldItem := range old {
+ oldMap[oldItem] = true
+ }
+ for _, newItem := range new {
+ if oldMap[newItem] {
+ intersect = append(intersect, newItem)
+ }
+ }
+ return intersect
+}
+
+func subtractSlice(source, subTarget []string) []string {
+ var subtract = []string{}
+ var oldMap = make(map[string]bool)
+ for _, oldItem := range source {
+ oldMap[oldItem] = true
+ }
+ for _, newItem := range subTarget {
+ if !oldMap[newItem] {
+ subtract = append(subtract, newItem)
+ }
+ }
+ return subtract
+}
+
+func (c *Controller) send2Edge(acc *policyv1alpha1.ServiceAccountAccess, targets []string, opr string) {
+ sendObj := acc.DeepCopy()
+ for _, node := range targets {
+ resource, err := messagelayer.BuildResource(node, sendObj.Namespace, model.ResourceTypeSaAccess, sendObj.Name)
+ if err != nil {
+ klog.Warningf("built message resource failed with error: %s", err)
+ continue
+ }
+ // filter out the node list data
+ sendObj.Status.NodeList = []string{}
+ msg := model.NewMessage("").
+ SetResourceVersion(sendObj.ResourceVersion).
+ FillBody(sendObj).BuildRouter(modules.PolicyControllerModuleName, constants.GroupResource, resource, opr)
+ if err := c.MessageLayer.Send(*msg); err != nil {
+ klog.Warningf("send message %s failed with error: %s", resource, err)
+ continue
+ }
+ }
+}
+
+func (c *Controller) syncRules(ctx context.Context, acc *policyv1alpha1.ServiceAccountAccess) (controllerruntime.Result, error) {
+ var newSA = &corev1.ServiceAccount{}
+ err := c.Client.Get(ctx, types.NamespacedName{Namespace: acc.Namespace, Name: acc.Spec.ServiceAccount.Name}, newSA)
+ if (err != nil && apierrors.IsNotFound(err)) || (err == nil && newSA.DeletionTimestamp != nil) {
+ klog.V(4).Infof("serviceaccount %s/%s is removed and delete the policy resource", acc.Namespace, acc.Spec.ServiceAccount.Name)
+ copyObj := acc.DeepCopy()
+ if err := c.Client.Delete(ctx, copyObj); err != nil {
+ klog.Errorf("failed to delete serviceaccountaccess %s/%s, %v", copyObj.Namespace, copyObj.Name, err)
+ return controllerruntime.Result{Requeue: true}, err
+ }
+ c.send2Edge(copyObj, copyObj.Status.NodeList, model.DeleteOperation)
+ return controllerruntime.Result{}, nil
+ } else if err != nil {
+ klog.Errorf("failed to get serviceaccount %s/%s, %v", acc.Namespace, acc.Spec.ServiceAccount.Name, err)
+ return controllerruntime.Result{Requeue: true}, err
+ }
+ userInfo := serviceaccount.UserInfo(newSA.Namespace, newSA.Name, string(newSA.UID))
+ var currentAcc = &policyv1alpha1.ServiceAccountAccess{}
+ c.VisitRulesFor(ctx, userInfo, acc.Namespace, currentAcc)
+ nodes, err := getNodeListOfServiceAccountAccess(ctx, c.Client, acc)
+ if err != nil {
+ klog.Errorf("failed to get node list of serviceaccountaccess %s/%s, %v", acc.Namespace, acc.Name, err)
+ return controllerruntime.Result{Requeue: true}, err
+ }
+ currentAcc.Spec.ServiceAccount = *newSA
+ currentAcc.Spec.ServiceAccountUID = newSA.UID
+ if len(nodes) == 0 && len(acc.Status.NodeList) == 0 {
+ klog.Warningf("no nodes found for serviceaccountaccess %s/%s", acc.Namespace, acc.Name)
+ return controllerruntime.Result{}, nil
+ }
+ deleteNodes := subtractSlice(nodes, acc.Status.NodeList)
+ if len(deleteNodes) != 0 {
+ // no nodes in the current acc status, delete the acc
+ if len(nodes) == 0 {
+ if err = c.Client.Delete(ctx, acc); err != nil {
+ klog.Errorf("failed to delete serviceaccountaccess %s/%s, %v", acc.Namespace, acc.Name, err)
+ return controllerruntime.Result{Requeue: true}, err
+ }
+ klog.V(4).Infof("delete serviceaccountaccess %s/%s", acc.Namespace, acc.Name)
+ c.send2Edge(acc, deleteNodes, model.DeleteOperation)
+ return controllerruntime.Result{}, nil
+ }
+ c.send2Edge(acc, deleteNodes, model.DeleteOperation)
+ }
+ sort.Slice(currentAcc.Spec.AccessRoleBinding, func(i, j int) bool {
+ return currentAcc.Spec.AccessRoleBinding[i].RoleBinding.Name < currentAcc.Spec.AccessRoleBinding[j].RoleBinding.Name
+ })
+ sort.Slice(currentAcc.Spec.AccessClusterRoleBinding, func(i, j int) bool {
+ return currentAcc.Spec.AccessClusterRoleBinding[i].ClusterRoleBinding.Name < currentAcc.Spec.AccessClusterRoleBinding[j].ClusterRoleBinding.Name
+ })
+ sort.Slice(nodes, func(i, j int) bool {
+ return nodes[i] < nodes[j]
+ })
+ if !equalAccessBindingSlice(acc.Spec.AccessClusterRoleBinding, currentAcc.Spec.AccessClusterRoleBinding) ||
+ !equalAccessBindingSlice(acc.Spec.AccessRoleBinding, currentAcc.Spec.AccessRoleBinding) ||
+ !equalServiceAccount(&acc.Spec.ServiceAccount, &currentAcc.Spec.ServiceAccount) ||
+ acc.Spec.ServiceAccountUID != currentAcc.Spec.ServiceAccountUID {
+ acc.Spec = *currentAcc.Spec.DeepCopy()
+ if err := c.Client.Update(ctx, acc); err != nil {
+ klog.Errorf("failed to update serviceaccountaccess %s/%s, %v", acc.Namespace, acc.Name, err)
+ return controllerruntime.Result{Requeue: true}, err
+ }
+ if !equality.Semantic.DeepEqual(acc.Status.NodeList, nodes) {
+ acc.Status.NodeList = append([]string{}, nodes...)
+ if err := c.Client.Status().Update(ctx, acc); err != nil {
+ klog.Errorf("failed to update serviceaccountaccess status %s/%s, %v", acc.Namespace, acc.Name, err)
+ return controllerruntime.Result{Requeue: true}, err
+ }
+ }
+ c.send2Edge(acc, nodes, model.UpdateOperation)
+ } else {
+ addNodes := subtractSlice(acc.Status.NodeList, nodes)
+ klog.V(4).Infof("serviceaccountaccess spec %s/%s is up to date", acc.Namespace, acc.Name)
+ if len(addNodes) != 0 {
+ acc.Status.NodeList = append([]string{}, nodes...)
+ if err := c.Client.Status().Update(ctx, acc); err != nil {
+ klog.Errorf("failed to update serviceaccountaccess status %s/%s, %v", acc.Namespace, acc.Name, err)
+ return controllerruntime.Result{Requeue: true}, err
+ }
+ c.send2Edge(acc, addNodes, model.InsertOperation)
+ }
+ }
+ return controllerruntime.Result{}, nil
+}
+
+func equalAccessBindingSlice(a, b interface{}) bool {
+ aBindings, aOk := a.([]policyv1alpha1.AccessRoleBinding)
+ bBindings, bOk := b.([]policyv1alpha1.AccessRoleBinding)
+ if !aOk || !bOk {
+ aClusterBindings, aClusterOk := a.([]policyv1alpha1.AccessClusterRoleBinding)
+ bClusterBindings, bClusterOk := b.([]policyv1alpha1.AccessClusterRoleBinding)
+ if !aClusterOk || !bClusterOk {
+ return false
+ }
+ if len(aClusterBindings) != len(bClusterBindings) {
+ return false
+ }
+ for i := range aClusterBindings {
+ if aClusterBindings[i].ClusterRoleBinding.Name != bClusterBindings[i].ClusterRoleBinding.Name ||
+ !equality.Semantic.DeepEqual(aClusterBindings[i].Rules, bClusterBindings[i].Rules) ||
+ !equality.Semantic.DeepEqual(aClusterBindings[i].ClusterRoleBinding.Labels, bClusterBindings[i].ClusterRoleBinding.Labels) ||
+ !equality.Semantic.DeepEqual(aClusterBindings[i].ClusterRoleBinding.Annotations, bClusterBindings[i].ClusterRoleBinding.Annotations) ||
+ !equality.Semantic.DeepEqual(aClusterBindings[i].ClusterRoleBinding.Subjects, bClusterBindings[i].ClusterRoleBinding.Subjects) ||
+ !equality.Semantic.DeepEqual(aClusterBindings[i].ClusterRoleBinding.RoleRef, bClusterBindings[i].ClusterRoleBinding.RoleRef) {
+ return false
+ }
+ }
+ } else {
+ if len(aBindings) != len(bBindings) {
+ return false
+ }
+ for i := range aBindings {
+ if aBindings[i].RoleBinding.Name != bBindings[i].RoleBinding.Name ||
+ aBindings[i].RoleBinding.Namespace != bBindings[i].RoleBinding.Namespace ||
+ !equality.Semantic.DeepEqual(aBindings[i].Rules, bBindings[i].Rules) ||
+ !equality.Semantic.DeepEqual(aBindings[i].RoleBinding.Labels, bBindings[i].RoleBinding.Labels) ||
+ !equality.Semantic.DeepEqual(aBindings[i].RoleBinding.Annotations, bBindings[i].RoleBinding.Annotations) ||
+ !equality.Semantic.DeepEqual(aBindings[i].RoleBinding.RoleRef, bBindings[i].RoleBinding.RoleRef) ||
+ !equality.Semantic.DeepEqual(aBindings[i].RoleBinding.Subjects, bBindings[i].RoleBinding.Subjects) {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+func equalServiceAccount(a, b *corev1.ServiceAccount) bool {
+ if a == nil || b == nil {
+ return false
+ }
+ aCopy := a.DeepCopy()
+ bCopy := b.DeepCopy()
+ // ignore metadata because it is not be allowed to update in crd
+ aCopy.ObjectMeta = bCopy.ObjectMeta
+ return equality.Semantic.DeepEqual(aCopy, bCopy)
+}
+
+func appliesToUser(user user.Info, subject rbacv1.Subject, namespace string) bool {
+ switch subject.Kind {
+ case rbacv1.UserKind:
+ return user.GetName() == subject.Name
+
+ case rbacv1.GroupKind:
+ return has(user.GetGroups(), subject.Name)
+
+ case rbacv1.ServiceAccountKind:
+ // default the namespace to namespace we're working in if its available. This allows rolebindings that reference
+ // SAs in th local namespace to avoid having to qualify them.
+ saNamespace := namespace
+ if len(subject.Namespace) > 0 {
+ saNamespace = subject.Namespace
+ }
+ if len(saNamespace) == 0 {
+ return false
+ }
+ // use a more efficient comparison for RBAC checking
+ return serviceaccount.MatchesUsername(saNamespace, subject.Name, user.GetName())
+ default:
+ return false
+ }
+}
+
+// appliesTo returns whether any of the bindingSubjects applies to the specified subject,
+// and if true, the index of the first subject that applies
+func appliesTo(user user.Info, bindingSubjects []rbacv1.Subject, namespace string) (int, bool) {
+ for i, bindingSubject := range bindingSubjects {
+ if appliesToUser(user, bindingSubject, namespace) {
+ return i, true
+ }
+ }
+ return 0, false
+}
+
+func has(set []string, ele string) bool {
+ for _, s := range set {
+ if s == ele {
+ return true
+ }
+ }
+ return false
+}
+
+// GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding.
+func (c *Controller) GetRoleReferenceRules(ctx context.Context, roleRef rbacv1.RoleRef, bindingNamespace string) ([]rbacv1.PolicyRule, error) {
+ switch roleRef.Kind {
+ case "Role":
+ var role = &rbacv1.Role{}
+ err := c.Client.Get(ctx, types.NamespacedName{Namespace: bindingNamespace, Name: roleRef.Name}, role)
+ if err != nil {
+ return nil, err
+ }
+ return role.Rules, nil
+
+ case "ClusterRole":
+ var clusterRole = &rbacv1.ClusterRole{}
+ err := c.Client.Get(ctx, types.NamespacedName{Name: roleRef.Name}, clusterRole)
+ if err != nil {
+ return nil, err
+ }
+ return clusterRole.Rules, nil
+
+ default:
+ return nil, fmt.Errorf("unsupported role reference kind: %q", roleRef.Kind)
+ }
+}
+
+func (c *Controller) VisitRulesFor(ctx context.Context, user user.Info, namespace string, acc *policyv1alpha1.ServiceAccountAccess) {
+ crbl := &rbacv1.ClusterRoleBindingList{}
+ if err := c.Client.List(ctx, crbl); err != nil {
+ klog.Errorf("failed to list clusterrolebindings, %v", err)
+ return
+ }
+ for _, crb := range crbl.Items {
+ _, applies := appliesTo(user, crb.Subjects, "")
+ if !applies {
+ continue
+ }
+ rules, err := c.GetRoleReferenceRules(ctx, crb.RoleRef, "")
+ if err != nil {
+ klog.Errorf("failed to get rules for clusterrolebinding %s, %v", crb.Name, err)
+ return
+ }
+ var accessClusterRoleBinding = policyv1alpha1.AccessClusterRoleBinding{
+ ClusterRoleBinding: crb,
+ Rules: rules,
+ }
+ acc.Spec.AccessClusterRoleBinding = append(acc.Spec.AccessClusterRoleBinding, accessClusterRoleBinding)
+ }
+
+ if len(namespace) > 0 {
+ var roleBindingList = &rbacv1.RoleBindingList{}
+ if err := c.Client.List(ctx, roleBindingList, &client.ListOptions{Namespace: namespace}); err != nil {
+ klog.Errorf("failed to list rolebindings, %v", err)
+ return
+ }
+ for _, roleBinding := range roleBindingList.Items {
+ _, applies := appliesTo(user, roleBinding.Subjects, namespace)
+ if !applies {
+ continue
+ }
+ rules, err := c.GetRoleReferenceRules(ctx, roleBinding.RoleRef, namespace)
+ if err != nil {
+ klog.Errorf("failed to get rules for rolebinding %s, %v", roleBinding.Name, err)
+ return
+ }
+ var accessRoleBinding = policyv1alpha1.AccessRoleBinding{
+ RoleBinding: roleBinding,
+ Rules: rules,
+ }
+ acc.Spec.AccessRoleBinding = append(acc.Spec.AccessRoleBinding, accessRoleBinding)
+ }
+ }
+}
diff --git a/cloud/pkg/policycontroller/manager/reconcile_test.go b/cloud/pkg/policycontroller/manager/reconcile_test.go
new file mode 100644
index 000000000..cee178392
--- /dev/null
+++ b/cloud/pkg/policycontroller/manager/reconcile_test.go
@@ -0,0 +1,1781 @@
+package controller
+
+import (
+ "context"
+ "encoding/json"
+ "reflect"
+ "sort"
+ "sync"
+ "testing"
+ "time"
+
+ v1 "k8s.io/api/core/v1"
+ rbacv1 "k8s.io/api/rbac/v1"
+ "k8s.io/apimachinery/pkg/api/equality"
+ apierror "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apiserver/pkg/authentication/user"
+ controllerruntime "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+ "github.com/kubeedge/beehive/pkg/common"
+ beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
+ "github.com/kubeedge/beehive/pkg/core/model"
+ "github.com/kubeedge/kubeedge/cloud/pkg/common/messagelayer"
+ "github.com/kubeedge/kubeedge/cloud/pkg/common/modules"
+ policyv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
+)
+
+func TestIntersectSlice(t *testing.T) {
+ tests := []struct {
+ name string
+ a []string
+ b []string
+ want []string
+ }{
+ {
+ name: "test1",
+ a: []string{"a", "b", "c"},
+ b: []string{"b", "c", "d"},
+ want: []string{"b", "c"},
+ },
+ {
+ name: "test2",
+ a: []string{"a", "b", "c"},
+ b: []string{"d", "e", "f"},
+ want: []string{},
+ },
+ {
+ name: "test3",
+ a: []string{"a", "b", "c"},
+ b: []string{"a", "b", "c"},
+ want: []string{"a", "b", "c"},
+ },
+ {
+ name: "test4",
+ a: []string{},
+ b: []string{"a", "b", "c"},
+ want: []string{},
+ },
+ {
+ name: "test5",
+ a: []string{"a", "b", "c"},
+ b: []string{},
+ want: []string{},
+ },
+ {
+ name: "test6",
+ a: []string{},
+ b: []string{},
+ want: []string{},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := intersectSlice(tt.a, tt.b); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("intersectSlice() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestSubtractSlice(t *testing.T) {
+ tests := []struct {
+ name string
+ a []string
+ b []string
+ want []string
+ }{
+ {
+ name: "test1",
+ a: []string{"a", "b", "c"},
+ b: []string{"b", "c", "d"},
+ want: []string{"a"},
+ },
+ {
+ name: "test2",
+ a: []string{"a", "b", "c"},
+ b: []string{"d", "e", "f"},
+ want: []string{"a", "b", "c"},
+ },
+ {
+ name: "test3",
+ a: []string{"a", "b", "c"},
+ b: []string{"a", "b", "c"},
+ want: []string{},
+ },
+ {
+ name: "test4",
+ a: []string{},
+ b: []string{"a", "b", "c"},
+ want: []string{},
+ },
+ {
+ name: "test5",
+ a: []string{"a", "b", "c"},
+ b: []string{},
+ want: []string{"a", "b", "c"},
+ },
+ {
+ name: "test6",
+ a: []string{},
+ b: []string{},
+ want: []string{},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := subtractSlice(tt.b, tt.a); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("subtractSlice() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestAppliesTo(t *testing.T) {
+ tests := []struct {
+ subjects []rbacv1.Subject
+ user user.Info
+ namespace string
+ appliesTo bool
+ index int
+ testCase string
+ }{
+ {
+ subjects: []rbacv1.Subject{
+ {Kind: rbacv1.UserKind, Name: "foobar"},
+ },
+ user: &user.DefaultInfo{Name: "foobar"},
+ appliesTo: true,
+ index: 0,
+ testCase: "single subject that matches username",
+ },
+ {
+ subjects: []rbacv1.Subject{
+ {Kind: rbacv1.UserKind, Name: "barfoo"},
+ {Kind: rbacv1.UserKind, Name: "foobar"},
+ },
+ user: &user.DefaultInfo{Name: "foobar"},
+ appliesTo: true,
+ index: 1,
+ testCase: "multiple subjects, one that matches username",
+ },
+ {
+ subjects: []rbacv1.Subject{
+ {Kind: rbacv1.UserKind, Name: "barfoo"},
+ {Kind: rbacv1.UserKind, Name: "foobar"},
+ },
+ user: &user.DefaultInfo{Name: "zimzam"},
+ appliesTo: false,
+ testCase: "multiple subjects, none that match username",
+ },
+ {
+ subjects: []rbacv1.Subject{
+ {Kind: rbacv1.UserKind, Name: "barfoo"},
+ {Kind: rbacv1.GroupKind, Name: "foobar"},
+ },
+ user: &user.DefaultInfo{Name: "zimzam", Groups: []string{"foobar"}},
+ appliesTo: true,
+ index: 1,
+ testCase: "multiple subjects, one that match group",
+ },
+ {
+ subjects: []rbacv1.Subject{
+ {Kind: rbacv1.UserKind, Name: "barfoo"},
+ {Kind: rbacv1.GroupKind, Name: "foobar"},
+ },
+ user: &user.DefaultInfo{Name: "zimzam", Groups: []string{"foobar"}},
+ namespace: "namespace1",
+ appliesTo: true,
+ index: 1,
+ testCase: "multiple subjects, one that match group, should ignore namespace",
+ },
+ {
+ subjects: []rbacv1.Subject{
+ {Kind: rbacv1.UserKind, Name: "barfoo"},
+ {Kind: rbacv1.GroupKind, Name: "foobar"},
+ {Kind: rbacv1.ServiceAccountKind, Namespace: "kube-system", Name: "default"},
+ },
+ user: &user.DefaultInfo{Name: "system:serviceaccount:kube-system:default"},
+ namespace: "default",
+ appliesTo: true,
+ index: 2,
+ testCase: "multiple subjects with a service account that matches",
+ },
+ {
+ subjects: []rbacv1.Subject{
+ {Kind: rbacv1.UserKind, Name: "*"},
+ },
+ user: &user.DefaultInfo{Name: "foobar"},
+ namespace: "default",
+ appliesTo: false,
+ testCase: "* user subject name doesn't match all users",
+ },
+ {
+ subjects: []rbacv1.Subject{
+ {Kind: rbacv1.GroupKind, Name: user.AllAuthenticated},
+ {Kind: rbacv1.GroupKind, Name: user.AllUnauthenticated},
+ },
+ user: &user.DefaultInfo{Name: "foobar", Groups: []string{user.AllAuthenticated}},
+ namespace: "default",
+ appliesTo: true,
+ index: 0,
+ testCase: "binding to all authenticated and unauthenticated subjects matches authenticated user",
+ },
+ {
+ subjects: []rbacv1.Subject{
+ {Kind: rbacv1.GroupKind, Name: user.AllAuthenticated},
+ {Kind: rbacv1.GroupKind, Name: user.AllUnauthenticated},
+ },
+ user: &user.DefaultInfo{Name: "system:anonymous", Groups: []string{user.AllUnauthenticated}},
+ namespace: "default",
+ appliesTo: true,
+ index: 1,
+ testCase: "binding to all authenticated and unauthenticated subjects matches anonymous user",
+ },
+ }
+
+ for _, tc := range tests {
+ gotIndex, got := appliesTo(tc.user, tc.subjects, tc.namespace)
+ if got != tc.appliesTo {
+ t.Errorf("case %q want appliesTo=%t, got appliesTo=%t", tc.testCase, tc.appliesTo, got)
+ }
+ if gotIndex != tc.index {
+ t.Errorf("case %q want index %d, got %d", tc.testCase, tc.index, gotIndex)
+ }
+ }
+}
+
+func newServiceAccount() *v1.ServiceAccount {
+ return &v1.ServiceAccount{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sa1",
+ Namespace: "ns1",
+ },
+ }
+}
+
+func TestNewSaAccessObject(t *testing.T) {
+ tests := []struct {
+ name string
+ sa *v1.ServiceAccount
+ result *policyv1alpha1.ServiceAccountAccess
+ }{
+ {
+ name: "test NewSaAccessObject",
+ sa: newServiceAccount(),
+ result: &policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sa1",
+ Namespace: "ns1",
+ },
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: *newServiceAccount(),
+ },
+ },
+ },
+ }
+ for _, tc := range tests {
+ got := newSaAccessObject(*tc.sa)
+ if !reflect.DeepEqual(got, tc.result) {
+ t.Errorf("case %q want=%v, got=%v", tc.name, tc.result, got)
+ }
+ }
+}
+
+var podStr1 = `{
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "name": "pod1",
+ "namespace": "my-namespace"
+ },
+ "spec": {
+ "serviceAccountName": "sa1",
+ "nodeName": "my-node",
+ "containers": [
+ {
+ "name": "my-container",
+ "image": "my-image",
+ "ports": [
+ {
+ "containerPort": 80,
+ "protocol": "TCP"
+ }
+ ]
+ }
+ ]
+ }
+}`
+
+var podDelStr1 = `{
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "name": "podDel",
+ "namespace": "my-namespace",
+ "deletionTimestamp": "2022-01-01T00:00:00Z"
+ },
+ "spec": {
+ "serviceAccountName": "sa1",
+ "nodeName": "my-node",
+ "containers": [
+ {
+ "name": "my-container",
+ "image": "my-image",
+ "ports": [
+ {
+ "containerPort": 80,
+ "protocol": "TCP"
+ }
+ ]
+ }
+ ]
+ }
+ }`
+
+var podStr2 = `{
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "name": "pod2",
+ "namespace": "my-namespace"
+ },
+ "spec": {
+ "serviceAccountName": "sa1",
+ "nodeName": "my-node-2",
+ "containers": [
+ {
+ "name": "my-container-2",
+ "image": "my-image",
+ "ports": [
+ {
+ "containerPort": 80,
+ "protocol": "TCP"
+ }
+ ]
+ }
+ ]
+ }
+}`
+
+var podStrWithoutNodeName = `{
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "name": "podwnn1",
+ "namespace": "my-namespace"
+ },
+ "spec": {
+ "serviceAccountName": "sa1",
+ "containers": [
+ {
+ "name": "my-container",
+ "image": "my-image",
+ "ports": [
+ {
+ "containerPort": 80,
+ "protocol": "TCP"
+ }
+ ]
+ }
+ ]
+ }
+}`
+
+var podStrWithoutSa = `{
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "name": "podwnn1",
+ "namespace": "my-namespace"
+ },
+ "spec": {
+ "nodeName": "my-node",
+ "containers": [
+ {
+ "name": "my-container",
+ "image": "my-image",
+ "ports": [
+ {
+ "containerPort": 80,
+ "protocol": "TCP"
+ }
+ ]
+ }
+ ]
+ }
+}`
+
+var saStr1 = `{
+ "apiVersion": "v1",
+ "kind": "ServiceAccount",
+ "metadata": {
+ "name": "sa1",
+ "namespace": "my-namespace",
+ "resourceVersion": "999"
+ }
+}`
+
+var saStr2 = `{
+ "apiVersion": "v1",
+ "kind": "ServiceAccount",
+ "metadata": {
+ "name": "sa2",
+ "namespace": "my-namespace",
+ "resourceVersion": "999"
+ }
+}`
+
+var saNsStr = `{
+ "apiVersion": "v1",
+ "kind": "ServiceAccount",
+ "metadata": {
+ "name": "sa1",
+ "namespace": "my-namespace2"
+ }
+}`
+
+var roleStr1 = `{
+ "apiVersion": "rbac.authorization.k8s.io/v1",
+ "kind": "Role",
+ "metadata": {
+ "name": "role1",
+ "namespace": "my-namespace",
+ "resourceVersion": "999"
+ },
+ "rules": [
+ {
+ "apiGroups": [""],
+ "resources": ["pods"],
+ "verbs": ["get", "list", "watch"]
+ },
+ {
+ "apiGroups": ["apps"],
+ "resources": ["deployments"],
+ "verbs": ["get", "list", "watch"]
+ }
+ ]
+}`
+
+var roleNsStr1 = `{
+ "apiVersion": "rbac.authorization.k8s.io/v1",
+ "kind": "Role",
+ "metadata": {
+ "name": "role1",
+ "namespace": "my-namespace2",
+ "resourceVersion": "999"
+ },
+ "rules": [
+ {
+ "apiGroups": [""],
+ "resources": ["pods"],
+ "verbs": ["get", "list", "watch"]
+ },
+ {
+ "apiGroups": ["apps"],
+ "resources": ["deployments"],
+ "verbs": ["get", "list", "watch"]
+ }
+ ]
+}`
+
+var roleStr2 = `{
+ "apiVersion": "rbac.authorization.k8s.io/v1",
+ "kind": "Role",
+ "metadata": {
+ "name": "role2",
+ "namespace": "my-namespace",
+ "resourceVersion": "999"
+ },
+ "rules": [
+ {
+ "apiGroups": [""],
+ "resources": ["pods"],
+ "verbs": ["get", "list", "watch"]
+ },
+ {
+ "apiGroups": ["apps"],
+ "resources": ["configmaps"],
+ "verbs": ["get", "list", "watch"]
+ }
+ ]
+}`
+
+var rbStr1 = `{
+ "apiVersion": "rbac.authorization.k8s.io/v1",
+ "kind": "RoleBinding",
+ "metadata": {
+ "name": "rb1",
+ "namespace": "my-namespace",
+ "resourceVersion": "999"
+ },
+ "roleRef": {
+ "apiGroup": "rbac.authorization.k8s.io",
+ "kind": "Role",
+ "name": "role1"
+ },
+ "subjects": [
+ {
+ "kind": "ServiceAccount",
+ "name": "sa1",
+ "namespace": "my-namespace"
+ }
+ ]
+}`
+
+var rbStr2 = `{
+ "apiVersion": "rbac.authorization.k8s.io/v1",
+ "kind": "RoleBinding",
+ "metadata": {
+ "name": "rb2",
+ "namespace": "my-namespace",
+ "resourceVersion": "999"
+ },
+ "roleRef": {
+ "apiGroup": "rbac.authorization.k8s.io",
+ "kind": "Role",
+ "name": "role2"
+ },
+ "subjects": [
+ {
+ "kind": "ServiceAccount",
+ "name": "sa1",
+ "namespace": "my-namespace"
+ }
+ ]
+}`
+
+var rbWithCrStr = `{
+ "apiVersion": "rbac.authorization.k8s.io/v1",
+ "kind": "RoleBinding",
+ "metadata": {
+ "name": "rbWithCr",
+ "namespace": "my-namespace",
+ "resourceVersion": "999"
+ },
+ "roleRef": {
+ "apiGroup": "rbac.authorization.k8s.io",
+ "kind": "ClusterRole",
+ "name": "cr1"
+ },
+ "subjects": [
+ {
+ "kind": "ServiceAccount",
+ "name": "sa1",
+ "namespace": "my-namespace"
+ }
+ ]
+}`
+
+var crStr1 = `{
+ "apiVersion": "rbac.authorization.k8s.io/v1",
+ "kind": "ClusterRole",
+ "metadata": {
+ "name": "cr1",
+ "resourceVersion": "999"
+ },
+ "rules": [
+ {
+ "apiGroups": [""],
+ "resources": ["pods"],
+ "verbs": ["get", "list", "watch"]
+ },
+ {
+ "apiGroups": ["apps"],
+ "resources": ["deployments"],
+ "verbs": ["get", "list", "watch"]
+ }
+ ]
+}`
+
+var crbStr1 = `{
+ "apiVersion": "rbac.authorization.k8s.io/v1",
+ "kind": "ClusterRoleBinding",
+ "metadata": {
+ "name": "crb1",
+ "resourceVersion": "999"
+ },
+ "roleRef": {
+ "apiGroup": "rbac.authorization.k8s.io",
+ "kind": "ClusterRole",
+ "name": "cr1"
+ },
+ "subjects": [
+ {
+ "kind": "ServiceAccount",
+ "name": "sa1",
+ "namespace": "my-namespace"
+ }
+ ]
+}`
+
+var crbStr2 = `{
+ "apiVersion": "rbac.authorization.k8s.io/v1",
+ "kind": "ClusterRoleBinding",
+ "metadata": {
+ "name": "crb2",
+ "resourceVersion": "999"
+ },
+ "roleRef": {
+ "apiGroup": "rbac.authorization.k8s.io",
+ "kind": "ClusterRole",
+ "name": "cr1"
+ },
+ "subjects": [
+ {
+ "kind": "ServiceAccount",
+ "name": "sa1",
+ "namespace": "my-namespace"
+ }
+ ]
+}`
+
+func TestFilterResource(t *testing.T) {
+ var sa1 v1.ServiceAccount
+ err := json.Unmarshal([]byte(saStr1), &sa1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal sa1: %v", err)
+ }
+ var sa2 v1.ServiceAccount
+ err = json.Unmarshal([]byte(saStr2), &sa2)
+ if err != nil {
+ t.Errorf("Failed to unmarshal sa2: %v", err)
+ }
+ var saNs v1.ServiceAccount
+ err = json.Unmarshal([]byte(saNsStr), &saNs)
+ if err != nil {
+ t.Errorf("Failed to unmarshal sa2: %v", err)
+ }
+ var role1 rbacv1.Role
+ err = json.Unmarshal([]byte(roleStr1), &role1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal role1: %v", err)
+ }
+ var roleNs rbacv1.Role
+ err = json.Unmarshal([]byte(roleNsStr1), &roleNs)
+ if err != nil {
+ t.Errorf("Failed to unmarshal roleNs: %v", err)
+ }
+ var role2 rbacv1.Role
+ err = json.Unmarshal([]byte(roleStr2), &role2)
+ if err != nil {
+ t.Errorf("Failed to unmarshal role2: %v", err)
+ }
+ var rb1 rbacv1.RoleBinding
+ err = json.Unmarshal([]byte(rbStr1), &rb1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal rb1: %v", err)
+ }
+ var rb2 rbacv1.RoleBinding
+ err = json.Unmarshal([]byte(rbStr2), &rb2)
+ if err != nil {
+ t.Errorf("Failed to unmarshal rb2: %v", err)
+ }
+ var rbWithCr rbacv1.RoleBinding
+ err = json.Unmarshal([]byte(rbWithCrStr), &rbWithCr)
+ if err != nil {
+ t.Errorf("Failed to unmarshal rbWithCr: %v", err)
+ }
+ var cr1 rbacv1.ClusterRole
+ err = json.Unmarshal([]byte(crStr1), &cr1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal cr1: %v", err)
+ }
+ var crb1 rbacv1.ClusterRoleBinding
+ err = json.Unmarshal([]byte(crbStr1), &crb1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal crb1: %v", err)
+ }
+ var crb2 rbacv1.ClusterRoleBinding
+ err = json.Unmarshal([]byte(crbStr2), &crb2)
+ if err != nil {
+ t.Errorf("Failed to unmarshal crb2: %v", err)
+ }
+ var pod1 v1.Pod
+ err = json.Unmarshal([]byte(podStr1), &pod1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal pod1: %v", err)
+ }
+ var podNoNodeName v1.Pod
+ err = json.Unmarshal([]byte(podStrWithoutNodeName), &podNoNodeName)
+ if err != nil {
+ t.Errorf("Failed to unmarshal podNoNodeName: %v", err)
+ }
+ var podNoSa v1.Pod
+ err = json.Unmarshal([]byte(podStrWithoutSa), &podNoSa)
+ if err != nil {
+ t.Errorf("Failed to unmarshal podNoSa: %v", err)
+ }
+ nodeList := &v1.NodeList{
+ Items: []v1.Node{
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-node",
+ Labels: map[string]string{
+ "node-role.kubernetes.io/edge": "",
+ },
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "node-2",
+ Labels: map[string]string{
+ "node-role.kubernetes.io/edge": "",
+ },
+ },
+ },
+ },
+ }
+ var tests = []struct {
+ name string
+ input []client.Object
+ rbacObj client.Object
+ obj client.Object
+ reconcileResult []controllerruntime.Request
+ rbacResult bool
+ objResult bool
+ }{
+ {
+ name: "filter role or serviceaccount success",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{
+ {RoleBinding: rb1}, {RoleBinding: rb2}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{
+ {ClusterRoleBinding: crb1}}},
+ }, &crb1, &rb1, &rb2},
+ rbacObj: &role1,
+ obj: &sa1,
+ rbacResult: true,
+ objResult: true,
+ reconcileResult: []controllerruntime.Request{
+ {
+ NamespacedName: types.NamespacedName{
+ Name: "sa1",
+ Namespace: "my-namespace",
+ },
+ },
+ },
+ },
+ {
+ name: "filter role or serviceaccount failed",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{
+ {RoleBinding: rb1}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{
+ {ClusterRoleBinding: crb1}}},
+ }, &crb1, &rb1},
+ rbacObj: &role2,
+ obj: &sa2,
+ rbacResult: false,
+ objResult: false,
+ reconcileResult: []controllerruntime.Request{},
+ },
+ {
+ name: "filter role failed with nil role",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{
+ {ClusterRoleBinding: crb1}}},
+ }, &crb1},
+ rbacObj: &role2,
+ rbacResult: false,
+ reconcileResult: []controllerruntime.Request{},
+ },
+ {
+ name: "filter role or serviceaccount failed with different namespace",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{
+ {RoleBinding: rb1},
+ },
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{
+ {ClusterRoleBinding: crb1}}},
+ }, &crb1, &rb1},
+ rbacObj: &roleNs,
+ obj: &saNs,
+ rbacResult: false,
+ objResult: false,
+ reconcileResult: []controllerruntime.Request{},
+ },
+ {
+ name: "filter role failed with nil rolebinding and clusterrolebinding",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{ServiceAccount: sa1},
+ }},
+ rbacObj: &role2,
+ rbacResult: false,
+ reconcileResult: []controllerruntime.Request{},
+ },
+ {
+ name: "filter rolebinding success",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{
+ {RoleBinding: rb1}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{
+ {ClusterRoleBinding: crb1}}},
+ }, &crb1, &rb1},
+ rbacObj: &rb1,
+ rbacResult: true,
+ reconcileResult: []controllerruntime.Request{
+ {NamespacedName: types.NamespacedName{Name: "sa1", Namespace: "my-namespace"}},
+ },
+ },
+ {
+ name: "filter rolebinding failed",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{
+ {RoleBinding: rb1}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{
+ {ClusterRoleBinding: crb1}}},
+ }, &crb1, &rb1},
+ rbacObj: &rb2,
+ rbacResult: true,
+ reconcileResult: []controllerruntime.Request{
+ {NamespacedName: types.NamespacedName{Name: "sa1", Namespace: "my-namespace"}},
+ },
+ },
+ {
+ name: "filter rolebinding failed with nil rolebinding",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{
+ {ClusterRoleBinding: crb1}}},
+ }, &crb1},
+ rbacObj: &rb2,
+ rbacResult: true,
+ reconcileResult: []controllerruntime.Request{
+ {NamespacedName: types.NamespacedName{Name: "sa1", Namespace: "my-namespace"}},
+ },
+ },
+ {
+ name: "filter clusterrolebinding success",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{
+ {RoleBinding: rb1}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{
+ {ClusterRoleBinding: crb1}}},
+ }, &crb1, &rb1},
+ rbacObj: &crb1,
+ rbacResult: true,
+ reconcileResult: []controllerruntime.Request{
+ {
+ NamespacedName: types.NamespacedName{
+ Name: "sa1",
+ Namespace: "my-namespace",
+ },
+ },
+ },
+ },
+ {
+ name: "filter clusterrole success",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{
+ {RoleBinding: rb1},
+ },
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{
+ {ClusterRoleBinding: crb1}}},
+ }, &crb1, &rb1},
+ rbacObj: &cr1,
+ rbacResult: true,
+ reconcileResult: []controllerruntime.Request{
+ {
+ NamespacedName: types.NamespacedName{
+ Name: "sa1",
+ Namespace: "my-namespace",
+ },
+ },
+ },
+ },
+ {
+ name: "filter rolebinding bind cluster role success",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{
+ {RoleBinding: rbWithCr}}},
+ }, &rbWithCr},
+ rbacObj: &cr1,
+ rbacResult: true,
+ reconcileResult: []controllerruntime.Request{
+ {
+ NamespacedName: types.NamespacedName{
+ Name: "sa1",
+ Namespace: "my-namespace",
+ },
+ },
+ },
+ },
+ {
+ name: "filter pod success",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sa1",
+ Namespace: "my-namespace",
+ },
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ },
+ }},
+ obj: &pod1,
+ objResult: true,
+ },
+ {
+ name: "filter pod failed for without service account",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sa1",
+ Namespace: "my-namespace",
+ },
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ },
+ }},
+ obj: &podNoSa,
+ objResult: false,
+ },
+ {
+ name: "filter pod failed for without node name",
+ input: []client.Object{&policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sa1",
+ Namespace: "my-namespace",
+ },
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ },
+ }},
+ obj: &podNoNodeName,
+ objResult: false,
+ },
+ }
+ var accessScheme = runtime.NewScheme()
+ if err := policyv1alpha1.AddToScheme(accessScheme); err != nil {
+ t.Errorf("Failed to add access scheme: %v", err)
+ }
+ if err := v1.AddToScheme(accessScheme); err != nil {
+ t.Errorf("Failed to add v1 scheme: %v", err)
+ }
+ if err := rbacv1.AddToScheme(accessScheme); err != nil {
+ t.Errorf("Failed to add rbacv1 scheme: %v", err)
+ }
+ for _, tc := range tests {
+ fakeClient := fake.NewClientBuilder().WithScheme(accessScheme).WithObjects(tc.input...).WithLists(nodeList).Build()
+ ctr := &Controller{
+ Client: fakeClient,
+ }
+ if tc.rbacObj != nil {
+ got := ctr.filterResource(context.Background(), tc.rbacObj)
+ if !reflect.DeepEqual(got, tc.rbacResult) {
+ t.Errorf("case %q want=%v, got=%v", tc.name, tc.rbacResult, got)
+ }
+ got2 := ctr.mapRolesFunc(tc.rbacObj)
+ if !equality.Semantic.DeepEqual(got2, tc.reconcileResult) {
+ t.Errorf("case %q want=%v, got=%v", tc.name, tc.reconcileResult, got2)
+ }
+ }
+ if tc.obj != nil {
+ got1 := ctr.filterObject(context.Background(), tc.obj)
+ if !reflect.DeepEqual(got1, tc.objResult) {
+ t.Errorf("case %q want=%v, got=%v", tc.name, tc.objResult, got1)
+ }
+ }
+ }
+}
+
+func TestMapObjectFunc(t *testing.T) {
+ var pod1 v1.Pod
+ err := json.Unmarshal([]byte(podStr1), &pod1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal pod1: %v", err)
+ }
+ var podDel v1.Pod
+ err = json.Unmarshal([]byte(podDelStr1), &podDel)
+ if err != nil {
+ t.Errorf("Failed to unmarshal podDel: %v", err)
+ }
+ var rb2 rbacv1.RoleBinding
+ err = json.Unmarshal([]byte(rbStr2), &rb2)
+ if err != nil {
+ t.Errorf("Failed to unmarshal rb2: %v", err)
+ }
+ var crb1 rbacv1.ClusterRoleBinding
+ err = json.Unmarshal([]byte(crbStr1), &crb1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal crb1: %v", err)
+ }
+ var sa1 v1.ServiceAccount
+ err = json.Unmarshal([]byte(saStr1), &sa1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal sa1: %v", err)
+ }
+ var sa2 v1.ServiceAccount
+ err = json.Unmarshal([]byte(saStr2), &sa2)
+ if err != nil {
+ t.Errorf("Failed to unmarshal sa2: %v", err)
+ }
+ var cr1 rbacv1.ClusterRole
+ err = json.Unmarshal([]byte(crStr1), &cr1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal cr1: %v", err)
+ }
+ var rb1 rbacv1.RoleBinding
+ err = json.Unmarshal([]byte(rbStr1), &rb1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal rb1: %v", err)
+ }
+ var role1 rbacv1.Role
+ err = json.Unmarshal([]byte(roleStr1), &role1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal role1: %v", err)
+ }
+ var tests = []struct {
+ name string
+ input *policyv1alpha1.ServiceAccountAccess
+ obj client.Object
+ reconcileResult []controllerruntime.Request
+ output *[]policyv1alpha1.ServiceAccountAccess
+ }{
+ {
+ name: "match pod success and won't reconcile",
+ input: &policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: rb1}, {RoleBinding: rb2}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: crb1}},
+ },
+ },
+ obj: &pod1,
+ reconcileResult: []controllerruntime.Request{},
+ output: &[]policyv1alpha1.ServiceAccountAccess{{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: rb1}, {RoleBinding: rb2}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: crb1}},
+ },
+ }},
+ },
+ {
+ name: "match deleting pod success and reconcile",
+ input: &policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: rb1}, {RoleBinding: rb2}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: crb1}},
+ },
+ },
+ obj: &podDel,
+ reconcileResult: []controllerruntime.Request{
+ {NamespacedName: types.NamespacedName{Name: "sa1", Namespace: "my-namespace"}},
+ },
+ output: &[]policyv1alpha1.ServiceAccountAccess{{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: rb1}, {RoleBinding: rb2}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: crb1}},
+ },
+ }},
+ },
+ {
+ name: "match pod not exist in access list",
+ input: &policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sa2",
+ Namespace: "my-namespace",
+ },
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa2,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{
+ {
+ RoleBinding: rb2,
+ },
+ },
+ },
+ },
+ obj: &pod1,
+ reconcileResult: []controllerruntime.Request{},
+ output: &[]policyv1alpha1.ServiceAccountAccess{
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sa2",
+ Namespace: "my-namespace",
+ },
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa2,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{
+ {
+ RoleBinding: rb2,
+ },
+ },
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sa1",
+ Namespace: "my-namespace",
+ },
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: v1.ServiceAccount{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sa1",
+ Namespace: "my-namespace",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "match serviceaccount success",
+ input: &policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sa1",
+ Namespace: "my-namespace",
+ },
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{
+ {
+ RoleBinding: rb1,
+ },
+ {
+ RoleBinding: rb2,
+ },
+ },
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{
+ {
+ ClusterRoleBinding: crb1,
+ },
+ },
+ },
+ },
+ obj: &sa1,
+ reconcileResult: []controllerruntime.Request{
+ {
+ NamespacedName: types.NamespacedName{
+ Name: "sa1",
+ Namespace: "my-namespace",
+ },
+ },
+ },
+ output: &[]policyv1alpha1.ServiceAccountAccess{{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sa1",
+ Namespace: "my-namespace",
+ },
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{
+ {
+ RoleBinding: rb1,
+ },
+ {
+ RoleBinding: rb2,
+ },
+ },
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{
+ {
+ ClusterRoleBinding: crb1,
+ },
+ },
+ },
+ }},
+ },
+ }
+ var accessScheme = runtime.NewScheme()
+ if err := policyv1alpha1.AddToScheme(accessScheme); err != nil {
+ t.Errorf("Failed to add access scheme: %v", err)
+ }
+ for _, tc := range tests {
+ fakeClient := fake.NewClientBuilder().WithScheme(accessScheme).WithObjects(tc.input).Build()
+ ctr := &Controller{
+ Client: fakeClient,
+ }
+ got := ctr.mapObjectFunc(tc.obj)
+ if !equality.Semantic.DeepEqual(got, tc.reconcileResult) {
+ t.Errorf("mapObjectFunc() = %v, want %v", got, tc.reconcileResult)
+ }
+ sort.Slice(*tc.output, func(i, j int) bool {
+ return (*tc.output)[i].Name < (*tc.output)[j].Name
+ })
+ accList := &policyv1alpha1.ServiceAccountAccessList{}
+ if err := ctr.Client.List(context.Background(), accList, &client.ListOptions{Namespace: tc.obj.GetNamespace()}); err != nil {
+ t.Errorf("Failed to list access: %v", err)
+ }
+ sort.Slice(accList.Items, func(i, j int) bool {
+ return accList.Items[i].Name < accList.Items[j].Name
+ })
+ for i := range accList.Items {
+ if accList.Items[i].Name != (*tc.output)[i].Name {
+ t.Errorf("case %q, got %v, want %v", tc.name, accList.Items[i].Name, (*tc.output)[i].Name)
+ }
+ if accList.Items[i].Namespace != (*tc.output)[i].Namespace {
+ t.Errorf("case %q, got %v, want %v", tc.name, accList.Items[i].Namespace, (*tc.output)[i].Namespace)
+ }
+ if !equality.Semantic.DeepEqual(accList.Items[i].Spec, (*tc.output)[i].Spec) {
+ t.Errorf("case %q, got %v, want %v", tc.name, accList.Items[i].Spec, (*tc.output)[i].Spec)
+ }
+ }
+ }
+}
+
+func TestGetNodeListOfServiceAccountAccess(t *testing.T) {
+ // Create a sample ServiceAccountAccess object
+ saa := &policyv1alpha1.ServiceAccountAccess{
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: v1.ServiceAccount{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-sa",
+ Namespace: "test-ns",
+ },
+ },
+ },
+ }
+
+ nodeList := &v1.NodeList{
+ Items: []v1.Node{
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "node-1",
+ Labels: map[string]string{
+ "node-role.kubernetes.io/edge": "",
+ },
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "node-2",
+ Labels: map[string]string{
+ "node-role.kubernetes.io/edge": "",
+ },
+ },
+ },
+ },
+ }
+
+ // Create a sample PodList object with two pods on different nodes
+ podList := &v1.PodList{
+ Items: []v1.Pod{
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pod-1",
+ Namespace: "test-ns",
+ },
+ Spec: v1.PodSpec{
+ NodeName: "node-1",
+ ServiceAccountName: "test-sa",
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pod-2",
+ Namespace: "test-ns",
+ },
+ Spec: v1.PodSpec{
+ NodeName: "node-2",
+ ServiceAccountName: "test-sa",
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pod-3",
+ Namespace: "test-ns",
+ },
+ Spec: v1.PodSpec{
+ NodeName: "node-2",
+ ServiceAccountName: "test-sa",
+ },
+ },
+ // This test case is base on sigs.k8s.io/controller-runtime version >= v0.14.0 to filter obj by fieldSelector.
+ // But now sigs.k8s.io/controller-runtime v0.14.0 is base on go1.19, and can't be bumped up to v0.14.0
+ /* {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pod-4",
+ Namespace: "test-ns",
+ },
+ Spec: v1.PodSpec{
+ NodeName: "node-4",
+ ServiceAccountName: "test-sa2",
+ },
+ },*/
+ },
+ }
+ var v1Scheme = runtime.NewScheme()
+ if err := v1.AddToScheme(v1Scheme); err != nil {
+ t.Errorf("Failed to add access scheme: %v", err)
+ }
+ withScheme := fake.NewClientBuilder().WithScheme(v1Scheme)
+ fakeClient := withScheme.Build()
+ got, err := getNodeListOfServiceAccountAccess(context.Background(), fakeClient, saa)
+ if err != nil {
+ t.Errorf("fakeClient get node list error = %v", err)
+ }
+ if !equality.Semantic.DeepEqual(got, []string{}) {
+ t.Errorf("testcase 1 got %v, want %v", got, []string{})
+ }
+ fakeClient2 := withScheme.WithObjects(&podList.Items[0]).WithLists(nodeList).Build()
+ got2, err := getNodeListOfServiceAccountAccess(context.Background(), fakeClient2, saa)
+ if err != nil {
+ t.Errorf("fakeClient2 get node list error = %v", err)
+ }
+ if !equality.Semantic.DeepEqual(got2, []string{"node-1"}) {
+ t.Errorf("testcase 2 got %v, want %v", got2, []string{"node-1"})
+ }
+ fakeClient3 := withScheme.WithObjects(&podList.Items[1]).WithObjects(&podList.Items[2]).Build()
+ got3, err := getNodeListOfServiceAccountAccess(context.Background(), fakeClient3, saa)
+ if err != nil {
+ t.Errorf("fakeClient3 get node list error = %v", err)
+ }
+ if !equality.Semantic.DeepEqual(got3, []string{"node-1", "node-2"}) {
+ t.Errorf("testcase 3 got %v, want %v", got3, []string{"node-1", "node-2"})
+ }
+}
+
+func TestSyncRules(t *testing.T) {
+ var pod1 v1.Pod
+ err := json.Unmarshal([]byte(podStr1), &pod1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal pod1: %v", err)
+ }
+ var pod2 v1.Pod
+ err = json.Unmarshal([]byte(podStr2), &pod2)
+ if err != nil {
+ t.Errorf("Failed to unmarshal pod2: %v", err)
+ }
+ var podWithoutNodeName v1.Pod
+ err = json.Unmarshal([]byte(podStrWithoutNodeName), &podWithoutNodeName)
+ if err != nil {
+ t.Errorf("Failed to unmarshal podWithoutNodeName: %v", err)
+ }
+ var rb2 rbacv1.RoleBinding
+ err = json.Unmarshal([]byte(rbStr2), &rb2)
+ if err != nil {
+ t.Errorf("Failed to unmarshal rb2: %v", err)
+ }
+ var crb1 rbacv1.ClusterRoleBinding
+ err = json.Unmarshal([]byte(crbStr1), &crb1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal crb1: %v", err)
+ }
+ var sa1 v1.ServiceAccount
+ err = json.Unmarshal([]byte(saStr1), &sa1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal sa1: %v", err)
+ }
+ var sa2 v1.ServiceAccount
+ err = json.Unmarshal([]byte(saStr2), &sa2)
+ if err != nil {
+ t.Errorf("Failed to unmarshal sa2: %v", err)
+ }
+ var cr1 rbacv1.ClusterRole
+ err = json.Unmarshal([]byte(crStr1), &cr1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal cr1: %v", err)
+ }
+ var rb1 rbacv1.RoleBinding
+ err = json.Unmarshal([]byte(rbStr1), &rb1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal rb1: %v", err)
+ }
+ var role1 rbacv1.Role
+ err = json.Unmarshal([]byte(roleStr1), &role1)
+ if err != nil {
+ t.Errorf("Failed to unmarshal role1: %v", err)
+ }
+ var role2 rbacv1.Role
+ err = json.Unmarshal([]byte(roleStr2), &role2)
+ if err != nil {
+ t.Errorf("Failed to unmarshal role1: %v", err)
+ }
+ nodeList := &v1.NodeList{
+ Items: []v1.Node{
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-node",
+ Labels: map[string]string{
+ "node-role.kubernetes.io/edge": "",
+ },
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-node-2",
+ Labels: map[string]string{
+ "node-role.kubernetes.io/edge": "",
+ },
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-node-3",
+ Labels: map[string]string{
+ "node-role.kubernetes.io/edge": "",
+ },
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-node-4",
+ },
+ },
+ },
+ }
+ var nodeStatus1 = policyv1alpha1.AccessStatus{NodeList: []string{"my-node"}}
+ var nodeStatus2 = policyv1alpha1.AccessStatus{NodeList: []string{"my-node", "my-node-2"}}
+ var nodeStatus3 = policyv1alpha1.AccessStatus{NodeList: []string{"my-node-2"}}
+ var nodeStatus4 = policyv1alpha1.AccessStatus{NodeList: []string{"my-node-2", "my-node-3"}}
+ var saa1 = policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: rb1, Rules: role1.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: crb1, Rules: cr1.Rules}},
+ },
+ Status: nodeStatus1,
+ }
+ var saa2 = policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: rb1, Rules: role1.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: crb1, Rules: cr1.Rules}},
+ },
+ Status: nodeStatus2,
+ }
+ var saa3 = policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: rb1, Rules: role1.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: crb1, Rules: cr1.Rules}},
+ },
+ Status: nodeStatus3,
+ }
+ var saa4 = policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: rb1, Rules: role1.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: crb1, Rules: cr1.Rules}},
+ },
+ Status: nodeStatus4,
+ }
+ var saa5 = policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: rb1, Rules: role1.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: crb1, Rules: cr1.Rules}},
+ },
+ Status: nodeStatus1,
+ }
+ var saaDiffName = policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa2", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa2,
+ },
+ Status: nodeStatus2,
+ }
+ var saaDeletion = policyv1alpha1.ServiceAccountAccess{
+ ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace", DeletionTimestamp: &metav1.Time{Time: time.Now()}},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: sa1,
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: rb1, Rules: role1.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: crb1, Rules: cr1.Rules}},
+ },
+ Status: nodeStatus1,
+ }
+ var tests = []struct {
+ name string
+ input *policyv1alpha1.ServiceAccountAccess
+ obj []client.Object
+ reconcileResult controllerruntime.Result
+ output *policyv1alpha1.ServiceAccountAccess
+ msgOpr []string
+ }{
+ {
+ name: "rolebinding updated only",
+ input: saa1.DeepCopy(),
+ obj: []client.Object{saa1.DeepCopy(), pod1.DeepCopy(), sa1.DeepCopy(), rb1.DeepCopy(), crb1.DeepCopy(),
+ cr1.DeepCopy(), role1.DeepCopy(), rb2.DeepCopy(), role2.DeepCopy()},
+ reconcileResult: controllerruntime.Result{},
+ output: &policyv1alpha1.ServiceAccountAccess{ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: *sa1.DeepCopy(),
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: *rb1.DeepCopy(), Rules: role1.Rules}, {RoleBinding: *rb2.DeepCopy(), Rules: role2.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: *crb1.DeepCopy(), Rules: cr1.Rules}},
+ },
+ Status: policyv1alpha1.AccessStatus{NodeList: []string{"my-node"}},
+ },
+ msgOpr: []string{model.UpdateOperation},
+ },
+ {
+ name: "rolebinding updated and inserted new node",
+ input: saa1.DeepCopy(),
+ obj: []client.Object{saa1.DeepCopy(), pod1.DeepCopy(), pod2.DeepCopy(), sa1.DeepCopy(), rb1.DeepCopy(),
+ crb1.DeepCopy(), cr1.DeepCopy(), role1.DeepCopy(), rb2.DeepCopy(), role2.DeepCopy()},
+ reconcileResult: controllerruntime.Result{},
+ output: &policyv1alpha1.ServiceAccountAccess{ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: *sa1.DeepCopy(),
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: *rb1.DeepCopy(), Rules: role1.Rules}, {RoleBinding: *rb2.DeepCopy(), Rules: role2.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: *crb1.DeepCopy(), Rules: cr1.Rules}},
+ },
+ Status: policyv1alpha1.AccessStatus{NodeList: []string{"my-node", "my-node-2"}},
+ },
+ msgOpr: []string{model.UpdateOperation, model.UpdateOperation},
+ },
+ {
+ name: "rolebinding updated and inserted/deleted new node",
+ input: saa3.DeepCopy(),
+ obj: []client.Object{saa3.DeepCopy(), pod1.DeepCopy(), sa1.DeepCopy(), rb1.DeepCopy(), crb1.DeepCopy(),
+ cr1.DeepCopy(), role1.DeepCopy(), rb2.DeepCopy(), role2.DeepCopy()},
+ reconcileResult: controllerruntime.Result{},
+ output: &policyv1alpha1.ServiceAccountAccess{ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: *sa1.DeepCopy(),
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: *rb1.DeepCopy(), Rules: role1.Rules}, {RoleBinding: *rb2.DeepCopy(), Rules: role2.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: *crb1.DeepCopy(), Rules: cr1.Rules}},
+ },
+ Status: policyv1alpha1.AccessStatus{NodeList: []string{"my-node"}},
+ },
+ msgOpr: []string{model.DeleteOperation, model.UpdateOperation},
+ },
+ {
+ name: "rolebinding updated and inserted/deleted/updated new node",
+ input: saa4.DeepCopy(),
+ obj: []client.Object{saa4.DeepCopy(), pod1.DeepCopy(), pod2.DeepCopy(), sa1.DeepCopy(), rb1.DeepCopy(),
+ crb1.DeepCopy(), cr1.DeepCopy(), role1.DeepCopy(), rb2.DeepCopy(), role2.DeepCopy()},
+ reconcileResult: controllerruntime.Result{},
+ output: &policyv1alpha1.ServiceAccountAccess{ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: *sa1.DeepCopy(),
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: *rb1.DeepCopy(), Rules: role1.Rules}, {RoleBinding: *rb2.DeepCopy(), Rules: role2.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: *crb1.DeepCopy(), Rules: cr1.Rules}},
+ },
+ Status: policyv1alpha1.AccessStatus{NodeList: []string{"my-node", "my-node-2"}},
+ },
+ msgOpr: []string{model.DeleteOperation, model.UpdateOperation},
+ },
+ {
+ name: "rolebinding updated and inserted new node with none old node",
+ input: saa5.DeepCopy(),
+ obj: []client.Object{saa5.DeepCopy(), &pod1, &pod2, &sa1, &rb1, &crb1, &cr1, &role1, &rb2, &role2},
+ reconcileResult: controllerruntime.Result{},
+ output: &policyv1alpha1.ServiceAccountAccess{ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: *sa1.DeepCopy(),
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: *rb1.DeepCopy(), Rules: role1.Rules}, {RoleBinding: *rb2.DeepCopy(), Rules: role2.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: *crb1.DeepCopy(), Rules: cr1.Rules}},
+ },
+ Status: policyv1alpha1.AccessStatus{NodeList: []string{"my-node", "my-node-2"}},
+ },
+ msgOpr: []string{model.UpdateOperation, model.UpdateOperation},
+ },
+ {
+ name: "rolebinding updated and deleted old node only",
+ input: saa4.DeepCopy(),
+ obj: []client.Object{saa4.DeepCopy(), podWithoutNodeName.DeepCopy(), sa1.DeepCopy(), rb1.DeepCopy(),
+ crb1.DeepCopy(), cr1.DeepCopy(), role1.DeepCopy(), rb2.DeepCopy(), role2.DeepCopy()},
+ reconcileResult: controllerruntime.Result{},
+ output: &policyv1alpha1.ServiceAccountAccess{ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: *sa1.DeepCopy(),
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: *rb1.DeepCopy(), Rules: role1.Rules}, {RoleBinding: *rb2.DeepCopy(), Rules: role2.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: *crb1.DeepCopy(), Rules: cr1.Rules}},
+ },
+ Status: policyv1alpha1.AccessStatus{NodeList: []string{}},
+ },
+ msgOpr: []string{model.DeleteOperation},
+ },
+ {
+ name: "rolebinding updated and updated/deleted node",
+ input: saa2.DeepCopy(),
+ obj: []client.Object{saa2.DeepCopy(), pod1.DeepCopy(), sa1.DeepCopy(), rb1.DeepCopy(),
+ crb1.DeepCopy(), cr1.DeepCopy(), role1.DeepCopy(), rb2.DeepCopy(), role2.DeepCopy()},
+ reconcileResult: controllerruntime.Result{},
+ output: &policyv1alpha1.ServiceAccountAccess{ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: *sa1.DeepCopy(),
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: *rb1.DeepCopy(), Rules: role1.Rules}, {RoleBinding: *rb2.DeepCopy(), Rules: role2.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: *crb1.DeepCopy(), Rules: cr1.Rules}},
+ },
+ Status: policyv1alpha1.AccessStatus{NodeList: []string{"my-node"}},
+ },
+ msgOpr: []string{model.DeleteOperation, model.UpdateOperation},
+ },
+ {
+ name: "rolebinding updated and none nodes",
+ input: saa5.DeepCopy(),
+ obj: []client.Object{saa5.DeepCopy(), podWithoutNodeName.DeepCopy(), sa1.DeepCopy(), rb1.DeepCopy(),
+ crb1.DeepCopy(), cr1.DeepCopy(), role1.DeepCopy(), rb2.DeepCopy(), role2.DeepCopy()},
+ reconcileResult: controllerruntime.Result{},
+ output: &policyv1alpha1.ServiceAccountAccess{ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: *sa1.DeepCopy(),
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: *rb1.DeepCopy(), Rules: role1.Rules}, {RoleBinding: *rb2.DeepCopy(), Rules: role2.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: *crb1.DeepCopy(), Rules: cr1.Rules}},
+ },
+ Status: policyv1alpha1.AccessStatus{NodeList: []string{}},
+ },
+ msgOpr: []string{},
+ },
+ {
+ name: "service account not found",
+ input: saa5.DeepCopy(),
+ obj: []client.Object{saa5.DeepCopy(), podWithoutNodeName.DeepCopy(), sa2.DeepCopy(), rb1.DeepCopy(),
+ crb1.DeepCopy(), cr1.DeepCopy(), role1.DeepCopy(), rb2.DeepCopy(), role2.DeepCopy()},
+ reconcileResult: controllerruntime.Result{},
+ output: &policyv1alpha1.ServiceAccountAccess{
+ Status: policyv1alpha1.AccessStatus{NodeList: []string{"my-node"}},
+ },
+ msgOpr: []string{model.DeleteOperation},
+ },
+ {
+ name: "insert only",
+ input: saa1.DeepCopy(),
+ obj: []client.Object{saa1.DeepCopy(), pod1.DeepCopy(), pod2.DeepCopy(), sa1.DeepCopy(), rb1.DeepCopy(),
+ crb1.DeepCopy(), cr1.DeepCopy(), role1.DeepCopy()},
+ reconcileResult: controllerruntime.Result{},
+ output: &policyv1alpha1.ServiceAccountAccess{ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: *sa1.DeepCopy(),
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: *rb1.DeepCopy(), Rules: role1.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: *crb1.DeepCopy(), Rules: cr1.Rules}},
+ },
+ Status: policyv1alpha1.AccessStatus{NodeList: []string{"my-node", "my-node-2"}},
+ },
+ msgOpr: []string{model.InsertOperation},
+ },
+ {
+ name: "reconcile failed cause serviceaccountaccess not found",
+ input: saaDiffName.DeepCopy(),
+ obj: []client.Object{saa1.DeepCopy(), pod1.DeepCopy(), pod2.DeepCopy(), sa1.DeepCopy(), rb1.DeepCopy(),
+ crb1.DeepCopy(), cr1.DeepCopy(), role1.DeepCopy()},
+ reconcileResult: controllerruntime.Result{},
+ output: &policyv1alpha1.ServiceAccountAccess{},
+ msgOpr: []string{},
+ },
+ {
+ name: "reconcile failed cause deletionTimestamp not nil",
+ input: saaDeletion.DeepCopy(),
+ obj: []client.Object{saaDeletion.DeepCopy(), pod1.DeepCopy(), sa1.DeepCopy(), rb1.DeepCopy(), crb1.DeepCopy(),
+ cr1.DeepCopy(), role1.DeepCopy()},
+ reconcileResult: controllerruntime.Result{},
+ output: &policyv1alpha1.ServiceAccountAccess{ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: "my-namespace"},
+ Spec: policyv1alpha1.AccessSpec{
+ ServiceAccount: *sa1.DeepCopy(),
+ AccessRoleBinding: []policyv1alpha1.AccessRoleBinding{{RoleBinding: *rb1.DeepCopy(), Rules: role1.Rules}},
+ AccessClusterRoleBinding: []policyv1alpha1.AccessClusterRoleBinding{{ClusterRoleBinding: *crb1.DeepCopy(), Rules: cr1.Rules}},
+ },
+ Status: policyv1alpha1.AccessStatus{NodeList: []string{"my-node"}},
+ },
+ msgOpr: []string{},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ cloudHub := &common.ModuleInfo{
+ ModuleName: modules.CloudHubModuleName,
+ ModuleType: common.MsgCtxTypeChannel,
+ }
+ beehiveContext.InitContext([]string{common.MsgCtxTypeChannel})
+ beehiveContext.AddModule(cloudHub)
+ var accessScheme = runtime.NewScheme()
+ if err := policyv1alpha1.AddToScheme(accessScheme); err != nil {
+ t.Errorf("Failed to add policyv1alpha1 scheme: %v", err)
+ }
+ if err := v1.AddToScheme(accessScheme); err != nil {
+ t.Errorf("Failed to add v1 scheme: %v", err)
+ }
+ if err := rbacv1.AddToScheme(accessScheme); err != nil {
+ t.Errorf("Failed to add rbacv1 scheme: %v", err)
+ }
+ fakeClient := fake.NewClientBuilder().WithScheme(accessScheme).WithObjects(tt.obj...).WithLists(nodeList).Build()
+ ctr := &Controller{
+ Client: fakeClient,
+ MessageLayer: messagelayer.PolicyControllerMessageLayer(),
+ }
+ var rst controllerruntime.Result
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ inputObj := tt.input.DeepCopy()
+ rst, err = ctr.Reconcile(context.Background(), controllerruntime.Request{NamespacedName: types.NamespacedName{Name: inputObj.Name, Namespace: inputObj.Namespace}})
+ if err != nil {
+ t.Errorf("TestCase %q Failed to syncRules: %v", tt.name, err)
+ }
+ }()
+ var oprs []string
+ for range tt.msgOpr {
+ message, _ := beehiveContext.Receive(modules.CloudHubModuleName)
+ oprs = append(oprs, message.GetOperation())
+ }
+ wg.Wait()
+ sort.Strings(oprs)
+ sort.Strings(tt.msgOpr)
+ if !equality.Semantic.DeepEqual(oprs, tt.msgOpr) {
+ t.Errorf("TestCase %q message operation got %v, want %v", tt.name, oprs, tt.msgOpr)
+ }
+ if !equality.Semantic.DeepEqual(rst, tt.reconcileResult) {
+ t.Errorf("TestCase %q Expected: %v, got: %v", tt.name, tt.reconcileResult, rst)
+ }
+ saa := &policyv1alpha1.ServiceAccountAccess{}
+ err = fakeClient.Get(context.Background(), types.NamespacedName{Name: tt.input.Name, Namespace: tt.input.Namespace}, saa)
+ if err != nil && apierror.IsNotFound(err) {
+ return
+ } else if err != nil {
+ t.Errorf("TestCase %q Failed to get saa: %v", tt.name, err)
+ }
+ if !equality.Semantic.DeepEqual((*saa).Spec.ServiceAccount, (*(tt.output)).Spec.ServiceAccount) {
+ t.Errorf("TestCase %q Expected spec serviceaccount: %+v, got: %+v", tt.name, tt.output.Spec.ServiceAccount, saa.Spec.ServiceAccount)
+ }
+ if !equality.Semantic.DeepEqual((*saa).Spec.AccessClusterRoleBinding, (*(tt.output)).Spec.AccessClusterRoleBinding) {
+ t.Errorf("TestCase %q Expected spec crb: %+v, got: %+v", tt.name, tt.output.Spec.AccessClusterRoleBinding, saa.Spec.AccessClusterRoleBinding)
+ }
+ if !equality.Semantic.DeepEqual((*saa).Spec.AccessRoleBinding, (*(tt.output)).Spec.AccessRoleBinding) {
+ t.Errorf("TestCase %q Expected spec rb: %+v, got: %+v", tt.name, tt.output.Spec.AccessRoleBinding, saa.Spec.AccessRoleBinding)
+ }
+ sort.Strings(saa.Status.NodeList)
+ sort.Strings(tt.output.Status.NodeList)
+ if !equality.Semantic.DeepEqual(saa.Status.NodeList, tt.output.Status.NodeList) {
+ t.Errorf("TestCase %q Expected status: %v, got: %v", tt.name, tt.output.Status.NodeList, saa.Status.NodeList)
+ }
+ })
+ }
+}
diff --git a/cloud/pkg/policycontroller/policycontroller.go b/cloud/pkg/policycontroller/policycontroller.go
new file mode 100644
index 000000000..734f0aee5
--- /dev/null
+++ b/cloud/pkg/policycontroller/policycontroller.go
@@ -0,0 +1,104 @@
+package policycontroller
+
+import (
+ "context"
+ "fmt"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/client-go/kubernetes/scheme"
+ "k8s.io/client-go/rest"
+ "k8s.io/klog/v2"
+ controllerruntime "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/manager"
+
+ "github.com/kubeedge/beehive/pkg/core"
+ beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
+ "github.com/kubeedge/kubeedge/cloud/pkg/common/messagelayer"
+ "github.com/kubeedge/kubeedge/cloud/pkg/common/modules"
+ pm "github.com/kubeedge/kubeedge/cloud/pkg/policycontroller/manager"
+ policyv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
+ kefeatures "github.com/kubeedge/kubeedge/pkg/features"
+)
+
+// policyController use beehive context message layer
+type policyController struct {
+ manager manager.Manager
+ ctx context.Context
+}
+
+var _ core.Module = (*policyController)(nil)
+
+var accessScheme = runtime.NewScheme()
+
+func init() {
+ utilruntime.Must(scheme.AddToScheme(accessScheme))
+ utilruntime.Must(policyv1alpha1.AddToScheme(accessScheme))
+}
+
+func NewAccessRoleControllerManager(ctx context.Context, kubeCfg *rest.Config) (manager.Manager, error) {
+ controllerManager, err := controllerruntime.NewManager(kubeCfg, controllerruntime.Options{
+ Scheme: accessScheme,
+ MetricsBindAddress: "0", // disable metrics
+ // TODO: leader election
+ // TODO: /healthz
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to create controller manager, %v", err)
+ }
+
+ if err := setupControllers(ctx, controllerManager); err != nil {
+ return nil, err
+ }
+ return controllerManager, nil
+}
+
+func setupControllers(ctx context.Context, mgr manager.Manager) error {
+ // This returned cli will directly acquire the unstructured objects from API Server which
+ // have not be registered in the accessScheme.
+ cli := mgr.GetClient()
+ pc := &pm.Controller{
+ Client: cli,
+ MessageLayer: messagelayer.PolicyControllerMessageLayer(),
+ }
+
+ klog.Info("setup policy controller")
+ if err := pc.SetupWithManager(ctx, mgr); err != nil {
+ return fmt.Errorf("failed to setup nodegroup controller, %v", err)
+ }
+ return nil
+}
+
+func Register(kubeCfg *rest.Config) {
+ var pc = &policyController{}
+ pc.ctx = beehiveContext.GetContext()
+ mgr, err := NewAccessRoleControllerManager(pc.ctx, kubeCfg)
+ if err != nil {
+ klog.Fatalf("failed to create controller manager, %v", err)
+ }
+ pc.manager = mgr
+ core.Register(pc)
+}
+
+// Name of controller
+func (pc *policyController) Name() string {
+ return modules.PolicyControllerModuleName
+}
+
+// Group of controller
+func (pc *policyController) Group() string {
+ return modules.PolicyControllerGroupName
+}
+
+// Enable indicates whether enable this module
+func (pc *policyController) Enable() bool {
+ return kefeatures.DefaultFeatureGate.Enabled(kefeatures.RequireAuthorization)
+}
+
+// Start controller
+func (pc *policyController) Start() {
+ // mgr.Start will block until the manager has stopped
+ if err := pc.manager.Start(pc.ctx); err != nil {
+ klog.Fatalf("failed to start controller manager, %v", err)
+ }
+}
diff --git a/cloud/pkg/policycontroller/policycontroller_test.go b/cloud/pkg/policycontroller/policycontroller_test.go
new file mode 100644
index 000000000..1cdd3247e
--- /dev/null
+++ b/cloud/pkg/policycontroller/policycontroller_test.go
@@ -0,0 +1,38 @@
+package policycontroller
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/kubeedge/kubeedge/pkg/features"
+)
+
+func TestRegister(t *testing.T) {
+ if err := features.DefaultMutableFeatureGate.SetFromMap(map[string]bool{string(features.RequireAuthorization): true}); err != nil {
+ t.Errorf("Failed to set feature gate: %v", err)
+ }
+ tests := []struct {
+ name string
+ controller *policyController
+ ctrEnable bool
+ ctrName string
+ }{
+ {
+ name: "Register Policy controller",
+ controller: &policyController{},
+ ctrName: "policycontroller",
+ ctrEnable: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var pc = &policyController{}
+ if !reflect.DeepEqual(tt.ctrName, pc.Name()) {
+ t.Errorf("TestCase %q got %v, want %v", tt.name, pc.Name(), tt.ctrName)
+ }
+ if !reflect.DeepEqual(tt.ctrEnable, pc.Enable()) {
+ t.Errorf("TestCase %q got %v, want %v", tt.name, pc.Enable(), tt.ctrEnable)
+ }
+ })
+ }
+}
diff --git a/cloud/pkg/synccontroller/objectsync.go b/cloud/pkg/synccontroller/objectsync.go
index c0a4d7fe7..bb517888d 100644
--- a/cloud/pkg/synccontroller/objectsync.go
+++ b/cloud/pkg/synccontroller/objectsync.go
@@ -9,7 +9,6 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
- "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
@@ -90,11 +89,6 @@ func (sctl *SyncController) gcOrphanedObjectSync(sync *v1alpha1.ObjectSync) {
func sendEvents(nodeName string, sync *v1alpha1.ObjectSync, resourceType string,
objectResourceVersion string, obj interface{}) {
- runtimeObj := obj.(runtime.Object)
- if err := util.SetMetaType(runtimeObj); err != nil {
- klog.Warningf("failed to set metatype :%v", err)
- }
-
if sync.Status.ObjectResourceVersion == "" {
klog.Errorf("The ObjectResourceVersion is empty in status of objectsync: %s", sync.Name)
return
diff --git a/common/constants/default.go b/common/constants/default.go
index fab9a5f16..ea58f3229 100644
--- a/common/constants/default.go
+++ b/common/constants/default.go
@@ -25,11 +25,12 @@ const (
// Resources
const (
// Certificates
- DefaultConfigDir = "/etc/kubeedge/config/"
- DefaultCAFile = "/etc/kubeedge/ca/rootCA.crt"
- DefaultCAKeyFile = "/etc/kubeedge/ca/rootCA.key"
- DefaultCertFile = "/etc/kubeedge/certs/server.crt"
- DefaultKeyFile = "/etc/kubeedge/certs/server.key"
+ DefaultConfigDir = "/etc/kubeedge/config/"
+ DefaultCAFile = "/etc/kubeedge/ca/rootCA.crt"
+ DefaultCAKeyFile = "/etc/kubeedge/ca/rootCA.key"
+ DefaultCertFile = "/etc/kubeedge/certs/server.crt"
+ DefaultKeyFile = "/etc/kubeedge/certs/server.key"
+ DefaultServiceAccountIssuer = "https://kubernetes.default.svc.cluster.local"
DefaultCAURL = "/ca.crt"
DefaultCertURL = "/edge.crt"
diff --git a/common/types/types.go b/common/types/types.go
index 7efe5e140..4fe163a68 100644
--- a/common/types/types.go
+++ b/common/types/types.go
@@ -3,6 +3,7 @@ package types
import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
+ metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
@@ -46,3 +47,9 @@ type NodeUpgradeJobResponse struct {
Status string
Reason string
}
+
+// ObjectResp is the object that api-server response
+type ObjectResp struct {
+ Object metaV1.Object
+ Err error
+}
diff --git a/edge/pkg/edged/kubeclientbridge/typed/core/v1/serviceaccount_bridge.go b/edge/pkg/edged/kubeclientbridge/typed/core/v1/serviceaccount_bridge.go
index 4f39c94f7..b16c93721 100644
--- a/edge/pkg/edged/kubeclientbridge/typed/core/v1/serviceaccount_bridge.go
+++ b/edge/pkg/edged/kubeclientbridge/typed/core/v1/serviceaccount_bridge.go
@@ -27,6 +27,7 @@ import (
"context"
authenticationv1 "k8s.io/api/authentication/v1"
+ corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fakecorev1 "k8s.io/client-go/kubernetes/typed/core/v1/fake"
@@ -44,3 +45,7 @@ type ServiceAccountsBridge struct {
func (c *ServiceAccountsBridge) CreateToken(ctx context.Context, serviceAccountName string, tokenRequest *authenticationv1.TokenRequest, opts metav1.CreateOptions) (result *authenticationv1.TokenRequest, err error) {
return c.MetaClient.ServiceAccountToken().GetServiceAccountToken(c.ns, serviceAccountName, tokenRequest)
}
+
+func (c *ServiceAccountsBridge) Get(ctx context.Context, name string, options metav1.GetOptions) (result *corev1.ServiceAccount, err error) {
+ return c.MetaClient.ServiceAccounts(c.ns).Get(name)
+}
diff --git a/edge/pkg/metamanager/client/metaclient.go b/edge/pkg/metamanager/client/metaclient.go
index 1b3c72106..f6d0a7022 100644
--- a/edge/pkg/metamanager/client/metaclient.go
+++ b/edge/pkg/metamanager/client/metaclient.go
@@ -16,7 +16,7 @@ var (
syncMsgRespTimeout = 1 * time.Minute
)
-//CoreInterface is interface of metaclient
+// CoreInterface is interface of metaclient
type CoreInterface interface {
PodsGetter
PodStatusGetter
@@ -25,6 +25,7 @@ type CoreInterface interface {
NodeStatusGetter
SecretsGetter
ServiceAccountTokenGetter
+ ServiceAccountsGetter
PersistentVolumesGetter
PersistentVolumeClaimsGetter
VolumeAttachmentsGetter
@@ -59,6 +60,10 @@ func (m *metaClient) ServiceAccountToken() ServiceAccountTokenInterface {
return newServiceAccountToken(m.send)
}
+func (m *metaClient) ServiceAccounts(namespace string) ServiceAccountInterface {
+ return newServiceAccount(namespace)
+}
+
func (m *metaClient) PodStatus(namespace string) PodStatusInterface {
return newPodStatus(namespace, m.send)
}
@@ -89,7 +94,7 @@ func New() CoreInterface {
}
}
-//SendInterface is to sync interface
+// SendInterface is to sync interface
type SendInterface interface {
SendSync(message *model.Message) (*model.Message, error)
Send(message *model.Message)
@@ -125,11 +130,3 @@ func (s *send) SendSync(message *model.Message) (*model.Message, error) {
func (s *send) Send(message *model.Message) {
beehiveContext.Send(modules.MetaManagerModuleName, *message)
}
-
-func SetSyncPeriod(time time.Duration) {
- syncPeriod = time
-}
-
-func SetSyncMsgRespTimeout(time time.Duration) {
- syncMsgRespTimeout = time
-}
diff --git a/edge/pkg/metamanager/client/nodestatus.go b/edge/pkg/metamanager/client/nodestatus.go
index 66db02a61..2f7eda187 100644
--- a/edge/pkg/metamanager/client/nodestatus.go
+++ b/edge/pkg/metamanager/client/nodestatus.go
@@ -38,6 +38,7 @@ func (c *nodeStatus) Create(ns *edgeapi.NodeStatusRequest) (*edgeapi.NodeStatusR
return nil, nil
}
+// deprecated
func (c *nodeStatus) Update(rsName string, ns edgeapi.NodeStatusRequest) error {
resource := fmt.Sprintf("%s/%s/%s", c.namespace, model.ResourceTypeNodeStatus, rsName)
nodeStatusMsg := message.BuildMsg(modules.MetaGroup, "", modules.EdgedModuleName, resource, model.UpdateOperation, ns)
diff --git a/edge/pkg/metamanager/client/pod.go b/edge/pkg/metamanager/client/pod.go
index 61ea60002..7cfcc1244 100644
--- a/edge/pkg/metamanager/client/pod.go
+++ b/edge/pkg/metamanager/client/pod.go
@@ -16,12 +16,12 @@ import (
"github.com/kubeedge/kubeedge/edge/pkg/common/modules"
)
-//PodsGetter is interface to get pods
+// PodsGetter is interface to get pods
type PodsGetter interface {
Pods(namespace string) PodsInterface
}
-//PodsInterface is pod interface
+// PodsInterface is pod interface
type PodsInterface interface {
Create(*corev1.Pod) (*corev1.Pod, error)
Update(*corev1.Pod) error
diff --git a/edge/pkg/metamanager/client/podstatus.go b/edge/pkg/metamanager/client/podstatus.go
index c6ad198af..5378518d3 100644
--- a/edge/pkg/metamanager/client/podstatus.go
+++ b/edge/pkg/metamanager/client/podstatus.go
@@ -39,6 +39,7 @@ func (c *podStatus) Create(ps *edgeapi.PodStatusRequest) (*edgeapi.PodStatusRequ
return nil, nil
}
+// deprecated
func (c *podStatus) Update(rsName string, ps edgeapi.PodStatusRequest) error {
podStatusMsg := message.BuildMsg(commodule.MetaGroup, "", commodule.EdgedModuleName, c.namespace+"/"+model.ResourceTypePodStatus+"/"+rsName, model.UpdateOperation, ps)
resp, err := c.send.SendSync(podStatusMsg)
diff --git a/edge/pkg/metamanager/client/secret.go b/edge/pkg/metamanager/client/secret.go
index 94f031bb7..13ee99af9 100644
--- a/edge/pkg/metamanager/client/secret.go
+++ b/edge/pkg/metamanager/client/secret.go
@@ -11,12 +11,12 @@ import (
"github.com/kubeedge/kubeedge/edge/pkg/common/modules"
)
-//SecretsGetter is interface to get client secrets
+// SecretsGetter is interface to get client secrets
type SecretsGetter interface {
Secrets(namespace string) SecretsInterface
}
-//SecretsInterface is interface for client secret
+// SecretsInterface is interface for client secret
type SecretsInterface interface {
Create(*api.Secret) (*api.Secret, error)
Update(*api.Secret) error
diff --git a/edge/pkg/metamanager/client/serviceaccount.go b/edge/pkg/metamanager/client/serviceaccount.go
index 0323fab40..eeb5e2efa 100644
--- a/edge/pkg/metamanager/client/serviceaccount.go
+++ b/edge/pkg/metamanager/client/serviceaccount.go
@@ -6,14 +6,15 @@ import (
"time"
authenticationv1 "k8s.io/api/authentication/v1"
+ corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/edge/pkg/common/message"
"github.com/kubeedge/kubeedge/edge/pkg/common/modules"
- "github.com/kubeedge/kubeedge/edge/pkg/metamanager"
"github.com/kubeedge/kubeedge/edge/pkg/metamanager/dao"
+ policyv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
)
// ServiceAccountTokenGetter is interface to get client service account token
@@ -85,8 +86,23 @@ func requiresRefresh(tr *authenticationv1.TokenRequest) bool {
return false
}
+// KeyFunc keys should be nonconfidential and safe to log
+func KeyFunc(name, namespace string, tr *authenticationv1.TokenRequest) string {
+ var exp int64
+ if tr.Spec.ExpirationSeconds != nil {
+ exp = *tr.Spec.ExpirationSeconds
+ }
+
+ var ref authenticationv1.BoundObjectReference
+ if tr.Spec.BoundObjectRef != nil {
+ ref = *tr.Spec.BoundObjectRef
+ }
+
+ return fmt.Sprintf("%q/%q/%#v/%#v/%#v", name, namespace, tr.Spec.Audiences, exp, ref)
+}
+
func getTokenLocally(name, namespace string, tr *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
- resKey := metamanager.KeyFunc(name, namespace, tr)
+ resKey := KeyFunc(name, namespace, tr)
metas, err := dao.QueryMeta("key", resKey)
if err != nil {
klog.Errorf("query meta %s failed: %v", resKey, err)
@@ -170,3 +186,66 @@ func handleServiceAccountTokenFromMetaManager(content []byte) (*authenticationv1
}
return &serviceAccount, nil
}
+
+// ServiceAccountGetter is interface to get Client service account
+type ServiceAccountsGetter interface {
+ ServiceAccounts(namespace string) ServiceAccountInterface
+}
+
+// ServiceAccountInterface is interface for Client service account token
+type ServiceAccountInterface interface {
+ Get(name string) (*corev1.ServiceAccount, error)
+}
+
+type serviceAccount struct {
+ namespace string
+}
+
+func newServiceAccount(namespace string) *serviceAccount {
+ return &serviceAccount{namespace: namespace}
+}
+
+func (s *serviceAccount) Get(name string) (*corev1.ServiceAccount, error) {
+ rst, err := dao.QueryMeta("type", model.ResourceTypeSaAccess)
+ if err != nil {
+ return nil, err
+ }
+ for _, v := range *rst {
+ var saAccess policyv1alpha1.ServiceAccountAccess
+ err = json.Unmarshal([]byte(v), &saAccess)
+ if err != nil {
+ klog.Errorf("failed to unmarshal saAccess %v", err)
+ return nil, err
+ }
+ if saAccess.Namespace == s.namespace && saAccess.Spec.ServiceAccount.Name == name {
+ rst := saAccess.Spec.ServiceAccount
+ rst.UID = saAccess.Spec.ServiceAccountUID
+ return &rst, nil
+ }
+ }
+ return nil, fmt.Errorf("serviceaccount %s/%s not found", s.namespace, name)
+}
+
+func CheckTokenExist(token string) bool {
+ if token == "" {
+ return false
+ }
+ metas, err := dao.QueryMeta("type", model.ResourceTypeServiceAccountToken)
+ if err != nil {
+ klog.Errorf("query meta %s failed: %v", model.ResourceTypeServiceAccountToken, err)
+ return false
+ }
+
+ for _, v := range *metas {
+ var tokenRequest authenticationv1.TokenRequest
+ err = json.Unmarshal([]byte(v), &tokenRequest)
+ if err != nil {
+ klog.Errorf("unmarshal resource %s token request failed: %v", model.ResourceTypeServiceAccountToken, err)
+ return false
+ }
+ if tokenRequest.Status.Token == token {
+ return true
+ }
+ }
+ return false
+}
diff --git a/edge/pkg/metamanager/client/serviceaccountaccess.go b/edge/pkg/metamanager/client/serviceaccountaccess.go
new file mode 100644
index 000000000..560be7d9d
--- /dev/null
+++ b/edge/pkg/metamanager/client/serviceaccountaccess.go
@@ -0,0 +1,189 @@
+package client
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ corev1 "k8s.io/api/core/v1"
+ rbacv1 "k8s.io/api/rbac/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ clientset "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/tools/cache"
+ "k8s.io/klog/v2"
+ "k8s.io/kubernetes/pkg/serviceaccount"
+
+ "github.com/kubeedge/beehive/pkg/core/model"
+ "github.com/kubeedge/kubeedge/edge/pkg/metamanager/dao"
+ policyv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
+)
+
+const (
+ roleKind = "Role"
+ clusterRoleKind = "ClusterRole"
+)
+
+type RoleGetter struct {
+}
+
+func (g *RoleGetter) GetRole(namespace, name string) (*rbacv1.Role, error) {
+ rst, err := dao.QueryMeta("type", model.ResourceTypeSaAccess)
+ if err != nil {
+ return nil, err
+ }
+ for _, v := range *rst {
+ var saAccess policyv1alpha1.ServiceAccountAccess
+ err = json.Unmarshal([]byte(v), &saAccess)
+ if err != nil {
+ klog.Errorf("failed to unmarshal saAccess %v", err)
+ return nil, err
+ }
+ for _, rb := range saAccess.Spec.AccessRoleBinding {
+ if rb.RoleBinding.RoleRef.Kind == roleKind && rb.RoleBinding.RoleRef.Name == name &&
+ saAccess.Namespace == namespace {
+ return &rbacv1.Role{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: rb.RoleBinding.RoleRef.Name,
+ Namespace: saAccess.Namespace,
+ },
+ Rules: rb.Rules,
+ }, nil
+ }
+ }
+ }
+ return nil, fmt.Errorf("role %s/%s not found", namespace, name)
+}
+
+type RoleBindingLister struct {
+}
+
+func (l *RoleBindingLister) ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error) {
+ rst, err := dao.QueryMeta("type", model.ResourceTypeSaAccess)
+ if err != nil {
+ return nil, err
+ }
+ var items = make(map[string]struct{})
+ var res []*rbacv1.RoleBinding
+ for _, v := range *rst {
+ var saAccess policyv1alpha1.ServiceAccountAccess
+ err = json.Unmarshal([]byte(v), &saAccess)
+ if err != nil {
+ klog.Errorf("failed to unmarshal saAccess %v", err)
+ return nil, err
+ }
+ for _, rb := range saAccess.Spec.AccessRoleBinding {
+ var tmp = rb.RoleBinding
+ if tmp.Namespace == namespace {
+ key, err := cache.MetaNamespaceKeyFunc(&tmp)
+ if err != nil {
+ continue
+ }
+ if _, ok := items[key]; ok {
+ continue
+ }
+ items[key] = struct{}{}
+ res = append(res, &tmp)
+ }
+ }
+ }
+ return res, nil
+}
+
+type ClusterRoleGetter struct {
+}
+
+func (g *ClusterRoleGetter) GetClusterRole(name string) (*rbacv1.ClusterRole, error) {
+ rst, err := dao.QueryMeta("type", model.ResourceTypeSaAccess)
+ if err != nil {
+ return nil, err
+ }
+ for _, v := range *rst {
+ var saAccess policyv1alpha1.ServiceAccountAccess
+ err = json.Unmarshal([]byte(v), &saAccess)
+ if err != nil {
+ klog.Errorf("failed to unmarshal saAccess %v", err)
+ return nil, err
+ }
+ for _, rb := range saAccess.Spec.AccessRoleBinding {
+ if rb.RoleBinding.RoleRef.Kind == clusterRoleKind && rb.RoleBinding.RoleRef.Name == name {
+ return &rbacv1.ClusterRole{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: rb.RoleBinding.RoleRef.Name,
+ },
+ Rules: rb.Rules,
+ }, nil
+ }
+ }
+ for _, crb := range saAccess.Spec.AccessClusterRoleBinding {
+ if crb.ClusterRoleBinding.RoleRef.Kind == clusterRoleKind && crb.ClusterRoleBinding.RoleRef.Name == name {
+ return &rbacv1.ClusterRole{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: crb.ClusterRoleBinding.RoleRef.Name,
+ },
+ Rules: crb.Rules,
+ }, nil
+ }
+ }
+ }
+ return nil, fmt.Errorf("clusterrole %s not found", name)
+}
+
+type ClusterRoleBindingLister struct {
+}
+
+func (l *ClusterRoleBindingLister) ListClusterRoleBindings() ([]*rbacv1.ClusterRoleBinding, error) {
+ rst, err := dao.QueryMeta("type", model.ResourceTypeSaAccess)
+ if err != nil {
+ klog.Errorf("failed to query meta %v", err)
+ return nil, err
+ }
+ var items = make(map[string]struct{})
+ var res []*rbacv1.ClusterRoleBinding
+ for _, v := range *rst {
+ var saAccess policyv1alpha1.ServiceAccountAccess
+ err = json.Unmarshal([]byte(v), &saAccess)
+ if err != nil {
+ klog.Errorf("failed to unmarshal saAccess %v", err)
+ return nil, err
+ }
+ for _, crb := range saAccess.Spec.AccessClusterRoleBinding {
+ var tmp = crb.ClusterRoleBinding
+ key, err := cache.MetaNamespaceKeyFunc(&tmp)
+ if err != nil {
+ klog.Warningf("failed to get key for clusterrolebinding %v", err)
+ continue
+ }
+ if _, ok := items[key]; ok {
+ continue
+ }
+ items[key] = struct{}{}
+ res = append(res, &tmp)
+ }
+ }
+ return res, nil
+}
+
+// getter implements ServiceAccountTokenGetter using a clientset.Interface
+type getter struct {
+ Client clientset.Interface
+}
+
+// NewGetterFromClient returns a ServiceAccountTokenGetter that
+// uses the specified Client to retrieve service accounts and secrets.
+// The Client should NOT authenticate using a service account token
+// the returned getter will be used to retrieve, or recursion will result.
+func NewGetterFromClient(client clientset.Interface) serviceaccount.ServiceAccountTokenGetter {
+ return getter{Client: client}
+}
+
+func (c getter) GetServiceAccount(namespace, name string) (*corev1.ServiceAccount, error) {
+ return c.Client.CoreV1().ServiceAccounts(namespace).Get(context.Background(), name, metav1.GetOptions{})
+}
+
+func (c getter) GetPod(namespace, name string) (*corev1.Pod, error) {
+ return c.Client.CoreV1().Pods(namespace).Get(context.Background(), name, metav1.GetOptions{})
+}
+
+func (c getter) GetSecret(namespace, name string) (*corev1.Secret, error) {
+ return c.Client.CoreV1().Secrets(namespace).Get(context.Background(), name, metav1.GetOptions{})
+}
diff --git a/edge/pkg/metamanager/constants/constants.go b/edge/pkg/metamanager/constants/constants.go
new file mode 100644
index 000000000..d9a79a898
--- /dev/null
+++ b/edge/pkg/metamanager/constants/constants.go
@@ -0,0 +1,9 @@
+package constants
+
+const (
+ TokenRequestIndexer = "tokenreq"
+ RolesIndexer = "roles"
+ ClusterRolesIndexer = "clusterroles"
+ RolebindingIndexer = "rolebinding"
+ ClusterRolebindingIndexer = "clusterrolebinding"
+)
diff --git a/edge/pkg/metamanager/metaserver/auth/auth.go b/edge/pkg/metamanager/metaserver/auth/auth.go
new file mode 100644
index 000000000..c10ef7552
--- /dev/null
+++ b/edge/pkg/metamanager/metaserver/auth/auth.go
@@ -0,0 +1,125 @@
+package auth
+
+import (
+ "context"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "gopkg.in/square/go-jose.v2/jwt"
+ utilerrors "k8s.io/apimachinery/pkg/util/errors"
+ "k8s.io/apiserver/pkg/authentication/authenticator"
+ "k8s.io/client-go/tools/cache"
+ "k8s.io/kubernetes/pkg/serviceaccount"
+
+ "github.com/kubeedge/kubeedge/edge/pkg/metamanager/client"
+)
+
+type jwtTokenAuthenticator struct {
+ // TODO implement cache indexer to authenticate token
+ indexer cache.Indexer
+ issuers map[string]bool
+ keys []interface{}
+ validator serviceaccount.Validator
+ implicitAuds authenticator.Audiences
+}
+
+func JWTTokenAuthenticator(indexer cache.Indexer, issuers []string, keys []interface{}, implicitAuds authenticator.Audiences, validator serviceaccount.Validator) authenticator.Token {
+ issuersMap := make(map[string]bool)
+ for _, issuer := range issuers {
+ issuersMap[issuer] = true
+ }
+ return &jwtTokenAuthenticator{
+ indexer: indexer,
+ issuers: issuersMap,
+ keys: keys,
+ implicitAuds: implicitAuds,
+ validator: validator,
+ }
+}
+
+func (j *jwtTokenAuthenticator) hasCorrectIssuer(tokenData string) bool {
+ parts := strings.Split(tokenData, ".")
+ if len(parts) != 3 {
+ return false
+ }
+ payload, err := base64.RawURLEncoding.DecodeString(parts[1])
+ if err != nil {
+ return false
+ }
+ claims := struct {
+ // WARNING: this JWT is not verified. Do not trust these claims.
+ Issuer string `json:"iss"`
+ }{}
+ if err := json.Unmarshal(payload, &claims); err != nil {
+ return false
+ }
+ return j.issuers[claims.Issuer]
+}
+
+func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData string) (*authenticator.Response, bool, error) {
+ if !j.hasCorrectIssuer(tokenData) {
+ return nil, false, nil
+ }
+ public := &jwt.Claims{}
+ private := j.validator.NewPrivateClaims()
+ if err := parseSigned(tokenData, public, private); err != nil {
+ return nil, false, err
+ }
+ if len(j.keys) == 0 {
+ // no public key for decode, auth token is existing in local db
+ if !client.CheckTokenExist(tokenData) {
+ return nil, false, fmt.Errorf("tokenData not found when authenticating")
+ }
+ } else {
+ tok, err := jwt.ParseSigned(tokenData)
+ if err != nil {
+ return nil, false, nil
+ }
+ var (
+ found bool
+ errlist []error
+ )
+ for _, key := range j.keys {
+ if err := tok.Claims(key, public, private); err != nil {
+ errlist = append(errlist, err)
+ continue
+ }
+ found = true
+ break
+ }
+
+ if !found {
+ return nil, false, utilerrors.NewAggregate(errlist)
+ }
+ }
+
+ tokenAudiences := authenticator.Audiences(public.Audience)
+ if len(tokenAudiences) == 0 {
+ tokenAudiences = j.implicitAuds
+ }
+
+ requestedAudiences, ok := authenticator.AudiencesFrom(ctx)
+ if !ok {
+ // default to apiserver audiences
+ requestedAudiences = j.implicitAuds
+ }
+
+ auds := authenticator.Audiences(tokenAudiences).Intersect(requestedAudiences)
+ if len(auds) == 0 && len(j.implicitAuds) != 0 {
+ return nil, false, fmt.Errorf("tokenData audiences %q is invalid for the target audiences %q", tokenAudiences, requestedAudiences)
+ }
+
+ // If we get here, we have a tokenData with a recognized signature and
+ // issuer string.
+ sa, err := j.validator.Validate(ctx, tokenData, public, private)
+ if err != nil {
+ return nil, false, err
+ }
+
+ return &authenticator.Response{
+ User: sa.UserInfo(),
+ Audiences: auds,
+ }, true, nil
+}
diff --git a/edge/pkg/metamanager/metaserver/auth/auth_test.go b/edge/pkg/metamanager/metaserver/auth/auth_test.go
new file mode 100644
index 000000000..50d4808b3
--- /dev/null
+++ b/edge/pkg/metamanager/metaserver/auth/auth_test.go
@@ -0,0 +1,418 @@
+package auth
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "reflect"
+ "testing"
+
+ "github.com/golang/mock/gomock"
+ "gopkg.in/square/go-jose.v2"
+ authenticationv1 "k8s.io/api/authentication/v1"
+ v1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apiserver/pkg/authentication/authenticator"
+ clientset "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/kubernetes/fake"
+ "k8s.io/client-go/util/keyutil"
+ "k8s.io/kubernetes/pkg/serviceaccount"
+
+ "github.com/kubeedge/beehive/pkg/core/model"
+ "github.com/kubeedge/kubeedge/edge/mocks/beego"
+ "github.com/kubeedge/kubeedge/edge/pkg/common/dbm"
+ "github.com/kubeedge/kubeedge/edge/pkg/metamanager/client"
+ "github.com/kubeedge/kubeedge/edge/pkg/metamanager/dao"
+)
+
+const otherPublicKey = `-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArXz0QkIG1B5Bj2/W69GH
+rsm5e+RC3kE+VTgocge0atqlLBek35tRqLgUi3AcIrBZ/0YctMSWDVcRt5fkhWwe
+Lqjj6qvAyNyOkrkBi1NFDpJBjYJtuKHgRhNxXbOzTSNpdSKXTfOkzqv56MwHOP25
+yP/NNAODUtr92D5ySI5QX8RbXW+uDn+ixul286PBW/BCrE4tuS88dA0tYJPf8LCu
+sqQOwlXYH/rNUg4Pyl9xxhR5DIJR0OzNNfChjw60zieRIt2LfM83fXhwk8IxRGkc
+gPZm7ZsipmfbZK2Tkhnpsa4QxDg7zHJPMsB5kxRXW0cQipXcC3baDyN9KBApNXa0
+PwIDAQAB
+-----END PUBLIC KEY-----`
+
+const rsaPublicKey = `-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA249XwEo9k4tM8fMxV7zx
+OhcrP+WvXn917koM5Qr2ZXs4vo26e4ytdlrV0bQ9SlcLpQVSYjIxNfhTZdDt+ecI
+zshKuv1gKIxbbLQMOuK1eA/4HALyEkFgmS/tleLJrhc65tKPMGD+pKQ/xhmzRuCG
+51RoiMgbQxaCyYxGfNLpLAZK9L0Tctv9a0mJmGIYnIOQM4kC1A1I1n3EsXMWmeJU
+j7OTh/AjjCnMnkgvKT2tpKxYQ59PgDgU8Ssc7RDSmSkLxnrv+OrN80j6xrw0OjEi
+B4Ycr0PqfzZcvy8efTtFQ/Jnc4Bp1zUtFXt7+QeevePtQ2EcyELXE0i63T1CujRM
+WwIDAQAB
+-----END PUBLIC KEY-----
+`
+
+// Obtained by:
+//
+// 1. Serializing rsaPublicKey as DER
+// 2. Taking the SHA256 of the DER bytes
+// 3. URLSafe Base64-encoding the sha bytes
+const rsaKeyID = "JHJehTTTZlsspKHT-GaJxK7Kd1NQgZJu3fyK6K_QDYU"
+
+// Fake value for testing.
+const rsaPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA249XwEo9k4tM8fMxV7zxOhcrP+WvXn917koM5Qr2ZXs4vo26
+e4ytdlrV0bQ9SlcLpQVSYjIxNfhTZdDt+ecIzshKuv1gKIxbbLQMOuK1eA/4HALy
+EkFgmS/tleLJrhc65tKPMGD+pKQ/xhmzRuCG51RoiMgbQxaCyYxGfNLpLAZK9L0T
+ctv9a0mJmGIYnIOQM4kC1A1I1n3EsXMWmeJUj7OTh/AjjCnMnkgvKT2tpKxYQ59P
+gDgU8Ssc7RDSmSkLxnrv+OrN80j6xrw0OjEiB4Ycr0PqfzZcvy8efTtFQ/Jnc4Bp
+1zUtFXt7+QeevePtQ2EcyELXE0i63T1CujRMWwIDAQABAoIBAHJx8GqyCBDNbqk7
+e7/hI9iE1S10Wwol5GH2RWxqX28cYMKq+8aE2LI1vPiXO89xOgelk4DN6urX6xjK
+ZBF8RRIMQy/e/O2F4+3wl+Nl4vOXV1u6iVXMsD6JRg137mqJf1Fr9elg1bsaRofL
+Q7CxPoB8dhS+Qb+hj0DhlqhgA9zG345CQCAds0ZYAZe8fP7bkwrLqZpMn7Dz9WVm
+++YgYYKjuE95kPuup/LtWfA9rJyE/Fws8/jGvRSpVn1XglMLSMKhLd27sE8ZUSV0
+2KUzbfRGE0+AnRULRrjpYaPu0XQ2JjdNvtkjBnv27RB89W9Gklxq821eH1Y8got8
+FZodjxECgYEA93pz7AQZ2xDs67d1XLCzpX84GxKzttirmyj3OIlxgzVHjEMsvw8v
+sjFiBU5xEEQDosrBdSknnlJqyiq1YwWG/WDckr13d8G2RQWoySN7JVmTQfXcLoTu
+YGRiiTuoEi3ab3ZqrgGrFgX7T/cHuasbYvzCvhM2b4VIR3aSxU2DTUMCgYEA4x7J
+T/ErP6GkU5nKstu/mIXwNzayEO1BJvPYsy7i7EsxTm3xe/b8/6cYOz5fvJLGH5mT
+Q8YvuLqBcMwZardrYcwokD55UvNLOyfADDFZ6l3WntIqbA640Ok2g1X4U8J09xIq
+ZLIWK1yWbbvi4QCeN5hvWq47e8sIj5QHjIIjRwkCgYEAyNqjltxFN9zmzPDa2d24
+EAvOt3pYTYBQ1t9KtqImdL0bUqV6fZ6PsWoPCgt+DBuHb+prVPGP7Bkr/uTmznU/
++AlTO+12NsYLbr2HHagkXE31DEXE7CSLa8RNjN/UKtz4Ohq7vnowJvG35FCz/mb3
+FUHbtHTXa2+bGBUOTf/5Hw0CgYBxw0r9EwUhw1qnUYJ5op7OzFAtp+T7m4ul8kCa
+SCL8TxGsgl+SQ34opE775dtYfoBk9a0RJqVit3D8yg71KFjOTNAIqHJm/Vyyjc+h
+i9rJDSXiuczsAVfLtPVMRfS0J9QkqeG4PIfkQmVLI/CZ2ZBmsqEcX+eFs4ZfPLun
+Qsxe2QKBgGuPilIbLeIBDIaPiUI0FwU8v2j8CEQBYvoQn34c95hVQsig/o5z7zlo
+UsO0wlTngXKlWdOcCs1kqEhTLrstf48djDxAYAxkw40nzeJOt7q52ib/fvf4/UBy
+X024wzbiw1q07jFCyfQmODzURAx1VNT7QVUMdz/N8vy47/H40AZJ
+-----END RSA PRIVATE KEY-----
+`
+
+// openssl ecparam -name prime256v1 -genkey -noout -out ecdsa256.pem
+// Fake value for testing.
+const ecdsaPrivateKey = `-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIEZmTmUhuanLjPA2CLquXivuwBDHTt5XYwgIr/kA1LtRoAoGCCqGSM49
+AwEHoUQDQgAEH6cuzP8XuD5wal6wf9M6xDljTOPLX2i8uIp/C/ASqiIGUeeKQtX0
+/IR3qCXyThP/dbCiHrF3v1cuhBOHY8CLVg==
+-----END EC PRIVATE KEY-----`
+
+// openssl ec -in ecdsa256.pem -pubout -out ecdsa256pub.pem
+const ecdsaPublicKey = `-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEH6cuzP8XuD5wal6wf9M6xDljTOPL
+X2i8uIp/C/ASqiIGUeeKQtX0/IR3qCXyThP/dbCiHrF3v1cuhBOHY8CLVg==
+-----END PUBLIC KEY-----`
+
+// Obtained by:
+//
+// 1. Serializing ecdsaPublicKey as DER
+// 2. Taking the SHA256 of the DER bytes
+// 3. URLSafe Base64-encoding the sha bytes
+const ecdsaKeyID = "SoABiieYuNx4UdqYvZRVeuC6SihxgLrhLy9peHMHpTc"
+
+func getPrivateKey(data string) interface{} {
+ key, err := keyutil.ParsePrivateKeyPEM([]byte(data))
+ if err != nil {
+ panic(fmt.Errorf("unexpected error parsing private key: %v", err))
+ }
+ return key
+}
+
+func getPublicKey(data string) interface{} {
+ keys, err := keyutil.ParsePublicKeysPEM([]byte(data))
+ if err != nil {
+ panic(fmt.Errorf("unexpected error parsing public key: %v", err))
+ }
+ return keys[0]
+}
+func TestTokenGenerateAndValidate(t *testing.T) {
+ expectedUserName := "system:serviceaccount:test:my-service-account"
+ expectedUserUID := "12345"
+
+ // Related API objects
+ serviceAccount := &v1.ServiceAccount{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-service-account",
+ UID: "12345",
+ Namespace: "test",
+ },
+ }
+ rsaSecret := &v1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-rsa-secret",
+ Namespace: "test",
+ },
+ }
+ ecdsaSecret := &v1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-ecdsa-secret",
+ Namespace: "test",
+ },
+ }
+
+ // Generate the RSA token
+ rsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(rsaPrivateKey))
+ if err != nil {
+ t.Fatalf("error making generator: %v", err)
+ }
+ rsaToken, err := rsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret))
+ if err != nil {
+ t.Fatalf("error generating token: %v", err)
+ }
+ if len(rsaToken) == 0 {
+ t.Fatalf("no token generated")
+ }
+ rsaSecret.Data = map[string][]byte{
+ "token": []byte(rsaToken),
+ }
+
+ checkJSONWebSignatureHasKeyID(t, rsaToken, rsaKeyID)
+
+ // Generate the ECDSA token
+ ecdsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(ecdsaPrivateKey))
+ if err != nil {
+ t.Fatalf("error making generator: %v", err)
+ }
+ ecdsaToken, err := ecdsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *ecdsaSecret))
+ if err != nil {
+ t.Fatalf("error generating token: %v", err)
+ }
+ if len(ecdsaToken) == 0 {
+ t.Fatalf("no token generated")
+ }
+ ecdsaSecret.Data = map[string][]byte{
+ "token": []byte(ecdsaToken),
+ }
+
+ checkJSONWebSignatureHasKeyID(t, ecdsaToken, ecdsaKeyID)
+
+ // Generate signer with same keys as RSA signer but different unrecognized issuer
+ badIssuerGenerator, err := serviceaccount.JWTTokenGenerator("foo", getPrivateKey(rsaPrivateKey))
+ if err != nil {
+ t.Fatalf("error making generator: %v", err)
+ }
+ badIssuerToken, err := badIssuerGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret))
+ if err != nil {
+ t.Fatalf("error generating token: %v", err)
+ }
+
+ // Generate signer with same keys as RSA signer but different recognized issuer
+ differentIssuerGenerator, err := serviceaccount.JWTTokenGenerator("bar", getPrivateKey(rsaPrivateKey))
+ if err != nil {
+ t.Fatalf("error making generator: %v", err)
+ }
+ differentIssuerToken, err := differentIssuerGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret))
+ if err != nil {
+ t.Fatalf("error generating token: %v", err)
+ }
+
+ testCases := map[string]struct {
+ Client clientset.Interface
+ InitObjs []runtime.Object
+ Keys []interface{}
+ Token string
+
+ ExpectedErr bool
+ ExpectedOK bool
+ ExpectedUserName string
+ ExpectedUserUID string
+ ExpectedGroups []string
+ }{
+ "no keys and indexer": {
+ Token: rsaToken,
+ Client: nil,
+ Keys: []interface{}{},
+ ExpectedErr: true,
+ ExpectedOK: false,
+ },
+ "no keys but indexer": {
+ Token: rsaToken,
+ Client: nil,
+ InitObjs: []runtime.Object{&authenticationv1.TokenRequest{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-name",
+ Namespace: "test",
+ },
+ Status: authenticationv1.TokenRequestStatus{
+ Token: rsaToken,
+ },
+ }},
+ Keys: []interface{}{},
+ ExpectedErr: false,
+ ExpectedOK: true,
+ ExpectedUserName: expectedUserName,
+ ExpectedUserUID: expectedUserUID,
+ ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"},
+ },
+ "invalid keys (rsa)": {
+ Token: rsaToken,
+ Client: nil,
+ Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(ecdsaPublicKey)},
+ ExpectedErr: true,
+ ExpectedOK: false,
+ },
+ "invalid keys (ecdsa)": {
+ Token: ecdsaToken,
+ Client: nil,
+ Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey)},
+ ExpectedErr: true,
+ ExpectedOK: false,
+ },
+ "valid key (rsa)": {
+ Token: rsaToken,
+ Client: nil,
+ Keys: []interface{}{getPublicKey(rsaPublicKey)},
+ ExpectedErr: false,
+ ExpectedOK: true,
+ ExpectedUserName: expectedUserName,
+ ExpectedUserUID: expectedUserUID,
+ ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"},
+ },
+ "valid key, invalid issuer (rsa)": {
+ Token: badIssuerToken,
+ Client: nil,
+ Keys: []interface{}{getPublicKey(rsaPublicKey)},
+ ExpectedErr: false,
+ ExpectedOK: false,
+ },
+ "valid key, different issuer (rsa)": {
+ Token: differentIssuerToken,
+ Client: nil,
+ Keys: []interface{}{getPublicKey(rsaPublicKey)},
+ ExpectedErr: false,
+ ExpectedOK: true,
+ ExpectedUserName: expectedUserName,
+ ExpectedUserUID: expectedUserUID,
+ ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"},
+ },
+ "valid key (ecdsa)": {
+ Token: ecdsaToken,
+ Client: nil,
+ Keys: []interface{}{getPublicKey(ecdsaPublicKey)},
+ ExpectedErr: false,
+ ExpectedOK: true,
+ ExpectedUserName: expectedUserName,
+ ExpectedUserUID: expectedUserUID,
+ ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"},
+ },
+ "rotated keys (rsa)": {
+ Token: rsaToken,
+ Client: nil,
+ Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(ecdsaPublicKey), getPublicKey(rsaPublicKey)},
+ ExpectedErr: false,
+ ExpectedOK: true,
+ ExpectedUserName: expectedUserName,
+ ExpectedUserUID: expectedUserUID,
+ ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"},
+ },
+ "rotated keys (ecdsa)": {
+ Token: ecdsaToken,
+ Client: nil,
+ Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey), getPublicKey(ecdsaPublicKey)},
+ ExpectedErr: false,
+ ExpectedOK: true,
+ ExpectedUserName: expectedUserName,
+ ExpectedUserUID: expectedUserUID,
+ ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"},
+ },
+ "valid lookup": {
+ Token: rsaToken,
+ Client: fake.NewSimpleClientset(serviceAccount, rsaSecret, ecdsaSecret),
+ InitObjs: []runtime.Object{serviceAccount, rsaSecret, ecdsaSecret},
+ Keys: []interface{}{getPublicKey(rsaPublicKey)},
+ ExpectedErr: false,
+ ExpectedOK: true,
+ ExpectedUserName: expectedUserName,
+ ExpectedUserUID: expectedUserUID,
+ ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"},
+ },
+ "invalid secret lookup": {
+ Token: rsaToken,
+ Client: fake.NewSimpleClientset(serviceAccount),
+ InitObjs: []runtime.Object{serviceAccount},
+ Keys: []interface{}{getPublicKey(rsaPublicKey)},
+ ExpectedErr: true,
+ ExpectedOK: false,
+ },
+ "invalid serviceaccount lookup": {
+ Token: rsaToken,
+ Client: fake.NewSimpleClientset(rsaSecret, ecdsaSecret),
+ InitObjs: []runtime.Object{rsaSecret, ecdsaSecret},
+ Keys: []interface{}{getPublicKey(rsaPublicKey)},
+ ExpectedErr: true,
+ ExpectedOK: false,
+ },
+ }
+
+ for k, tc := range testCases {
+ if len(tc.Keys) == 0 {
+ // ormerMock is mocked Ormer implementation
+ var ormerMock *beego.MockOrmer
+ // querySeterMock is mocked QuerySeter implementation
+ var querySeterMock *beego.MockQuerySeter
+
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+ ormerMock = beego.NewMockOrmer(mockCtrl)
+ querySeterMock = beego.NewMockQuerySeter(mockCtrl)
+ dbm.DBAccess = ormerMock
+ var fakeTr = new([]dao.Meta)
+ for _, v := range tc.InitObjs {
+ var tmp = new(dao.Meta)
+ content, _ := json.Marshal(v)
+ tmp.Type = model.ResourceTypeServiceAccountToken
+ tmp.Value = string(content)
+ tmp.Key = fmt.Sprintf("%s/%s/%s", "test", model.ResourceTypeServiceAccountToken, "test-name")
+ *fakeTr = append(*fakeTr, *tmp)
+ }
+ querySeterMock.EXPECT().All(gomock.Any()).SetArg(0, *fakeTr).Return(int64(1), nil).Times(1)
+ querySeterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(querySeterMock).Times(1)
+ ormerMock.EXPECT().QueryTable(gomock.Any()).Return(querySeterMock).Times(1)
+ }
+ auds := authenticator.Audiences{"api"}
+ authn := JWTTokenAuthenticator(nil,
+ []string{serviceaccount.LegacyIssuer, "bar"}, tc.Keys, auds,
+ serviceaccount.NewLegacyValidator(tc.Client != nil, client.NewGetterFromClient(tc.Client)))
+
+ // An invalid, non-JWT token should always fail
+ ctx := authenticator.WithAudiences(context.Background(), auds)
+ if _, ok, err := authn.AuthenticateToken(ctx, "invalid token"); err != nil || ok {
+ t.Errorf("%s: Expected err=nil, ok=false for non-JWT token", k)
+ continue
+ }
+
+ resp, ok, err := authn.AuthenticateToken(ctx, tc.Token)
+ if (err != nil) != tc.ExpectedErr {
+ t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err)
+ continue
+ }
+
+ if ok != tc.ExpectedOK {
+ t.Errorf("%s: Expected ok=%v, got %v", k, tc.ExpectedOK, ok)
+ continue
+ }
+
+ if err != nil || !ok {
+ continue
+ }
+
+ if resp.User.GetName() != tc.ExpectedUserName {
+ t.Errorf("%s: Expected username=%v, got %v", k, tc.ExpectedUserName, resp.User.GetName())
+ continue
+ }
+ if resp.User.GetUID() != tc.ExpectedUserUID {
+ t.Errorf("%s: Expected userUID=%v, got %v", k, tc.ExpectedUserUID, resp.User.GetUID())
+ continue
+ }
+ if !reflect.DeepEqual(resp.User.GetGroups(), tc.ExpectedGroups) {
+ t.Errorf("%s: Expected groups=%v, got %v", k, tc.ExpectedGroups, resp.User.GetGroups())
+ continue
+ }
+ }
+}
+
+func checkJSONWebSignatureHasKeyID(t *testing.T, jwsString string, expectedKeyID string) {
+ jws, err := jose.ParseSigned(jwsString)
+ if err != nil {
+ t.Fatalf("Error checking for key ID: couldn't parse token: %v", err)
+ }
+
+ if jws.Signatures[0].Header.KeyID != expectedKeyID {
+ t.Errorf("Token %q has the wrong KeyID (got %q, want %q)", jwsString, jws.Signatures[0].Header.KeyID, expectedKeyID)
+ }
+}
diff --git a/edge/pkg/metamanager/metaserver/auth/claims.go b/edge/pkg/metamanager/metaserver/auth/claims.go
new file mode 100644
index 000000000..81ab32f84
--- /dev/null
+++ b/edge/pkg/metamanager/metaserver/auth/claims.go
@@ -0,0 +1,169 @@
+package auth
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "time"
+
+ "gopkg.in/square/go-jose.v2"
+ "gopkg.in/square/go-jose.v2/jwt"
+ apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
+ "k8s.io/klog/v2"
+ "k8s.io/kubernetes/pkg/serviceaccount"
+)
+
+// time.Now stubbed out to allow testing
+var now = time.Now
+
+type privateClaims struct {
+ Kubernetes kubernetes `json:"kubernetes.io,omitempty"`
+}
+
+type ref struct {
+ Name string `json:"name,omitempty"`
+ UID string `json:"uid,omitempty"`
+}
+
+type kubernetes struct {
+ Namespace string `json:"namespace,omitempty"`
+ Svcacct ref `json:"serviceaccount,omitempty"`
+ Pod *ref `json:"pod,omitempty"`
+ Secret *ref `json:"secret,omitempty"`
+ WarnAfter jwt.NumericDate `json:"warnafter,omitempty"`
+}
+
+type validator struct {
+ getter serviceaccount.ServiceAccountTokenGetter
+}
+
+var _ = serviceaccount.Validator(&validator{})
+
+func NewValidator(getter serviceaccount.ServiceAccountTokenGetter) serviceaccount.Validator {
+ return &validator{
+ getter: getter,
+ }
+}
+
+func (v *validator) Validate(ctx context.Context, _ string, public *jwt.Claims, privateObj interface{}) (*apiserverserviceaccount.ServiceAccountInfo, error) {
+ private, ok := privateObj.(*privateClaims)
+ if !ok {
+ klog.Errorf("service account jwt validator expected private claim of type *privateClaims but got: %T", privateObj)
+ return nil, fmt.Errorf("service account token claims could not be validated due to unexpected private claim")
+ }
+ nowTime := now()
+ err := public.Validate(jwt.Expected{
+ Time: nowTime,
+ })
+ switch err {
+ case nil:
+ // successful validation
+
+ case jwt.ErrExpired:
+ return nil, fmt.Errorf("service account token has expired")
+
+ case jwt.ErrNotValidYet:
+ return nil, fmt.Errorf("service account token is not valid yet")
+
+ // our current use of jwt.Expected above should make these cases impossible to hit
+ case jwt.ErrInvalidAudience, jwt.ErrInvalidID, jwt.ErrInvalidIssuer, jwt.ErrInvalidSubject:
+ klog.Errorf("service account token claim validation got unexpected validation failure: %v", err)
+ return nil, fmt.Errorf("service account token claims could not be validated: %w", err) // safe to pass these errors back to the user
+
+ default:
+ klog.Errorf("service account token claim validation got unexpected error type: %T", err) // avoid leaking unexpected information into the logs
+ return nil, fmt.Errorf("service account token claims could not be validated due to unexpected validation error") // return an opaque error
+ }
+
+ // consider things deleted prior to now()-leeway to be invalid
+ invalidIfDeletedBefore := nowTime.Add(-jwt.DefaultLeeway)
+ namespace := private.Kubernetes.Namespace
+ saref := private.Kubernetes.Svcacct
+ podref := private.Kubernetes.Pod
+ secref := private.Kubernetes.Secret
+ // Make sure service account still exists (name and UID)
+ serviceAccount, err := v.getter.GetServiceAccount(namespace, saref.Name)
+ if err != nil {
+ klog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, saref.Name, err)
+ return nil, err
+ }
+ if serviceAccount.DeletionTimestamp != nil && serviceAccount.DeletionTimestamp.Time.Before(invalidIfDeletedBefore) {
+ klog.V(4).Infof("Service account has been deleted %s/%s", namespace, saref.Name)
+ return nil, fmt.Errorf("service account %s/%s has been deleted", namespace, saref.Name)
+ }
+ if string(serviceAccount.UID) != saref.UID {
+ klog.V(4).Infof("Service account UID no longer matches %s/%s: %q != %q", namespace, saref.Name, string(serviceAccount.UID), saref.UID)
+ return nil, fmt.Errorf("service account UID (%s) does not match claim (%s)", serviceAccount.UID, saref.UID)
+ }
+
+ if secref != nil {
+ // Make sure token hasn't been invalidated by deletion of the secret
+ secret, err := v.getter.GetSecret(namespace, secref.Name)
+ if err != nil {
+ klog.V(4).Infof("Could not retrieve bound secret %s/%s for service account %s/%s: %v", namespace, secref.Name, namespace, saref.Name, err)
+ return nil, fmt.Errorf("service account token has been invalidated")
+ }
+ if secret.DeletionTimestamp != nil && secret.DeletionTimestamp.Time.Before(invalidIfDeletedBefore) {
+ klog.V(4).Infof("Bound secret is deleted and awaiting removal: %s/%s for service account %s/%s", namespace, secref.Name, namespace, saref.Name)
+ return nil, fmt.Errorf("service account token has been invalidated")
+ }
+ if secref.UID != string(secret.UID) {
+ klog.V(4).Infof("Secret UID no longer matches %s/%s: %q != %q", namespace, secref.Name, string(secret.UID), secref.UID)
+ return nil, fmt.Errorf("secret UID (%s) does not match service account secret ref claim (%s)", secret.UID, secref.UID)
+ }
+ }
+
+ var podName, podUID string
+ if podref != nil {
+ // Make sure token hasn't been invalidated by deletion of the pod
+ pod, err := v.getter.GetPod(namespace, podref.Name)
+ if err != nil {
+ klog.V(4).Infof("Could not retrieve bound pod %s/%s for service account %s/%s: %v", namespace, podref.Name, namespace, saref.Name, err)
+ return nil, fmt.Errorf("service account token has been invalidated")
+ }
+ if pod.DeletionTimestamp != nil && pod.DeletionTimestamp.Time.Before(invalidIfDeletedBefore) {
+ klog.V(4).Infof("Bound pod is deleted and awaiting removal: %s/%s for service account %s/%s", namespace, podref.Name, namespace, saref.Name)
+ return nil, fmt.Errorf("service account token has been invalidated")
+ }
+ if podref.UID != string(pod.UID) {
+ klog.V(4).Infof("Pod UID no longer matches %s/%s: %q != %q", namespace, podref.Name, string(pod.UID), podref.UID)
+ return nil, fmt.Errorf("pod UID (%s) does not match service account pod ref claim (%s)", pod.UID, podref.UID)
+ }
+ podName = podref.Name
+ podUID = podref.UID
+ }
+
+ return &apiserverserviceaccount.ServiceAccountInfo{
+ Namespace: private.Kubernetes.Namespace,
+ Name: private.Kubernetes.Svcacct.Name,
+ UID: private.Kubernetes.Svcacct.UID,
+ PodName: podName,
+ PodUID: podUID,
+ }, nil
+}
+
+func (v *validator) NewPrivateClaims() interface{} {
+ return &privateClaims{}
+}
+
+func parseSigned(tokenData string, dest ...interface{}) error {
+ sig, err := jose.ParseSigned(tokenData)
+ if err != nil {
+ klog.Errorf("failed to parse token: %v", err)
+ return err
+ }
+ claims := func(dest ...interface{}) error {
+ b := sig.UnsafePayloadWithoutVerification()
+ for _, d := range dest {
+ if err := json.Unmarshal(b, d); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ if err := claims(dest...); err != nil {
+ klog.Errorf("failed to parse claims: %v", err)
+ return err
+ }
+ return nil
+}
diff --git a/edge/pkg/metamanager/metaserver/auth/claims_test.go b/edge/pkg/metamanager/metaserver/auth/claims_test.go
new file mode 100644
index 000000000..55ed76309
--- /dev/null
+++ b/edge/pkg/metamanager/metaserver/auth/claims_test.go
@@ -0,0 +1,222 @@
+package auth
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "gopkg.in/square/go-jose.v2/jwt"
+ v1 "k8s.io/api/core/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/kubernetes/pkg/serviceaccount"
+)
+
+func init() {
+ now = func() time.Time {
+ // epoch time: 1514764800
+ return time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC)
+ }
+}
+
+type deletionTestCase struct {
+ name string
+ time *metav1.Time
+ expectErr bool
+}
+
+type claimTestCase struct {
+ name string
+ getter serviceaccount.ServiceAccountTokenGetter
+ private *privateClaims
+ expiry jwt.NumericDate
+ notBefore jwt.NumericDate
+ expectErr string
+}
+
+func TestValidatePrivateClaims(t *testing.T) {
+ var (
+ nowUnix = int64(1514764800)
+
+ serviceAccount = &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "saname", Namespace: "ns", UID: "sauid"}}
+ secret = &v1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "secretname", Namespace: "ns", UID: "secretuid"}}
+ pod = &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "podname", Namespace: "ns", UID: "poduid"}}
+ )
+
+ deletionTestCases := []deletionTestCase{
+ {
+ name: "valid",
+ time: nil,
+ },
+ {
+ name: "deleted now",
+ time: &metav1.Time{Time: time.Unix(nowUnix, 0)},
+ },
+ {
+ name: "deleted near past",
+ time: &metav1.Time{Time: time.Unix(nowUnix-1, 0)},
+ },
+ {
+ name: "deleted near future",
+ time: &metav1.Time{Time: time.Unix(nowUnix+1, 0)},
+ },
+ {
+ name: "deleted now-leeway",
+ time: &metav1.Time{Time: time.Unix(nowUnix-60, 0)},
+ },
+ {
+ name: "deleted now-leeway-1",
+ time: &metav1.Time{Time: time.Unix(nowUnix-61, 0)},
+ expectErr: true,
+ },
+ }
+
+ testcases := []claimTestCase{
+ {
+ name: "good",
+ getter: fakeGetter{serviceAccount, nil, nil},
+ private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Namespace: "ns"}},
+ expectErr: "",
+ },
+ {
+ name: "expired",
+ getter: fakeGetter{serviceAccount, nil, nil},
+ private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Namespace: "ns"}},
+ expiry: jwt.NewNumericDate(now().Add(-1_000 * time.Hour)),
+ expectErr: "service account token has expired",
+ },
+ {
+ name: "not yet valid",
+ getter: fakeGetter{serviceAccount, nil, nil},
+ private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Namespace: "ns"}},
+ notBefore: jwt.NewNumericDate(now().Add(1_000 * time.Hour)),
+ expectErr: "service account token is not valid yet",
+ },
+ {
+ name: "missing serviceaccount",
+ getter: fakeGetter{nil, nil, nil},
+ private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Namespace: "ns"}},
+ expectErr: `serviceaccounts "saname" not found`,
+ },
+ {
+ name: "missing secret",
+ getter: fakeGetter{serviceAccount, nil, nil},
+ private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Secret: &ref{Name: "secretname", UID: "secretuid"}, Namespace: "ns"}},
+ expectErr: "service account token has been invalidated",
+ },
+ {
+ name: "missing pod",
+ getter: fakeGetter{serviceAccount, nil, nil},
+ private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Pod: &ref{Name: "podname", UID: "poduid"}, Namespace: "ns"}},
+ expectErr: "service account token has been invalidated",
+ },
+ {
+ name: "different uid serviceaccount",
+ getter: fakeGetter{serviceAccount, nil, nil},
+ private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauidold"}, Namespace: "ns"}},
+ expectErr: "service account UID (sauid) does not match claim (sauidold)",
+ },
+ {
+ name: "different uid secret",
+ getter: fakeGetter{serviceAccount, secret, nil},
+ private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Secret: &ref{Name: "secretname", UID: "secretuidold"}, Namespace: "ns"}},
+ expectErr: "secret UID (secretuid) does not match service account secret ref claim (secretuidold)",
+ },
+ {
+ name: "different uid pod",
+ getter: fakeGetter{serviceAccount, nil, pod},
+ private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Pod: &ref{Name: "podname", UID: "poduidold"}, Namespace: "ns"}},
+ expectErr: "pod UID (poduid) does not match service account pod ref claim (poduidold)",
+ },
+ }
+
+ for _, deletionTestCase := range deletionTestCases {
+ var (
+ deletedServiceAccount = serviceAccount.DeepCopy()
+ deletedPod = pod.DeepCopy()
+ deletedSecret = secret.DeepCopy()
+ )
+ deletedServiceAccount.DeletionTimestamp = deletionTestCase.time
+ deletedPod.DeletionTimestamp = deletionTestCase.time
+ deletedSecret.DeletionTimestamp = deletionTestCase.time
+
+ var saDeletedErr, deletedErr string
+ if deletionTestCase.expectErr {
+ saDeletedErr = "service account ns/saname has been deleted"
+ deletedErr = "service account token has been invalidated"
+ }
+
+ testcases = append(testcases,
+ claimTestCase{
+ name: deletionTestCase.name + " serviceaccount",
+ getter: fakeGetter{deletedServiceAccount, nil, nil},
+ private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Namespace: "ns"}},
+ expectErr: saDeletedErr,
+ },
+ claimTestCase{
+ name: deletionTestCase.name + " secret",
+ getter: fakeGetter{serviceAccount, deletedSecret, nil},
+ private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Secret: &ref{Name: "secretname", UID: "secretuid"}, Namespace: "ns"}},
+ expectErr: deletedErr,
+ },
+ claimTestCase{
+ name: deletionTestCase.name + " pod",
+ getter: fakeGetter{serviceAccount, nil, deletedPod},
+ private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Pod: &ref{Name: "podname", UID: "poduid"}, Namespace: "ns"}},
+ expectErr: deletedErr,
+ },
+ )
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ v := &validator{tc.getter}
+ expiry := jwt.NumericDate(nowUnix)
+ if tc.expiry != 0 {
+ expiry = tc.expiry
+ }
+ _, err := v.Validate(context.Background(), "", &jwt.Claims{Expiry: expiry, NotBefore: tc.notBefore}, tc.private)
+ if len(tc.expectErr) > 0 {
+ if errStr := errString(err); tc.expectErr != errStr {
+ t.Fatalf("expected error %q but got %q", tc.expectErr, errStr)
+ }
+ } else if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ })
+ }
+}
+
+func errString(err error) string {
+ if err == nil {
+ return ""
+ }
+
+ return err.Error()
+}
+
+type fakeGetter struct {
+ serviceAccount *v1.ServiceAccount
+ secret *v1.Secret
+ pod *v1.Pod
+}
+
+func (f fakeGetter) GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) {
+ if f.serviceAccount == nil {
+ return nil, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "serviceaccounts"}, name)
+ }
+ return f.serviceAccount, nil
+}
+func (f fakeGetter) GetPod(namespace, name string) (*v1.Pod, error) {
+ if f.pod == nil {
+ return nil, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, name)
+ }
+ return f.pod, nil
+}
+func (f fakeGetter) GetSecret(namespace, name string) (*v1.Secret, error) {
+ if f.secret == nil {
+ return nil, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "secrets"}, name)
+ }
+ return f.secret, nil
+}
diff --git a/edge/pkg/metamanager/metaserver/config/config.go b/edge/pkg/metamanager/metaserver/config/config.go
index 85f64678f..acfa5dda8 100644
--- a/edge/pkg/metamanager/metaserver/config/config.go
+++ b/edge/pkg/metamanager/metaserver/config/config.go
@@ -22,5 +22,8 @@ func InitConfigure(c *v1alpha2.MetaServer) {
// so edgehub must register before metamanager
NodeName: edgehubconfig.Config.NodeName,
}
+ if len(Config.APIAudiences) == 0 && len(Config.ServiceAccountIssuers) != 0 {
+ Config.APIAudiences = Config.ServiceAccountIssuers
+ }
})
}
diff --git a/edge/pkg/metamanager/metaserver/kubernetes/storage/storage.go b/edge/pkg/metamanager/metaserver/kubernetes/storage/storage.go
index ca4c9a8b4..e82964112 100644
--- a/edge/pkg/metamanager/metaserver/kubernetes/storage/storage.go
+++ b/edge/pkg/metamanager/metaserver/kubernetes/storage/storage.go
@@ -22,7 +22,6 @@ import (
"github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/agent"
"github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite"
"github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator"
- kefeatures "github.com/kubeedge/kubeedge/pkg/features"
"github.com/kubeedge/kubeedge/pkg/metaserver"
"github.com/kubeedge/kubeedge/pkg/metaserver/util"
)
@@ -104,9 +103,8 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions)
return obj, nil
}()
- // If we get object from cloud failed and RequireAuthorization FeatureGate
- // is not enabled, try to get the object from the local metaManager
- if err != nil && !kefeatures.DefaultFeatureGate.Enabled(kefeatures.RequireAuthorization) {
+ // If we get object from cloud failed, try to get the object from the local metaManager
+ if err != nil {
obj, err = r.Store.Get(ctx, "", options) // name is needless, we get all key information from ctx
if err != nil {
return nil, errors.NewNotFound(schema.GroupResource{Group: info.APIGroup, Resource: info.Resource}, info.Name)
@@ -141,9 +139,8 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption
return list, nil
}()
- // If we list object from cloud failed and RequireAuthorization FeatureGate
- // is not enabled, try to list the object from the local metaManager
- if err != nil && !kefeatures.DefaultFeatureGate.Enabled(kefeatures.RequireAuthorization) {
+ // If we list object from cloud failed, try to list the object from the local metaManager
+ if err != nil {
list, err = r.Store.List(ctx, options)
if err != nil {
return nil, err
@@ -178,11 +175,9 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio
return nil, nil
}()
- // If we watch object from cloud failed and RequireAuthorization FeatureGate
- // is enabled, just return the err
- if err != nil && kefeatures.DefaultFeatureGate.Enabled(kefeatures.RequireAuthorization) {
+ // If we watch object from cloud failed, try to watch the object from the local metaManager
+ if err != nil {
klog.Errorf("[metaserver/reststorage] failed to get a approved application for watch(%v) from cloud application center, %v", info.Path, err)
- return nil, err
}
return r.Store.Watch(ctx, options)
diff --git a/edge/pkg/metamanager/metaserver/server.go b/edge/pkg/metamanager/metaserver/server.go
index 58f42de2f..cb4f2232e 100644
--- a/edge/pkg/metamanager/metaserver/server.go
+++ b/edge/pkg/metamanager/metaserver/server.go
@@ -16,18 +16,26 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
utilwaitgroup "k8s.io/apimachinery/pkg/util/waitgroup"
+ "k8s.io/apiserver/pkg/authentication/authenticator"
+ "k8s.io/apiserver/pkg/authentication/request/bearertoken"
+ "k8s.io/apiserver/pkg/authorization/authorizer"
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/server"
genericfilters "k8s.io/apiserver/pkg/server/filters"
certutil "k8s.io/client-go/util/cert"
+ "k8s.io/client-go/util/keyutil"
"k8s.io/klog/v2"
+ "k8s.io/kubernetes/pkg/api/legacyscheme"
+ "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
- commontypes "github.com/kubeedge/kubeedge/common/types"
"github.com/kubeedge/kubeedge/edge/pkg/common/modules"
+ "github.com/kubeedge/kubeedge/edge/pkg/edged/kubeclientbridge"
"github.com/kubeedge/kubeedge/edge/pkg/edgehub"
+ "github.com/kubeedge/kubeedge/edge/pkg/metamanager/client"
+ "github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/auth"
metaserverconfig "github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/config"
"github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/handlerfactory"
"github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/kubernetes/serializer"
@@ -42,6 +50,35 @@ type MetaServer struct {
Handler http.Handler
NegotiatedSerializer runtime.NegotiatedSerializer
Factory *handlerfactory.Factory
+ Auth *metaServerAuth
+}
+
+type metaServerAuth struct {
+ Authenticator authenticator.Request
+ Authorizer authorizer.Authorizer
+}
+
+func buildAuth() *metaServerAuth {
+ newAuthorizer := rbac.New(
+ &client.RoleGetter{},
+ &client.RoleBindingLister{},
+ &client.ClusterRoleGetter{},
+ &client.ClusterRoleBindingLister{})
+
+ allPublicKeys := []interface{}{}
+ for _, keyfile := range metaserverconfig.Config.ServiceAccountKeyFiles {
+ publicKeys, err := keyutil.PublicKeysFromFile(keyfile)
+ if err != nil {
+ klog.Errorf("Failed to load public key file %s: %v", keyfile, err)
+ return nil
+ }
+ allPublicKeys = append(allPublicKeys, publicKeys...)
+ }
+ tokenAuthenticator := auth.JWTTokenAuthenticator(nil,
+ metaserverconfig.Config.ServiceAccountIssuers, allPublicKeys, metaserverconfig.Config.APIAudiences,
+ auth.NewValidator(client.NewGetterFromClient(kubeclientbridge.NewSimpleClientset(client.New()))))
+ newAuthenticator := bearertoken.New(tokenAuthenticator)
+ return &metaServerAuth{newAuthenticator, newAuthorizer}
}
func NewMetaServer() *MetaServer {
@@ -50,6 +87,7 @@ func NewMetaServer() *MetaServer {
LongRunningFunc: genericfilters.BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString()),
NegotiatedSerializer: serializer.NewNegotiatedSerializer(),
Factory: handlerfactory.NewFactory(),
+ Auth: buildAuth(),
}
return &ls
}
@@ -206,18 +244,13 @@ func BuildHandlerChain(handler http.Handler, ls *MetaServer) http.Handler {
cfg := &server.Config{
LegacyAPIGroupPrefixes: sets.NewString(server.DefaultLegacyAPIPrefix),
}
-
+ if kefeatures.DefaultFeatureGate.Enabled(kefeatures.RequireAuthorization) {
+ handler = genericapifilters.WithAuthorization(handler, ls.Auth.Authorizer, legacyscheme.Codecs)
+ failedHandler := genericapifilters.Unauthorized(legacyscheme.Codecs)
+ handler = genericapifilters.WithAuthentication(handler, ls.Auth.Authenticator, failedHandler, metaserverconfig.Config.APIAudiences)
+ }
handler = genericfilters.WithWaitGroup(handler, ls.LongRunningFunc, ls.HandlerChainWaitGroup)
handler = genericapifilters.WithRequestInfo(handler, server.NewRequestInfoResolver(cfg))
handler = genericfilters.WithPanicRecovery(handler, &apirequest.RequestInfoFactory{})
- handler = WithAuthorizationHeader(handler)
return handler
}
-
-func WithAuthorizationHeader(handler http.Handler) http.Handler {
- return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
- token := request.Header.Get(commontypes.AuthorizationKey)
- request = request.WithContext(context.WithValue(request.Context(), commontypes.AuthorizationKey, token))
- handler.ServeHTTP(writer, request)
- })
-}
diff --git a/edge/pkg/metamanager/process.go b/edge/pkg/metamanager/process.go
index 0f347cd4e..e9aed6d36 100644
--- a/edge/pkg/metamanager/process.go
+++ b/edge/pkg/metamanager/process.go
@@ -16,6 +16,7 @@ import (
"github.com/kubeedge/kubeedge/common/constants"
connect "github.com/kubeedge/kubeedge/edge/pkg/common/cloudconnection"
"github.com/kubeedge/kubeedge/edge/pkg/common/modules"
+ "github.com/kubeedge/kubeedge/edge/pkg/metamanager/client"
metaManagerConfig "github.com/kubeedge/kubeedge/edge/pkg/metamanager/config"
"github.com/kubeedge/kubeedge/edge/pkg/metamanager/dao"
"github.com/kubeedge/kubeedge/edge/pkg/metamanager/metaserver/kubernetes/storage/sqlite/imitator"
@@ -23,19 +24,14 @@ import (
// Constants to check metamanager processes
const (
- OK = "OK"
-
- GroupResource = "resource"
-
+ OK = "OK"
+ GroupResource = "resource"
CloudControllerModel = "edgecontroller"
+ errNotConnected = "not connected"
)
-func feedbackError(err error, info string, request model.Message) {
- errInfo := "Something wrong"
- if err != nil {
- errInfo = fmt.Sprintf(info+": %v", err)
- }
- errResponse := model.NewErrorMessage(&request, errInfo).SetRoute(modules.MetaManagerModuleName, request.GetGroup())
+func feedbackError(err error, request model.Message) {
+ errResponse := model.NewErrorMessage(&request, err.Error()).SetRoute(modules.MetaManagerModuleName, request.GetGroup())
if request.GetSource() == modules.EdgedModuleName {
sendToEdged(errResponse, request.IsSync())
} else {
@@ -64,7 +60,8 @@ func sendToCloud(message *model.Message) {
// Resource format: <namespace>/<restype>[/resid]
// return <reskey, restype, resid>
-func parseResource(resource string) (string, string, string) {
+func parseResource(message *model.Message) (string, string, string) {
+ resource := message.GetResource()
tokens := strings.Split(resource, constants.ResourceSep)
resType := ""
resID := ""
@@ -76,7 +73,26 @@ func parseResource(resource string) (string, string, string) {
resID = tokens[len(tokens)-1]
default:
}
- return resource, resType, resID
+ if resType != model.ResourceTypeServiceAccountToken {
+ return resource, resType, resID
+ }
+ var tokenReq authenticationv1.TokenRequest
+ content, err := message.GetContentData()
+ if err != nil {
+ klog.Errorf("failed to get token request from message %s, error %s", message.GetID(), err)
+ return "", "", ""
+ }
+ if err = json.Unmarshal(content, &tokenReq); err != nil {
+ klog.Errorf("failed to unmarshal token request from message %s, error %s", message.GetID(), err)
+ return "", "", ""
+ }
+
+ trTokens := strings.Split(resource, constants.ResourceSep)
+ if len(trTokens) != 3 {
+ klog.Errorf("failed to get resource %s name and namespace", resource)
+ return "", "", ""
+ }
+ return client.KeyFunc(trTokens[2], trTokens[0], &tokenReq), resType, ""
}
// is resource type require remote query
@@ -95,38 +111,63 @@ func msgDebugInfo(message *model.Message) string {
return fmt.Sprintf("msgID[%s] resource[%s]", message.GetID(), message.GetResource())
}
-func (m *metaManager) processInsert(message model.Message) {
- content, err := message.GetContentData()
- if err != nil {
- klog.Errorf("get insert message content data failed, %s", msgDebugInfo(&message))
- feedbackError(err, "Error to get insert message content data", message)
- return
+func (m *metaManager) handleMessage(message *model.Message) error {
+ resKey, resType, _ := parseResource(message)
+ switch message.GetOperation() {
+ case model.InsertOperation, model.UpdateOperation, model.PatchOperation, model.ResponseOperation:
+ content, err := message.GetContentData()
+ if err != nil {
+ klog.Errorf("get message content data failed, message: %s, error: %s", msgDebugInfo(message), err)
+ return fmt.Errorf("get message content data failed, error: %s", err)
+ }
+ meta := &dao.Meta{
+ Key: resKey,
+ Type: resType,
+ Value: string(content)}
+ err = dao.InsertOrUpdate(meta)
+ if err != nil {
+ klog.Errorf("insert or update meta failed, message: %s, error: %v", msgDebugInfo(message), err)
+ return fmt.Errorf("insert or update meta failed, %s", err)
+ }
+ case model.DeleteOperation:
+ if resType == model.ResourceTypePod {
+ err := processDeletePodDB(*message)
+ if err != nil {
+ klog.Errorf("delete pod meta failed, message %s, err: %v", msgDebugInfo(message), err)
+ return fmt.Errorf("failed to delete pod meta to DB: %s", err)
+ }
+ } else {
+ err := dao.DeleteMetaByKey(resKey)
+ if err != nil {
+ klog.Errorf("delete meta failed, %s", msgDebugInfo(message))
+ return fmt.Errorf("delete meta failed, %s", err)
+ }
+ }
}
+ return nil
+}
+func (m *metaManager) processInsert(message model.Message) {
imitator.DefaultV2Client.Inject(message)
- resKey, resType, _ := parseResource(message.GetResource())
- meta := &dao.Meta{
- Key: resKey,
- Type: resType,
- Value: string(content)}
- err = dao.SaveMeta(meta)
- if err != nil {
- klog.Errorf("save meta failed, %s: %v", msgDebugInfo(&message), err)
- feedbackError(err, "Error to save meta to DB", message)
+ msgSource := message.GetSource()
+ if msgSource == modules.EdgedModuleName {
+ if !connect.IsConnected() {
+ klog.Warningf("process remote failed, req[%s], err: %v", msgDebugInfo(&message), errNotConnected)
+ feedbackError(fmt.Errorf("failed to process remote: %s", errNotConnected), message)
+ return
+ }
+ m.processRemote(message)
return
}
-
- if (resType == model.ResourceTypeNode || resType == model.ResourceTypeLease) && message.GetSource() == modules.EdgedModuleName {
- sendToCloud(&message)
+ if err := m.handleMessage(&message); err != nil {
+ feedbackError(err, message)
return
}
-
- msgSource := message.GetSource()
if msgSource == cloudmodules.DeviceControllerModuleName {
message.SetRoute(modules.MetaGroup, modules.DeviceTwinModuleName)
beehiveContext.Send(modules.DeviceTwinModuleName, message)
- } else {
+ } else if msgSource != cloudmodules.PolicyControllerModuleName {
// Notify edged
sendToEdged(&message, false)
}
@@ -136,38 +177,30 @@ func (m *metaManager) processInsert(message model.Message) {
}
func (m *metaManager) processUpdate(message model.Message) {
- content, err := message.GetContentData()
- if err != nil {
- klog.Errorf("get update message content data failed, %s", msgDebugInfo(&message))
- feedbackError(err, "Error to get update message content data", message)
- return
- }
-
imitator.DefaultV2Client.Inject(message)
- resKey, resType, _ := parseResource(message.GetResource())
-
- meta := &dao.Meta{
- Key: resKey,
- Type: resType,
- Value: string(content)}
- err = dao.InsertOrUpdate(meta)
- if err != nil {
- klog.Errorf("update meta failed, %s", msgDebugInfo(&message))
- feedbackError(err, "Error to update meta to DB", message)
+ msgSource := message.GetSource()
+ _, resType, _ := parseResource(&message)
+ if msgSource == modules.EdgedModuleName && resType == model.ResourceTypeLease {
+ if !connect.IsConnected() {
+ klog.Warningf("process remote failed, req[%s], err: %v", msgDebugInfo(&message), errNotConnected)
+ feedbackError(fmt.Errorf("failed to process remote: %s", errNotConnected), message)
+ return
+ }
+ m.processRemote(message)
+ return
+ }
+ if err := m.handleMessage(&message); err != nil {
+ feedbackError(err, message)
return
}
-
- msgSource := message.GetSource()
switch msgSource {
case modules.EdgedModuleName:
- sendToCloud(&message)
// For pod status update message, we need to wait for the response message
// to ensure that the pod status is correctly reported to the kube-apiserver
- if resType != model.ResourceTypePodStatus && resType != model.ResourceTypeLease {
- resp := message.NewRespByMessage(&message, OK)
- sendToEdged(resp, message.IsSync())
- }
+ sendToCloud(&message)
+ resp := message.NewRespByMessage(&message, OK)
+ sendToEdged(resp, message.IsSync())
case cloudmodules.EdgeControllerModuleName, cloudmodules.DynamicControllerModuleName:
sendToEdged(&message, message.IsSync())
resp := message.NewRespByMessage(&message, OK)
@@ -175,59 +208,29 @@ func (m *metaManager) processUpdate(message model.Message) {
case cloudmodules.DeviceControllerModuleName:
resp := message.NewRespByMessage(&message, OK)
sendToCloud(resp)
-
message.SetRoute(modules.MetaGroup, modules.DeviceTwinModuleName)
beehiveContext.Send(modules.DeviceTwinModuleName, message)
-
+ case cloudmodules.PolicyControllerModuleName:
+ resp := message.NewRespByMessage(&message, OK)
+ sendToCloud(resp)
default:
klog.Errorf("unsupport message source, %s", msgSource)
}
}
func (m *metaManager) processPatch(message model.Message) {
- content, err := message.GetContentData()
- if err != nil {
- klog.Errorf("get patch message content data failed, %s", msgDebugInfo(&message))
- feedbackError(err, "Error to get update message content data", message)
- return
- }
-
- resKey, resType, _ := parseResource(message.GetResource())
-
- meta := &dao.Meta{
- Key: resKey,
- Type: resType,
- Value: string(content)}
- err = dao.InsertOrUpdate(meta)
- if err != nil {
- klog.Errorf("update meta failed, %s", msgDebugInfo(&message))
- feedbackError(err, "Error to update meta to DB", message)
+ if err := m.handleMessage(&message); err != nil {
+ feedbackError(err, message)
return
}
-
sendToCloud(&message)
}
func (m *metaManager) processResponse(message model.Message) {
- content, err := message.GetContentData()
- if err != nil {
- klog.Errorf("get response message content data failed, %s", msgDebugInfo(&message))
- feedbackError(err, "Error to get response message content data", message)
- return
- }
-
- resKey, resType, _ := parseResource(message.GetResource())
- meta := &dao.Meta{
- Key: resKey,
- Type: resType,
- Value: string(content)}
- err = dao.InsertOrUpdate(meta)
- if err != nil {
- klog.Errorf("update meta failed, %s", msgDebugInfo(&message))
- feedbackError(err, "Error to update meta to DB", message)
+ if err := m.handleMessage(&message); err != nil {
+ feedbackError(err, message)
return
}
-
// Notify edged if the data is coming from cloud
if message.GetSource() == CloudControllerModel {
sendToEdged(&message, message.IsSync())
@@ -239,38 +242,27 @@ func (m *metaManager) processResponse(message model.Message) {
func (m *metaManager) processDelete(message model.Message) {
imitator.DefaultV2Client.Inject(message)
- _, resType, _ := parseResource(message.GetResource())
-
+ _, resType, _ := parseResource(&message)
if resType == model.ResourceTypePod && message.GetSource() == modules.EdgedModuleName {
+ // if pod is deleted in K8s, then a new delete message will be sent to edge
sendToCloud(&message)
return
}
- var err error
- if resType == model.ResourceTypePod {
- err = processDeletePodDB(message)
- if err != nil {
- klog.Errorf("delete pod meta failed, %s, err:%v", msgDebugInfo(&message), err)
- feedbackError(err, "Error to delete pod meta to DB", message)
- return
- }
- } else {
- err = dao.DeleteMetaByKey(message.GetResource())
- if err != nil {
- klog.Errorf("delete meta failed, %s", msgDebugInfo(&message))
- feedbackError(err, "Error to delete meta to DB", message)
- return
- }
+ if err := m.handleMessage(&message); err != nil {
+ feedbackError(err, message)
+ return
}
-
msgSource := message.GetSource()
if msgSource == cloudmodules.DeviceControllerModuleName {
message.SetRoute(modules.MetaGroup, modules.DeviceTwinModuleName)
beehiveContext.Send(modules.DeviceTwinModuleName, message)
}
- // Notify edged
- sendToEdged(&message, false)
+ if msgSource != cloudmodules.PolicyControllerModuleName {
+ // Notify edged
+ sendToEdged(&message, false)
+ }
resp := message.NewRespByMessage(&message, OK)
sendToCloud(resp)
}
@@ -314,14 +306,6 @@ func processDeletePodDB(message model.Message) error {
return err
}
- podStatusKey := strings.Replace(message.GetResource(),
- constants.ResourceSep+model.ResourceTypePod+constants.ResourceSep,
- constants.ResourceSep+model.ResourceTypePodStatus+constants.ResourceSep, 1)
- err = dao.DeleteMetaByKey(podStatusKey)
- if err != nil {
- return err
- }
-
podPatchKey := strings.Replace(message.GetResource(),
constants.ResourceSep+model.ResourceTypePod+constants.ResourceSep,
constants.ResourceSep+model.ResourceTypePodPatch+constants.ResourceSep, 1)
@@ -333,55 +317,12 @@ func processDeletePodDB(message model.Message) error {
return nil
}
-// KeyFunc keys should be nonconfidential and safe to log
-func KeyFunc(name, namespace string, tr *authenticationv1.TokenRequest) string {
- var exp int64
- if tr.Spec.ExpirationSeconds != nil {
- exp = *tr.Spec.ExpirationSeconds
- }
-
- var ref authenticationv1.BoundObjectReference
- if tr.Spec.BoundObjectRef != nil {
- ref = *tr.Spec.BoundObjectRef
- }
-
- return fmt.Sprintf("%q/%q/%#v/%#v/%#v", name, namespace, tr.Spec.Audiences, exp, ref)
-}
-
-// getSpecialResourceKey get service account db key
-func getSpecialResourceKey(resType, resKey string, message model.Message) (string, error) {
- if resType != model.ResourceTypeServiceAccountToken {
- return resKey, nil
- }
- tokenReq, ok := message.GetContent().(*authenticationv1.TokenRequest)
- if !ok {
- return "", fmt.Errorf("failed to get resource %s name and namespace", resKey)
- }
- tokens := strings.Split(resKey, constants.ResourceSep)
- if len(tokens) != 3 {
- return "", fmt.Errorf("failed to get resource %s name and namespace", resKey)
- }
- return KeyFunc(tokens[2], tokens[0], tokenReq), nil
-}
-
func (m *metaManager) processQuery(message model.Message) {
- resKey, resType, resID := parseResource(message.GetResource())
+ resKey, resType, resID := parseResource(&message)
var metas *[]string
var err error
if requireRemoteQuery(resType) && connect.IsConnected() {
- resKey, err = getSpecialResourceKey(resType, resKey, message)
- if err != nil {
- klog.Errorf("failed to get special resource %s key", resKey)
- return
- }
- metas, err = dao.QueryMeta("key", resKey)
- if err != nil || len(*metas) == 0 || resType == model.ResourceTypeNode || resType == constants.ResourceTypeVolumeAttachment || resType == model.ResourceTypeLease {
- m.processRemoteQuery(message)
- } else {
- resp := message.NewRespByMessage(&message, *metas)
- resp.SetRoute(modules.MetaManagerModuleName, resp.GetGroup())
- sendToEdged(resp, message.IsSync())
- }
+ m.processRemote(message)
return
}
@@ -393,7 +334,7 @@ func (m *metaManager) processQuery(message model.Message) {
}
if err != nil {
klog.Errorf("query meta failed, %s", msgDebugInfo(&message))
- feedbackError(err, "Error to query meta in DB", message)
+ feedbackError(fmt.Errorf("failed to query meta in DB: %s", err), message)
} else {
resp := message.NewRespByMessage(&message, *metas)
resp.SetRoute(modules.MetaManagerModuleName, resp.GetGroup())
@@ -401,7 +342,7 @@ func (m *metaManager) processQuery(message model.Message) {
}
}
-func (m *metaManager) processRemoteQuery(message model.Message) {
+func (m *metaManager) processRemote(message model.Message) {
go func() {
// TODO: retry
originalID := message.GetID()
@@ -411,43 +352,50 @@ func (m *metaManager) processRemoteQuery(message model.Message) {
message,
time.Duration(metaManagerConfig.Config.RemoteQueryTimeout)*time.Second)
if err != nil {
- klog.Errorf("remote query failed, req[%s], err: %v", msgDebugInfo(&message), err)
- feedbackError(err, "Error to query meta in DB", message)
+ klog.Errorf("process remote failed, req[%s], err: %v", msgDebugInfo(&message), err)
+ feedbackError(fmt.Errorf("failed to process remote: %s", err), message)
+ return
+ }
+ klog.V(4).Infof("process remote: req[%s], resp[%s]", msgDebugInfo(&message), msgDebugInfo(&resp))
+ content, ok := resp.GetContent().(string)
+ if ok && content == constants.MessageSuccessfulContent {
+ klog.V(4).Infof("process remote successfully")
+ feedbackResponse(&message, originalID, &resp)
return
}
errContent, ok := resp.GetContent().(error)
if ok {
- klog.V(4).Infof("process remote query err: %v", errContent)
+ klog.V(4).Infof("process remote err: %v", errContent)
feedbackResponse(&message, originalID, &resp)
return
}
- klog.V(4).Infof("process remote query: req[%s], resp[%s]", msgDebugInfo(&message), msgDebugInfo(&resp))
- content, err := resp.GetContentData()
- if err != nil {
- klog.Errorf("get remote query response content data failed, %s", msgDebugInfo(&resp))
- feedbackError(err, "Error to get remote query response message content data", message)
- return
+ mapContent, ok := resp.GetContent().(map[string]interface{})
+ if ok && isObjectResp(mapContent) {
+ if mapContent["Err"] != nil {
+ klog.V(4).Infof("process remote objResp err: %v", mapContent["Err"])
+ feedbackResponse(&message, originalID, &resp)
+ return
+ }
+ klog.V(4).Infof("process remote objResp: %+v", mapContent["Object"])
+ resp.Content = mapContent["Object"]
}
-
- resKey, resType, _ := parseResource(message.GetResource())
- resKey, err = getSpecialResourceKey(resType, resKey, message)
- if err != nil {
- klog.Errorf("get remote query response content data failed, %s", msgDebugInfo(&resp))
- feedbackError(err, "Error to get remote query response message content data", message)
+ if err := m.handleMessage(&resp); err != nil {
+ feedbackError(err, message)
return
}
- meta := &dao.Meta{
- Key: resKey,
- Type: resType,
- Value: string(content)}
- err = dao.InsertOrUpdate(meta)
- if err != nil {
- klog.Errorf("update meta failed, %s", msgDebugInfo(&resp))
- }
feedbackResponse(&message, originalID, &resp)
}()
}
+func isObjectResp(data map[string]interface{}) bool {
+ _, ok := data["Object"]
+ if !ok {
+ return false
+ }
+ _, ok = data["Err"]
+ return ok
+}
+
func (m *metaManager) processVolume(message model.Message) {
klog.Info("process volume started")
back, err := beehiveContext.SendSync(modules.EdgedModuleName, message, constants.CSISyncMsgRespTimeout)
diff --git a/edge/pkg/metamanager/process_test.go b/edge/pkg/metamanager/process_test.go
index 59e8a6d95..2bdaf1eea 100644
--- a/edge/pkg/metamanager/process_test.go
+++ b/edge/pkg/metamanager/process_test.go
@@ -23,11 +23,15 @@ import (
"testing"
"github.com/golang/mock/gomock"
+ coordinationv1 "k8s.io/api/coordination/v1"
+ v1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/kubeedge/beehive/pkg/common"
"github.com/kubeedge/beehive/pkg/core"
beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
"github.com/kubeedge/beehive/pkg/core/model"
+ "github.com/kubeedge/kubeedge/common/constants"
"github.com/kubeedge/kubeedge/edge/mocks/beego"
connect "github.com/kubeedge/kubeedge/edge/pkg/common/cloudconnection"
"github.com/kubeedge/kubeedge/edge/pkg/common/dbm"
@@ -47,8 +51,8 @@ const (
ModuleNameEdgeHub = "websocket"
// ModuleNameController is the name of the controller module
ModuleNameController = "edgecontroller"
- // OperationNodeConnection is message with operation publish
- OperationNodeConnection = "publish"
+ marshalError = "get message content data failed, error: marshal message content failed: json: unsupported type: chan int"
+ testHolderIdentity = "edgehub"
)
// errFailedDBOperation is common Database operation fail error
@@ -71,6 +75,7 @@ func TestProcessInsert(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
ormerMock := beego.NewMockOrmer(mockCtrl)
+ rawSetterMock := beego.NewMockRawSeter(mockCtrl)
dbm.DBAccess = ormerMock
meta := newMetaManager(true)
core.Register(meta)
@@ -92,54 +97,119 @@ func TestProcessInsert(t *testing.T) {
ModuleType: common.MsgCtxTypeChannel,
}
beehiveContext.AddModule(edged)
+ connect.SetConnected(true)
//SaveMeta Failed, feedbackError SendToCloud
- ormerMock.EXPECT().Insert(gomock.Any()).Return(int64(1), errFailedDBOperation).Times(1)
- msg := model.NewMessage("").BuildRouter(modules.MetaManagerModuleName, GroupResource, model.ResourceTypePodStatus, model.InsertOperation)
+ rawSetterMock.EXPECT().Exec().Return(nil, errFailedDBOperation).Times(1)
+ ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
+
+ msg := model.NewMessage("").BuildRouter(modules.MetaManagerModuleName, GroupResource, model.ResourceTypeConfigmap, model.InsertOperation)
meta.processInsert(*msg)
- //beehiveContext.Send(MetaManagerModuleName, *msg)
message, err := beehiveContext.Receive(ModuleNameEdgeHub)
- t.Run("EdgeHubChannelRegistration", func(t *testing.T) {
+ t.Run("insert error", func(t *testing.T) {
if err != nil {
t.Errorf("EdgeHub Channel not found: %v", err)
return
}
- want := "Error to save meta to DB: " + FailedDBOperation
+ want := "insert or update meta failed, " + FailedDBOperation
if message.GetContent() != want {
t.Errorf("Wrong Error message received : Wanted %v and Got %v", want, message.GetContent())
}
})
//SaveMeta Failed, feedbackError SendToEdged and 2 resources
- ormerMock.EXPECT().Insert(gomock.Any()).Return(int64(1), errFailedDBOperation).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypePodStatus+"/secondRes", model.InsertOperation)
+ ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
+ rawSetterMock.EXPECT().Exec().Return(nil, errFailedDBOperation).Times(1)
+
+ resource := fmt.Sprintf("%s/%s/%s", "default", model.ResourceTypeLease, "leaseName")
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, resource, model.InsertOperation)
meta.processInsert(*msg)
+ message, err = beehiveContext.Receive(ModuleNameEdgeHub)
+ if err != nil {
+ t.Errorf("EdgeHub Channel not found: %v", err)
+ return
+ }
+ message.Header.ParentID = message.GetID()
+ message.Content = &coordinationv1.Lease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "leaseName",
+ Namespace: "default",
+ },
+ }
+ beehiveContext.SendResp(message)
message, err = beehiveContext.Receive(ModuleNameEdged)
- t.Run("ErrorMessageToEdged", func(t *testing.T) {
+ t.Run("Insert failed", func(t *testing.T) {
if err != nil {
t.Errorf("EdgeD Channel not found: %v", err)
return
}
- want := "Error to save meta to DB: " + FailedDBOperation
+ want := "insert or update meta failed, " + FailedDBOperation
if message.GetContent() != want {
t.Errorf("Wrong Error message received : Wanted %v and Got %v", want, message.GetContent())
}
})
+ //SaveMeta Failed, feedbackError SendToEdged and 2 resources
+ ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
+ rawSetterMock.EXPECT().Exec().Return(nil, nil).Times(1)
+
+ resource = fmt.Sprintf("%s/%s/%s", "default", model.ResourceTypeLease, "leaseName")
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, resource, model.InsertOperation)
+ meta.processInsert(*msg)
+ message, err = beehiveContext.Receive(ModuleNameEdgeHub)
+ if err != nil {
+ t.Errorf("EdgeHub Channel not found: %v", err)
+ return
+ }
+ message.Header.ParentID = message.GetID()
+ message.Content = &coordinationv1.Lease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "leaseName",
+ Namespace: "default",
+ },
+ }
+ beehiveContext.SendResp(message)
+ message, err = beehiveContext.Receive(ModuleNameEdged)
+ beehiveContext.Receive(ModuleNameEdgeHub)
+ t.Run("Insert successfully", func(t *testing.T) {
+ if err != nil {
+ t.Errorf("EdgeD Channel not found: %v", err)
+ return
+ }
+ leaseBytes, _ := message.GetContentData()
+ lease := &coordinationv1.Lease{}
+ err = json.Unmarshal(leaseBytes, lease)
+ if err != nil {
+ t.Errorf("Unmarshal lease failed: %v", err)
+ return
+ }
+ if lease.Name != "leaseName" || lease.Namespace != "default" {
+ t.Errorf("Wrong lease received : Wanted leaseName and Got %v", lease.Name)
+ }
+ })
+
//jsonMarshall fail
- msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypePodStatus, model.InsertOperation).FillBody(make(chan int))
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypeLease, model.InsertOperation).FillBody(make(chan int))
meta.processInsert(*msg)
- message, _ = beehiveContext.Receive(ModuleNameEdged)
+ message, err = beehiveContext.Receive(ModuleNameEdgeHub)
+ if err != nil {
+ t.Errorf("EdgeHub Channel not found: %v", err)
+ return
+ }
+ message.Header.ParentID = message.GetID()
+ beehiveContext.SendResp(message)
+ message, err = beehiveContext.Receive(ModuleNameEdged)
t.Run("MarshallFail", func(t *testing.T) {
- want := "Error to get insert message content data: marshal message content failed: json: unsupported type: chan int"
- if message.GetContent() != want {
- t.Errorf("Wrong Error message received : Wanted %v and Got %v", want, message.GetContent())
+ if message.GetContent() != marshalError {
+ t.Errorf("Wrong Error message received : Wanted %v and Got %v", marshalError, message.GetContent())
}
})
//Successful Case and 3 resources
- ormerMock.EXPECT().Insert(gomock.Any()).Return(int64(1), nil).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypePodStatus+"/secondRes"+"/thirdRes", model.InsertOperation)
+ ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
+ rawSetterMock.EXPECT().Exec().Return(nil, nil).Times(1)
+
+ msg = model.NewMessage("").BuildRouter(ModuleNameController, GroupResource, model.ResourceTypeLease+"/secondRes"+"/thirdRes", model.InsertOperation)
meta.processInsert(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdged)
t.Run("InsertMessageToEdged", func(t *testing.T) {
@@ -184,26 +254,29 @@ func TestProcessUpdate(t *testing.T) {
ModuleType: common.MsgCtxTypeChannel,
}
beehiveContext.AddModule(edged)
+ connect.SetConnected(true)
//jsonMarshall fail
- msg := model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypePodStatus, model.UpdateOperation).FillBody(make(chan int))
+ msg := model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, "namespace/"+model.ResourceTypeLease+"/id", model.UpdateOperation).FillBody(make(chan int))
meta.processUpdate(*msg)
- message, _ := beehiveContext.Receive(ModuleNameEdged)
+ message, _ := beehiveContext.Receive(ModuleNameEdgeHub)
+ message.Header.ParentID = message.GetID()
+ beehiveContext.SendResp(message)
+ message, _ = beehiveContext.Receive(ModuleNameEdged)
t.Run("MarshallFail", func(t *testing.T) {
- want := "Error to get update message content data: marshal message content failed: json: unsupported type: chan int"
- if message.GetContent() != want {
- t.Errorf("Wrong Error message received : Wanted %v and Got %v", want, message.GetContent())
+ if message.GetContent() != marshalError {
+ t.Errorf("Wrong Error message received : Wanted %v and Got %v", marshalError, message.GetContent())
}
})
//Database save error
rawSetterMock.EXPECT().Exec().Return(nil, errFailedDBOperation).Times(1)
ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypePodStatus, model.UpdateOperation)
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypeNode, model.UpdateOperation)
meta.processUpdate(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdged)
t.Run("DatabaseSaveError", func(t *testing.T) {
- want := "Error to update meta to DB: " + FailedDBOperation
+ want := "insert or update meta failed, " + FailedDBOperation
if message.GetContent() != want {
t.Errorf("Wrong Error message received : Wanted %v and Got %v", want, message.GetContent())
}
@@ -212,7 +285,7 @@ func TestProcessUpdate(t *testing.T) {
//Success Case Source Edged, sync = true
ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
rawSetterMock.EXPECT().Exec().Return(nil, nil).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypePodStatus, model.UpdateOperation)
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypeLease, model.UpdateOperation)
meta.processUpdate(*msg)
edgehubMsg, _ := beehiveContext.Receive(ModuleNameEdgeHub)
t.Run("SuccessSourceEdgedReceiveEdgehub", func(t *testing.T) {
@@ -232,7 +305,7 @@ func TestProcessUpdate(t *testing.T) {
//Success Case Source CloudControllerModel
ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
rawSetterMock.EXPECT().Exec().Return(nil, nil).Times(1)
- msg = model.NewMessage("").BuildRouter(CloudControllerModel, GroupResource, model.ResourceTypePodStatus, model.UpdateOperation)
+ msg = model.NewMessage("").BuildRouter(CloudControllerModel, GroupResource, model.ResourceTypeLease, model.UpdateOperation)
meta.processUpdate(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdged)
t.Run("SuccessSend[CloudController->Edged]", func(t *testing.T) {
@@ -248,6 +321,90 @@ func TestProcessUpdate(t *testing.T) {
t.Errorf("Wrong message received : Wanted %v and Got %v", want, message.GetContent())
}
})
+
+ ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
+ rawSetterMock.EXPECT().Exec().Return(nil, errFailedDBOperation).Times(1)
+ resource := fmt.Sprintf("%s/%s/%s", "default", model.ResourceTypeLease, "leaseName2")
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, resource, model.UpdateOperation)
+ meta.processUpdate(*msg)
+ message, err := beehiveContext.Receive(ModuleNameEdgeHub)
+ if err != nil {
+ t.Errorf("EdgeHub Channel not found: %v", err)
+ return
+ }
+ message.Header.ParentID = message.GetID()
+ holderIdentity := testHolderIdentity
+ message.Content = &coordinationv1.Lease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "leaseName2",
+ Namespace: "default",
+ },
+ Spec: coordinationv1.LeaseSpec{
+ HolderIdentity: &holderIdentity,
+ },
+ }
+ beehiveContext.SendResp(message)
+ message, err = beehiveContext.Receive(ModuleNameEdged)
+ t.Run("Update failed", func(t *testing.T) {
+ if err != nil {
+ t.Errorf("EdgeD Channel not found: %v", err)
+ return
+ }
+ want := "insert or update meta failed, " + FailedDBOperation
+ if message.GetContent() != want {
+ t.Errorf("Wrong Error message received : Wanted %v and Got %v", want, message.GetContent())
+ }
+ })
+
+ ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
+ rawSetterMock.EXPECT().Exec().Return(nil, nil).Times(1)
+ resource = fmt.Sprintf("%s/%s/%s", "default", model.ResourceTypeLease, "leaseName2")
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, resource, model.UpdateOperation)
+ meta.processUpdate(*msg)
+ message, err = beehiveContext.Receive(ModuleNameEdgeHub)
+ if err != nil {
+ t.Errorf("EdgeHub Channel not found: %v", err)
+ return
+ }
+ message.Header.ParentID = message.GetID()
+ holderIdentity = testHolderIdentity
+ message.Content = &coordinationv1.Lease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "leaseName2",
+ Namespace: "default",
+ },
+ Spec: coordinationv1.LeaseSpec{
+ HolderIdentity: &holderIdentity,
+ },
+ }
+ beehiveContext.SendResp(message)
+ message, err = beehiveContext.Receive(ModuleNameEdged)
+ t.Run("Update successfully", func(t *testing.T) {
+ if err != nil {
+ t.Errorf("EdgeD Channel not found: %v", err)
+ return
+ }
+ leBytes, err := message.GetContentData()
+ if err != nil {
+ t.Errorf("Error getting lease data: %v", err)
+ return
+ }
+ var le = &coordinationv1.Lease{}
+ err = json.Unmarshal(leBytes, le)
+ if err != nil {
+ t.Errorf("Error unmarshal lease: %v", err)
+ return
+ }
+
+ if le.Spec.HolderIdentity == nil {
+ t.Errorf("Error getting lease holderIdentity")
+ return
+ }
+ if *le.Spec.HolderIdentity != testHolderIdentity {
+ t.Errorf("Error getting lease holderIdentity, expected edgehub, got %s", *le.Spec.HolderIdentity)
+ return
+ }
+ })
}
// TestProcessResponse is function to test processResponse
@@ -279,25 +436,24 @@ func TestProcessResponse(t *testing.T) {
beehiveContext.AddModule(addEdged)
//jsonMarshall fail
- msg := model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypePodStatus, model.ResponseOperation).FillBody(make(chan int))
+ msg := model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypeLease, model.ResponseOperation).FillBody(make(chan int))
beehiveContext.Send(modules.MetaManagerModuleName, *msg)
meta.processResponse(*msg)
message, _ := beehiveContext.Receive(ModuleNameEdged)
t.Run("MarshallFail", func(t *testing.T) {
- want := "Error to get response message content data: marshal message content failed: json: unsupported type: chan int"
- if message.GetContent() != want {
- t.Errorf("Wrong Error message received : Wanted %v and Got %v", want, message.GetContent())
+ if message.GetContent() != marshalError {
+ t.Errorf("Wrong Error message received : Wanted %v and Got %v", marshalError, message.GetContent())
}
})
//Database save error
ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
rawSetterMock.EXPECT().Exec().Return(nil, errFailedDBOperation).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypePodStatus, model.ResponseOperation)
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypeLease, model.ResponseOperation)
meta.processResponse(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdged)
t.Run("DatabaseSaveError", func(t *testing.T) {
- want := "Error to update meta to DB: " + FailedDBOperation
+ want := "insert or update meta failed, " + FailedDBOperation
if message.GetContent() != want {
t.Errorf("Wrong Error message received : Wanted %v and Got %v", want, message.GetContent())
}
@@ -306,7 +462,7 @@ func TestProcessResponse(t *testing.T) {
//Success Case Source EdgeD
ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
rawSetterMock.EXPECT().Exec().Return(nil, nil).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypePodStatus, model.ResponseOperation)
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, model.ResourceTypeLease, model.ResponseOperation)
meta.processResponse(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdgeHub)
t.Run("SuccessSourceEdged", func(t *testing.T) {
@@ -319,7 +475,7 @@ func TestProcessResponse(t *testing.T) {
//Success Case Source EdgeHub
ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
rawSetterMock.EXPECT().Exec().Return(nil, nil).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameController, GroupResource, model.ResourceTypePodStatus, model.ResponseOperation)
+ msg = model.NewMessage("").BuildRouter(ModuleNameController, GroupResource, model.ResourceTypeLease, model.ResponseOperation)
meta.processResponse(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdged)
t.Run("SuccessSourceEdgeHub", func(t *testing.T) {
@@ -362,11 +518,11 @@ func TestProcessDelete(t *testing.T) {
querySetterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(querySetterMock).Times(1)
querySetterMock.EXPECT().Delete().Return(int64(1), errFailedDBOperation).Times(1)
ormerMock.EXPECT().QueryTable(gomock.Any()).Return(querySetterMock).Times(1)
- msg := model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, model.ResourceTypePodStatus, model.DeleteOperation)
+ msg := model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, model.ResourceTypeLease, model.DeleteOperation)
meta.processDelete(*msg)
message, _ := beehiveContext.Receive(ModuleNameEdgeHub)
t.Run("DatabaseDeleteError", func(t *testing.T) {
- want := "Error to delete meta to DB: " + FailedDBOperation
+ want := "delete meta failed, " + FailedDBOperation
if message.GetContent() != want {
t.Errorf("Wrong message received : Wanted %v and Got %v", want, message.GetContent())
}
@@ -376,7 +532,7 @@ func TestProcessDelete(t *testing.T) {
querySetterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(querySetterMock).Times(1)
querySetterMock.EXPECT().Delete().Return(int64(1), nil).Times(1)
ormerMock.EXPECT().QueryTable(gomock.Any()).Return(querySetterMock).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, model.ResourceTypePodStatus, model.DeleteOperation)
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, model.ResourceTypeLease, model.DeleteOperation)
meta.processDelete(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdged)
t.Run("SuccessSourceEdgeHub", func(t *testing.T) {
@@ -392,17 +548,67 @@ func TestProcessDelete(t *testing.T) {
t.Errorf("Wrong message received : Wanted %v and Got %v", want, message.GetContent())
}
})
-
// Success Case
+ pod := &v1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "nginx",
+ Namespace: "test",
+ UID: "1234567890",
+ },
+ }
+ podBytes, _ := json.Marshal(pod)
resource := fmt.Sprintf("test/%s/nginx", model.ResourceTypePod)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdged, modules.MetaGroup, resource, model.DeleteOperation)
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdged, modules.MetaGroup, resource, model.DeleteOperation).FillBody(podBytes)
meta.processDelete(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdgeHub)
- t.Run("SuccessSourceEdged", func(t *testing.T) {
+
+ t.Run("SuccessDeleteRemotePod", func(t *testing.T) {
want := ModuleNameEdged
if message.GetSource() != want {
t.Errorf("Wrong message received : Wanted from source %v and Got from source %v", want, message.GetSource())
}
+ if message.GetOperation() != model.DeleteOperation {
+ t.Errorf("Wrong message received : Wanted %v and Got %v", constants.MessageSuccessfulContent, message.GetContent())
+ }
+ })
+
+ fakeDao := new([]dao.Meta)
+ fakeDaoArray := make([]dao.Meta, 1)
+ fakeDaoArray[0] = dao.Meta{Key: "Test", Value: string(podBytes)}
+ fakeDao = &fakeDaoArray
+ querySetterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(querySetterMock).Times(3)
+ querySetterMock.EXPECT().All(gomock.Any()).SetArg(0, *fakeDao).Return(int64(1), nil).Times(1)
+ querySetterMock.EXPECT().Delete().Return(int64(1), nil).Times(2)
+ ormerMock.EXPECT().QueryTable(gomock.Any()).Return(querySetterMock).Times(3)
+
+ resource = fmt.Sprintf("test/%s/nginx", model.ResourceTypePod)
+ msg = model.NewMessage("").BuildRouter(ModuleNameController, modules.MetaGroup, resource, model.DeleteOperation).FillBody(podBytes)
+ meta.processDelete(*msg)
+ message, _ = beehiveContext.Receive(ModuleNameEdged)
+ t.Run("SuccessDeletePodToEdged", func(t *testing.T) {
+ want := ModuleNameController
+ if message.GetSource() != want {
+ t.Errorf("Wrong message received : Wanted from source %v and Got from source %v", want, message.GetSource())
+ }
+ if message.GetOperation() != model.DeleteOperation {
+ t.Errorf("Wrong message received : Wanted %v and Got %v", model.DeleteOperation, message.GetOperation())
+ }
+ if message.GetResource() != resource {
+ t.Errorf("Wrong message received resource: Wanted %v and Got %v", resource, message.GetResource())
+ }
+ })
+ message, _ = beehiveContext.Receive(ModuleNameEdgeHub)
+ t.Run("SuccessDeletePodResponse", func(t *testing.T) {
+ want := ModuleNameController
+ if message.GetSource() != want {
+ t.Errorf("Wrong message received : Wanted from source %v and Got from source %v", want, message.GetSource())
+ }
+ if message.GetOperation() != model.ResponseOperation {
+ t.Errorf("Wrong message received : Wanted %v and Got %v", model.ResponseOperation, message.GetOperation())
+ }
+ if message.GetContent() != OK {
+ t.Errorf("Wrong message received : Wanted %v and Got %v", constants.MessageSuccessfulContent, message.GetContent())
+ }
})
}
@@ -412,7 +618,6 @@ func TestProcessQuery(t *testing.T) {
defer mockCtrl.Finish()
ormerMock := beego.NewMockOrmer(mockCtrl)
querySetterMock := beego.NewMockQuerySeter(mockCtrl)
- rawSetterMock := beego.NewMockRawSeter(mockCtrl)
dbm.DBAccess = ormerMock
meta := newMetaManager(true)
core.Register(meta)
@@ -438,27 +643,21 @@ func TestProcessQuery(t *testing.T) {
connect.SetConnected(true)
//process remote query jsonMarshall error
- querySetterMock.EXPECT().All(gomock.Any()).Return(int64(1), errFailedDBOperation).Times(1)
- querySetterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(querySetterMock).Times(1)
- ormerMock.EXPECT().QueryTable(gomock.Any()).Return(querySetterMock).Times(1)
- msg := model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, "test/"+model.ResourceTypeConfigmap, model.QueryOperation)
+ msg := model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, "test/"+model.ResourceTypeConfigmap, model.QueryOperation).FillBody(make(chan int))
meta.processQuery(*msg)
message, _ := beehiveContext.Receive(ModuleNameEdgeHub)
- msg = model.NewMessage(message.GetID()).BuildRouter(ModuleNameEdgeHub, GroupResource, "test/"+model.ResourceTypeConfigmap, model.QueryOperation).FillBody(make(chan int))
- beehiveContext.SendResp(*msg)
+ message.Header.ParentID = message.GetID()
+ message.SetResourceOperation("test/"+model.ResourceTypeConfigmap, model.ResponseOperation)
+ beehiveContext.SendResp(message)
message, _ = beehiveContext.Receive(ModuleNameEdgeHub)
t.Run("ProcessRemoteQueryMarshallFail", func(t *testing.T) {
- want := "Error to get remote query response message content data: marshal message content failed: json: unsupported type: chan int"
- if message.GetContent() != want {
- t.Errorf("Wrong Error message received : Wanted %v and Got %v", want, message.GetContent())
+ if message.GetContent() != marshalError {
+ t.Errorf("Wrong Error message received : Wanted %v and Got %v", marshalError, message.GetContent())
}
})
//process remote query response error content
- querySetterMock.EXPECT().All(gomock.Any()).Return(int64(1), errFailedDBOperation).Times(1)
- querySetterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(querySetterMock).Times(1)
- ormerMock.EXPECT().QueryTable(gomock.Any()).Return(querySetterMock).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, "test/"+model.ResourceTypeConfigmap, model.QueryOperation)
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdged, GroupResource, "test/"+model.ResourceTypeConfigmap, model.QueryOperation)
meta.processQuery(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdgeHub)
msg = model.NewMessage(message.GetID()).BuildRouter(ModuleNameEdgeHub, GroupResource, "test/"+model.ResourceTypeConfigmap, model.QueryOperation).FillBody(fmt.Errorf("test"))
@@ -477,11 +676,6 @@ func TestProcessQuery(t *testing.T) {
})
//process remote query db fail
- rawSetterMock.EXPECT().Exec().Return(nil, errFailedDBOperation).Times(1)
- ormerMock.EXPECT().Raw(gomock.Any(), gomock.Any()).Return(rawSetterMock).Times(1)
- querySetterMock.EXPECT().All(gomock.Any()).Return(int64(1), errFailedDBOperation).Times(1)
- querySetterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(querySetterMock).Times(1)
- ormerMock.EXPECT().QueryTable(gomock.Any()).Return(querySetterMock).Times(1)
msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, "test/"+model.ResourceTypeConfigmap, model.QueryOperation)
meta.processQuery(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdgeHub)
@@ -496,7 +690,7 @@ func TestProcessQuery(t *testing.T) {
}
})
- //No error and connected true
+ // No error and connected true
fakeDao := new([]dao.Meta)
fakeDaoArray := make([]dao.Meta, 1)
fakeDaoArray[0] = dao.Meta{Key: "Test", Value: MessageTest}
@@ -504,10 +698,10 @@ func TestProcessQuery(t *testing.T) {
querySetterMock.EXPECT().All(gomock.Any()).SetArg(0, *fakeDao).Return(int64(1), nil).Times(1)
querySetterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(querySetterMock).Times(1)
ormerMock.EXPECT().QueryTable(gomock.Any()).Return(querySetterMock).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, "test/"+model.ResourceTypeConfigmap, model.QueryOperation)
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, "test/"+model.ResourceTypeRule, model.QueryOperation)
meta.processQuery(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdged)
- t.Run("DatabaseNoErrorAndMetaFound", func(t *testing.T) {
+ t.Run("LocalDatabaseNoErrorAndMetaFound", func(t *testing.T) {
want := make([]string, 1)
want[0] = MessageTest
bytesWant, _ := json.Marshal(want)
@@ -518,16 +712,14 @@ func TestProcessQuery(t *testing.T) {
})
//ResId Nil database error
- msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, model.ResourceTypePodStatus, OperationNodeConnection).FillBody(connect.CloudDisconnected)
-
querySetterMock.EXPECT().All(gomock.Any()).Return(int64(1), errFailedDBOperation).Times(1)
querySetterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(querySetterMock).Times(1)
ormerMock.EXPECT().QueryTable(gomock.Any()).Return(querySetterMock).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, model.ResourceTypeConfigmap, model.QueryOperation)
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, "test/"+model.ResourceTypeRule, model.QueryOperation)
meta.processQuery(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdgeHub)
t.Run("ResIDNilDatabaseError", func(t *testing.T) {
- want := "Error to query meta in DB: " + FailedDBOperation
+ want := "failed to query meta in DB: " + FailedDBOperation
if message.GetContent() != want {
t.Errorf("Wrong message receive : Wanted %v and Got %v", want, message.GetContent())
}
@@ -537,12 +729,11 @@ func TestProcessQuery(t *testing.T) {
querySetterMock.EXPECT().All(gomock.Any()).Return(int64(1), errFailedDBOperation).Times(1)
querySetterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(querySetterMock).Times(1)
ormerMock.EXPECT().QueryTable(gomock.Any()).Return(querySetterMock).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, "test/test/"+model.ResourceTypeConfigmap, model.QueryOperation)
- //beehiveContext.Send(MetaManagerModuleName, *msg)
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, "test/"+model.ResourceTypeRule+"/test", model.QueryOperation)
meta.processQuery(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdgeHub)
t.Run("ResIDNotNilDatabaseError", func(t *testing.T) {
- want := "Error to query meta in DB: " + FailedDBOperation
+ want := "failed to query meta in DB: " + FailedDBOperation
if message.GetContent() != want {
t.Errorf("Wrong message receive : Wanted %v and Got %v", want, message.GetContent())
}
@@ -552,7 +743,7 @@ func TestProcessQuery(t *testing.T) {
querySetterMock.EXPECT().All(gomock.Any()).SetArg(0, *fakeDao).Return(int64(1), nil).Times(1)
querySetterMock.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(querySetterMock).Times(1)
ormerMock.EXPECT().QueryTable(gomock.Any()).Return(querySetterMock).Times(1)
- msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, "test/test/"+model.ResourceTypeConfigmap, model.QueryOperation)
+ msg = model.NewMessage("").BuildRouter(ModuleNameEdgeHub, GroupResource, "test/"+model.ResourceTypeRule+"/test", model.QueryOperation)
//beehiveContext.Send(MetaManagerModuleName, *msg)
meta.processQuery(*msg)
message, _ = beehiveContext.Receive(ModuleNameEdged)
diff --git a/go.mod b/go.mod
index a764770e5..6370a3251 100644
--- a/go.mod
+++ b/go.mod
@@ -58,6 +58,7 @@ require (
require (
github.com/pkg/errors v0.9.1
+ gopkg.in/square/go-jose.v2 v2.5.1
gopkg.in/yaml.v3 v3.0.1
)
@@ -246,7 +247,6 @@ require (
gopkg.in/gorp.v1 v1.7.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
- gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
gopkg.in/warnings.v0 v0.1.1 // indirect
diff --git a/hack/generate-crds.sh b/hack/generate-crds.sh
index ce851073c..0fcb0f4ca 100755
--- a/hack/generate-crds.sh
+++ b/hack/generate-crds.sh
@@ -23,6 +23,7 @@ CRD_OUTPUTS=build/crds
DEVICES_VERSION=v1alpha2
OPERATIONS_VERSION=v1alpha1
RELIABLESYNCS_VERSION=v1alpha1
+SERVICEACCOUNTACCESS_VERSION=v1alpha1
APPS_VERSION=v1alpha1
HELM_CRDS_DIR=manifests/charts/cloudcore/crds
ROUTER_DIR=build/crds/router
@@ -84,6 +85,7 @@ function :copy:to:destination {
mkdir -p ${CRD_OUTPUTS}/devices
mkdir -p ${CRD_OUTPUTS}/reliablesyncs
mkdir -p ${CRD_OUTPUTS}/apps
+ mkdir -p ${CRD_OUTPUTS}/policy
for entry in `ls /tmp/crds/*.yaml`; do
CRD_NAME=$(echo ${entry} | cut -d'.' -f3 | cut -d'_' -f2)
@@ -96,6 +98,10 @@ function :copy:to:destination {
CRD_NAME=$(remove_suffix_s "$CRD_NAME")
cp -v ${entry} ${CRD_OUTPUTS}/apps/apps_${APPS_VERSION}_${CRD_NAME}.yaml
cp -v ${entry} ${HELM_CRDS_DIR}/apps_${APPS_VERSION}_${CRD_NAME}.yaml
+ elif [ "$CRD_NAME" == "serviceaccountaccesses" ]; then
+ CRD_NAME="serviceaccountaccess"
+ cp -v ${entry} ${CRD_OUTPUTS}/policy/policy_${SERVICEACCOUNTACCESS_VERSION}_${CRD_NAME}.yaml
+ cp -v ${entry} ${HELM_CRDS_DIR}/policy_${SERVICEACCOUNTACCESS_VERSION}_${CRD_NAME}.yaml
elif [ "$CRD_NAME" == "clusterobjectsyncs" ]; then
cp -v ${entry} ${CRD_OUTPUTS}/reliablesyncs/cluster_objectsync_${RELIABLESYNCS_VERSION}.yaml
cp -v ${entry} ${HELM_CRDS_DIR}/cluster_objectsync_${RELIABLESYNCS_VERSION}.yaml
diff --git a/hack/local-up-kubeedge.sh b/hack/local-up-kubeedge.sh
index e00c3781b..9535621d4 100755
--- a/hack/local-up-kubeedge.sh
+++ b/hack/local-up-kubeedge.sh
@@ -100,6 +100,11 @@ function create_operation_crd {
kubectl apply -f ${KUBEEDGE_ROOT}/build/crds/operations/operations_v1alpha1_nodeupgradejob.yaml
}
+function create_serviceaccountaccess_crd {
+ echo "creating the saaccess crd..."
+ kubectl apply -f ${KUBEEDGE_ROOT}/build/crds/policy/policy_v1alpha1_serviceaccountaccess.yaml
+}
+
function build_cloudcore {
echo "building the cloudcore..."
make -C "${KUBEEDGE_ROOT}" WHAT="cloudcore"
@@ -267,6 +272,7 @@ create_device_crd
create_objectsync_crd
create_rule_crd
create_operation_crd
+create_serviceaccountaccess_crd
generate_streamserver_cert
diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh
index ec222a0ff..7b534df08 100755
--- a/hack/update-codegen.sh
+++ b/hack/update-codegen.sh
@@ -36,5 +36,5 @@ export GOPATH="${go_path}"
${KUBEEDGE_ROOT}/hack/generate-groups.sh "deepcopy,client,informer,lister" \
github.com/kubeedge/kubeedge/pkg/client github.com/kubeedge/kubeedge/pkg/apis \
-"devices:v1alpha2 reliablesyncs:v1alpha1 rules:v1 apps:v1alpha1 operations:v1alpha1" \
+"devices:v1alpha2 reliablesyncs:v1alpha1 rules:v1 apps:v1alpha1 operations:v1alpha1 policy:v1alpha1" \
--go-header-file ${KUBEEDGE_ROOT}/hack/boilerplate/boilerplate.txt
diff --git a/manifests/charts/cloudcore/crds/policy_v1alpha1_serviceaccountaccess.yaml b/manifests/charts/cloudcore/crds/policy_v1alpha1_serviceaccountaccess.yaml
new file mode 100644
index 000000000..a6a2c27e0
--- /dev/null
+++ b/manifests/charts/cloudcore/crds/policy_v1alpha1_serviceaccountaccess.yaml
@@ -0,0 +1,501 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.6.2
+ creationTimestamp: null
+ name: serviceaccountaccesses.policy.kubeedge.io
+spec:
+ group: policy.kubeedge.io
+ names:
+ kind: ServiceAccountAccess
+ listKind: ServiceAccountAccessList
+ plural: serviceaccountaccesses
+ shortNames:
+ - saaccess
+ singular: serviceaccountaccess
+ scope: Namespaced
+ versions:
+ - name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: ServiceAccountAccess is the Schema for the ServiceAccountAccess
+ API
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this representation
+ of an object. Servers should convert recognized schemas to the latest
+ internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST resource this
+ object represents. Servers may infer this from the endpoint the client
+ submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: Spec represents the specification of rbac.
+ properties:
+ accessClusterRoleBinding:
+ description: AccessClusterRoleBinding represents rbac ClusterRoleBinding
+ plus detailed ClusterRole info.
+ items:
+ description: AccessClusterRoleBinding represents rbac ClusterRoleBinding
+ plus detailed ClusterRole info.
+ properties:
+ clusterRoleBinding:
+ description: ClusterRoleBinding represents rbac ClusterRoleBinding.
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of
+ this representation of an object. Servers should convert
+ recognized schemas to the latest internal value, and may
+ reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST
+ resource this object represents. Servers may infer this
+ from the endpoint the client submits requests to. Cannot
+ be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ description: Standard object's metadata.
+ properties:
+ annotations:
+ additionalProperties:
+ type: string
+ type: object
+ finalizers:
+ items:
+ type: string
+ type: array
+ labels:
+ additionalProperties:
+ type: string
+ type: object
+ name:
+ type: string
+ namespace:
+ type: string
+ type: object
+ roleRef:
+ description: RoleRef can only reference a ClusterRole in
+ the global namespace. If the RoleRef cannot be resolved,
+ the Authorizer must return an error.
+ properties:
+ apiGroup:
+ description: APIGroup is the group for the resource
+ being referenced
+ type: string
+ kind:
+ description: Kind is the type of resource being referenced
+ type: string
+ name:
+ description: Name is the name of resource being referenced
+ type: string
+ required:
+ - apiGroup
+ - kind
+ - name
+ type: object
+ subjects:
+ description: Subjects holds references to the objects the
+ role applies to.
+ items:
+ description: Subject contains a reference to the object
+ or user identities a role binding applies to. This
+ can either hold a direct API object reference, or a
+ value for non-objects such as user and group names.
+ properties:
+ apiGroup:
+ description: APIGroup holds the API group of the referenced
+ subject. Defaults to "" for ServiceAccount subjects.
+ Defaults to "rbac.authorization.k8s.io" for User
+ and Group subjects.
+ type: string
+ kind:
+ description: Kind of object being referenced. Values
+ defined by this API group are "User", "Group", and
+ "ServiceAccount". If the Authorizer does not recognized
+ the kind value, the Authorizer should report an
+ error.
+ type: string
+ name:
+ description: Name of the object being referenced.
+ type: string
+ namespace:
+ description: Namespace of the referenced object. If
+ the object kind is non-namespace, such as "User"
+ or "Group", and this value is not empty the Authorizer
+ should report an error.
+ type: string
+ required:
+ - kind
+ - name
+ type: object
+ type: array
+ required:
+ - roleRef
+ type: object
+ rules:
+ description: Rules contains role rules.
+ items:
+ description: PolicyRule holds information that describes a
+ policy rule, but does not contain information about who
+ the rule applies to or which namespace the rule applies
+ to.
+ properties:
+ apiGroups:
+ description: APIGroups is the name of the APIGroup that
+ contains the resources. If multiple API groups are
+ specified, any action requested against one of the enumerated
+ resources in any API group will be allowed.
+ items:
+ type: string
+ type: array
+ nonResourceURLs:
+ description: NonResourceURLs is a set of partial urls
+ that a user should have access to. *s are allowed,
+ but only as the full, final step in the path Since non-resource
+ URLs are not namespaced, this field is only applicable
+ for ClusterRoles referenced from a ClusterRoleBinding.
+ Rules can either apply to API resources (such as "pods"
+ or "secrets") or non-resource URL paths (such as "/api"), but
+ not both.
+ items:
+ type: string
+ type: array
+ resourceNames:
+ description: ResourceNames is an optional white list of
+ names that the rule applies to. An empty set means
+ that everything is allowed.
+ items:
+ type: string
+ type: array
+ resources:
+ description: Resources is a list of resources this rule
+ applies to. '*' represents all resources.
+ items:
+ type: string
+ type: array
+ verbs:
+ description: Verbs is a list of Verbs that apply to ALL
+ the ResourceKinds contained in this rule. '*' represents
+ all verbs.
+ items:
+ type: string
+ type: array
+ required:
+ - verbs
+ type: object
+ type: array
+ type: object
+ type: array
+ accessRoleBinding:
+ description: AccessRoleBinding represents rbac rolebinding plus detailed
+ role info.
+ items:
+ description: AccessRoleBinding represents rbac rolebinding plus
+ detailed role info.
+ properties:
+ roleBinding:
+ description: RoleBinding represents rbac rolebinding.
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of
+ this representation of an object. Servers should convert
+ recognized schemas to the latest internal value, and may
+ reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST
+ resource this object represents. Servers may infer this
+ from the endpoint the client submits requests to. Cannot
+ be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ description: Standard object's metadata.
+ properties:
+ annotations:
+ additionalProperties:
+ type: string
+ type: object
+ finalizers:
+ items:
+ type: string
+ type: array
+ labels:
+ additionalProperties:
+ type: string
+ type: object
+ name:
+ type: string
+ namespace:
+ type: string
+ type: object
+ roleRef:
+ description: RoleRef can reference a Role in the current
+ namespace or a ClusterRole in the global namespace. If
+ the RoleRef cannot be resolved, the Authorizer must return
+ an error.
+ properties:
+ apiGroup:
+ description: APIGroup is the group for the resource
+ being referenced
+ type: string
+ kind:
+ description: Kind is the type of resource being referenced
+ type: string
+ name:
+ description: Name is the name of resource being referenced
+ type: string
+ required:
+ - apiGroup
+ - kind
+ - name
+ type: object
+ subjects:
+ description: Subjects holds references to the objects the
+ role applies to.
+ items:
+ description: Subject contains a reference to the object
+ or user identities a role binding applies to. This
+ can either hold a direct API object reference, or a
+ value for non-objects such as user and group names.
+ properties:
+ apiGroup:
+ description: APIGroup holds the API group of the referenced
+ subject. Defaults to "" for ServiceAccount subjects.
+ Defaults to "rbac.authorization.k8s.io" for User
+ and Group subjects.
+ type: string
+ kind:
+ description: Kind of object being referenced. Values
+ defined by this API group are "User", "Group", and
+ "ServiceAccount". If the Authorizer does not recognized
+ the kind value, the Authorizer should report an
+ error.
+ type: string
+ name:
+ description: Name of the object being referenced.
+ type: string
+ namespace:
+ description: Namespace of the referenced object. If
+ the object kind is non-namespace, such as "User"
+ or "Group", and this value is not empty the Authorizer
+ should report an error.
+ type: string
+ required:
+ - kind
+ - name
+ type: object
+ type: array
+ required:
+ - roleRef
+ type: object
+ rules:
+ description: Rules contains role rules.
+ items:
+ description: PolicyRule holds information that describes a
+ policy rule, but does not contain information about who
+ the rule applies to or which namespace the rule applies
+ to.
+ properties:
+ apiGroups:
+ description: APIGroups is the name of the APIGroup that
+ contains the resources. If multiple API groups are
+ specified, any action requested against one of the enumerated
+ resources in any API group will be allowed.
+ items:
+ type: string
+ type: array
+ nonResourceURLs:
+ description: NonResourceURLs is a set of partial urls
+ that a user should have access to. *s are allowed,
+ but only as the full, final step in the path Since non-resource
+ URLs are not namespaced, this field is only applicable
+ for ClusterRoles referenced from a ClusterRoleBinding.
+ Rules can either apply to API resources (such as "pods"
+ or "secrets") or non-resource URL paths (such as "/api"), but
+ not both.
+ items:
+ type: string
+ type: array
+ resourceNames:
+ description: ResourceNames is an optional white list of
+ names that the rule applies to. An empty set means
+ that everything is allowed.
+ items:
+ type: string
+ type: array
+ resources:
+ description: Resources is a list of resources this rule
+ applies to. '*' represents all resources.
+ items:
+ type: string
+ type: array
+ verbs:
+ description: Verbs is a list of Verbs that apply to ALL
+ the ResourceKinds contained in this rule. '*' represents
+ all verbs.
+ items:
+ type: string
+ type: array
+ required:
+ - verbs
+ type: object
+ type: array
+ type: object
+ type: array
+ serviceAccount:
+ description: ServiceAccount is one-to-one corresponding relations
+ with the serviceaccountaccess.
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this
+ representation of an object. Servers should convert recognized
+ schemas to the latest internal value, and may reject unrecognized
+ values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ automountServiceAccountToken:
+ description: AutomountServiceAccountToken indicates whether pods
+ running as this service account should have an API token automatically
+ mounted. Can be overridden at the pod level.
+ type: boolean
+ imagePullSecrets:
+ description: 'ImagePullSecrets is a list of references to secrets
+ in the same namespace to use for pulling any images in pods
+ that reference this ServiceAccount. ImagePullSecrets are distinct
+ from Secrets because Secrets can be mounted in the pod, but
+ ImagePullSecrets are only accessed by the kubelet. More info:
+ https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod'
+ items:
+ description: LocalObjectReference contains enough information
+ to let you locate the referenced object inside the same namespace.
+ properties:
+ name:
+ description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ TODO: Add other useful fields. apiVersion, kind, uid?'
+ type: string
+ type: object
+ type: array
+ kind:
+ description: 'Kind is a string value representing the REST resource
+ this object represents. Servers may infer this from the endpoint
+ the client submits requests to. Cannot be updated. In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata'
+ properties:
+ annotations:
+ additionalProperties:
+ type: string
+ type: object
+ finalizers:
+ items:
+ type: string
+ type: array
+ labels:
+ additionalProperties:
+ type: string
+ type: object
+ name:
+ type: string
+ namespace:
+ type: string
+ type: object
+ secrets:
+ description: 'Secrets is the list of secrets allowed to be used
+ by pods running using this ServiceAccount. More info: https://kubernetes.io/docs/concepts/configuration/secret'
+ items:
+ description: 'ObjectReference contains enough information to
+ let you inspect or modify the referred object. --- New uses
+ of this type are discouraged because of difficulty describing
+ its usage when embedded in APIs. 1. Ignored fields. It includes
+ many fields which are not generally honored. For instance,
+ ResourceVersion and FieldPath are both very rarely valid in
+ actual usage. 2. Invalid usage help. It is impossible to
+ add specific help for individual usage. In most embedded
+ usages, there are particular restrictions like, "must
+ refer only to types A and B" or "UID not honored" or "name
+ must be restricted". Those cannot be well described when
+ embedded. 3. Inconsistent validation. Because the usages
+ are different, the validation rules are different by usage,
+ which makes it hard for users to predict what will happen. 4.
+ The fields are both imprecise and overly precise. Kind is
+ not a precise mapping to a URL. This can produce ambiguity during
+ interpretation and require a REST mapping. In most cases,
+ the dependency is on the group,resource tuple and the
+ version of the actual struct is irrelevant. 5. We cannot
+ easily change it. Because this type is embedded in many locations,
+ updates to this type will affect numerous schemas. Don''t
+ make new APIs embed an underspecified API type they do not
+ control. Instead of using this type, create a locally provided
+ and used type that is well-focused on your reference. For
+ example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533
+ .'
+ properties:
+ apiVersion:
+ description: API version of the referent.
+ type: string
+ fieldPath:
+ description: 'If referring to a piece of an object instead
+ of an entire object, this string should contain a valid
+ JSON/Go field access statement, such as desiredState.manifest.containers[2].
+ For example, if the object reference is to a container
+ within a pod, this would take on a value like: "spec.containers{name}"
+ (where "name" refers to the name of the container that
+ triggered the event) or if no container name is specified
+ "spec.containers[2]" (container with index 2 in this pod).
+ This syntax is chosen only to have some well-defined way
+ of referencing a part of an object. TODO: this design
+ is not final and this field is subject to change in the
+ future.'
+ type: string
+ kind:
+ description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ name:
+ description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
+ type: string
+ namespace:
+ description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
+ type: string
+ resourceVersion:
+ description: 'Specific resourceVersion to which this reference
+ is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
+ type: string
+ uid:
+ description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
+ type: string
+ type: object
+ type: array
+ type: object
+ serviceAccountUid:
+ description: ServiceAccountUID is the uid of serviceaccount.
+ type: string
+ type: object
+ status:
+ description: Status represents the node list which store the rules.
+ properties:
+ nodeList:
+ description: NodeList represents the node name which store the rules.
+ items:
+ type: string
+ type: array
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
+status:
+ acceptedNames:
+ kind: ""
+ plural: ""
+ conditions: []
+ storedVersions: []
diff --git a/pkg/apis/componentconfig/edgecore/v1alpha2/default.go b/pkg/apis/componentconfig/edgecore/v1alpha2/default.go
index 7ee335c69..8c2ef5a7e 100644
--- a/pkg/apis/componentconfig/edgecore/v1alpha2/default.go
+++ b/pkg/apis/componentconfig/edgecore/v1alpha2/default.go
@@ -135,11 +135,12 @@ func NewDefaultEdgeCoreConfig() *EdgeCoreConfig {
ContextSendModule: metaconfig.ModuleNameEdgeHub,
RemoteQueryTimeout: constants.DefaultRemoteQueryTimeout,
MetaServer: &MetaServer{
- Enable: false,
- Server: constants.DefaultMetaServerAddr,
- TLSCaFile: constants.DefaultCAFile,
- TLSCertFile: constants.DefaultCertFile,
- TLSPrivateKeyFile: constants.DefaultKeyFile,
+ Enable: false,
+ Server: constants.DefaultMetaServerAddr,
+ TLSCaFile: constants.DefaultCAFile,
+ TLSCertFile: constants.DefaultCertFile,
+ TLSPrivateKeyFile: constants.DefaultKeyFile,
+ ServiceAccountIssuers: []string{constants.DefaultServiceAccountIssuer},
},
},
ServiceBus: &ServiceBus{
diff --git a/pkg/apis/componentconfig/edgecore/v1alpha2/types.go b/pkg/apis/componentconfig/edgecore/v1alpha2/types.go
index 0a93e0cdb..f2c75c108 100644
--- a/pkg/apis/componentconfig/edgecore/v1alpha2/types.go
+++ b/pkg/apis/componentconfig/edgecore/v1alpha2/types.go
@@ -996,11 +996,14 @@ type MetaManager struct {
}
type MetaServer struct {
- Enable bool `json:"enable"`
- Server string `json:"server"`
- TLSCaFile string `json:"tlsCaFile"`
- TLSCertFile string `json:"tlsCertFile"`
- TLSPrivateKeyFile string `json:"tlsPrivateKeyFile"`
+ Enable bool `json:"enable"`
+ Server string `json:"server"`
+ TLSCaFile string `json:"tlsCaFile"`
+ TLSCertFile string `json:"tlsCertFile"`
+ TLSPrivateKeyFile string `json:"tlsPrivateKeyFile"`
+ ServiceAccountIssuers []string `json:"serviceAccountIssuers"`
+ APIAudiences []string `json:"apiAudiences"`
+ ServiceAccountKeyFiles []string `json:"serviceAccountKeyFiles"`
}
// ServiceBus indicates the ServiceBus module config
diff --git a/pkg/apis/policy/v1alpha1/doc.go b/pkg/apis/policy/v1alpha1/doc.go
new file mode 100644
index 000000000..2a61807f1
--- /dev/null
+++ b/pkg/apis/policy/v1alpha1/doc.go
@@ -0,0 +1,21 @@
+/*
+Copyright 2023 The KubeEdge 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 v1alpha1 is the v1alpha1 version of the API.
+// +k8s:openapi-gen=true
+// +k8s:deepcopy-gen=package,register
+// +groupName=policy.kubeedge.io
+package v1alpha1
diff --git a/pkg/apis/policy/v1alpha1/register.go b/pkg/apis/policy/v1alpha1/register.go
new file mode 100644
index 000000000..103bc4fdd
--- /dev/null
+++ b/pkg/apis/policy/v1alpha1/register.go
@@ -0,0 +1,61 @@
+// Copyright 2023 The KubeEdge 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 v1alpha1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+var (
+ SchemeBuilder runtime.SchemeBuilder
+ localSchemeBuilder = &SchemeBuilder
+ AddToScheme = localSchemeBuilder.AddToScheme
+)
+
+const (
+ // GroupName is the group name use in this package.
+ GroupName = "policy.kubeedge.io"
+ // Version is the API version.
+ Version = "v1alpha1"
+)
+
+var (
+ // SchemeGroupVersion is the group version used to register these objects.
+ SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: Version}
+)
+
+func init() {
+ // We only register manually written functions here. The registration of the
+ // generated functions takes place in the generated files. The separation
+ // makes the code compile even when the generated files are missing.
+ localSchemeBuilder.Register(addKnownTypes)
+}
+
+// Resource takes an unqualified resource and returns a Group-qualified GroupResource.
+func Resource(resource string) schema.GroupResource {
+ return SchemeGroupVersion.WithResource(resource).GroupResource()
+}
+
+// addKnownTypes adds the set of types defined in this package to the supplied scheme.
+func addKnownTypes(scheme *runtime.Scheme) error {
+ scheme.AddKnownTypes(SchemeGroupVersion,
+ &ServiceAccountAccess{},
+ &ServiceAccountAccessList{},
+ )
+ metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
+ return nil
+}
diff --git a/pkg/apis/policy/v1alpha1/serviceaccountaccess_types.go b/pkg/apis/policy/v1alpha1/serviceaccountaccess_types.go
new file mode 100644
index 000000000..c052bb82b
--- /dev/null
+++ b/pkg/apis/policy/v1alpha1/serviceaccountaccess_types.go
@@ -0,0 +1,88 @@
+/*
+Copyright 2023 The KubeEdge 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 v1alpha1
+
+import (
+ corev1 "k8s.io/api/core/v1"
+ rbac "k8s.io/api/rbac/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
+)
+
+// +genclient
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:shortName=saaccess
+
+// ServiceAccountAccess is the Schema for the ServiceAccountAccess API
+type ServiceAccountAccess struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ // Spec represents the specification of rbac.
+ // +required
+ Spec AccessSpec `json:"spec,omitempty"`
+
+ // Status represents the node list which store the rules.
+ // +optional
+ Status AccessStatus `json:"status,omitempty"`
+}
+
+// AccessStatus defines the observed state of ServiceAccountAccess
+type AccessStatus struct {
+ // NodeList represents the node name which store the rules.
+ NodeList []string `json:"nodeList,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// ServiceAccountAccessList contains a list of ServiceAccountAccess
+type ServiceAccountAccessList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []ServiceAccountAccess `json:"items"`
+}
+
+// AccessSpec defines the desired state of AccessSpec
+type AccessSpec struct {
+ // ServiceAccount is one-to-one corresponding relations with the serviceaccountaccess.
+ ServiceAccount corev1.ServiceAccount `json:"serviceAccount,omitempty"`
+ // ServiceAccountUID is the uid of serviceaccount.
+ ServiceAccountUID types.UID `json:"serviceAccountUid,omitempty"`
+ // AccessRoleBinding represents rbac rolebinding plus detailed role info.
+ AccessRoleBinding []AccessRoleBinding `json:"accessRoleBinding,omitempty"`
+ // AccessClusterRoleBinding represents rbac ClusterRoleBinding plus detailed ClusterRole info.
+ AccessClusterRoleBinding []AccessClusterRoleBinding `json:"accessClusterRoleBinding,omitempty"`
+}
+
+// AccessRoleBinding represents rbac rolebinding plus detailed role info.
+type AccessRoleBinding struct {
+ // RoleBinding represents rbac rolebinding.
+ RoleBinding rbac.RoleBinding `json:"roleBinding,omitempty"`
+ // Rules contains role rules.
+ Rules []rbac.PolicyRule `json:"rules,omitempty"`
+}
+
+// AccessClusterRoleBinding represents rbac ClusterRoleBinding plus detailed ClusterRole info.
+type AccessClusterRoleBinding struct {
+ // ClusterRoleBinding represents rbac ClusterRoleBinding.
+ ClusterRoleBinding rbac.ClusterRoleBinding `json:"clusterRoleBinding,omitempty"`
+ // Rules contains role rules.
+ Rules []rbac.PolicyRule `json:"rules,omitempty"`
+}
diff --git a/pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go
new file mode 100644
index 000000000..0acd0afd6
--- /dev/null
+++ b/pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go
@@ -0,0 +1,188 @@
+//go:build !ignore_autogenerated
+// +build !ignore_autogenerated
+
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by deepcopy-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ v1 "k8s.io/api/rbac/v1"
+ runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AccessClusterRoleBinding) DeepCopyInto(out *AccessClusterRoleBinding) {
+ *out = *in
+ in.ClusterRoleBinding.DeepCopyInto(&out.ClusterRoleBinding)
+ if in.Rules != nil {
+ in, out := &in.Rules, &out.Rules
+ *out = make([]v1.PolicyRule, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessClusterRoleBinding.
+func (in *AccessClusterRoleBinding) DeepCopy() *AccessClusterRoleBinding {
+ if in == nil {
+ return nil
+ }
+ out := new(AccessClusterRoleBinding)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AccessRoleBinding) DeepCopyInto(out *AccessRoleBinding) {
+ *out = *in
+ in.RoleBinding.DeepCopyInto(&out.RoleBinding)
+ if in.Rules != nil {
+ in, out := &in.Rules, &out.Rules
+ *out = make([]v1.PolicyRule, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessRoleBinding.
+func (in *AccessRoleBinding) DeepCopy() *AccessRoleBinding {
+ if in == nil {
+ return nil
+ }
+ out := new(AccessRoleBinding)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AccessSpec) DeepCopyInto(out *AccessSpec) {
+ *out = *in
+ in.ServiceAccount.DeepCopyInto(&out.ServiceAccount)
+ if in.AccessRoleBinding != nil {
+ in, out := &in.AccessRoleBinding, &out.AccessRoleBinding
+ *out = make([]AccessRoleBinding, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ if in.AccessClusterRoleBinding != nil {
+ in, out := &in.AccessClusterRoleBinding, &out.AccessClusterRoleBinding
+ *out = make([]AccessClusterRoleBinding, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessSpec.
+func (in *AccessSpec) DeepCopy() *AccessSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(AccessSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AccessStatus) DeepCopyInto(out *AccessStatus) {
+ *out = *in
+ if in.NodeList != nil {
+ in, out := &in.NodeList, &out.NodeList
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessStatus.
+func (in *AccessStatus) DeepCopy() *AccessStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(AccessStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServiceAccountAccess) DeepCopyInto(out *ServiceAccountAccess) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ in.Status.DeepCopyInto(&out.Status)
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountAccess.
+func (in *ServiceAccountAccess) DeepCopy() *ServiceAccountAccess {
+ if in == nil {
+ return nil
+ }
+ out := new(ServiceAccountAccess)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ServiceAccountAccess) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServiceAccountAccessList) DeepCopyInto(out *ServiceAccountAccessList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]ServiceAccountAccess, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountAccessList.
+func (in *ServiceAccountAccessList) DeepCopy() *ServiceAccountAccessList {
+ if in == nil {
+ return nil
+ }
+ out := new(ServiceAccountAccessList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ServiceAccountAccessList) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go
index 33ba808de..10908589e 100644
--- a/pkg/client/clientset/versioned/clientset.go
+++ b/pkg/client/clientset/versioned/clientset.go
@@ -25,6 +25,7 @@ import (
appsv1alpha1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/apps/v1alpha1"
devicesv1alpha2 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/devices/v1alpha2"
operationsv1alpha1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/operations/v1alpha1"
+ policyv1alpha1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/policy/v1alpha1"
reliablesyncsv1alpha1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/reliablesyncs/v1alpha1"
rulesv1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/rules/v1"
discovery "k8s.io/client-go/discovery"
@@ -37,6 +38,7 @@ type Interface interface {
AppsV1alpha1() appsv1alpha1.AppsV1alpha1Interface
DevicesV1alpha2() devicesv1alpha2.DevicesV1alpha2Interface
OperationsV1alpha1() operationsv1alpha1.OperationsV1alpha1Interface
+ PolicyV1alpha1() policyv1alpha1.PolicyV1alpha1Interface
ReliablesyncsV1alpha1() reliablesyncsv1alpha1.ReliablesyncsV1alpha1Interface
RulesV1() rulesv1.RulesV1Interface
}
@@ -48,6 +50,7 @@ type Clientset struct {
appsV1alpha1 *appsv1alpha1.AppsV1alpha1Client
devicesV1alpha2 *devicesv1alpha2.DevicesV1alpha2Client
operationsV1alpha1 *operationsv1alpha1.OperationsV1alpha1Client
+ policyV1alpha1 *policyv1alpha1.PolicyV1alpha1Client
reliablesyncsV1alpha1 *reliablesyncsv1alpha1.ReliablesyncsV1alpha1Client
rulesV1 *rulesv1.RulesV1Client
}
@@ -67,6 +70,11 @@ func (c *Clientset) OperationsV1alpha1() operationsv1alpha1.OperationsV1alpha1In
return c.operationsV1alpha1
}
+// PolicyV1alpha1 retrieves the PolicyV1alpha1Client
+func (c *Clientset) PolicyV1alpha1() policyv1alpha1.PolicyV1alpha1Interface {
+ return c.policyV1alpha1
+}
+
// ReliablesyncsV1alpha1 retrieves the ReliablesyncsV1alpha1Client
func (c *Clientset) ReliablesyncsV1alpha1() reliablesyncsv1alpha1.ReliablesyncsV1alpha1Interface {
return c.reliablesyncsV1alpha1
@@ -133,6 +141,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset,
if err != nil {
return nil, err
}
+ cs.policyV1alpha1, err = policyv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
+ if err != nil {
+ return nil, err
+ }
cs.reliablesyncsV1alpha1, err = reliablesyncsv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
@@ -165,6 +177,7 @@ func New(c rest.Interface) *Clientset {
cs.appsV1alpha1 = appsv1alpha1.New(c)
cs.devicesV1alpha2 = devicesv1alpha2.New(c)
cs.operationsV1alpha1 = operationsv1alpha1.New(c)
+ cs.policyV1alpha1 = policyv1alpha1.New(c)
cs.reliablesyncsV1alpha1 = reliablesyncsv1alpha1.New(c)
cs.rulesV1 = rulesv1.New(c)
diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go
index ce1710f79..5da802c6b 100644
--- a/pkg/client/clientset/versioned/fake/clientset_generated.go
+++ b/pkg/client/clientset/versioned/fake/clientset_generated.go
@@ -26,6 +26,8 @@ import (
fakedevicesv1alpha2 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/devices/v1alpha2/fake"
operationsv1alpha1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/operations/v1alpha1"
fakeoperationsv1alpha1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/operations/v1alpha1/fake"
+ policyv1alpha1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/policy/v1alpha1"
+ fakepolicyv1alpha1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/policy/v1alpha1/fake"
reliablesyncsv1alpha1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/reliablesyncs/v1alpha1"
fakereliablesyncsv1alpha1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/reliablesyncs/v1alpha1/fake"
rulesv1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/rules/v1"
@@ -102,6 +104,11 @@ func (c *Clientset) OperationsV1alpha1() operationsv1alpha1.OperationsV1alpha1In
return &fakeoperationsv1alpha1.FakeOperationsV1alpha1{Fake: &c.Fake}
}
+// PolicyV1alpha1 retrieves the PolicyV1alpha1Client
+func (c *Clientset) PolicyV1alpha1() policyv1alpha1.PolicyV1alpha1Interface {
+ return &fakepolicyv1alpha1.FakePolicyV1alpha1{Fake: &c.Fake}
+}
+
// ReliablesyncsV1alpha1 retrieves the ReliablesyncsV1alpha1Client
func (c *Clientset) ReliablesyncsV1alpha1() reliablesyncsv1alpha1.ReliablesyncsV1alpha1Interface {
return &fakereliablesyncsv1alpha1.FakeReliablesyncsV1alpha1{Fake: &c.Fake}
diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go
index 30a08cf8d..80a7b2fcc 100644
--- a/pkg/client/clientset/versioned/fake/register.go
+++ b/pkg/client/clientset/versioned/fake/register.go
@@ -22,6 +22,7 @@ import (
appsv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/apps/v1alpha1"
devicesv1alpha2 "github.com/kubeedge/kubeedge/pkg/apis/devices/v1alpha2"
operationsv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/operations/v1alpha1"
+ policyv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
reliablesyncsv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/reliablesyncs/v1alpha1"
rulesv1 "github.com/kubeedge/kubeedge/pkg/apis/rules/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -38,6 +39,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
appsv1alpha1.AddToScheme,
devicesv1alpha2.AddToScheme,
operationsv1alpha1.AddToScheme,
+ policyv1alpha1.AddToScheme,
reliablesyncsv1alpha1.AddToScheme,
rulesv1.AddToScheme,
}
diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go
index 55b21eafb..766c6f57c 100644
--- a/pkg/client/clientset/versioned/scheme/register.go
+++ b/pkg/client/clientset/versioned/scheme/register.go
@@ -22,6 +22,7 @@ import (
appsv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/apps/v1alpha1"
devicesv1alpha2 "github.com/kubeedge/kubeedge/pkg/apis/devices/v1alpha2"
operationsv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/operations/v1alpha1"
+ policyv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
reliablesyncsv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/reliablesyncs/v1alpha1"
rulesv1 "github.com/kubeedge/kubeedge/pkg/apis/rules/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -38,6 +39,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
appsv1alpha1.AddToScheme,
devicesv1alpha2.AddToScheme,
operationsv1alpha1.AddToScheme,
+ policyv1alpha1.AddToScheme,
reliablesyncsv1alpha1.AddToScheme,
rulesv1.AddToScheme,
}
diff --git a/pkg/client/clientset/versioned/typed/policy/v1alpha1/doc.go b/pkg/client/clientset/versioned/typed/policy/v1alpha1/doc.go
new file mode 100644
index 000000000..62dbf54f4
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/policy/v1alpha1/doc.go
@@ -0,0 +1,20 @@
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+// This package has the automatically generated typed clients.
+package v1alpha1
diff --git a/pkg/client/clientset/versioned/typed/policy/v1alpha1/fake/doc.go b/pkg/client/clientset/versioned/typed/policy/v1alpha1/fake/doc.go
new file mode 100644
index 000000000..937651e2e
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/policy/v1alpha1/fake/doc.go
@@ -0,0 +1,20 @@
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+// Package fake has the automatically generated clients.
+package fake
diff --git a/pkg/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_policy_client.go b/pkg/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_policy_client.go
new file mode 100644
index 000000000..2b2d4f4df
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_policy_client.go
@@ -0,0 +1,40 @@
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+ v1alpha1 "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/typed/policy/v1alpha1"
+ rest "k8s.io/client-go/rest"
+ testing "k8s.io/client-go/testing"
+)
+
+type FakePolicyV1alpha1 struct {
+ *testing.Fake
+}
+
+func (c *FakePolicyV1alpha1) ServiceAccountAccesses(namespace string) v1alpha1.ServiceAccountAccessInterface {
+ return &FakeServiceAccountAccesses{c, namespace}
+}
+
+// RESTClient returns a RESTClient that is used to communicate
+// with API server by this client implementation.
+func (c *FakePolicyV1alpha1) RESTClient() rest.Interface {
+ var ret *rest.RESTClient
+ return ret
+}
diff --git a/pkg/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_serviceaccountaccess.go b/pkg/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_serviceaccountaccess.go
new file mode 100644
index 000000000..318c14bb4
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_serviceaccountaccess.go
@@ -0,0 +1,142 @@
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+ "context"
+
+ v1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ labels "k8s.io/apimachinery/pkg/labels"
+ schema "k8s.io/apimachinery/pkg/runtime/schema"
+ types "k8s.io/apimachinery/pkg/types"
+ watch "k8s.io/apimachinery/pkg/watch"
+ testing "k8s.io/client-go/testing"
+)
+
+// FakeServiceAccountAccesses implements ServiceAccountAccessInterface
+type FakeServiceAccountAccesses struct {
+ Fake *FakePolicyV1alpha1
+ ns string
+}
+
+var serviceaccountaccessesResource = schema.GroupVersionResource{Group: "policy.kubeedge.io", Version: "v1alpha1", Resource: "serviceaccountaccesses"}
+
+var serviceaccountaccessesKind = schema.GroupVersionKind{Group: "policy.kubeedge.io", Version: "v1alpha1", Kind: "ServiceAccountAccess"}
+
+// Get takes name of the serviceAccountAccess, and returns the corresponding serviceAccountAccess object, and an error if there is any.
+func (c *FakeServiceAccountAccesses) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ServiceAccountAccess, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewGetAction(serviceaccountaccessesResource, c.ns, name), &v1alpha1.ServiceAccountAccess{})
+
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.ServiceAccountAccess), err
+}
+
+// List takes label and field selectors, and returns the list of ServiceAccountAccesses that match those selectors.
+func (c *FakeServiceAccountAccesses) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ServiceAccountAccessList, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewListAction(serviceaccountaccessesResource, serviceaccountaccessesKind, c.ns, opts), &v1alpha1.ServiceAccountAccessList{})
+
+ if obj == nil {
+ return nil, err
+ }
+
+ label, _, _ := testing.ExtractFromListOptions(opts)
+ if label == nil {
+ label = labels.Everything()
+ }
+ list := &v1alpha1.ServiceAccountAccessList{ListMeta: obj.(*v1alpha1.ServiceAccountAccessList).ListMeta}
+ for _, item := range obj.(*v1alpha1.ServiceAccountAccessList).Items {
+ if label.Matches(labels.Set(item.Labels)) {
+ list.Items = append(list.Items, item)
+ }
+ }
+ return list, err
+}
+
+// Watch returns a watch.Interface that watches the requested serviceAccountAccesses.
+func (c *FakeServiceAccountAccesses) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+ return c.Fake.
+ InvokesWatch(testing.NewWatchAction(serviceaccountaccessesResource, c.ns, opts))
+
+}
+
+// Create takes the representation of a serviceAccountAccess and creates it. Returns the server's representation of the serviceAccountAccess, and an error, if there is any.
+func (c *FakeServiceAccountAccesses) Create(ctx context.Context, serviceAccountAccess *v1alpha1.ServiceAccountAccess, opts v1.CreateOptions) (result *v1alpha1.ServiceAccountAccess, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewCreateAction(serviceaccountaccessesResource, c.ns, serviceAccountAccess), &v1alpha1.ServiceAccountAccess{})
+
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.ServiceAccountAccess), err
+}
+
+// Update takes the representation of a serviceAccountAccess and updates it. Returns the server's representation of the serviceAccountAccess, and an error, if there is any.
+func (c *FakeServiceAccountAccesses) Update(ctx context.Context, serviceAccountAccess *v1alpha1.ServiceAccountAccess, opts v1.UpdateOptions) (result *v1alpha1.ServiceAccountAccess, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewUpdateAction(serviceaccountaccessesResource, c.ns, serviceAccountAccess), &v1alpha1.ServiceAccountAccess{})
+
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.ServiceAccountAccess), err
+}
+
+// UpdateStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+func (c *FakeServiceAccountAccesses) UpdateStatus(ctx context.Context, serviceAccountAccess *v1alpha1.ServiceAccountAccess, opts v1.UpdateOptions) (*v1alpha1.ServiceAccountAccess, error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewUpdateSubresourceAction(serviceaccountaccessesResource, "status", c.ns, serviceAccountAccess), &v1alpha1.ServiceAccountAccess{})
+
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.ServiceAccountAccess), err
+}
+
+// Delete takes name of the serviceAccountAccess and deletes it. Returns an error if one occurs.
+func (c *FakeServiceAccountAccesses) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+ _, err := c.Fake.
+ Invokes(testing.NewDeleteActionWithOptions(serviceaccountaccessesResource, c.ns, name, opts), &v1alpha1.ServiceAccountAccess{})
+
+ return err
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *FakeServiceAccountAccesses) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+ action := testing.NewDeleteCollectionAction(serviceaccountaccessesResource, c.ns, listOpts)
+
+ _, err := c.Fake.Invokes(action, &v1alpha1.ServiceAccountAccessList{})
+ return err
+}
+
+// Patch applies the patch and returns the patched serviceAccountAccess.
+func (c *FakeServiceAccountAccesses) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ServiceAccountAccess, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewPatchSubresourceAction(serviceaccountaccessesResource, c.ns, name, pt, data, subresources...), &v1alpha1.ServiceAccountAccess{})
+
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.ServiceAccountAccess), err
+}
diff --git a/pkg/client/clientset/versioned/typed/policy/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/policy/v1alpha1/generated_expansion.go
new file mode 100644
index 000000000..3a23c0ae4
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/policy/v1alpha1/generated_expansion.go
@@ -0,0 +1,21 @@
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1alpha1
+
+type ServiceAccountAccessExpansion interface{}
diff --git a/pkg/client/clientset/versioned/typed/policy/v1alpha1/policy_client.go b/pkg/client/clientset/versioned/typed/policy/v1alpha1/policy_client.go
new file mode 100644
index 000000000..e1ef05fe2
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/policy/v1alpha1/policy_client.go
@@ -0,0 +1,107 @@
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ "net/http"
+
+ v1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
+ "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/scheme"
+ rest "k8s.io/client-go/rest"
+)
+
+type PolicyV1alpha1Interface interface {
+ RESTClient() rest.Interface
+ ServiceAccountAccessesGetter
+}
+
+// PolicyV1alpha1Client is used to interact with features provided by the policy.kubeedge.io group.
+type PolicyV1alpha1Client struct {
+ restClient rest.Interface
+}
+
+func (c *PolicyV1alpha1Client) ServiceAccountAccesses(namespace string) ServiceAccountAccessInterface {
+ return newServiceAccountAccesses(c, namespace)
+}
+
+// NewForConfig creates a new PolicyV1alpha1Client for the given config.
+// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
+// where httpClient was generated with rest.HTTPClientFor(c).
+func NewForConfig(c *rest.Config) (*PolicyV1alpha1Client, error) {
+ config := *c
+ if err := setConfigDefaults(&config); err != nil {
+ return nil, err
+ }
+ httpClient, err := rest.HTTPClientFor(&config)
+ if err != nil {
+ return nil, err
+ }
+ return NewForConfigAndClient(&config, httpClient)
+}
+
+// NewForConfigAndClient creates a new PolicyV1alpha1Client for the given config and http client.
+// Note the http client provided takes precedence over the configured transport values.
+func NewForConfigAndClient(c *rest.Config, h *http.Client) (*PolicyV1alpha1Client, error) {
+ config := *c
+ if err := setConfigDefaults(&config); err != nil {
+ return nil, err
+ }
+ client, err := rest.RESTClientForConfigAndClient(&config, h)
+ if err != nil {
+ return nil, err
+ }
+ return &PolicyV1alpha1Client{client}, nil
+}
+
+// NewForConfigOrDie creates a new PolicyV1alpha1Client for the given config and
+// panics if there is an error in the config.
+func NewForConfigOrDie(c *rest.Config) *PolicyV1alpha1Client {
+ client, err := NewForConfig(c)
+ if err != nil {
+ panic(err)
+ }
+ return client
+}
+
+// New creates a new PolicyV1alpha1Client for the given RESTClient.
+func New(c rest.Interface) *PolicyV1alpha1Client {
+ return &PolicyV1alpha1Client{c}
+}
+
+func setConfigDefaults(config *rest.Config) error {
+ gv := v1alpha1.SchemeGroupVersion
+ config.GroupVersion = &gv
+ config.APIPath = "/apis"
+ config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
+
+ if config.UserAgent == "" {
+ config.UserAgent = rest.DefaultKubernetesUserAgent()
+ }
+
+ return nil
+}
+
+// RESTClient returns a RESTClient that is used to communicate
+// with API server by this client implementation.
+func (c *PolicyV1alpha1Client) RESTClient() rest.Interface {
+ if c == nil {
+ return nil
+ }
+ return c.restClient
+}
diff --git a/pkg/client/clientset/versioned/typed/policy/v1alpha1/serviceaccountaccess.go b/pkg/client/clientset/versioned/typed/policy/v1alpha1/serviceaccountaccess.go
new file mode 100644
index 000000000..98ddc8d94
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/policy/v1alpha1/serviceaccountaccess.go
@@ -0,0 +1,195 @@
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ "context"
+ "time"
+
+ v1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
+ scheme "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned/scheme"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ types "k8s.io/apimachinery/pkg/types"
+ watch "k8s.io/apimachinery/pkg/watch"
+ rest "k8s.io/client-go/rest"
+)
+
+// ServiceAccountAccessesGetter has a method to return a ServiceAccountAccessInterface.
+// A group's client should implement this interface.
+type ServiceAccountAccessesGetter interface {
+ ServiceAccountAccesses(namespace string) ServiceAccountAccessInterface
+}
+
+// ServiceAccountAccessInterface has methods to work with ServiceAccountAccess resources.
+type ServiceAccountAccessInterface interface {
+ Create(ctx context.Context, serviceAccountAccess *v1alpha1.ServiceAccountAccess, opts v1.CreateOptions) (*v1alpha1.ServiceAccountAccess, error)
+ Update(ctx context.Context, serviceAccountAccess *v1alpha1.ServiceAccountAccess, opts v1.UpdateOptions) (*v1alpha1.ServiceAccountAccess, error)
+ UpdateStatus(ctx context.Context, serviceAccountAccess *v1alpha1.ServiceAccountAccess, opts v1.UpdateOptions) (*v1alpha1.ServiceAccountAccess, error)
+ Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
+ DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
+ Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.ServiceAccountAccess, error)
+ List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.ServiceAccountAccessList, error)
+ Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
+ Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ServiceAccountAccess, err error)
+ ServiceAccountAccessExpansion
+}
+
+// serviceAccountAccesses implements ServiceAccountAccessInterface
+type serviceAccountAccesses struct {
+ client rest.Interface
+ ns string
+}
+
+// newServiceAccountAccesses returns a ServiceAccountAccesses
+func newServiceAccountAccesses(c *PolicyV1alpha1Client, namespace string) *serviceAccountAccesses {
+ return &serviceAccountAccesses{
+ client: c.RESTClient(),
+ ns: namespace,
+ }
+}
+
+// Get takes name of the serviceAccountAccess, and returns the corresponding serviceAccountAccess object, and an error if there is any.
+func (c *serviceAccountAccesses) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ServiceAccountAccess, err error) {
+ result = &v1alpha1.ServiceAccountAccess{}
+ err = c.client.Get().
+ Namespace(c.ns).
+ Resource("serviceaccountaccesses").
+ Name(name).
+ VersionedParams(&options, scheme.ParameterCodec).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// List takes label and field selectors, and returns the list of ServiceAccountAccesses that match those selectors.
+func (c *serviceAccountAccesses) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ServiceAccountAccessList, err error) {
+ var timeout time.Duration
+ if opts.TimeoutSeconds != nil {
+ timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+ }
+ result = &v1alpha1.ServiceAccountAccessList{}
+ err = c.client.Get().
+ Namespace(c.ns).
+ Resource("serviceaccountaccesses").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Timeout(timeout).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Watch returns a watch.Interface that watches the requested serviceAccountAccesses.
+func (c *serviceAccountAccesses) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+ var timeout time.Duration
+ if opts.TimeoutSeconds != nil {
+ timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+ }
+ opts.Watch = true
+ return c.client.Get().
+ Namespace(c.ns).
+ Resource("serviceaccountaccesses").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Timeout(timeout).
+ Watch(ctx)
+}
+
+// Create takes the representation of a serviceAccountAccess and creates it. Returns the server's representation of the serviceAccountAccess, and an error, if there is any.
+func (c *serviceAccountAccesses) Create(ctx context.Context, serviceAccountAccess *v1alpha1.ServiceAccountAccess, opts v1.CreateOptions) (result *v1alpha1.ServiceAccountAccess, err error) {
+ result = &v1alpha1.ServiceAccountAccess{}
+ err = c.client.Post().
+ Namespace(c.ns).
+ Resource("serviceaccountaccesses").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(serviceAccountAccess).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Update takes the representation of a serviceAccountAccess and updates it. Returns the server's representation of the serviceAccountAccess, and an error, if there is any.
+func (c *serviceAccountAccesses) Update(ctx context.Context, serviceAccountAccess *v1alpha1.ServiceAccountAccess, opts v1.UpdateOptions) (result *v1alpha1.ServiceAccountAccess, err error) {
+ result = &v1alpha1.ServiceAccountAccess{}
+ err = c.client.Put().
+ Namespace(c.ns).
+ Resource("serviceaccountaccesses").
+ Name(serviceAccountAccess.Name).
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(serviceAccountAccess).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// UpdateStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+func (c *serviceAccountAccesses) UpdateStatus(ctx context.Context, serviceAccountAccess *v1alpha1.ServiceAccountAccess, opts v1.UpdateOptions) (result *v1alpha1.ServiceAccountAccess, err error) {
+ result = &v1alpha1.ServiceAccountAccess{}
+ err = c.client.Put().
+ Namespace(c.ns).
+ Resource("serviceaccountaccesses").
+ Name(serviceAccountAccess.Name).
+ SubResource("status").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(serviceAccountAccess).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Delete takes name of the serviceAccountAccess and deletes it. Returns an error if one occurs.
+func (c *serviceAccountAccesses) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+ return c.client.Delete().
+ Namespace(c.ns).
+ Resource("serviceaccountaccesses").
+ Name(name).
+ Body(&opts).
+ Do(ctx).
+ Error()
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *serviceAccountAccesses) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+ var timeout time.Duration
+ if listOpts.TimeoutSeconds != nil {
+ timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
+ }
+ return c.client.Delete().
+ Namespace(c.ns).
+ Resource("serviceaccountaccesses").
+ VersionedParams(&listOpts, scheme.ParameterCodec).
+ Timeout(timeout).
+ Body(&opts).
+ Do(ctx).
+ Error()
+}
+
+// Patch applies the patch and returns the patched serviceAccountAccess.
+func (c *serviceAccountAccesses) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ServiceAccountAccess, err error) {
+ result = &v1alpha1.ServiceAccountAccess{}
+ err = c.client.Patch(pt).
+ Namespace(c.ns).
+ Resource("serviceaccountaccesses").
+ Name(name).
+ SubResource(subresources...).
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(data).
+ Do(ctx).
+ Into(result)
+ return
+}
diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go
index 0caccdf0c..3f7a50649 100644
--- a/pkg/client/informers/externalversions/factory.go
+++ b/pkg/client/informers/externalversions/factory.go
@@ -28,6 +28,7 @@ import (
devices "github.com/kubeedge/kubeedge/pkg/client/informers/externalversions/devices"
internalinterfaces "github.com/kubeedge/kubeedge/pkg/client/informers/externalversions/internalinterfaces"
operations "github.com/kubeedge/kubeedge/pkg/client/informers/externalversions/operations"
+ policy "github.com/kubeedge/kubeedge/pkg/client/informers/externalversions/policy"
reliablesyncs "github.com/kubeedge/kubeedge/pkg/client/informers/externalversions/reliablesyncs"
rules "github.com/kubeedge/kubeedge/pkg/client/informers/externalversions/rules"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -179,6 +180,7 @@ type SharedInformerFactory interface {
Apps() apps.Interface
Devices() devices.Interface
Operations() operations.Interface
+ Policy() policy.Interface
Reliablesyncs() reliablesyncs.Interface
Rules() rules.Interface
}
@@ -195,6 +197,10 @@ func (f *sharedInformerFactory) Operations() operations.Interface {
return operations.New(f, f.namespace, f.tweakListOptions)
}
+func (f *sharedInformerFactory) Policy() policy.Interface {
+ return policy.New(f, f.namespace, f.tweakListOptions)
+}
+
func (f *sharedInformerFactory) Reliablesyncs() reliablesyncs.Interface {
return reliablesyncs.New(f, f.namespace, f.tweakListOptions)
}
diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go
index 0efc5fe3b..c9fd39b70 100644
--- a/pkg/client/informers/externalversions/generic.go
+++ b/pkg/client/informers/externalversions/generic.go
@@ -24,6 +24,7 @@ import (
v1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/apps/v1alpha1"
v1alpha2 "github.com/kubeedge/kubeedge/pkg/apis/devices/v1alpha2"
operationsv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/operations/v1alpha1"
+ policyv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
reliablesyncsv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/reliablesyncs/v1alpha1"
v1 "github.com/kubeedge/kubeedge/pkg/apis/rules/v1"
schema "k8s.io/apimachinery/pkg/runtime/schema"
@@ -72,6 +73,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
case operationsv1alpha1.SchemeGroupVersion.WithResource("nodeupgradejobs"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Operations().V1alpha1().NodeUpgradeJobs().Informer()}, nil
+ // Group=policy.kubeedge.io, Version=v1alpha1
+ case policyv1alpha1.SchemeGroupVersion.WithResource("serviceaccountaccesses"):
+ return &genericInformer{resource: resource.GroupResource(), informer: f.Policy().V1alpha1().ServiceAccountAccesses().Informer()}, nil
+
// Group=reliablesyncs.kubeedge.io, Version=v1alpha1
case reliablesyncsv1alpha1.SchemeGroupVersion.WithResource("clusterobjectsyncs"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Reliablesyncs().V1alpha1().ClusterObjectSyncs().Informer()}, nil
diff --git a/pkg/client/informers/externalversions/policy/interface.go b/pkg/client/informers/externalversions/policy/interface.go
new file mode 100644
index 000000000..d0ccc848c
--- /dev/null
+++ b/pkg/client/informers/externalversions/policy/interface.go
@@ -0,0 +1,46 @@
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package policy
+
+import (
+ internalinterfaces "github.com/kubeedge/kubeedge/pkg/client/informers/externalversions/internalinterfaces"
+ v1alpha1 "github.com/kubeedge/kubeedge/pkg/client/informers/externalversions/policy/v1alpha1"
+)
+
+// Interface provides access to each of this group's versions.
+type Interface interface {
+ // V1alpha1 provides access to shared informers for resources in V1alpha1.
+ V1alpha1() v1alpha1.Interface
+}
+
+type group struct {
+ factory internalinterfaces.SharedInformerFactory
+ namespace string
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+}
+
+// New returns a new Interface.
+func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
+ return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
+}
+
+// V1alpha1 returns a new v1alpha1.Interface.
+func (g *group) V1alpha1() v1alpha1.Interface {
+ return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
+}
diff --git a/pkg/client/informers/externalversions/policy/v1alpha1/interface.go b/pkg/client/informers/externalversions/policy/v1alpha1/interface.go
new file mode 100644
index 000000000..1af843a9f
--- /dev/null
+++ b/pkg/client/informers/externalversions/policy/v1alpha1/interface.go
@@ -0,0 +1,45 @@
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ internalinterfaces "github.com/kubeedge/kubeedge/pkg/client/informers/externalversions/internalinterfaces"
+)
+
+// Interface provides access to all the informers in this group version.
+type Interface interface {
+ // ServiceAccountAccesses returns a ServiceAccountAccessInformer.
+ ServiceAccountAccesses() ServiceAccountAccessInformer
+}
+
+type version struct {
+ factory internalinterfaces.SharedInformerFactory
+ namespace string
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+}
+
+// New returns a new Interface.
+func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
+ return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
+}
+
+// ServiceAccountAccesses returns a ServiceAccountAccessInformer.
+func (v *version) ServiceAccountAccesses() ServiceAccountAccessInformer {
+ return &serviceAccountAccessInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
+}
diff --git a/pkg/client/informers/externalversions/policy/v1alpha1/serviceaccountaccess.go b/pkg/client/informers/externalversions/policy/v1alpha1/serviceaccountaccess.go
new file mode 100644
index 000000000..7c6694aeb
--- /dev/null
+++ b/pkg/client/informers/externalversions/policy/v1alpha1/serviceaccountaccess.go
@@ -0,0 +1,90 @@
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ "context"
+ time "time"
+
+ policyv1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
+ versioned "github.com/kubeedge/kubeedge/pkg/client/clientset/versioned"
+ internalinterfaces "github.com/kubeedge/kubeedge/pkg/client/informers/externalversions/internalinterfaces"
+ v1alpha1 "github.com/kubeedge/kubeedge/pkg/client/listers/policy/v1alpha1"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ runtime "k8s.io/apimachinery/pkg/runtime"
+ watch "k8s.io/apimachinery/pkg/watch"
+ cache "k8s.io/client-go/tools/cache"
+)
+
+// ServiceAccountAccessInformer provides access to a shared informer and lister for
+// ServiceAccountAccesses.
+type ServiceAccountAccessInformer interface {
+ Informer() cache.SharedIndexInformer
+ Lister() v1alpha1.ServiceAccountAccessLister
+}
+
+type serviceAccountAccessInformer struct {
+ factory internalinterfaces.SharedInformerFactory
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+ namespace string
+}
+
+// NewServiceAccountAccessInformer constructs a new informer for ServiceAccountAccess type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewServiceAccountAccessInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+ return NewFilteredServiceAccountAccessInformer(client, namespace, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredServiceAccountAccessInformer constructs a new informer for ServiceAccountAccess type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewFilteredServiceAccountAccessInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
+ return cache.NewSharedIndexInformer(
+ &cache.ListWatch{
+ ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.PolicyV1alpha1().ServiceAccountAccesses(namespace).List(context.TODO(), options)
+ },
+ WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.PolicyV1alpha1().ServiceAccountAccesses(namespace).Watch(context.TODO(), options)
+ },
+ },
+ &policyv1alpha1.ServiceAccountAccess{},
+ resyncPeriod,
+ indexers,
+ )
+}
+
+func (f *serviceAccountAccessInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+ return NewFilteredServiceAccountAccessInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *serviceAccountAccessInformer) Informer() cache.SharedIndexInformer {
+ return f.factory.InformerFor(&policyv1alpha1.ServiceAccountAccess{}, f.defaultInformer)
+}
+
+func (f *serviceAccountAccessInformer) Lister() v1alpha1.ServiceAccountAccessLister {
+ return v1alpha1.NewServiceAccountAccessLister(f.Informer().GetIndexer())
+}
diff --git a/pkg/client/listers/policy/v1alpha1/expansion_generated.go b/pkg/client/listers/policy/v1alpha1/expansion_generated.go
new file mode 100644
index 000000000..e94ff1647
--- /dev/null
+++ b/pkg/client/listers/policy/v1alpha1/expansion_generated.go
@@ -0,0 +1,27 @@
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by lister-gen. DO NOT EDIT.
+
+package v1alpha1
+
+// ServiceAccountAccessListerExpansion allows custom methods to be added to
+// ServiceAccountAccessLister.
+type ServiceAccountAccessListerExpansion interface{}
+
+// ServiceAccountAccessNamespaceListerExpansion allows custom methods to be added to
+// ServiceAccountAccessNamespaceLister.
+type ServiceAccountAccessNamespaceListerExpansion interface{}
diff --git a/pkg/client/listers/policy/v1alpha1/serviceaccountaccess.go b/pkg/client/listers/policy/v1alpha1/serviceaccountaccess.go
new file mode 100644
index 000000000..722eb516a
--- /dev/null
+++ b/pkg/client/listers/policy/v1alpha1/serviceaccountaccess.go
@@ -0,0 +1,99 @@
+/*
+Copyright The KubeEdge 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.
+*/
+
+// Code generated by lister-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ v1alpha1 "github.com/kubeedge/kubeedge/pkg/apis/policy/v1alpha1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/client-go/tools/cache"
+)
+
+// ServiceAccountAccessLister helps list ServiceAccountAccesses.
+// All objects returned here must be treated as read-only.
+type ServiceAccountAccessLister interface {
+ // List lists all ServiceAccountAccesses in the indexer.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*v1alpha1.ServiceAccountAccess, err error)
+ // ServiceAccountAccesses returns an object that can list and get ServiceAccountAccesses.
+ ServiceAccountAccesses(namespace string) ServiceAccountAccessNamespaceLister
+ ServiceAccountAccessListerExpansion
+}
+
+// serviceAccountAccessLister implements the ServiceAccountAccessLister interface.
+type serviceAccountAccessLister struct {
+ indexer cache.Indexer
+}
+
+// NewServiceAccountAccessLister returns a new ServiceAccountAccessLister.
+func NewServiceAccountAccessLister(indexer cache.Indexer) ServiceAccountAccessLister {
+ return &serviceAccountAccessLister{indexer: indexer}
+}
+
+// List lists all ServiceAccountAccesses in the indexer.
+func (s *serviceAccountAccessLister) List(selector labels.Selector) (ret []*v1alpha1.ServiceAccountAccess, err error) {
+ err = cache.ListAll(s.indexer, selector, func(m interface{}) {
+ ret = append(ret, m.(*v1alpha1.ServiceAccountAccess))
+ })
+ return ret, err
+}
+
+// ServiceAccountAccesses returns an object that can list and get ServiceAccountAccesses.
+func (s *serviceAccountAccessLister) ServiceAccountAccesses(namespace string) ServiceAccountAccessNamespaceLister {
+ return serviceAccountAccessNamespaceLister{indexer: s.indexer, namespace: namespace}
+}
+
+// ServiceAccountAccessNamespaceLister helps list and get ServiceAccountAccesses.
+// All objects returned here must be treated as read-only.
+type ServiceAccountAccessNamespaceLister interface {
+ // List lists all ServiceAccountAccesses in the indexer for a given namespace.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*v1alpha1.ServiceAccountAccess, err error)
+ // Get retrieves the ServiceAccountAccess from the indexer for a given namespace and name.
+ // Objects returned here must be treated as read-only.
+ Get(name string) (*v1alpha1.ServiceAccountAccess, error)
+ ServiceAccountAccessNamespaceListerExpansion
+}
+
+// serviceAccountAccessNamespaceLister implements the ServiceAccountAccessNamespaceLister
+// interface.
+type serviceAccountAccessNamespaceLister struct {
+ indexer cache.Indexer
+ namespace string
+}
+
+// List lists all ServiceAccountAccesses in the indexer for a given namespace.
+func (s serviceAccountAccessNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.ServiceAccountAccess, err error) {
+ err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
+ ret = append(ret, m.(*v1alpha1.ServiceAccountAccess))
+ })
+ return ret, err
+}
+
+// Get retrieves the ServiceAccountAccess from the indexer for a given namespace and name.
+func (s serviceAccountAccessNamespaceLister) Get(name string) (*v1alpha1.ServiceAccountAccess, error) {
+ obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
+ if err != nil {
+ return nil, err
+ }
+ if !exists {
+ return nil, errors.NewNotFound(v1alpha1.Resource("serviceaccountaccess"), name)
+ }
+ return obj.(*v1alpha1.ServiceAccountAccess), nil
+}
diff --git a/pkg/metaserver/application.go b/pkg/metaserver/application.go
index 2a25c4b24..ccd64361f 100644
--- a/pkg/metaserver/application.go
+++ b/pkg/metaserver/application.go
@@ -32,7 +32,6 @@ import (
beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
"github.com/kubeedge/beehive/pkg/core/model"
- commontypes "github.com/kubeedge/kubeedge/common/types"
)
// Application record the resources that are in applying for requesting to be transferred down from the cloud, please:
@@ -50,7 +49,6 @@ type Application struct {
Option []byte
ReqBody []byte
Subresource string
- Token string
// The following field defines the Application response result
RespBody []byte
@@ -78,11 +76,6 @@ func NewApplication(ctx context.Context, key string, verb ApplicationVerb, noden
}
option = v1
}
- token, ok := ctx.Value(commontypes.AuthorizationKey).(string)
- if !ok {
- klog.Errorf("unsupported Token type :%T", ctx.Value(commontypes.AuthorizationKey))
- return nil, fmt.Errorf("unsupported Token type :%T", ctx.Value(commontypes.AuthorizationKey))
- }
ctx2, cancel := context.WithCancel(ctx)
app := &Application{
Key: key,
@@ -92,7 +85,6 @@ func NewApplication(ctx context.Context, key string, verb ApplicationVerb, noden
Status: PreApplying,
Option: ToBytes(option),
ReqBody: ToBytes(reqBody),
- Token: token,
ctx: ctx2,
cancel: cancel,
count: 0,
@@ -113,7 +105,6 @@ func (a *Application) Identifier() string {
b = append(b, a.Option...)
b = append(b, a.ReqBody...)
b = append(b, []byte(a.Subresource)...)
- b = append(b, []byte(a.Token)...)
a.ID = fmt.Sprintf("%x", sha256.Sum256(b))
return a.ID
}
diff --git a/staging/src/github.com/kubeedge/beehive/pkg/core/model/message.go b/staging/src/github.com/kubeedge/beehive/pkg/core/model/message.go
index 172c819d9..bdf1bec92 100644
--- a/staging/src/github.com/kubeedge/beehive/pkg/core/model/message.go
+++ b/staging/src/github.com/kubeedge/beehive/pkg/core/model/message.go
@@ -35,6 +35,7 @@ const (
ResourceTypeRuleEndpoint = "ruleendpoint"
ResourceTypeRuleStatus = "rulestatus"
ResourceTypeLease = "lease"
+ ResourceTypeSaAccess = "serviceaccountaccess"
)
// Message struct
diff --git a/vendor/k8s.io/component-helpers/auth/rbac/validation/policy_comparator.go b/vendor/k8s.io/component-helpers/auth/rbac/validation/policy_comparator.go
new file mode 100644
index 000000000..7a0268b5e
--- /dev/null
+++ b/vendor/k8s.io/component-helpers/auth/rbac/validation/policy_comparator.go
@@ -0,0 +1,173 @@
+/*
+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 validation
+
+import (
+ "strings"
+
+ rbacv1 "k8s.io/api/rbac/v1"
+)
+
+// Covers determines whether or not the ownerRules cover the servantRules in terms of allowed actions.
+// It returns whether or not the ownerRules cover and a list of the rules that the ownerRules do not cover.
+func Covers(ownerRules, servantRules []rbacv1.PolicyRule) (bool, []rbacv1.PolicyRule) {
+ // 1. Break every servantRule into individual rule tuples: group, verb, resource, resourceName
+ // 2. Compare the mini-rules against each owner rule. Because the breakdown is down to the most atomic level, we're guaranteed that each mini-servant rule will be either fully covered or not covered by a single owner rule
+ // 3. Any left over mini-rules means that we are not covered and we have a nice list of them.
+ // TODO: it might be nice to collapse the list down into something more human readable
+
+ subrules := []rbacv1.PolicyRule{}
+ for _, servantRule := range servantRules {
+ subrules = append(subrules, BreakdownRule(servantRule)...)
+ }
+
+ uncoveredRules := []rbacv1.PolicyRule{}
+ for _, subrule := range subrules {
+ covered := false
+ for _, ownerRule := range ownerRules {
+ if ruleCovers(ownerRule, subrule) {
+ covered = true
+ break
+ }
+ }
+
+ if !covered {
+ uncoveredRules = append(uncoveredRules, subrule)
+ }
+ }
+
+ return (len(uncoveredRules) == 0), uncoveredRules
+}
+
+// BreadownRule takes a rule and builds an equivalent list of rules that each have at most one verb, one
+// resource, and one resource name
+func BreakdownRule(rule rbacv1.PolicyRule) []rbacv1.PolicyRule {
+ subrules := []rbacv1.PolicyRule{}
+ for _, group := range rule.APIGroups {
+ for _, resource := range rule.Resources {
+ for _, verb := range rule.Verbs {
+ if len(rule.ResourceNames) > 0 {
+ for _, resourceName := range rule.ResourceNames {
+ subrules = append(subrules, rbacv1.PolicyRule{APIGroups: []string{group}, Resources: []string{resource}, Verbs: []string{verb}, ResourceNames: []string{resourceName}})
+ }
+
+ } else {
+ subrules = append(subrules, rbacv1.PolicyRule{APIGroups: []string{group}, Resources: []string{resource}, Verbs: []string{verb}})
+ }
+
+ }
+ }
+ }
+
+ // Non-resource URLs are unique because they only combine with verbs.
+ for _, nonResourceURL := range rule.NonResourceURLs {
+ for _, verb := range rule.Verbs {
+ subrules = append(subrules, rbacv1.PolicyRule{NonResourceURLs: []string{nonResourceURL}, Verbs: []string{verb}})
+ }
+ }
+
+ return subrules
+}
+
+func has(set []string, ele string) bool {
+ for _, s := range set {
+ if s == ele {
+ return true
+ }
+ }
+ return false
+}
+
+func hasAll(set, contains []string) bool {
+ owning := make(map[string]struct{}, len(set))
+ for _, ele := range set {
+ owning[ele] = struct{}{}
+ }
+ for _, ele := range contains {
+ if _, ok := owning[ele]; !ok {
+ return false
+ }
+ }
+ return true
+}
+
+func resourceCoversAll(setResources, coversResources []string) bool {
+ // if we have a star or an exact match on all resources, then we match
+ if has(setResources, rbacv1.ResourceAll) || hasAll(setResources, coversResources) {
+ return true
+ }
+
+ for _, path := range coversResources {
+ // if we have an exact match, then we match.
+ if has(setResources, path) {
+ continue
+ }
+ // if we're not a subresource, then we definitely don't match. fail.
+ if !strings.Contains(path, "/") {
+ return false
+ }
+ tokens := strings.SplitN(path, "/", 2)
+ resourceToCheck := "*/" + tokens[1]
+ if !has(setResources, resourceToCheck) {
+ return false
+ }
+ }
+
+ return true
+}
+
+func nonResourceURLsCoversAll(set, covers []string) bool {
+ for _, path := range covers {
+ covered := false
+ for _, owner := range set {
+ if nonResourceURLCovers(owner, path) {
+ covered = true
+ break
+ }
+ }
+ if !covered {
+ return false
+ }
+ }
+ return true
+}
+
+func nonResourceURLCovers(ownerPath, subPath string) bool {
+ if ownerPath == subPath {
+ return true
+ }
+ return strings.HasSuffix(ownerPath, "*") && strings.HasPrefix(subPath, strings.TrimRight(ownerPath, "*"))
+}
+
+// ruleCovers determines whether the ownerRule (which may have multiple verbs, resources, and resourceNames) covers
+// the subrule (which may only contain at most one verb, resource, and resourceName)
+func ruleCovers(ownerRule, subRule rbacv1.PolicyRule) bool {
+ verbMatches := has(ownerRule.Verbs, rbacv1.VerbAll) || hasAll(ownerRule.Verbs, subRule.Verbs)
+ groupMatches := has(ownerRule.APIGroups, rbacv1.APIGroupAll) || hasAll(ownerRule.APIGroups, subRule.APIGroups)
+ resourceMatches := resourceCoversAll(ownerRule.Resources, subRule.Resources)
+ nonResourceURLMatches := nonResourceURLsCoversAll(ownerRule.NonResourceURLs, subRule.NonResourceURLs)
+
+ resourceNameMatches := false
+
+ if len(subRule.ResourceNames) == 0 {
+ resourceNameMatches = (len(ownerRule.ResourceNames) == 0)
+ } else {
+ resourceNameMatches = (len(ownerRule.ResourceNames) == 0) || hasAll(ownerRule.ResourceNames, subRule.ResourceNames)
+ }
+
+ return verbMatches && groupMatches && resourceMatches && resourceNameMatches && nonResourceURLMatches
+}
diff --git a/vendor/k8s.io/kubernetes/pkg/registry/rbac/validation/internal_version_adapter.go b/vendor/k8s.io/kubernetes/pkg/registry/rbac/validation/internal_version_adapter.go
new file mode 100644
index 000000000..bfb57242d
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/pkg/registry/rbac/validation/internal_version_adapter.go
@@ -0,0 +1,39 @@
+/*
+Copyright 2018 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 validation
+
+import (
+ "context"
+
+ rbacv1 "k8s.io/api/rbac/v1"
+ "k8s.io/kubernetes/pkg/apis/rbac"
+ rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
+)
+
+func ConfirmNoEscalationInternal(ctx context.Context, ruleResolver AuthorizationRuleResolver, inRules []rbac.PolicyRule) error {
+ rules := []rbacv1.PolicyRule{}
+ for i := range inRules {
+ v1Rule := rbacv1.PolicyRule{}
+ err := rbacv1helpers.Convert_rbac_PolicyRule_To_v1_PolicyRule(&inRules[i], &v1Rule, nil)
+ if err != nil {
+ return err
+ }
+ rules = append(rules, v1Rule)
+ }
+
+ return ConfirmNoEscalation(ctx, ruleResolver, rules)
+}
diff --git a/vendor/k8s.io/kubernetes/pkg/registry/rbac/validation/policy_compact.go b/vendor/k8s.io/kubernetes/pkg/registry/rbac/validation/policy_compact.go
new file mode 100644
index 000000000..182657b1c
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/pkg/registry/rbac/validation/policy_compact.go
@@ -0,0 +1,89 @@
+/*
+Copyright 2017 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 validation
+
+import (
+ "reflect"
+
+ rbacv1 "k8s.io/api/rbac/v1"
+)
+
+type simpleResource struct {
+ Group string
+ Resource string
+ ResourceNameExist bool
+ ResourceName string
+}
+
+// CompactRules combines rules that contain a single APIGroup/Resource, differ only by verb, and contain no other attributes.
+// this is a fast check, and works well with the decomposed "missing rules" list from a Covers check.
+func CompactRules(rules []rbacv1.PolicyRule) ([]rbacv1.PolicyRule, error) {
+ compacted := make([]rbacv1.PolicyRule, 0, len(rules))
+
+ simpleRules := map[simpleResource]*rbacv1.PolicyRule{}
+ for _, rule := range rules {
+ if resource, isSimple := isSimpleResourceRule(&rule); isSimple {
+ if existingRule, ok := simpleRules[resource]; ok {
+ // Add the new verbs to the existing simple resource rule
+ if existingRule.Verbs == nil {
+ existingRule.Verbs = []string{}
+ }
+ existingRule.Verbs = append(existingRule.Verbs, rule.Verbs...)
+ } else {
+ // Copy the rule to accumulate matching simple resource rules into
+ simpleRules[resource] = rule.DeepCopy()
+ }
+ } else {
+ compacted = append(compacted, rule)
+ }
+ }
+
+ // Once we've consolidated the simple resource rules, add them to the compacted list
+ for _, simpleRule := range simpleRules {
+ compacted = append(compacted, *simpleRule)
+ }
+
+ return compacted, nil
+}
+
+// isSimpleResourceRule returns true if the given rule contains verbs, a single resource, a single API group, at most one Resource Name, and no other values
+func isSimpleResourceRule(rule *rbacv1.PolicyRule) (simpleResource, bool) {
+ resource := simpleResource{}
+
+ // If we have "complex" rule attributes, return early without allocations or expensive comparisons
+ if len(rule.ResourceNames) > 1 || len(rule.NonResourceURLs) > 0 {
+ return resource, false
+ }
+ // If we have multiple api groups or resources, return early
+ if len(rule.APIGroups) != 1 || len(rule.Resources) != 1 {
+ return resource, false
+ }
+
+ // Test if this rule only contains APIGroups/Resources/Verbs/ResourceNames
+ simpleRule := &rbacv1.PolicyRule{APIGroups: rule.APIGroups, Resources: rule.Resources, Verbs: rule.Verbs, ResourceNames: rule.ResourceNames}
+ if !reflect.DeepEqual(simpleRule, rule) {
+ return resource, false
+ }
+
+ if len(rule.ResourceNames) == 0 {
+ resource = simpleResource{Group: rule.APIGroups[0], Resource: rule.Resources[0], ResourceNameExist: false}
+ } else {
+ resource = simpleResource{Group: rule.APIGroups[0], Resource: rule.Resources[0], ResourceNameExist: true, ResourceName: rule.ResourceNames[0]}
+ }
+
+ return resource, true
+}
diff --git a/vendor/k8s.io/kubernetes/pkg/registry/rbac/validation/rule.go b/vendor/k8s.io/kubernetes/pkg/registry/rbac/validation/rule.go
new file mode 100644
index 000000000..603f56afb
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/pkg/registry/rbac/validation/rule.go
@@ -0,0 +1,368 @@
+/*
+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 validation
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "strings"
+
+ "k8s.io/klog/v2"
+
+ rbacv1 "k8s.io/api/rbac/v1"
+ utilerrors "k8s.io/apimachinery/pkg/util/errors"
+ "k8s.io/apimachinery/pkg/util/sets"
+ "k8s.io/apiserver/pkg/authentication/serviceaccount"
+ "k8s.io/apiserver/pkg/authentication/user"
+ genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
+ "k8s.io/component-helpers/auth/rbac/validation"
+ rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
+)
+
+type AuthorizationRuleResolver interface {
+ // GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding. The passed namespace should be the namespace
+ // of the role binding, the empty string if a cluster role binding.
+ GetRoleReferenceRules(roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error)
+
+ // RulesFor returns the list of rules that apply to a given user in a given namespace and error. If an error is returned, the slice of
+ // PolicyRules may not be complete, but it contains all retrievable rules. This is done because policy rules are purely additive and policy determinations
+ // can be made on the basis of those rules that are found.
+ RulesFor(user user.Info, namespace string) ([]rbacv1.PolicyRule, error)
+
+ // VisitRulesFor invokes visitor() with each rule that applies to a given user in a given namespace, and each error encountered resolving those rules.
+ // If visitor() returns false, visiting is short-circuited.
+ VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool)
+}
+
+// ConfirmNoEscalation determines if the roles for a given user in a given namespace encompass the provided role.
+func ConfirmNoEscalation(ctx context.Context, ruleResolver AuthorizationRuleResolver, rules []rbacv1.PolicyRule) error {
+ ruleResolutionErrors := []error{}
+
+ user, ok := genericapirequest.UserFrom(ctx)
+ if !ok {
+ return fmt.Errorf("no user on context")
+ }
+ namespace, _ := genericapirequest.NamespaceFrom(ctx)
+
+ ownerRules, err := ruleResolver.RulesFor(user, namespace)
+ if err != nil {
+ // As per AuthorizationRuleResolver contract, this may return a non fatal error with an incomplete list of policies. Log the error and continue.
+ klog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err)
+ ruleResolutionErrors = append(ruleResolutionErrors, err)
+ }
+
+ ownerRightsCover, missingRights := validation.Covers(ownerRules, rules)
+ if !ownerRightsCover {
+ compactMissingRights := missingRights
+ if compact, err := CompactRules(missingRights); err == nil {
+ compactMissingRights = compact
+ }
+
+ missingDescriptions := sets.NewString()
+ for _, missing := range compactMissingRights {
+ missingDescriptions.Insert(rbacv1helpers.CompactString(missing))
+ }
+
+ msg := fmt.Sprintf("user %q (groups=%q) is attempting to grant RBAC permissions not currently held:\n%s", user.GetName(), user.GetGroups(), strings.Join(missingDescriptions.List(), "\n"))
+ if len(ruleResolutionErrors) > 0 {
+ msg = msg + fmt.Sprintf("; resolution errors: %v", ruleResolutionErrors)
+ }
+
+ return errors.New(msg)
+ }
+ return nil
+}
+
+type DefaultRuleResolver struct {
+ roleGetter RoleGetter
+ roleBindingLister RoleBindingLister
+ clusterRoleGetter ClusterRoleGetter
+ clusterRoleBindingLister ClusterRoleBindingLister
+}
+
+func NewDefaultRuleResolver(roleGetter RoleGetter, roleBindingLister RoleBindingLister, clusterRoleGetter ClusterRoleGetter, clusterRoleBindingLister ClusterRoleBindingLister) *DefaultRuleResolver {
+ return &DefaultRuleResolver{roleGetter, roleBindingLister, clusterRoleGetter, clusterRoleBindingLister}
+}
+
+type RoleGetter interface {
+ GetRole(namespace, name string) (*rbacv1.Role, error)
+}
+
+type RoleBindingLister interface {
+ ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error)
+}
+
+type ClusterRoleGetter interface {
+ GetClusterRole(name string) (*rbacv1.ClusterRole, error)
+}
+
+type ClusterRoleBindingLister interface {
+ ListClusterRoleBindings() ([]*rbacv1.ClusterRoleBinding, error)
+}
+
+func (r *DefaultRuleResolver) RulesFor(user user.Info, namespace string) ([]rbacv1.PolicyRule, error) {
+ visitor := &ruleAccumulator{}
+ r.VisitRulesFor(user, namespace, visitor.visit)
+ return visitor.rules, utilerrors.NewAggregate(visitor.errors)
+}
+
+type ruleAccumulator struct {
+ rules []rbacv1.PolicyRule
+ errors []error
+}
+
+func (r *ruleAccumulator) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool {
+ if rule != nil {
+ r.rules = append(r.rules, *rule)
+ }
+ if err != nil {
+ r.errors = append(r.errors, err)
+ }
+ return true
+}
+
+func describeSubject(s *rbacv1.Subject, bindingNamespace string) string {
+ switch s.Kind {
+ case rbacv1.ServiceAccountKind:
+ if len(s.Namespace) > 0 {
+ return fmt.Sprintf("%s %q", s.Kind, s.Name+"/"+s.Namespace)
+ }
+ return fmt.Sprintf("%s %q", s.Kind, s.Name+"/"+bindingNamespace)
+ default:
+ return fmt.Sprintf("%s %q", s.Kind, s.Name)
+ }
+}
+
+type clusterRoleBindingDescriber struct {
+ binding *rbacv1.ClusterRoleBinding
+ subject *rbacv1.Subject
+}
+
+func (d *clusterRoleBindingDescriber) String() string {
+ return fmt.Sprintf("ClusterRoleBinding %q of %s %q to %s",
+ d.binding.Name,
+ d.binding.RoleRef.Kind,
+ d.binding.RoleRef.Name,
+ describeSubject(d.subject, ""),
+ )
+}
+
+type roleBindingDescriber struct {
+ binding *rbacv1.RoleBinding
+ subject *rbacv1.Subject
+}
+
+func (d *roleBindingDescriber) String() string {
+ return fmt.Sprintf("RoleBinding %q of %s %q to %s",
+ d.binding.Name+"/"+d.binding.Namespace,
+ d.binding.RoleRef.Kind,
+ d.binding.RoleRef.Name,
+ describeSubject(d.subject, d.binding.Namespace),
+ )
+}
+
+func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) {
+ if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil {
+ if !visitor(nil, nil, err) {
+ return
+ }
+ } else {
+ sourceDescriber := &clusterRoleBindingDescriber{}
+ for _, clusterRoleBinding := range clusterRoleBindings {
+ subjectIndex, applies := appliesTo(user, clusterRoleBinding.Subjects, "")
+ if !applies {
+ continue
+ }
+ rules, err := r.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
+ if err != nil {
+ if !visitor(nil, nil, err) {
+ return
+ }
+ continue
+ }
+ sourceDescriber.binding = clusterRoleBinding
+ sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex]
+ for i := range rules {
+ if !visitor(sourceDescriber, &rules[i], nil) {
+ return
+ }
+ }
+ }
+ }
+
+ if len(namespace) > 0 {
+ if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil {
+ if !visitor(nil, nil, err) {
+ return
+ }
+ } else {
+ sourceDescriber := &roleBindingDescriber{}
+ for _, roleBinding := range roleBindings {
+ subjectIndex, applies := appliesTo(user, roleBinding.Subjects, namespace)
+ if !applies {
+ continue
+ }
+ rules, err := r.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
+ if err != nil {
+ if !visitor(nil, nil, err) {
+ return
+ }
+ continue
+ }
+ sourceDescriber.binding = roleBinding
+ sourceDescriber.subject = &roleBinding.Subjects[subjectIndex]
+ for i := range rules {
+ if !visitor(sourceDescriber, &rules[i], nil) {
+ return
+ }
+ }
+ }
+ }
+ }
+}
+
+// GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding.
+func (r *DefaultRuleResolver) GetRoleReferenceRules(roleRef rbacv1.RoleRef, bindingNamespace string) ([]rbacv1.PolicyRule, error) {
+ switch roleRef.Kind {
+ case "Role":
+ role, err := r.roleGetter.GetRole(bindingNamespace, roleRef.Name)
+ if err != nil {
+ return nil, err
+ }
+ return role.Rules, nil
+
+ case "ClusterRole":
+ clusterRole, err := r.clusterRoleGetter.GetClusterRole(roleRef.Name)
+ if err != nil {
+ return nil, err
+ }
+ return clusterRole.Rules, nil
+
+ default:
+ return nil, fmt.Errorf("unsupported role reference kind: %q", roleRef.Kind)
+ }
+}
+
+// appliesTo returns whether any of the bindingSubjects applies to the specified subject,
+// and if true, the index of the first subject that applies
+func appliesTo(user user.Info, bindingSubjects []rbacv1.Subject, namespace string) (int, bool) {
+ for i, bindingSubject := range bindingSubjects {
+ if appliesToUser(user, bindingSubject, namespace) {
+ return i, true
+ }
+ }
+ return 0, false
+}
+
+func has(set []string, ele string) bool {
+ for _, s := range set {
+ if s == ele {
+ return true
+ }
+ }
+ return false
+}
+
+func appliesToUser(user user.Info, subject rbacv1.Subject, namespace string) bool {
+ switch subject.Kind {
+ case rbacv1.UserKind:
+ return user.GetName() == subject.Name
+
+ case rbacv1.GroupKind:
+ return has(user.GetGroups(), subject.Name)
+
+ case rbacv1.ServiceAccountKind:
+ // default the namespace to namespace we're working in if its available. This allows rolebindings that reference
+ // SAs in th local namespace to avoid having to qualify them.
+ saNamespace := namespace
+ if len(subject.Namespace) > 0 {
+ saNamespace = subject.Namespace
+ }
+ if len(saNamespace) == 0 {
+ return false
+ }
+ // use a more efficient comparison for RBAC checking
+ return serviceaccount.MatchesUsername(saNamespace, subject.Name, user.GetName())
+ default:
+ return false
+ }
+}
+
+// NewTestRuleResolver returns a rule resolver from lists of role objects.
+func NewTestRuleResolver(roles []*rbacv1.Role, roleBindings []*rbacv1.RoleBinding, clusterRoles []*rbacv1.ClusterRole, clusterRoleBindings []*rbacv1.ClusterRoleBinding) (AuthorizationRuleResolver, *StaticRoles) {
+ r := StaticRoles{
+ roles: roles,
+ roleBindings: roleBindings,
+ clusterRoles: clusterRoles,
+ clusterRoleBindings: clusterRoleBindings,
+ }
+ return newMockRuleResolver(&r), &r
+}
+
+func newMockRuleResolver(r *StaticRoles) AuthorizationRuleResolver {
+ return NewDefaultRuleResolver(r, r, r, r)
+}
+
+// StaticRoles is a rule resolver that resolves from lists of role objects.
+type StaticRoles struct {
+ roles []*rbacv1.Role
+ roleBindings []*rbacv1.RoleBinding
+ clusterRoles []*rbacv1.ClusterRole
+ clusterRoleBindings []*rbacv1.ClusterRoleBinding
+}
+
+func (r *StaticRoles) GetRole(namespace, name string) (*rbacv1.Role, error) {
+ if len(namespace) == 0 {
+ return nil, errors.New("must provide namespace when getting role")
+ }
+ for _, role := range r.roles {
+ if role.Namespace == namespace && role.Name == name {
+ return role, nil
+ }
+ }
+ return nil, errors.New("role not found")
+}
+
+func (r *StaticRoles) GetClusterRole(name string) (*rbacv1.ClusterRole, error) {
+ for _, clusterRole := range r.clusterRoles {
+ if clusterRole.Name == name {
+ return clusterRole, nil
+ }
+ }
+ return nil, errors.New("clusterrole not found")
+}
+
+func (r *StaticRoles) ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error) {
+ if len(namespace) == 0 {
+ return nil, errors.New("must provide namespace when listing role bindings")
+ }
+
+ roleBindingList := []*rbacv1.RoleBinding{}
+ for _, roleBinding := range r.roleBindings {
+ if roleBinding.Namespace != namespace {
+ continue
+ }
+ // TODO(ericchiang): need to implement label selectors?
+ roleBindingList = append(roleBindingList, roleBinding)
+ }
+ return roleBindingList, nil
+}
+
+func (r *StaticRoles) ListClusterRoleBindings() ([]*rbacv1.ClusterRoleBinding, error) {
+ return r.clusterRoleBindings, nil
+}
diff --git a/vendor/k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/rbac.go b/vendor/k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/rbac.go
new file mode 100644
index 000000000..4f25d1372
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/rbac.go
@@ -0,0 +1,225 @@
+/*
+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 rbac implements the authorizer.Authorizer interface using roles base access control.
+package rbac
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+
+ "k8s.io/klog/v2"
+
+ rbacv1 "k8s.io/api/rbac/v1"
+ "k8s.io/apimachinery/pkg/labels"
+ utilerrors "k8s.io/apimachinery/pkg/util/errors"
+ "k8s.io/apiserver/pkg/authentication/user"
+ "k8s.io/apiserver/pkg/authorization/authorizer"
+ rbaclisters "k8s.io/client-go/listers/rbac/v1"
+ rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
+ rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
+)
+
+type RequestToRuleMapper interface {
+ // RulesFor returns all known PolicyRules and any errors that happened while locating those rules.
+ // Any rule returned is still valid, since rules are deny by default. If you can pass with the rules
+ // supplied, you do not have to fail the request. If you cannot, you should indicate the error along
+ // with your denial.
+ RulesFor(subject user.Info, namespace string) ([]rbacv1.PolicyRule, error)
+
+ // VisitRulesFor invokes visitor() with each rule that applies to a given user in a given namespace,
+ // and each error encountered resolving those rules. Rule may be nil if err is non-nil.
+ // If visitor() returns false, visiting is short-circuited.
+ VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool)
+}
+
+type RBACAuthorizer struct {
+ authorizationRuleResolver RequestToRuleMapper
+}
+
+// authorizingVisitor short-circuits once allowed, and collects any resolution errors encountered
+type authorizingVisitor struct {
+ requestAttributes authorizer.Attributes
+
+ allowed bool
+ reason string
+ errors []error
+}
+
+func (v *authorizingVisitor) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool {
+ if rule != nil && RuleAllows(v.requestAttributes, rule) {
+ v.allowed = true
+ v.reason = fmt.Sprintf("RBAC: allowed by %s", source.String())
+ return false
+ }
+ if err != nil {
+ v.errors = append(v.errors, err)
+ }
+ return true
+}
+
+func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) {
+ ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes}
+
+ r.authorizationRuleResolver.VisitRulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit)
+ if ruleCheckingVisitor.allowed {
+ return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil
+ }
+
+ // Build a detailed log of the denial.
+ // Make the whole block conditional so we don't do a lot of string-building we won't use.
+ if klog.V(5).Enabled() {
+ var operation string
+ if requestAttributes.IsResourceRequest() {
+ b := &bytes.Buffer{}
+ b.WriteString(`"`)
+ b.WriteString(requestAttributes.GetVerb())
+ b.WriteString(`" resource "`)
+ b.WriteString(requestAttributes.GetResource())
+ if len(requestAttributes.GetAPIGroup()) > 0 {
+ b.WriteString(`.`)
+ b.WriteString(requestAttributes.GetAPIGroup())
+ }
+ if len(requestAttributes.GetSubresource()) > 0 {
+ b.WriteString(`/`)
+ b.WriteString(requestAttributes.GetSubresource())
+ }
+ b.WriteString(`"`)
+ if len(requestAttributes.GetName()) > 0 {
+ b.WriteString(` named "`)
+ b.WriteString(requestAttributes.GetName())
+ b.WriteString(`"`)
+ }
+ operation = b.String()
+ } else {
+ operation = fmt.Sprintf("%q nonResourceURL %q", requestAttributes.GetVerb(), requestAttributes.GetPath())
+ }
+
+ var scope string
+ if ns := requestAttributes.GetNamespace(); len(ns) > 0 {
+ scope = fmt.Sprintf("in namespace %q", ns)
+ } else {
+ scope = "cluster-wide"
+ }
+
+ klog.Infof("RBAC: no rules authorize user %q with groups %q to %s %s", requestAttributes.GetUser().GetName(), requestAttributes.GetUser().GetGroups(), operation, scope)
+ }
+
+ reason := ""
+ if len(ruleCheckingVisitor.errors) > 0 {
+ reason = fmt.Sprintf("RBAC: %v", utilerrors.NewAggregate(ruleCheckingVisitor.errors))
+ }
+ return authorizer.DecisionNoOpinion, reason, nil
+}
+
+func (r *RBACAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
+ var (
+ resourceRules []authorizer.ResourceRuleInfo
+ nonResourceRules []authorizer.NonResourceRuleInfo
+ )
+
+ policyRules, err := r.authorizationRuleResolver.RulesFor(user, namespace)
+ for _, policyRule := range policyRules {
+ if len(policyRule.Resources) > 0 {
+ r := authorizer.DefaultResourceRuleInfo{
+ Verbs: policyRule.Verbs,
+ APIGroups: policyRule.APIGroups,
+ Resources: policyRule.Resources,
+ ResourceNames: policyRule.ResourceNames,
+ }
+ var resourceRule authorizer.ResourceRuleInfo = &r
+ resourceRules = append(resourceRules, resourceRule)
+ }
+ if len(policyRule.NonResourceURLs) > 0 {
+ r := authorizer.DefaultNonResourceRuleInfo{
+ Verbs: policyRule.Verbs,
+ NonResourceURLs: policyRule.NonResourceURLs,
+ }
+ var nonResourceRule authorizer.NonResourceRuleInfo = &r
+ nonResourceRules = append(nonResourceRules, nonResourceRule)
+ }
+ }
+ return resourceRules, nonResourceRules, false, err
+}
+
+func New(roles rbacregistryvalidation.RoleGetter, roleBindings rbacregistryvalidation.RoleBindingLister, clusterRoles rbacregistryvalidation.ClusterRoleGetter, clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister) *RBACAuthorizer {
+ authorizer := &RBACAuthorizer{
+ authorizationRuleResolver: rbacregistryvalidation.NewDefaultRuleResolver(
+ roles, roleBindings, clusterRoles, clusterRoleBindings,
+ ),
+ }
+ return authorizer
+}
+
+func RulesAllow(requestAttributes authorizer.Attributes, rules ...rbacv1.PolicyRule) bool {
+ for i := range rules {
+ if RuleAllows(requestAttributes, &rules[i]) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func RuleAllows(requestAttributes authorizer.Attributes, rule *rbacv1.PolicyRule) bool {
+ if requestAttributes.IsResourceRequest() {
+ combinedResource := requestAttributes.GetResource()
+ if len(requestAttributes.GetSubresource()) > 0 {
+ combinedResource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource()
+ }
+
+ return rbacv1helpers.VerbMatches(rule, requestAttributes.GetVerb()) &&
+ rbacv1helpers.APIGroupMatches(rule, requestAttributes.GetAPIGroup()) &&
+ rbacv1helpers.ResourceMatches(rule, combinedResource, requestAttributes.GetSubresource()) &&
+ rbacv1helpers.ResourceNameMatches(rule, requestAttributes.GetName())
+ }
+
+ return rbacv1helpers.VerbMatches(rule, requestAttributes.GetVerb()) &&
+ rbacv1helpers.NonResourceURLMatches(rule, requestAttributes.GetPath())
+}
+
+type RoleGetter struct {
+ Lister rbaclisters.RoleLister
+}
+
+func (g *RoleGetter) GetRole(namespace, name string) (*rbacv1.Role, error) {
+ return g.Lister.Roles(namespace).Get(name)
+}
+
+type RoleBindingLister struct {
+ Lister rbaclisters.RoleBindingLister
+}
+
+func (l *RoleBindingLister) ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error) {
+ return l.Lister.RoleBindings(namespace).List(labels.Everything())
+}
+
+type ClusterRoleGetter struct {
+ Lister rbaclisters.ClusterRoleLister
+}
+
+func (g *ClusterRoleGetter) GetClusterRole(name string) (*rbacv1.ClusterRole, error) {
+ return g.Lister.Get(name)
+}
+
+type ClusterRoleBindingLister struct {
+ Lister rbaclisters.ClusterRoleBindingLister
+}
+
+func (l *ClusterRoleBindingLister) ListClusterRoleBindings() ([]*rbacv1.ClusterRoleBinding, error) {
+ return l.Lister.List(labels.Everything())
+}
diff --git a/vendor/k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/subject_locator.go b/vendor/k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/subject_locator.go
new file mode 100644
index 000000000..cdd327e5b
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/subject_locator.go
@@ -0,0 +1,123 @@
+/*
+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 rbac implements the authorizer.Authorizer interface using roles base access control.
+package rbac
+
+import (
+ rbacv1 "k8s.io/api/rbac/v1"
+ utilerrors "k8s.io/apimachinery/pkg/util/errors"
+ "k8s.io/apiserver/pkg/authentication/user"
+ "k8s.io/apiserver/pkg/authorization/authorizer"
+ rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
+)
+
+type RoleToRuleMapper interface {
+ // GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding. The passed namespace should be the namespace
+ // of the role binding, the empty string if a cluster role binding.
+ GetRoleReferenceRules(roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error)
+}
+
+type SubjectLocator interface {
+ AllowedSubjects(attributes authorizer.Attributes) ([]rbacv1.Subject, error)
+}
+
+var _ = SubjectLocator(&SubjectAccessEvaluator{})
+
+type SubjectAccessEvaluator struct {
+ superUser string
+
+ roleBindingLister rbacregistryvalidation.RoleBindingLister
+ clusterRoleBindingLister rbacregistryvalidation.ClusterRoleBindingLister
+ roleToRuleMapper RoleToRuleMapper
+}
+
+func NewSubjectAccessEvaluator(roles rbacregistryvalidation.RoleGetter, roleBindings rbacregistryvalidation.RoleBindingLister, clusterRoles rbacregistryvalidation.ClusterRoleGetter, clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister, superUser string) *SubjectAccessEvaluator {
+ subjectLocator := &SubjectAccessEvaluator{
+ superUser: superUser,
+ roleBindingLister: roleBindings,
+ clusterRoleBindingLister: clusterRoleBindings,
+ roleToRuleMapper: rbacregistryvalidation.NewDefaultRuleResolver(
+ roles, roleBindings, clusterRoles, clusterRoleBindings,
+ ),
+ }
+ return subjectLocator
+}
+
+// AllowedSubjects returns the subjects that can perform an action and any errors encountered while computing the list.
+// It is possible to have both subjects and errors returned if some rolebindings couldn't be resolved, but others could be.
+func (r *SubjectAccessEvaluator) AllowedSubjects(requestAttributes authorizer.Attributes) ([]rbacv1.Subject, error) {
+ subjects := []rbacv1.Subject{{Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: user.SystemPrivilegedGroup}}
+ if len(r.superUser) > 0 {
+ subjects = append(subjects, rbacv1.Subject{Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: r.superUser})
+ }
+ errorlist := []error{}
+
+ if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil {
+ errorlist = append(errorlist, err)
+
+ } else {
+ for _, clusterRoleBinding := range clusterRoleBindings {
+ rules, err := r.roleToRuleMapper.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
+ if err != nil {
+ // if we have an error, just keep track of it and keep processing. Since rules are additive,
+ // missing a reference is bad, but we can continue with other rolebindings and still have a list
+ // that does not contain any invalid values
+ errorlist = append(errorlist, err)
+ }
+ if RulesAllow(requestAttributes, rules...) {
+ subjects = append(subjects, clusterRoleBinding.Subjects...)
+ }
+ }
+ }
+
+ if namespace := requestAttributes.GetNamespace(); len(namespace) > 0 {
+ if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil {
+ errorlist = append(errorlist, err)
+
+ } else {
+ for _, roleBinding := range roleBindings {
+ rules, err := r.roleToRuleMapper.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
+ if err != nil {
+ // if we have an error, just keep track of it and keep processing. Since rules are additive,
+ // missing a reference is bad, but we can continue with other rolebindings and still have a list
+ // that does not contain any invalid values
+ errorlist = append(errorlist, err)
+ }
+ if RulesAllow(requestAttributes, rules...) {
+ subjects = append(subjects, roleBinding.Subjects...)
+ }
+ }
+ }
+ }
+
+ dedupedSubjects := []rbacv1.Subject{}
+ for _, subject := range subjects {
+ found := false
+ for _, curr := range dedupedSubjects {
+ if curr == subject {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ dedupedSubjects = append(dedupedSubjects, subject)
+ }
+ }
+
+ return subjects, utilerrors.NewAggregate(errorlist)
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 4e8dc5f4d..ab6d0cb47 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1989,6 +1989,7 @@ k8s.io/component-base/version/verflag
# k8s.io/component-helpers v0.0.0 => github.com/kubeedge/kubernetes/staging/src/k8s.io/component-helpers v1.23.15-kubeedge1
## explicit; go 1.16
k8s.io/component-helpers/apimachinery/lease
+k8s.io/component-helpers/auth/rbac/validation
k8s.io/component-helpers/node/util
k8s.io/component-helpers/node/util/sysctl
k8s.io/component-helpers/scheduling/corev1
@@ -2320,6 +2321,7 @@ k8s.io/kubernetes/pkg/proxy/metaproxier
k8s.io/kubernetes/pkg/proxy/metrics
k8s.io/kubernetes/pkg/proxy/util
k8s.io/kubernetes/pkg/proxy/util/iptables
+k8s.io/kubernetes/pkg/registry/rbac/validation
k8s.io/kubernetes/pkg/scheduler/apis/config
k8s.io/kubernetes/pkg/scheduler/apis/config/v1beta2
k8s.io/kubernetes/pkg/scheduler/apis/config/v1beta3
@@ -2389,6 +2391,7 @@ k8s.io/kubernetes/pkg/volume/util/volumepathhandler
k8s.io/kubernetes/pkg/volume/validation
k8s.io/kubernetes/pkg/windows/service
k8s.io/kubernetes/plugin/pkg/admission/serviceaccount
+k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac
k8s.io/kubernetes/test/e2e/apps
k8s.io/kubernetes/test/e2e/auth
k8s.io/kubernetes/test/e2e/common
@@ -2509,6 +2512,7 @@ sigs.k8s.io/controller-runtime/pkg/certwatcher
sigs.k8s.io/controller-runtime/pkg/client
sigs.k8s.io/controller-runtime/pkg/client/apiutil
sigs.k8s.io/controller-runtime/pkg/client/config
+sigs.k8s.io/controller-runtime/pkg/client/fake
sigs.k8s.io/controller-runtime/pkg/cluster
sigs.k8s.io/controller-runtime/pkg/config
sigs.k8s.io/controller-runtime/pkg/config/v1alpha1
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go
new file mode 100644
index 000000000..f72afe19d
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go
@@ -0,0 +1,765 @@
+/*
+Copyright 2018 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 (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+ "sync"
+
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ utilrand "k8s.io/apimachinery/pkg/util/rand"
+ "k8s.io/apimachinery/pkg/util/validation/field"
+ "k8s.io/apimachinery/pkg/watch"
+ "k8s.io/client-go/kubernetes/scheme"
+ "k8s.io/client-go/testing"
+
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+ "sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
+)
+
+type versionedTracker struct {
+ testing.ObjectTracker
+ scheme *runtime.Scheme
+}
+
+type fakeClient struct {
+ tracker versionedTracker
+ scheme *runtime.Scheme
+ restMapper meta.RESTMapper
+ schemeWriteLock sync.Mutex
+}
+
+var _ client.WithWatch = &fakeClient{}
+
+const (
+ maxNameLength = 63
+ randomLength = 5
+ maxGeneratedNameLength = maxNameLength - randomLength
+)
+
+// NewFakeClient creates a new fake client for testing.
+// You can choose to initialize it with a slice of runtime.Object.
+//
+// Deprecated: Please use NewClientBuilder instead.
+func NewFakeClient(initObjs ...runtime.Object) client.WithWatch {
+ return NewClientBuilder().WithRuntimeObjects(initObjs...).Build()
+}
+
+// NewFakeClientWithScheme creates a new fake client with the given scheme
+// for testing.
+// You can choose to initialize it with a slice of runtime.Object.
+//
+// Deprecated: Please use NewClientBuilder instead.
+func NewFakeClientWithScheme(clientScheme *runtime.Scheme, initObjs ...runtime.Object) client.WithWatch {
+ return NewClientBuilder().WithScheme(clientScheme).WithRuntimeObjects(initObjs...).Build()
+}
+
+// NewClientBuilder returns a new builder to create a fake client.
+func NewClientBuilder() *ClientBuilder {
+ return &ClientBuilder{}
+}
+
+// ClientBuilder builds a fake client.
+type ClientBuilder struct {
+ scheme *runtime.Scheme
+ restMapper meta.RESTMapper
+ initObject []client.Object
+ initLists []client.ObjectList
+ initRuntimeObjects []runtime.Object
+}
+
+// WithScheme sets this builder's internal scheme.
+// If not set, defaults to client-go's global scheme.Scheme.
+func (f *ClientBuilder) WithScheme(scheme *runtime.Scheme) *ClientBuilder {
+ f.scheme = scheme
+ return f
+}
+
+// WithRESTMapper sets this builder's restMapper.
+// The restMapper is directly set as mapper in the Client. This can be used for example
+// with a meta.DefaultRESTMapper to provide a static rest mapping.
+// If not set, defaults to an empty meta.DefaultRESTMapper.
+func (f *ClientBuilder) WithRESTMapper(restMapper meta.RESTMapper) *ClientBuilder {
+ f.restMapper = restMapper
+ return f
+}
+
+// WithObjects can be optionally used to initialize this fake client with client.Object(s).
+func (f *ClientBuilder) WithObjects(initObjs ...client.Object) *ClientBuilder {
+ f.initObject = append(f.initObject, initObjs...)
+ return f
+}
+
+// WithLists can be optionally used to initialize this fake client with client.ObjectList(s).
+func (f *ClientBuilder) WithLists(initLists ...client.ObjectList) *ClientBuilder {
+ f.initLists = append(f.initLists, initLists...)
+ return f
+}
+
+// WithRuntimeObjects can be optionally used to initialize this fake client with runtime.Object(s).
+func (f *ClientBuilder) WithRuntimeObjects(initRuntimeObjs ...runtime.Object) *ClientBuilder {
+ f.initRuntimeObjects = append(f.initRuntimeObjects, initRuntimeObjs...)
+ return f
+}
+
+// Build builds and returns a new fake client.
+func (f *ClientBuilder) Build() client.WithWatch {
+ if f.scheme == nil {
+ f.scheme = scheme.Scheme
+ }
+ if f.restMapper == nil {
+ f.restMapper = meta.NewDefaultRESTMapper([]schema.GroupVersion{})
+ }
+
+ tracker := versionedTracker{ObjectTracker: testing.NewObjectTracker(f.scheme, scheme.Codecs.UniversalDecoder()), scheme: f.scheme}
+ for _, obj := range f.initObject {
+ if err := tracker.Add(obj); err != nil {
+ panic(fmt.Errorf("failed to add object %v to fake client: %w", obj, err))
+ }
+ }
+ for _, obj := range f.initLists {
+ if err := tracker.Add(obj); err != nil {
+ panic(fmt.Errorf("failed to add list %v to fake client: %w", obj, err))
+ }
+ }
+ for _, obj := range f.initRuntimeObjects {
+ if err := tracker.Add(obj); err != nil {
+ panic(fmt.Errorf("failed to add runtime object %v to fake client: %w", obj, err))
+ }
+ }
+ return &fakeClient{
+ tracker: tracker,
+ scheme: f.scheme,
+ restMapper: f.restMapper,
+ }
+}
+
+const trackerAddResourceVersion = "999"
+
+func (t versionedTracker) Add(obj runtime.Object) error {
+ var objects []runtime.Object
+ if meta.IsListType(obj) {
+ var err error
+ objects, err = meta.ExtractList(obj)
+ if err != nil {
+ return err
+ }
+ } else {
+ objects = []runtime.Object{obj}
+ }
+ for _, obj := range objects {
+ accessor, err := meta.Accessor(obj)
+ if err != nil {
+ return fmt.Errorf("failed to get accessor for object: %w", err)
+ }
+ if accessor.GetResourceVersion() == "" {
+ // We use a "magic" value of 999 here because this field
+ // is parsed as uint and and 0 is already used in Update.
+ // As we can't go lower, go very high instead so this can
+ // be recognized
+ accessor.SetResourceVersion(trackerAddResourceVersion)
+ }
+
+ obj, err = convertFromUnstructuredIfNecessary(t.scheme, obj)
+ if err != nil {
+ return err
+ }
+ if err := t.ObjectTracker.Add(obj); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (t versionedTracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
+ accessor, err := meta.Accessor(obj)
+ if err != nil {
+ return fmt.Errorf("failed to get accessor for object: %v", err)
+ }
+ if accessor.GetName() == "" {
+ return apierrors.NewInvalid(
+ obj.GetObjectKind().GroupVersionKind().GroupKind(),
+ accessor.GetName(),
+ field.ErrorList{field.Required(field.NewPath("metadata.name"), "name is required")})
+ }
+ if accessor.GetResourceVersion() != "" {
+ return apierrors.NewBadRequest("resourceVersion can not be set for Create requests")
+ }
+ accessor.SetResourceVersion("1")
+ obj, err = convertFromUnstructuredIfNecessary(t.scheme, obj)
+ if err != nil {
+ return err
+ }
+ if err := t.ObjectTracker.Create(gvr, obj, ns); err != nil {
+ accessor.SetResourceVersion("")
+ return err
+ }
+
+ return nil
+}
+
+// convertFromUnstructuredIfNecessary will convert *unstructured.Unstructured for a GVK that is recocnized
+// by the schema into the whatever the schema produces with New() for said GVK.
+// This is required because the tracker unconditionally saves on manipulations, but it's List() implementation
+// tries to assign whatever it finds into a ListType it gets from schema.New() - Thus we have to ensure
+// we save as the very same type, otherwise subsequent List requests will fail.
+func convertFromUnstructuredIfNecessary(s *runtime.Scheme, o runtime.Object) (runtime.Object, error) {
+ u, isUnstructured := o.(*unstructured.Unstructured)
+ if !isUnstructured || !s.Recognizes(u.GroupVersionKind()) {
+ return o, nil
+ }
+
+ typed, err := s.New(u.GroupVersionKind())
+ if err != nil {
+ return nil, fmt.Errorf("scheme recognizes %s but failed to produce an object for it: %w", u.GroupVersionKind().String(), err)
+ }
+
+ unstructuredSerialized, err := json.Marshal(u)
+ if err != nil {
+ return nil, fmt.Errorf("failed to serialize %T: %w", unstructuredSerialized, err)
+ }
+ if err := json.Unmarshal(unstructuredSerialized, typed); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal the content of %T into %T: %w", u, typed, err)
+ }
+
+ return typed, nil
+}
+
+func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
+ accessor, err := meta.Accessor(obj)
+ if err != nil {
+ return fmt.Errorf("failed to get accessor for object: %v", err)
+ }
+
+ if accessor.GetName() == "" {
+ return apierrors.NewInvalid(
+ obj.GetObjectKind().GroupVersionKind().GroupKind(),
+ accessor.GetName(),
+ field.ErrorList{field.Required(field.NewPath("metadata.name"), "name is required")})
+ }
+
+ gvk := obj.GetObjectKind().GroupVersionKind()
+ if gvk.Empty() {
+ gvk, err = apiutil.GVKForObject(obj, t.scheme)
+ if err != nil {
+ return err
+ }
+ }
+
+ oldObject, err := t.ObjectTracker.Get(gvr, ns, accessor.GetName())
+ if err != nil {
+ // If the resource is not found and the resource allows create on update, issue a
+ // create instead.
+ if apierrors.IsNotFound(err) && allowsCreateOnUpdate(gvk) {
+ return t.Create(gvr, obj, ns)
+ }
+ return err
+ }
+
+ oldAccessor, err := meta.Accessor(oldObject)
+ if err != nil {
+ return err
+ }
+
+ // If the new object does not have the resource version set and it allows unconditional update,
+ // default it to the resource version of the existing resource
+ if accessor.GetResourceVersion() == "" && allowsUnconditionalUpdate(gvk) {
+ accessor.SetResourceVersion(oldAccessor.GetResourceVersion())
+ }
+ if accessor.GetResourceVersion() != oldAccessor.GetResourceVersion() {
+ return apierrors.NewConflict(gvr.GroupResource(), accessor.GetName(), errors.New("object was modified"))
+ }
+ if oldAccessor.GetResourceVersion() == "" {
+ oldAccessor.SetResourceVersion("0")
+ }
+ intResourceVersion, err := strconv.ParseUint(oldAccessor.GetResourceVersion(), 10, 64)
+ if err != nil {
+ return fmt.Errorf("can not convert resourceVersion %q to int: %v", oldAccessor.GetResourceVersion(), err)
+ }
+ intResourceVersion++
+ accessor.SetResourceVersion(strconv.FormatUint(intResourceVersion, 10))
+ if !accessor.GetDeletionTimestamp().IsZero() && len(accessor.GetFinalizers()) == 0 {
+ return t.ObjectTracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
+ }
+ obj, err = convertFromUnstructuredIfNecessary(t.scheme, obj)
+ if err != nil {
+ return err
+ }
+ return t.ObjectTracker.Update(gvr, obj, ns)
+}
+
+func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+ gvr, err := getGVRFromObject(obj, c.scheme)
+ if err != nil {
+ return err
+ }
+ o, err := c.tracker.Get(gvr, key.Namespace, key.Name)
+ if err != nil {
+ return err
+ }
+
+ gvk, err := apiutil.GVKForObject(obj, c.scheme)
+ if err != nil {
+ return err
+ }
+ ta, err := meta.TypeAccessor(o)
+ if err != nil {
+ return err
+ }
+ ta.SetKind(gvk.Kind)
+ ta.SetAPIVersion(gvk.GroupVersion().String())
+
+ j, err := json.Marshal(o)
+ if err != nil {
+ return err
+ }
+ decoder := scheme.Codecs.UniversalDecoder()
+ zero(obj)
+ _, _, err = decoder.Decode(j, nil, obj)
+ return err
+}
+
+func (c *fakeClient) Watch(ctx context.Context, list client.ObjectList, opts ...client.ListOption) (watch.Interface, error) {
+ gvk, err := apiutil.GVKForObject(list, c.scheme)
+ if err != nil {
+ return nil, err
+ }
+
+ if strings.HasSuffix(gvk.Kind, "List") {
+ gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
+ }
+
+ listOpts := client.ListOptions{}
+ listOpts.ApplyOptions(opts)
+
+ gvr, _ := meta.UnsafeGuessKindToResource(gvk)
+ return c.tracker.Watch(gvr, listOpts.Namespace)
+}
+
+func (c *fakeClient) List(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error {
+ gvk, err := apiutil.GVKForObject(obj, c.scheme)
+ if err != nil {
+ return err
+ }
+
+ originalKind := gvk.Kind
+
+ if strings.HasSuffix(gvk.Kind, "List") {
+ gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
+ }
+
+ if _, isUnstructuredList := obj.(*unstructured.UnstructuredList); isUnstructuredList && !c.scheme.Recognizes(gvk) {
+ // We need to register the ListKind with UnstructuredList:
+ // https://github.com/kubernetes/kubernetes/blob/7b2776b89fb1be28d4e9203bdeec079be903c103/staging/src/k8s.io/client-go/dynamic/fake/simple.go#L44-L51
+ c.schemeWriteLock.Lock()
+ c.scheme.AddKnownTypeWithName(gvk.GroupVersion().WithKind(gvk.Kind+"List"), &unstructured.UnstructuredList{})
+ c.schemeWriteLock.Unlock()
+ }
+
+ listOpts := client.ListOptions{}
+ listOpts.ApplyOptions(opts)
+
+ gvr, _ := meta.UnsafeGuessKindToResource(gvk)
+ o, err := c.tracker.List(gvr, gvk, listOpts.Namespace)
+ if err != nil {
+ return err
+ }
+
+ ta, err := meta.TypeAccessor(o)
+ if err != nil {
+ return err
+ }
+ ta.SetKind(originalKind)
+ ta.SetAPIVersion(gvk.GroupVersion().String())
+
+ j, err := json.Marshal(o)
+ if err != nil {
+ return err
+ }
+ decoder := scheme.Codecs.UniversalDecoder()
+ zero(obj)
+ _, _, err = decoder.Decode(j, nil, obj)
+ if err != nil {
+ return err
+ }
+
+ if listOpts.LabelSelector != nil {
+ objs, err := meta.ExtractList(obj)
+ if err != nil {
+ return err
+ }
+ filteredObjs, err := objectutil.FilterWithLabels(objs, listOpts.LabelSelector)
+ if err != nil {
+ return err
+ }
+ err = meta.SetList(obj, filteredObjs)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (c *fakeClient) Scheme() *runtime.Scheme {
+ return c.scheme
+}
+
+func (c *fakeClient) RESTMapper() meta.RESTMapper {
+ return c.restMapper
+}
+
+func (c *fakeClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
+ createOptions := &client.CreateOptions{}
+ createOptions.ApplyOptions(opts)
+
+ for _, dryRunOpt := range createOptions.DryRun {
+ if dryRunOpt == metav1.DryRunAll {
+ return nil
+ }
+ }
+
+ gvr, err := getGVRFromObject(obj, c.scheme)
+ if err != nil {
+ return err
+ }
+ accessor, err := meta.Accessor(obj)
+ if err != nil {
+ return err
+ }
+
+ if accessor.GetName() == "" && accessor.GetGenerateName() != "" {
+ base := accessor.GetGenerateName()
+ if len(base) > maxGeneratedNameLength {
+ base = base[:maxGeneratedNameLength]
+ }
+ accessor.SetName(fmt.Sprintf("%s%s", base, utilrand.String(randomLength)))
+ }
+
+ return c.tracker.Create(gvr, obj, accessor.GetNamespace())
+}
+
+func (c *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
+ gvr, err := getGVRFromObject(obj, c.scheme)
+ if err != nil {
+ return err
+ }
+ accessor, err := meta.Accessor(obj)
+ if err != nil {
+ return err
+ }
+ delOptions := client.DeleteOptions{}
+ delOptions.ApplyOptions(opts)
+
+ // Check the ResourceVersion if that Precondition was specified.
+ if delOptions.Preconditions != nil && delOptions.Preconditions.ResourceVersion != nil {
+ name := accessor.GetName()
+ dbObj, err := c.tracker.Get(gvr, accessor.GetNamespace(), name)
+ if err != nil {
+ return err
+ }
+ oldAccessor, err := meta.Accessor(dbObj)
+ if err != nil {
+ return err
+ }
+ actualRV := oldAccessor.GetResourceVersion()
+ expectRV := *delOptions.Preconditions.ResourceVersion
+ if actualRV != expectRV {
+ msg := fmt.Sprintf(
+ "the ResourceVersion in the precondition (%s) does not match the ResourceVersion in record (%s). "+
+ "The object might have been modified",
+ expectRV, actualRV)
+ return apierrors.NewConflict(gvr.GroupResource(), name, errors.New(msg))
+ }
+ }
+
+ return c.deleteObject(gvr, accessor)
+}
+
+func (c *fakeClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
+ gvk, err := apiutil.GVKForObject(obj, c.scheme)
+ if err != nil {
+ return err
+ }
+
+ dcOptions := client.DeleteAllOfOptions{}
+ dcOptions.ApplyOptions(opts)
+
+ gvr, _ := meta.UnsafeGuessKindToResource(gvk)
+ o, err := c.tracker.List(gvr, gvk, dcOptions.Namespace)
+ if err != nil {
+ return err
+ }
+
+ objs, err := meta.ExtractList(o)
+ if err != nil {
+ return err
+ }
+ filteredObjs, err := objectutil.FilterWithLabels(objs, dcOptions.LabelSelector)
+ if err != nil {
+ return err
+ }
+ for _, o := range filteredObjs {
+ accessor, err := meta.Accessor(o)
+ if err != nil {
+ return err
+ }
+ err = c.deleteObject(gvr, accessor)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (c *fakeClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
+ updateOptions := &client.UpdateOptions{}
+ updateOptions.ApplyOptions(opts)
+
+ for _, dryRunOpt := range updateOptions.DryRun {
+ if dryRunOpt == metav1.DryRunAll {
+ return nil
+ }
+ }
+
+ gvr, err := getGVRFromObject(obj, c.scheme)
+ if err != nil {
+ return err
+ }
+ accessor, err := meta.Accessor(obj)
+ if err != nil {
+ return err
+ }
+ return c.tracker.Update(gvr, obj, accessor.GetNamespace())
+}
+
+func (c *fakeClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
+ patchOptions := &client.PatchOptions{}
+ patchOptions.ApplyOptions(opts)
+
+ for _, dryRunOpt := range patchOptions.DryRun {
+ if dryRunOpt == metav1.DryRunAll {
+ return nil
+ }
+ }
+
+ gvr, err := getGVRFromObject(obj, c.scheme)
+ if err != nil {
+ return err
+ }
+ accessor, err := meta.Accessor(obj)
+ if err != nil {
+ return err
+ }
+ data, err := patch.Data(obj)
+ if err != nil {
+ return err
+ }
+
+ reaction := testing.ObjectReaction(c.tracker)
+ handled, o, err := reaction(testing.NewPatchAction(gvr, accessor.GetNamespace(), accessor.GetName(), patch.Type(), data))
+ if err != nil {
+ return err
+ }
+ if !handled {
+ panic("tracker could not handle patch method")
+ }
+
+ gvk, err := apiutil.GVKForObject(obj, c.scheme)
+ if err != nil {
+ return err
+ }
+ ta, err := meta.TypeAccessor(o)
+ if err != nil {
+ return err
+ }
+ ta.SetKind(gvk.Kind)
+ ta.SetAPIVersion(gvk.GroupVersion().String())
+
+ j, err := json.Marshal(o)
+ if err != nil {
+ return err
+ }
+ decoder := scheme.Codecs.UniversalDecoder()
+ zero(obj)
+ _, _, err = decoder.Decode(j, nil, obj)
+ return err
+}
+
+func (c *fakeClient) Status() client.StatusWriter {
+ return &fakeStatusWriter{client: c}
+}
+
+func (c *fakeClient) deleteObject(gvr schema.GroupVersionResource, accessor metav1.Object) error {
+ old, err := c.tracker.Get(gvr, accessor.GetNamespace(), accessor.GetName())
+ if err == nil {
+ oldAccessor, err := meta.Accessor(old)
+ if err == nil {
+ if len(oldAccessor.GetFinalizers()) > 0 {
+ now := metav1.Now()
+ oldAccessor.SetDeletionTimestamp(&now)
+ return c.tracker.Update(gvr, old, accessor.GetNamespace())
+ }
+ }
+ }
+
+ //TODO: implement propagation
+ return c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
+}
+
+func getGVRFromObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionResource, error) {
+ gvk, err := apiutil.GVKForObject(obj, scheme)
+ if err != nil {
+ return schema.GroupVersionResource{}, err
+ }
+ gvr, _ := meta.UnsafeGuessKindToResource(gvk)
+ return gvr, nil
+}
+
+type fakeStatusWriter struct {
+ client *fakeClient
+}
+
+func (sw *fakeStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
+ // TODO(droot): This results in full update of the obj (spec + status). Need
+ // a way to update status field only.
+ return sw.client.Update(ctx, obj, opts...)
+}
+
+func (sw *fakeStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
+ // TODO(droot): This results in full update of the obj (spec + status). Need
+ // a way to update status field only.
+ return sw.client.Patch(ctx, obj, patch, opts...)
+}
+
+func allowsUnconditionalUpdate(gvk schema.GroupVersionKind) bool {
+ switch gvk.Group {
+ case "apps":
+ switch gvk.Kind {
+ case "ControllerRevision", "DaemonSet", "Deployment", "ReplicaSet", "StatefulSet":
+ return true
+ }
+ case "autoscaling":
+ switch gvk.Kind {
+ case "HorizontalPodAutoscaler":
+ return true
+ }
+ case "batch":
+ switch gvk.Kind {
+ case "CronJob", "Job":
+ return true
+ }
+ case "certificates":
+ switch gvk.Kind {
+ case "Certificates":
+ return true
+ }
+ case "flowcontrol":
+ switch gvk.Kind {
+ case "FlowSchema", "PriorityLevelConfiguration":
+ return true
+ }
+ case "networking":
+ switch gvk.Kind {
+ case "Ingress", "IngressClass", "NetworkPolicy":
+ return true
+ }
+ case "policy":
+ switch gvk.Kind {
+ case "PodSecurityPolicy":
+ return true
+ }
+ case "rbac":
+ switch gvk.Kind {
+ case "ClusterRole", "ClusterRoleBinding", "Role", "RoleBinding":
+ return true
+ }
+ case "scheduling":
+ switch gvk.Kind {
+ case "PriorityClass":
+ return true
+ }
+ case "settings":
+ switch gvk.Kind {
+ case "PodPreset":
+ return true
+ }
+ case "storage":
+ switch gvk.Kind {
+ case "StorageClass":
+ return true
+ }
+ case "":
+ switch gvk.Kind {
+ case "ConfigMap", "Endpoint", "Event", "LimitRange", "Namespace", "Node",
+ "PersistentVolume", "PersistentVolumeClaim", "Pod", "PodTemplate",
+ "ReplicationController", "ResourceQuota", "Secret", "Service",
+ "ServiceAccount", "EndpointSlice":
+ return true
+ }
+ }
+
+ return false
+}
+
+func allowsCreateOnUpdate(gvk schema.GroupVersionKind) bool {
+ switch gvk.Group {
+ case "coordination":
+ switch gvk.Kind {
+ case "Lease":
+ return true
+ }
+ case "node":
+ switch gvk.Kind {
+ case "RuntimeClass":
+ return true
+ }
+ case "rbac":
+ switch gvk.Kind {
+ case "ClusterRole", "ClusterRoleBinding", "Role", "RoleBinding":
+ return true
+ }
+ case "":
+ switch gvk.Kind {
+ case "Endpoint", "Event", "LimitRange", "Service":
+ return true
+ }
+ }
+
+ return false
+}
+
+// zero zeros the value of a pointer.
+func zero(x interface{}) {
+ if x == nil {
+ return
+ }
+ res := reflect.ValueOf(x).Elem()
+ res.Set(reflect.Zero(res.Type()))
+}
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/doc.go
new file mode 100644
index 000000000..7d680690d
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/doc.go
@@ -0,0 +1,39 @@
+/*
+Copyright 2018 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 provides a fake client for testing.
+
+A fake client is backed by its simple object store indexed by GroupVersionResource.
+You can create a fake client with optional objects.
+
+ client := NewFakeClientWithScheme(scheme, initObjs...) // initObjs is a slice of runtime.Object
+
+You can invoke the methods defined in the Client interface.
+
+When in doubt, it's almost always better not to use this package and instead use
+envtest.Environment with a real client and API server.
+
+WARNING: ⚠️ Current Limitations / Known Issues with the fake Client ⚠️
+- This client does not have a way to inject specific errors to test handled vs. unhandled errors.
+- There is some support for sub resources which can cause issues with tests if you're trying to update
+ e.g. metadata and status in the same reconcile.
+- No OpeanAPI validation is performed when creating or updating objects.
+- ObjectMeta's `Generation` and `ResourceVersion` don't behave properly, Patch or Update
+operations that rely on these fields will fail, or give false positives.
+
+*/
+package fake