Skip to content

Commit

Permalink
fix: use grpc load-balancing when connecting to trustd
Browse files Browse the repository at this point in the history
Instead of doing our homegrown "try all the endpoints" method,
use gRPC load-balancing across configured endpoints.

Generalize load-balancer via gRPC resolver we had in Talos API client,
use it in remote certificate generator code. Generalized resolver is
still under `machinery/`, as `pkg/grpc` is not in `machinery/`, and we
can't depend on Talos code from `machinery/`.

Related to: #3068

Full fix for #3068 requires dynamic updates to control plane endpoints
while apid is running, this is coming in the next PR.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
  • Loading branch information
smira authored and talos-bot committed Feb 2, 2021
1 parent 6eafca0 commit 389349c
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 110 deletions.
2 changes: 0 additions & 2 deletions internal/app/apid/pkg/provider/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (

"github.com/talos-systems/talos/pkg/grpc/gen"
"github.com/talos-systems/talos/pkg/machinery/config"
"github.com/talos-systems/talos/pkg/machinery/constants"
)

// TLSConfig provides client & server TLS configs for apid.
Expand Down Expand Up @@ -46,7 +45,6 @@ func NewTLSConfig(config config.Provider, endpoints []string) (*TLSConfig, error
generator, err := gen.NewRemoteGenerator(
config.Machine().Security().Token(),
endpoints,
constants.TrustdPort,
)
if err != nil {
return nil, fmt.Errorf("failed to create remote certificate genertor: %w", err)
Expand Down
49 changes: 22 additions & 27 deletions pkg/grpc/gen/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,24 @@ import (
"context"
"fmt"
"log"
"strings"
"time"

"github.com/hashicorp/go-multierror"
"github.com/talos-systems/crypto/x509"
"google.golang.org/grpc"

"github.com/talos-systems/talos/pkg/grpc/middleware/auth/basic"
securityapi "github.com/talos-systems/talos/pkg/machinery/api/security"
"github.com/talos-systems/talos/pkg/machinery/client/resolver"
"github.com/talos-systems/talos/pkg/machinery/constants"
)

var trustdResolverScheme string

func init() {
trustdResolverScheme = resolver.RegisterRoundRobinResolver(constants.TrustdPort)
}

// RemoteGenerator represents the OS identity generator.
type RemoteGenerator struct {
client securityapi.SecurityServiceClient
Expand All @@ -26,46 +34,33 @@ type RemoteGenerator struct {
}

// NewRemoteGenerator initializes a RemoteGenerator with a preconfigured grpc.ClientConn.
func NewRemoteGenerator(token string, endpoints []string, port int) (g *RemoteGenerator, err error) {
func NewRemoteGenerator(token string, endpoints []string) (g *RemoteGenerator, err error) {
if len(endpoints) == 0 {
return nil, fmt.Errorf("at least one root of trust endpoint is required")
}

creds := basic.NewTokenCredentials(token)

// Loop through trustd endpoints and attempt to download PKI
var (
conn *grpc.ClientConn
multiError *multierror.Error
)

for i := 0; i < len(endpoints); i++ {
conn, err = basic.NewConnection(endpoints[i], port, creds)
if err != nil {
multiError = multierror.Append(multiError, err)
// Unable to connect, bail and attempt to contact next endpoint
continue
}

client := securityapi.NewSecurityServiceClient(conn)
conn, err := basic.NewConnection(fmt.Sprintf("%s:///%s", trustdResolverScheme, strings.Join(endpoints, ",")), creds)
if err != nil {
return nil, err
}

g := &RemoteGenerator{
client: client,
conn: conn,
done: make(chan struct{}),
}
client := securityapi.NewSecurityServiceClient(conn)

return g, nil
g = &RemoteGenerator{
client: client,
conn: conn,
done: make(chan struct{}),
}

// We were unable to connect to any trustd endpoint
// Return error from last attempt.
return nil, multiError.ErrorOrNil()
return g, nil
}

// Certificate implements the securityapi.SecurityClient interface.
func (g *RemoteGenerator) Certificate(in *securityapi.CertificateRequest) (resp *securityapi.CertificateResponse, err error) {
ctx := context.Background()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

resp, err = g.client.Certificate(ctx, in)
if err != nil {
Expand Down
6 changes: 2 additions & 4 deletions pkg/grpc/middleware/auth/basic/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ package basic

import (
"crypto/tls"
"fmt"

"github.com/talos-systems/net"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
Expand All @@ -22,7 +20,7 @@ type Credentials interface {

// NewConnection initializes a grpc.ClientConn configured for basic
// authentication.
func NewConnection(address string, port int, creds credentials.PerRPCCredentials) (conn *grpc.ClientConn, err error) {
func NewConnection(address string, creds credentials.PerRPCCredentials) (conn *grpc.ClientConn, err error) {
grpcOpts := []grpc.DialOption{}

grpcOpts = append(
Expand All @@ -34,7 +32,7 @@ func NewConnection(address string, port int, creds credentials.PerRPCCredentials
grpc.WithPerRPCCredentials(creds),
)

conn, err = grpc.Dial(fmt.Sprintf("%s:%d", net.FormatAddress(address), port), grpcOpts...)
conn, err = grpc.Dial(address, grpcOpts...)
if err != nil {
return
}
Expand Down
81 changes: 4 additions & 77 deletions pkg/machinery/client/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,85 +5,12 @@
package client

import (
"fmt"
"math/rand"
"strings"

"github.com/talos-systems/net"
"google.golang.org/grpc/resolver"

"github.com/talos-systems/talos/pkg/machinery/client/resolver"
"github.com/talos-systems/talos/pkg/machinery/constants"
)

func init() {
resolver.Register(&talosListResolverBuilder{})
}

const talosListResolverScheme = "taloslist"

type talosListResolverBuilder struct{}

// Build implements resolver.Builder.
func (b *talosListResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
r := &talosListResolver{
target: target,
cc: cc,
}

if err := r.start(); err != nil {
return nil, err
}

return r, nil
}

// Build implements resolver.Builder.
func (b *talosListResolverBuilder) Scheme() string {
return talosListResolverScheme
}

type talosListResolver struct {
target resolver.Target
cc resolver.ClientConn
}

func (r *talosListResolver) start() error {
var addrs []resolver.Address // nolint: prealloc

for _, a := range strings.Split(r.target.Endpoint, ",") {
addrs = append(addrs, resolver.Address{
ServerName: a,
Addr: fmt.Sprintf("%s:%d", net.FormatAddress(a), constants.ApidPort),
})
}

// shuffle the list in case client does just one request
rand.Shuffle(len(addrs), func(i, j int) {
addrs[i], addrs[j] = addrs[j], addrs[i]
})

serviceConfigJSON := `{
"loadBalancingConfig": [{
"round_robin": {}
}]
}`
var talosListResolverScheme string

parsedServiceConfig := r.cc.ParseServiceConfig(serviceConfigJSON)

if parsedServiceConfig.Err != nil {
return parsedServiceConfig.Err
}

r.cc.UpdateState(resolver.State{
Addresses: addrs,
ServiceConfig: parsedServiceConfig,
})

return nil
func init() {
talosListResolverScheme = resolver.RegisterRoundRobinResolver(constants.ApidPort)
}

// ResolveNow implements resolver.Resolver.
func (r *talosListResolver) ResolveNow(o resolver.ResolveNowOptions) {}

// ResolveNow implements resolver.Resolver.
func (r *talosListResolver) Close() {}
6 changes: 6 additions & 0 deletions pkg/machinery/client/resolver/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package resolver implements gRPC resolvers.
package resolver
100 changes: 100 additions & 0 deletions pkg/machinery/client/resolver/roundrobin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package resolver

import (
"fmt"
"math/rand"
"strings"

"github.com/talos-systems/net"
"google.golang.org/grpc/resolver"
)

// RegisterRoundRobinResolver registers round-robin gRPC resolver for specified port and returns scheme to use in grpc.Dial.
func RegisterRoundRobinResolver(port int) (scheme string) {
scheme = fmt.Sprintf(roundRobinResolverScheme, port)

resolver.Register(&roundRobinResolverBuilder{
port: port,
scheme: scheme,
})

return
}

const roundRobinResolverScheme = "taloslist-%d"

type roundRobinResolverBuilder struct {
port int
scheme string
}

// Build implements resolver.Builder.
func (b *roundRobinResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
r := &roundRobinResolver{
target: target,
cc: cc,
port: b.port,
}

if err := r.start(); err != nil {
return nil, err
}

return r, nil
}

// Build implements resolver.Builder.
func (b *roundRobinResolverBuilder) Scheme() string {
return b.scheme
}

type roundRobinResolver struct {
target resolver.Target
cc resolver.ClientConn
port int
}

func (r *roundRobinResolver) start() error {
var addrs []resolver.Address // nolint: prealloc

for _, a := range strings.Split(r.target.Endpoint, ",") {
addrs = append(addrs, resolver.Address{
ServerName: a,
Addr: fmt.Sprintf("%s:%d", net.FormatAddress(a), r.port),
})
}

// shuffle the list in case client does just one request
rand.Shuffle(len(addrs), func(i, j int) {
addrs[i], addrs[j] = addrs[j], addrs[i]
})

serviceConfigJSON := `{
"loadBalancingConfig": [{
"round_robin": {}
}]
}`

parsedServiceConfig := r.cc.ParseServiceConfig(serviceConfigJSON)

if parsedServiceConfig.Err != nil {
return parsedServiceConfig.Err
}

r.cc.UpdateState(resolver.State{
Addresses: addrs,
ServiceConfig: parsedServiceConfig,
})

return nil
}

// ResolveNow implements resolver.Resolver.
func (r *roundRobinResolver) ResolveNow(o resolver.ResolveNowOptions) {}

// ResolveNow implements resolver.Resolver.
func (r *roundRobinResolver) Close() {}

0 comments on commit 389349c

Please sign in to comment.