Andrey Smirnov 5b7bea2471 feat: use grpc-proxy in apid
This replaces codegen version of apid proxying with
talos-systems/grpc-proxy based version. Proxying is transparent, it
doesn't require exact information about methods and response types. It
requires some common layout response to enhance it properly with node
metadata or errors.

There should be no signifcant changes to the API with the previous
version, but it's worth mentioning a few changes:

1. grpc.ClientConn is established just once per upstream (either local
service or remote apid instance).

2. When called without `-t` (`targets`), apid proxies immediately down
to local service skipping proxying to itself (as before), which results
in empty node metadata in response (before it had local node IP). Might
revert this later to proxy to itself (?).

3. Streaming APIs are now fully supported with multiple targets, but
message definition doesn't contain `ResponseMetadata`, so streaming APIs
are broken now with targets (needs a fix).

4. Errors are now returned as responses with `Error` field set in
`ResponseMetadata`, this requires client library update and `osctl` to
handle it properly.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
2019-11-29 22:57:25 +03:00

154 lines
4.1 KiB
Go

// 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 main
import (
"flag"
"log"
stdlibnet "net"
"os"
"regexp"
"strings"
"github.com/talos-systems/grpc-proxy/proxy"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/talos-systems/talos/internal/app/apid/pkg/backend"
"github.com/talos-systems/talos/internal/app/apid/pkg/director"
"github.com/talos-systems/talos/pkg/config"
"github.com/talos-systems/talos/pkg/constants"
"github.com/talos-systems/talos/pkg/grpc/factory"
"github.com/talos-systems/talos/pkg/grpc/tls"
"github.com/talos-systems/talos/pkg/net"
"github.com/talos-systems/talos/pkg/startup"
)
var (
configPath *string
endpoints *string
)
func init() {
log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds | log.Ltime)
configPath = flag.String("config", "", "the path to the config")
endpoints = flag.String("endpoints", "", "the IPs of the control plane nodes")
flag.Parse()
}
func main() {
if err := startup.RandSeed(); err != nil {
log.Fatalf("failed to seed RNG: %v", err)
}
provider, err := createProvider()
if err != nil {
log.Fatalf("failed to create remote certificate provider: %+v", err)
}
ca, err := provider.GetCA()
if err != nil {
log.Fatalf("failed to get root CA: %+v", err)
}
tlsConfig, err := tls.New(
tls.WithClientAuthType(tls.Mutual),
tls.WithCACertPEM(ca),
tls.WithCertificateProvider(provider),
)
if err != nil {
log.Fatalf("failed to create OS-level TLS configuration: %v", err)
}
// TODO: refactor
certs, err := provider.GetCertificate(nil)
if err != nil {
log.Fatalf("failed to get TLS certs: %v", err)
}
clientTLSConfig, err := tls.New(
tls.WithClientAuthType(tls.Mutual),
tls.WithCACertPEM(ca),
tls.WithKeypair(*certs), // TODO: this doesn't support cert refresh, fix me!
)
if err != nil {
log.Fatalf("failed to create client TLS config: %v", err)
}
backendFactory := backend.NewAPIDFactory(clientTLSConfig)
router := director.NewRouter(backendFactory.Get)
router.RegisterLocalBackend("os.OS", backend.NewLocal("osd", constants.OSSocketPath))
router.RegisterLocalBackend("machine.Machine", backend.NewLocal("machined", constants.MachineSocketPath))
router.RegisterLocalBackend("time.Time", backend.NewLocal("timed", constants.TimeSocketPath))
router.RegisterLocalBackend("network.Network", backend.NewLocal("networkd", constants.NetworkSocketPath))
// all existing streaming methods
for _, methodName := range []string{
"/machine.Machine/CopyOut",
"/machine.Machine/Kubeconfig",
"/machine.Machine/LS",
"/machine.Machine/Logs",
"/machine.Machine/Read",
} {
router.RegisterStreamedRegex("^" + regexp.QuoteMeta(methodName) + "$")
}
// register future pattern: method should have suffix "Stream"
router.RegisterStreamedRegex("Stream$")
err = factory.ListenAndServe(
router,
factory.Port(constants.ApidPort),
factory.WithDefaultLog(),
factory.ServerOptions(
grpc.Creds(
credentials.NewTLS(tlsConfig),
),
grpc.CustomCodec(proxy.Codec()),
grpc.UnknownServiceHandler(
proxy.TransparentHandler(
router.Director,
proxy.WithStreamedDetector(router.StreamedDetector),
)),
),
)
if err != nil {
log.Fatalf("listen: %v", err)
}
}
func createProvider() (tls.CertificateProvider, error) {
content, err := config.FromFile(*configPath)
if err != nil {
log.Fatalf("open config: %v", err)
}
config, err := config.New(content)
if err != nil {
log.Fatalf("open config: %v", err)
}
ips, err := net.IPAddrs()
if err != nil {
log.Fatalf("failed to discover IP addresses: %+v", err)
}
// TODO(andrewrynhard): Allow for DNS names.
for _, san := range config.Machine().Security().CertSANs() {
if ip := stdlibnet.ParseIP(san); ip != nil {
ips = append(ips, ip)
}
}
hostname, err := os.Hostname()
if err != nil {
log.Fatalf("failed to discover hostname: %+v", err)
}
return tls.NewRemoteRenewingFileCertificateProvider(config.Machine().Security().Token(), strings.Split(*endpoints, ","), constants.TrustdPort, hostname, ips)
}