Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,35 @@ The following encryption methods are supported:
| `tls` | Enable TLS |
| `mtls` | Enable mutual TLS |

## Metrics Export

The `metrics_export` configuration enables exporting Prometheus metrics via OTLP to an OpenTelemetry Collector, independently of distributed tracing.

```yaml
metrics_export:
type: otlp/http
address: localhost:4318
export_interval_ms: 5000
service_name: opa
encryption: "off"
allow_insecure_tls: false
tls_ca_cert_file: /path/to/ca.pem
tls_cert_file: /path/to/cert.pem
tls_private_key_file: /path/to/key.pem
```

| Field | Type | Required | Description |
| ------------------------------------- | -------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `metrics_export.type` | `string` | No | `"otlp/grpc"` or `"otlp/http"`. Omit (or `""`) to disable metrics export. |
| `metrics_export.address` | `string` | No (default: `localhost:4317` if `type` is `otlp/grpc`, `localhost:4318` if `type` is `otlp/http`) | Address of the OpenTelemetry Collector endpoint. |
| `metrics_export.export_interval_ms` | `int` | No (default: `60000`) | Interval between metric exports in milliseconds. Must be > 0. |
| `metrics_export.service_name` | `string` | No (default: `opa`) | Logical name of the service reported in exported metrics. |
| `metrics_export.encryption` | `string` | No (default: `off`) | Configures TLS: `off`, `tls`, or `mtls`. |
| `metrics_export.allow_insecure_tls` | `bool` | No (default: `false`) | Allow insecure TLS. |
| `metrics_export.tls_ca_cert_file` | `string` | No | The path to the root CA certificate. |
| `metrics_export.tls_cert_file` | `string` | No (unless `encryption` equals `mtls`) | The path to the client certificate to authenticate with. |
| `metrics_export.tls_private_key_file` | `string` | No (unless `tls_cert_file` provided) | The path to the private key of the client certificate. |

## Disk Storage

The `storage` configuration key allows for enabling, and configuring, the
Expand Down
12 changes: 8 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,18 @@ require (
github.com/vektah/gqlparser/v2 v2.5.32
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415
github.com/yashtewari/glob-intersection v0.2.0
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0
go.opentelemetry.io/otel/sdk v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.opentelemetry.io/proto/otlp v1.9.0
go.uber.org/automaxprocs v1.6.0
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/net v0.52.0
Expand Down Expand Up @@ -99,8 +104,8 @@ require (
github.com/olekukonko/ll v0.0.9 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
Expand All @@ -114,8 +119,7 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/sys v0.42.0 // indirect
Expand Down
18 changes: 12 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
Expand Down Expand Up @@ -222,10 +222,16 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk=
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
Expand All @@ -246,8 +252,8 @@ go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
92 changes: 16 additions & 76 deletions internal/distributedtracing/distributedtracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ package distributedtracing
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"os"
"strings"
"time"

Expand All @@ -25,6 +22,7 @@ import (
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
"google.golang.org/grpc/credentials"

"github.com/open-policy-agent/opa/internal/tlsutil"
"github.com/open-policy-agent/opa/v1/config"
"github.com/open-policy-agent/opa/v1/logging"
"github.com/open-policy-agent/opa/v1/util"
Expand Down Expand Up @@ -112,36 +110,31 @@ func Init(ctx context.Context, raw []byte, id string) (*otlptrace.Exporter, *tra
return nil, nil, nil, nil
}

certificate, err := loadCertificate(distributedTracingConfig.TLSCertFile, distributedTracingConfig.TLSCertPrivateKeyFile)
certificate, err := tlsutil.LoadCertificate(distributedTracingConfig.TLSCertFile, distributedTracingConfig.TLSCertPrivateKeyFile)
if err != nil {
return nil, nil, nil, err
}

certPool, err := loadCertPool(distributedTracingConfig.TLSCACertFile)
certPool, err := tlsutil.LoadCertPool(distributedTracingConfig.TLSCACertFile)
if err != nil {
return nil, nil, nil, err
}

tlsConfig, err := tlsutil.BuildTLSConfig(distributedTracingConfig.EncryptionScheme, *distributedTracingConfig.EncryptionSkipVerify, certificate, certPool)
if err != nil {
return nil, nil, nil, err
}

var traceExporter *otlptrace.Exporter
if strings.EqualFold(distributedTracingConfig.Type, "grpc") {
tlsOption, err := grpcTLSOption(distributedTracingConfig.EncryptionScheme, *distributedTracingConfig.EncryptionSkipVerify, certificate, certPool)
if err != nil {
return nil, nil, nil, err
}

traceExporter = otlptracegrpc.NewUnstarted(
otlptracegrpc.WithEndpoint(distributedTracingConfig.Address),
tlsOption,
grpcTLSOption(distributedTracingConfig.EncryptionScheme, tlsConfig),
)
} else if strings.EqualFold(distributedTracingConfig.Type, "http") {
tlsOption, err := httpTLSOption(distributedTracingConfig.EncryptionScheme, *distributedTracingConfig.EncryptionSkipVerify, certificate, certPool)
if err != nil {
return nil, nil, nil, err
}

traceExporter = otlptracehttp.NewUnstarted(
otlptracehttp.WithEndpoint(distributedTracingConfig.Address),
tlsOption,
httpTLSOption(distributedTracingConfig.EncryptionScheme, tlsConfig),
)
}

Expand Down Expand Up @@ -301,71 +294,18 @@ func (c *distributedTracingConfig) validateAndInjectDefaults() error {
return nil
}

func loadCertificate(tlsCertFile, tlsPrivateKeyFile string) (*tls.Certificate, error) {

if tlsCertFile != "" && tlsPrivateKeyFile != "" {
cert, err := tls.LoadX509KeyPair(tlsCertFile, tlsPrivateKeyFile)
if err != nil {
return nil, err
}
return &cert, nil
}

if tlsCertFile != "" || tlsPrivateKeyFile != "" {
return nil, errors.New("distributed_tracing.tls_cert_file and distributed_tracing.tls_private_key_file must be specified together")
}

return nil, nil
}

func loadCertPool(tlsCACertFile string) (*x509.CertPool, error) {
if tlsCACertFile == "" {
return nil, nil
}

caCertPEM, err := os.ReadFile(tlsCACertFile)
if err != nil {
return nil, fmt.Errorf("read CA cert file: %v", err)
}
pool := x509.NewCertPool()
if ok := pool.AppendCertsFromPEM(caCertPEM); !ok {
return nil, fmt.Errorf("failed to parse CA cert %q", tlsCACertFile)
}
return pool, nil
}

func grpcTLSOption(encryptionScheme string, encryptionSkipVerify bool, cert *tls.Certificate, certPool *x509.CertPool) (otlptracegrpc.Option, error) {
func grpcTLSOption(encryptionScheme string, tlsConfig *tls.Config) otlptracegrpc.Option {
if encryptionScheme == "off" {
return otlptracegrpc.WithInsecure(), nil
}
tlsConfig := &tls.Config{
RootCAs: certPool,
InsecureSkipVerify: encryptionSkipVerify,
}
if encryptionScheme == "mtls" {
if cert == nil {
return nil, errors.New("distributed_tracing.tls_cert_file required but not supplied")
}
tlsConfig.Certificates = []tls.Certificate{*cert}
return otlptracegrpc.WithInsecure()
}
return otlptracegrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)), nil
return otlptracegrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))
}

func httpTLSOption(encryptionScheme string, encryptionSkipVerify bool, cert *tls.Certificate, certPool *x509.CertPool) (otlptracehttp.Option, error) {
func httpTLSOption(encryptionScheme string, tlsConfig *tls.Config) otlptracehttp.Option {
if encryptionScheme == "off" {
return otlptracehttp.WithInsecure(), nil
}
tlsConfig := &tls.Config{
RootCAs: certPool,
InsecureSkipVerify: encryptionSkipVerify,
}
if encryptionScheme == "mtls" {
if cert == nil {
return nil, errors.New("distributed_tracing.tls_cert_file required but not supplied")
}
tlsConfig.Certificates = []tls.Certificate{*cert}
return otlptracehttp.WithInsecure()
}
return otlptracehttp.WithTLSClientConfig(tlsConfig), nil
return otlptracehttp.WithTLSClientConfig(tlsConfig)
}

type errorHandler struct {
Expand Down
18 changes: 18 additions & 0 deletions internal/distributedtracing/distributedtracing_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2024 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.

package distributedtracing

import "testing"

func TestInitNoTypeReturnsAllNil(t *testing.T) {
raw := []byte(`{"distributed_tracing": {}}`)
exp, tp, res, err := Init(t.Context(), raw, "test")
if err != nil {
t.Fatal(err)
}
if exp != nil || tp != nil || res != nil {
t.Fatal("expected all nil when type is not set")
}
}
Loading
Loading