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