package agentd import ( "context" "crypto/tls" "crypto/x509" "errors" "fmt" "net" "os" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" ) // Dialer opens an authenticated long-lived connection to the control plane. type Dialer func(ctx context.Context) (*grpc.ClientConn, error) // mtlsClientConfig builds the client *tls.Config from the persisted node key/cert // and pinned CA. func mtlsClientConfig(cfg Config) (*tls.Config, error) { cert, err := tls.LoadX509KeyPair(cfg.CertPath(), cfg.KeyPath()) if err != nil { return nil, fmt.Errorf("agentd: load client keypair: %w", err) } caPEM, err := os.ReadFile(cfg.CAPath()) if err != nil { return nil, fmt.Errorf("agentd: read CA: %w", err) } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(caPEM) { return nil, errors.New("agentd: CA file contains no certificate") } return &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: pool, ServerName: cfg.tlsServerName(), MinVersion: tls.VersionTLS13, }, nil } // tlsServerName returns the SNI to validate the control-plane cert against. func (c Config) tlsServerName() string { if c.ServerName != "" { return c.ServerName } host, _, err := net.SplitHostPort(c.ControlPlaneAddr) if err != nil { return c.ControlPlaneAddr } return host } // NewMTLSDialer returns a Dialer that connects to the control plane over mTLS, // presenting the node's client certificate. func NewMTLSDialer(cfg Config) Dialer { return func(ctx context.Context) (*grpc.ClientConn, error) { tlsCfg, err := mtlsClientConfig(cfg) if err != nil { return nil, err } return grpc.NewClient(cfg.ControlPlaneAddr, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)), ) } } // NewEnrollDialer returns an EnrollDialer for the bootstrap (pre-certificate) // Enroll call. It pins the control-plane CA if ca.crt is already present // (cloud-init may inject it); otherwise it falls back to a server-unauthenticated // TLS handshake — the bootstrap token is the trust anchor for that single call. func NewEnrollDialer(cfg Config) EnrollDialer { return func(ctx context.Context) (*grpc.ClientConn, error) { var creds credentials.TransportCredentials if caPEM, err := os.ReadFile(cfg.CAPath()); err == nil { pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(caPEM) { return nil, errors.New("agentd: pinned CA file invalid") } creds = credentials.NewTLS(&tls.Config{ RootCAs: pool, ServerName: cfg.tlsServerName(), MinVersion: tls.VersionTLS13, }) } else { creds = credentials.NewTLS(&tls.Config{ InsecureSkipVerify: true, //nolint:gosec // bootstrap-token-authenticated enroll only MinVersion: tls.VersionTLS13, }) } return grpc.NewClient(cfg.ControlPlaneAddr, grpc.WithTransportCredentials(creds)) } } // insecureDialer is used only by tests/dev (cfg.Insecure). func insecureDialer(addr string) Dialer { return func(ctx context.Context) (*grpc.ClientConn, error) { return grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials())) } }