meshauth

package module
v0.0.0-...-0569425 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2025 License: Apache-2.0 Imports: 17 Imported by: 31

README

Minimal mesh-focused auth library

This is an attempt to have a minimal, low-resource/WASM friendly library with no external dependencies encapsulating the most common mesh auth and provisioning patterns.

The code is in large part based on/forked from Istio and few of my projects, to remove dependencies and extract the minimum required.

Goals

  • minimal code - only auth features commonly used in mesh, with no external dependency
  • provide common networking abstractions and platform adapters, but without large dependencies and using a common model for gRPC/REST/K8S-REST.

The platform and k8s integrations are focused on bootstraping and getting tokens.

Config

Provisioning and config are separated from 'mesh' - Istio is using gRPC/XDS, but ztunnel can also use a local config file. XDS is a big dependency, as is K8S client library.

I am also more interested in signed configurations - where the transport doesn't matter and control plane is not trusted (zero-trust).

Unlike Istio - 'on demand' is the only option. Pushing all configs (SOW) is possible, with the configs saved to disk (cached) and still loaded on demand.

Certs and Tokens

It is my belief that 'certificates' and 'tokens' are about the same thing - signed claims, using JSON or DER format (or CBOR, etc). As such, every workload, every user, config providers are expected to have (at least) one private key and to configure at least one trusted 'mesh root'.

Unlike Istio, the relation is very granular (and 'federated') - a workload may only trust the public key of the workload owner (whoever operates the workload and has platform permission to exec and run the code on the specific machine), so each deployment and 'service account' can have a different root of trust.

Configs determine the signer for each destination - and may use different sources or mechanisms, including public CA infra, public DNS(SEC), private DNSSEC or any other mechanism appropriate for the service. The configs are either signed or retrieved from a trusted source (like a trusted control plane)

Certs

The certs package has many helpers to work with certificates - including a CA, similar to Istio Citadel but minimized.

The 'internet certificates' rely on 'certificate authorities', which have a huge power (can sign any domain). The Internet CAs have the 'isCA' bit set, and verification of internet cert chains requires the isCA bit.

However, a 'cert' without 'isCA' is still a claim, under the authority of the signer - just like a JWT issued by an IDP. The JWT provider is not trusted for all identities on the internet - only for a domain.

Just like OIDC or ATproto bootstrap the trust for a FQDN by did:web or the 'well known' public keys, a signing key for certificates associated with a domain can be configured or loaded.

Tokens

This library includes very minimal code to parse/verify JWT and extract info. It is not complete or intended as a general library - just as a minimal one, focused for the tokens used in K8S/Istio - as well as Webpush VAPID tokens.

It includes both verification and issuing/signing code. Like certificates, any workload can sign 'claims'.

Webpush

This package also include the basic primitives for using Webpush - crypto and VAPID. While webpush is primarily used for push messages to browsers, it is a very interesting mechanism for mesh config and events.

Packages

  • certs: utils around certificate signing, verification. Deps free.
  • tokens: utils around token signing, verification - including OIDC. Deps free.
  • webpush: encrypt/decrypt - VAPID is in tokens.
  • xnet: retry and network helpers.
  • meshauth(certs,tokens): Dest and common mesh properties.
  • uk8s(tokens,meshauth,xnet): utils around K8S tokens and raw access - not using K8S libs
  • ugcp(tokens,xnet,meshauth,xnet): utils around GCP integration (not using GCP libs) - MDS, STS, etc
Environment and Defaults

The certs/JWT also include identity information that can be extracted - trust domain, namespace, etc.

  • Root certificates for mesh communication: - /var/run/secrets/.... (platform provided roots), /etc/ssl/certs/ca-certificates.crt (Istio injected pods default), XDS_ROOT_CA, CA_ROOT_CA, system certificates.

  • Workload identity: cert provisioned by CertManager, Istio, etc

  • Trust domain: extracted from cert or JWT, TRUST_DOMAIN. For GKE expected to be PROJECT_ID.svc.id.goog format.

  • Namespace: POD_NAMESPACE, extracted from cert/JWT

  • Pod name: POD_NAME, hostname

  • Service account: SERVICE_ACCOUNT, extracted from cert/JWT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	TokenPayload = tokens.TokenPayload
	DecodeJWT    = tokens.DecodeJWT
	JwtRawParse  = tokens.JwtRawParse
)
View Source
var NewListener func(ctx context.Context, addr string) net.Listener

Functions

func Listen

func Listen(addr string) (net.Listener, error)

func ListenAndServe

func ListenAndServe(addr string, f func(conn net.Conn)) (net.Listener, error)

func Load

func Load(ctx context.Context, d *Dest, obj interface{}, kind, ns string, name string) error

Load will populate the object from a K8Service-like service.

'kind' can be 'configmap', 'secret', etc for using K8Service-style URL format

Types

type ContextDialer

type ContextDialer interface {
	// Dial with a context based on tls package - 'once successfully
	// connected, any expiration of the context will not affect the
	// connection'.
	DialContext(ctx context.Context, net, addr string) (net.Conn, error)
}

type Dest

type Dest struct {

	// Addr is the main (VIP or URL) address of the destination.
	//
	// For HTTP, Addr is the URL, including any required path prefix, for the destination.
	//
	// For TCP, Addr is the TCP address - VIP:port or DNS:port format. tcp:// is assumed.
	//
	// For K8S service it should be in the form serviceName.namespace.svc:svcPort
	// The cluster suffix is assumed to be 'cluster.local' or the custom k8s suffix,
	// equivalent to cluster.server in kubeconfig.
	//
	// This can also be an 'original dst' address for individual endpoints.
	//
	// Individual IPs (real or relay like waypoints or egress GW, etc) will be in
	// the info.addrs field.
	Addr string `json:"addr,omitempty"`

	// Proto determines how to talk with the Dest.
	// Could be 'hbone' (compat with Istio), 'h2c', 'h2', 'ssh', etc.
	// If not set - Dial will use regular TCP or TLS, depending on the other settings.
	Proto string `json:"protocol,omitempty"`

	// Name is the 'basename' or alias of the service.
	//
	Name string `json:"id,omitempty"`

	// If empty, the cluster is using system certs or SPIFFE CAs - as configured in
	// Mesh.
	//
	// Otherwise, it's the configured root certs list, in PEM format.
	// May include multiple concatenated roots.
	//
	// TODO: allow root SHA only.
	// TODO: move to trust config
	CACertPEM string `json:"ca_cert,omitempty"`

	// Sources of trust for validating the peer destination.
	// Typically, a certificate - if not set, SYSTEM certificates will be used for non-mesh destinations
	// and the MESH certificates for destinations using one of the mesh domains.
	// If not set, the nodes' trust config is used.
	// TrustConfig *tokens.TrustConfig `json:"trust,omitempty"`
	// If set, Bearer tokens will be added.
	TokenProvider TokenSource `json:-`

	// If set, a token source with this name is used. The provider must be set in MeshEnv.AuthProviders
	// If not found, no tokens will be added. If found, errors getting tokens will result
	// in errors connecting.
	// In K8S - it will be the well-known token file.
	TokenSource string `json:"tokens,omitempty"`

	// If set, the destination is using HTTP protocol - and this is the roundtripper to use.
	// HttpClient() returns a http client initialized for this destination.
	// For special cases ( reverse connections in h2r ) it will be a *http2.ClientConn.
	//
	RoundTripper          http.RoundTripper `json:-`
	InsecureSkipTLSVerify bool              `json:"insecure,omitempty"`

	Dialer ContextDialer `json:-`

	// L4Secure is set if the destination can be reached over a secure L4 network (ambient, VPC, IPSec, secure CNI, etc)
	L4Secure bool `json:"l4secure,omitempty"`

	Backoff time.Duration `json:"-"`
	// contains filtered or unexported fields
}

Dest represents the metadata associated with an address.

It is discovered from multiple sources - local config, control plane, DNS.

This is primarily concerned with the security aspects (auth, transport), and include 'trusted' attributes from K8S configs or JWT/cert.

K8S clusters can be represented as a Dest - rest.Config Host is Addr, CACertPEM, same for all REST or gRPC services. For L7 destinations a HttpClient and RoundTripper are created based on the metadata. This is required because the Transport is associated with the trust configs.

func (*Dest) AddTrustPEM

func (d *Dest) AddTrustPEM(c []byte)

func (*Dest) BackoffReset

func (d *Dest) BackoffReset()

BackoffReset and Sleep implement the backoff interface

func (*Dest) BackoffSleep

func (d *Dest) BackoffSleep()

func (*Dest) HttpClient

func (d *Dest) HttpClient() *http.Client

HttpClient returns a http client configured to talk with this Dest using a secure connection. - if CACertPEM is set, will be used instead of system

The client is cached in Dest.

func (*Dest) RestClient

func (d *Dest) RestClient(g, v string) (*RESTRequest, error)

func (*Dest) RoundTrip

func (d *Dest) RoundTrip(r *http.Request) (*http.Response, error)

func (*Dest) String

func (d *Dest) String() string

type Discoverer

type Discoverer interface {

	// Discover will use configs, DNS, XDS, K8S or other means to find the properties of a destination.
	// If none is found, the default is:
	// - if addr is a general FQDN, use it as a standard internet destination.
	// - if addr is a public IP - use the standard Dialer.
	// - if addr has '.internal' or '.local' - use mesh root certificates and HBONE or SSH protocol.
	// - for private IP ranges - same
	//
	// Custom Discover can set egress gateways or adjust.
	Discover(ctx context.Context, addr string) (*Dest, error)
}

type HttpProvider

type HttpProvider interface {

	// The module must use domain and prefix to register with the mux.
	RegisterMux(mux *http.ServeMux)
}

The normal pattern of registering in 'init' or startup is simple but lacks flexibility. It panics on duplicates, can't remove.

Instead the listener and http servers use a handler that allows swapping the Mux, and each module can be re-created with a new Mux.

type Mesh

type Mesh struct {
	*MeshCfg

	// ResourceStore is used to load configs and data on-demand and unmarshal
	// them to the appropriate type.
	//
	// It acts as a filesystem.
	ResourceStore ResourceStore `json:"-"`

	// Primary workload ID TLS certificate and private key. Loaded or generated.
	// Default is to use EC256 certs. The private key can be used to sign JWTs.
	// The public key and sha can be used as a node identity.
	Cert *certs.Certs `json:"-"`

	// AuthProviders - matching kubeconfig user.authProvider.name
	// It is expected to return tokens with the given audience - in case of GCP
	// returns access tokens. If not set the cluster can't be created.
	//
	// A number of pre-defined token sources are used:
	// - gcp - returns GCP access tokens using MDS or default credentials. Used for example by GKE clusters.
	// - k8s - return K8S WorkloadID tokens with the given audience for default K8S cluster.
	// - istio-ca - returns K8S tokens with istio-ca audience - used by Citadel and default Istiod
	// - sts - federated google access tokens associated with GCP identity pools.
	AuthProviders map[string]TokenSource `json:"-"`

	// Default dialer used to connect to host:port extracted from metadata.
	// Defaults to net.Dialer, making real connections.
	//
	// Can be replaced with a mux or egress dialer or router for
	// integrations.
	NetDialer ContextDialer `json:"-"`

	// Mux is used for HTTP and gRPC handler exposed externally.
	//
	// It is the default handler for "hbone" and "hbonec" protocol handlers.
	//
	// The HTTP server on localhost:15000 uses http.DefaultServerMux -
	// which is also used by pprof and others by default and can't be changed.
	// It could also be exposed with 'admin' auth wrapper.
	Mux *http.ServeMux `json:"-"`

	HandlerWrapper func(hf http.Handler, op string) http.Handler `json:"-"`
	RTWrapper      func(rt http.RoundTripper) http.RoundTripper  `json:"-"`
	// contains filtered or unexported fields
}

Mesh represents a workload identity and associated info required for minimal mesh-compatible security. Includes helpers for authentication and basic provisioning.

A workload may be associated with multiple service accounts and identity providers, and may have multiple certificates.

func FromEnv

func FromEnv(ctx context.Context, base string) (*Mesh, error)

FromEnv will attempt to identify and Init the certificates. This should be called from main() and for normal app use.

New can be used in tests or for fine control over what cert is loaded.

- default GKE/Istio location for workload identity - /var/run/secrets/...FindC - /etc/istio/certs - $HOME/.ssh/id_ecdsa - if it is in standard pem format

ssh-keygen -t ecdsa -b 256 -m PEM -f id_ecdsa

If a cert is found, the identity is extracted from the cert. The platform is expected to refresh the cert.

If a cert is not found, Cert field will be nil, and the app should use one of the methods of getting a cert or call InitSelfSigned.

func New

func New() *Mesh

func (*Mesh) DialContext

func (mesh *Mesh) DialContext(ctx context.Context, network, addr string) (net.Conn, error)

DialContext should connect to the address, using one of the modules and config - falling back to the default dialer.

Normal golang - network is "tcp" and address is host:port - or custom values are allowed.

All forwarding/tunneling methods should call this method to establish outbound connections.

func (*Mesh) Discover

func (mesh *Mesh) Discover(ctx context.Context, addr string) (*Dest, error)

func (*Mesh) GetToken

func (mesh *Mesh) GetToken(ctx context.Context, aud string) (string, error)

GetToken is the default token source for the object. Will try: - MDS - AuthProvider["K8S"],"GCP" - locally signed token, using the mesh private key.

TODO: add some audience policy - for example googleapis.com or .mesh.internal.

func (*Mesh) L4HttpTransport

func (mesh *Mesh) L4HttpTransport(ctx context.Context) *http.Transport

func (*Mesh) Provision

func (mesh *Mesh) Provision(ctx context.Context) error

type MeshCfg

type MeshCfg struct {
	ConfigLocation string

	// AuthnConfig defines the trust config for the node - the list of signers that are trusted for specific
	// issuers and domains, audiences, etc.
	//
	// Based on Istio jwtRules, but generalized to all signer types.
	//
	// Authz is separated - this only defines who do we trust (and policies on what we trust it for)
	//
	// Destinations and listeners may have their own AuthnConfig - this is the default.
	tokens.AuthnConfig `json:",inline"`

	// Trusted roots, in DER format.
	// Deprecated - AuthnConfig
	RootCertificates [][]byte `json:"roots,omitempty"`

	// Domain is extracted from the cert or set by user, used to verify
	// peer certificates. If not set, will be populated when cert is loaded.
	// Should be a real domain with OIDC keys or platform specific.
	// NOT cluster.local
	Domain string `json:"domain,omitempty"`

	DomainAliases []string `json:"domainAliases,omitempty"`

	// Namespace and Name are extracted from the certificate or set by user.
	// Namespace is used to verify peer certificates
	Namespace string `json:"namespace,omitempty"`

	// Name of the service account. Can be an email or spiffee or just the naked name.
	Name string `json:"name,omitempty"`

	// Deprecated - MDS
	ProjectID string `json:"project_id,omitempty"`
	GSA       string `json:"gsa,omitempty"`

	// Authz: Additional namespaces to allow access from. If no authz rule is set, 'same namespace'
	// and 'istio-system' are allowed.
	AllowedNamespaces []string `json:"allow_namespaces,omitempty"`

	// DER public key
	PublicKey []byte `json:"pub,omitempty"`

	// MeshAddr is a URL or string representing the primary (bootstrap) address
	// for the mesh config - can be a K8S cluster, XDS server, file.
	MeshAddr string `json:"meshAddr,omitempty"`

	// Dst contains pre-configured or discovered properties for destination services.
	// When running in K8S, "KUBERNETES" is set with the in-cluster config.
	// A .kube/config file can be converted to dst if a subset of auth is used.
	//
	// K8S Services, SSH hosts, etc are also represented as Dst.
	Dst map[string]*Dest `json:"dst,omitempty"`

	TCPUserTimeout time.Duration

	// Timeout used for TLS or SSH handshakes. If not set, 3 seconds is used.
	HandsahakeTimeout time.Duration
}

MeshCfg is used to configure the mesh basic settings related to security.

type Module

type Module struct {
	Address string `json:"address,omitempty"`

	// Modules that listen to a port should use this listener or implement a
	// Handle or HandleConn method.
	NetListener net.Listener `json:"-"`

	// TODO: rename to 'remoteAddress' or 'dest' - use to indicate an address to use as client
	ForwardTo string `json:"forwardTo,omitempty"`

	// Provides access to the config, DialContext, RoundTripper, tokens and certs
	Mesh *Mesh `json:"-"`

	// Handler for the accepted connections.
	Handler string `json:"handler,omitempty"`

	// The module native interface.
	Module any `json:"-"`

	ConnHandler func(net.Conn) `json:"-"`
	// contains filtered or unexported fields
}

Module abstract a Listener and a component capable of handling accepted connections (and UDP equivalent).

The low level net.Listener needs to be abstracted - the accepted streams may be forwarded, encrypted, etc.

func (*Module) Accept

func (l *Module) Accept() (net.Conn, error)

func (*Module) Addr

func (l *Module) Addr() net.Addr

func (*Module) Close

func (l *Module) Close() error

func (*Module) OnConnection

func (l *Module) OnConnection(c net.Conn) error

func (*Module) Provision

func (m *Module) Provision(ctx context.Context) error

func (*Module) Start

func (m *Module) Start() error

func (*Module) String

func (m *Module) String() string

type RESTRequest

type RESTRequest struct {

	// Must be set for namespaced resources ('default' can be used)
	// If not set - cluster-wide resources.
	Namespace string

	// Type - required for normal K8Service resources.
	Kind string

	// Namespaced name - required for normal K8Service resources.
	Name string

	// If set - will be used instead of /api/v1/namespaces/{NS}/{KIND}s/{NAME}
	Path string

	// This is the resource body, typically json. Will be uploaded as POST.
	Spec []byte

	// If set, will be added at the end (must include ?). For example ?watch=0
	Query string

	// Defaults to GET if no Spec, POST otherwise.
	Method string

	Dest *Dest
}

RESTRequest is a REST or K8Service style request. Will be used with a Dest.

It is based on/inspired from kelseyhightower/konfig - which uses the 'raw' K8Service protocol to avoid a big dependency. Google, K8Service and many other APIs have a raw representation and don't require complex client libraries and dependencies.

No code generation or protos are used - raw JSON in []byte is used, caller can handle marshalling.

Close to K8Service raw REST client - but without the builder style.

func (*RESTRequest) Do

func (k *RESTRequest) Do(ctx context.Context, dest *Dest) ([]byte, error)

func (*RESTRequest) HttpRequest

func (kr *RESTRequest) HttpRequest(ctx context.Context, d *Dest) *http.Request

HttpRequest creates the populated http.Request for connecting to a K8S-like server.

The destination has the Addr and config (credentials, etc).

func (*RESTRequest) Post

func (k *RESTRequest) Post() *RESTRequest

type ResourceStore

type ResourceStore interface {
	Get(ctx context.Context, s string) (any, error)
}

type TokenSource

type TokenSource interface {
	// GetToken for a given audience.
	GetToken(context.Context, string) (string, error)
}

TokenSource is a common interface for anything returning Bearer or other kind of tokens.

Directories

Path Synopsis
cmd module
pkg
certs
Package certs provides helpers around certificates.
Package certs provides helpers around certificates.
h2c
tokens
tokens implements helpers around signed and auth tokens
tokens implements helpers around signed and auth tokens
oidc module

Jump to

Keyboard shortcuts

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