hub csr controller

This commit is contained in:
liuwei
2020-04-27 11:24:59 +08:00
committed by liuwei
parent 15a0c82c2c
commit ccc2a489e7
11 changed files with 803 additions and 4 deletions
+1
View File
@@ -11,6 +11,7 @@ include $(addprefix ./vendor/github.com/openshift/build-machinery-go/make/, \
)
$(call add-bindata,spokecluster,./pkg/hub/spokecluster/manifests/...,bindata,bindata,./pkg/hub/spokecluster/bindata/bindata.go)
$(call add-bindata,csr,./pkg/hub/csr/manifests/...,bindata,bindata,./pkg/hub/csr/bindata/bindata.go)
clean:
$(RM) ./registration
+262
View File
@@ -0,0 +1,262 @@
// Code generated by go-bindata.
// sources:
// pkg/hub/csr/manifests/clusterrole.yaml
// pkg/hub/csr/manifests/clusterrolebinding.yaml
// DO NOT EDIT!
package bindata
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
type asset struct {
bytes []byte
info os.FileInfo
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var _pkgHubCsrManifestsClusterroleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: system:open-cluster-management:spokecluster:{{ .SpokeClusterName }}-csrapproved
rules:
# Allow spoke agent to create/get/list/watch certificatesigningrequests
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests"]
verbs: ["create", "get", "list", "watch"]
# Allow spoke agent to create/get/list/watch spoke cluster
- apiGroups: ["cluster.open-cluster-management.io"]
resources: ["spokeclusters"]
verbs: ["create", "get", "list", "watch"]
`)
func pkgHubCsrManifestsClusterroleYamlBytes() ([]byte, error) {
return _pkgHubCsrManifestsClusterroleYaml, nil
}
func pkgHubCsrManifestsClusterroleYaml() (*asset, error) {
bytes, err := pkgHubCsrManifestsClusterroleYamlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "pkg/hub/csr/manifests/clusterrole.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _pkgHubCsrManifestsClusterrolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: system:open-cluster-management:spokecluster:{{ .SpokeClusterName }}-csrapproved
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:open-cluster-management:spokecluster:{{ .SpokeClusterName }}-csrapproved
subjects:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:open-cluster-management:{{ .SpokeClusterName }}
`)
func pkgHubCsrManifestsClusterrolebindingYamlBytes() ([]byte, error) {
return _pkgHubCsrManifestsClusterrolebindingYaml, nil
}
func pkgHubCsrManifestsClusterrolebindingYaml() (*asset, error) {
bytes, err := pkgHubCsrManifestsClusterrolebindingYamlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "pkg/hub/csr/manifests/clusterrolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"pkg/hub/csr/manifests/clusterrole.yaml": pkgHubCsrManifestsClusterroleYaml,
"pkg/hub/csr/manifests/clusterrolebinding.yaml": pkgHubCsrManifestsClusterrolebindingYaml,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"pkg": {nil, map[string]*bintree{
"hub": {nil, map[string]*bintree{
"csr": {nil, map[string]*bintree{
"manifests": {nil, map[string]*bintree{
"clusterrole.yaml": {pkgHubCsrManifestsClusterroleYaml, map[string]*bintree{}},
"clusterrolebinding.yaml": {pkgHubCsrManifestsClusterrolebindingYaml, map[string]*bintree{}},
}},
}},
}},
}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}
+195
View File
@@ -0,0 +1,195 @@
package csr
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"path/filepath"
"strings"
"github.com/open-cluster-management/registration/pkg/hub/csr/bindata"
"github.com/openshift/library-go/pkg/assets"
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/resource/resourceapply"
operatorhelpers "github.com/openshift/library-go/pkg/operator/v1helpers"
authorizationv1 "k8s.io/api/authorization/v1"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/klog"
)
const (
manifestDir = "pkg/hub/csr"
subjectPrefix = "system:open-cluster-management:"
)
// csrController reconciles instances of spoke cluster CertificateSigningRequests on the hub.
type csrController struct {
kubeClient kubernetes.Interface
eventRecorder events.Recorder
}
// NewCSRController creates a new csr controller
func NewCSRController(kubeClient kubernetes.Interface, csrInformer factory.Informer, recorder events.Recorder) factory.Controller {
c := &csrController{
kubeClient: kubeClient,
eventRecorder: recorder.WithComponentSuffix("csr-controller"),
}
return factory.New().
WithInformersQueueKeyFunc(func(obj runtime.Object) string {
accessor, _ := meta.Accessor(obj)
return accessor.GetName()
}, csrInformer).
WithSync(c.sync).
ToController("CSRController", recorder)
}
func (c *csrController) sync(ctx context.Context, syncCtx factory.SyncContext) error {
csrName := syncCtx.QueueKey()
klog.V(4).Infof("Reconciling CertificateSigningRequests %q", csrName)
csr, err := c.kubeClient.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
if errors.IsNotFound(err) {
return nil
}
if err != nil {
return err
}
// Current csr is denied, do nothing.
approved, denied := getCertApprovalCondition(&csr.Status)
if denied {
return nil
}
// Recognize whether current csr is a spoker clsuter csr.
recognized, spokeClusterName := recognize(csr)
if !recognized {
klog.V(4).Infof("CSR %q was not recognized", csr.Name)
return nil
}
// Current spoker clsuter csr is approved, apply the corresponding clusterrole/clusterrolebinding.
if approved {
resourceResults := resourceapply.ApplyDirectly(
resourceapply.NewKubeClientHolder(c.kubeClient),
syncCtx.Recorder(),
func(name string) ([]byte, error) {
config := struct {
SpokeClusterName string
}{
SpokeClusterName: spokeClusterName,
}
return assets.MustCreateAssetFromTemplate(name, bindata.MustAsset(filepath.Join(manifestDir, name)), config).Data, nil
},
"manifests/clusterrole.yaml",
"manifests/clusterrolebinding.yaml",
)
errs := []error{}
for _, result := range resourceResults {
if result.Error != nil {
errs = append(errs, fmt.Errorf("%q (%T): %w", result.File, result.Type, result.Error))
}
}
return operatorhelpers.NewMultiLineAggregate(errs)
}
// Using SubjectAccessReview API to authorize whether the current spoke agent has been authorized to renew its csr.
// A spoke agent is authorized after its cluster is accepted by hub cluster admin.
allowed, err := c.authorize(ctx, csr)
if err != nil {
return err
}
if !allowed {
klog.V(4).Infof("Spoke cluster csr %q cannont be auto approved due to subject access review was not approved", csr.Name)
return nil
}
// Auto approve the spoker clsuter csr
csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
Type: certificatesv1beta1.CertificateApproved,
Reason: "AutoApproved",
Message: "Auto approving spoke cluster agent certificate after SubjectAccessReview.",
})
_, err = c.kubeClient.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(ctx, csr, metav1.UpdateOptions{})
if err != nil {
return err
}
c.eventRecorder.Eventf("SpokeClusterCSRAutoApproved", "spoke cluster csr %q is auto approved by hub csr controller", csr.Name)
return nil
}
func (c *csrController) authorize(ctx context.Context, csr *certificatesv1beta1.CertificateSigningRequest) (bool, error) {
extra := make(map[string]authorizationv1.ExtraValue)
for k, v := range csr.Spec.Extra {
extra[k] = authorizationv1.ExtraValue(v)
}
sar := &authorizationv1.SubjectAccessReview{
Spec: authorizationv1.SubjectAccessReviewSpec{
User: csr.Spec.Username,
UID: csr.Spec.UID,
Groups: csr.Spec.Groups,
Extra: extra,
ResourceAttributes: &authorizationv1.ResourceAttributes{
Group: "certificates.k8s.io",
Resource: "certificatesigningrequests",
Verb: "create",
Subresource: "spokeclusteragent",
},
},
}
sar, err := c.kubeClient.AuthorizationV1().SubjectAccessReviews().Create(ctx, sar, metav1.CreateOptions{})
if err != nil {
return false, err
}
return sar.Status.Allowed, nil
}
// To recognize a valid spoke cluster csr, we check
// 1. if organization field and commonName field in csr request is valid.
// 2. if user name in csr is the same as commonName field in csr request.
func recognize(csr *certificatesv1beta1.CertificateSigningRequest) (recognized bool, spokeClusterName string) {
block, _ := pem.Decode(csr.Spec.Request)
if block == nil || block.Type != "CERTIFICATE REQUEST" {
klog.V(4).Infof("csr %q was not recognized: PEM block type is not CERTIFICATE REQUEST", csr.Name)
return false, ""
}
x509cr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
klog.V(4).Infof("csr %q was not recognized: %w", csr.Name, err)
return false, ""
}
if len(x509cr.Subject.Organization) != 1 {
return false, ""
}
organization := x509cr.Subject.Organization[0]
if !strings.HasPrefix(organization, subjectPrefix) {
return false, ""
}
if !strings.HasPrefix(x509cr.Subject.CommonName, organization) {
return false, ""
}
return csr.Spec.Username == x509cr.Subject.CommonName, strings.TrimPrefix(organization, subjectPrefix)
}
func getCertApprovalCondition(status *certificatesv1beta1.CertificateSigningRequestStatus) (approved, denied bool) {
for _, c := range status.Conditions {
if c.Type == certificatesv1beta1.CertificateApproved {
approved = true
}
if c.Type == certificatesv1beta1.CertificateDenied {
denied = true
}
}
return
}
+299
View File
@@ -0,0 +1,299 @@
package csr
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/rand"
"net"
"testing"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/events/eventstesting"
authorizationv1 "k8s.io/api/authorization/v1"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime"
kubefake "k8s.io/client-go/kubernetes/fake"
clienttesting "k8s.io/client-go/testing"
"k8s.io/client-go/util/workqueue"
)
const testCSRName = "test_csr"
func TestSync(t *testing.T) {
cases := []struct {
name string
startingCSRs []runtime.Object
autoApprovingAllowed bool
validateActions func(t *testing.T, actions []clienttesting.Action)
expectedErr string
}{
{
name: "sync a deleted csr",
startingCSRs: []runtime.Object{},
validateActions: func(t *testing.T, actions []clienttesting.Action) {
if len(actions) != 1 {
t.Errorf("expected 1 call but got: %#v", actions)
}
assertAction(t, actions[0], "get")
},
},
{
name: "sync a denied csr",
startingCSRs: []runtime.Object{newDeniedCSR()},
validateActions: func(t *testing.T, actions []clienttesting.Action) {
if len(actions) != 1 {
t.Errorf("expected 1 call but got: %#v", actions)
}
assertAction(t, actions[0], "get")
},
},
{
name: "sync an unrecognized csr",
startingCSRs: []runtime.Object{newUnrecognizedCSR()},
validateActions: func(t *testing.T, actions []clienttesting.Action) {
if len(actions) != 1 {
t.Errorf("expected 1 call but got: %#v", actions)
}
assertAction(t, actions[0], "get")
},
},
{
name: "sync a hub cluster admin approved csr",
startingCSRs: []runtime.Object{newApprovedCSR()},
validateActions: func(t *testing.T, actions []clienttesting.Action) {
if len(actions) != 5 {
t.Errorf("expected 5 call but got: %#v", actions)
}
assertAction(t, actions[4], "create")
assertClusterRoleBindingCreated(t, actions[4].(clienttesting.CreateActionImpl).Object)
},
},
{
name: "deny an auto approving csr",
startingCSRs: []runtime.Object{newRecognizedCSR()},
validateActions: func(t *testing.T, actions []clienttesting.Action) {
if len(actions) != 2 {
t.Errorf("expected 2 call but got: %#v", actions)
}
assertAction(t, actions[1], "create")
assertSubjectAccessReviewCreated(t, actions[1].(clienttesting.CreateActionImpl).Object)
},
},
{
name: "allow an auto approving csr",
startingCSRs: []runtime.Object{newRecognizedCSR()},
autoApprovingAllowed: true,
validateActions: func(t *testing.T, actions []clienttesting.Action) {
if len(actions) != 3 {
t.Errorf("expected 3 call but got: %#v", actions)
}
assertAction(t, actions[2], "update")
assertCondition(t, actions[2].(clienttesting.UpdateActionImpl).Object, certificatesv1beta1.CertificateApproved, "AutoApproved")
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
kubeClient := kubefake.NewSimpleClientset(c.startingCSRs...)
kubeClient.PrependReactor(
"create",
"subjectaccessreviews",
func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &authorizationv1.SubjectAccessReview{
Status: authorizationv1.SubjectAccessReviewStatus{
Allowed: c.autoApprovingAllowed,
},
}, nil
},
)
ctrl := &csrController{kubeClient, eventstesting.NewTestingEventRecorder(t)}
syncErr := ctrl.sync(context.TODO(), newFakeSyncContext(t))
if len(c.expectedErr) > 0 && syncErr == nil {
t.Errorf("expected %q error", c.expectedErr)
return
}
if len(c.expectedErr) > 0 && syncErr != nil && syncErr.Error() != c.expectedErr {
t.Errorf("expected %q error, got %q", c.expectedErr, syncErr.Error())
return
}
if syncErr != nil {
t.Errorf("unexpected err: %v", syncErr)
}
c.validateActions(t, kubeClient.Actions())
})
}
}
func TestRecognize(t *testing.T) {
cases := []struct {
name string
csr *certificatesv1beta1.CertificateSigningRequest
recognized bool
expectedClusterName string
}{
{
name: "a wrong block type",
csr: newCSR("", []string{}, "", "RSA PRIVATE KEY"),
recognized: false,
},
{
name: "an empty organization",
csr: newCSR("", []string{}, "", "CERTIFICATE REQUEST"),
recognized: false,
},
{
name: "an invalid organization",
csr: newCSR("", []string{"test"}, "", "CERTIFICATE REQUEST"),
recognized: false,
},
{
name: "an invalid common name",
csr: newCSR("", []string{"system:open-cluster-management:spokecluster1"}, "", "CERTIFICATE REQUEST"),
recognized: false,
},
{
name: "a recognied csr",
csr: newRecognizedCSR(),
recognized: true,
expectedClusterName: "spokecluster1",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
recognized, clusterName := recognize(c.csr)
if recognized != c.recognized {
t.Errorf("expected %t, but failed", c.recognized)
}
if clusterName != c.expectedClusterName {
t.Errorf("expected %q, but got %q", c.expectedClusterName, clusterName)
}
})
}
}
func newCSR(cn string, orgs []string, username string, reqBlockType string) *certificatesv1beta1.CertificateSigningRequest {
insecureRand := rand.New(rand.NewSource(0))
signerName := "tester"
pk, err := ecdsa.GenerateKey(elliptic.P256(), insecureRand)
if err != nil {
panic(err)
}
csrb, err := x509.CreateCertificateRequest(insecureRand, &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: cn,
Organization: orgs,
},
DNSNames: []string{},
EmailAddresses: []string{},
IPAddresses: []net.IP{},
}, pk)
if err != nil {
panic(err)
}
return &certificatesv1beta1.CertificateSigningRequest{
Spec: certificatesv1beta1.CertificateSigningRequestSpec{
Username: username,
Usages: []certificatesv1beta1.KeyUsage{},
SignerName: &signerName,
Request: pem.EncodeToMemory(&pem.Block{Type: reqBlockType, Bytes: csrb}),
},
}
}
func newRecognizedCSR() *certificatesv1beta1.CertificateSigningRequest {
csr := newCSR(
"system:open-cluster-management:spokecluster1:spokeagent1",
[]string{"system:open-cluster-management:spokecluster1"},
"system:open-cluster-management:spokecluster1:spokeagent1",
"CERTIFICATE REQUEST",
)
csr.Name = testCSRName
return csr
}
func newUnrecognizedCSR() *certificatesv1beta1.CertificateSigningRequest {
csr := newCSR(
"system:open-cluster-management:spokecluster1:spokeagent2",
[]string{"system:open-cluster-management:spokecluster1"},
"system:open-cluster-management:spokecluster1:spokeagent1",
"CERTIFICATE REQUEST",
)
csr.Name = testCSRName
return csr
}
func newDeniedCSR() *certificatesv1beta1.CertificateSigningRequest {
csr := newRecognizedCSR()
csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
Type: certificatesv1beta1.CertificateDenied,
})
return csr
}
func newApprovedCSR() *certificatesv1beta1.CertificateSigningRequest {
csr := newRecognizedCSR()
csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
Type: certificatesv1beta1.CertificateApproved,
})
return csr
}
func assertAction(t *testing.T, actual clienttesting.Action, expected string) {
if actual.GetVerb() != expected {
t.Errorf("expected %s action but got: %#v", expected, actual)
}
}
func assertClusterRoleBindingCreated(t *testing.T, actual runtime.Object) {
_, ok := actual.(*rbacv1.ClusterRoleBinding)
if !ok {
t.Errorf("expected clusterrolebinding created, but got: %#v", actual)
}
}
func assertSubjectAccessReviewCreated(t *testing.T, actual runtime.Object) {
_, ok := actual.(*authorizationv1.SubjectAccessReview)
if !ok {
t.Errorf("expected subjectaccessreview created, but got: %#v", actual)
}
}
func assertCondition(t *testing.T, actual runtime.Object, expectedCondition certificatesv1beta1.RequestConditionType, expectedReason string) {
csr := actual.(*certificatesv1beta1.CertificateSigningRequest)
conditions := csr.Status.Conditions
if len(conditions) != 1 {
t.Errorf("expected 1 condition but got: %#v", conditions)
}
condition := conditions[0]
if condition.Type != expectedCondition {
t.Errorf("expected %s but got: %s", expectedCondition, condition.Type)
}
if condition.Reason != expectedReason {
t.Errorf("expected %s but got: %s", expectedReason, condition.Reason)
}
}
type fakeSyncContext struct {
csrName string
recorder events.Recorder
}
func newFakeSyncContext(t *testing.T) *fakeSyncContext {
return &fakeSyncContext{
csrName: testCSRName,
recorder: eventstesting.NewTestingEventRecorder(t),
}
}
func (f fakeSyncContext) Queue() workqueue.RateLimitingInterface { return nil }
func (f fakeSyncContext) QueueKey() string { return f.csrName }
func (f fakeSyncContext) Recorder() events.Recorder { return f.recorder }
+3
View File
@@ -0,0 +1,3 @@
// package csr contains the hub-side reconciler for the spoke cluster CertificateSigningRequests
// resource.
package csr
+13
View File
@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: system:open-cluster-management:spokecluster:{{ .SpokeClusterName }}-csrapproved
rules:
# Allow spoke agent to create/get/list/watch certificatesigningrequests
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests"]
verbs: ["create", "get", "list", "watch"]
# Allow spoke agent to create/get/list/watch spoke cluster
- apiGroups: ["cluster.open-cluster-management.io"]
resources: ["spokeclusters"]
verbs: ["create", "get", "list", "watch"]
@@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: system:open-cluster-management:spokecluster:{{ .SpokeClusterName }}-csrapproved
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:open-cluster-management:spokecluster:{{ .SpokeClusterName }}-csrapproved
subjects:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:open-cluster-management:{{ .SpokeClusterName }}
+12
View File
@@ -9,7 +9,9 @@ import (
clusterv1client "github.com/open-cluster-management/api/client/cluster/clientset/versioned"
clusterv1informers "github.com/open-cluster-management/api/client/cluster/informers/externalversions"
"github.com/open-cluster-management/registration/pkg/hub/csr"
"github.com/open-cluster-management/registration/pkg/hub/spokecluster"
kubeinformers "k8s.io/client-go/informers"
)
// RunControllerManager starts the controllers on hub to manage spoke cluster registraiton.
@@ -23,7 +25,9 @@ func RunControllerManager(ctx context.Context, controllerContext *controllercmd.
if err != nil {
return err
}
clusterInformers := clusterv1informers.NewSharedInformerFactory(clusterClient, 10*time.Minute)
csrInformers := kubeinformers.NewSharedInformerFactory(kubeClient, 10*time.Minute)
spokeClusterController := spokecluster.NewSpokeClusterController(
kubeClient,
@@ -32,9 +36,17 @@ func RunControllerManager(ctx context.Context, controllerContext *controllercmd.
controllerContext.EventRecorder,
)
csrController := csr.NewCSRController(
kubeClient,
csrInformers.Certificates().V1beta1().CertificateSigningRequests().Informer(),
controllerContext.EventRecorder,
)
go clusterInformers.Start(ctx.Done())
go csrInformers.Start(ctx.Done())
go spokeClusterController.Run(ctx, 1)
go csrController.Run(ctx, 1)
<-ctx.Done()
return nil
+4 -2
View File
@@ -96,7 +96,8 @@ roleRef:
subjects:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:open-cluster-management:spokecluster:{{ .SpokeClusterName }}`)
name: system:open-cluster-management:{{ .SpokeClusterName }}
`)
func pkgHubSpokeclusterManifestsSpokeclusterClusterrolebindingYamlBytes() ([]byte, error) {
return _pkgHubSpokeclusterManifestsSpokeclusterClusterrolebindingYaml, nil
@@ -116,7 +117,8 @@ func pkgHubSpokeclusterManifestsSpokeclusterClusterrolebindingYaml() (*asset, er
var _pkgHubSpokeclusterManifestsSpokeclusterNamespaceYaml = []byte(`apiVersion: v1
kind: Namespace
metadata:
name: {{ .SpokeClusterName }}`)
name: {{ .SpokeClusterName }}
`)
func pkgHubSpokeclusterManifestsSpokeclusterNamespaceYamlBytes() ([]byte, error) {
return _pkgHubSpokeclusterManifestsSpokeclusterNamespaceYaml, nil
@@ -9,4 +9,4 @@ roleRef:
subjects:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:open-cluster-management:spokecluster:{{ .SpokeClusterName }}
name: system:open-cluster-management:{{ .SpokeClusterName }}
@@ -1,4 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: {{ .SpokeClusterName }}
name: {{ .SpokeClusterName }}