CVE-2022-36103 (Go)

Talos Linux is a Linux distribution built for Kubernetes deployments. Talos worker nodes use a join token to get accepted into the Talos cluster. Due to improper validation of the request while signing a worker node CSR (certificate signing request) Talos control plane node might issue Talos API certificate which allows full access to Talos API on a control plane node. Accessing Talos API with full level access on a control plane node might reveal sensitive information which allows full level access to the cluster (Kubernetes and Talos PKI, etc.). Talos API join token is stored in the machine configuration on the worker node. When configured correctly, Kubernetes workloads don't have access to the machine configuration, but due to a misconfiguration workload might access the machine configuration and reveal the join token. This problem has been fixed in Talos 1.2.2. Enabling the Pod Security Standards mitigates the vulnerability by denying hostPath mounts and host networking by default in the baseline policy. Clusters that don't run untrusted workloads are not affected. Clusters with correct Pod Security configurations which don't allow hostPath mounts, and secure access to cloud metadata server (or machine configuration is not supplied via cloud metadata server) are not affected.

Weakness:
CWE-863: name: Incorrect Authorization
description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.

Repository: https://github.com/siderolabs/talos

Vulnerable code:
file_path: internal/app/trustd/internal/reg/reg.go
snippet: func (r *Registrator) Certificate(ctx context.Context, in *securityapi.CertificateRequest) (resp *securityapi.CertificateResponse, err error) {
	osRoot, err := safe.StateGet[*secrets.OSRoot](ctx, r.Resources, resource.NewMetadata(secrets.NamespaceName, secrets.OSRootType, secrets.OSRootID, resource.VersionUndefined))
	if err != nil {
		return nil, err
	}

	// TODO: Verify that the request is coming from the IP addresss declared in
	// the CSR.
	signed, err := x509.NewCertificateFromCSRBytes(osRoot.TypedSpec().CA.Crt, osRoot.TypedSpec().CA.Key, in.Csr)
	if err != nil {
		return
	}

	resp = &securityapi.CertificateResponse{
		Ca:  osRoot.TypedSpec().CA.Crt,
		Crt: signed.X509CertificatePEM,
	}

	return resp, nil
}

Fixed code:
file_path: internal/app/trustd/internal/reg/reg.go
snippet: import (
	"context"
	stdx509 "crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"log"

	"github.com/cosi-project/runtime/pkg/resource"
	"github.com/cosi-project/runtime/pkg/safe"
	"github.com/cosi-project/runtime/pkg/state"
	"github.com/siderolabs/crypto/x509"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/peer"
	"google.golang.org/grpc/status"

	securityapi "github.com/talos-systems/talos/pkg/machinery/api/security"
	"github.com/talos-systems/talos/pkg/machinery/resources/secrets"
)

file_path: internal/app/trustd/internal/reg/reg.go
snippet: func (r *Registrator) Certificate(ctx context.Context, in *securityapi.CertificateRequest) (resp *securityapi.CertificateResponse, err error) {
	remotePeer, ok := peer.FromContext(ctx)
	if !ok {
		return nil, status.Error(codes.PermissionDenied, "peer not found")
	}

	osRoot, err := safe.StateGet[*secrets.OSRoot](ctx, r.Resources, resource.NewMetadata(secrets.NamespaceName, secrets.OSRootType, secrets.OSRootID, resource.VersionUndefined))
	if err != nil {
		return nil, err
	}

	// decode and validate CSR
	csrPemBlock, _ := pem.Decode(in.Csr)
	if csrPemBlock == nil {
		return nil, status.Errorf(codes.InvalidArgument, "failed to decode CSR")
	}

	request, err := stdx509.ParseCertificateRequest(csrPemBlock.Bytes)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "failed to parse CSR: %s", err)
	}

	log.Printf("received CSR signing request from %s: subject %s dns names %s addresses %s", remotePeer.Addr, request.Subject, request.DNSNames, request.IPAddresses)

	// allow only server auth certificates
	x509Opts := []x509.Option{
		x509.KeyUsage(stdx509.KeyUsageDigitalSignature),
		x509.ExtKeyUsage([]stdx509.ExtKeyUsage{stdx509.ExtKeyUsageServerAuth}),
	}

	// don't allow any certificates which can be used for client authentication
	//
	// we don't return an error here, as otherwise workers running old versions of Talos
	// will fail to provision client certificate and will never launch apid
	//
	// instead, the returned certificate will be rejected when being used
	if len(request.Subject.Organization) > 0 {
		log.Printf("removing client auth organization from CSR: %s", request.Subject.Organization)

		x509Opts = append(x509Opts, x509.OverrideSubject(func(subject *pkix.Name) {
			subject.Organization = nil
		}))
	}

	// TODO: Verify that the request is coming from the IP address declared in
	// the CSR.
	signed, err := x509.NewCertificateFromCSRBytes(
		osRoot.TypedSpec().CA.Crt,
		osRoot.TypedSpec().CA.Key,
		in.Csr,
		x509Opts...,
	)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to sign CSR: %s", err)
	}

	resp = &securityapi.CertificateResponse{
		Ca:  osRoot.TypedSpec().CA.Crt,
		Crt: signed.X509CertificatePEM,
	}

	return resp, nil
}