Ctrl K
ComunidadGuías…

examples/clickhouse-public-endpoint-acceptance/main.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Disposable, explicitly synthetic ClickHouse public endpoint acceptance.
package main

import (
	"context"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/hex"
	"encoding/pem"
	"errors"
	"flag"
	"fmt"
	"math/big"
	"net"
	"net/netip"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
	"time"
)

type options struct {
	mode, check, purpose, host, ca, fingerprint, password, address, expectedAddress string
	port, shards, replicas                                                          int
	revoke                                                                          time.Duration
	caBytes                                                                         []byte
}

func main() {
	o := options{}
	flag.StringVar(&o.mode, "mode", "probe", "probe or hold")
	flag.StringVar(&o.check, "check", "verified", "acceptance check")
	flag.StringVar(&o.purpose, "purpose", "native", "native or https")
	flag.StringVar(&o.host, "host", "", "reviewed public hostname")
	flag.StringVar(&o.expectedAddress, "expect-address", "", "optional reviewed IPv4 address required for every DNS result")
	flag.IntVar(&o.port, "port", 0, "public TCP port")
	flag.StringVar(&o.ca, "ca", "", "public CA PEM file")
	flag.StringVar(&o.fingerprint, "expect-fingerprint", "", "optional SHA256 leaf fingerprint")
	flag.DurationVar(&o.revoke, "expect-revocation-within", 0, "require existing session closure within this bound")
	flag.IntVar(&o.shards, "shards", 1, "shards: 1, 2 or 8")
	flag.IntVar(&o.replicas, "replicas", 1, "members per shard: 1, 2 or 6")
	flag.Parse()
	ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
	defer cancel()
	if o.mode == "hold" {
		select {
		case <-ctx.Done():
		case <-time.After(24 * time.Hour):
		}
		return
	}
	if err := o.validate(); err != nil {
		fail("invalid probe arguments")
	}
	o.password = os.Getenv("CLICKHOUSE_PASSWORD")
	if len(o.password) != 64 {
		fail("provide a protected hexadecimal CLICKHOUSE_PASSWORD")
	}
	if raw, err := hex.DecodeString(o.password); err != nil || len(raw) != 32 {
		fail("invalid password format")
	}
	file, err := os.Open(o.ca)
	if err != nil {
		fail("public CA file is unavailable")
	}
	o.caBytes, err = readBounded(file, 32<<10)
	_ = file.Close()
	if err != nil {
		fail("public CA file is invalid or oversized")
	}
	roots := x509.NewCertPool()
	if !roots.AppendCertsFromPEM(o.caBytes) {
		fail("public CA is invalid")
	}
	lookup, stop := context.WithTimeout(ctx, 3*time.Second)
	ips, err := net.DefaultResolver.LookupIPAddr(lookup, o.host)
	stop()
	if err != nil || len(ips) == 0 || len(ips) > 16 {
		fail("DNS resolution failed or exceeded its bound")
	}
	pinned, err := reviewedDNSAddress(ips, o.expectedAddress)
	if err != nil {
		fail("DNS results do not match the reviewed IPv4 address")
	}
	o.address = net.JoinHostPort(pinned, strconv.Itoa(o.port))
	if o.check == "unreachable" {
		step, done := context.WithTimeout(ctx, 4*time.Second)
		defer done()
		conn, err := (&net.Dialer{}).DialContext(step, "tcp", o.address)
		if err == nil {
			config, e := tlsConfig(o, "")
			if e != nil {
				conn.Close()
				fail("CA configuration failed")
			}
			secure := tls.Client(conn, config)
			err = secure.HandshakeContext(step)
			_ = secure.Close()
			if err == nil {
				fail("unlisted source completed verified TLS")
			}
		}
		var dns *net.DNSError
		var certificate *tls.CertificateVerificationError
		if errors.As(err, &dns) || errors.As(err, &certificate) {
			fail("DNS or certificate errors do not prove source refusal")
		}
		var timeout net.Error
		if !closedRemotely(err) && !errors.Is(err, syscall.ECONNREFUSED) && !(errors.As(err, &timeout) && timeout.Timeout()) {
			fail("failure did not prove TCP refusal")
		}
		fmt.Println("PASS unreachable")
		return
	}
	run, done := context.WithTimeout(ctx, 4*time.Minute+o.revoke)
	defer done()
	if err = verified(run, o); err != nil {
		fail("verified ClickHouse query failed")
	}
	if o.revoke > 0 {
		if o.purpose == "native" {
			err = nativeRevocation(run, o)
		} else {
			err = httpsRevocation(run, o)
		}
		if err != nil {
			fail("existing ClickHouse session revocation was not proved")
		}
		fmt.Println("REVOKED existing ClickHouse session closed")
		return
	}
	switch o.check {
	case "verified":
	case "crud", "data-preserved":
		err = dataCheck(run, o)
	default:
		err = negative(run, o)
	}
	if err != nil {
		fail("requested ClickHouse check failed")
	}
	if err = verified(run, o); err != nil {
		fail("successful query after check failed")
	}
	fmt.Println("PASS " + o.check)
}
func fail(message string) { fmt.Fprintln(os.Stderr, message); os.Exit(1) }
func reviewedDNSAddress(ips []net.IPAddr, expected string) (string, error) {
	if len(ips) == 0 || len(ips) > 16 {
		return "", errors.New("DNS result bound")
	}
	if expected != "" {
		reviewed, err := netip.ParseAddr(expected)
		if err != nil || !reviewed.Is4() {
			return "", errors.New("reviewed address must be IPv4")
		}
		for _, ip := range ips {
			if ip.Zone != "" || ip.IP.To4() == nil || ip.IP.String() != reviewed.String() {
				return "", errors.New("DNS address changed")
			}
		}
		return reviewed.String(), nil
	}
	for _, ip := range ips {
		if ip.Zone == "" && ip.IP.To4() != nil {
			return ip.IP.String(), nil
		}
	}
	return "", errors.New("no IPv4 address")
}
func (o options) validate() error {
	checks := map[string]bool{"verified": true, "crud": true, "data-preserved": true, "plaintext-rejected": true, "wrong-hostname-rejected": true, "wrong-ca-rejected": true, "bad-password-rejected": true, "unreachable": true}
	if o.mode != "probe" || !checks[o.check] || (o.purpose != "native" && o.purpose != "https") || o.port < 1 || o.port > 65535 || o.ca == "" || o.revoke < 0 || o.revoke > 10*time.Minute || o.revoke > 0 && o.check != "verified" || (o.shards != 1 && o.shards != 2 && o.shards != 8) || (o.replicas != 1 && o.replicas != 2 && o.replicas != 6) {
		return errors.New("arguments")
	}
	if len(o.host) < 1 || len(o.host) > 253 || net.ParseIP(o.host) != nil {
		return errors.New("hostname")
	}
	for _, label := range strings.Split(o.host, ".") {
		if label == "" || len(label) > 63 || label[0] == '-' || label[len(label)-1] == '-' {
			return errors.New("hostname")
		}
		for _, c := range label {
			if !(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-') {
				return errors.New("hostname")
			}
		}
	}
	if o.fingerprint != "" {
		raw, err := hex.DecodeString(o.fingerprint)
		if err != nil || len(raw) != 32 {
			return errors.New("fingerprint")
		}
	}
	if o.expectedAddress != "" {
		address, err := netip.ParseAddr(o.expectedAddress)
		if err != nil || !address.Is4() {
			return errors.New("reviewed address")
		}
	}
	return nil
}
func tlsConfig(o options, check string) (*tls.Config, error) {
	roots := x509.NewCertPool()
	if !roots.AppendCertsFromPEM(o.caBytes) {
		return nil, errors.New("CA")
	}
	host := o.host
	if check == "wrong-hostname-rejected" {
		host = "wrong-hostname.invalid"
	}
	if check == "wrong-ca-rejected" {
		roots = x509.NewCertPool()
	}
	config := &tls.Config{MinVersion: tls.VersionTLS12, ServerName: host, RootCAs: roots, NextProtos: []string{"http/1.1"}}
	if o.fingerprint != "" {
		config.VerifyConnection = func(s tls.ConnectionState) error {
			if len(s.PeerCertificates) == 0 {
				return errors.New("missing leaf")
			}
			sum := sha256.Sum256(s.PeerCertificates[0].Raw)
			if !strings.EqualFold(hex.EncodeToString(sum[:]), o.fingerprint) {
				return errors.New("leaf changed")
			}
			return nil
		}
	}
	return config, nil
}
func tlsOpen(ctx context.Context, o options, check string) (*tls.Conn, error) {
	config, err := tlsConfig(o, check)
	if err != nil {
		return nil, err
	}
	step, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()
	raw, err := (&net.Dialer{}).DialContext(step, "tcp", o.address)
	if err != nil {
		return nil, err
	}
	conn := tls.Client(raw, config)
	if err = conn.HandshakeContext(step); err != nil {
		_ = conn.Close()
		return nil, err
	}
	return conn, nil
}
func verified(ctx context.Context, o options) error {
	out, err := query(ctx, o, "SELECT currentUser(), currentDatabase() FORMAT TSV")
	if err != nil {
		return err
	}
	if strings.TrimSpace(out) != "app\tapp" {
		return errors.New("identity")
	}
	return nil
}
func query(ctx context.Context, o options, sql string) (string, error) {
	if o.purpose == "native" {
		return nativeQuery(ctx, o, sql, "verified")
	}
	conn, err := tlsOpen(ctx, o, "")
	if err != nil {
		return "", err
	}
	defer conn.Close()
	out, code, err := httpsQuery(ctx, conn, o, sql, o.password)
	if err != nil {
		return "", err
	}
	if code != 0 {
		return "", errors.New("query rejected")
	}
	return out, nil
}
func negative(ctx context.Context, o options) error {
	if o.purpose == "native" {
		_, err := nativeQuery(ctx, o, "SELECT currentUser(), currentDatabase() FORMAT TSV", o.check)
		if err == nil {
			return errors.New("negative succeeded")
		}
		var rejected *nativeRejection
		if !errors.As(err, &rejected) || rejected.check != o.check {
			return errors.New("unclassified native failure")
		}
		return nil
	}
	if o.check == "wrong-hostname-rejected" || o.check == "wrong-ca-rejected" {
		conn, err := tlsOpen(ctx, o, o.check)
		if err == nil {
			conn.Close()
			return errors.New("TLS accepted")
		}
		var host x509.HostnameError
		var ca x509.UnknownAuthorityError
		if o.check == "wrong-hostname-rejected" && errors.As(err, &host) || o.check == "wrong-ca-rejected" && errors.As(err, &ca) {
			return nil
		}
		return errors.New("wrong TLS failure")
	}
	if o.check == "plaintext-rejected" {
		step, cancel := context.WithTimeout(ctx, 5*time.Second)
		defer cancel()
		raw, err := (&net.Dialer{}).DialContext(step, "tcp", o.address)
		if err != nil {
			return errors.New("plaintext TCP failed")
		}
		defer raw.Close()
		_, _, err = httpsQuery(step, raw, o, "SELECT 1", "")
		if closedRemotely(err) {
			return nil
		}
		return errors.New("plaintext not rejected")
	}
	conn, err := tlsOpen(ctx, o, "")
	if err != nil {
		return err
	}
	defer conn.Close()
	_, code, err := httpsQuery(ctx, conn, o, "SELECT 1", "invalid-public-probe-password")
	if err == nil && code == 516 {
		return nil
	}
	return errors.New("authentication not rejected")
}
func unrelatedCA() ([]byte, error) {
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, err
	}
	cert := &x509.Certificate{SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "Synthetic unrelated acceptance CA"}, NotBefore: time.Now().Add(-time.Minute), NotAfter: time.Now().Add(time.Hour), IsCA: true, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign}
	der, err := x509.CreateCertificate(rand.Reader, cert, cert, key.Public(), key)
	if err != nil {
		return nil, err
	}
	return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), nil
}