blob: 814fcf0738712b48895e5da3dac87e09eaf38acd [file] [log] [blame]
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package api
18
19import (
20 "context"
21 "crypto/rand"
22 "encoding/hex"
23 "fmt"
24 schema "git.monogon.dev/source/nexantic.git/core/generated/api"
Leopold Schabel68c58752019-11-14 21:00:59 +010025 "git.monogon.dev/source/nexantic.git/core/internal/common/grpc"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020026
27 "errors"
28
29 "go.uber.org/zap"
30)
31
32var (
33 ErrAttestationFailed = errors.New("attestation_failed")
34)
35
36func (s *Server) AddNode(ctx context.Context, req *schema.AddNodeRequest) (*schema.AddNodeResponse, error) {
37 // Setup API client
Leopold Schabel68c58752019-11-14 21:00:59 +010038 c, err := grpc.NewSmalltownAPIClient(fmt.Sprintf("%s:%d", req.Addr, s.config.Port))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020039 if err != nil {
40 return nil, err
41 }
42
43 // Check attestation
44 nonce := make([]byte, 20)
45 _, err = rand.Read(nonce)
46 if err != nil {
47 return nil, err
48 }
49 hexNonce := hex.EncodeToString(nonce)
50
51 aRes, err := c.Setup.Attest(ctx, &schema.AttestRequest{
52 Challenge: hexNonce,
53 })
54 if err != nil {
55 return nil, err
56 }
57
58 //TODO(hendrik): Verify response
59 if aRes.Response != hexNonce {
60 return nil, ErrAttestationFailed
61 }
62
Leopold Schabel68c58752019-11-14 21:00:59 +010063 consensusCerts, err := s.consensusService.IssueCertificate(req.Addr)
Lorenz Bruna4ea9d02019-10-31 11:40:30 +010064 if err != nil {
65 return nil, err
66 }
67
Leopold Schabel68c58752019-11-14 21:00:59 +010068 // TODO(leo): fetch remote hostname rather than using the addr
69 name := req.Addr
70
71 // Add new node to local etcd cluster.
72 memberID, err := s.consensusService.AddMember(ctx, name, fmt.Sprintf("https://%s:%d", req.Addr, s.config.Port))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020073 if err != nil {
74 return nil, err
75 }
76
Leopold Schabel68c58752019-11-14 21:00:59 +010077 s.Logger.Info("Added new node to consensus cluster; sending cluster join request to node",
78 zap.String("addr", req.Addr), zap.Uint16("port", s.config.Port))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020079
Leopold Schabel68c58752019-11-14 21:00:59 +010080 // Send JoinCluster request to new node to make it join.
81 _, err = c.Setup.JoinCluster(ctx, &schema.JoinClusterRequest{
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020082 InitialCluster: s.consensusService.GetInitialClusterString(),
Leopold Schabel68c58752019-11-14 21:00:59 +010083 ProvisioningToken: req.ProvisioningToken,
Lorenz Bruna4ea9d02019-10-31 11:40:30 +010084 Certs: consensusCerts,
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020085 })
86 if err != nil {
Leopold Schabel68c58752019-11-14 21:00:59 +010087 errRevoke := s.consensusService.RevokeCertificate(req.Addr)
88 if errRevoke != nil {
89 s.Logger.Error("Failed to revoke a certificate after rollback - potential security risk", zap.Error(errRevoke))
Lorenz Bruna4ea9d02019-10-31 11:40:30 +010090 }
Leopold Schabel68c58752019-11-14 21:00:59 +010091 // Revert etcd add member - might fail if consensus cannot be established.
92 errRemove := s.consensusService.RemoveMember(ctx, memberID)
93 if errRemove != nil || errRevoke != nil {
94 return nil, fmt.Errorf("rollback failed after failed provisioning; err=%v; err_rb=%v; err_revoke=%v", err, errRemove, errRevoke)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020095 }
96 return nil, err
97 }
98 s.Logger.Info("Fully provisioned new node",
Leopold Schabel68c58752019-11-14 21:00:59 +010099 zap.String("host", req.Addr),
100 zap.Uint16("apiPort", s.config.Port),
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200101 zap.Uint64("member_id", memberID))
102
103 return &schema.AddNodeResponse{}, nil
104}
105
Serge Bazanski71049af2020-02-03 16:05:52 +0100106func (s *Server) RemoveNode(context.Context, *schema.RemoveNodeRequest) (*schema.RemoveNodeResponse, error) {
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200107 panic("implement me")
108}
109
Leopold Schabel68c58752019-11-14 21:00:59 +0100110func (s *Server) ListNodes(context.Context, *schema.ListNodesRequest) (*schema.ListNodesResponse, error) {
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200111 nodes := s.consensusService.GetNodes()
112 resNodes := make([]*schema.Node, len(nodes))
113
114 for i, node := range nodes {
115 resNodes[i] = &schema.Node{
116 Id: node.ID,
117 Name: node.Name,
118 Address: node.Address,
119 Synced: node.Synced,
120 }
121 }
122
Leopold Schabel68c58752019-11-14 21:00:59 +0100123 return &schema.ListNodesResponse{
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200124 Nodes: resNodes,
125 }, nil
126}