Files
kubesphere/vendor/github.com/open-policy-agent/opa/config/config.go
hongming cfebd96a1f update dependencies (#6267)
Signed-off-by: hongming <coder.scala@gmail.com>
2024-11-06 10:27:06 +08:00

260 lines
7.1 KiB
Go

// Copyright 2018 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 config implements OPA configuration file parsing and validation.
package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/internal/ref"
"github.com/open-policy-agent/opa/util"
"github.com/open-policy-agent/opa/version"
)
// Config represents the configuration file that OPA can be started with.
type Config struct {
Services json.RawMessage `json:"services,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Discovery json.RawMessage `json:"discovery,omitempty"`
Bundle json.RawMessage `json:"bundle,omitempty"` // Deprecated: Use `bundles` instead
Bundles json.RawMessage `json:"bundles,omitempty"`
DecisionLogs json.RawMessage `json:"decision_logs,omitempty"`
Status json.RawMessage `json:"status,omitempty"`
Plugins map[string]json.RawMessage `json:"plugins,omitempty"`
Keys json.RawMessage `json:"keys,omitempty"`
DefaultDecision *string `json:"default_decision,omitempty"`
DefaultAuthorizationDecision *string `json:"default_authorization_decision,omitempty"`
Caching json.RawMessage `json:"caching,omitempty"`
NDBuiltinCache bool `json:"nd_builtin_cache,omitempty"`
PersistenceDirectory *string `json:"persistence_directory,omitempty"`
DistributedTracing json.RawMessage `json:"distributed_tracing,omitempty"`
Server *struct {
Encoding json.RawMessage `json:"encoding,omitempty"`
Decoding json.RawMessage `json:"decoding,omitempty"`
Metrics json.RawMessage `json:"metrics,omitempty"`
} `json:"server,omitempty"`
Storage *struct {
Disk json.RawMessage `json:"disk,omitempty"`
} `json:"storage,omitempty"`
Extra map[string]json.RawMessage `json:"-"`
}
// ParseConfig returns a valid Config object with defaults injected. The id
// and version parameters will be set in the labels map.
func ParseConfig(raw []byte, id string) (*Config, error) {
// NOTE(sr): based on https://stackoverflow.com/a/33499066/993018
var result Config
objValue := reflect.ValueOf(&result).Elem()
knownFields := map[string]reflect.Value{}
for i := 0; i != objValue.NumField(); i++ {
jsonName := strings.Split(objValue.Type().Field(i).Tag.Get("json"), ",")[0]
knownFields[jsonName] = objValue.Field(i)
}
if err := util.Unmarshal(raw, &result.Extra); err != nil {
return nil, err
}
for key, chunk := range result.Extra {
if field, found := knownFields[key]; found {
if err := util.Unmarshal(chunk, field.Addr().Interface()); err != nil {
return nil, err
}
delete(result.Extra, key)
}
}
if len(result.Extra) == 0 {
result.Extra = nil
}
return &result, result.validateAndInjectDefaults(id)
}
// PluginNames returns a sorted list of names of enabled plugins.
func (c Config) PluginNames() (result []string) {
if c.Bundle != nil || c.Bundles != nil {
result = append(result, "bundles")
}
if c.Status != nil {
result = append(result, "status")
}
if c.DecisionLogs != nil {
result = append(result, "decision_logs")
}
for name := range c.Plugins {
result = append(result, name)
}
sort.Strings(result)
return result
}
// PluginsEnabled returns true if one or more plugin features are enabled.
//
// Deprecated. Use PluginNames instead.
func (c Config) PluginsEnabled() bool {
return c.Bundle != nil || c.Bundles != nil || c.DecisionLogs != nil || c.Status != nil || len(c.Plugins) > 0
}
// DefaultDecisionRef returns the default decision as a reference.
func (c Config) DefaultDecisionRef() ast.Ref {
r, _ := ref.ParseDataPath(*c.DefaultDecision)
return r
}
// DefaultAuthorizationDecisionRef returns the default authorization decision
// as a reference.
func (c Config) DefaultAuthorizationDecisionRef() ast.Ref {
r, _ := ref.ParseDataPath(*c.DefaultAuthorizationDecision)
return r
}
// NDBuiltinCacheEnabled returns if the ND builtins cache should be used.
func (c Config) NDBuiltinCacheEnabled() bool {
return c.NDBuiltinCache
}
func (c *Config) validateAndInjectDefaults(id string) error {
if c.DefaultDecision == nil {
s := defaultDecisionPath
c.DefaultDecision = &s
}
_, err := ref.ParseDataPath(*c.DefaultDecision)
if err != nil {
return err
}
if c.DefaultAuthorizationDecision == nil {
s := defaultAuthorizationDecisionPath
c.DefaultAuthorizationDecision = &s
}
_, err = ref.ParseDataPath(*c.DefaultAuthorizationDecision)
if err != nil {
return err
}
if c.Labels == nil {
c.Labels = map[string]string{}
}
c.Labels["id"] = id
c.Labels["version"] = version.Version
return nil
}
// GetPersistenceDirectory returns the configured persistence directory, or $PWD/.opa if none is configured
func (c Config) GetPersistenceDirectory() (string, error) {
if c.PersistenceDirectory == nil {
pwd, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Join(pwd, ".opa"), nil
}
return *c.PersistenceDirectory, nil
}
// ActiveConfig returns OPA's active configuration
// with the credentials and crypto keys removed
func (c *Config) ActiveConfig() (interface{}, error) {
bs, err := json.Marshal(c)
if err != nil {
return nil, err
}
var result map[string]interface{}
if err := util.UnmarshalJSON(bs, &result); err != nil {
return nil, err
}
for k, e := range c.Extra {
var v any
if err := util.UnmarshalJSON(e, &v); err != nil {
return nil, err
}
result[k] = v
}
if err := removeServiceCredentials(result["services"]); err != nil {
return nil, err
}
if err := removeCryptoKeys(result["keys"]); err != nil {
return nil, err
}
return result, nil
}
func removeServiceCredentials(x interface{}) error {
switch x := x.(type) {
case nil:
return nil
case []interface{}:
for _, v := range x {
err := removeKey(v, "credentials")
if err != nil {
return err
}
}
case map[string]interface{}:
for _, v := range x {
err := removeKey(v, "credentials")
if err != nil {
return err
}
}
default:
return fmt.Errorf("illegal service config type: %T", x)
}
return nil
}
func removeCryptoKeys(x interface{}) error {
switch x := x.(type) {
case nil:
return nil
case map[string]interface{}:
for _, v := range x {
err := removeKey(v, "key", "private_key")
if err != nil {
return err
}
}
default:
return fmt.Errorf("illegal keys config type: %T", x)
}
return nil
}
func removeKey(x interface{}, keys ...string) error {
val, ok := x.(map[string]interface{})
if !ok {
return fmt.Errorf("type assertion error")
}
for _, key := range keys {
delete(val, key)
}
return nil
}
const (
defaultDecisionPath = "/system/main"
defaultAuthorizationDecisionPath = "/system/authz/allow"
)