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