pkienginereceiver

package module
v0.148.0 Latest Latest
Warning

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

Go to latest
Published: Mar 22, 2026 License: Apache-2.0 Imports: 36 Imported by: 0

README

OpenTelemetry Collector PKI engine receiver

This OpenTelemetry Collector Receiver scrapes metrics for OpenBao and Vault PKI engines by auto-discovering mounts, issuers and certificate revocation lists (CRLs). It supports setups where the CRLs are hosted outside of the secret store, e.g., in multi-tier PKI setups with an offline root.

flowchart LR

  OTEL[OTEL Collector]

  subgraph Vault [OpenBao/Vault]
    direction TB

    subgraph PKI1 [PKI engine]
      direction LR
      I1[Issuer]
      C1[CRL]
    end
  end

  subgraph External [External]
    direction TB
    style External stroke-dasharray: 5 5
    CRL_Ext[CRL]
  end

  OTEL -- "1. Scrape" --> Vault
  I1 -- "2. Reference" --> CRL_Ext
  OTEL -- "3. Scrape" --> CRL_Ext

Features

  • Auto discover engines of type pki, issuers and CRLs (base & delta).
  • Collect referenced CRLs hosted outside of the secret store.
  • Tested against OpenBao and Vault.
  • Support for namespaces.
  • Resolve AIA URL templating.
  • Implemented CRL protocols:
    • HTTP
    • LDAP
  • Efficient CRL fetching:
    • Prevents thundering hurds.
    • Respects ETag and Last-Modified HTTP headers.
  • Authenticate via:
    • Token
    • AppRole
    • Kubernetes
    • JWT

Metrics

For collected metrics and attributes, see documentation.md.

Considerations

  • This receiver doesn't cover leaf certificates. It's recommended to monitor leaf certificates where they are actively used via e.g. tlscheckreceiver. When using cert-manager on Kubernetes consider collecting Certificate resources via the k8sobjectsreceiver.

  • Tidy metrics which includes counters for issued and revoked certificates can be collected via the exposed telemetry endpoints by the secret store and scraped via e.g. Prometheus jobs. See secrets.pki.tidy.* metrics (OpenBao | Vault). For extra large deployments consider disabling metric pkiengine.mount.certificates_stored and rely on Tidy metrics as secret stores efficiently track changes to maintain internal counters.

Installation

Adding components to an OTeL Collector requires building a custom collector via OpenTelemetry Collector builder (or ocb for short).

When building a custom collector you can add this receiver to the manifest file like the following:

receivers:
  - gomod: github.com/cvdtang/pkienginereceiver v0.148.0

Configuration

Example

receivers:
  pkiengine:
    address: https://openbao.example.com
    namespace: tenant-a
    collection_interval: 5m
    crl:
      retries: 1
    auth:
      type: approle
      approle:
        role_id: "my-role-id"
        secret_id: "my-secret-id"

Options

  • address (string): (default = http://127.0.0.1:8200) Address of secret store. Must be formatted as {protocol}://{host}.
  • collection_interval (string): (default = 5m) How frequently the scraper should be called. This value must be a string readable by Golang's time.ParseDuration. Valid time units are ns, us (or µs), ms, s, m, h.
  • initial_delay (string): (default = 0s) Initial start delay for the scraper.
  • timeout (string): Optional value used to set scraper's context deadline.
  • namespace (string): (default = "") Secret store namespace path.
  • match_regex (string): (default = ".*") Regular expression in RE2 syntax of allowed mount paths, e.g. pki/v1/ica/v\d.
  • concurrency_limit (uint): Maximum number of concurrent worker tasks (mount/issuer/CRL). Defaults to the number of CPU cores determined by GOMAXPROCS.
CRL
  • crl.enabled (bool): (default = true) Enable CRL processing.
  • crl.scrape_parent (bool): (default = true) Enable scraping issuer-role (parent) CRLs referenced in issuer certificates. When false, only subject-role CRLs from issuer API fields are scraped.
  • crl.cache_size (uint): (default = 50) Maximum number of entries in the LRU cache for processed CRLs. Set to 0 to disable CRL caching.
  • crl.timeout (string): (default = 5s) Maximum time to wait for a CRL fetch response.
  • crl.retries (uint): (default = 0) Number of retry attempts for CRL fetching after the initial attempt.
  • crl.retry_interval (string): (default = 3s) Wait interval between CRL fetch retry attempts.
Authentication
  • auth.type (string): (default = token) Authentication method to use. Allowed values: token, approle, kubernetes and jwt.
Token

Docs: OpenBao | Vault

  • auth.token.token (string): Static authentication token.
receivers:
  pkiengine:
    auth:
      type: token
      token:
        token: ${env:TOKEN}
AppRole

Docs: OpenBao | Vault

  • auth.approle.role_id (string): AppRole RoleID.
  • auth.approle.secret_id (string): AppRole SecretID.
  • auth.approle.wrapping_token (bool): (default = false) Use single-use token that prevents the underlying credential from being exposed in transit.
  • auth.approle.mount_path (string): (default = approle) Mount path of the AppRole auth engine.
receivers:
  pkiengine:
    auth:
      type: approle
      approle:
        role_id: ${env:APPROLE_ROLE_ID}
        secret_id: ${env:APPROLE_SECRET_ID}
Kubernetes

Docs: OpenBao | Vault

  • auth.kubernetes.role_name (string): Name of the secret store role with Kubernetes service account bound to it.
  • auth.kubernetes.service_account_token (string): Override Kubernetes service account JWT.
  • auth.kubernetes.service_account_token_path (string): Path where the Kubernetes service account token is mounted in the pod.
  • auth.kubernetes.mount_path (string): (default = kubernetes) Mount path of the Kubernetes auth engine.

The kubernetes auth method can be used to authenticate with the secret stores using a Bound Service Account Token. Defaults to /var/run/secrets/kubernetes.io/serviceaccount/token:

receivers:
  pkiengine:
    auth:
      type: kubernetes
      kubernetes:
        role_name: my-role

Alternatively, long-lived tokens can be used:

receivers:
  pkiengine:
    auth:
      type: kubernetes
      kubernetes:
        role_name: my-role
        service_account_token: ${env:SERVICE_ACCOUNT_TOKEN}
JWT

Docs: OpenBao | Vault

  • auth.jwt.role_name (string): Name of the role in OpenBao/Vault with JWT claims bound to it.
  • auth.jwt.token (string): JWT token to use for authentication.
  • auth.jwt.token_path (string): Path to a file containing the JWT token.
  • auth.jwt.mount_path (string): (default = jwt) Mount path of the JWT auth engine.

From value:

receivers:
  pkiengine:
    auth:
      type: jwt
      jwt:
        role_name: my-role
        token: ${env:JWT_TOKEN}

From file:

receivers:
  pkiengine:
    auth:
      type: jwt
      jwt:
        role_name: my-role
        token_path: /my/token
Metrics & Resource Attributes
  • metrics.<name>.enabled (bool): (default = true) Enable/disable emitting the specified metric.
  • resource_attributes.<name>.enabled (bool): (default = true) Enable/disable emitting the specified resource attribute.

Additionally, the Vault Go SDK environment variables can be set, e.g. VAULT_SKIP_VERIFY.

Authorization policy

Required permissions used by the receiver.

Note that the pki/* paths can differ depending on where the PKI engines are mounted.

# ref: https://developer.hashicorp.com/vault/api-docs/system/mounts#list-mounted-secrets-engines
path "sys/mounts" {
  capabilities = [ "read" ]
}

# ref: https://developer.hashicorp.com/vault/api-docs/secret/pki#read-cluster-configuration
path "pki/config/cluster" {
  capabilities = [ "read" ]
}

# ref: https://developer.hashicorp.com/vault/api-docs/secret/pki#list-certificates
path "pki/certs" {
  capabilities = [ "list" ]
}

# ref: https://developer.hashicorp.com/vault/api-docs/secret/pki#read-issuer
path "pki/issuer/+" {
  capabilities = [ "read" ]
}

Troubleshooting

Enable OTeL Collector debug logging:

receivers:
  pkiengine:
    address: http://127.0.0.1:8200
    initial_delay: 0s
    collection_interval: 1m

service:
  telemetry:
    logs:
      level: debug

  pipelines:
    metrics:
      receivers: [pkiengine]
      exporters: [debug]

Development

For development, internals and testing, see DEVELOPMENT.md.

Documentation

Index

Constants

View Source
const (
	AuthTypeToken      = "token"
	AuthTypeAppRole    = "approle"
	AuthTypeKubernetes = "kubernetes"
	AuthTypeJWT        = "jwt"
)

Variables

View Source
var (
	ErrEmptySecret = errors.New("empty secret")
)
View Source
var ErrNotModified = errors.New("CRL not modified")

Functions

func NewFactory

func NewFactory() receiver.Factory

Types

type AuthAppRole

type AuthAppRole struct {
	RoleID        string              `mapstructure:"role_id"`
	SecretID      configopaque.String `mapstructure:"secret_id"`
	WrappingToken bool                `mapstructure:"wrapping_token"`
	MountPath     string              `mapstructure:"mount_path"`
}

type AuthConfig

type AuthConfig struct {
	AuthType       string         `mapstructure:"type"`
	AuthToken      AuthToken      `mapstructure:"token"`
	AuthAppRole    AuthAppRole    `mapstructure:"approle"`
	AuthKubernetes AuthKubernetes `mapstructure:"kubernetes"`
	AuthJWT        AuthJWT        `mapstructure:"jwt"`
}

type AuthJWT

type AuthJWT struct {
	RoleName  string              `mapstructure:"role_name"`
	Token     configopaque.String `mapstructure:"token"`
	TokenPath string              `mapstructure:"token_path"`
	MountPath string              `mapstructure:"mount_path"`
}

type AuthKubernetes

type AuthKubernetes struct {
	RoleName                string              `mapstructure:"role_name"`
	ServiceAccountToken     configopaque.String `mapstructure:"service_account_token"`
	ServiceAccountTokenPath string              `mapstructure:"service_account_token_path"`
	MountPath               string              `mapstructure:"mount_path"`
}

type AuthToken

type AuthToken struct {
	Token configopaque.String `mapstructure:"token"`
}

type CRL

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

func NewCRL

func NewCRL(
	logger *zap.Logger,
	shared *ScrapeShared,
	uri string,
	role metadata.AttributeCrlRole,
	kind metadata.AttributeCrlKind,
) *CRL

Creates a CRL processor for a single issuer CRL URI.

func (*CRL) Collect

func (c *CRL) Collect(ctx context.Context) (crlMetrics, error)

Fetches, parses and caches CRL data.

func (*CRL) Emit

func (c *CRL) Emit(mb *metadata.MetricsBuilder, metrics crlMetrics)

Records CRL metrics to the metrics builder.

type Certificate

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

func NewCertificate

func NewCertificate(
	state *ScrapeShared,
	mount string,
	issuerId string,
	certificate string,
) Certificate

func (*Certificate) Collect

func (c *Certificate) Collect() error

func (*Certificate) Emit

func (c *Certificate) Emit()

type Config

type Config struct {
	scraperhelper.ControllerConfig `mapstructure:",squash"`
	metadata.MetricsBuilderConfig  `mapstructure:",squash"`
	Address                        string     `mapstructure:"address"`
	Namespace                      string     `mapstructure:"namespace"`
	MatchRegex                     string     `mapstructure:"match_regex"`
	ConcurrencyLimit               uint       `mapstructure:"concurrency_limit"`
	Crl                            CrlConfig  `mapstructure:"crl"`
	Auth                           AuthConfig `mapstructure:"auth"`
	// contains filtered or unexported fields
}

func (*Config) Validate

func (c *Config) Validate() error

type CrlConfig

type CrlConfig struct {
	Enabled       bool          `mapstructure:"enabled"`
	ScrapeParent  bool          `mapstructure:"scrape_parent"`
	CacheSize     uint          `mapstructure:"cache_size"`
	Timeout       time.Duration `mapstructure:"timeout"`
	Retries       uint          `mapstructure:"retries"`
	RetryInterval time.Duration `mapstructure:"retry_interval"`
}

type Issuer

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

func NewIssuer

func NewIssuer(
	logger *zap.Logger,
	secretStore secretStore,
	state *ScrapeShared,

	mountPath string,
	id string,
	clusterConfig clusterConfig,
) Issuer

Creates an issuer processor for a mount and issuer ID.

func (*Issuer) Collect

func (ie *Issuer) Collect(ctx context.Context) (issuerResult, error)

Reads issuer data, processes the certificate and prepares CRL tasks.

type LDAPURI

type LDAPURI struct {
	Scheme     string   // ldap, ldaps, ldapi
	Host       string   // hostname or IP
	Port       string   // port number (e.g., "389")
	DN         string   // Distinguished Name (path)
	Attributes []string // List of attributes to retrieve
	Scope      string   // base, one, or sub
	Filter     string   // Search filter
	Extensions []string // Extension strings
}

Components of a parsed LDAP URI.

type Mount

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

func NewMount

func NewMount(
	logger *zap.Logger,
	secretStore secretStore,
	state *ScrapeShared,

	path string,
) Mount

Creates a mount processor for a PKI engine mount path.

func (*Mount) Collect

func (m *Mount) Collect(ctx context.Context) (mountResult, error)

Reads mount config, mount metrics and issuer IDs.

type ScrapeShared

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

Common, per-scrape shared state.

type Vault

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

func NewVault

func NewVault(ctx context.Context, cfg Config, logger *zap.Logger) (*Vault, error)

Creates a Vault client and performs initial authentication.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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