Skip to content

Commit

Permalink
Extract topaz CLI configuration package (#234)
Browse files Browse the repository at this point in the history
* Extract topaz CLI configuration package

* Update loader

* Use loader get volumes for mount paths

* Update generator

* Move configuration package into topaz cc

* Add config loader and generator to config package

* Remove UI.Progress from cert generation
  • Loading branch information
carabasdaniel authored Jan 17, 2024
1 parent 314bbcd commit b7f409b
Show file tree
Hide file tree
Showing 12 changed files with 469 additions and 230 deletions.
9 changes: 5 additions & 4 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,11 @@ By default Topaz does not initiate a decision logger, however if you need to kee
Example configuration:
```
decision_logger:
log_file_path: /tmp/mytopaz.log
max_file_size_mb: 50
max_file_count: 2
type: "file"
config:
log_file_path: /tmp/mytopaz.log
max_file_size_mb: 50
max_file_count: 2
```

To use the decision logger the OPA configuration must contain the [configuration information](https://github.com/aserto-dev/topaz/blob/main/decision_log/plugin/plugin.go#L23) for the decision log plugin.
Expand All @@ -216,7 +218,6 @@ opa:

When deploying topaz as an [Aserto Edge Authorizer](https://docs.aserto.com/docs/edge-authorizers/overview) you can configure the decision logger to send the logs to the upstream Aserto policy instance. For configuration details see: https://docs.aserto.com/docs/edge-authorizers/decision-logs


## 4. Controller configuration (optional)

The controller allows an edge Topaz authorizer to connect to the Aserto Control Plane through a secure mTLS connection. This way the edge authorizers can sync their running policy with an upstream policy instance and sync their local directory with a remote directory.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ require (
google.golang.org/grpc v1.60.1
google.golang.org/protobuf v1.32.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v2 v2.4.0
sigs.k8s.io/controller-runtime v0.16.3
)

Expand Down Expand Up @@ -175,6 +174,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
oras.land/oras-go/v2 v2.3.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
Expand Down
28 changes: 28 additions & 0 deletions go.sum

Large diffs are not rendered by default.

92 changes: 16 additions & 76 deletions pkg/cc/config/config.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
package config

import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/spf13/viper"

"github.com/aserto-dev/certs"
"github.com/aserto-dev/go-aserto/client"
"github.com/aserto-dev/go-edge-ds/pkg/directory"
"github.com/aserto-dev/logger"
"github.com/aserto-dev/runtime"
builder "github.com/aserto-dev/service-host"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)

// CommandMode -- enum type.
Expand Down Expand Up @@ -91,7 +85,6 @@ type Overrider func(*Config)
func NewConfig(configPath Path, log *zerolog.Logger, overrides Overrider, certsGenerator *certs.Generator) (*Config, error) { // nolint:funlen // default list of values can be long
newLogger := log.With().Str("component", "config").Logger()
log = &newLogger
v := viper.New()

file := "config.yaml"
if configPath != "" {
Expand All @@ -107,97 +100,66 @@ func NewConfig(configPath Path, log *zerolog.Logger, overrides Overrider, certsG
file = string(configPath)
}

v.SetConfigType("yaml")
v.AddConfigPath(".")
v.SetConfigFile(file)
v.SetEnvPrefix("TOPAZ")
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))

// Set defaults
v.SetDefault("jwt.acceptable_time_skew_seconds", 5)

v.SetDefault("opa.max_plugin_wait_time_seconds", "30")

v.SetDefault("remote_directory.address", "0.0.0.0:9292")
v.SetDefault("remote_directory.insecure", "true")

defaults(v)

configExists, err := FileExists(file)
if err != nil {
return nil, errors.Wrapf(err, "filesystem error")
}

configLoader := new(Loader)

if configExists {
buf, err := os.ReadFile(v.ConfigFileUsed())
configLoader, err = LoadConfiguration(file)
if err != nil {
return nil, errors.Wrapf(err, "failed to read config file '%s'", file)
}
subBuf := subEnvVars(string(buf))
r := bytes.NewReader([]byte(subBuf))

if err := v.ReadConfig(r); err != nil {
return nil, errors.Wrapf(err, "failed to parse config file '%s'", file)
return nil, err
}

err = validateVersion(v.GetInt("version"))
err = validateVersion(configLoader.Configuration.Version)
if err != nil {
return nil, err
}
}

v.AutomaticEnv()

cfg := new(Config)

err = v.UnmarshalExact(cfg, func(dc *mapstructure.DecoderConfig) {
dc.TagName = "json"
})
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal config file")
}

if overrides != nil {
overrides(cfg)
overrides(configLoader.Configuration)
}

// This is where validation of config happens.
err = func() error {
var err error

if cfg.Logging.LogLevel == "" {
cfg.Logging.LogLevelParsed = zerolog.InfoLevel
if configLoader.Configuration.Logging.LogLevel == "" {
configLoader.Configuration.Logging.LogLevelParsed = zerolog.InfoLevel
} else {
cfg.Logging.LogLevelParsed, err = zerolog.ParseLevel(cfg.Logging.LogLevel)
configLoader.Configuration.Logging.LogLevelParsed, err = zerolog.ParseLevel(configLoader.Configuration.Logging.LogLevel)
if err != nil {
return errors.Wrapf(err, "logging.log_level failed to parse")
}
}

if cfg.JWT.AcceptableTimeSkewSeconds < 0 {
if configLoader.Configuration.JWT.AcceptableTimeSkewSeconds < 0 {
return errors.New("jwt.acceptable_time_skew_seconds must be positive or 0")
}

return cfg.validation()
return configLoader.Configuration.validation()
}()

if err != nil {
return nil, errors.Wrap(err, "failed to validate config file")
}

err = setDefaultCerts(cfg)
err = setDefaultCerts(configLoader.Configuration)
if err != nil {
return nil, err
}

if certsGenerator != nil {
err = cfg.setupCerts(log, certsGenerator)
err = configLoader.Configuration.setupCerts(log, certsGenerator)
if err != nil {
return nil, errors.Wrap(err, "failed to setup certs")
}
}

return cfg, nil
return configLoader.Configuration, nil
}

// NewLoggerConfig creates a new LoggerConfig.
Expand Down Expand Up @@ -284,28 +246,6 @@ func FileExists(path string) (bool, error) {
}
}

var envRegex = regexp.MustCompile(`(?U:\${.*})`)

// subEnvVars will look for any environment variables in the passed in string
// with the syntax of ${VAR_NAME} and replace that string with ENV[VAR_NAME].
func subEnvVars(s string) string {
updatedConfig := envRegex.ReplaceAllStringFunc(s, func(s string) string {
// Trim off the '${' and '}'
if len(s) <= 3 {
// This should never happen..
return ""
}
varName := s[2 : len(s)-1]

// Lookup the variable in the environment. We play by
// bash rules.. if its undefined we'll treat it as an
// empty string instead of raising an error.
return os.Getenv(varName)
})

return updatedConfig
}

func setDefaultCerts(cfg *Config) error {
for srvName, config := range cfg.APIConfig.Services {
if config.GRPC.ListenAddress == "" {
Expand Down
105 changes: 105 additions & 0 deletions pkg/cc/config/generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package config

import (
"html/template"
"io"
"os"
"path"
)

type Generator struct {
templateParams
ConfigName string
}

func NewGenerator(configName string) *Generator {
return &Generator{ConfigName: configName}
}

func (g *Generator) WithVersion(version int) *Generator {
g.Version = version
return g
}
func (g *Generator) WithLocalPolicyImage(image string) *Generator {
g.LocalPolicyImage = image
return g
}

func (g *Generator) WithPolicyName(policyName string) *Generator {
g.PolicyName = policyName
return g
}
func (g *Generator) WithResource(resource string) *Generator {
g.Resource = resource
return g
}

func (g *Generator) WithEdgeDirectory(enabled bool) *Generator {
g.EdgeDirectory = enabled
return g
}

func (g *Generator) WithEnableDirectoryV2(enabled bool) *Generator {
g.EnableDirectoryV2 = enabled
return g
}

func (g *Generator) GenerateConfig(w io.Writer, templateData string) error {
return g.writeConfig(w, templateData)
}

func (g *Generator) CreateConfigDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}

configDir := path.Join(home, "/.config/topaz/cfg")
if fi, err := os.Stat(configDir); err == nil && fi.IsDir() {
return configDir, nil
}

return configDir, os.MkdirAll(configDir, 0700)
}

func (g *Generator) CreateCertsDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}

certsDir := path.Join(home, "/.config/topaz/certs")
if fi, err := os.Stat(certsDir); err == nil && fi.IsDir() {
return certsDir, nil
}

return certsDir, os.MkdirAll(certsDir, 0700)
}

func (g *Generator) CreateDataDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}

dataDir := path.Join(home, "/.config/topaz/db")
if fi, err := os.Stat(dataDir); err == nil && fi.IsDir() {
return dataDir, nil
}

return dataDir, os.MkdirAll(dataDir, 0700)
}

func (g *Generator) writeConfig(w io.Writer, templ string) error {
t, err := template.New("config").Parse(templ)
if err != nil {
return err
}

err = t.Execute(w, g)
if err != nil {
return err
}

return nil
}
Loading

0 comments on commit b7f409b

Please sign in to comment.