blob: 4aeda7507fb96c452e7996e1b0af20cb89620477 [file] [log] [blame]
Mateusz Zalega18a67b02022-08-02 13:37:50 +02001package core
2
3import (
4 "context"
5 "crypto/ed25519"
6 "crypto/tls"
7 "crypto/x509"
8 "fmt"
9 "io"
10 "net"
11
12 "golang.org/x/net/proxy"
13 "google.golang.org/grpc"
14
15 "source.monogon.dev/metropolis/node"
16 "source.monogon.dev/metropolis/node/core/rpc"
17 "source.monogon.dev/metropolis/node/core/rpc/resolver"
18 "source.monogon.dev/metropolis/proto/api"
19)
20
21type ResolverLogger func(format string, args ...interface{})
22
23// DialCluster dials the cluster control address. The owner certificate, and
24// proxy address parameters are optional and can be left nil, and empty,
25// respectively. At least one cluster endpoint must be provided. A missing
26// owner certificate will result in a connection that is authenticated with
27// ephemeral credentials, restricting the available API surface. proxyAddr
28// must point at a SOCKS5 endpoint.
29func DialCluster(ctx context.Context, opkey ed25519.PrivateKey, ocert *x509.Certificate, proxyAddr string, clusterEndpoints []string, rlf ResolverLogger) (*grpc.ClientConn, error) {
30 var dialOpts []grpc.DialOption
31
32 if opkey == nil {
33 return nil, fmt.Errorf("an owner's private key must be provided")
34 }
35 if len(clusterEndpoints) == 0 {
36 return nil, fmt.Errorf("at least one cluster endpoint must be provided")
37 }
38
39 if proxyAddr != "" {
40 socksDialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
41 if err != nil {
42 return nil, fmt.Errorf("failed to build a SOCKS dialer: %v", err)
43 }
44 grpcd := func(_ context.Context, addr string) (net.Conn, error) {
45 return socksDialer.Dial("tcp", addr)
46 }
47 dialOpts = append(dialOpts, grpc.WithContextDialer(grpcd))
48 }
49
50 if ocert == nil {
51 creds, err := rpc.NewEphemeralCredentials(opkey, nil)
52 if err != nil {
53 return nil, fmt.Errorf("while building ephemeral credentials: %v", err)
54 }
55 dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds))
56 } else {
57 tlsc := tls.Certificate{
58 Certificate: [][]byte{ocert.Raw},
59 PrivateKey: opkey,
60 }
Serge Bazanski8535cb52023-03-29 14:15:08 +020061 creds := rpc.NewAuthenticatedCredentials(tlsc, rpc.WantInsecure())
Mateusz Zalega18a67b02022-08-02 13:37:50 +020062 dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds))
63 }
64
65 var resolverOpts []resolver.ResolverOption
66 if rlf != nil {
67 resolverOpts = append(resolverOpts, resolver.WithLogger(rlf))
68 }
69 r := resolver.New(ctx, resolverOpts...)
70
71 for _, eps := range clusterEndpoints {
72 ep := resolver.NodeByHostPort(eps, uint16(node.CuratorServicePort))
73 r.AddEndpoint(ep)
74 }
75 dialOpts = append(dialOpts, grpc.WithResolvers(r))
76
77 c, err := grpc.Dial(resolver.MetropolisControlAddress, dialOpts...)
78 if err != nil {
79 return nil, fmt.Errorf("could not dial: %v", err)
80 }
81 return c, nil
82}
83
Serge Bazanskib91938f2023-03-29 14:31:22 +020084func DialNode(ctx context.Context, opkey ed25519.PrivateKey, ocert, ca *x509.Certificate, proxyAddr, nodeId, nodeAddr string) (*grpc.ClientConn, error) {
85 var dialOpts []grpc.DialOption
86
87 if opkey == nil {
88 return nil, fmt.Errorf("an owner's private key must be provided")
89 }
90 if proxyAddr != "" {
91 socksDialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
92 if err != nil {
93 return nil, fmt.Errorf("failed to build a SOCKS dialer: %v", err)
94 }
95 grpcd := func(_ context.Context, addr string) (net.Conn, error) {
96 return socksDialer.Dial("tcp", addr)
97 }
98 dialOpts = append(dialOpts, grpc.WithContextDialer(grpcd))
99 }
100 tlsc := tls.Certificate{
101 Certificate: [][]byte{ocert.Raw},
102 PrivateKey: opkey,
103 }
104 creds := rpc.NewAuthenticatedCredentials(tlsc, rpc.WantRemoteCluster(ca), rpc.WantRemoteNode(nodeId))
105 dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds))
106
107 endpoint := net.JoinHostPort(nodeAddr, node.NodeManagement.PortString())
108 return grpc.Dial(endpoint, dialOpts...)
109}
110
Mateusz Zalega18a67b02022-08-02 13:37:50 +0200111// GetNodes retrieves node records, filtered by the supplied node filter
112// expression fexp.
113func GetNodes(ctx context.Context, mgmt api.ManagementClient, fexp string) ([]*api.Node, error) {
114 resN, err := mgmt.GetNodes(ctx, &api.GetNodesRequest{
115 Filter: fexp,
116 })
117 if err != nil {
118 return nil, err
119 }
120
121 var nodes []*api.Node
122 for {
123 node, err := resN.Recv()
124 if err == io.EOF {
125 break
126 }
127 if err != nil {
128 return nil, err
129 }
130 nodes = append(nodes, node)
131 }
132 return nodes, nil
133}