mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-01 22:56:41 +00:00
Add transparent proxy inspection engine with envoy sidecar support
This commit is contained in:
571
client/internal/engine_tproxy.go
Normal file
571
client/internal/engine_tproxy.go
Normal file
@@ -0,0 +1,571 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/firewall/uspfilter/forwarder"
|
||||
"github.com/netbirdio/netbird/client/inspect"
|
||||
"github.com/netbirdio/netbird/client/internal/acl/id"
|
||||
"github.com/netbirdio/netbird/shared/management/domain"
|
||||
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||
)
|
||||
|
||||
// updateTransparentProxy processes transparent proxy configuration from the network map.
|
||||
func (e *Engine) updateTransparentProxy(cfg *mgmProto.TransparentProxyConfig) {
|
||||
if cfg == nil || !cfg.Enabled {
|
||||
if cfg == nil {
|
||||
log.Tracef("inspect: config is nil")
|
||||
} else {
|
||||
log.Tracef("inspect: config disabled")
|
||||
}
|
||||
// Only stop if explicitly disabled. Don't stop on nil config to avoid
|
||||
// a gap during policy edits where management briefly pushes empty config.
|
||||
if cfg != nil && !cfg.Enabled {
|
||||
e.stopTransparentProxy()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("inspect: config received: enabled=%v mode=%v default_action=%v rules=%d has_ca=%v",
|
||||
cfg.Enabled, cfg.Mode, cfg.DefaultAction, len(cfg.Rules), len(cfg.CaCertPem) > 0)
|
||||
|
||||
// BlockInbound prevents adding TPROXY rules since kernel TPROXY bypasses ACLs.
|
||||
// The userspace forwarder path still works as it operates within the forwarder hook.
|
||||
if e.config.BlockInbound {
|
||||
log.Warnf("inspect: BlockInbound is set, skipping redirect rules (userspace path still active)")
|
||||
}
|
||||
|
||||
proxyConfig, err := toProxyConfig(cfg)
|
||||
if err != nil {
|
||||
log.Errorf("inspect: parse config: %v", err)
|
||||
e.stopTransparentProxy()
|
||||
return
|
||||
}
|
||||
|
||||
// CA priority: local config > management-pushed > auto-generated self-signed.
|
||||
// Local wins over mgmt to prevent compromised management from injecting a CA.
|
||||
e.resolveInspectionCA(&proxyConfig)
|
||||
|
||||
if e.transparentProxy != nil {
|
||||
// Mode change requires full recreate (envoy lifecycle, listener changes).
|
||||
if proxyConfig.Mode != e.transparentProxy.Mode() {
|
||||
log.Infof("inspect: mode changed to %s, recreating engine", proxyConfig.Mode)
|
||||
e.stopTransparentProxy()
|
||||
} else {
|
||||
e.transparentProxy.UpdateConfig(proxyConfig)
|
||||
e.syncTProxyRules(proxyConfig)
|
||||
e.syncUDPInspectionHook()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if e.wgInterface != nil {
|
||||
proxyConfig.WGNetwork = e.wgInterface.Address().Network
|
||||
proxyConfig.ListenAddr = netip.AddrPortFrom(
|
||||
e.wgInterface.Address().IP.Unmap(),
|
||||
proxyConfig.ListenAddr.Port(),
|
||||
)
|
||||
}
|
||||
|
||||
// Pass local IP checker for SSRF prevention
|
||||
if checker, ok := e.firewall.(inspect.LocalIPChecker); ok {
|
||||
proxyConfig.LocalIPChecker = checker
|
||||
}
|
||||
|
||||
p, err := inspect.New(e.ctx, log.WithField("component", "inspect"), proxyConfig)
|
||||
if err != nil {
|
||||
log.Errorf("inspect: start engine: %v", err)
|
||||
return
|
||||
}
|
||||
e.transparentProxy = p
|
||||
|
||||
e.attachProxyToForwarder(p)
|
||||
e.syncTProxyRules(proxyConfig)
|
||||
e.syncUDPInspectionHook()
|
||||
|
||||
log.Infof("inspect: engine started (mode=%s, rules=%d)", proxyConfig.Mode, len(proxyConfig.Rules))
|
||||
}
|
||||
|
||||
// stopTransparentProxy shuts down the transparent proxy and removes interception.
|
||||
func (e *Engine) stopTransparentProxy() {
|
||||
if e.transparentProxy == nil {
|
||||
return
|
||||
}
|
||||
|
||||
e.attachProxyToForwarder(nil)
|
||||
e.removeTProxyRule()
|
||||
e.removeUDPInspectionHook()
|
||||
|
||||
if err := e.transparentProxy.Close(); err != nil {
|
||||
log.Debugf("inspect: close engine: %v", err)
|
||||
}
|
||||
e.transparentProxy = nil
|
||||
|
||||
log.Info("inspect: engine stopped")
|
||||
}
|
||||
|
||||
const tproxyRuleID = "tproxy-redirect"
|
||||
|
||||
// syncTProxyRules adds a TPROXY rule via the firewall manager to intercept
|
||||
// matching traffic on the WG interface and redirect it to the proxy socket.
|
||||
func (e *Engine) syncTProxyRules(config inspect.Config) {
|
||||
if e.config.BlockInbound {
|
||||
e.removeTProxyRule()
|
||||
return
|
||||
}
|
||||
|
||||
var listenPort uint16
|
||||
if e.transparentProxy != nil {
|
||||
listenPort = e.transparentProxy.ListenPort()
|
||||
}
|
||||
if listenPort == 0 {
|
||||
e.removeTProxyRule()
|
||||
return
|
||||
}
|
||||
|
||||
if e.firewall == nil {
|
||||
return
|
||||
}
|
||||
|
||||
dstPorts := make([]uint16, len(config.RedirectPorts))
|
||||
copy(dstPorts, config.RedirectPorts)
|
||||
|
||||
log.Debugf("inspect: syncing redirect rules: listen port %d, redirect ports %v, sources %v",
|
||||
listenPort, dstPorts, config.RedirectSources)
|
||||
|
||||
if err := e.firewall.AddTProxyRule(tproxyRuleID, config.RedirectSources, dstPorts, listenPort); err != nil {
|
||||
log.Errorf("inspect: add redirect rule: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// removeTProxyRule removes the TPROXY redirect rule.
|
||||
func (e *Engine) removeTProxyRule() {
|
||||
if e.firewall == nil {
|
||||
return
|
||||
}
|
||||
if err := e.firewall.RemoveTProxyRule(tproxyRuleID); err != nil {
|
||||
log.Debugf("inspect: remove redirect rule: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// syncUDPInspectionHook registers a UDP packet hook on port 443 for QUIC SNI blocking.
|
||||
// The hook is called by the USP filter for each UDP packet matching the port,
|
||||
// allowing the inspection engine to extract QUIC SNI and block by domain.
|
||||
func (e *Engine) syncUDPInspectionHook() {
|
||||
e.removeUDPInspectionHook()
|
||||
|
||||
if e.firewall == nil || e.transparentProxy == nil {
|
||||
return
|
||||
}
|
||||
|
||||
p := e.transparentProxy
|
||||
hookID := e.firewall.AddUDPInspectionHook(443, func(packet []byte) bool {
|
||||
srcIP, dstIP, dstPort, udpPayload, ok := parseUDPPacket(packet)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
src := inspect.SourceInfo{IP: srcIP}
|
||||
dst := netip.AddrPortFrom(dstIP, dstPort)
|
||||
action := p.HandleUDPPacket(udpPayload, dst, src)
|
||||
return action == inspect.ActionBlock
|
||||
})
|
||||
|
||||
e.udpInspectionHookID = hookID
|
||||
log.Debugf("inspect: registered UDP inspection hook on port 443 (id=%s)", hookID)
|
||||
}
|
||||
|
||||
// removeUDPInspectionHook removes the QUIC inspection hook.
|
||||
func (e *Engine) removeUDPInspectionHook() {
|
||||
if e.udpInspectionHookID == "" || e.firewall == nil {
|
||||
return
|
||||
}
|
||||
e.firewall.RemoveUDPInspectionHook(e.udpInspectionHookID)
|
||||
e.udpInspectionHookID = ""
|
||||
}
|
||||
|
||||
// parseUDPPacket extracts source/destination IP, destination port, and UDP
|
||||
// payload from a raw IP packet. Supports both IPv4 and IPv6.
|
||||
func parseUDPPacket(packet []byte) (srcIP, dstIP netip.Addr, dstPort uint16, payload []byte, ok bool) {
|
||||
if len(packet) < 1 {
|
||||
return srcIP, dstIP, 0, nil, false
|
||||
}
|
||||
|
||||
version := packet[0] >> 4
|
||||
|
||||
var udpOffset int
|
||||
switch version {
|
||||
case 4:
|
||||
if len(packet) < 20 {
|
||||
return srcIP, dstIP, 0, nil, false
|
||||
}
|
||||
ihl := int(packet[0]&0x0f) * 4
|
||||
if len(packet) < ihl+8 {
|
||||
return srcIP, dstIP, 0, nil, false
|
||||
}
|
||||
var srcOK, dstOK bool
|
||||
srcIP, srcOK = netip.AddrFromSlice(packet[12:16])
|
||||
dstIP, dstOK = netip.AddrFromSlice(packet[16:20])
|
||||
if !srcOK || !dstOK {
|
||||
return srcIP, dstIP, 0, nil, false
|
||||
}
|
||||
udpOffset = ihl
|
||||
|
||||
case 6:
|
||||
// IPv6 fixed header is 40 bytes. Next header must be UDP (17).
|
||||
if len(packet) < 48 { // 40 header + 8 UDP
|
||||
return srcIP, dstIP, 0, nil, false
|
||||
}
|
||||
nextHeader := packet[6]
|
||||
if nextHeader != 17 { // not UDP (may have extension headers)
|
||||
return srcIP, dstIP, 0, nil, false
|
||||
}
|
||||
var srcOK, dstOK bool
|
||||
srcIP, srcOK = netip.AddrFromSlice(packet[8:24])
|
||||
dstIP, dstOK = netip.AddrFromSlice(packet[24:40])
|
||||
if !srcOK || !dstOK {
|
||||
return srcIP, dstIP, 0, nil, false
|
||||
}
|
||||
udpOffset = 40
|
||||
|
||||
default:
|
||||
return srcIP, dstIP, 0, nil, false
|
||||
}
|
||||
|
||||
srcIP = srcIP.Unmap()
|
||||
dstIP = dstIP.Unmap()
|
||||
dstPort = uint16(packet[udpOffset+2])<<8 | uint16(packet[udpOffset+3])
|
||||
payload = packet[udpOffset+8:]
|
||||
|
||||
return srcIP, dstIP, dstPort, payload, true
|
||||
}
|
||||
|
||||
// attachProxyToForwarder sets or clears the proxy on the userspace forwarder.
|
||||
func (e *Engine) attachProxyToForwarder(p *inspect.Proxy) {
|
||||
type forwarderGetter interface {
|
||||
GetForwarder() *forwarder.Forwarder
|
||||
}
|
||||
|
||||
if fg, ok := e.firewall.(forwarderGetter); ok {
|
||||
if fwd := fg.GetForwarder(); fwd != nil {
|
||||
fwd.SetProxy(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// toProxyConfig converts a proto TransparentProxyConfig to the inspect.Config type.
|
||||
func toProxyConfig(cfg *mgmProto.TransparentProxyConfig) (inspect.Config, error) {
|
||||
config := inspect.Config{
|
||||
Enabled: cfg.Enabled,
|
||||
DefaultAction: toProxyAction(cfg.DefaultAction),
|
||||
}
|
||||
|
||||
switch cfg.Mode {
|
||||
case mgmProto.TransparentProxyMode_TP_MODE_ENVOY:
|
||||
config.Mode = inspect.ModeEnvoy
|
||||
case mgmProto.TransparentProxyMode_TP_MODE_EXTERNAL:
|
||||
config.Mode = inspect.ModeExternal
|
||||
default:
|
||||
config.Mode = inspect.ModeBuiltin
|
||||
}
|
||||
|
||||
if cfg.ExternalProxyUrl != "" {
|
||||
u, err := url.Parse(cfg.ExternalProxyUrl)
|
||||
if err != nil {
|
||||
return inspect.Config{}, fmt.Errorf("parse external proxy URL: %w", err)
|
||||
}
|
||||
config.ExternalURL = u
|
||||
}
|
||||
|
||||
for _, s := range cfg.RedirectSources {
|
||||
prefix, err := netip.ParsePrefix(s)
|
||||
if err != nil {
|
||||
return inspect.Config{}, fmt.Errorf("parse redirect source %q: %w", s, err)
|
||||
}
|
||||
config.RedirectSources = append(config.RedirectSources, prefix)
|
||||
}
|
||||
|
||||
for _, p := range cfg.RedirectPorts {
|
||||
config.RedirectPorts = append(config.RedirectPorts, uint16(p))
|
||||
}
|
||||
|
||||
// TPROXY listen port: fixed default, overridable via env var.
|
||||
if config.Mode == inspect.ModeBuiltin {
|
||||
port := uint16(inspect.DefaultTProxyPort)
|
||||
if v := os.Getenv("NB_TPROXY_PORT"); v != "" {
|
||||
if p, err := strconv.ParseUint(v, 10, 16); err == nil {
|
||||
port = uint16(p)
|
||||
} else {
|
||||
log.Warnf("invalid NB_TPROXY_PORT %q, using default %d", v, inspect.DefaultTProxyPort)
|
||||
}
|
||||
}
|
||||
config.ListenAddr = netip.AddrPortFrom(netip.IPv4Unspecified(), port)
|
||||
}
|
||||
|
||||
for _, r := range cfg.Rules {
|
||||
rule, err := toProxyRule(r)
|
||||
if err != nil {
|
||||
return inspect.Config{}, fmt.Errorf("parse rule %q: %w", r.Id, err)
|
||||
}
|
||||
config.Rules = append(config.Rules, rule)
|
||||
}
|
||||
|
||||
if cfg.Icap != nil {
|
||||
icapCfg, err := toICAPConfig(cfg.Icap)
|
||||
if err != nil {
|
||||
return inspect.Config{}, fmt.Errorf("parse ICAP config: %w", err)
|
||||
}
|
||||
config.ICAP = icapCfg
|
||||
}
|
||||
|
||||
if len(cfg.CaCertPem) > 0 && len(cfg.CaKeyPem) > 0 {
|
||||
tlsCfg, err := parseTLSConfig(cfg.CaCertPem, cfg.CaKeyPem)
|
||||
if err != nil {
|
||||
return inspect.Config{}, fmt.Errorf("parse TLS config: %w", err)
|
||||
}
|
||||
config.TLS = tlsCfg
|
||||
}
|
||||
|
||||
if config.Mode == inspect.ModeEnvoy {
|
||||
envCfg := &inspect.EnvoyConfig{
|
||||
BinaryPath: cfg.EnvoyBinaryPath,
|
||||
AdminPort: uint16(cfg.EnvoyAdminPort),
|
||||
}
|
||||
if cfg.EnvoySnippets != nil {
|
||||
envCfg.Snippets = &inspect.EnvoySnippets{
|
||||
HTTPFilters: cfg.EnvoySnippets.HttpFilters,
|
||||
NetworkFilters: cfg.EnvoySnippets.NetworkFilters,
|
||||
Clusters: cfg.EnvoySnippets.Clusters,
|
||||
}
|
||||
}
|
||||
config.Envoy = envCfg
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func toProxyRule(r *mgmProto.TransparentProxyRule) (inspect.Rule, error) {
|
||||
rule := inspect.Rule{
|
||||
ID: id.RuleID(r.Id),
|
||||
Action: toProxyAction(r.Action),
|
||||
Priority: int(r.Priority),
|
||||
}
|
||||
|
||||
for _, d := range r.Domains {
|
||||
dom, err := domain.FromString(d)
|
||||
if err != nil {
|
||||
return inspect.Rule{}, fmt.Errorf("parse domain %q: %w", d, err)
|
||||
}
|
||||
rule.Domains = append(rule.Domains, dom)
|
||||
}
|
||||
|
||||
for _, n := range r.Networks {
|
||||
prefix, err := netip.ParsePrefix(n)
|
||||
if err != nil {
|
||||
return inspect.Rule{}, fmt.Errorf("parse network %q: %w", n, err)
|
||||
}
|
||||
rule.Networks = append(rule.Networks, prefix)
|
||||
}
|
||||
|
||||
for _, p := range r.Ports {
|
||||
rule.Ports = append(rule.Ports, uint16(p))
|
||||
}
|
||||
|
||||
for _, proto := range r.Protocols {
|
||||
rule.Protocols = append(rule.Protocols, toProxyProtoType(proto))
|
||||
}
|
||||
|
||||
rule.Paths = r.Paths
|
||||
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
func toProxyProtoType(p mgmProto.TransparentProxyProtocol) inspect.ProtoType {
|
||||
switch p {
|
||||
case mgmProto.TransparentProxyProtocol_TP_PROTO_HTTP:
|
||||
return inspect.ProtoHTTP
|
||||
case mgmProto.TransparentProxyProtocol_TP_PROTO_HTTPS:
|
||||
return inspect.ProtoHTTPS
|
||||
case mgmProto.TransparentProxyProtocol_TP_PROTO_H2:
|
||||
return inspect.ProtoH2
|
||||
case mgmProto.TransparentProxyProtocol_TP_PROTO_H3:
|
||||
return inspect.ProtoH3
|
||||
case mgmProto.TransparentProxyProtocol_TP_PROTO_WEBSOCKET:
|
||||
return inspect.ProtoWebSocket
|
||||
case mgmProto.TransparentProxyProtocol_TP_PROTO_OTHER:
|
||||
return inspect.ProtoOther
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func toProxyAction(a mgmProto.TransparentProxyAction) inspect.Action {
|
||||
switch a {
|
||||
case mgmProto.TransparentProxyAction_TP_ACTION_BLOCK:
|
||||
return inspect.ActionBlock
|
||||
case mgmProto.TransparentProxyAction_TP_ACTION_INSPECT:
|
||||
return inspect.ActionInspect
|
||||
default:
|
||||
return inspect.ActionAllow
|
||||
}
|
||||
}
|
||||
|
||||
func toICAPConfig(cfg *mgmProto.TransparentProxyICAPConfig) (*inspect.ICAPConfig, error) {
|
||||
icap := &inspect.ICAPConfig{
|
||||
MaxConnections: int(cfg.MaxConnections),
|
||||
}
|
||||
|
||||
if cfg.ReqmodUrl != "" {
|
||||
u, err := url.Parse(cfg.ReqmodUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse ICAP reqmod URL: %w", err)
|
||||
}
|
||||
icap.ReqModURL = u
|
||||
}
|
||||
|
||||
if cfg.RespmodUrl != "" {
|
||||
u, err := url.Parse(cfg.RespmodUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse ICAP respmod URL: %w", err)
|
||||
}
|
||||
icap.RespModURL = u
|
||||
}
|
||||
|
||||
return icap, nil
|
||||
}
|
||||
|
||||
func parseTLSConfig(certPEM, keyPEM []byte) (*inspect.TLSConfig, error) {
|
||||
block, _ := pem.Decode(certPEM)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("decode CA certificate PEM")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse CA certificate: %w", err)
|
||||
}
|
||||
|
||||
keyBlock, _ := pem.Decode(keyPEM)
|
||||
if keyBlock == nil {
|
||||
return nil, fmt.Errorf("decode CA key PEM")
|
||||
}
|
||||
|
||||
key, err := x509.ParseECPrivateKey(keyBlock.Bytes)
|
||||
if err != nil {
|
||||
// Try PKCS8 as fallback
|
||||
pkcs8Key, pkcs8Err := x509.ParsePKCS8PrivateKey(keyBlock.Bytes)
|
||||
if pkcs8Err != nil {
|
||||
return nil, fmt.Errorf("parse CA private key (tried EC and PKCS8): %w", err)
|
||||
}
|
||||
return &inspect.TLSConfig{CA: cert, CAKey: pkcs8Key}, nil
|
||||
}
|
||||
|
||||
return &inspect.TLSConfig{CA: cert, CAKey: key}, nil
|
||||
}
|
||||
|
||||
// resolveInspectionCA sets the TLS config on the proxy config using priority:
|
||||
// 1. Local config file CA (InspectionCACertPath/InspectionCAKeyPath)
|
||||
// 2. Management-pushed CA (already parsed in toProxyConfig)
|
||||
// 3. Auto-generated self-signed CA (ephemeral, for testing)
|
||||
// Local always wins to prevent a compromised management server from injecting a CA.
|
||||
func (e *Engine) resolveInspectionCA(config *inspect.Config) {
|
||||
// 1. Local CA from config file or env vars
|
||||
certPath := e.config.InspectionCACertPath
|
||||
keyPath := e.config.InspectionCAKeyPath
|
||||
if certPath == "" {
|
||||
certPath = os.Getenv("NB_INSPECTION_CA_CERT")
|
||||
}
|
||||
if keyPath == "" {
|
||||
keyPath = os.Getenv("NB_INSPECTION_CA_KEY")
|
||||
}
|
||||
if certPath != "" && keyPath != "" {
|
||||
certPEM, err := os.ReadFile(certPath)
|
||||
if err != nil {
|
||||
log.Errorf("read local inspection CA cert %s: %v", certPath, err)
|
||||
return
|
||||
}
|
||||
keyPEM, err := os.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
log.Errorf("read local inspection CA key %s: %v", keyPath, err)
|
||||
return
|
||||
}
|
||||
tlsCfg, err := parseTLSConfig(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
log.Errorf("parse local inspection CA: %v", err)
|
||||
return
|
||||
}
|
||||
log.Infof("inspect: using local CA from %s", certPath)
|
||||
config.TLS = tlsCfg
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Management-pushed CA (already set by toProxyConfig)
|
||||
if config.TLS != nil {
|
||||
log.Infof("inspect: using management-pushed CA")
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Auto-generate self-signed CA for testing / accept-cert UX
|
||||
tlsCfg, err := generateSelfSignedCA()
|
||||
if err != nil {
|
||||
log.Errorf("generate self-signed inspection CA: %v", err)
|
||||
return
|
||||
}
|
||||
log.Infof("inspect: using auto-generated self-signed CA (clients will see certificate warnings)")
|
||||
config.TLS = tlsCfg
|
||||
}
|
||||
|
||||
// generateSelfSignedCA creates an ephemeral ECDSA P-256 CA certificate.
|
||||
// Clients will see certificate warnings but can choose to accept.
|
||||
func generateSelfSignedCA() (*inspect.TLSConfig, error) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate CA key: %w", err)
|
||||
}
|
||||
|
||||
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate serial: %w", err)
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"NetBird Transparent Proxy"},
|
||||
CommonName: "NetBird Inspection CA (auto-generated)",
|
||||
},
|
||||
NotBefore: time.Now().Add(-1 * time.Hour),
|
||||
NotAfter: time.Now().Add(365 * 24 * time.Hour),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
MaxPathLen: 0,
|
||||
}
|
||||
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create CA certificate: %w", err)
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(certDER)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse generated CA certificate: %w", err)
|
||||
}
|
||||
|
||||
return &inspect.TLSConfig{CA: cert, CAKey: key}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user