ztp

package module
v0.0.0-...-9fdb38c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 12, 2026 License: MIT Imports: 24 Imported by: 0

README

go-ztp

go-ztp implements a Zero-Trust Proxy / Protocol Gateway.

Go Report Card codecov Reference CodeQL

License

The go-ztp package is released under the terms of the MIT license. For details, see the LICENSE file in the repository root.

Status

The go-ztp package is very new, very unstable and very fluid. It is far too early for interested parties to utilize this package for anything other than pure testing/development/PoC endeavors. But, with that said ...

HELP IS WANTED: contributions, 3rd party QA, PEN tests, SME reviews ... at this point I'm not going to turn down anything.

Donations for animal/environmental causes

If you or your organization use my software regularly and find it useful, I only ask that you donate to animal shelters, non-profit environmental entities or similar. If you cannot afford a monetary contribution to these causes, please volunteer at animal shelters and/or visit kill shelters for the purpose of liberating animals unfairly awaiting execution.

Dependencies

This package relies upon the following packages from the Go standard library:

  • bufio
  • bytes
  • context
  • crypto/ecdsa
  • crypto/elliptic
  • crypto/rand
  • crypto/rsa
  • crypto/tls
  • crypto/x509
  • crypto/x509/pkix
  • encoding/base64
  • encoding/json
  • encoding/pem
  • errors
  • io
  • log
  • math/big
  • net
  • net/http
  • os
  • path/filepath
  • strconv
  • strings
  • sync
  • sync/atomic
  • testing
  • time

For third party dependencies, whether direct or indirect, see the Dependency Graph.

Usage

The following subsections describe basic steps for testing this application, or authoring your own listener for your own purposes.

The _example folder

The _example folder contains basic server.go and client.go demo files. This section describes them.

The server.go file represents your ZTP listener. It is in this file that services are registered and elements such as preauth, authenticators, directory backends and policy routines (authorization controls) are defined. By default, as described in the ZTP Wire Protocol section below, the listener listens on all addresses via TCP/9743.

For the sake of simplicity, the server.go file uses the simplest directory backend -- a YAML directory. The directory data is found within the flat.example.yaml file and, at present, contains two valid test users: "alice" and "bob".

The client.go file represents a general client. This file connects to the listener and requests a particular service based on the client PKI certificate, in which the service name is established as a TLS SNI, e.g.: udp-dns.svc.internal. Naturally users may have their own domain and may name their SNI-routed services according to their own established naming conventions.

PKI

This service is very dependent on a solid PKI infrastructure, namely a valid CA utility or service which issues X.509 server and client certificates. For the purpose of my own testing, I simply used openssl. Below are the commands I used personally to generate the certificates referenced in server.go and client.go:

Here is the command for generating the issuing private key and certificate.

openssl req -x509 \
  -newkey rsa:4096 
  -keyout ca.key
  -out ca.pem
  -sha256 -days 365 
  -nodes 
  -subj "/CN=Test CA"

A mode of 0444 is sufficient for protecting the issuing certificate. A mode of 0400 (read for owner only) is strongly recommended for protecting its private key. If this key is compromised, a malicious entity can issue certificates on your behalf. This would be undesirable.

Here is the command for generating our server (listener) private key:

openssl genrsa -out server.key 2048

A mode of 0400 (read for owner only) is strongly recommended for protecting this private key.

Here is the command which generates the server certificate signing request (CSR):

openssl req -new \
  -key server.key \
  -out server.csr \
  -config server-openssl.cnf

A mode of 0444 (read for everyone) is sufficient for protecting this CSR, though strictly speaking it need not be preserved after signing takes place.

Here is the command used to sign our server certificate using our CA, carrying SAN DNS:localhost:

openssl x509 -req \
  -in server.csr \
  -CA ca.pem \
  -CAkey ca.key \
  -CAcreateserial \
  -days 365 \
  -out server.pem \
  -extfile server-openssl.cnf \
  -extensions req_ext

A mode of 0444 (read for everyone) is sufficient for protecting this certificate.

Here is the server-openssl.cnf file referenced above. Note that we're using wildcard DNS SANs for an entire subdomain (svc.internal), as the proxy listener may be hosting multiple services:

[ req ]
default_bits       = 2048
prompt             = no
default_md         = sha256
req_extensions     = req_ext
distinguished_name = dn

[ dn ]
CN = ztp.svc.internal

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = localhost
DNS.2 = *.svc.internal

Here is the command for generating our client's private key (intended for test user "alice"):

openssl genrsa -out client.key 2048

A mode of 0400 (read for owner only) is strongly recommended for protecting this private key.

Here is the command which generates the client certificate signing request (CSR):

openssl req -new \
  -key client.key \
  -out client.csr \
  -config client-openssl.cnf

A mode of 0444 (read for everyone) is sufficient for protecting this CSR, though strictly speaking it need not be preserved after signing takes place.

Here is the command used to sign our client certificate using our CA, carrying an Email SAN for "alice@example.com":

openssl x509 -req \
  -in client.csr \
  -CA ca.pem \
  -CAkey ca.key \
  -CAcreateserial \
  -days 365 \
  -out client.pem \
  -extfile client-openssl.cnf \
  -extensions req_ext

A mode of 0444 (read for everyone) is sufficient for protecting this certificate.

Here is the client-openssl.cnf file referenced above:

[ req ]
default_bits       = 2048
prompt             = no
default_md         = sha256
req_extensions     = req_ext
distinguished_name = dn

[ dn ]
CN = alice@example.com

[ req_ext ]
subjectAltName = email:alice@example.com

Repeat as needed for other client certificates, such as for the test user "bob".

Of course, actual organizations may have their own procedures for generating keys and certificates. So long as the SAN configuration resembles that shown above (with respect to your own custom domain/naming conventions), you should be clear to proceed.

Backend Testing

Its a safe bet the reason you're using this package is to proxy some kind of protocol-bound service request to a system, ostensibly one that is not directly accessible to you, hence the need for a proxy. This section offers some pointers to help the user to that end.

Users have two primary options:

  • Real: use a real backend server or service -- HTTP, LDAP, MySQL, to name a few -- that already exists in your server armada
  • Fake: Use a dumb listener like nc or openssl s_server

For the purposes of simple testing for end users who may or may not have access to such systems, we'll be using nc.

First, this section assumes you've copied and/or tailored the client.go and server.go examples within the _examples directory. This would entail registering the desired backend service in server.go, which would correspond to the nc listener we're about to launch, e.g.:

{
  Name:     `quic.svc.internal`,       // must match SNI in client certificate(s), wildcards OK
  Protocol: ztp.ProtocolQUIC,	       // the transport protocol associated with this service
  Address:  `:4433`,                   // whatever, can also lock this down to a single address, like 127.0.0.1:4433
  Groups:   nil,                       // normally you'd want groups, but not needed here
  Handler:  ztp.NewQUICProxyHandler(), // or write your own custom handler
},

Second, this section assumes you've generated or provided your own issuing CA certificate, a listener cert and key and a client cert and key. If not, see the PKI section above. If you're generating your own CA to sign other certificates, you'll need to have its private key -- which is VERY sensitive -- on-hand. On this topic, this section assumes the aforementioned modifications to the server.go and client.go include references to these cryptographic files as needed.

Assuming all of this tracks, we're now ready to start testing. The following example assumes the above variables are as shown, for instance the port, service, etc. Alter as needed for your own purposes.

First, launch nc -kul 4433, which translates to "keep-alive UDP listening on service port 4433". For those unaware, QUIC is a subset of UDP in essence, which is why it might appear that we're "mixing" these protocols haphazardly.

Next, launch your ZTP listener via go run _examples/server.go (or you could build a binary and just use that).

Finally, launch your generic ZTP "dumb" client via go run _examples/client.go.

If all goes well, you should see nc barf up some bizarre Unicode Question Mark characters. This means the test was successful, and your terminal is merely doing its best to interpret control bytes out of context.

So what happened?

  • You launched a backend listener, nc
  • You launched your ZTP listener, which was configured to interact with the aforementioned backend
  • You launched a basic ZTP client, which (once the user was verified and authenticated by the ZTP listener) sent a forged QUIC packet through the proxy, which relayed it to nc

Of course, better tests would revolve around actual, real networking services and not dumb listeners receiving fake test packets. This is where you, the end user, come in. It would probably also make sense to write a real client ... one that isn't "dumb".

Good luck!

The ZTP Wire Protocol

This section defines the ZTP wire protocol used by the proxy listener, a secure multiplexing gateway that accepts inbound client connections on a single TCP/TLS port and routes them to backend services based on authenticated identity and TLS Server Name Indication (SNI).

The ZTP Listener provides:

  • Mutual TLS authentication
  • Identity extraction
  • SNI‑based service selection
  • Policy enforcement
  • Protocol dispatch
  • Transport‑agnostic payload forwarding (TCP, UDP, QUIC)
Conventions and Terminology

The key words MUST, MUST NOT, SHOULD, and MAY are to be interpreted as described in RFC 2119.

  • Client: An entity initiating a connection to the ZTP Listener
  • Proxy: The ZTP listener accepting inbound connections
  • Backend: A service endpoint to which the proxy forwards traffic
  • Identity: The authenticated client identity derived from the TLS handshake
  • Service: A configured backend identified by an SNI hostname
  • Declared Protocol: The protocol explicitly configured for a service
  • Detected Protocol: The protocol inferred from the first bytes of application data
Transport Layer

The ZTP Listener operates exclusively over TCP via IPv4 and IPv6.

The default listener port for ZTP is 9743/TCP. This port is unassigned, non‑privileged, and selected to avoid conflicts with existing services. Clients MUST initiate a TCP connection to this port before any further protocol steps occur.

TLS Layer

Immediately after TCP connection establishment, the TLS layer is initialized.

TLS Version

The client MUST initiate a TLS 1.3 handshake. Earlier versions MUST be rejected.

Server Name Indication (SNI)

Clients MUST include an SNI extension in the ClientHello.

The SNI hostname identifies the desired backend service. Example:

dns-udp.svc.internal

If the SNI does not match a configured service, the proxy MUST terminate the connection.

Client Authentication

The proxy performs mutual TLS authentication. Clients MUST present a certificate trusted by the proxy’s configured CA set.

Identity extraction rules (e.g., SAN email, subject CN) are implementation‑defined but MUST be deterministic.

If authentication fails, the proxy MUST terminate the connection.

Service Lookup

After successful TLS handshake and identity extraction, the proxy performs:

  • Lookup of the service by SNI
  • Authorization of the identity against the service’s access policy

If authorization fails, the proxy MUST send an error message and close the connection.

Protocol Selection

After service lookup, the proxy determines the protocol to be used for backend communication.

Declared Protocol

If the service configuration specifies a protocol (e.g., udp, tcp, https), the proxy MUST use that protocol and MUST NOT perform protocol detection.

Detected Protocol

If the service does not declare a protocol, the proxy MAY inspect the first bytes of application data to infer the protocol.

Protocol detection is implementation‑defined and out of scope for this document.

Protocol Mismatch

If the handler associated with the service does not support the declared protocol, the proxy MUST terminate the connection.

Application Data Flow

Once protocol selection is complete, the proxy transitions into transparent forwarding mode.

TCP Services

For TCP‑based services:

  • The proxy establishes a TCP connection to the backend
  • All TLS‑protected application data from the client is forwarded verbatim
  • All backend responses are forwarded back to the client
UDP Services

For UDP‑based services:

  • The proxy creates a UDP socket bound to an ephemeral port
  • The first application data packet received from the client MUST be forwarded as a single UDP datagram
  • The proxy MUST wait for a single UDP response from the backend
  • The response MUST be forwarded to the client as TLS application data
  • After forwarding the response, the proxy MAY close the connection
QUIC Services

For QUIC‑based services, the proxy treats QUIC traffic as opaque, datagram‑oriented payloads transported over UDP. The proxy does not participate in QUIC version negotiation, handshake processing, connection ID management, or any other QUIC‑specific state machinery. All QUIC semantics remain strictly between the client and the backend QUIC server.

QUIC Forwarding Behavior

  • The proxy creates a UDP socket bound to an ephemeral port
  • The first application data packet received from the client MUST be forwarded as a single UDP datagram to the backend QUIC server
  • The proxy MUST NOT inspect, modify, or interpret any QUIC header fields, including but not limited to the long‑header form, version field, destination connection ID, source connection ID, token, length, or encrypted payload
  • The proxy MUST wait for a single UDP response from the backend
  • The response MUST be forwarded to the client as TLS application data
  • After forwarding the response, the proxy MAY close the connection

QUIC State Handling Requirements

  • The proxy MUST NOT attempt to maintain QUIC connection state
  • The proxy MUST NOT track or rewrite connection IDs
  • The proxy MUST NOT perform retransmissions, loss recovery, congestion control, or any other QUIC transport‑layer behavior
  • QUIC services MUST be explicitly declared as quic in service configuration to ensure correct handler selection

QUIC Limitations

The following limitations apply to QUIC‑based services.

The proxy does not implement any QUIC transport‑layer behavior, including but not limited to:

  • version negotiation
  • connection establishment
  • handshake processing
  • packet number management
  • retransmission
  • loss detection
  • congestion control

The proxy does not maintain per‑connection QUIC state and does not track connection IDs across packets.

The proxy does not support forwarding multiple QUIC datagrams in a single session unless explicitly implemented by the handler. This specification only requires forwarding of a single request datagram and a single response datagram.

The proxy does not support 0‑RTT data, session resumption, or any QUIC‑TLS integration features.

The proxy does not support multiplexing multiple QUIC streams within a single TLS session. Each TLS session corresponds to a single forwarded QUIC datagram exchange.

The proxy does not validate QUIC packet integrity, encryption, or formatting. All QUIC packets are treated as opaque byte sequences.

QUIC Security Considerations

The following security considerations apply to QUIC‑based services.

Because QUIC packets are encrypted end‑to‑end between the client and backend, the proxy cannot inspect or validate QUIC payloads. The proxy relies entirely on TLS‑level authentication and authorization for access control.

The proxy does not enforce QUIC version restrictions. Any QUIC version identifier present in the forwarded datagram is accepted and forwarded without validation.

The proxy does not protect against QUIC‑level amplification attacks. Implementations SHOULD ensure that backend QUIC servers enforce appropriate anti‑amplification limits.

The proxy does not enforce QUIC connection ID routing rules. If the backend relies on connection ID–based load balancing, the proxy MUST be positioned such that it does not interfere with backend routing semantics.

The proxy does not provide replay protection beyond what TLS provides. QUIC servers MUST implement their own replay mitigation as required by the QUIC transport specification.

Because QUIC is forwarded over UDP, backend QUIC servers MUST implement their own rate limiting, anti‑spoofing, and DoS protections.

Wire‑Level Packet Diagram

The following diagram illustrates the complete sequence of packets exchanged during a typical ZTP session.

Client                                      Proxy
------                                      -----

TCP SYN  --------------------------------->  
TCP SYN/ACK  <-----------------------------  
TCP ACK  --------------------------------->  

TLS ClientHello
  - SNI = service name
  - Client certificate
  ----------------------------------------->

TLS ServerHello
  <-----------------------------------------

TLS Encrypted Handshake
  <---------------------------------------->
(TLS session established)

Application Data (opaque bytes)
  ----------------------------------------->

Proxy performs:
  - Identity extraction
  - SNI lookup
  - Authorization
  - Protocol selection
  - Handler dispatch

Proxy -> Backend (TCP or UDP)
  ----------------------------------------->

Backend -> Proxy
  <-----------------------------------------

TLS Application Data (response)
  <-----------------------------------------

Client or Proxy closes connection
Security Considerations
  • All traffic between client and proxy is encrypted via TLS 1.3
  • Backend traffic is not encrypted unless the backend protocol provides its own security
  • Identity is bound to the TLS session and cannot be spoofed
  • SNI is authenticated and cannot be altered post‑handshake
  • UDP tunneling does not provide replay protection beyond what TLS provides

Protocol Translation

At the time of this writing, this is all theoretical. I know it should work, but writing a demo for this is a couple lines down on my TO DO list.

That said, nothing in the ZTP wire protocol specification, nor in this implementation of it, necessarily requires traffic always be forwarded to a backend. In fact, the whole reason that happens with the built-in handlers today is because I wrote them to do just that.

But the ZTP framework also allows for handlers of a different sort: protocol translators, in essence letting ZTP serve not only as a proxy but also a protocol gateway.

In theory, a handler can be written to act as a client unto itself. For example, a handler could be designed to act as a DUA -- a traditional DAP/LDAP directory client -- and receive instructions by way of HTTP GET, POST and DELETE commands from the end user, perhaps encoded as JSON.

Now, it is true: traffic is still "passing through a handler" from client to backend, so if we consider semantics, sure its still "kind of a proxy mechanism". But, more specifically, the handler is acting as a "go-between", converting HTTP codes and commands, setting the termination point and relaying those commands to the appropriate counterpart DUA functions for execution by the handler directly.

Just a thought :)

Documentation

Index

Constants

View Source
const TLSMinimumVersion uint16 = tls.VersionTLS13

TLSMinimumVersion defines the minimum acceptable TLS version honored by this package. Currently, this is TLS 1.3.

Variables

This section is empty.

Functions

func DirectoryGroupCheck

func DirectoryGroupCheck(id *Identity, groups []string) error

func DirectoryRequired

func DirectoryRequired(id *Identity) error

func Protect

func Protect(conn net.Conn, fn func() error)

Protect will protect the application from crashing due to panics. Instead, panics are recovered safely so as to not disconnect other users currently connected.

func RegisterProtocolDetector

func RegisterProtocolDetector(fn ProtocolDetector)

RegisterProtocolDetector allows users to add their own protocol detectors. These run BEFORE built-in detectors.

func RegisterProtocolName

func RegisterProtocolName(p Protocol, name string)

RegisterProtocolName lets users define names for custom protocols. Example:

const ProtocolRedis ztp.Protocol = 1000
ztp.RegisterProtocolName(ProtocolRedis, "REDIS")

func SendErrorFrame

func SendErrorFrame(conn net.Conn, code ErrorCode, msg string)

func Serve

func Serve(conn net.Conn, svc *Service, id *Identity) (err error)

Serve returns an error following an attempt to execute the Handler associated with the input *Service definition. Generally, this is the last operation needed in applications which import ztp to serve proxied services.

Types

type Authenticator

type Authenticator interface {
	Authenticate(context.Context, net.Conn, *tls.ConnectionState) (*Identity, error)
}

Authenticator implements an authentication provider, such as the TLSAuthenticator.

type CIDRCheck

type CIDRCheck struct {
	// contains filtered or unexported fields
}

func NewCIDRCheck

func NewCIDRCheck(cidrs []string) (*CIDRCheck, error)

func (*CIDRCheck) Check

func (r *CIDRCheck) Check(conn net.Conn) error

type ConsoleLogger

type ConsoleLogger struct{}

ConsoleLogger writes log messages via log.Println, log.Fatalln, log.Printf or log.Fatalf.

func (ConsoleLogger) Fatalf

func (_ ConsoleLogger) Fatalf(s string, args ...any)

func (ConsoleLogger) Fatalln

func (_ ConsoleLogger) Fatalln(args ...any)

func (ConsoleLogger) Printf

func (_ ConsoleLogger) Printf(s string, args ...any)

func (ConsoleLogger) Println

func (_ ConsoleLogger) Println(args ...any)

type Counter

type Counter struct {
	// contains filtered or unexported fields
}

Counter is a general use counter. Instances of this type are thread-safe.

func (*Counter) Count

func (r *Counter) Count() uint64

Count returns the current uint64 count from the underlying receiver instance.

func (*Counter) Decrement

func (r *Counter) Decrement()

Decrement decrements the counter by one (1).

func (*Counter) Increment

func (r *Counter) Increment()

Increment increments the counter by one (1).

type Directory

type Directory interface {
	// Lookup returns a *DirectoryEntry alongside an error
	// following an attempt to source an identity.
	Lookup(identity string) (*DirectoryRecord, error)

	// Type returns the string name held by the directory
	// (e.g.: "ldap", "yaml", et al).
	Type() string
}

type DirectoryCache

type DirectoryCache interface {
	Get(string) (*DirectoryRecord, bool)
	Set(string, *DirectoryRecord)
	Clear()
}

DirectoryCache implements a basic directory backend caching instance.

type DirectoryManager

type DirectoryManager struct {
	Cache DirectoryCache
	// contains filtered or unexported fields
}

DirectoryManager stores all directory backends for centralized access across various systems of record.

func NewDirectoryManager

func NewDirectoryManager(cache DirectoryCache, sources ...Directory) *DirectoryManager

NewDirectoryManager returns an instance of *DirectoryManager following an attempt to initialize and source one or more directory backends.

func (*DirectoryManager) Lookup

func (r *DirectoryManager) Lookup(identity string) (*DirectoryRecord, error)

Lookup returns an instance of *DirectoryRecord alongside an error following an attempt to lookup the specified identity within a backend.

func (DirectoryManager) Type

func (r DirectoryManager) Type() string

Type returns the string literal `manager`.

type DirectoryRecord

type DirectoryRecord struct {
	UserID      string              `json:"user_id"`
	Email       string              `json:"email"`
	DisplayName string              `json:"display_name"`
	Groups      []string            `json:"groups"`
	Attributes  map[string][]string `json:"attributes"`
}

Directory describes any single directory entry (e.g.: an identity).

type DirectoryRequiredCheck

type DirectoryRequiredCheck struct {
	Directory
}

func NewDirectoryRequiredCheck

func NewDirectoryRequiredCheck(dir Directory) *DirectoryRequiredCheck

func (*DirectoryRequiredCheck) Run

type Engine

type Engine interface {
	List() []string
	Lookup(string) (*Service, error)
	Authorize(*Identity, *Service) error
}

type ErrorCode

type ErrorCode int
const (
	AuthFailed ErrorCode = iota + 1
	IdentityInvalid
	ServiceNotFound
	PolicyDenied
	HandlerError
	InternalError
)

func (ErrorCode) String

func (r ErrorCode) String() string

type GroupMembershipCheck

type GroupMembershipCheck struct {
	Directory
	RequiredGroups []string
}

func NewGroupMembershipCheck

func NewGroupMembershipCheck(dir Directory, groups []string) *GroupMembershipCheck

func (*GroupMembershipCheck) Run

func (r *GroupMembershipCheck) Run(id *Identity) error

type HTTPProxyHandler

type HTTPProxyHandler struct{}

func NewHTTPProxyHandler

func NewHTTPProxyHandler() *HTTPProxyHandler

func (*HTTPProxyHandler) Handle

func (_ *HTTPProxyHandler) Handle(client net.Conn, svc *Service, id *Identity) error

func (*HTTPProxyHandler) Protocol

func (_ *HTTPProxyHandler) Protocol() Protocol

type Handler

type Handler interface {
	Protocol() Protocol
	Handle(net.Conn, *Service, *Identity) error
}

Handler implements a handler interface, usable by users in creating their own handlers for special protocols, or corner-cases in which standard protocols like TCP or QUIC need to be handled differently than the base handlers included in this package operate.

type Identity

type Identity struct {
	ID       string            `json:"id"`
	Type     string            `json:"type"`
	Groups   []string          `json:"groups,omitempty"`
	Metadata map[string]string `json:"metadata"`
	Source   string            `json:"source"`
	Entry    *DirectoryRecord  `json:"entry,omitempty"`
	Cert     *x509.Certificate `json:"client_cert,omitempty"`
}

Identity describes a particular user identity.

type IdentityMapper

type IdentityMapper func(*Identity) (string, error)

IdentityMapper defines a first class function signature for users to leverage in authoring their own username mapping scheme.

The most likely use case for this would be mapping an X.509 client certificate SubjectDN to a derived identity.

If no such function is devised, the raw identity is used as-is. This would mean, in the case of an X.509 Subject DN, it would likely manifest as something resembling:

<X.509 subject DN>,cn=external,cn=auth

... which may or may not be what you want.

type LDAPDirectory

type LDAPDirectory struct {
	Conn      *ldap.Conn
	BaseDN    string
	UserAttr  string
	GroupAttr string
}

LDAPDirectory describes a given X.500 (LDAP) backend.

func (*LDAPDirectory) Lookup

func (r *LDAPDirectory) Lookup(identity string) (*DirectoryRecord, error)

Lookup returns an instance of *DirectoryRecord alongside an error following an attempt to lookup the specified identity within a backend.

func (LDAPDirectory) Type

func (_ LDAPDirectory) Type() string

Type returns the string literal `ldap`.

type Logger

type Logger interface {
	Printf(string, ...any)
	Fatalf(string, ...any)

	Println(...any)
	Fatalln(...any)
}

Logger is qualified through facilities bearing the followint methods. A given qualifier of this interface can be made active via the ActiveLogger global variable.

var ActiveLogger Logger = ConsoleLogger{}

ActiveLogger contains an interface qualifier instance of the logging facility currently engaged within the package. Any Logger qualified facility may be made active here.

type Monitor

type Monitor struct {
	// contains filtered or unexported fields
}

Monitor implements the storage type for all metrics and monitoring information currently known to the server.

func NewMonitor

func NewMonitor() *Monitor

NewMonitor returns a freshly initialized instance of *Monitor.

func (*Monitor) AddConnection

func (r *Monitor) AddConnection(conn net.Conn, svc *Service) uint64

AddConnection returns an instance of uint64 following the addition of a new connection record instance to the underlying connection table. When complete, this method increments both the current and total connection counters by one (1).

The return uint64 value reflects the connection number assigned to the new connection record.

func (*Monitor) RemoveConnection

func (r *Monitor) RemoveConnection(id uint64) bool

RemoveConnection returns a Boolean value indicative of whether an attempt to delete the underlying net.Conn instance associated with the input uint64 id from the connection table was successful.

If successful, this method decrements the current connection counter by one (1).

func (*Monitor) Services

func (r *Monitor) Services() (svcs map[string]Service)

func (*Monitor) SetProtocol

func (r *Monitor) SetProtocol(id uint64, p Protocol)

SetProtocol updates the given connection -- as identified by the input uint64 value -- with the specified Protocol. This is necessary when a protocol has not been determined yet, for example, before a service lookup has been conducted.

func (*Monitor) Snapshot

func (r *Monitor) Snapshot() MonitorSnapshot

Snapshot returns a read-only copy of MonitorSnapshot, safe for access without risk of panic.

func (*Monitor) UpdateConnection

func (r *Monitor) UpdateConnection(id uint64, fn func(net.Conn))

UpdateConnection executes the input closure function (fn) against the net.Conn instance associated with the input uint64 id. Among other things, this is used to update the "last active" timestamp in the underlying connection table for a particular session.

type MonitorSnapshot

type MonitorSnapshot struct {
	Total    uint64                 `json:"total"`
	Current  uint64                 `json:"current"`
	Sessions []*monitoredConnection `json:"sessions"`
	Stats    MonitorStats           `json:"stats"`
	Services map[string]Service     `json:"services"`
}

MonitorSnapshot contains a read-only copy of the current metrics and connection table. Instances of this type are produced via the Monitor.Snapshot method.

type MonitorStats

type MonitorStats struct {
	StartTime       time.Time         `json:"start_time"`
	UptimeSeconds   uint64            `json:"uptime_seconds"`
	MaxConcurrent   uint64            `json:"max_concurrent"`
	ProtocolTotals  map[string]uint64 `json:"protocol_totals"`
	ProtocolCurrent map[string]uint64 `json:"protocol_current"`
}

MonitorStats contains high-level metrics pertaining to the ZTP service, such as total running time, protocol usage counts, etc. Instances of this type may be found within the *Monitor instance.

type NegativeCache

type NegativeCache interface {
	Get(string) bool
	Set(string)
}

type NullLogger

type NullLogger struct{}

NullLogger ignores all log messages.

func (NullLogger) Fatalf

func (_ NullLogger) Fatalf(_ string, _ ...any)

func (NullLogger) Fatalln

func (_ NullLogger) Fatalln(_ ...any)

func (NullLogger) Printf

func (_ NullLogger) Printf(_ string, _ ...any)

func (NullLogger) Println

func (_ NullLogger) Println(_ ...any)

type OIDCAuthConfig

type OIDCAuthConfig struct {
	Directory  *DirectoryManager
	SourceName string // e.g. "oidc"
}

type OIDCAuthenticator

type OIDCAuthenticator struct {
	// contains filtered or unexported fields
}

func NewOIDCAuthenticator

func NewOIDCAuthenticator(cfg OIDCAuthConfig) *OIDCAuthenticator

func (*OIDCAuthenticator) Authenticate

func (r *OIDCAuthenticator) Authenticate(
	ctx context.Context,
	req *http.Request,
) (*Identity, error)

type OIDCConfig

type OIDCConfig struct {
	IssuerURL string        // e.g. "http://127.0.0.1:5556/dex"
	ClientID  string        // e.g. "ztp"
	JWKSURL   string        // e.g. "http://127.0.0.1:5556/dex/keys"
	Timeout   time.Duration // HTTP timeout for discovery/JWKS

}

OIDCConfig defines the configuration parameters for an OIDC directory backend.

type OIDCDirectory

type OIDCDirectory struct {
	// contains filtered or unexported fields
}

OIDCDirectory implements Directory for an OIDC / JWT-based backend.

func NewOIDCDirectory

func NewOIDCDirectory(cfg OIDCConfig) *OIDCDirectory

func (*OIDCDirectory) Lookup

func (r *OIDCDirectory) Lookup(identity string) (*DirectoryRecord, error)

Lookup validates the provided ID token and returns a DirectoryRecord derived from its claims. ASSUMPTION: identity == raw ID token string.

func (*OIDCDirectory) Type

func (_ *OIDCDirectory) Type() string

Type returns the string literal `oidc`.

type PolicyEngine

type PolicyEngine struct {
	// contains filtered or unexported fields
}

PolicyEngine represents the standard package-included Engine qualifier. Users may use this instead of creating a custom Engine qualifier, if unneeded.

func NewPolicyEngine

func NewPolicyEngine(svcs []*Service, mon ...*Monitor) *PolicyEngine

NewPolicyEngine returns a freshly initialized instance of *PolicyEngine.

func (*PolicyEngine) Authorize

func (r *PolicyEngine) Authorize(id *Identity, svc *Service) error

Authorize returns an error following an attempt to authorize the provided *Identity for access to the specified *Service.

func (*PolicyEngine) List

func (r *PolicyEngine) List() (list []string)

List returns slices of names, each corresponding to a registered *Service instance within the receiver instance.

As the underlying index is map-based, the ordering of string slices is not fixed.

func (*PolicyEngine) Lookup

func (r *PolicyEngine) Lookup(name string) (*Service, error)

Lookup returns an instance of *Service alongside an error following an attempt to lookup the provided name within the receiver instance.

Case is significant in the matching process of name.

type PreAuthCheck

type PreAuthCheck interface {
	Check(conn net.Conn) error
}

type PreAuthPipeline

type PreAuthPipeline struct {
	// contains filtered or unexported fields
}

func NewPreAuthPipeline

func NewPreAuthPipeline(checks ...PreAuthCheck) *PreAuthPipeline

func (*PreAuthPipeline) Run

func (p *PreAuthPipeline) Run(conn net.Conn) error

type Protocol

type Protocol int
const (
	ProtocolUnknown Protocol = 0
	ProtocolTCP     Protocol = 1
	ProtocolUDP     Protocol = 2
	ProtocolQUIC    Protocol = 3
	ProtocolHTTP    Protocol = 4
	ProtocolSSH     Protocol = 5
)

Built‑in protocol constants. Users may define their own in their own packages.

func DetectProtocol

func DetectProtocol(r io.Reader) (Protocol, io.Reader, error)

DetectProtocol reads a small prefix from the stream, runs user-registered detectors first, then built-in detectors, and returns the detected protocol along with a reader that preserves the consumed prefix.

UDP, and those protocols upon which UDP is based, are not eligible for this form of detection.

func ParseProtocol

func ParseProtocol(name string) Protocol

ParseProtocol converts a config string (e.g. "http") into a Protocol enum. Users can extend this via RegisterProtocolName.

func (Protocol) MarshalJSON

func (p Protocol) MarshalJSON() ([]byte, error)

MarshalJSON exists merely for special handling support by the json encoding package.

func (Protocol) String

func (r Protocol) String() string

String returns the registered name for the protocol. If unknown, returns "UNKNOWN".

type ProtocolDetector

type ProtocolDetector func(prefix []byte) (Protocol, bool)

ProtocolDetector is a user-defined function that inspects the prefix and returns (protocol, true) if it recognizes the protocol.

type QUICProxyHandler

type QUICProxyHandler struct{}

func NewQUICProxyHandler

func NewQUICProxyHandler() *QUICProxyHandler

func (*QUICProxyHandler) Handle

func (_ *QUICProxyHandler) Handle(c net.Conn, svc *Service, id *Identity) error

func (*QUICProxyHandler) Protocol

func (_ *QUICProxyHandler) Protocol() Protocol

type Service

type Service struct {
	Name        string   `json:"listener_name"`
	Address     string   `json:"backend_name"`
	AllowedUser string   `json:"allowed_user,omitempty"`
	Groups      []string `json:"groups,omitempty"`

	Handler  `json:"-"`
	Protocol `json:"protocol"`
	// contains filtered or unexported fields
}

Service represents a single service to be registered within a qualifying instance of Engine.

func (*Service) MarshalJSON

func (r *Service) MarshalJSON() ([]byte, error)

func (*Service) Monitor

func (r *Service) Monitor() *Monitor

Monitor returns the underlying *Monitor instance, or nil if unset.

type TCPProxyHandler

type TCPProxyHandler struct{}

func NewTCPProxyHandler

func NewTCPProxyHandler() *TCPProxyHandler

func (*TCPProxyHandler) Handle

func (_ *TCPProxyHandler) Handle(client net.Conn, svc *Service, id *Identity) error

func (*TCPProxyHandler) Protocol

func (_ *TCPProxyHandler) Protocol() Protocol

Protocol returns the ProtocolTCP Protocol instance.

type TLSAuthConfig

type TLSAuthConfig struct {
	TrustedCAPEM []byte // PEM-encoded CA bundle
	IdentityRule TLSIdentityMappingRule
	Users        []TLSUserMapping // optional explicit mappings
	SourceName   string           // e.g. "tls"
	Directory    *DirectoryManager
}

TLSAuthConfig defines the parameters for authenticating via certificates. An instance of this type circumscribes an issuing certificate (PEM), a mapping rule and other elements.

type TLSAuthenticator

type TLSAuthenticator struct {
	// contains filtered or unexported fields
}

TLSAuthenticator implements the basis for TLS authentication. An instance of this type circumscribes an *x509.CertPool, a TLSAuthConfig and a user index.

func NewTLSAuthenticator

func NewTLSAuthenticator(cfg TLSAuthConfig) (*TLSAuthenticator, error)

NewTLSAuthentivator returns a freshly initialized instance of *TLSAuthenticator alongside an error. The input cfg defines the parameters of TLS auth.

func (*TLSAuthenticator) AuthConfig

func (r *TLSAuthenticator) AuthConfig() (cfg TLSAuthConfig)

AuthConfig returns the underlying instance of TLSAuthConfig.

func (*TLSAuthenticator) Authenticate

func (r *TLSAuthenticator) Authenticate(
	ctx context.Context,
	conn net.Conn,
	state *tls.ConnectionState,
) (*Identity, error)

Authenticate returns an instance of *Identity alongside an error following a call by the listener when a user attempts to utilize the proxy service via TLS authentication. This method accepts a context.Context, a net.Conn, and a *tls.ConnectionState.

func (*TLSAuthenticator) CRL

CRL returns the underlying instance of *x509.RevocationList, or nil if unset.

This method is thread-safe.

func (*TLSAuthenticator) CRLChanged

func (r *TLSAuthenticator) CRLChanged(a, b *x509.RevocationList) bool

CRLChanged returns a Boolean value indicative of *x509.RevocationList "a" differing from "b".

func (*TLSAuthenticator) IsRevoked

func (r *TLSAuthenticator) IsRevoked(cert *x509.Certificate) (revoked bool)

IsRevoked returns true if cert.SerialNumber is listed in the CRL.

func (*TLSAuthenticator) LoadCRL

func (r *TLSAuthenticator) LoadCRL(path string) (*x509.RevocationList, error)

LoadCRL returns an instance of *x509.RevocationList alongside an error following a one-time load of a CRL from disk (PEM or DER).

This method is NOT thread-safe on its own.

See *TLSAuthenticator.PollCRL for the thread-safe and continuous caller of this method.

func (*TLSAuthenticator) PollCRL

func (r *TLSAuthenticator) PollCRL(path string, interval time.Duration) (err error)

PollCRL performs a continuous call of *TLSAuthenticator.LoadCRL with a frequency of time.Duration. This method is thread-safe.

func (*TLSAuthenticator) TLSConfigForListener

func (r *TLSAuthenticator) TLSConfigForListener() *tls.Config

TLSConfigForListener returns an instance of *tls.Config, which is intended for submission to the ZTP Listener.

type TLSIdentityMappingRule

type TLSIdentityMappingRule struct {
	// One of these can be used to derive the Identity.ID
	UseSANEmail  bool // prefer SAN email if present
	UseSANDNS    bool // or SAN DNSName
	UseSubjectCN bool
}

TLSIdentityMappingRule defines the requirements for mapping a particular certificate to a particular user.

type TLSUserMapping

type TLSUserMapping struct {
	// Optional explicit mapping from cert attributes → canonical ID
	SubjectDN string   // full subject string match (e.g. "CN=Alice,...")
	Email     string   // SAN email
	Principal string   // resulting principal ID, e.g. "alice"
	Groups    []string // optional groups
}

TLSUserMapping implements the storage type for TLS certificate mapping logic, whereby a user's email address in a certificate can be mapped to an identity.

type TTLCache

type TTLCache struct {
	// contains filtered or unexported fields
}

TTLCache implements a caching type for directory entries.

func NewTTLCache

func NewTTLCache(ttl time.Duration) *TTLCache

NewTTLCache returns a freshly initialized instance of *TTLCache.

func (*TTLCache) Clear

func (r *TTLCache) Clear()

Clear purges all cached items from the receiver instance.

func (*TTLCache) Get

func (r *TTLCache) Get(id string) (*DirectoryRecord, bool)

Get returns a *DirectoryRecord instance alongside a Boolean value indicative of a successful call of id from within the receiver instance.

func (*TTLCache) Set

func (r *TTLCache) Set(id string, rec *DirectoryRecord)

Set associates a id with an instance of *DirectoryRecord within the receiver instance.

type UDPProxyHandler

type UDPProxyHandler struct{}

func NewUDPProxyHandler

func NewUDPProxyHandler() *UDPProxyHandler

func (*UDPProxyHandler) Handle

func (_ *UDPProxyHandler) Handle(client net.Conn, svc *Service, id *Identity) (err error)

func (*UDPProxyHandler) Protocol

func (_ *UDPProxyHandler) Protocol() Protocol

Protocol returns the ProtocolUDP Protocol instance.

type YAMLDirectory

type YAMLDirectory struct {
	// contains filtered or unexported fields
}

YAMLDirectory describes a YAML-based (flat file) backend.

func NewYAMLDirectory

func NewYAMLDirectory(x any) (y *YAMLDirectory, err error)

NewYAMLDirectory returns a freshly initialized instance of *YAMLDirectory alongside an error following an attempt to read x as YAML data. If x is a string, it is assumed the value represents a path/filename, while if []byte is used, it is assumed to be a pre-read YAML byte sequence. Any other type input is an error.

func (*YAMLDirectory) HasChanged

func (r *YAMLDirectory) HasChanged() (changed bool)

HasChanged returns a Boolean value indicative of whether the specified directory backend has been updated.

func (*YAMLDirectory) Lookup

func (r *YAMLDirectory) Lookup(identity string) (*DirectoryRecord, error)

func (*YAMLDirectory) Type

func (_ *YAMLDirectory) Type() string

Type returns the string literal `yaml`.

Directories

Path Synopsis
This DEMO package, h2l, provides an HTTP-to-LDAP frontend.
This DEMO package, h2l, provides an HTTP-to-LDAP frontend.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL