github.com/braveheart12/insolar-09-08-19@v0.8.7/network/controller/bootstrap/authorization.go (about) 1 /* 2 * The Clear BSD License 3 * 4 * Copyright (c) 2019 Insolar Technologies 5 * 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the disclaimer below) provided that the following conditions are met: 9 * 10 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 12 * Neither the name of Insolar Technologies nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 13 * 14 * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 * 16 */ 17 18 package bootstrap 19 20 import ( 21 "context" 22 "encoding/gob" 23 "fmt" 24 "time" 25 26 "github.com/insolar/insolar/certificate" 27 "github.com/insolar/insolar/component" 28 "github.com/insolar/insolar/consensus/packets" 29 "github.com/insolar/insolar/core" 30 "github.com/insolar/insolar/instrumentation/inslogger" 31 "github.com/insolar/insolar/instrumentation/instracer" 32 "github.com/insolar/insolar/log" 33 "github.com/insolar/insolar/network" 34 "github.com/insolar/insolar/network/controller/common" 35 "github.com/insolar/insolar/network/transport/packet/types" 36 "github.com/insolar/insolar/platformpolicy" 37 "github.com/jbenet/go-base58" 38 "github.com/pkg/errors" 39 "go.opencensus.io/stats" 40 "go.opencensus.io/tag" 41 "go.opencensus.io/trace" 42 ) 43 44 const ( 45 registrationRetries = 10 46 ) 47 48 type AuthorizationController interface { 49 component.Initer 50 51 Authorize(ctx context.Context, discoveryNode *DiscoveryNode, cert core.AuthorizationCertificate) (SessionID, error) 52 Register(ctx context.Context, discoveryNode *DiscoveryNode, sessionID SessionID) error 53 } 54 55 type authorizationController struct { 56 NodeKeeper network.NodeKeeper `inject:""` 57 NetworkCoordinator core.NetworkCoordinator `inject:""` 58 SessionManager SessionManager `inject:""` 59 60 options *common.Options 61 transport network.InternalTransport 62 } 63 64 type OperationCode uint8 65 66 const ( 67 OpConfirmed OperationCode = iota + 1 68 OpRejected 69 OpRetry 70 ) 71 72 // AuthorizationRequest 73 type AuthorizationRequest struct { 74 Certificate []byte 75 } 76 77 // AuthorizationResponse 78 type AuthorizationResponse struct { 79 Code OperationCode 80 Error string 81 SessionID SessionID 82 } 83 84 // RegistrationRequest 85 type RegistrationRequest struct { 86 SessionID SessionID 87 Version string 88 JoinClaim *packets.NodeJoinClaim 89 } 90 91 // RegistrationResponse 92 type RegistrationResponse struct { 93 Code OperationCode 94 RetryIn time.Duration 95 Error string 96 } 97 98 func init() { 99 gob.Register(&AuthorizationRequest{}) 100 gob.Register(&AuthorizationResponse{}) 101 gob.Register(&RegistrationRequest{}) 102 gob.Register(&RegistrationResponse{}) 103 } 104 105 // Authorize node on the discovery node (step 2 of the bootstrap process) 106 func (ac *authorizationController) Authorize(ctx context.Context, discoveryNode *DiscoveryNode, cert core.AuthorizationCertificate) (SessionID, error) { 107 inslogger.FromContext(ctx).Infof("Authorizing on host: %s", discoveryNode.Host) 108 inslogger.FromContext(ctx).Infof("cert: %s", cert) 109 110 ctx, span := instracer.StartSpan(ctx, "AuthorizationController.Authorize") 111 span.AddAttributes( 112 trace.StringAttribute("node", discoveryNode.Node.GetNodeRef().String()), 113 ) 114 defer span.End() 115 serializedCert, err := certificate.Serialize(cert) 116 if err != nil { 117 return 0, errors.Wrap(err, "Error serializing certificate") 118 } 119 120 request := ac.transport.NewRequestBuilder().Type(types.Authorize).Data(&AuthorizationRequest{ 121 Certificate: serializedCert, 122 }).Build() 123 future, err := ac.transport.SendRequestPacket(ctx, request, discoveryNode.Host) 124 if err != nil { 125 return 0, errors.Wrapf(err, "Error sending authorize request") 126 } 127 response, err := future.GetResponse(ac.options.PacketTimeout) 128 if err != nil { 129 return 0, errors.Wrapf(err, "Error getting response for authorize request") 130 } 131 data := response.GetData().(*AuthorizationResponse) 132 if data.Code == OpRejected { 133 return 0, errors.New("Authorize rejected: " + data.Error) 134 } 135 return data.SessionID, nil 136 } 137 138 // Register node on the discovery node (step 4 of the bootstrap process) 139 func (ac *authorizationController) Register(ctx context.Context, discoveryNode *DiscoveryNode, sessionID SessionID) error { 140 return ac.register(ctx, discoveryNode, sessionID, 0) 141 } 142 143 func (ac *authorizationController) register(ctx context.Context, discoveryNode *DiscoveryNode, 144 sessionID SessionID, attempt int) error { 145 146 if attempt == 0 { 147 inslogger.FromContext(ctx).Infof("Registering on host: %s", discoveryNode.Host) 148 } else { 149 inslogger.FromContext(ctx).Infof("Registering on host: %s; attempt: %d", discoveryNode.Host, attempt+1) 150 } 151 152 ctx, span := instracer.StartSpan(ctx, "AuthorizationController.Register") 153 span.AddAttributes( 154 trace.StringAttribute("node", discoveryNode.Node.GetNodeRef().String()), 155 ) 156 defer span.End() 157 originClaim, err := ac.NodeKeeper.GetOriginJoinClaim() 158 if err != nil { 159 return errors.Wrap(err, "Failed to get origin claim") 160 } 161 request := ac.transport.NewRequestBuilder().Type(types.Register).Data(&RegistrationRequest{ 162 Version: ac.NodeKeeper.GetOrigin().Version(), 163 SessionID: sessionID, 164 JoinClaim: originClaim, 165 }).Build() 166 future, err := ac.transport.SendRequestPacket(ctx, request, discoveryNode.Host) 167 if err != nil { 168 return errors.Wrapf(err, "Error sending register request") 169 } 170 response, err := future.GetResponse(ac.options.PacketTimeout) 171 if err != nil { 172 return errors.Wrapf(err, "Error getting response for register request") 173 } 174 data := response.GetData().(*RegistrationResponse) 175 if data.Code == OpRejected { 176 return errors.New("Register rejected: " + data.Error) 177 } 178 if data.Code == OpRetry { 179 if attempt >= registrationRetries { 180 return errors.Errorf("Exceeded maximum number of registration retries (%d)", registrationRetries) 181 } 182 log.Warnf("Failed to register on discovery node %s. Reason: node %s is already in network active list. "+ 183 "Retrying registration in %v", discoveryNode.Host, ac.NodeKeeper.GetOrigin().ID(), data.RetryIn) 184 time.Sleep(data.RetryIn) 185 return ac.register(ctx, discoveryNode, sessionID, attempt+1) 186 } 187 return nil 188 } 189 190 func (ac *authorizationController) buildRegistrationResponse(sessionID SessionID, claim *packets.NodeJoinClaim) *RegistrationResponse { 191 session, err := ac.getSession(sessionID, claim) 192 if err != nil { 193 return &RegistrationResponse{Code: OpRejected, Error: err.Error()} 194 } 195 if node := ac.NodeKeeper.GetActiveNode(claim.NodeRef); node != nil { 196 retryIn := session.TTL / 2 197 198 keyProc := platformpolicy.NewKeyProcessor() 199 // little hack: ignoring error, because it never fails in current implementation 200 nodeKey, _ := keyProc.ExportPublicKeyBinary(node.PublicKey()) 201 202 log.Warnf("Joiner node (ID: %s, PK: %s) conflicts with node (ID: %s, PK: %s) in active list, sending request to reconnect in %v", 203 claim.NodeRef, base58.Encode(claim.NodePK[:]), node.ID(), base58.Encode(nodeKey), retryIn) 204 205 statsErr := stats.RecordWithTags(context.Background(), []tag.Mutator{ 206 tag.Upsert(tagNodeRef, claim.NodeRef.String()), 207 }, statBootstrapReconnectRequired.M(1)) 208 if statsErr != nil { 209 log.Warn("Failed to record reconnection retries metric: " + statsErr.Error()) 210 } 211 212 ac.SessionManager.ProlongateSession(sessionID, session) 213 return &RegistrationResponse{Code: OpRetry, RetryIn: retryIn} 214 } 215 return &RegistrationResponse{Code: OpConfirmed} 216 } 217 218 func (ac *authorizationController) getSession(sessionID SessionID, claim *packets.NodeJoinClaim) (*Session, error) { 219 session, err := ac.SessionManager.ReleaseSession(sessionID) 220 if err != nil { 221 return nil, errors.Wrapf(err, "Error getting session %d for authorization", sessionID) 222 } 223 if !claim.NodeRef.Equal(session.NodeID) { 224 return nil, errors.New("Claim node ID is not equal to session node ID") 225 } 226 // TODO: check claim signature 227 return session, nil 228 } 229 230 func (ac *authorizationController) processRegisterRequest(ctx context.Context, request network.Request) (network.Response, error) { 231 data := request.GetData().(*RegistrationRequest) 232 if data.Version != ac.NodeKeeper.GetOrigin().Version() { 233 response := &RegistrationResponse{Code: OpRejected, 234 Error: fmt.Sprintf("Joiner version %s does not match discovery version %s", 235 data.Version, ac.NodeKeeper.GetOrigin().Version())} 236 return ac.transport.BuildResponse(ctx, request, response), nil 237 } 238 response := ac.buildRegistrationResponse(data.SessionID, data.JoinClaim) 239 if response.Code != OpConfirmed { 240 return ac.transport.BuildResponse(ctx, request, response), nil 241 } 242 243 // TODO: fix Short ID assignment logic 244 if CheckShortIDCollision(ac.NodeKeeper, data.JoinClaim.ShortNodeID) { 245 response = &RegistrationResponse{Code: OpRejected, 246 Error: "Short ID of the joiner node conflicts with active node short ID"} 247 return ac.transport.BuildResponse(ctx, request, response), nil 248 } 249 250 inslogger.FromContext(ctx).Infof("Added join claim from node %s", request.GetSender()) 251 ac.NodeKeeper.AddPendingClaim(data.JoinClaim) 252 return ac.transport.BuildResponse(ctx, request, response), nil 253 } 254 255 func (ac *authorizationController) processAuthorizeRequest(ctx context.Context, request network.Request) (network.Response, error) { 256 data := request.GetData().(*AuthorizationRequest) 257 cert, err := certificate.Deserialize(data.Certificate, platformpolicy.NewKeyProcessor()) 258 if err != nil { 259 return ac.transport.BuildResponse(ctx, request, &AuthorizationResponse{Code: OpRejected, Error: err.Error()}), nil 260 } 261 valid, err := ac.NetworkCoordinator.ValidateCert(ctx, cert) 262 if !valid { 263 if err == nil { 264 err = errors.New("Certificate validation failed") 265 } 266 return ac.transport.BuildResponse(ctx, request, &AuthorizationResponse{Code: OpRejected, Error: err.Error()}), nil 267 } 268 session := ac.SessionManager.NewSession(request.GetSender(), cert, ac.options.HandshakeSessionTTL) 269 return ac.transport.BuildResponse(ctx, request, &AuthorizationResponse{Code: OpConfirmed, SessionID: session}), nil 270 } 271 272 func (ac *authorizationController) Init(ctx context.Context) error { 273 ac.transport.RegisterPacketHandler(types.Register, ac.processRegisterRequest) 274 ac.transport.RegisterPacketHandler(types.Authorize, ac.processAuthorizeRequest) 275 return nil 276 } 277 278 func NewAuthorizationController(options *common.Options, transport network.InternalTransport) AuthorizationController { 279 return &authorizationController{ 280 options: options, 281 transport: transport, 282 } 283 }