Skip to content

Commit

Permalink
Merge pull request #165 from 0xff-dev/cc-webhook
Browse files Browse the repository at this point in the history
feat: webhook verify the chaincode and ednrosepolicy fields match the…
  • Loading branch information
bjwswang authored Mar 6, 2023
2 parents 221b967 + 900de50 commit 66dc2aa
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 4 deletions.
9 changes: 5 additions & 4 deletions api/v1beta1/chaincode.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package v1beta1

const (
// ChaincodeProposalLabel the main purpose of this label is to make it easier to clean up the proposal when the chaincode is deleted.
ChaincodeProposalLabel = "bestchains.chaincode.delete.proposal"
ChaincodeChannelLabel = "bestchains.chaincode.channel"
ChaincodeIDLabel = "bestchains.chaincode.id"
ChaincodeVersionLabel = "bestchains.chaincode.version"
ChaincodeProposalLabel = "bestchains.chaincode.delete.proposal"
ChaincodeChannelLabel = "bestchains.chaincode.channel"
ChaincodeIDLabel = "bestchains.chaincode.id"
ChaincodeVersionLabel = "bestchains.chaincode.version"
ChaincodeUsedEndorsementPolicy = "bestchians.chaincode.endorsementpolicy"
)

var condOrder = []ChaincodeConditionType{ChaincodeCondDone, ChaincodeCondPackaged, ChaincodeCondInstalled,
Expand Down
82 changes: 82 additions & 0 deletions api/v1beta1/chaincode_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright contributors to the Hyperledger Fabric Operator project
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package v1beta1

import (
"context"
"fmt"

authenticationv1 "k8s.io/api/authentication/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
)

// log is for logging in this package.
var ccLogger = logf.Log.WithName("chaincode-resource")

//+kubebuilder:webhook:path=/mutate-ibp-com-v1beta1-chaincode,mutating=true,failurePolicy=fail,sideEffects=None,groups=ibp.com,resources=chaincodes,verbs=create;update,versions=v1beta1,name=chaincode.mutate.webhook,admissionReviewVersions=v1

var _ defaulter = &Chaincode{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *Chaincode) Default(ctx context.Context, client client.Client, user authenticationv1.UserInfo) {
ccLogger.Info("default", "name", r.Name, "user", user.String())
}

//+kubebuilder:webhook:path=/validate-ibp-com-v1beta1-chaincode,mutating=false,failurePolicy=fail,sideEffects=None,groups=ibp.com,resources=chaincodes,verbs=create;update;delete,versions=v1beta1,name=chaincode.validate.webhook,admissionReviewVersions=v1

var _ validator = &Chaincode{}

// checkChAndEp Check if both ch and ep are present
func checkChAndEp(c client.Client, chName, epName string) error {
ch := &Channel{}
if err := c.Get(context.TODO(), types.NamespacedName{Name: chName}, ch); err != nil {
return err
}

ep := &EndorsePolicy{}
return c.Get(context.TODO(), types.NamespacedName{Name: epName}, ep)
}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *Chaincode) ValidateCreate(ctx context.Context, client client.Client, user authenticationv1.UserInfo) error {
ccLogger.Info("validate create", "name", r.Name, "user", user.String())
return checkChAndEp(client, r.Spec.Channel, r.Spec.EndorsePolicyRef.Name)
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *Chaincode) ValidateUpdate(ctx context.Context, client client.Client, old runtime.Object, user authenticationv1.UserInfo) error {
ccLogger.Info("validate update", "name", r.Name, "user", user.String())
oldcc := old.(*Chaincode)
return checkChAndEp(client, oldcc.Spec.Channel, oldcc.Spec.EndorsePolicyRef.Name)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *Chaincode) ValidateDelete(ctx context.Context, c client.Client, user authenticationv1.UserInfo) error {
ccLogger.Info("validate delete", "name", r.Name, "user", user.String())
if err := checkChAndEp(c, r.Spec.Channel, r.Spec.EndorsePolicyRef.Name); err != nil {
return err
}
if r.Status.Phase != ChaincodePhaseUnapproved {
return fmt.Errorf("it can only be deleted if the vote is not approved")
}
return nil
}
93 changes: 93 additions & 0 deletions api/v1beta1/endorsepolicy_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright contributors to the Hyperledger Fabric Operator project
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package v1beta1

import (
"context"
"fmt"

"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/policydsl"
authenticationv1 "k8s.io/api/authentication/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
)

// log is for logging in this package.
var epLogger = logf.Log.WithName("endorsement-policy-resource")

//+kubebuilder:webhook:path=/mutate-ibp-com-v1beta1-endorsepolicy,mutating=true,failurePolicy=fail,sideEffects=None,groups=ibp.com,resources=endorsepolicies,verbs=create;update,versions=v1beta1,name=endorsepolicy.mutate.webhook,admissionReviewVersions=v1

var _ defaulter = &EndorsePolicy{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *EndorsePolicy) Default(ctx context.Context, client client.Client, user authenticationv1.UserInfo) {
epLogger.Info("default", "name", r.Name, "user", user.String())
}

//+kubebuilder:webhook:path=/validate-ibp-com-v1beta1-endorsepolicy,mutating=false,failurePolicy=fail,sideEffects=None,groups=ibp.com,resources=endorsepolicies,verbs=create;update;delete,versions=v1beta1,name=endorsepolicy.validate.webhook,admissionReviewVersions=v1

var _ validator = &EndorsePolicy{}

// checkCh Check if both ch and ep are present
func checkCh(c client.Client, chName string) error {
ch := &Channel{}
return c.Get(context.TODO(), types.NamespacedName{Name: chName}, ch)
}

func isPolicyValidate(p string) bool {
_, err := policydsl.FromString(p)
if err != nil {
epLogger.Error(err, "")
}
return err == nil
}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *EndorsePolicy) ValidateCreate(ctx context.Context, client client.Client, user authenticationv1.UserInfo) error {
epLogger.Info("validate create", "name", r.Name, "user", user.String())
if !isPolicyValidate(r.Spec.Value) {
return fmt.Errorf("invalid policy %s", r.Spec.Value)
}
return checkCh(client, r.Spec.Channel)
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *EndorsePolicy) ValidateUpdate(ctx context.Context, client client.Client, old runtime.Object, user authenticationv1.UserInfo) error {
epLogger.Info("validate update", "name", r.Name, "user", user.String())
if !isPolicyValidate(r.Spec.Value) {
return fmt.Errorf("invalid policy %s", r.Spec.Value)
}
return checkCh(client, r.Spec.Channel)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *EndorsePolicy) ValidateDelete(ctx context.Context, c client.Client, user authenticationv1.UserInfo) error {
epLogger.Info("validate delete", "name", r.Name, "user", user.String())

ccList := &ChaincodeList{}
if err := c.List(context.TODO(), ccList, client.MatchingLabels{ChaincodeUsedEndorsementPolicy: r.GetName()}); err != nil {
return err
}
if len(ccList.Items) > 0 {
return fmt.Errorf("there are other chaincode that are using this policy and cannot be deleted")
}
return nil
}
6 changes: 6 additions & 0 deletions api/v1beta1/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ func AddWebhooks(mgr ctrl.Manager, setupLog logr.Logger) (err error) {
setupLog.Error(err, "unable to create webhook", "webhook", "Channel")
return err
}
if err = registerCustomWebhook(mgr, &Chaincode{}, operatorUser); err != nil {
setupLog.Error(err, "unable create webhook", "webhook", "Chaincode")
}
if err = registerCustomWebhook(mgr, &EndorsePolicy{}, operatorUser); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "EndorsePolicy")
}
return nil
}

Expand Down
82 changes: 82 additions & 0 deletions config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ metadata:
creationTimestamp: null
name: mutating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-service
namespace: system
path: /mutate-ibp-com-v1beta1-chaincode
failurePolicy: Fail
name: chaincode.mutate.webhook
rules:
- apiGroups:
- ibp.com
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
resources:
- chaincodes
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
Expand All @@ -25,6 +45,26 @@ webhooks:
resources:
- channels
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-service
namespace: system
path: /mutate-ibp-com-v1beta1-endorsepolicy
failurePolicy: Fail
name: endorsepolicy.mutate.webhook
rules:
- apiGroups:
- ibp.com
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
resources:
- endorsepolicies
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
Expand Down Expand Up @@ -132,6 +172,27 @@ metadata:
creationTimestamp: null
name: validating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-service
namespace: system
path: /validate-ibp-com-v1beta1-chaincode
failurePolicy: Fail
name: chaincode.validate.webhook
rules:
- apiGroups:
- ibp.com
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
- DELETE
resources:
- chaincodes
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
Expand All @@ -153,6 +214,27 @@ webhooks:
resources:
- channels
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-service
namespace: system
path: /validate-ibp-com-v1beta1-endorsepolicy
failurePolicy: Fail
name: endorsepolicy.validate.webhook
rules:
- apiGroups:
- ibp.com
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
- DELETE
resources:
- endorsepolicies
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
Expand Down
4 changes: 4 additions & 0 deletions controllers/chaincode/chaincode_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ func (r *ReconcileChaincode) Reconcile(ctx context.Context, request reconcile.Re
update = true
instance.Labels[current.ChaincodeVersionLabel] = instance.Spec.Version
}
if instance.Labels[current.ChaincodeUsedEndorsementPolicy] != instance.Spec.EndorsePolicyRef.Name {
update = true
instance.Labels[current.ChaincodeUsedEndorsementPolicy] = instance.Spec.EndorsePolicyRef.Name
}
if update {
reqLogger.Info(fmt.Sprintf("update chaincode %s's labels", instance.GetName()))
return reconcile.Result{Requeue: true}, r.client.Update(context.TODO(), instance)
Expand Down

0 comments on commit 66dc2aa

Please sign in to comment.