blob: 64a757f21e039c87ebec5eaa3f424c108a63159a [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"
25 "git.monogon.dev/source/nexantic.git/core/internal/common"
26
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
38 c, err := common.NewSmalltownAPIClient(fmt.Sprintf("%s:%d", req.Host, req.ApiPort))
39 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
Lorenz Bruna4ea9d02019-10-31 11:40:30 +010063 consensusCerts, err := s.consensusService.IssueCertificate(req.Host)
64 if err != nil {
65 return nil, err
66 }
67
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020068 // Provision cluster info locally
Lorenz Bruna4ea9d02019-10-31 11:40:30 +010069 memberID, err := s.consensusService.AddMember(ctx, req.Name, fmt.Sprintf("https://%s:%d", req.Host, req.ConsensusPort))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020070 if err != nil {
71 return nil, err
72 }
73
74 s.Logger.Info("Added new node to consensus cluster; provisioning external node now",
75 zap.String("host", req.Host), zap.Uint32("port", req.ApiPort),
76 zap.Uint32("consensus_port", req.ConsensusPort), zap.String("name", req.Name))
77
78 // Provision cluster info externally
79 _, err = c.Setup.ProvisionCluster(ctx, &schema.ProvisionClusterRequest{
80 InitialCluster: s.consensusService.GetInitialClusterString(),
81 ProvisioningToken: req.Token,
82 ExternalHost: req.Host,
83 NodeName: req.Name,
84 TrustBackend: req.TrustBackend,
Lorenz Bruna4ea9d02019-10-31 11:40:30 +010085 Certs: consensusCerts,
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020086 })
87 if err != nil {
Lorenz Bruna4ea9d02019-10-31 11:40:30 +010088 err3 := s.consensusService.RevokeCertificate(req.Host)
89 if err3 != nil {
90 s.Logger.Error("Failed to revoke a certificate after rollback, potential security risk", zap.Error(err3))
91 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020092 // Revert Consensus add member - might fail if consensus cannot be established
93 err2 := s.consensusService.RemoveMember(ctx, memberID)
Lorenz Bruna4ea9d02019-10-31 11:40:30 +010094 if err2 != nil || err3 != nil {
95 return nil, fmt.Errorf("Rollback failed after failed provisioning; err=%v; err_rb=%v; err_revoke=%v", err, err2, err3)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020096 }
97 return nil, err
98 }
99 s.Logger.Info("Fully provisioned new node",
100 zap.String("host", req.Host), zap.Uint32("port", req.ApiPort),
101 zap.Uint32("consensus_port", req.ConsensusPort), zap.String("name", req.Name),
102 zap.Uint64("member_id", memberID))
103
104 return &schema.AddNodeResponse{}, nil
105}
106
107func (s *Server) RemoveNode(context.Context, *schema.RemoveNodeRequest) (*schema.RemoveNodeRequest, error) {
108 panic("implement me")
109}
110
111func (s *Server) GetNodes(context.Context, *schema.GetNodesRequest) (*schema.GetNodesResponse, error) {
112 nodes := s.consensusService.GetNodes()
113 resNodes := make([]*schema.Node, len(nodes))
114
115 for i, node := range nodes {
116 resNodes[i] = &schema.Node{
117 Id: node.ID,
118 Name: node.Name,
119 Address: node.Address,
120 Synced: node.Synced,
121 }
122 }
123
124 return &schema.GetNodesResponse{
125 Nodes: resNodes,
126 }, nil
127}