mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-13 03:42:35 -06:00
40dd2b90d2
Follow-up to #8900. Moves the gzip encoding and decoding config validation off the Go `validateAndInjectDefaults` methods and onto embedded Rego policies, injecting defaults and reporting value errors. Each config registers its recognized options via `config.RegisterConfigSpec` so unknown-option warnings live with the owning struct. Field type validation stays in the Go decode step. --------- Signed-off-by: Sebastian Spaink <sebastianspaink@gmail.com>
1149 lines
38 KiB
Go
1149 lines
38 KiB
Go
// Copyright 2016 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 runtime
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"crypto/rand"
|
||
"crypto/tls"
|
||
"crypto/x509"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
mr "math/rand"
|
||
"net/http"
|
||
"net/url"
|
||
"os"
|
||
"os/signal"
|
||
"strings"
|
||
"sync"
|
||
"syscall"
|
||
"time"
|
||
|
||
"github.com/fsnotify/fsnotify"
|
||
prometheus_sdk "github.com/prometheus/client_golang/prometheus"
|
||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||
"go.opentelemetry.io/otel/propagation"
|
||
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||
|
||
"github.com/open-policy-agent/opa/internal/compiler"
|
||
"github.com/open-policy-agent/opa/internal/config"
|
||
internal_tracing "github.com/open-policy-agent/opa/internal/distributedtracing"
|
||
internal_logging "github.com/open-policy-agent/opa/internal/logging"
|
||
internal_metrics "github.com/open-policy-agent/opa/internal/metricsexport"
|
||
"github.com/open-policy-agent/opa/internal/pathwatcher"
|
||
"github.com/open-policy-agent/opa/internal/prometheus"
|
||
"github.com/open-policy-agent/opa/internal/ref"
|
||
initload "github.com/open-policy-agent/opa/internal/runtime/init"
|
||
"github.com/open-policy-agent/opa/internal/uuid"
|
||
"github.com/open-policy-agent/opa/internal/versioncheck"
|
||
"github.com/open-policy-agent/opa/v1/ast"
|
||
"github.com/open-policy-agent/opa/v1/bundle"
|
||
opa_config "github.com/open-policy-agent/opa/v1/config"
|
||
"github.com/open-policy-agent/opa/v1/hooks"
|
||
"github.com/open-policy-agent/opa/v1/loader"
|
||
"github.com/open-policy-agent/opa/v1/logging"
|
||
"github.com/open-policy-agent/opa/v1/metrics"
|
||
"github.com/open-policy-agent/opa/v1/plugins"
|
||
"github.com/open-policy-agent/opa/v1/plugins/discovery"
|
||
filelogger "github.com/open-policy-agent/opa/v1/plugins/logger/file"
|
||
"github.com/open-policy-agent/opa/v1/plugins/logs"
|
||
metrics_config "github.com/open-policy-agent/opa/v1/plugins/server/metrics"
|
||
"github.com/open-policy-agent/opa/v1/repl"
|
||
"github.com/open-policy-agent/opa/v1/runtime/info"
|
||
"github.com/open-policy-agent/opa/v1/server"
|
||
"github.com/open-policy-agent/opa/v1/storage"
|
||
"github.com/open-policy-agent/opa/v1/storage/disk"
|
||
"github.com/open-policy-agent/opa/v1/storage/inmem"
|
||
"github.com/open-policy-agent/opa/v1/tracing"
|
||
"github.com/open-policy-agent/opa/v1/util"
|
||
"github.com/open-policy-agent/opa/v1/version"
|
||
)
|
||
|
||
var (
|
||
registeredPlugins map[string]plugins.Factory
|
||
registeredPluginsMux sync.Mutex
|
||
|
||
registeredStorageBackend StorageBackendBuilder
|
||
registeredStorageBackendMux sync.Mutex
|
||
)
|
||
|
||
const (
|
||
// default interval between OPA version report uploads after startup (1h)
|
||
defaultInitialUploadInterval = time.Hour
|
||
// upload interval when OPA has been running for 6+ hrs (6h)
|
||
defaultLaterUploadInterval = 6 * time.Hour
|
||
)
|
||
|
||
// StorageBackendBuilder defines a function that creates a storage.Store instance.
|
||
// This is the signature used for registering custom storage backends via
|
||
// RegisterStorageBackend. The function receives the same parameters as the
|
||
// StoreBuilder field in Params, allowing custom backends to initialize properly.
|
||
type StorageBackendBuilder func(ctx context.Context, logger logging.Logger, registerer prometheus_sdk.Registerer, config []byte, id string) (storage.Store, error)
|
||
|
||
// RegisterPlugin registers a plugin factory with the runtime
|
||
// package. When the runtime is created, the factories are used to parse
|
||
// plugin configuration and instantiate plugins. If no configuration is
|
||
// provided, plugins are not instantiated. This function is idempotent.
|
||
func RegisterPlugin(name string, factory plugins.Factory) {
|
||
registeredPluginsMux.Lock()
|
||
defer registeredPluginsMux.Unlock()
|
||
registeredPlugins[name] = factory
|
||
}
|
||
|
||
// RegisterStorageBackend registers a custom storage backend builder.
|
||
// If registered, it will be used instead of the default inmem storage.
|
||
// Implement storage.Closer for resource cleanup during shutdown.
|
||
func RegisterStorageBackend(builder StorageBackendBuilder) {
|
||
registeredStorageBackendMux.Lock()
|
||
defer registeredStorageBackendMux.Unlock()
|
||
registeredStorageBackend = builder
|
||
}
|
||
|
||
// Params stores the configuration for an OPA instance.
|
||
type Params struct {
|
||
// Globally unique identifier for this OPA instance. If an ID is not specified,
|
||
// the runtime will generate one.
|
||
ID string
|
||
|
||
// Addrs are the listening addresses that the OPA server will bind to.
|
||
Addrs *[]string
|
||
|
||
// DiagnosticAddrs are the listening addresses that the OPA server will bind to
|
||
// for read-only diagnostic API's (/health, /metrics, etc)
|
||
DiagnosticAddrs *[]string
|
||
|
||
// H2CEnabled flag controls whether OPA will allow H2C (HTTP/2 cleartext) on
|
||
// HTTP listeners.
|
||
H2CEnabled bool
|
||
|
||
// Authentication is the type of authentication scheme to use.
|
||
Authentication server.AuthenticationScheme
|
||
|
||
// Authorization is the type of authorization scheme to use.
|
||
Authorization server.AuthorizationScheme
|
||
|
||
// Certificate is the certificate to use in server-mode. If the certificate
|
||
// is nil, the server will NOT use TLS.
|
||
Certificate *tls.Certificate
|
||
|
||
// CertificateFile and CertificateKeyFile are the paths to the cert and its
|
||
// keyfile. It'll be used to periodically reload the files from disk if they
|
||
// have changed. The server will attempt to refresh every 5 minutes, unless
|
||
// a different CertificateRefresh time.Duration is provided
|
||
CertificateFile string
|
||
CertificateKeyFile string
|
||
CertificateRefresh time.Duration
|
||
|
||
// CertPool holds the CA certs trusted by the OPA server.
|
||
CertPool *x509.CertPool
|
||
// CertPoolFile, if set permits the reloading of the CA cert pool from disk
|
||
CertPoolFile string
|
||
|
||
// MinVersion contains the minimum TLS version that is acceptable.
|
||
// If zero, TLS 1.2 is currently taken as the minimum.
|
||
MinTLSVersion uint16
|
||
|
||
// HistoryPath is the filename to store the interactive shell user
|
||
// input history.
|
||
HistoryPath string
|
||
|
||
// Output format controls how the REPL will print query results.
|
||
// Default: "pretty".
|
||
OutputFormat string
|
||
|
||
// Paths contains filenames of base documents and policy modules to load on
|
||
// startup. Data files may be prefixed with "<dotted-path>:" to indicate
|
||
// where the contained document should be loaded.
|
||
Paths []string
|
||
|
||
// Optional filter that will be passed to the file loader.
|
||
Filter loader.Filter
|
||
|
||
// BundleMode will enable treating the Paths provided as bundles rather than
|
||
// loading all data & policy files.
|
||
BundleMode bool
|
||
|
||
// Watch flag controls whether OPA will watch the Paths files for changes.
|
||
// If this flag is true, OPA will watch the Paths files for changes and
|
||
// reload the storage layer each time they change. This is useful for
|
||
// interactive development.
|
||
Watch bool
|
||
|
||
// ErrorLimit is the number of errors the compiler will allow to occur before
|
||
// exiting early.
|
||
ErrorLimit int
|
||
|
||
// PprofEnabled flag controls whether pprof endpoints are enabled
|
||
PprofEnabled bool
|
||
|
||
// DecisionIDFactory generates decision IDs to include in API responses
|
||
// sent by the server (in response to Data API queries.)
|
||
DecisionIDFactory func() string
|
||
|
||
// Logging configures the logging behaviour.
|
||
Logging LoggingConfig
|
||
|
||
// Logger sets the logger implementation to use for debug logs.
|
||
Logger logging.Logger
|
||
|
||
// ConsoleLogger sets the logger implementation to use for console logs.
|
||
ConsoleLogger logging.Logger
|
||
|
||
// ConfigFile refers to the OPA configuration to load on startup.
|
||
ConfigFile string
|
||
|
||
// ConfigOverrides are overrides for the OPA configuration that are applied
|
||
// over top the config file They are in a list of key=value syntax that
|
||
// conform to the syntax defined in the `strval` package
|
||
ConfigOverrides []string
|
||
|
||
// ConfigOverrideFiles Similar to `ConfigOverrides` except they are in the
|
||
// form of `key=path/to/file`where the file contains the value to be used.
|
||
ConfigOverrideFiles []string
|
||
|
||
// Output is the output stream used when run as an interactive shell. This
|
||
// is mostly for test purposes.
|
||
Output io.Writer
|
||
|
||
// ConsoleInput is the reader the interactive shell reads query input from.
|
||
// When nil, os.Stdin is used. Mostly for tests and non-terminal hosts.
|
||
ConsoleInput io.Reader
|
||
|
||
// GracefulShutdownPeriod is the time (in seconds) to wait for the http
|
||
// server to shutdown gracefully.
|
||
GracefulShutdownPeriod int
|
||
|
||
// ShutdownWaitPeriod is the time (in seconds) to wait before initiating shutdown.
|
||
ShutdownWaitPeriod int
|
||
|
||
// EnableVersionCheck flag controls whether OPA will report its version to an external service.
|
||
// If this flag is true, OPA will report its version to the external service
|
||
EnableVersionCheck bool
|
||
|
||
// BundleVerificationConfig sets the key configuration used to verify a signed bundle
|
||
BundleVerificationConfig *bundle.VerificationConfig
|
||
|
||
// SkipBundleVerification flag controls whether OPA will verify a signed bundle
|
||
SkipBundleVerification bool
|
||
|
||
// BundleActivatorPlugin controls the name of the activator plugin used to load bundles into the store.
|
||
BundleActivatorPlugin string
|
||
|
||
// BundleLazyLoadingMode flag controls whether OPA will load bundle contents in lazy mode.
|
||
BundleLazyLoadingMode bool
|
||
|
||
// SkipKnownSchemaCheck flag controls whether OPA will perform type checking on known input schemas
|
||
SkipKnownSchemaCheck bool
|
||
|
||
// ReadyTimeout flag controls if and for how long OPA server will wait (in seconds) for
|
||
// configured bundles and plugins to be activated/ready before listening for traffic.
|
||
// A value of 0 or less means no wait is exercised.
|
||
ReadyTimeout int
|
||
|
||
// Router is the router to which handlers for the REST API are added.
|
||
// Router uses a first-matching-route-wins strategy, so no existing routes are overridden
|
||
// If it is nil, a new http.ServeMux will be created
|
||
Router *http.ServeMux
|
||
|
||
// DiskStorage, if set, will make the runtime instantiate a disk-backed storage
|
||
// implementation (instead of the default, in-memory store).
|
||
// It can also be enabled via config, and this runtime field takes precedence.
|
||
DiskStorage *disk.Options
|
||
|
||
// StoreBuilder allows passing a storage backend builder
|
||
StoreBuilder func(_ context.Context, _ logging.Logger, _ prometheus_sdk.Registerer, config []byte, id string) (storage.Store, error)
|
||
|
||
DistributedTracingOpts tracing.Options
|
||
|
||
// Check if default Addr is set or the user has changed it.
|
||
AddrSetByUser bool
|
||
|
||
// UnixSocketPerm specifies the permission for the Unix domain socket if used to listen for connections
|
||
UnixSocketPerm *string
|
||
|
||
// V0Compatible will enable OPA features and behaviors that were enabled by default in OPA v0.x releases.
|
||
// Takes precedence over V1Compatible.
|
||
V0Compatible bool
|
||
|
||
// V1Compatible will enable OPA features and behaviors that will be enabled by default in a future OPA v1.0 release.
|
||
// This flag allows users to opt-in to the new behavior and helps transition to the future release upon which
|
||
// the new behavior will be enabled by default.
|
||
// If V0Compatible is set, V1Compatible will be ignored.
|
||
V1Compatible bool
|
||
|
||
// CipherSuites specifies the list of enabled TLS 1.0–1.2 cipher suites
|
||
CipherSuites *[]uint16
|
||
|
||
// ReadAstValuesFromStore controls whether the storage layer should return AST values when reading from the store.
|
||
// This is an eager conversion, that comes with an upfront performance cost when updating the store (e.g. bundle updates).
|
||
// Evaluation performance is affected in that data doesn't need to be converted to AST during evaluation.
|
||
// Only applicable when using the default in-memory store, and not when used together with the DiskStorage option.
|
||
ReadAstValuesFromStore bool
|
||
|
||
// ExtraDiscoveryOpts allows for passing options to the discovery plugin, as instantiated by the runtime.
|
||
ExtraDiscoveryOpts []func(*discovery.Discovery)
|
||
|
||
// Hooks is our generic extension mechanism.
|
||
Hooks hooks.Hooks
|
||
|
||
// NDBCacheEnabled allows enabling the non-deterministic builtin cache globally.
|
||
NDBCacheEnabled bool
|
||
|
||
Brand string
|
||
}
|
||
|
||
func (p *Params) regoVersion() ast.RegoVersion {
|
||
// v0 takes precedence over v1
|
||
if p.V0Compatible {
|
||
return ast.RegoV0
|
||
}
|
||
if p.V1Compatible {
|
||
return ast.RegoV1
|
||
}
|
||
return ast.DefaultRegoVersion
|
||
}
|
||
|
||
func (p *Params) parserOptions() ast.ParserOptions {
|
||
return ast.ParserOptions{
|
||
ProcessAnnotation: true,
|
||
RegoVersion: p.regoVersion(),
|
||
}
|
||
}
|
||
|
||
// LoggingConfig stores the configuration for OPA's logging behaviour.
|
||
type LoggingConfig struct {
|
||
Level string
|
||
Format string
|
||
TimestampFormat string
|
||
}
|
||
|
||
// NewParams returns a new Params object.
|
||
func NewParams() Params {
|
||
return Params{
|
||
Output: os.Stdout,
|
||
BundleMode: false,
|
||
EnableVersionCheck: false,
|
||
GracefulShutdownPeriod: 1,
|
||
Brand: "OPA", // default
|
||
}
|
||
}
|
||
|
||
type ServerStatus int
|
||
|
||
const (
|
||
ServerNotStarted ServerStatus = iota
|
||
ServerWaitingForPlugins
|
||
ServerInitialized
|
||
ServerStopped
|
||
)
|
||
|
||
// Runtime represents a single OPA instance.
|
||
type Runtime struct {
|
||
Params Params
|
||
Store storage.Store
|
||
Manager *plugins.Manager
|
||
|
||
logger logging.Logger
|
||
server *server.Server
|
||
metrics *prometheus.Provider
|
||
versionChecker versioncheck.Checker
|
||
traceExporter *otlptrace.Exporter
|
||
meterProvider *sdkmetric.MeterProvider
|
||
loadedPathsResult *initload.LoadPathsResult
|
||
|
||
serverStatus ServerStatus
|
||
serverInitMtx sync.RWMutex
|
||
done chan struct{}
|
||
repl *repl.REPL
|
||
}
|
||
|
||
// NewRuntime returns a new Runtime object initialized with params. Clients must
|
||
// call StartServer() or StartREPL() to start the runtime in either mode.
|
||
func NewRuntime(ctx context.Context, params Params) (*Runtime, error) {
|
||
if params.ID == "" {
|
||
var err error
|
||
params.ID, err = generateInstanceID()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
level, err := internal_logging.GetLevel(params.Logging.Level)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// NOTE(tsandall): This is a temporary hack to ensure that log formatting
|
||
// and leveling is applied correctly. Currently there are a few places where
|
||
// the global logger is used as a fallback, however, that fallback _should_
|
||
// never be used. This ensures that _if_ the fallback is used accidentally,
|
||
// that the logging configuration is applied. Once we remove all usage of
|
||
// the global logger and we remove the API that allows callers to access the
|
||
// global logger, we can remove this.
|
||
logging.Get().SetFormatter(internal_logging.GetFormatter(params.Logging.Format, params.Logging.TimestampFormat))
|
||
logging.Get().SetLevel(level)
|
||
|
||
var logger logging.Logger
|
||
|
||
if params.Logger != nil {
|
||
logger = params.Logger
|
||
} else {
|
||
// Always use BufferedLogger to capture early startup logs
|
||
// After plugins start, we'll flush to either a logger plugin or StandardLogger
|
||
bufferedLogger := logging.NewBufferedLogger(1000)
|
||
bufferedLogger.SetLevel(level)
|
||
logger = bufferedLogger
|
||
}
|
||
|
||
if err := params.Hooks.Validate(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var filePaths []string
|
||
urlPathCount := 0
|
||
for _, path := range params.Paths {
|
||
if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
|
||
urlPathCount++
|
||
override, err := urlPathToConfigOverride(urlPathCount, path)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
params.ConfigOverrides = append(params.ConfigOverrides, override...)
|
||
} else {
|
||
filePaths = append(filePaths, path)
|
||
}
|
||
}
|
||
params.Paths = filePaths
|
||
|
||
config, err := config.Load(params.ConfigFile, params.ConfigOverrides, params.ConfigOverrideFiles)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("config error: %w", err)
|
||
}
|
||
|
||
var versionChecker versioncheck.Checker
|
||
if params.EnableVersionCheck {
|
||
var err error
|
||
versionChecker, err = versioncheck.New(versioncheck.Options{Logger: logger})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("config error: %w", err)
|
||
}
|
||
}
|
||
|
||
loaded, err := initload.LoadPathsForRegoVersion(params.parserOptions(), params.Paths, params.Filter, params.BundleMode, params.BundleVerificationConfig, params.SkipBundleVerification, params.BundleLazyLoadingMode, false, nil)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("load error: %w", err)
|
||
}
|
||
|
||
isAuthorizationEnabled := params.Authorization != server.AuthorizationOff
|
||
|
||
runtimeInfo, err := info.NewWithOptions(info.Options{Config: config, IsAuthorizationEnabled: isAuthorizationEnabled, SkipKnownSchemaCheck: params.SkipKnownSchemaCheck})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
consoleLogger := params.ConsoleLogger
|
||
if consoleLogger == nil {
|
||
l := logging.New()
|
||
l.SetFormatter(internal_logging.GetFormatter(params.Logging.Format, params.Logging.TimestampFormat))
|
||
consoleLogger = l
|
||
}
|
||
|
||
if params.Router == nil {
|
||
params.Router = http.NewServeMux()
|
||
}
|
||
|
||
metricsConfig, parseConfigErr := extractMetricsConfig(ctx, config, params)
|
||
if parseConfigErr != nil {
|
||
return nil, parseConfigErr
|
||
}
|
||
metrics := prometheus.New(metrics.New(), errorLogger(logger), metricsConfig.Prom.HTTPRequestDurationSeconds.Buckets)
|
||
|
||
var store storage.Store
|
||
if params.DiskStorage == nil {
|
||
params.DiskStorage, err = disk.OptionsFromConfig(config, params.ID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("parse disk store configuration: %w", err)
|
||
}
|
||
}
|
||
|
||
// If no explicit StoreBuilder is set, check for a registered custom backend
|
||
if params.StoreBuilder == nil {
|
||
registeredStorageBackendMux.Lock()
|
||
params.StoreBuilder = registeredStorageBackend
|
||
registeredStorageBackendMux.Unlock()
|
||
}
|
||
|
||
switch {
|
||
case params.DiskStorage != nil:
|
||
store, err = disk.New(ctx, logger, metrics, *params.DiskStorage)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("initialize disk store: %w", err)
|
||
}
|
||
case params.StoreBuilder != nil:
|
||
store, err = params.StoreBuilder(ctx, logger, metrics, config, params.ID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("initialize store: %w", err)
|
||
}
|
||
default:
|
||
store = inmem.NewWithOpts(inmem.OptRoundTripOnWrite(false),
|
||
inmem.OptReturnASTValuesOnRead(params.ReadAstValuesFromStore))
|
||
}
|
||
|
||
traceExporter, tracerProvider, _, err := internal_tracing.Init(ctx, config, params.ID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("config error: %w", err)
|
||
}
|
||
|
||
meterProvider, err := internal_metrics.Init(ctx, config, params.ID, metrics.Gatherer())
|
||
if err != nil {
|
||
return nil, fmt.Errorf("config error: %w", err)
|
||
}
|
||
if tracerProvider != nil {
|
||
params.DistributedTracingOpts = tracing.NewOptions(
|
||
otelhttp.WithTracerProvider(tracerProvider),
|
||
otelhttp.WithPropagators(propagation.TraceContext{}),
|
||
)
|
||
}
|
||
|
||
manager, err := plugins.New(config,
|
||
params.ID,
|
||
store,
|
||
plugins.Info(runtimeInfo),
|
||
plugins.InitBundles(loaded.Bundles),
|
||
plugins.InitFiles(loaded.Files),
|
||
plugins.MaxErrors(params.ErrorLimit),
|
||
plugins.GracefulShutdownPeriod(params.GracefulShutdownPeriod),
|
||
plugins.ConsoleLogger(consoleLogger),
|
||
plugins.Logger(logger),
|
||
plugins.EnablePrintStatements(logger.GetLevel() >= logging.Info),
|
||
plugins.PrintHook(loggingPrintHook{logger: logger}),
|
||
plugins.WithRouter(params.Router),
|
||
plugins.WithPrometheusRegister(metrics),
|
||
plugins.WithTracerProvider(tracerProvider),
|
||
plugins.WithEnableVersionCheck(params.EnableVersionCheck),
|
||
plugins.WithParserOptions(params.parserOptions()),
|
||
plugins.WithDistributedTracingOpts(params.DistributedTracingOpts),
|
||
plugins.WithBundleActivatorPlugin(params.BundleActivatorPlugin),
|
||
plugins.WithHooks(params.Hooks),
|
||
plugins.WithMinTLSVersion(params.MinTLSVersion),
|
||
plugins.WithCipherSuites(params.CipherSuites),
|
||
)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("config error: %w", err)
|
||
}
|
||
|
||
// Surface non-fatal config warnings (e.g. unrecognized options).
|
||
for _, w := range manager.Config.Warnings {
|
||
logger.Warn(w)
|
||
}
|
||
|
||
if err := manager.Init(ctx); err != nil {
|
||
return nil, fmt.Errorf("initialization error: %w", err)
|
||
}
|
||
|
||
if isAuthorizationEnabled && !params.SkipKnownSchemaCheck {
|
||
if err := verifyAuthorizationPolicySchema(manager); err != nil {
|
||
return nil, fmt.Errorf("initialization error: %w", err)
|
||
}
|
||
}
|
||
|
||
var bootConfig map[string]any
|
||
err = util.Unmarshal(config, &bootConfig)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("config error: %w", err)
|
||
}
|
||
|
||
opts := make([]func(*discovery.Discovery), 0, len(params.ExtraDiscoveryOpts)+3)
|
||
opts = append(opts,
|
||
discovery.Factories(registeredPlugins),
|
||
discovery.Metrics(metrics),
|
||
discovery.BootConfig(bootConfig),
|
||
)
|
||
opts = append(opts, params.ExtraDiscoveryOpts...)
|
||
disco, err := discovery.New(manager, opts...)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("config error: %w", err)
|
||
}
|
||
|
||
manager.Register(discovery.Name, disco)
|
||
|
||
rt := &Runtime{
|
||
Store: manager.Store,
|
||
Params: params,
|
||
Manager: manager,
|
||
logger: logger,
|
||
metrics: metrics,
|
||
versionChecker: versionChecker,
|
||
serverStatus: ServerNotStarted,
|
||
traceExporter: traceExporter,
|
||
meterProvider: meterProvider,
|
||
loadedPathsResult: loaded,
|
||
}
|
||
|
||
return rt, nil
|
||
}
|
||
|
||
// extractMetricsConfig returns the configuration for server metrics and parsing errors if any
|
||
func extractMetricsConfig(ctx context.Context, config []byte, params Params) (*metrics_config.Config, error) {
|
||
opaParsedConfig, opaParsedConfigErr := opa_config.ParseConfig(config, params.ID)
|
||
if opaParsedConfigErr != nil {
|
||
return nil, opaParsedConfigErr
|
||
}
|
||
|
||
var serverMetricsData []byte
|
||
if opaParsedConfig.Server != nil {
|
||
serverMetricsData = opaParsedConfig.Server.Metrics
|
||
}
|
||
|
||
configBuilder := metrics_config.NewConfigBuilder()
|
||
metricsParsedConfig, metricsParsedConfigErr := configBuilder.WithBytes(serverMetricsData).ParseWithContext(ctx)
|
||
if metricsParsedConfigErr != nil {
|
||
return nil, fmt.Errorf("server metrics configuration parse error: %w", metricsParsedConfigErr)
|
||
}
|
||
|
||
return metricsParsedConfig, nil
|
||
}
|
||
|
||
func (rt *Runtime) setServerStatus(status ServerStatus) {
|
||
rt.serverInitMtx.Lock()
|
||
defer rt.serverInitMtx.Unlock()
|
||
rt.serverStatus = status
|
||
}
|
||
|
||
func (rt *Runtime) ServerStatus() ServerStatus {
|
||
rt.serverInitMtx.RLock()
|
||
defer rt.serverInitMtx.RUnlock()
|
||
return rt.serverStatus
|
||
}
|
||
|
||
// StartServer starts the runtime in server mode. This function will block the
|
||
// calling goroutine.
|
||
func (rt *Runtime) StartServer(ctx context.Context) {
|
||
err := rt.Serve(ctx)
|
||
if err != nil {
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
|
||
// Serve will start a new REST API server and listen for requests. This
|
||
// will block until either: an error occurs, the context is canceled, or
|
||
// a SIGTERM or SIGKILL signal is sent.
|
||
func (rt *Runtime) Serve(ctx context.Context) (err error) {
|
||
if rt.Params.Addrs == nil {
|
||
return errors.New("at least one address must be configured in runtime parameters")
|
||
}
|
||
|
||
serverInitializingMessage := "Initializing server."
|
||
if !rt.Params.AddrSetByUser && rt.Params.V0Compatible {
|
||
serverInitializingMessage += " OPA is running on a public (0.0.0.0) network interface. Unless you intend to expose OPA outside of the host, binding to the localhost interface (--addr localhost:8181) is recommended. See https://www.openpolicyagent.org/docs/latest/security/#interface-binding"
|
||
}
|
||
|
||
if rt.Params.DiagnosticAddrs == nil {
|
||
rt.Params.DiagnosticAddrs = &[]string{}
|
||
}
|
||
|
||
rt.logger.WithFields(map[string]any{
|
||
"addrs": *rt.Params.Addrs,
|
||
"diagnostic-addrs": *rt.Params.DiagnosticAddrs,
|
||
}).Info(serverInitializingMessage)
|
||
|
||
if rt.Params.Authorization == server.AuthorizationOff && rt.Params.Authentication == server.AuthenticationToken {
|
||
rt.logger.Error("Token authentication enabled without authorization. Authentication will be ineffective. See https://www.openpolicyagent.org/docs/latest/security/#authentication-and-authorization for more information.")
|
||
}
|
||
|
||
checkUserPrivileges(rt.logger)
|
||
|
||
if err := rt.Manager.Start(ctx); err != nil {
|
||
rt.logger.WithFields(map[string]any{"err": err}).Error("Failed to start plugins.")
|
||
return err
|
||
}
|
||
|
||
defer rt.Manager.Stop(ctx)
|
||
|
||
// Resolve the buffered logger: flush to logger plugin if configured,
|
||
// otherwise fall back to the standard logger.
|
||
stdLogger := logging.New()
|
||
stdLogger.SetLevel(rt.logger.GetLevel())
|
||
stdLogger.SetFormatter(internal_logging.GetFormatter(rt.Params.Logging.Format, rt.Params.Logging.TimestampFormat))
|
||
rt.logger = rt.Manager.ResolveBufferedLogger(stdLogger)
|
||
|
||
if rt.traceExporter != nil {
|
||
if err := rt.traceExporter.Start(ctx); err != nil {
|
||
rt.logger.WithFields(map[string]any{"err": err}).Error("Failed to start OpenTelemetry trace exporter.")
|
||
return err
|
||
}
|
||
}
|
||
|
||
rt.server = server.New().
|
||
WithRouter(rt.Params.Router).
|
||
WithStore(rt.Store).
|
||
WithManager(rt.Manager).
|
||
WithCompilerErrorLimit(rt.Params.ErrorLimit).
|
||
WithPprofEnabled(rt.Params.PprofEnabled).
|
||
WithAddresses(*rt.Params.Addrs).
|
||
WithH2CEnabled(rt.Params.H2CEnabled).
|
||
// always use the initial values for the certificate and ca pool, reloading behavior is configured below
|
||
WithCertificate(rt.Params.Certificate).
|
||
WithCertPool(rt.Params.CertPool).
|
||
WithAuthentication(rt.Params.Authentication).
|
||
WithAuthorization(rt.Params.Authorization).
|
||
WithDecisionIDFactory(rt.decisionIDFactory).
|
||
WithDecisionLoggerWithErr(rt.decisionLogger).
|
||
WithRuntime(rt.Manager.Info).
|
||
WithMetrics(rt.metrics).
|
||
WithMinTLSVersion(rt.Params.MinTLSVersion).
|
||
WithCipherSuites(rt.Params.CipherSuites).
|
||
WithDistributedTracingOpts(rt.Params.DistributedTracingOpts).
|
||
WithHooks(rt.Params.Hooks).
|
||
WithNDBCacheEnabled(rt.Params.NDBCacheEnabled)
|
||
|
||
// If decision_logging plugin enabled, check to see if we opted in to the ND builtins cache.
|
||
if lp := logs.Lookup(rt.Manager); lp != nil {
|
||
rt.server = rt.server.WithNDBCacheEnabled(rt.Params.NDBCacheEnabled || rt.Manager.GetConfig().NDBuiltinCacheEnabled())
|
||
}
|
||
|
||
if rt.Params.DiagnosticAddrs != nil {
|
||
rt.server = rt.server.WithDiagnosticAddresses(*rt.Params.DiagnosticAddrs)
|
||
}
|
||
|
||
if rt.Params.UnixSocketPerm != nil {
|
||
rt.server = rt.server.WithUnixSocketPermission(rt.Params.UnixSocketPerm)
|
||
}
|
||
|
||
// If a refresh period is set, then we will periodically reload the certificate and ca pool. Otherwise, we will only
|
||
// reload cert, key and ca pool files when they change on disk.
|
||
if rt.Params.CertificateRefresh > 0 {
|
||
rt.server = rt.server.WithCertRefresh(rt.Params.CertificateRefresh)
|
||
}
|
||
|
||
// if either the cert or the ca pool file is set then these fields will be set on the server and reloaded when they
|
||
// change on disk.
|
||
if rt.Params.CertificateFile != "" || rt.Params.CertPoolFile != "" {
|
||
rt.server = rt.server.WithTLSConfig(&server.TLSConfig{
|
||
CertFile: rt.Params.CertificateFile,
|
||
KeyFile: rt.Params.CertificateKeyFile,
|
||
CertPoolFile: rt.Params.CertPoolFile,
|
||
})
|
||
}
|
||
|
||
ctx, cancel := context.WithCancel(ctx)
|
||
defer cancel()
|
||
rt.server, err = rt.server.Init(ctx)
|
||
if err != nil {
|
||
rt.logger.WithFields(map[string]any{"err": err}).Error("Unable to initialize server.")
|
||
return err
|
||
}
|
||
|
||
if rt.Params.Watch {
|
||
if err := rt.startWatcher(ctx, rt.Params.Paths, rt.onReloadLogger); err != nil {
|
||
rt.logger.WithFields(map[string]any{"err": err}).Error("Unable to open watch.")
|
||
return err
|
||
}
|
||
}
|
||
|
||
if rt.Params.EnableVersionCheck {
|
||
rt.done = make(chan struct{})
|
||
go rt.checkOPAUpdateLoop(ctx, rt.done)
|
||
}
|
||
|
||
defer func() {
|
||
if rt.done != nil {
|
||
rt.done <- struct{}{}
|
||
}
|
||
}()
|
||
|
||
rt.server.Handler = NewLoggingHandler(rt.logger, rt.server.Handler)
|
||
rt.server.DiagnosticHandler = NewLoggingHandler(rt.logger, rt.server.DiagnosticHandler)
|
||
|
||
rt.setServerStatus(ServerWaitingForPlugins)
|
||
|
||
if err := rt.waitPluginsReady(
|
||
100*time.Millisecond,
|
||
time.Second*time.Duration(rt.Params.ReadyTimeout)); err != nil {
|
||
rt.logger.WithFields(map[string]any{"err": err}).Error("Failed to wait for plugins activation.")
|
||
return err
|
||
}
|
||
|
||
loops, err := rt.server.Listeners()
|
||
if err != nil {
|
||
rt.logger.WithFields(map[string]any{"err": err}).Error("Unable to create listeners.")
|
||
return err
|
||
}
|
||
|
||
errc := make(chan error)
|
||
for _, loop := range loops {
|
||
go func(serverLoop func() error) {
|
||
errc <- serverLoop()
|
||
}(loop)
|
||
}
|
||
|
||
// Buffer one element as os/signal uses non-blocking channel sends.
|
||
// This prevents potentially dropping the first element and failing to shut
|
||
// down gracefully. A buffer of 1 is sufficient as we're just looking for a
|
||
// one-time shutdown signal.
|
||
signalc := make(chan os.Signal, 1)
|
||
signal.Notify(signalc, syscall.SIGINT, syscall.SIGTERM)
|
||
|
||
// Note that there is a small chance the socket of the server listener is still
|
||
// closed by the time this block is executed, due to the serverLoop above
|
||
// executing in a goroutine.
|
||
rt.setServerStatus(ServerInitialized)
|
||
rt.Manager.ServerInitialized()
|
||
|
||
rt.logger.Debug("Server initialized.")
|
||
|
||
defer rt.setServerStatus(ServerStopped)
|
||
|
||
for {
|
||
select {
|
||
case <-ctx.Done():
|
||
return rt.gracefulServerShutdown(rt.server)
|
||
case <-signalc:
|
||
return rt.gracefulServerShutdown(rt.server)
|
||
case err := <-errc:
|
||
rt.logger.WithFields(map[string]any{"err": err}).Error("Listener failed.")
|
||
os.Exit(1) //nolint:gocritic
|
||
}
|
||
}
|
||
}
|
||
|
||
// Addrs returns a list of addresses that the runtime is listening on (when
|
||
// in server mode). Returns an empty list if it hasn't started listening.
|
||
func (rt *Runtime) Addrs() []string {
|
||
rt.serverInitMtx.RLock()
|
||
defer rt.serverInitMtx.RUnlock()
|
||
|
||
if rt.serverStatus < ServerInitialized {
|
||
return nil
|
||
}
|
||
|
||
return rt.server.Addrs()
|
||
}
|
||
|
||
// DiagnosticAddrs returns a list of diagnostic addresses that the runtime is
|
||
// listening on (when in server mode). Returns an empty list if it hasn't
|
||
// started listening.
|
||
func (rt *Runtime) DiagnosticAddrs() []string {
|
||
if rt.server == nil {
|
||
return nil
|
||
}
|
||
|
||
return rt.server.DiagnosticAddrs()
|
||
}
|
||
|
||
// StartREPL starts the runtime in REPL mode. This function will block the calling goroutine.
|
||
func (rt *Runtime) StartREPL(ctx context.Context) error {
|
||
if err := rt.Manager.Start(ctx); err != nil {
|
||
fmt.Fprintln(rt.Params.Output, "error starting plugins:", err)
|
||
return err
|
||
}
|
||
|
||
defer rt.Manager.Stop(ctx)
|
||
|
||
banner := rt.getBanner()
|
||
repl := repl.New(rt.Store, rt.Params.HistoryPath, rt.Params.Output, rt.Params.OutputFormat, rt.Params.ErrorLimit, banner).
|
||
WithRuntime(rt.Manager.Info).
|
||
WithRegoVersion(rt.Params.regoVersion()).
|
||
WithInitBundles(rt.loadedPathsResult.Bundles).
|
||
WithConsoleInput(rt.Params.ConsoleInput).
|
||
WithStderrWriter(rt.Params.Output)
|
||
|
||
if rt.Params.Watch {
|
||
if err := rt.startWatcher(ctx, rt.Params.Paths, onReloadPrinter(rt.Params.Output)); err != nil {
|
||
fmt.Fprintln(rt.Params.Output, "error opening watch:", err)
|
||
return err
|
||
}
|
||
}
|
||
|
||
if rt.Params.EnableVersionCheck {
|
||
go func() {
|
||
repl.SetOPAVersionReport(rt.checkOPAUpdate(ctx).Slice())
|
||
}()
|
||
}
|
||
|
||
rt.repl = repl
|
||
return repl.Loop(ctx)
|
||
}
|
||
|
||
// SetDistributedTracingLogging configures the distributed tracing's ErrorHandler,
|
||
// and logger instances.
|
||
func (rt *Runtime) SetDistributedTracingLogging() {
|
||
internal_tracing.SetupLogging(rt.logger)
|
||
}
|
||
|
||
func (rt *Runtime) checkOPAUpdate(ctx context.Context) *versioncheck.DataResponse {
|
||
resp, _ := rt.versionChecker.LatestVersion(ctx)
|
||
return resp
|
||
}
|
||
|
||
func (rt *Runtime) checkOPAUpdateLoop(ctx context.Context, done chan struct{}) {
|
||
rt.checkOPAUpdateLoopDurations(ctx, done, defaultInitialUploadInterval, defaultLaterUploadInterval)
|
||
}
|
||
|
||
func (rt *Runtime) checkOPAUpdateLoopDurations(ctx context.Context, done chan struct{}, initialDur, laterDur time.Duration) {
|
||
ticker := time.NewTicker(initialDur)
|
||
i := 0
|
||
mr.New(mr.NewSource(time.Now().UnixNano())) // Seed the PRNG.
|
||
|
||
for {
|
||
resp, err := rt.versionChecker.LatestVersion(ctx)
|
||
if err != nil {
|
||
rt.logger.WithFields(map[string]any{"err": err}).Debug("Unable to check %s version.", rt.Params.Brand)
|
||
} else {
|
||
if resp.Latest.OPAUpToDate {
|
||
rt.logger.WithFields(map[string]any{
|
||
"current_version": version.Version,
|
||
}).Debug("%s is up to date.", rt.Params.Brand)
|
||
} else {
|
||
rt.logger.WithFields(map[string]any{
|
||
"download_opa": resp.Latest.Download,
|
||
"release_notes": resp.Latest.ReleaseNotes,
|
||
"current_version": version.Version,
|
||
"latest_version": strings.TrimPrefix(resp.Latest.LatestRelease, "v"),
|
||
}).Info("%s is out of date.", rt.Params.Brand)
|
||
}
|
||
}
|
||
select {
|
||
case <-ticker.C:
|
||
ticker.Stop()
|
||
i++ // count the attempts
|
||
|
||
newInterval := time.Duration(mr.Int63n(int64(time.Hour / time.Second))) // spray, between 0 and 1 hr
|
||
if i < 6 {
|
||
newInterval += initialDur
|
||
} else {
|
||
newInterval += laterDur
|
||
}
|
||
ticker = time.NewTicker(newInterval)
|
||
case <-done:
|
||
ticker.Stop()
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
func (rt *Runtime) decisionIDFactory() string {
|
||
if rt.Params.DecisionIDFactory != nil {
|
||
return rt.Params.DecisionIDFactory()
|
||
}
|
||
if logs.Lookup(rt.Manager) != nil {
|
||
return generateDecisionID()
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func (rt *Runtime) decisionLogger(ctx context.Context, event *server.Info) error {
|
||
plugin := logs.Lookup(rt.Manager)
|
||
if plugin == nil {
|
||
return nil
|
||
}
|
||
|
||
return plugin.Log(ctx, event)
|
||
}
|
||
|
||
func (rt *Runtime) startWatcher(ctx context.Context, paths []string, onReload func(time.Duration, error)) error {
|
||
watcher, err := rt.getWatcher(paths)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
go rt.readWatcher(ctx, watcher, paths, onReload)
|
||
return nil
|
||
}
|
||
|
||
func (rt *Runtime) readWatcher(ctx context.Context, watcher *fsnotify.Watcher, paths []string, onReload func(time.Duration, error)) {
|
||
|
||
for {
|
||
select {
|
||
case evt := <-watcher.Events:
|
||
removalMask := fsnotify.Remove | fsnotify.Rename
|
||
mask := fsnotify.Create | fsnotify.Write | removalMask
|
||
if (evt.Op & mask) != 0 {
|
||
rt.logger.WithFields(map[string]any{
|
||
"event": evt.String(),
|
||
}).Debug("Registered file event.")
|
||
t0 := time.Now()
|
||
removed := ""
|
||
if (evt.Op & removalMask) != 0 {
|
||
removed = evt.Name
|
||
}
|
||
err := rt.processWatcherUpdate(ctx, paths, removed)
|
||
onReload(time.Since(t0), err)
|
||
}
|
||
case <-ctx.Done():
|
||
watcher.Close()
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
func (rt *Runtime) processWatcherUpdate(ctx context.Context, paths []string, removed string) error {
|
||
return pathwatcher.ProcessWatcherUpdateForRegoVersion(ctx, rt.Manager.ParserOptions(), paths, removed, rt.Store, rt.Params.Filter, rt.Params.BundleMode, rt.Params.BundleLazyLoadingMode, func(ctx context.Context, txn storage.Transaction, loaded *initload.LoadPathsResult) error {
|
||
_, err := initload.InsertAndCompile(ctx, initload.InsertAndCompileOptions{
|
||
Store: rt.Store,
|
||
Txn: txn,
|
||
Files: loaded.Files,
|
||
Bundles: loaded.Bundles,
|
||
MaxErrors: -1,
|
||
ParserOptions: rt.Manager.ParserOptions(),
|
||
})
|
||
|
||
return err
|
||
})
|
||
}
|
||
|
||
func (rt *Runtime) getBanner() string {
|
||
var buf bytes.Buffer
|
||
fmt.Fprintf(&buf, "%s %v (commit %v, built at %v)\n", rt.Params.Brand, version.Version, version.Vcs, version.Timestamp)
|
||
fmt.Fprintf(&buf, "\n")
|
||
fmt.Fprintf(&buf, "Run 'help' to see a list of commands and check for updates.\n")
|
||
return buf.String()
|
||
}
|
||
|
||
func (rt *Runtime) gracefulServerShutdown(s *server.Server) error {
|
||
if rt.Params.ShutdownWaitPeriod > 0 {
|
||
rt.logger.Info("Waiting %vs before initiating shutdown...", rt.Params.ShutdownWaitPeriod)
|
||
time.Sleep(time.Duration(rt.Params.ShutdownWaitPeriod) * time.Second)
|
||
}
|
||
|
||
rt.logger.Info("Shutting down...")
|
||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(rt.Params.GracefulShutdownPeriod)*time.Second)
|
||
defer cancel()
|
||
err := s.Shutdown(ctx)
|
||
if err != nil {
|
||
rt.logger.WithFields(map[string]any{"err": err}).Error("Failed to shutdown server gracefully.")
|
||
return err
|
||
}
|
||
rt.logger.Info("Server shutdown.")
|
||
|
||
if rt.traceExporter != nil {
|
||
err = rt.traceExporter.Shutdown(ctx)
|
||
if err != nil {
|
||
rt.logger.WithFields(map[string]any{"err": err}).Error("Failed to shutdown OpenTelemetry trace exporter gracefully.")
|
||
}
|
||
}
|
||
|
||
if rt.meterProvider != nil {
|
||
if err := rt.meterProvider.Shutdown(ctx); err != nil {
|
||
rt.logger.WithFields(map[string]any{"err": err}).Error("Failed to shutdown OpenTelemetry meter provider gracefully.")
|
||
}
|
||
}
|
||
|
||
// Close storage if it implements the storage.Closer interface
|
||
if closer, ok := rt.Store.(storage.Closer); ok {
|
||
if err := closer.Close(ctx); err != nil {
|
||
rt.logger.WithFields(map[string]any{"err": err}).Error("Failed to close storage gracefully.")
|
||
return err
|
||
}
|
||
rt.logger.Debug("Storage closed.")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (rt *Runtime) waitPluginsReady(checkInterval, timeout time.Duration) error {
|
||
if timeout <= 0 {
|
||
return nil
|
||
}
|
||
|
||
// check readiness of all plugins
|
||
pluginsReady := func() bool {
|
||
for _, status := range rt.Manager.PluginStatus() {
|
||
if status != nil && status.State != plugins.StateOK {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
rt.logger.Debug("Waiting for plugins activation (%v).", timeout)
|
||
|
||
return util.WaitFunc(pluginsReady, checkInterval, timeout)
|
||
}
|
||
|
||
func (rt *Runtime) onReloadLogger(d time.Duration, err error) {
|
||
rt.logger.WithFields(map[string]any{
|
||
"duration": d,
|
||
"err": err,
|
||
}).Info("Processed file watch event.")
|
||
}
|
||
|
||
func (rt *Runtime) getWatcher(rootPaths []string) (*fsnotify.Watcher, error) {
|
||
watcher, err := pathwatcher.CreatePathWatcher(rootPaths)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
for _, path := range watcher.WatchList() {
|
||
rt.logger.WithFields(map[string]any{"path": path}).Debug("watching path")
|
||
}
|
||
|
||
return watcher, nil
|
||
}
|
||
|
||
func urlPathToConfigOverride(pathCount int, path string) ([]string, error) {
|
||
uri, err := url.Parse(path)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
baseURL := uri.Scheme + "://" + uri.Host
|
||
urlPath := uri.Path
|
||
if uri.RawQuery != "" {
|
||
urlPath += "?" + uri.RawQuery
|
||
}
|
||
|
||
return []string{
|
||
fmt.Sprintf("services.cli%d.url=%s", pathCount, baseURL),
|
||
fmt.Sprintf("bundles.cli%d.service=cli%d", pathCount, pathCount),
|
||
fmt.Sprintf("bundles.cli%d.resource=%s", pathCount, urlPath),
|
||
fmt.Sprintf("bundles.cli%d.persist=true", pathCount),
|
||
}, nil
|
||
}
|
||
|
||
func errorLogger(logger logging.Logger) func(attrs map[string]any, f string, a ...any) {
|
||
return func(attrs map[string]any, f string, a ...any) {
|
||
logger.WithFields(attrs).Error(f, a...)
|
||
}
|
||
}
|
||
|
||
func onReloadPrinter(output io.Writer) func(time.Duration, error) {
|
||
return func(d time.Duration, err error) {
|
||
if err != nil {
|
||
fmt.Fprintf(output, "\n# reload error (took %v): %v", d, err)
|
||
} else {
|
||
fmt.Fprintf(output, "\n# reloaded files (took %v)", d)
|
||
}
|
||
}
|
||
}
|
||
|
||
func generateInstanceID() (string, error) {
|
||
return uuid.New(rand.Reader)
|
||
}
|
||
|
||
func generateDecisionID() string {
|
||
id, err := uuid.New(rand.Reader)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
return id
|
||
}
|
||
|
||
func init() {
|
||
registeredPlugins = map[string]plugins.Factory{
|
||
filelogger.Name: &filelogger.Factory{},
|
||
}
|
||
}
|
||
|
||
func verifyAuthorizationPolicySchema(m *plugins.Manager) error {
|
||
authorizationDecisionRef, err := ref.ParseDataPath(*m.GetConfig().DefaultAuthorizationDecision)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return compiler.VerifyAuthorizationPolicySchema(m.GetCompiler(), authorizationDecisionRef)
|
||
}
|