blob: eccf0067ca4a58e37379baabf8492ac250ffc27e [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
15 cpb "source.monogon.dev/metropolis/node/core/curator/proto/api"
16 "source.monogon.dev/metropolis/node/core/identity"
Serge Bazanskifb0fb6d2022-02-18 12:11:28 +010017 "source.monogon.dev/metropolis/pkg/logtree"
Serge Bazanski3379a5d2021-09-09 12:56:40 +020018 apb "source.monogon.dev/metropolis/proto/api"
19)
20
Serge Bazanski3379a5d2021-09-09 12:56:40 +020021// ServerSecurity are the security options of a RPC server that will run
22// ClusterServices on a Metropolis node. It contains all the data for the
23// server implementation to authenticate itself to the clients and authenticate
24// and authorize clients connecting to it.
Serge Bazanskif3c4b422022-03-10 00:37:57 +010025type ServerSecurity struct {
Serge Bazanski3379a5d2021-09-09 12:56:40 +020026 // NodeCredentials which will be used to run the gRPC server, and whose CA
27 // certificate will be used to authenticate incoming requests.
28 NodeCredentials *identity.NodeCredentials
29
30 // nodePermissions is used by tests to inject the permissions available to a
31 // node. When not set, it defaults to the global nodePermissions map.
32 nodePermissions Permissions
33}
34
Serge Bazanskif3c4b422022-03-10 00:37:57 +010035// SetupExternalGRPC returns a grpc.Server ready to listen and serve all gRPC
36// services that the cluster server implementation should run, with all calls
37// authenticated and authorized based on the data in ServerSecurity. The
Serge Bazanski3379a5d2021-09-09 12:56:40 +020038// argument 'impls' is the object implementing the gRPC APIs.
39//
Serge Bazanskif3c4b422022-03-10 00:37:57 +010040// Under the hood, this configures gRPC interceptors that verify
Serge Bazanski3379a5d2021-09-09 12:56:40 +020041// metropolis.proto.ext.authorization options and authenticate/authorize
42// incoming connections. It also runs the gRPC server with the correct TLS
43// settings for authenticating itself to callers.
Serge Bazanskif3c4b422022-03-10 00:37:57 +010044func (s *ServerSecurity) SetupExternalGRPC(logger logtree.LeveledLogger, impls ClusterServices) *grpc.Server {
Serge Bazanski3379a5d2021-09-09 12:56:40 +020045 externalCreds := credentials.NewTLS(&tls.Config{
Serge Bazanskif3c4b422022-03-10 00:37:57 +010046 Certificates: []tls.Certificate{s.NodeCredentials.TLSCredentials()},
Serge Bazanski3379a5d2021-09-09 12:56:40 +020047 ClientAuth: tls.RequestClientCert,
48 })
49
Serge Bazanskif3c4b422022-03-10 00:37:57 +010050 srv := grpc.NewServer(
Serge Bazanski3379a5d2021-09-09 12:56:40 +020051 grpc.Creds(externalCreds),
Serge Bazanskif3c4b422022-03-10 00:37:57 +010052 grpc.UnaryInterceptor(s.unaryInterceptor(logger)),
53 grpc.StreamInterceptor(s.streamInterceptor(logger)),
Serge Bazanski3379a5d2021-09-09 12:56:40 +020054 )
Serge Bazanskif3c4b422022-03-10 00:37:57 +010055 cpb.RegisterCuratorServer(srv, impls)
56 apb.RegisterAAAServer(srv, impls)
57 apb.RegisterManagementServer(srv, impls)
58 return srv
Serge Bazanski3379a5d2021-09-09 12:56:40 +020059}
60
Serge Bazanskif3c4b422022-03-10 00:37:57 +010061// streamInterceptor returns a gRPC StreamInterceptor interface for use with
62// grpc.NewServer. It's applied to gRPC servers started within Metropolis,
63// notably to the Curator.
64func (s *ServerSecurity) streamInterceptor(logger logtree.LeveledLogger) grpc.StreamServerInterceptor {
65 return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
66 var span *logtreeSpan
67 if logger != nil {
68 span = newLogtreeSpan(logger)
69 span.Printf("RPC invoked: streaming request: %s", info.FullMethod)
70 ss = &spanServerStream{
71 ServerStream: ss,
72 span: span,
73 }
74 }
75
76 pi, err := s.authenticationCheck(ss.Context(), info.FullMethod)
77 if err != nil {
78 if s != nil {
79 span.Printf("RPC send: authentication failed: %v", err)
80 }
81 return err
82 }
83 if span != nil {
84 span.Printf("RPC peerInfo: %s", pi.String())
85 }
86
87 return handler(srv, pi.serverStream(ss))
88 }
89}
90
91// unaryInterceptor returns a gRPC UnaryInterceptor interface for use with
92// grpc.NewServer. It's applied to gRPC servers started within Metropolis,
93// notably to the Curator.
94func (s *ServerSecurity) unaryInterceptor(logger logtree.LeveledLogger) grpc.UnaryServerInterceptor {
95 return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
96 // Inject span if we have a logger.
97 if logger != nil {
98 ctx = contextWithSpan(ctx, newLogtreeSpan(logger))
99 }
100
101 Trace(ctx).Printf("RPC invoked: unary request: %s", info.FullMethod)
102
103 // Perform authentication check and inject PeerInfo.
104 pi, err := s.authenticationCheck(ctx, info.FullMethod)
105 if err != nil {
106 Trace(ctx).Printf("RPC send: authentication failed: %v", err)
107 return nil, err
108 }
109 ctx = pi.apply(ctx)
110
111 // Log authentication information.
112 Trace(ctx).Printf("RPC peerInfo: %s", pi.String())
113
114 // Call underlying handler.
115 resp, err = handler(ctx, req)
116
117 // Log result into span.
118 if err != nil {
119 Trace(ctx).Printf("RPC send: error: %v", err)
120 } else {
121 Trace(ctx).Printf("RPC send: ok, %s", protoMessagePretty(resp))
122 }
123 return
124 }
125}
126
127// authenticationCheck is called by the unary and server interceptors to perform
128// authentication and authorization checks for a given RPC.
129func (s *ServerSecurity) authenticationCheck(ctx context.Context, methodName string) (*PeerInfo, error) {
130 mi, err := getMethodInfo(methodName)
131 if err != nil {
132 return nil, err
133 }
134
135 if mi.unauthenticated {
136 return s.getPeerInfoUnauthenticated(ctx)
137 }
138
139 pi, err := s.getPeerInfo(ctx)
140 if err != nil {
141 return nil, err
142 }
143 if err := pi.CheckPermissions(mi.need); err != nil {
144 return nil, err
145 }
146 return pi, nil
147}
148
149// getPeerInfo is be called by authenticationCheck to authenticate incoming gRPC
150// calls. It returns PeerInfo structure describing the authenticated other end
151// of the connection, or a gRPC status if the other side could not be
152// successfully authenticated.
153//
154// The returned PeerInfo can then be used to perform authorization checks based
155// on the configured authentication of a given gRPC method, as described by the
156// metropolis.proto.ext.authorization extension.
157func (s *ServerSecurity) getPeerInfo(ctx context.Context) (*PeerInfo, error) {
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200158 cert, err := getPeerCertificate(ctx)
159 if err != nil {
160 return nil, err
161 }
162
163 // Ensure that the certificate is signed by the cluster CA.
Serge Bazanskif3c4b422022-03-10 00:37:57 +0100164 if err := cert.CheckSignatureFrom(s.NodeCredentials.ClusterCA()); err != nil {
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200165 return nil, status.Errorf(codes.Unauthenticated, "certificate not signed by cluster CA: %v", err)
166 }
167
Serge Bazanskif3c4b422022-03-10 00:37:57 +0100168 nodepk, errNode := identity.VerifyNodeInCluster(cert, s.NodeCredentials.ClusterCA())
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200169 if errNode == nil {
170 // This is a Metropolis node.
Serge Bazanskif3c4b422022-03-10 00:37:57 +0100171 np := s.nodePermissions
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200172 if np == nil {
173 np = nodePermissions
174 }
175 return &PeerInfo{
176 Node: &PeerInfoNode{
177 PublicKey: nodepk,
178 Permissions: np,
179 },
180 }, nil
181 }
182
Serge Bazanskif3c4b422022-03-10 00:37:57 +0100183 userid, errUser := identity.VerifyUserInCluster(cert, s.NodeCredentials.ClusterCA())
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200184 if errUser == nil {
185 // This is a Metropolis user/manager.
186 return &PeerInfo{
187 User: &PeerInfoUser{
188 Identity: userid,
189 },
190 }, nil
191 }
192
193 // Could not parse as either node or user certificate.
194 return nil, status.Errorf(codes.Unauthenticated, "presented certificate is neither user certificate (%v) nor node certificate (%v)", errUser, errNode)
195}
196
Serge Bazanskif3c4b422022-03-10 00:37:57 +0100197// getPeerInfoUnauthenticated is an equivalent to getPeerInfo, but called when a
198// method is marked as 'unauthenticated'. The implementation should return a
199// PeerInfo containing Unauthenticated, potentially populating it with
200// UnauthenticatedPublicKey if such a public key could be retrieved.
201func (s *ServerSecurity) getPeerInfoUnauthenticated(ctx context.Context) (*PeerInfo, error) {
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200202 res := PeerInfo{
203 Unauthenticated: &PeerInfoUnauthenticated{},
204 }
205
206 // If peer presented a valid self-signed certificate, attach that to the
207 // Unauthenticated struct.
208 cert, err := getPeerCertificate(ctx)
209 if err == nil {
210 if err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature); err != nil {
211 return nil, status.Errorf(codes.Unauthenticated, "presented certificate must be self-signed (check error: %v)", err)
212 }
213 res.Unauthenticated.SelfSignedPublicKey = cert.PublicKey.(ed25519.PublicKey)
214 }
215
216 return &res, nil
217}
218
219// getPeerCertificate returns the x509 certificate associated with the given
220// gRPC connection's context and ensures that it is a certificate for an Ed25519
221// keypair. The certificate is _not_ checked against the cluster CA.
222//
223// A gRPC status is returned if the certificate is invalid / unauthenticated for
224// any reason.
225func getPeerCertificate(ctx context.Context) (*x509.Certificate, error) {
226 p, ok := peer.FromContext(ctx)
227 if !ok {
228 return nil, status.Error(codes.Unavailable, "could not retrive peer info")
229 }
230 tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo)
231 if !ok {
232 return nil, status.Error(codes.Unauthenticated, "connection not secure")
233 }
234 count := len(tlsInfo.State.PeerCertificates)
235 if count == 0 {
236 return nil, status.Errorf(codes.Unauthenticated, "no client certificate presented")
237 }
238 if count > 1 {
239 return nil, status.Errorf(codes.Unauthenticated, "exactly one client certificate must be sent (got %d)", count)
240 }
241 cert := tlsInfo.State.PeerCertificates[0]
242 if _, ok := cert.PublicKey.(ed25519.PublicKey); !ok {
243 return nil, status.Errorf(codes.Unauthenticated, "certificate must be issued for an ED25519 keypair")
244 }
245
246 return cert, nil
247}