blob: 516883aa96a4b4c5c9397391c2d9131b883e4450 [file] [log] [blame]
Serge Bazanski3379a5d2021-09-09 12:56:40 +02001package rpc
2
3import (
4 "context"
5 "crypto/ed25519"
6 "crypto/tls"
7 "crypto/x509"
8
9 "google.golang.org/grpc"
10 "google.golang.org/grpc/codes"
11 "google.golang.org/grpc/credentials"
12 "google.golang.org/grpc/peer"
13 "google.golang.org/grpc/status"
14
Serge Bazanski3379a5d2021-09-09 12:56:40 +020015 "source.monogon.dev/metropolis/node/core/identity"
Serge Bazanskifb0fb6d2022-02-18 12:11:28 +010016 "source.monogon.dev/metropolis/pkg/logtree"
Serge Bazanski3379a5d2021-09-09 12:56:40 +020017)
18
Serge Bazanski3379a5d2021-09-09 12:56:40 +020019// ServerSecurity are the security options of a RPC server that will run
Serge Bazanski05c1db92022-06-23 14:24:29 +020020// cluster services on a Metropolis node. It contains all the data for the
Serge Bazanski3379a5d2021-09-09 12:56:40 +020021// server implementation to authenticate itself to the clients and authenticate
22// and authorize clients connecting to it.
Serge Bazanskif3c4b422022-03-10 00:37:57 +010023type ServerSecurity struct {
Serge Bazanski3379a5d2021-09-09 12:56:40 +020024 // NodeCredentials which will be used to run the gRPC server, and whose CA
25 // certificate will be used to authenticate incoming requests.
26 NodeCredentials *identity.NodeCredentials
27
28 // nodePermissions is used by tests to inject the permissions available to a
29 // node. When not set, it defaults to the global nodePermissions map.
30 nodePermissions Permissions
31}
32
Serge Bazanski5e9cb572022-05-16 15:54:50 +020033// GRPCOptions returns a list of gRPC ServerOptions used to run a Metropolis
34// gRPC server with security and logging middleware enabled.
Serge Bazanski3379a5d2021-09-09 12:56:40 +020035//
Serge Bazanskif3c4b422022-03-10 00:37:57 +010036// Under the hood, this configures gRPC interceptors that verify
Serge Bazanski3379a5d2021-09-09 12:56:40 +020037// metropolis.proto.ext.authorization options and authenticate/authorize
38// incoming connections. It also runs the gRPC server with the correct TLS
39// settings for authenticating itself to callers.
Serge Bazanski5e9cb572022-05-16 15:54:50 +020040func (s *ServerSecurity) GRPCOptions(logger logtree.LeveledLogger) []grpc.ServerOption {
Serge Bazanski3379a5d2021-09-09 12:56:40 +020041 externalCreds := credentials.NewTLS(&tls.Config{
Serge Bazanskif3c4b422022-03-10 00:37:57 +010042 Certificates: []tls.Certificate{s.NodeCredentials.TLSCredentials()},
Serge Bazanski3379a5d2021-09-09 12:56:40 +020043 ClientAuth: tls.RequestClientCert,
44 })
45
Serge Bazanski5e9cb572022-05-16 15:54:50 +020046 return []grpc.ServerOption{
Serge Bazanski3379a5d2021-09-09 12:56:40 +020047 grpc.Creds(externalCreds),
Serge Bazanskif3c4b422022-03-10 00:37:57 +010048 grpc.UnaryInterceptor(s.unaryInterceptor(logger)),
49 grpc.StreamInterceptor(s.streamInterceptor(logger)),
Serge Bazanski5e9cb572022-05-16 15:54:50 +020050 }
Serge Bazanski3379a5d2021-09-09 12:56:40 +020051}
52
Serge Bazanskif3c4b422022-03-10 00:37:57 +010053// streamInterceptor returns a gRPC StreamInterceptor interface for use with
54// grpc.NewServer. It's applied to gRPC servers started within Metropolis,
55// notably to the Curator.
56func (s *ServerSecurity) streamInterceptor(logger logtree.LeveledLogger) grpc.StreamServerInterceptor {
57 return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
58 var span *logtreeSpan
Serge Bazanskib58102c2023-03-29 17:48:18 +020059 // HACK: Do not log any log retrieval methods into the log, otherwise logs blow up
60 // on retrieval.
61 //
62 // TOOD(q3k): make this more generic
63 if logger != nil && info.FullMethod != "/metropolis.proto.api.NodeManagement/Logs" {
Serge Bazanskif3c4b422022-03-10 00:37:57 +010064 span = newLogtreeSpan(logger)
65 span.Printf("RPC invoked: streaming request: %s", info.FullMethod)
66 ss = &spanServerStream{
67 ServerStream: ss,
68 span: span,
69 }
70 }
71
72 pi, err := s.authenticationCheck(ss.Context(), info.FullMethod)
73 if err != nil {
74 if s != nil {
75 span.Printf("RPC send: authentication failed: %v", err)
76 }
77 return err
78 }
79 if span != nil {
80 span.Printf("RPC peerInfo: %s", pi.String())
81 }
82
83 return handler(srv, pi.serverStream(ss))
84 }
85}
86
87// unaryInterceptor returns a gRPC UnaryInterceptor interface for use with
88// grpc.NewServer. It's applied to gRPC servers started within Metropolis,
89// notably to the Curator.
90func (s *ServerSecurity) unaryInterceptor(logger logtree.LeveledLogger) grpc.UnaryServerInterceptor {
91 return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
92 // Inject span if we have a logger.
93 if logger != nil {
94 ctx = contextWithSpan(ctx, newLogtreeSpan(logger))
95 }
96
97 Trace(ctx).Printf("RPC invoked: unary request: %s", info.FullMethod)
98
99 // Perform authentication check and inject PeerInfo.
100 pi, err := s.authenticationCheck(ctx, info.FullMethod)
101 if err != nil {
102 Trace(ctx).Printf("RPC send: authentication failed: %v", err)
103 return nil, err
104 }
105 ctx = pi.apply(ctx)
106
107 // Log authentication information.
108 Trace(ctx).Printf("RPC peerInfo: %s", pi.String())
109
110 // Call underlying handler.
111 resp, err = handler(ctx, req)
112
113 // Log result into span.
114 if err != nil {
115 Trace(ctx).Printf("RPC send: error: %v", err)
116 } else {
117 Trace(ctx).Printf("RPC send: ok, %s", protoMessagePretty(resp))
118 }
119 return
120 }
121}
122
123// authenticationCheck is called by the unary and server interceptors to perform
124// authentication and authorization checks for a given RPC.
125func (s *ServerSecurity) authenticationCheck(ctx context.Context, methodName string) (*PeerInfo, error) {
126 mi, err := getMethodInfo(methodName)
127 if err != nil {
128 return nil, err
129 }
130
131 if mi.unauthenticated {
132 return s.getPeerInfoUnauthenticated(ctx)
133 }
134
135 pi, err := s.getPeerInfo(ctx)
136 if err != nil {
137 return nil, err
138 }
139 if err := pi.CheckPermissions(mi.need); err != nil {
140 return nil, err
141 }
142 return pi, nil
143}
144
145// getPeerInfo is be called by authenticationCheck to authenticate incoming gRPC
146// calls. It returns PeerInfo structure describing the authenticated other end
147// of the connection, or a gRPC status if the other side could not be
148// successfully authenticated.
149//
150// The returned PeerInfo can then be used to perform authorization checks based
151// on the configured authentication of a given gRPC method, as described by the
152// metropolis.proto.ext.authorization extension.
153func (s *ServerSecurity) getPeerInfo(ctx context.Context) (*PeerInfo, error) {
Serge Bazanski4abeb132022-10-11 11:32:19 +0200154 cert, err := GetPeerCertificate(ctx)
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200155 if err != nil {
156 return nil, err
157 }
158
159 // Ensure that the certificate is signed by the cluster CA.
Serge Bazanskif3c4b422022-03-10 00:37:57 +0100160 if err := cert.CheckSignatureFrom(s.NodeCredentials.ClusterCA()); err != nil {
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200161 return nil, status.Errorf(codes.Unauthenticated, "certificate not signed by cluster CA: %v", err)
162 }
163
Serge Bazanskif3c4b422022-03-10 00:37:57 +0100164 nodepk, errNode := identity.VerifyNodeInCluster(cert, s.NodeCredentials.ClusterCA())
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200165 if errNode == nil {
166 // This is a Metropolis node.
Serge Bazanskif3c4b422022-03-10 00:37:57 +0100167 np := s.nodePermissions
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200168 if np == nil {
169 np = nodePermissions
170 }
171 return &PeerInfo{
172 Node: &PeerInfoNode{
173 PublicKey: nodepk,
174 Permissions: np,
175 },
176 }, nil
177 }
178
Serge Bazanskif3c4b422022-03-10 00:37:57 +0100179 userid, errUser := identity.VerifyUserInCluster(cert, s.NodeCredentials.ClusterCA())
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200180 if errUser == nil {
181 // This is a Metropolis user/manager.
182 return &PeerInfo{
183 User: &PeerInfoUser{
184 Identity: userid,
185 },
186 }, nil
187 }
188
189 // Could not parse as either node or user certificate.
190 return nil, status.Errorf(codes.Unauthenticated, "presented certificate is neither user certificate (%v) nor node certificate (%v)", errUser, errNode)
191}
192
Serge Bazanskif3c4b422022-03-10 00:37:57 +0100193// getPeerInfoUnauthenticated is an equivalent to getPeerInfo, but called when a
194// method is marked as 'unauthenticated'. The implementation should return a
195// PeerInfo containing Unauthenticated, potentially populating it with
196// UnauthenticatedPublicKey if such a public key could be retrieved.
197func (s *ServerSecurity) getPeerInfoUnauthenticated(ctx context.Context) (*PeerInfo, error) {
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200198 res := PeerInfo{
199 Unauthenticated: &PeerInfoUnauthenticated{},
200 }
201
202 // If peer presented a valid self-signed certificate, attach that to the
203 // Unauthenticated struct.
Serge Bazanski4abeb132022-10-11 11:32:19 +0200204 cert, err := GetPeerCertificate(ctx)
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200205 if err == nil {
206 if err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature); err != nil {
Serge Bazanski003a3b02022-06-22 12:58:24 +0200207 // Peer presented a certificate that is not self-signed - for example a user or
208 // node certificate. Ignore it.
209 return &res, nil
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200210 }
211 res.Unauthenticated.SelfSignedPublicKey = cert.PublicKey.(ed25519.PublicKey)
212 }
213
214 return &res, nil
215}
216
Serge Bazanski4abeb132022-10-11 11:32:19 +0200217// GetPeerCertificate returns the x509 certificate associated with the given
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200218// gRPC connection's context and ensures that it is a certificate for an Ed25519
219// keypair. The certificate is _not_ checked against the cluster CA.
220//
221// A gRPC status is returned if the certificate is invalid / unauthenticated for
222// any reason.
Serge Bazanski4abeb132022-10-11 11:32:19 +0200223func GetPeerCertificate(ctx context.Context) (*x509.Certificate, error) {
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200224 p, ok := peer.FromContext(ctx)
225 if !ok {
226 return nil, status.Error(codes.Unavailable, "could not retrive peer info")
227 }
228 tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo)
229 if !ok {
230 return nil, status.Error(codes.Unauthenticated, "connection not secure")
231 }
232 count := len(tlsInfo.State.PeerCertificates)
233 if count == 0 {
234 return nil, status.Errorf(codes.Unauthenticated, "no client certificate presented")
235 }
236 if count > 1 {
237 return nil, status.Errorf(codes.Unauthenticated, "exactly one client certificate must be sent (got %d)", count)
238 }
239 cert := tlsInfo.State.PeerCertificates[0]
240 if _, ok := cert.PublicKey.(ed25519.PublicKey); !ok {
241 return nil, status.Errorf(codes.Unauthenticated, "certificate must be issued for an ED25519 keypair")
242 }
243
244 return cert, nil
245}