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 }