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  }