github.com/kotalco/kotal@v0.3.0/apis/ethereum/v1alpha1/node_validation_webhook.go (about)

     1  package v1alpha1
     2  
     3  import (
     4  	"fmt"
     5  
     6  	apierrors "k8s.io/apimachinery/pkg/api/errors"
     7  	"k8s.io/apimachinery/pkg/runtime"
     8  	"k8s.io/apimachinery/pkg/runtime/schema"
     9  	"k8s.io/apimachinery/pkg/util/validation/field"
    10  	"sigs.k8s.io/controller-runtime/pkg/webhook"
    11  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    12  )
    13  
    14  // +kubebuilder:webhook:verbs=create;update,path=/validate-ethereum-kotal-io-v1alpha1-node,mutating=false,failurePolicy=fail,groups=ethereum.kotal.io,resources=nodes,versions=v1alpha1,name=validate-ethereum-v1alpha1-node.kb.io,sideEffects=None,admissionReviewVersions=v1
    15  
    16  var _ webhook.Validator = &Node{}
    17  
    18  // validate validates a node with a given path
    19  func (n *Node) validate() field.ErrorList {
    20  	var nodeErrors field.ErrorList
    21  
    22  	privateNetwork := n.Spec.Genesis != nil
    23  
    24  	path := field.NewPath("spec")
    25  
    26  	// network: can't specifiy genesis while joining existing network
    27  	if n.Spec.Network != "" && n.Spec.Genesis != nil {
    28  		err := field.Invalid(field.NewPath("spec").Child("network"), n.Spec.Network, "must be none if spec.genesis is specified")
    29  		nodeErrors = append(nodeErrors, err)
    30  	}
    31  
    32  	// genesis: must specify genesis if there's no network to join
    33  	if n.Spec.Network == "" && n.Spec.Genesis == nil {
    34  		err := field.Invalid(field.NewPath("spec").Child("genesis"), "", "must be specified if spec.network is none")
    35  		nodeErrors = append(nodeErrors, err)
    36  	}
    37  
    38  	if !n.Spec.Client.SupportsVerbosityLevel(n.Spec.Logging) {
    39  		err := field.Invalid(path.Child("logging"), n.Spec.Logging, fmt.Sprintf("not supported by client %s", n.Spec.Client))
    40  		nodeErrors = append(nodeErrors, err)
    41  	}
    42  
    43  	// validate coinbase is provided if node is miner
    44  	if n.Spec.Miner && n.Spec.Coinbase == "" {
    45  		err := field.Invalid(path.Child("coinbase"), "", "must provide coinbase if miner is true")
    46  		nodeErrors = append(nodeErrors, err)
    47  	}
    48  
    49  	// validate jwtSecretName is provided if engine is enabled
    50  	if n.Spec.Engine && n.Spec.JWTSecretName == "" {
    51  		err := field.Invalid(path.Child("jwtSecretName"), "", "must provide jwtSecretName if engine is true")
    52  		nodeErrors = append(nodeErrors, err)
    53  	}
    54  
    55  	// validate coinbase can't be set if miner is not set explicitly as true
    56  	if n.Spec.Coinbase != "" && !n.Spec.Miner {
    57  		err := field.Invalid(path.Child("miner"), false, "must set miner to true if coinbase is provided")
    58  		nodeErrors = append(nodeErrors, err)
    59  	}
    60  
    61  	// validate that besu doesn't support importing ethereum accounts
    62  	// Netermind, go-ethereum, and OpenEthereum support importing accounts
    63  	if n.Spec.Client == BesuClient && n.Spec.Import != nil {
    64  		err := field.Invalid(path.Child("client"), n.Spec.Client, "client doesn't support importing accounts")
    65  		nodeErrors = append(nodeErrors, err)
    66  	}
    67  
    68  	// validate rpc must be enabled if grapql is enabled and geth is used
    69  	if n.Spec.Client == GethClient && n.Spec.GraphQL && !n.Spec.RPC {
    70  		err := field.Invalid(path.Child("rpc"), n.Spec.RPC, "must enable rpc if client is geth and graphql is enabled")
    71  		nodeErrors = append(nodeErrors, err)
    72  	}
    73  
    74  	// validate nethermind doesn't support GraphQL
    75  	if n.Spec.GraphQL && n.Spec.Client == NethermindClient {
    76  		err := field.Invalid(path.Child("client"), n.Spec.Client, "client doesn't support GraphQL")
    77  		nodeErrors = append(nodeErrors, err)
    78  	}
    79  
    80  	// validate nethermind doesn't support hosts whitelisting
    81  	if len(n.Spec.Hosts) > 0 && n.Spec.Client == NethermindClient {
    82  		err := field.Invalid(path.Child("client"), n.Spec.Client, "client doesn't support hosts whitelisting")
    83  		nodeErrors = append(nodeErrors, err)
    84  	}
    85  
    86  	// validate nethermind doesn't support CORS domains
    87  	if len(n.Spec.CORSDomains) > 0 && n.Spec.Client == NethermindClient {
    88  		err := field.Invalid(path.Child("client"), n.Spec.Client, "client doesn't support CORS domains")
    89  		nodeErrors = append(nodeErrors, err)
    90  	}
    91  
    92  	// validate only geth client supports light sync mode
    93  	if (n.Spec.SyncMode == LightSynchronization || n.Spec.SyncMode == SnapSynchronization) && n.Spec.Client != GethClient {
    94  		err := field.Invalid(path.Child("syncMode"), n.Spec.SyncMode, fmt.Sprintf("not supported by client %s", n.Spec.Client))
    95  		nodeErrors = append(nodeErrors, err)
    96  	}
    97  
    98  	// validate geth supports only pow and poa
    99  	if privateNetwork && n.Spec.Genesis.IBFT2 != nil && n.Spec.Client != BesuClient {
   100  		err := field.Invalid(path.Child("client"), n.Spec.Client, "client doesn't support ibft2 consensus")
   101  		nodeErrors = append(nodeErrors, err)
   102  	}
   103  
   104  	// validate besu only support fixed difficulty ethash networks
   105  	if privateNetwork && n.Spec.Genesis.Ethash != nil && n.Spec.Genesis.Ethash.FixedDifficulty != nil && n.Spec.Client != BesuClient {
   106  		err := field.Invalid(path.Child("client"), n.Spec.Client, "client doesn't support fixed difficulty pow networks")
   107  		nodeErrors = append(nodeErrors, err)
   108  	}
   109  
   110  	// validate account must be imported if coinbase is provided
   111  	if n.Spec.Client != BesuClient && n.Spec.Coinbase != "" && n.Spec.Import == nil {
   112  		err := field.Invalid(path.Child("import"), "", "must import coinbase account")
   113  		nodeErrors = append(nodeErrors, err)
   114  	}
   115  
   116  	// validate rpc can't be enabled for node with imported account
   117  	if n.Spec.Client != BesuClient && n.Spec.Import != nil && n.Spec.RPC {
   118  		err := field.Invalid(path.Child("rpc"), n.Spec.RPC, "must be false if import is provided")
   119  		nodeErrors = append(nodeErrors, err)
   120  	}
   121  
   122  	// validate ws can't be enabled for node with imported account
   123  	if n.Spec.Client != BesuClient && n.Spec.Import != nil && n.Spec.WS {
   124  		err := field.Invalid(path.Child("ws"), n.Spec.WS, "must be false if import is provided")
   125  		nodeErrors = append(nodeErrors, err)
   126  	}
   127  
   128  	// validate graphql can't be enabled for node with imported account
   129  	if n.Spec.Client != BesuClient && n.Spec.Import != nil && n.Spec.GraphQL {
   130  		err := field.Invalid(path.Child("graphql"), n.Spec.GraphQL, "must be false if import is provided")
   131  		nodeErrors = append(nodeErrors, err)
   132  	}
   133  
   134  	return nodeErrors
   135  }
   136  
   137  // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
   138  func (n *Node) ValidateCreate() (admission.Warnings, error) {
   139  	var allErrors field.ErrorList
   140  
   141  	nodelog.Info("validate create", "name", n.Name)
   142  
   143  	allErrors = append(allErrors, n.validate()...)
   144  	allErrors = append(allErrors, n.Spec.Resources.ValidateCreate()...)
   145  
   146  	// validate genesis block
   147  	if n.Spec.Genesis != nil {
   148  		allErrors = append(allErrors, n.Spec.Genesis.ValidateCreate()...)
   149  	}
   150  
   151  	if len(allErrors) == 0 {
   152  		return nil, nil
   153  	}
   154  
   155  	return nil, apierrors.NewInvalid(schema.GroupKind{}, n.Name, allErrors)
   156  }
   157  
   158  // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
   159  func (n *Node) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
   160  	var allErrors field.ErrorList
   161  	oldNode := old.(*Node)
   162  
   163  	nodelog.Info("validate update", "name", n.Name)
   164  
   165  	if n.Spec.Client != oldNode.Spec.Client {
   166  		err := field.Invalid(field.NewPath("spec").Child("client"), n.Spec.Client, "field is immutable")
   167  		allErrors = append(allErrors, err)
   168  	}
   169  
   170  	if oldNode.Spec.Network != n.Spec.Network {
   171  		err := field.Invalid(field.NewPath("spec").Child("network"), n.Spec.Network, "field is immutable")
   172  		allErrors = append(allErrors, err)
   173  	}
   174  
   175  	// validate genesis block
   176  	if oldNode.Spec.Genesis != nil {
   177  		allErrors = append(allErrors, n.Spec.Genesis.ValidateUpdate(oldNode.Spec.Genesis)...)
   178  	}
   179  
   180  	allErrors = append(allErrors, n.validate()...)
   181  	allErrors = append(allErrors, n.Spec.Resources.ValidateUpdate(&oldNode.Spec.Resources)...)
   182  
   183  	if len(allErrors) == 0 {
   184  		return nil, nil
   185  	}
   186  
   187  	return nil, apierrors.NewInvalid(schema.GroupKind{}, n.Name, allErrors)
   188  }
   189  
   190  // ValidateDelete implements webhook.Validator so a webhook will be registered for the type
   191  func (n *Node) ValidateDelete() (admission.Warnings, error) {
   192  	nodelog.Info("validate delete", "name", n.Name)
   193  
   194  	return nil, nil
   195  }