mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-05 12:26:21 +02:00
refactor: decouple grpc client and userdata code
This detangles the gRPC client code from the userdata code. The motivation behind this is to make creating clients more simple and not dependent on our configuration format. Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
This commit is contained in:
parent
607d68008c
commit
6ec5cb02cb
@ -191,7 +191,7 @@ func init() {
|
||||
// Certificate Authorities
|
||||
caCmd.Flags().StringVar(&organization, "organization", "", "X.509 distinguished name for the Organization")
|
||||
helpers.Should(cobra.MarkFlagRequired(caCmd.Flags(), "organization"))
|
||||
caCmd.Flags().IntVar(&hours, "hours", 24, "the hours from now on which the certificate validity period ends")
|
||||
caCmd.Flags().IntVar(&hours, "hours", 87600, "the hours from now on which the certificate validity period ends")
|
||||
caCmd.Flags().BoolVar(&rsa, "rsa", false, "generate in RSA format")
|
||||
// Keys
|
||||
keyCmd.Flags().StringVar(&name, "name", "", "the basename of the generated file")
|
||||
|
||||
@ -36,19 +36,6 @@ var injectOSCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
// injectIdentityCmd represents the inject command
|
||||
// nolint: dupl
|
||||
var injectIdentityCmd = &cobra.Command{
|
||||
Use: "identity",
|
||||
Short: "inject identity data.",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := inject(args, crt, key, injectIdentityData); err != nil {
|
||||
helpers.Fatalf("%s", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// injectKubernetesCmd represents the inject command
|
||||
// nolint: dupl
|
||||
var injectKubernetesCmd = &cobra.Command{
|
||||
@ -76,20 +63,6 @@ func injectOSData(u *userdata.UserData, crt, key string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func injectIdentityData(u *userdata.UserData, crt, key string) (err error) {
|
||||
if u.Security == nil {
|
||||
u.Security = newSecurity()
|
||||
}
|
||||
crtAndKey, err := x509.NewCertificateAndKeyFromFiles(crt, key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
u.Security.OS.Identity = crtAndKey
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func injectKubernetesData(u *userdata.UserData, crt, key string) (err error) {
|
||||
if u.Security == nil {
|
||||
@ -144,19 +117,14 @@ func newSecurity() *userdata.Security {
|
||||
|
||||
func init() {
|
||||
injectOSCmd.Flags().StringVar(&crt, "crt", "", "the path to the PKI certificate")
|
||||
injectIdentityCmd.Flags().StringVar(&crt, "crt", "", "the path to the PKI certificate")
|
||||
injectKubernetesCmd.Flags().StringVar(&crt, "crt", "", "the path to the PKI certificate")
|
||||
helpers.Should(injectOSCmd.MarkFlagRequired("crt"))
|
||||
helpers.Should(injectIdentityCmd.MarkFlagRequired("crt"))
|
||||
helpers.Should(injectKubernetesCmd.MarkFlagRequired("crt"))
|
||||
|
||||
injectOSCmd.Flags().StringVar(&key, "key", "", "the path to the PKI key")
|
||||
injectIdentityCmd.Flags().StringVar(&key, "key", "", "the path to the PKI key")
|
||||
injectKubernetesCmd.Flags().StringVar(&key, "key", "", "the path to the PKI key")
|
||||
helpers.Should(injectOSCmd.MarkFlagRequired("key"))
|
||||
helpers.Should(injectIdentityCmd.MarkFlagRequired("key"))
|
||||
helpers.Should(injectKubernetesCmd.MarkFlagRequired("key"))
|
||||
|
||||
injectCmd.AddCommand(injectOSCmd, injectIdentityCmd, injectKubernetesCmd)
|
||||
rootCmd.AddCommand(injectCmd)
|
||||
}
|
||||
|
||||
@ -101,14 +101,11 @@ func Execute() {
|
||||
|
||||
// setupClient wraps common code to initialize osd client
|
||||
func setupClient(action func(*client.Client)) {
|
||||
creds, err := client.NewDefaultClientCredentials(talosconfig)
|
||||
t, creds, err := client.NewClientTargetAndCredentialsFromConfig(talosconfig)
|
||||
if err != nil {
|
||||
helpers.Fatalf("error getting client credentials: %s", err)
|
||||
}
|
||||
if target != "" {
|
||||
creds.Target = target
|
||||
}
|
||||
c, err := client.NewClient(constants.OsdPort, creds)
|
||||
c, err := client.NewClient(creds, t, constants.OsdPort)
|
||||
if err != nil {
|
||||
helpers.Fatalf("error constructing client: %s", err)
|
||||
}
|
||||
|
||||
@ -30,10 +30,9 @@ import (
|
||||
// Credentials represents the set of values required to initialize a vaild
|
||||
// Client.
|
||||
type Credentials struct {
|
||||
Target string
|
||||
ca []byte
|
||||
crt []byte
|
||||
key []byte
|
||||
ca []byte
|
||||
crt []byte
|
||||
key []byte
|
||||
}
|
||||
|
||||
// Client implements the proto.OSClient interface. It serves as the
|
||||
@ -46,70 +45,84 @@ type Client struct {
|
||||
NetworkClient networkapi.NetworkClient
|
||||
}
|
||||
|
||||
// NewDefaultClientCredentials initializes ClientCredentials using default paths
|
||||
// NewClientTargetAndCredentialsFromConfig initializes ClientCredentials using default paths
|
||||
// to the required CA, certificate, and key.
|
||||
func NewDefaultClientCredentials(p string) (creds *Credentials, err error) {
|
||||
func NewClientTargetAndCredentialsFromConfig(p string) (target string, creds *Credentials, err error) {
|
||||
c, err := config.Open(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.Context == "" {
|
||||
return nil, fmt.Errorf("'context' key is not set in the config")
|
||||
return "", nil, fmt.Errorf("'context' key is not set in the config")
|
||||
}
|
||||
|
||||
context := c.Contexts[c.Context]
|
||||
if context == nil {
|
||||
return nil, fmt.Errorf("context %q is not defined in 'contexts' key in config", c.Context)
|
||||
return "", nil, fmt.Errorf("context %q is not defined in 'contexts' key in config", c.Context)
|
||||
}
|
||||
|
||||
caBytes, err := base64.StdEncoding.DecodeString(context.CA)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
crtBytes, err := base64.StdEncoding.DecodeString(context.Crt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
keyBytes, err := base64.StdEncoding.DecodeString(context.Key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
creds = &Credentials{
|
||||
Target: context.Target,
|
||||
ca: caBytes,
|
||||
crt: crtBytes,
|
||||
key: keyBytes,
|
||||
return "", nil, fmt.Errorf("error decoding CA: %v", err)
|
||||
}
|
||||
|
||||
return creds, nil
|
||||
crtBytes, err := base64.StdEncoding.DecodeString(context.Crt)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("error decoding certificate: %v", err)
|
||||
}
|
||||
|
||||
keyBytes, err := base64.StdEncoding.DecodeString(context.Key)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("error decoding key: %v", err)
|
||||
}
|
||||
|
||||
creds = &Credentials{
|
||||
ca: caBytes,
|
||||
crt: crtBytes,
|
||||
key: keyBytes,
|
||||
}
|
||||
|
||||
return context.Target, creds, nil
|
||||
}
|
||||
|
||||
// NewClientCredentials initializes ClientCredentials using default paths
|
||||
// to the required CA, certificate, and key.
|
||||
func NewClientCredentials(ca, crt, key []byte) (creds *Credentials) {
|
||||
creds = &Credentials{
|
||||
ca: ca,
|
||||
crt: crt,
|
||||
key: key,
|
||||
}
|
||||
|
||||
return creds
|
||||
}
|
||||
|
||||
// NewClient initializes a Client.
|
||||
func NewClient(port int, clientcreds *Credentials) (c *Client, err error) {
|
||||
func NewClient(creds *Credentials, target string, port int) (c *Client, err error) {
|
||||
grpcOpts := []grpc.DialOption{}
|
||||
|
||||
c = &Client{}
|
||||
crt, err := tls.X509KeyPair(clientcreds.crt, clientcreds.key)
|
||||
crt, err := tls.X509KeyPair(creds.crt, creds.key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load client key pair: %s", err)
|
||||
}
|
||||
certPool := x509.NewCertPool()
|
||||
if ok := certPool.AppendCertsFromPEM(clientcreds.ca); !ok {
|
||||
if ok := certPool.AppendCertsFromPEM(creds.ca); !ok {
|
||||
return nil, fmt.Errorf("failed to append client certs")
|
||||
}
|
||||
// TODO(andrewrynhard): Do not parse the address. Pass the IP and port in as separate
|
||||
// parameters.
|
||||
creds := credentials.NewTLS(&tls.Config{
|
||||
ServerName: clientcreds.Target,
|
||||
transportCreds := credentials.NewTLS(&tls.Config{
|
||||
ServerName: target,
|
||||
Certificates: []tls.Certificate{crt},
|
||||
// Set the root certificate authorities to use the self-signed
|
||||
// certificate.
|
||||
RootCAs: certPool,
|
||||
})
|
||||
|
||||
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(creds))
|
||||
c.conn, err = grpc.Dial(fmt.Sprintf("%s:%d", net.FormatAddress(clientcreds.Target), port), grpcOpts...)
|
||||
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(transportCreds))
|
||||
c.conn, err = grpc.Dial(fmt.Sprintf("%s:%d", net.FormatAddress(target), port), grpcOpts...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@ -1,61 +0,0 @@
|
||||
/* 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 userdata
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||
"github.com/talos-systems/talos/internal/pkg/platform"
|
||||
"github.com/talos-systems/talos/internal/pkg/runtime"
|
||||
"github.com/talos-systems/talos/pkg/constants"
|
||||
"github.com/talos-systems/talos/pkg/crypto/x509"
|
||||
"github.com/talos-systems/talos/pkg/grpc/gen"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
// PKI represents the PKI task.
|
||||
type PKI struct{}
|
||||
|
||||
// NewPKITask initializes and returns an UserData task.
|
||||
func NewPKITask() phase.Task {
|
||||
return &PKI{}
|
||||
}
|
||||
|
||||
// RuntimeFunc returns the runtime function.
|
||||
func (task *PKI) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
||||
return task.runtime
|
||||
}
|
||||
|
||||
func (task *PKI) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||
if data.Services.Kubeadm.IsControlPlane() {
|
||||
log.Println("generating PKI locally")
|
||||
var csr *x509.CertificateSigningRequest
|
||||
if csr, err = data.NewIdentityCSR(); err != nil {
|
||||
return err
|
||||
}
|
||||
var crt *x509.Certificate
|
||||
crt, err = x509.NewCertificateFromCSRBytes(data.Security.OS.CA.Crt, data.Security.OS.CA.Key, csr.X509CertificateRequestPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data.Security.OS.Identity.Crt = crt.X509CertificatePEM
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Println("generating PKI from trustd")
|
||||
var generator *gen.Generator
|
||||
generator, err = gen.NewGenerator(data, constants.TrustdPort)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create trustd client")
|
||||
}
|
||||
if err = generator.Identity(data); err != nil {
|
||||
return errors.Wrap(err, "failed to generate identity")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -64,7 +64,6 @@ func (d *Sequencer) Boot() error {
|
||||
),
|
||||
phase.NewPhase(
|
||||
"user requests",
|
||||
userdatatask.NewPKITask(),
|
||||
userdatatask.NewExtraEnvVarsTask(),
|
||||
userdatatask.NewExtraFilesTask(),
|
||||
),
|
||||
|
||||
@ -221,18 +221,12 @@ func FileSet(files []string) []*securityapi.ReadFileRequest {
|
||||
return fileRequests
|
||||
}
|
||||
|
||||
// CreateSecurityClients handles instantiating a trustd client connection
|
||||
// CreateSecurityClients handles instantiating a security API client connection
|
||||
// to each trustd endpoint defined in userdata
|
||||
func CreateSecurityClients(data *userdata.UserData) ([]securityapi.SecurityClient, error) {
|
||||
var creds basic.Credentials
|
||||
var err error
|
||||
func CreateSecurityClients(data *userdata.UserData) (clients []securityapi.SecurityClient, err error) {
|
||||
clients = []securityapi.SecurityClient{}
|
||||
|
||||
trustds := []securityapi.SecurityClient{}
|
||||
|
||||
creds, err = basic.NewCredentials(data.Services.Trustd)
|
||||
if err != nil {
|
||||
return trustds, err
|
||||
}
|
||||
creds := basic.NewTokenCredentials(data.Services.Trustd.Token)
|
||||
|
||||
// Create a trustd client for each endpoint to set up
|
||||
// a fan out approach to gathering the files
|
||||
@ -240,14 +234,14 @@ func CreateSecurityClients(data *userdata.UserData) ([]securityapi.SecurityClien
|
||||
for _, endpoint := range data.Services.Trustd.Endpoints {
|
||||
conn, err = basic.NewConnection(endpoint, constants.TrustdPort, creds)
|
||||
if err != nil {
|
||||
return trustds, err
|
||||
return clients, err
|
||||
}
|
||||
trustds = append(trustds, securityapi.NewSecurityClient(conn))
|
||||
clients = append(clients, securityapi.NewSecurityClient(conn))
|
||||
}
|
||||
return trustds, nil
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
// Download handles the retrieval of files from a trustd endpoint
|
||||
// Download handles the retrieval of files from a security API endpoint.
|
||||
func Download(ctx context.Context, client securityapi.SecurityClient, file *securityapi.ReadFileRequest, content chan<- []byte) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
||||
@ -5,9 +5,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
stdlibnet "net"
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
@ -16,6 +17,7 @@ import (
|
||||
"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"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
@ -28,9 +30,10 @@ func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func main() {
|
||||
if err := startup.RandSeed(); err != nil {
|
||||
log.Fatalf("startup: %s", err)
|
||||
log.Fatalf("failed to seed RNG: %s", err)
|
||||
}
|
||||
|
||||
data, err := userdata.Open(*dataPath)
|
||||
@ -38,40 +41,63 @@ func main() {
|
||||
log.Fatalf("open user data: %v", err)
|
||||
}
|
||||
|
||||
tlsCertProvider, err := tls.NewRenewingFileCertificateProvider(context.TODO(), data)
|
||||
ips, err := net.IPAddrs()
|
||||
if err != nil {
|
||||
log.Fatalln("failed to create new dynamic certificate provider:", err)
|
||||
log.Fatalf("failed to discover IP addresses: %+v", err)
|
||||
}
|
||||
config, err := tls.NewConfigWithOpts(
|
||||
// TODO(andrewrynhard): Allow for DNS names.
|
||||
for _, san := range data.Services.Trustd.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)
|
||||
}
|
||||
|
||||
var provider tls.CertificateProvider
|
||||
provider, err = tls.NewRemoteRenewingFileCertificateProvider(data.Services.Trustd.Token, data.Services.Trustd.Endpoints, constants.TrustdPort, hostname, ips)
|
||||
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)
|
||||
}
|
||||
|
||||
config, err := tls.New(
|
||||
tls.WithClientAuthType(tls.Mutual),
|
||||
tls.WithCACertPEM(data.Security.OS.CA.Crt),
|
||||
tls.WithCertificateProvider(tlsCertProvider))
|
||||
tls.WithCACertPEM(ca),
|
||||
tls.WithCertificateProvider(provider),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create OS-level TLS configuration: %v", err)
|
||||
}
|
||||
|
||||
MachineClient, err := reg.NewMachineClient()
|
||||
machineClient, err := reg.NewMachineClient()
|
||||
if err != nil {
|
||||
log.Fatalf("init client: %v", err)
|
||||
}
|
||||
|
||||
TimeClient, err := reg.NewTimeClient()
|
||||
timeClient, err := reg.NewTimeClient()
|
||||
if err != nil {
|
||||
log.Fatalf("ntp client: %v", err)
|
||||
}
|
||||
|
||||
NetworkClient, err := reg.NewNetworkClient()
|
||||
networkClient, err := reg.NewNetworkClient()
|
||||
if err != nil {
|
||||
log.Fatalf("networkd client: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Starting osd")
|
||||
err = factory.ListenAndServe(
|
||||
®.Registrator{
|
||||
Data: data,
|
||||
MachineClient: MachineClient,
|
||||
TimeClient: TimeClient,
|
||||
NetworkClient: NetworkClient,
|
||||
MachineClient: machineClient,
|
||||
TimeClient: timeClient,
|
||||
NetworkClient: networkClient,
|
||||
},
|
||||
factory.Port(constants.OsdPort),
|
||||
factory.ServerOptions(
|
||||
|
||||
@ -7,6 +7,8 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
stdlibnet "net"
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
@ -16,6 +18,7 @@ import (
|
||||
"github.com/talos-systems/talos/pkg/grpc/factory"
|
||||
"github.com/talos-systems/talos/pkg/grpc/middleware/auth/basic"
|
||||
"github.com/talos-systems/talos/pkg/grpc/tls"
|
||||
"github.com/talos-systems/talos/pkg/net"
|
||||
"github.com/talos-systems/talos/pkg/startup"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
@ -28,6 +31,7 @@ func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
@ -37,18 +41,45 @@ func main() {
|
||||
|
||||
data, err := userdata.Open(*dataPath)
|
||||
if err != nil {
|
||||
log.Fatalf("credentials: %v", err)
|
||||
log.Fatalf("failed to open machine config: %v", err)
|
||||
}
|
||||
|
||||
config, err := tls.NewConfig(tls.ServerOnly, data.Security.OS)
|
||||
if err != nil {
|
||||
log.Fatalf("credentials: %v", err)
|
||||
}
|
||||
|
||||
creds, err := basic.NewCredentials(data.Services.Trustd)
|
||||
ips, err := net.IPAddrs()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, san := range data.Services.Trustd.CertSANs {
|
||||
if ip := stdlibnet.ParseIP(san); ip != nil {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var provider tls.CertificateProvider
|
||||
provider, err = tls.NewLocalRenewingFileCertificateProvider(data.Security.OS.CA.Key, data.Security.OS.CA.Crt, hostname, ips)
|
||||
if err != nil {
|
||||
log.Fatalln("failed to create local certificate provider:", err)
|
||||
}
|
||||
|
||||
ca, err := provider.GetCA()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
config, err := tls.New(
|
||||
tls.WithClientAuthType(tls.ServerOnly),
|
||||
tls.WithCACertPEM(ca),
|
||||
tls.WithCertificateProvider(provider),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create TLS config: %v", err)
|
||||
}
|
||||
|
||||
creds := basic.NewTokenCredentials(data.Services.Trustd.Token)
|
||||
|
||||
err = factory.ListenAndServe(
|
||||
®.Registrator{Data: data.Security.OS},
|
||||
|
||||
@ -23,8 +23,7 @@ func (i *ISO) UserData() (data *userdata.UserData, err error) {
|
||||
data = &userdata.UserData{
|
||||
Security: &userdata.Security{
|
||||
OS: &userdata.OSSecurity{
|
||||
CA: &x509.PEMEncodedCertificateAndKey{},
|
||||
Identity: &x509.PEMEncodedCertificateAndKey{},
|
||||
CA: &x509.PEMEncodedCertificateAndKey{},
|
||||
},
|
||||
Kubernetes: &userdata.KubernetesSecurity{
|
||||
CA: &x509.PEMEncodedCertificateAndKey{},
|
||||
|
||||
@ -175,11 +175,8 @@ const (
|
||||
// RootfsAsset defines a well known name for our rootfs filename
|
||||
RootfsAsset = "rootfs.sqsh"
|
||||
|
||||
// NodeCertFile is the filename where the current Talos Node Certificate may be found
|
||||
NodeCertFile = SystemRunPath + "/talos-node.crt"
|
||||
|
||||
// NodeCertRenewalInterval is the default interval at which Talos Node Certifications should be renewed
|
||||
NodeCertRenewalInterval = 24 * time.Hour
|
||||
// DefaultCertificateValidityDuration is the default duration for a certificate.
|
||||
DefaultCertificateValidityDuration = 24 * time.Hour
|
||||
|
||||
// SystemVarPath is the path to write runtime system related files and
|
||||
// directories.
|
||||
|
||||
@ -22,6 +22,8 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/constants"
|
||||
)
|
||||
|
||||
// CertificateAuthority represents a CA.
|
||||
@ -136,7 +138,7 @@ func NewDefaultOptions(setters ...Option) *Options {
|
||||
DNSNames: []string{},
|
||||
Bits: 4096,
|
||||
RSA: false,
|
||||
NotAfter: time.Now().Add(24 * time.Hour),
|
||||
NotAfter: time.Now().Add(constants.DefaultCertificateValidityDuration),
|
||||
}
|
||||
|
||||
for _, setter := range setters {
|
||||
@ -385,6 +387,41 @@ func NewCertificateAndKeyFromFiles(crt, key string) (p *PEMEncodedCertificateAnd
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// NewCSRAndIdentity generates and PEM encoded certificate and key, along with a
|
||||
// CSR for the generated key.
|
||||
func NewCSRAndIdentity(hostname string, ips []net.IP) (csr *CertificateSigningRequest, identity *PEMEncodedCertificateAndKey, err error) {
|
||||
var key *Key
|
||||
key, err = NewKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
identity = &PEMEncodedCertificateAndKey{
|
||||
Key: key.KeyPEM,
|
||||
}
|
||||
|
||||
pemBlock, _ := pem.Decode(key.KeyPEM)
|
||||
if pemBlock == nil {
|
||||
return nil, nil, fmt.Errorf("failed to decode key")
|
||||
}
|
||||
|
||||
keyEC, err := x509.ParseECPrivateKey(pemBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
opts := []Option{}
|
||||
opts = append(opts, DNSNames([]string{hostname}))
|
||||
opts = append(opts, IPAddresses(ips))
|
||||
|
||||
csr, err = NewCertificateSigningRequest(keyEC, opts...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return csr, identity, nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface for
|
||||
// PEMEncodedCertificateAndKey. It is expected that the Crt and Key are a base64
|
||||
// encoded string in the YAML file. This function decodes the strings into byte
|
||||
|
||||
35
pkg/grpc/gen/local.go
Normal file
35
pkg/grpc/gen/local.go
Normal file
@ -0,0 +1,35 @@
|
||||
/* 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 gen
|
||||
|
||||
import (
|
||||
"github.com/talos-systems/talos/pkg/crypto/x509"
|
||||
)
|
||||
|
||||
// LocalGenerator represents the OS identity generator.
|
||||
type LocalGenerator struct {
|
||||
caKey []byte
|
||||
caCrt []byte
|
||||
}
|
||||
|
||||
// NewLocalGenerator initializes a LocalGenerator.
|
||||
func NewLocalGenerator(caKey, caCrt []byte) (g *LocalGenerator, err error) {
|
||||
g = &LocalGenerator{caKey, caCrt}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// Identity creates an identity certificate using a local root CA.
|
||||
func (g *LocalGenerator) Identity(csr *x509.CertificateSigningRequest) (ca, crt []byte, err error) {
|
||||
var c *x509.Certificate
|
||||
c, err = x509.NewCertificateFromCSRBytes(g.caCrt, g.caKey, csr.X509CertificateRequestPEM)
|
||||
if err != nil {
|
||||
return ca, crt, err
|
||||
}
|
||||
|
||||
crt = c.X509CertificatePEM
|
||||
|
||||
return g.caCrt, crt, nil
|
||||
}
|
||||
@ -16,37 +16,33 @@ import (
|
||||
securityapi "github.com/talos-systems/talos/api/security"
|
||||
"github.com/talos-systems/talos/pkg/crypto/x509"
|
||||
"github.com/talos-systems/talos/pkg/grpc/middleware/auth/basic"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
// Generator represents the OS identity generator.
|
||||
type Generator struct {
|
||||
// RemoteGenerator represents the OS identity generator.
|
||||
type RemoteGenerator struct {
|
||||
client securityapi.SecurityClient
|
||||
}
|
||||
|
||||
// NewGenerator initializes a Generator with a preconfigured grpc.ClientConn.
|
||||
func NewGenerator(data *userdata.UserData, port int) (g *Generator, err error) {
|
||||
if len(data.Services.Trustd.Endpoints) == 0 {
|
||||
// NewRemoteGenerator initializes a RemoteGenerator with a preconfigured grpc.ClientConn.
|
||||
func NewRemoteGenerator(token string, endpoints []string, port int) (g *RemoteGenerator, err error) {
|
||||
if len(endpoints) == 0 {
|
||||
return nil, fmt.Errorf("at least one root of trust endpoint is required")
|
||||
}
|
||||
|
||||
creds, err := basic.NewCredentials(data.Services.Trustd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creds := basic.NewTokenCredentials(token)
|
||||
|
||||
// Loop through trustd endpoints and attempt to download PKI
|
||||
var conn *grpc.ClientConn
|
||||
var multiError *multierror.Error
|
||||
for i := 0; i < len(data.Services.Trustd.Endpoints); i++ {
|
||||
conn, err = basic.NewConnection(data.Services.Trustd.Endpoints[i], port, creds)
|
||||
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.NewSecurityClient(conn)
|
||||
return &Generator{client: client}, nil
|
||||
return &RemoteGenerator{client: client}, nil
|
||||
}
|
||||
|
||||
// We were unable to connect to any trustd endpoint
|
||||
@ -55,35 +51,31 @@ func NewGenerator(data *userdata.UserData, port int) (g *Generator, err error) {
|
||||
}
|
||||
|
||||
// Certificate implements the securityapi.SecurityClient interface.
|
||||
func (g *Generator) Certificate(in *securityapi.CertificateRequest) (resp *securityapi.CertificateResponse, err error) {
|
||||
func (g *RemoteGenerator) Certificate(in *securityapi.CertificateRequest) (resp *securityapi.CertificateResponse, err error) {
|
||||
ctx := context.Background()
|
||||
resp, err = g.client.Certificate(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Identity creates a CSR and sends it to trustd for signing.
|
||||
// A signed certificate is returned.
|
||||
func (g *Generator) Identity(data *userdata.UserData) (err error) {
|
||||
if data.Security == nil {
|
||||
data.Security = &userdata.Security{}
|
||||
}
|
||||
data.Security.OS = &userdata.OSSecurity{CA: &x509.PEMEncodedCertificateAndKey{}}
|
||||
var csr *x509.CertificateSigningRequest
|
||||
if csr, err = data.NewIdentityCSR(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Identity creates an identity certificate via the security API.
|
||||
func (g *RemoteGenerator) Identity(csr *x509.CertificateSigningRequest) (ca, crt []byte, err error) {
|
||||
req := &securityapi.CertificateRequest{
|
||||
Csr: csr.X509CertificateRequestPEM,
|
||||
}
|
||||
|
||||
return poll(g, req, data.Security.OS)
|
||||
ca, crt, err = g.poll(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return ca, crt, nil
|
||||
}
|
||||
|
||||
func poll(g *Generator, in *securityapi.CertificateRequest, data *userdata.OSSecurity) (err error) {
|
||||
func (g *RemoteGenerator) poll(in *securityapi.CertificateRequest) (ca []byte, crt []byte, err error) {
|
||||
timeout := time.NewTimer(time.Minute * 5)
|
||||
defer timeout.Stop()
|
||||
tick := time.NewTicker(time.Second * 5)
|
||||
@ -92,18 +84,19 @@ func poll(g *Generator, in *securityapi.CertificateRequest, data *userdata.OSSec
|
||||
for {
|
||||
select {
|
||||
case <-timeout.C:
|
||||
return fmt.Errorf("timeout waiting for certificate")
|
||||
return nil, nil, fmt.Errorf("timeout waiting for certificate")
|
||||
case <-tick.C:
|
||||
resp, _err := g.Certificate(in)
|
||||
if _err != nil {
|
||||
log.Println(_err)
|
||||
var resp *securityapi.CertificateResponse
|
||||
resp, err = g.Certificate(in)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
data.CA = &x509.PEMEncodedCertificateAndKey{}
|
||||
data.CA.Crt = resp.Ca
|
||||
data.Identity.Crt = resp.Crt
|
||||
|
||||
return nil
|
||||
ca = resp.Ca
|
||||
crt = resp.Crt
|
||||
|
||||
return ca, crt, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,14 +6,12 @@ package basic
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/net"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
// Credentials describes an authorization method.
|
||||
@ -43,19 +41,3 @@ func NewConnection(address string, port int, creds credentials.PerRPCCredentials
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// NewCredentials returns credentials.PerRPCCredentials based on username and
|
||||
// password, or a token. The token method takes precedence over the username
|
||||
// and password.
|
||||
func NewCredentials(data *userdata.Trustd) (creds Credentials, err error) {
|
||||
switch {
|
||||
case data.Username != "" && data.Password != "":
|
||||
creds = NewUsernameAndPasswordCredentials(data.Username, data.Password)
|
||||
case data.Token != "":
|
||||
creds = NewTokenCredentials(data.Token)
|
||||
default:
|
||||
return nil, errors.New("failed to find valid credentials")
|
||||
}
|
||||
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
@ -1,185 +0,0 @@
|
||||
/* 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 tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/constants"
|
||||
"github.com/talos-systems/talos/pkg/grpc/gen"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
// CertificateProvider describes an interface by which TLS certificates may be managed
|
||||
type CertificateProvider interface {
|
||||
|
||||
// GetCertificate returns the current certificate matching the given client request
|
||||
GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error)
|
||||
|
||||
// UpdateCertificate updates the stored certificate for the given client request
|
||||
UpdateCertificate(h *tls.ClientHelloInfo, cert *tls.Certificate) error
|
||||
}
|
||||
|
||||
type singleCertificateProvider struct {
|
||||
sync.RWMutex
|
||||
cert *tls.Certificate
|
||||
|
||||
updateHooks []func(newCert *tls.Certificate)
|
||||
}
|
||||
|
||||
func (p *singleCertificateProvider) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if p == nil {
|
||||
return nil, errors.New("no provider")
|
||||
}
|
||||
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
return p.cert, nil
|
||||
}
|
||||
|
||||
func (p *singleCertificateProvider) UpdateCertificate(h *tls.ClientHelloInfo, cert *tls.Certificate) error {
|
||||
p.Lock()
|
||||
p.cert = cert
|
||||
p.Unlock()
|
||||
|
||||
for _, f := range p.updateHooks {
|
||||
f(cert)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type userDataCertificateProvider struct {
|
||||
data *userdata.OSSecurity
|
||||
}
|
||||
|
||||
func (p *userDataCertificateProvider) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
cert, err := tls.X509KeyPair(p.data.Identity.Crt, p.data.Identity.Key)
|
||||
return &cert, err
|
||||
}
|
||||
|
||||
func (p *userDataCertificateProvider) UpdateCertificate(h *tls.ClientHelloInfo, cert *tls.Certificate) error {
|
||||
// No-op
|
||||
return nil
|
||||
}
|
||||
|
||||
type renewingFileCertificateProvider struct {
|
||||
singleCertificateProvider
|
||||
|
||||
certFile string
|
||||
|
||||
// For now, this is using the complete userdata object. It should probably
|
||||
// be pared down to just what is necessary, later. However, the current TLS
|
||||
// generator code requires the whole thing... so we do, too.
|
||||
data *userdata.UserData
|
||||
|
||||
g *gen.Generator
|
||||
}
|
||||
|
||||
// NewRenewingFileCertificateProvider returns a new CertificateProvider which
|
||||
// manages and updates its certificates from trustd, storing a cache file copy.
|
||||
//
|
||||
// TODO: the flow here is a bit wonky, but it should be fixable after we change
|
||||
// to have the default be ephemeral node certificates. Until then, however, we
|
||||
// are doing a dance between the userdata-stored cert, the filesystem-cached
|
||||
// cert, and the memory-cached cert.
|
||||
func NewRenewingFileCertificateProvider(ctx context.Context, data *userdata.UserData) (CertificateProvider, error) {
|
||||
g, err := gen.NewGenerator(data, constants.TrustdPort)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create TLS generator")
|
||||
}
|
||||
|
||||
p := &renewingFileCertificateProvider{
|
||||
g: g,
|
||||
data: data,
|
||||
certFile: constants.NodeCertFile,
|
||||
}
|
||||
|
||||
if err = p.loadInitialCert(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to load initial certificate")
|
||||
}
|
||||
|
||||
go p.manageUpdates(ctx)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *renewingFileCertificateProvider) loadInitialCert() error {
|
||||
// TODO: eventually, we will reverse this priority, and have the override
|
||||
// come from the userdata. For now, however, we use the local file to
|
||||
// override the userdata, because we _always_ have userdata certs, and the
|
||||
// userdata is intended to be immutable.
|
||||
|
||||
data, err := ioutil.ReadFile(p.certFile)
|
||||
if err != nil || len(data) == 0 {
|
||||
// If we cannot read the cert from the file, then we use the userdata-supplied one
|
||||
data = p.data.Security.OS.Identity.Crt
|
||||
}
|
||||
|
||||
cert, err := tls.X509KeyPair(data, p.data.Security.OS.Identity.Key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse cert and key into a TLS Certificate")
|
||||
}
|
||||
|
||||
return p.UpdateCertificate(nil, &cert)
|
||||
}
|
||||
|
||||
func (p *renewingFileCertificateProvider) manageUpdates(ctx context.Context) {
|
||||
nextRenewal := constants.NodeCertRenewalInterval
|
||||
|
||||
for ctx.Err() == nil {
|
||||
if c, _ := p.GetCertificate(nil); c != nil { // nolint: errcheck
|
||||
if len(c.Certificate) > 0 {
|
||||
cert, err := x509.ParseCertificate(c.Certificate[0])
|
||||
if err == nil {
|
||||
nextRenewal = time.Until(cert.NotAfter) / 2
|
||||
} else {
|
||||
log.Println("failed to parse current leaf certificate")
|
||||
}
|
||||
} else {
|
||||
log.Println("current leaf certificate not found")
|
||||
}
|
||||
} else {
|
||||
log.Println("certificate not found")
|
||||
}
|
||||
|
||||
if nextRenewal > constants.NodeCertRenewalInterval {
|
||||
nextRenewal = constants.NodeCertRenewalInterval
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(nextRenewal):
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
if err := p.renewCert(); err != nil {
|
||||
log.Println("failed to renew certificate:", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *renewingFileCertificateProvider) renewCert() error {
|
||||
if err := p.g.Identity(p.data); err != nil {
|
||||
return errors.Wrap(err, "failed to renew certificate")
|
||||
}
|
||||
|
||||
// TODO: updating the cert using the generator automatically stores the new
|
||||
// cert to userdata. Therefore, we need to pull that cert out in order to
|
||||
// update the CertificateProvider's cache of it
|
||||
cert, err := tls.X509KeyPair(p.data.Security.OS.Identity.Crt, p.data.Security.OS.Identity.Key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse cert and key into a TLS Certificate")
|
||||
}
|
||||
|
||||
return p.UpdateCertificate(nil, &cert)
|
||||
}
|
||||
90
pkg/grpc/tls/local.go
Normal file
90
pkg/grpc/tls/local.go
Normal file
@ -0,0 +1,90 @@
|
||||
/* 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 tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/crypto/x509"
|
||||
"github.com/talos-systems/talos/pkg/grpc/gen"
|
||||
)
|
||||
|
||||
type renewingLocalCertificateProvider struct {
|
||||
embeddableCertificateProvider
|
||||
|
||||
caKey []byte
|
||||
caCrt []byte
|
||||
|
||||
generator *gen.LocalGenerator
|
||||
}
|
||||
|
||||
// NewLocalRenewingFileCertificateProvider returns a new CertificateProvider
|
||||
// which manages and updates its certificates using a local key.
|
||||
func NewLocalRenewingFileCertificateProvider(caKey, caCrt []byte, hostname string, ips []net.IP) (CertificateProvider, error) {
|
||||
g, err := gen.NewLocalGenerator(caKey, caCrt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create TLS generator")
|
||||
}
|
||||
|
||||
provider := &renewingLocalCertificateProvider{
|
||||
caKey: caKey,
|
||||
caCrt: caCrt,
|
||||
generator: g,
|
||||
}
|
||||
|
||||
provider.embeddableCertificateProvider = embeddableCertificateProvider{
|
||||
hostname: hostname,
|
||||
ips: ips,
|
||||
updateFunc: provider.update,
|
||||
}
|
||||
|
||||
var (
|
||||
ca []byte
|
||||
cert tls.Certificate
|
||||
)
|
||||
if ca, cert, err = provider.updateFunc(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create initial certificate")
|
||||
}
|
||||
|
||||
if err = provider.UpdateCertificates(ca, &cert); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
go provider.manageUpdates(context.Background())
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func (p *renewingLocalCertificateProvider) update() (ca []byte, cert tls.Certificate, err error) {
|
||||
var (
|
||||
crt []byte
|
||||
csr *x509.CertificateSigningRequest
|
||||
identity *x509.PEMEncodedCertificateAndKey
|
||||
)
|
||||
|
||||
csr, identity, err = x509.NewCSRAndIdentity(p.hostname, p.ips)
|
||||
if err != nil {
|
||||
return nil, cert, err
|
||||
}
|
||||
|
||||
if ca, crt, err = p.generator.Identity(csr); err != nil {
|
||||
return nil, cert, errors.Wrap(err, "failed to generate identity")
|
||||
}
|
||||
|
||||
identity.Crt = crt
|
||||
|
||||
cert, err = tls.X509KeyPair(identity.Crt, identity.Key)
|
||||
if err != nil {
|
||||
return nil, cert, errors.Wrap(err, "failed to parse cert and key into a TLS Certificate")
|
||||
}
|
||||
|
||||
return ca, cert, nil
|
||||
}
|
||||
129
pkg/grpc/tls/provider.go
Normal file
129
pkg/grpc/tls/provider.go
Normal file
@ -0,0 +1,129 @@
|
||||
/* 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 tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/constants"
|
||||
)
|
||||
|
||||
// CertificateProvider describes an interface by which TLS certificates may be managed.
|
||||
type CertificateProvider interface {
|
||||
// GetCA returns the active root CA.
|
||||
GetCA() ([]byte, error)
|
||||
|
||||
// GetCertificate returns the current certificate matching the given client request.
|
||||
GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error)
|
||||
|
||||
// UpdateCertificate updates the stored certificate for the given client request.
|
||||
UpdateCertificates([]byte, *tls.Certificate) error
|
||||
}
|
||||
|
||||
type embeddableCertificateProvider struct {
|
||||
sync.RWMutex
|
||||
|
||||
ca []byte
|
||||
crt *tls.Certificate
|
||||
|
||||
hostname string
|
||||
ips []net.IP
|
||||
|
||||
updateFunc func() ([]byte, tls.Certificate, error)
|
||||
updateHooks []func(newCert *tls.Certificate)
|
||||
}
|
||||
|
||||
func (p *embeddableCertificateProvider) GetCA() ([]byte, error) {
|
||||
if p == nil {
|
||||
return nil, errors.New("no provider")
|
||||
}
|
||||
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
return p.ca, nil
|
||||
}
|
||||
|
||||
func (p *embeddableCertificateProvider) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if p == nil {
|
||||
return nil, errors.New("no provider")
|
||||
}
|
||||
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
return p.crt, nil
|
||||
}
|
||||
|
||||
func (p *embeddableCertificateProvider) UpdateCertificates(ca []byte, cert *tls.Certificate) error {
|
||||
p.Lock()
|
||||
p.ca = ca
|
||||
p.crt = cert
|
||||
p.Unlock()
|
||||
|
||||
for _, f := range p.updateHooks {
|
||||
f(cert)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *embeddableCertificateProvider) manageUpdates(ctx context.Context) (err error) {
|
||||
nextRenewal := constants.DefaultCertificateValidityDuration
|
||||
|
||||
for ctx.Err() == nil {
|
||||
// nolint: errcheck
|
||||
if c, _ := p.GetCertificate(nil); c != nil {
|
||||
if len(c.Certificate) > 0 {
|
||||
var crt *x509.Certificate
|
||||
crt, err = x509.ParseCertificate(c.Certificate[0])
|
||||
if err == nil {
|
||||
nextRenewal = time.Until(crt.NotAfter) / 2
|
||||
} else {
|
||||
log.Println("failed to parse current leaf certificate")
|
||||
}
|
||||
} else {
|
||||
log.Println("current leaf certificate not found")
|
||||
}
|
||||
} else {
|
||||
log.Println("certificate not found")
|
||||
}
|
||||
|
||||
log.Println("next renewal in", nextRenewal)
|
||||
if nextRenewal > constants.DefaultCertificateValidityDuration {
|
||||
nextRenewal = constants.DefaultCertificateValidityDuration
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(nextRenewal):
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
ca []byte
|
||||
cert tls.Certificate
|
||||
)
|
||||
if ca, cert, err = p.updateFunc(); err != nil {
|
||||
log.Println("failed to renew certificate:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = p.UpdateCertificates(ca, &cert); err != nil {
|
||||
log.Println("failed to renew certificate:", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("certificate update manager exited unexpectedly")
|
||||
}
|
||||
85
pkg/grpc/tls/remote.go
Normal file
85
pkg/grpc/tls/remote.go
Normal file
@ -0,0 +1,85 @@
|
||||
/* 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 tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/crypto/x509"
|
||||
"github.com/talos-systems/talos/pkg/grpc/gen"
|
||||
)
|
||||
|
||||
type renewingRemoteCertificateProvider struct {
|
||||
embeddableCertificateProvider
|
||||
|
||||
generator *gen.RemoteGenerator
|
||||
}
|
||||
|
||||
// NewRemoteRenewingFileCertificateProvider returns a new CertificateProvider
|
||||
// which manages and updates its certificates from the security API.
|
||||
func NewRemoteRenewingFileCertificateProvider(token string, endpoints []string, port int, hostname string, ips []net.IP) (CertificateProvider, error) {
|
||||
g, err := gen.NewRemoteGenerator(token, endpoints, port)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create TLS generator")
|
||||
}
|
||||
|
||||
provider := &renewingRemoteCertificateProvider{
|
||||
generator: g,
|
||||
}
|
||||
|
||||
provider.embeddableCertificateProvider = embeddableCertificateProvider{
|
||||
hostname: hostname,
|
||||
ips: ips,
|
||||
updateFunc: provider.update,
|
||||
}
|
||||
|
||||
var (
|
||||
ca []byte
|
||||
cert tls.Certificate
|
||||
)
|
||||
if ca, cert, err = provider.updateFunc(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create initial certificate")
|
||||
}
|
||||
|
||||
if err = provider.UpdateCertificates(ca, &cert); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
go provider.manageUpdates(context.Background())
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func (p *renewingRemoteCertificateProvider) update() (ca []byte, cert tls.Certificate, err error) {
|
||||
var (
|
||||
crt []byte
|
||||
csr *x509.CertificateSigningRequest
|
||||
identity *x509.PEMEncodedCertificateAndKey
|
||||
)
|
||||
|
||||
csr, identity, err = x509.NewCSRAndIdentity(p.hostname, p.ips)
|
||||
if err != nil {
|
||||
return nil, cert, err
|
||||
}
|
||||
|
||||
if ca, crt, err = p.generator.Identity(csr); err != nil {
|
||||
return nil, cert, errors.Wrap(err, "failed to generate identity")
|
||||
}
|
||||
|
||||
identity.Crt = crt
|
||||
|
||||
cert, err = tls.X509KeyPair(identity.Crt, identity.Key)
|
||||
if err != nil {
|
||||
return nil, cert, errors.Wrap(err, "failed to parse cert and key into a TLS Certificate")
|
||||
}
|
||||
|
||||
return ca, cert, nil
|
||||
}
|
||||
@ -9,8 +9,6 @@ import (
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
// Type represents the TLS authentication type.
|
||||
@ -47,8 +45,7 @@ func WithClientAuthType(t Type) func(*tls.Config) error {
|
||||
// certificate.
|
||||
//
|
||||
// NOTE: specifying this option will CLEAR any configured Certificates, since
|
||||
// they would otherwise override this option
|
||||
//
|
||||
// they would otherwise override this option.
|
||||
func WithCertificateProvider(p CertificateProvider) func(*tls.Config) error {
|
||||
return func(cfg *tls.Config) error {
|
||||
if p == nil {
|
||||
@ -101,8 +98,8 @@ func defaultConfig() *tls.Config {
|
||||
}
|
||||
}
|
||||
|
||||
// NewConfigWithOpts returns a new TLS Configuration modified by any provided configuration options
|
||||
func NewConfigWithOpts(opts ...ConfigOptionFunc) (cfg *tls.Config, err error) {
|
||||
// New returns a new TLS Configuration modified by any provided configuration options
|
||||
func New(opts ...ConfigOptionFunc) (cfg *tls.Config, err error) {
|
||||
cfg = defaultConfig()
|
||||
|
||||
for _, f := range opts {
|
||||
@ -112,20 +109,3 @@ func NewConfigWithOpts(opts ...ConfigOptionFunc) (cfg *tls.Config, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewConfig initializes a TLS config for the specified type.
|
||||
func NewConfig(t Type, data *userdata.OSSecurity) (config *tls.Config, err error) {
|
||||
config = defaultConfig()
|
||||
|
||||
if err = WithClientAuthType(t)(config); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to apply ClientAuthType preference")
|
||||
}
|
||||
if err = WithCACertPEM(data.CA.Crt)(config); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to apply CA Certificate from UserData")
|
||||
}
|
||||
if err = WithCertificateProvider(&userDataCertificateProvider{data: data})(config); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to apply userdata-sourced CertificateProvider")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -12,8 +12,7 @@ import (
|
||||
|
||||
// OSSecurity represents the set of security options specific to the OS.
|
||||
type OSSecurity struct {
|
||||
CA *x509.PEMEncodedCertificateAndKey `yaml:"ca"`
|
||||
Identity *x509.PEMEncodedCertificateAndKey `yaml:"identity"`
|
||||
CA *x509.PEMEncodedCertificateAndKey `yaml:"ca"`
|
||||
}
|
||||
|
||||
// OSSecurityCheck defines the function type for checks
|
||||
|
||||
@ -5,11 +5,9 @@
|
||||
package userdata
|
||||
|
||||
import (
|
||||
stdlibx509 "crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
stdlibnet "net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@ -17,7 +15,6 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/crypto/x509"
|
||||
"github.com/talos-systems/talos/pkg/net"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
@ -90,50 +87,6 @@ type File struct {
|
||||
Path string `yaml:"path"`
|
||||
}
|
||||
|
||||
// NewIdentityCSR creates a new CSR for the node's identity certificate.
|
||||
func (data *UserData) NewIdentityCSR() (csr *x509.CertificateSigningRequest, err error) {
|
||||
var key *x509.Key
|
||||
key, err = x509.NewKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data.Security.OS.Identity = &x509.PEMEncodedCertificateAndKey{}
|
||||
data.Security.OS.Identity.Key = key.KeyPEM
|
||||
|
||||
pemBlock, _ := pem.Decode(key.KeyPEM)
|
||||
if pemBlock == nil {
|
||||
return nil, fmt.Errorf("failed to decode key")
|
||||
}
|
||||
keyEC, err := stdlibx509.ParseECPrivateKey(pemBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips, err := net.IPAddrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, san := range data.Services.Trustd.CertSANs {
|
||||
if ip := stdlibnet.ParseIP(san); ip != nil {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
opts := []x509.Option{}
|
||||
names := []string{hostname}
|
||||
opts = append(opts, x509.DNSNames(names))
|
||||
opts = append(opts, x509.IPAddresses(ips))
|
||||
csr, err = x509.NewCertificateSigningRequest(keyEC, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return csr, nil
|
||||
}
|
||||
|
||||
// Open is a convenience function that reads the user data from disk, and
|
||||
// unmarshals it.
|
||||
func Open(p string) (data *UserData, err error) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user