m/cli/metroctl: clean up RPC API
This removes a bunch of logic from the metroctl core RPC functions,
forcing users (currently only other metroctl code) to use grpc.Dial and
the metropolis RPC library directly.
We also make the core functions take ConnectOptions structures where
appropriate instead of passing around tons of arguments.
Change-Id: I4d7aa232a659097da35027dfb9b87c58cbb4ab84
Reviewed-on: https://review.monogon.dev/c/monogon/+/2742
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/cli/metroctl/BUILD.bazel b/metropolis/cli/metroctl/BUILD.bazel
index 40d97b7..9100031 100644
--- a/metropolis/cli/metroctl/BUILD.bazel
+++ b/metropolis/cli/metroctl/BUILD.bazel
@@ -46,6 +46,7 @@
"//metropolis/cli/pkg/datafile",
"//metropolis/node/core/identity",
"//metropolis/node/core/rpc",
+ "//metropolis/node/core/rpc/resolver",
"//metropolis/pkg/blkio",
"//metropolis/pkg/fat32",
"//metropolis/pkg/logtree",
diff --git a/metropolis/cli/metroctl/cmd_takeownership.go b/metropolis/cli/metroctl/cmd_takeownership.go
index 740dfb1..61d6c4e 100644
--- a/metropolis/cli/metroctl/cmd_takeownership.go
+++ b/metropolis/cli/metroctl/cmd_takeownership.go
@@ -7,10 +7,12 @@
"os/exec"
"github.com/spf13/cobra"
+ "google.golang.org/grpc"
"source.monogon.dev/metropolis/cli/metroctl/core"
clicontext "source.monogon.dev/metropolis/cli/pkg/context"
"source.monogon.dev/metropolis/node/core/rpc"
+ "source.monogon.dev/metropolis/node/core/rpc/resolver"
apb "source.monogon.dev/metropolis/proto/api"
)
@@ -40,7 +42,17 @@
log.Fatalf("Couldn't get owner's key: %v", err)
}
ctx := clicontext.WithInterrupt(context.Background())
- cc, err := core.DialCluster(ctx, opk, nil, flags.proxyAddr, flags.clusterEndpoints, rpcLogger)
+ opts, err := core.DialOpts(ctx, connectOptions())
+ if err != nil {
+ log.Fatalf("While configuring cluster dial opts: %v", err)
+ }
+ creds, err := rpc.NewEphemeralCredentials(opk, rpc.WantInsecure())
+ if err != nil {
+ log.Fatalf("While generating ephemeral credentials: %v", err)
+ }
+ opts = append(opts, grpc.WithTransportCredentials(creds))
+
+ cc, err := grpc.Dial(resolver.MetropolisControlAddress, opts...)
if err != nil {
log.Fatalf("While dialing the cluster: %v", err)
}
@@ -50,6 +62,7 @@
if err != nil {
log.Fatalf("Failed to retrive owner certificate from cluster: %v", err)
}
+
if err := core.WriteOwnerCertificate(flags.configPath, ownerCert.Certificate[0]); err != nil {
log.Printf("Failed to store retrieved owner certificate: %v", err)
log.Fatalln("Sorry, the cluster has been lost as taking ownership cannot be repeated. Fix the reason the file couldn't be written and reinstall the node.")
diff --git a/metropolis/cli/metroctl/core/config.go b/metropolis/cli/metroctl/core/config.go
index 9714421..1307d61 100644
--- a/metropolis/cli/metroctl/core/config.go
+++ b/metropolis/cli/metroctl/core/config.go
@@ -219,6 +219,9 @@
// instances that metroctl should use to establish connectivity to a cluster.
// These instances should have the ControlPlane role set.
Endpoints []string
+ // ResolverLogger can be set to enable verbose logging of the Metropolis RPC
+ // resolver layer.
+ ResolverLogger ResolverLogger
}
// ToFlags returns the metroctl flags corresponding to the options described by
diff --git a/metropolis/cli/metroctl/core/rpc.go b/metropolis/cli/metroctl/core/rpc.go
index e519906..1c4fa4f 100644
--- a/metropolis/cli/metroctl/core/rpc.go
+++ b/metropolis/cli/metroctl/core/rpc.go
@@ -20,65 +20,36 @@
type ResolverLogger func(format string, args ...interface{})
-// DialCluster dials the cluster control address. The owner certificate, and
-// proxy address parameters are optional and can be left nil, and empty,
-// respectively. At least one cluster endpoint must be provided. A missing
-// owner certificate will result in a connection that is authenticated with
-// ephemeral credentials, restricting the available API surface. proxyAddr
-// must point at a SOCKS5 endpoint.
-func DialCluster(ctx context.Context, opkey ed25519.PrivateKey, ocert *x509.Certificate, proxyAddr string, clusterEndpoints []string, rlf ResolverLogger) (*grpc.ClientConn, error) {
- var dialOpts []grpc.DialOption
-
- if opkey == nil {
- return nil, fmt.Errorf("an owner's private key must be provided")
- }
- if len(clusterEndpoints) == 0 {
- return nil, fmt.Errorf("at least one cluster endpoint must be provided")
- }
-
- if proxyAddr != "" {
- socksDialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
+func DialOpts(ctx context.Context, c *ConnectOptions) ([]grpc.DialOption, error) {
+ var opts []grpc.DialOption
+ if c.ProxyServer != "" {
+ socksDialer, err := proxy.SOCKS5("tcp", c.ProxyServer, nil, proxy.Direct)
if err != nil {
return nil, fmt.Errorf("failed to build a SOCKS dialer: %v", err)
}
grpcd := func(_ context.Context, addr string) (net.Conn, error) {
return socksDialer.Dial("tcp", addr)
}
- dialOpts = append(dialOpts, grpc.WithContextDialer(grpcd))
- }
-
- if ocert == nil {
- creds, err := rpc.NewEphemeralCredentials(opkey, rpc.WantInsecure())
- if err != nil {
- return nil, fmt.Errorf("while building ephemeral credentials: %v", err)
- }
- dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds))
- } else {
- tlsc := tls.Certificate{
- Certificate: [][]byte{ocert.Raw},
- PrivateKey: opkey,
- }
- creds := rpc.NewAuthenticatedCredentials(tlsc, rpc.WantInsecure())
- dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds))
+ opts = append(opts, grpc.WithContextDialer(grpcd))
}
var resolverOpts []resolver.ResolverOption
- if rlf != nil {
- resolverOpts = append(resolverOpts, resolver.WithLogger(rlf))
+ if c.ResolverLogger != nil {
+ resolverOpts = append(resolverOpts, resolver.WithLogger(c.ResolverLogger))
}
+
r := resolver.New(ctx, resolverOpts...)
- for _, eps := range clusterEndpoints {
+ if len(c.Endpoints) == 0 {
+ return nil, fmt.Errorf("no cluster endpoints specified")
+ }
+ for _, eps := range c.Endpoints {
ep := resolver.NodeByHostPort(eps, uint16(node.CuratorServicePort))
r.AddEndpoint(ep)
}
- dialOpts = append(dialOpts, grpc.WithResolvers(r))
+ opts = append(opts, grpc.WithResolvers(r))
- c, err := grpc.Dial(resolver.MetropolisControlAddress, dialOpts...)
- if err != nil {
- return nil, fmt.Errorf("could not dial: %v", err)
- }
- return c, nil
+ return opts, nil
}
func DialNode(ctx context.Context, opkey ed25519.PrivateKey, ocert, ca *x509.Certificate, proxyAddr, nodeId, nodeAddr string) (*grpc.ClientConn, error) {
diff --git a/metropolis/cli/metroctl/main.go b/metropolis/cli/metroctl/main.go
index a4ea438..698dbbc 100644
--- a/metropolis/cli/metroctl/main.go
+++ b/metropolis/cli/metroctl/main.go
@@ -65,8 +65,9 @@
// currently set.
func connectOptions() *core.ConnectOptions {
return &core.ConnectOptions{
- ProxyServer: flags.proxyAddr,
- Endpoints: flags.clusterEndpoints,
- ConfigPath: flags.configPath,
+ ConfigPath: flags.configPath,
+ ProxyServer: flags.proxyAddr,
+ Endpoints: flags.clusterEndpoints,
+ ResolverLogger: rpcLogger,
}
}
diff --git a/metropolis/cli/metroctl/rpc.go b/metropolis/cli/metroctl/rpc.go
index 6d7beab..f1c27e6 100644
--- a/metropolis/cli/metroctl/rpc.go
+++ b/metropolis/cli/metroctl/rpc.go
@@ -2,12 +2,15 @@
import (
"context"
+ "crypto/tls"
"crypto/x509"
"log"
"google.golang.org/grpc"
"source.monogon.dev/metropolis/cli/metroctl/core"
+ "source.monogon.dev/metropolis/node/core/rpc"
+ "source.monogon.dev/metropolis/node/core/rpc/resolver"
)
func dialAuthenticated(ctx context.Context) *grpc.ClientConn {
@@ -20,7 +23,19 @@
if len(flags.clusterEndpoints) == 0 {
log.Fatal("Please provide at least one cluster endpoint using the --endpoint parameter.")
}
- cc, err := core.DialCluster(ctx, opkey, ocert, flags.proxyAddr, flags.clusterEndpoints, rpcLogger)
+ tlsc := tls.Certificate{
+ Certificate: [][]byte{ocert.Raw},
+ PrivateKey: opkey,
+ }
+ // TODO(q3k): check remote CA
+ creds := rpc.NewAuthenticatedCredentials(tlsc, rpc.WantInsecure())
+ opts, err := core.DialOpts(ctx, connectOptions())
+ if err != nil {
+ log.Fatalf("While configuring dial options: %v", err)
+ }
+ opts = append(opts, grpc.WithTransportCredentials(creds))
+
+ cc, err := grpc.Dial(resolver.MetropolisControlAddress, opts...)
if err != nil {
log.Fatalf("While dialing the cluster: %v", err)
}