blob: 9e9476d22f20dd8ca09032b372f3a31904312954 [file] [log] [blame]
Serge Bazanski3379a5d2021-09-09 12:56:40 +02001package rpc
2
3import (
4 "context"
Serge Bazanski54c4f182022-02-18 13:20:13 +01005 "encoding/hex"
Serge Bazanski3379a5d2021-09-09 12:56:40 +02006 "fmt"
Serge Bazanski54c4f182022-02-18 13:20:13 +01007 "sort"
8 "strings"
Serge Bazanski3379a5d2021-09-09 12:56:40 +02009
10 "google.golang.org/grpc"
11 "google.golang.org/grpc/codes"
12 "google.golang.org/grpc/status"
13
Serge Bazanski54c4f182022-02-18 13:20:13 +010014 "source.monogon.dev/metropolis/node/core/identity"
Serge Bazanski3379a5d2021-09-09 12:56:40 +020015 epb "source.monogon.dev/metropolis/proto/ext"
16)
17
18type Permissions map[epb.Permission]bool
19
Serge Bazanski54c4f182022-02-18 13:20:13 +010020func (p Permissions) String() string {
21 var res []string
22
23 for k, _ := range p {
24 res = append(res, k.String())
25 }
26
27 sort.Strings(res)
28 return strings.Join(res, ", ")
29}
30
Serge Bazanski3379a5d2021-09-09 12:56:40 +020031// PeerInfo represents the Metropolis-level information about the remote side
32// of a gRPC RPC, ie. about the calling client in server handlers and about the
33// handling server in client code.
34//
35// Exactly one of {Node, User, Unauthenticated} will be non-nil.
36type PeerInfo struct {
37 // Node is the information about a peer Node, and identifies that the other side
38 // of the connection is either a Node servicng gRPC requests for a cluster, or a
39 // Node connecting to a gRPC service.
40 Node *PeerInfoNode
41 // User is the information about a peer User, and identifies that the other side
42 // of the connection is a Metropolis user or manager (eg. owner). This will only
43 // be set in service handlers, as users cannot serve gRPC connections.
44 User *PeerInfoUser
45 // Unauthenticated is set for incoming gRPC connections which that have the
46 // Unauthenticated authorization extension set to true, and mark that the other
47 // side of the connection has not been verified at all.
48 Unauthenticated *PeerInfoUnauthenticated
49}
50
51// PeerInfoNode contains information about a Node on the other side of a gRPC
52// connection.
53type PeerInfoNode struct {
54 // PublicKey is the ED25519 public key bytes of the node.
55 PublicKey []byte
56
57 // Permissions are the set of permissions this node has.
58 Permissions Permissions
59}
60
61// PeerInfoUser contains information about a user on the other side of a gRPC
62// connection.
63type PeerInfoUser struct {
64 // Identity is an opaque identifier for the user. MVP: Currently this is always
65 // "manager".
66 Identity string
67}
68
69type PeerInfoUnauthenticated struct {
70 // SelfSignedPublicKey is the ED25519 public key bytes of the other side of the
71 // connection, if that side presented a self-signed certificate to prove control
72 // of a private key corresponding to this public key. If it did not present a
73 // self-signed certificate that can be parsed for such a key, this will be nil.
74 //
75 // This can be used by code with expects Unauthenticated RPCs but wants to
76 // authenticate the connection based on ownership of some keypair, for example
77 // in the AAA.Escrow method.
78 SelfSignedPublicKey []byte
79}
80
81// GetPeerInfo returns the PeerInfo of the peer of a gRPC connection, or nil if
82// this connection does not carry any PeerInfo.
83func GetPeerInfo(ctx context.Context) *PeerInfo {
84 if pi, ok := ctx.Value(peerInfoKey).(*PeerInfo); ok {
85 return pi
86 }
87 return nil
88}
89
90func (p *PeerInfo) CheckPermissions(need Permissions) error {
91 if p.Unauthenticated != nil {
92 // This generally shouldn't happen, as unauthenticated users shouldn't be
93 // allowed to reach this part of the code - methods with Need != nil will not be
94 // processed as unauthenticated for security, and will instead act as
95 // authenticated methods and reject unauthenticated connections.
96 for _, v := range need {
97 if v {
98 return status.Error(codes.Unauthenticated, "unauthenticated connection")
99 }
100 }
101 return nil
102 } else if p.User != nil {
103 // MVP: all permissions are granted to all users.
104 // TODO(q3k): check authz.Need once we have a user/identity system implemented.
105 return nil
106 } else if p.Node != nil {
107 for n, v := range need {
108 if v && !p.Node.Permissions[n] {
109 return status.Errorf(codes.PermissionDenied, "node missing %s permission", n.String())
110 }
111 }
112 return nil
113 }
114
115 return fmt.Errorf("invalid PeerInfo: neither Unauthenticated, User nor Node is set")
116}
117
Serge Bazanski54c4f182022-02-18 13:20:13 +0100118func (p *PeerInfo) String() string {
119 if p == nil {
120 return "nil"
121 }
122 switch {
123 case p.Node != nil:
124 return fmt.Sprintf("node: %s, %s", identity.NodeID(p.Node.PublicKey), p.Node.Permissions)
125 case p.User != nil:
126 return fmt.Sprintf("user: %s", p.User.Identity)
127 case p.Unauthenticated != nil:
128 return fmt.Sprintf("unauthenticated: pubkey %s", hex.EncodeToString(p.Unauthenticated.SelfSignedPublicKey))
129 default:
130 return "invalid"
131 }
132}
133
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200134type peerInfoKeyType string
135
136// peerInfoKey is the context key for storing PeerInfo.
137const peerInfoKey = peerInfoKeyType("peerInfo")
138
139// apply returns the given context with itself stored under a unique key, that
140// can be later retrieved via GetPeerInfo.
141func (p *PeerInfo) apply(ctx context.Context) context.Context {
142 return context.WithValue(ctx, peerInfoKey, p)
143}
144
145// peerInfoServerStream is a grpc.ServerStream wrapper which contains some
146// PeerInfo, and returns it as part of the Context() of the ServerStream.
147type peerInfoServerStream struct {
148 grpc.ServerStream
149 pi *PeerInfo
150}
151
152func (p *peerInfoServerStream) Context() context.Context {
153 return p.pi.apply(p.ServerStream.Context())
154}
155
156// serverStream wraps a grpc.ServerStream with a structure that attaches this
157// PeerInfo in all contexts returned by Context().
158func (p *PeerInfo) serverStream(ss grpc.ServerStream) grpc.ServerStream {
159 return &peerInfoServerStream{ss, p}
160}