blob: 88f33f92d7178f9c5eda671c1a60698d5e95cef9 [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 (
Lorenz Brunaa6b7342019-12-12 02:55:02 +010020 "context"
21 "crypto/ed25519"
22 "crypto/rand"
23 "crypto/tls"
24 "crypto/x509"
25 "crypto/x509/pkix"
26 "errors"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020027 "fmt"
Lorenz Brunaa6b7342019-12-12 02:55:02 +010028 "math/big"
29 "net"
30 "time"
31
Hendrik Hofstadt8efe51e2020-02-28 12:53:41 +010032 "go.etcd.io/etcd/clientv3"
33 "go.uber.org/zap"
34 "google.golang.org/grpc"
35 "google.golang.org/grpc/credentials"
36 "google.golang.org/grpc/reflection"
37
Lorenz Brunaa6b7342019-12-12 02:55:02 +010038 "git.monogon.dev/source/nexantic.git/core/generated/api"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020039 schema "git.monogon.dev/source/nexantic.git/core/generated/api"
40 "git.monogon.dev/source/nexantic.git/core/internal/common"
Leopold Schabel68c58752019-11-14 21:00:59 +010041 "git.monogon.dev/source/nexantic.git/core/internal/common/service"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020042 "git.monogon.dev/source/nexantic.git/core/internal/consensus"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020043)
44
45type (
46 Server struct {
Leopold Schabel68c58752019-11-14 21:00:59 +010047 *service.BaseService
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020048
Lorenz Brunaa6b7342019-12-12 02:55:02 +010049 grpcServer *grpc.Server
50 externalGrpcServer *grpc.Server
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020051
52 consensusService *consensus.Service
53
54 config *Config
55 }
56
57 Config struct {
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020058 }
59)
60
Lorenz Brunaa6b7342019-12-12 02:55:02 +010061var (
62 // From RFC 5280 Section 4.1.2.5
63 unknownNotAfter = time.Unix(253402300799, 0)
64)
65
66func NewApiServer(config *Config, logger *zap.Logger, consensusService *consensus.Service) (*Server, error) {
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020067 s := &Server{
68 config: config,
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020069 consensusService: consensusService,
70 }
71
Leopold Schabel68c58752019-11-14 21:00:59 +010072 s.BaseService = service.NewBaseService("api", logger, s)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020073
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020074 return s, nil
75}
76
Lorenz Brunaa6b7342019-12-12 02:55:02 +010077func (s *Server) getStore() clientv3.KV {
78 // Cannot be moved to initialization because an internal reference will be nil
79 return s.consensusService.GetStore("api", "")
80}
81
82// BootstrapNewClusterHook creates the necessary key material for the API Servers and stores it in
83// the consensus service. It also creates a node entry for the initial node.
84func (s *Server) BootstrapNewClusterHook(initNodeReq *api.NewNodeInfo) error {
85 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 127)
86 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
87 if err != nil {
88 return fmt.Errorf("Failed to generate serial number: %w", err)
89 }
90
91 pubKey, privKeyRaw, err := ed25519.GenerateKey(rand.Reader)
92 if err != nil {
93 return err
94 }
95 privkey, err := x509.MarshalPKCS8PrivateKey(privKeyRaw)
96 if err != nil {
97 return err
98 }
99
100 // This has no SANs because it authenticates by public key, not by name
101 masterCert := &x509.Certificate{
102 SerialNumber: serialNumber,
103 Subject: pkix.Name{
104 CommonName: "Smalltown Master",
105 },
106 IsCA: false,
107 BasicConstraintsValid: true,
108 NotBefore: time.Now(),
109 NotAfter: unknownNotAfter,
110 // Certificate is used both as server & client
111 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
112 }
113 cert, err := x509.CreateCertificate(rand.Reader, masterCert, masterCert, pubKey, privKeyRaw)
114 if err != nil {
115 return err
116 }
117 store := s.getStore()
118 if _, err := store.Put(context.Background(), "master.der", string(cert)); err != nil {
119 return err
120 }
121 if _, err := store.Put(context.Background(), "master-key.der", string(privkey)); err != nil {
122 return err
123 }
124
125 // TODO: Further integrity providers need to be plumbed in here
126 node, err := s.TPM2BootstrapNode(initNodeReq)
127 if err != nil {
128 return err
129 }
130
131 if err := s.registerNewNode(node); err != nil {
132 return err
133 }
134 return nil
135}
136
137// GetMasterCert gets the master certificate in X.509 DER form
138// This is mainly used to issue enrolment configs
139func (s *Server) GetMasterCert() ([]byte, error) {
140 store := s.getStore()
141 res, err := store.Get(context.Background(), "master.der")
142 if err != nil {
143 return []byte{}, err
144 }
145 if len(res.Kvs) != 1 {
146 return []byte{}, errors.New("master certificate not found")
147 }
148 certRaw := res.Kvs[0].Value
149 return certRaw, nil
150}
151
152// TODO(lorenz): Move consensus/certificate interaction into a utility, is now duplicated too often
153func (s *Server) loadMasterCert() (*tls.Certificate, error) {
154
155 store := s.getStore()
156 var tlsCert tls.Certificate
157 res, err := store.Get(context.Background(), "master.der")
158 if err != nil {
159 return nil, err
160 }
161 if len(res.Kvs) != 1 {
162 return nil, errors.New("master certificate not found")
163 }
164 certRaw := res.Kvs[0].Value
165
166 tlsCert.Certificate = append(tlsCert.Certificate, certRaw)
167 tlsCert.Leaf, err = x509.ParseCertificate(certRaw)
168
169 res, err = store.Get(context.Background(), "master-key.der")
170 if err != nil {
171 return nil, err
172 }
173 if len(res.Kvs) != 1 {
174 return nil, errors.New("master certificate not found")
175 }
176 keyRaw := res.Kvs[0].Value
177 key, err := x509.ParsePKCS8PrivateKey(keyRaw)
178 if err != nil {
179 return nil, fmt.Errorf("failed to load master private key: %w", err)
180 }
181 edKey, ok := key.(ed25519.PrivateKey)
182 if !ok {
183 return nil, errors.New("invalid private key")
184 }
185 tlsCert.PrivateKey = edKey
186 return &tlsCert, nil
187}
188
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200189func (s *Server) OnStart() error {
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100190 masterListenHost := fmt.Sprintf(":%d", common.MasterServicePort)
191 lis, err := net.Listen("tcp", masterListenHost)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200192 if err != nil {
193 s.Logger.Fatal("failed to listen", zap.Error(err))
194 }
195
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100196 externalListeneHost := fmt.Sprintf(":%d", common.ExternalServicePort)
197 externalListener, err := net.Listen("tcp", externalListeneHost)
198 if err != nil {
199 s.Logger.Fatal("failed to listen", zap.Error(err))
200 }
201
202 masterCert, err := s.loadMasterCert()
203 if err != nil {
204 s.Logger.Error("Failed to load Master Service Key Material: %w", zap.Error(err))
205 return err
206 }
207
208 masterTransportCredentials := credentials.NewServerTLSFromCert(masterCert)
209
210 masterGrpcServer := grpc.NewServer(grpc.Creds(masterTransportCredentials))
211 clusterManagementGrpcServer := grpc.NewServer()
212 schema.RegisterClusterManagementServer(clusterManagementGrpcServer, s)
213 schema.RegisterNodeManagementServiceServer(masterGrpcServer, s)
214
215 reflection.Register(masterGrpcServer)
216
217 s.grpcServer = masterGrpcServer
218 s.externalGrpcServer = clusterManagementGrpcServer
219
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200220 go func() {
221 err = s.grpcServer.Serve(lis)
222 s.Logger.Error("API server failed", zap.Error(err))
223 }()
224
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100225 go func() {
226 err = s.externalGrpcServer.Serve(externalListener)
227 s.Logger.Error("API server failed", zap.Error(err))
228 }()
229
230 s.Logger.Info("gRPC listening", zap.String("host", masterListenHost))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200231
232 return nil
233}
234
235func (s *Server) OnStop() error {
236 s.grpcServer.Stop()
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100237 s.externalGrpcServer.Stop()
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200238
239 return nil
240}