<commit_before>package governor

import (
	"context"
	vaultapi "github.com/hashicorp/vault/api"
	"github.com/spf13/viper"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"sync"
	"time"
)

type (
	// ConfigOpts is the server base configuration
	ConfigOpts struct {
		DefaultFile string
		Appname     string
		Description string
		Version     string
		VersionHash string
		EnvPrefix   string
	}

	// Config is the complete server configuration including the dynamic
	// (runtime) options
	Config struct {
		config        *viper.Viper
		vault         *vaultapi.Client
		vaultK8sAuth  bool
		vaultExpire   int64
		mu            *sync.RWMutex
		Appname       string
		Version       string
		VersionHash   string
		LogLevel      int
		LogOutput     io.Writer
		Port          string
		BaseURL       string
		PublicDir     string
		MaxReqSize    string
		FrontendProxy []string
		Origins       []string
		RouteRewrite  map[string]string
	}
)

func newConfig(conf ConfigOpts) *Config {
	v := viper.New()
	v.SetDefault("mode", "INFO")
	v.SetDefault("logoutput", "STDOUT")
	v.SetDefault("port", "8080")
	v.SetDefault("baseurl", "/")
	v.SetDefault("publicdir", "public")
	v.SetDefault("templatedir", "templates")
	v.SetDefault("maxreqsize", "2M")
	v.SetDefault("frontendproxy", []string{})
	v.SetDefault("alloworigins", []string{})
	v.SetDefault("vault.addr", "")
	v.SetDefault("vault.k8s.auth", false)
	v.SetDefault("vault.k8s.role", "")
	v.SetDefault("vault.k8s.loginpath", "/auth/kubernetes/login")
	v.SetDefault("vault.k8s.jwtpath", "/var/run/secrets/kubernetes.io/serviceaccount/token")

	v.SetConfigName(conf.DefaultFile)
	v.AddConfigPath(".")
	v.AddConfigPath(path.Join(".", "config"))
	if cfgdir, err := os.UserConfigDir(); err == nil {
		v.AddConfigPath(path.Join(cfgdir, conf.Appname))
	}
	v.SetConfigType("yaml")

	v.SetEnvPrefix(conf.EnvPrefix)
	v.AutomaticEnv()

	return &Config{
		config:      v,
		mu:          &sync.RWMutex{},
		Appname:     conf.Appname,
		Version:     conf.Version,
		VersionHash: conf.VersionHash,
	}
}

func (c *Config) setConfigFile(file string) {
	c.config.SetConfigFile(file)
}

func (c *Config) init() error {
	if err := c.config.ReadInConfig(); err != nil {
		return NewError("Failed to read in config", http.StatusInternalServerError, err)
	}
	c.LogLevel = envToLevel(c.config.GetString("mode"))
	c.LogOutput = envToLogOutput(c.config.GetString("logoutput"))
	c.Port = c.config.GetString("port")
	c.BaseURL = c.config.GetString("baseurl")
	c.PublicDir = c.config.GetString("publicdir")
	c.MaxReqSize = c.config.GetString("maxreqsize")
	c.FrontendProxy = c.config.GetStringSlice("frontendproxy")
	c.Origins = c.config.GetStringSlice("alloworigins")
	c.RouteRewrite = c.config.GetStringMapString("routerewrite")
	return nil
}

func (c *Config) initvault(ctx context.Context, l Logger) error {
	vconfig := c.config.GetStringMapString("vault")
	vaultconfig := vaultapi.DefaultConfig()
	if err := vaultconfig.Error; err != nil {
		l.Warn("error creating vault config", map[string]string{
			"phase":      "init",
			"error":      err.Error(),
			"actiontype": "vaultdefaultconfig",
		})
	}
	if vaddr := vconfig["addr"]; vaddr != "" {
		vaultconfig.Address = vaddr
	}
	vault, err := vaultapi.NewClient(vaultconfig)
	if err != nil {
		return NewError("Failed to create vault client", http.StatusInternalServerError, err)
	}
	c.vault = vault
	if c.config.GetBool("vault.k8s.auth") {
		c.vaultK8sAuth = true
		if err := c.authVault(); err != nil {
			return err
		}
	}
	return nil
}

func (c *Config) ensureValidAuth() error {
	if !c.vaultK8sAuth {
		return nil
	}
	if c.authVaultValid() {
		return nil
	}
	return c.authVault()
}

func (c *Config) authVaultValidLocked() bool {
	return c.vaultExpire-time.Now().Round(0).Unix() > 5
}

func (c *Config) authVaultValid() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.authVaultValidLocked()
}

func (c *Config) authVault() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.authVaultValidLocked() {
		return nil
	}

	vault := c.vault.Logical()
	kconfig := c.config.GetStringMapString("vault.k8s")
	role := kconfig["role"]
	loginpath := kconfig["loginpath"]
	jwtpath := kconfig["jwtpath"]
	if role == "" {
		return NewError("No vault role set", http.StatusBadRequest, nil)
	}
	if loginpath == "" {
		return NewError("No vault k8s login path set", http.StatusBadRequest, nil)
	}
	if jwtpath == "" {
		return NewError("No path for vault k8s service account jwt auth", http.StatusBadRequest, nil)
	}
	jwtbytes, err := ioutil.ReadFile(jwtpath)
	if err != nil {
		return NewError("Failed to read vault k8s service account jwt", http.StatusInternalServerError, err)
	}
	jwt := string(jwtbytes)
	authsecret, err := vault.Write(loginpath, map[string]interface{}{
		"jwt":  jwt,
		"role": role,
	})
	if err != nil {
		return NewError("Failed to auth with vault k8s", http.StatusInternalServerError, err)
	}
	c.vaultExpire = time.Now().Round(0).Unix() + int64(authsecret.Auth.LeaseDuration)
	c.vault.SetToken(authsecret.Auth.ClientToken)
	return nil
}

// IsDebug returns if the configuration is in debug mode
func (c *Config) IsDebug() bool {
	return c.LogLevel == levelDebug
}

type (
	// ConfigRegistrar sets default values on the config parser
	ConfigRegistrar interface {
		SetDefault(key string, value interface{})
	}

	configRegistrar struct {
		prefix string
		v      *viper.Viper<commit_msg>Bugfix repeated file read for vault k8s jwt auth<commit_after>package governor

import (
	"context"
	vaultapi "github.com/hashicorp/vault/api"
	"github.com/spf13/viper"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"sync"
	"time"
)

type (
	// ConfigOpts is the server base configuration
	ConfigOpts struct {
		DefaultFile string
		Appname     string
		Description string
		Version     string
		VersionHash string
		EnvPrefix   string
	}

	// Config is the complete server configuration including the dynamic
	// (runtime) options
	Config struct {
		config         *viper.Viper
		vault          *vaultapi.Client
		vaultK8sAuth   bool
		vaultRole      string
		vaultJWT       string
		vaultLoginPath string
		vaultExpire    int64
		mu             *sync.RWMutex
		Appname        string
		Version        string
		VersionHash    string
		LogLevel       int
		LogOutput      io.Writer
		Port           string
		BaseURL        string
		PublicDir      string
		MaxReqSize     string
		FrontendProxy  []string
		Origins        []string
		RouteRewrite   map[string]string
	}
)

func newConfig(conf ConfigOpts) *Config {
	v := viper.New()
	v.SetDefault("mode", "INFO")
	v.SetDefault("logoutput", "STDOUT")
	v.SetDefault("port", "8080")
	v.SetDefault("baseurl", "/")
	v.SetDefault("publicdir", "public")
	v.SetDefault("templatedir", "templates")
	v.SetDefault("maxreqsize", "2M")
	v.SetDefault("frontendproxy", []string{})
	v.SetDefault("alloworigins", []string{})
	v.SetDefault("vault.addr", "")
	v.SetDefault("vault.k8s.auth", false)
	v.SetDefault("vault.k8s.role", "")
	v.SetDefault("vault.k8s.loginpath", "/auth/kubernetes/login")
	v.SetDefault("vault.k8s.jwtpath", "/var/run/secrets/kubernetes.io/serviceaccount/token")

	v.SetConfigName(conf.DefaultFile)
	v.AddConfigPath(".")
	v.AddConfigPath(path.Join(".", "config"))
	if cfgdir, err := os.UserConfigDir(); err == nil {
		v.AddConfigPath(path.Join(cfgdir, conf.Appname))
	}
	v.SetConfigType("yaml")

	v.SetEnvPrefix(conf.EnvPrefix)
	v.AutomaticEnv()

	return &Config{
		config:      v,
		mu:          &sync.RWMutex{},
		Appname:     conf.Appname,
		Version:     conf.Version,
		VersionHash: conf.VersionHash,
	}
}

func (c *Config) setConfigFile(file string) {
	c.config.SetConfigFile(file)
}

func (c *Config) init() error {
	if err := c.config.ReadInConfig(); err != nil {
		return NewError("Failed to read in config", http.StatusInternalServerError, err)
	}
	c.LogLevel = envToLevel(c.config.GetString("mode"))
	c.LogOutput = envToLogOutput(c.config.GetString("logoutput"))
	c.Port = c.config.GetString("port")
	c.BaseURL = c.config.GetString("baseurl")
	c.PublicDir = c.config.GetString("publicdir")
	c.MaxReqSize = c.config.GetString("maxreqsize")
	c.FrontendProxy = c.config.GetStringSlice("frontendproxy")
	c.Origins = c.config.GetStringSlice("alloworigins")
	c.RouteRewrite = c.config.GetStringMapString("routerewrite")
	return nil
}

func (c *Config) initvault(ctx context.Context, l Logger) error {
	vconfig := c.config.GetStringMapString("vault")
	vaultconfig := vaultapi.DefaultConfig()
	if err := vaultconfig.Error; err != nil {
		l.Warn("error creating vault config", map[string]string{
			"phase":      "init",
			"error":      err.Error(),
			"actiontype": "vaultdefaultconfig",
		})
	}
	if vaddr := vconfig["addr"]; vaddr != "" {
		vaultconfig.Address = vaddr
	}
	vault, err := vaultapi.NewClient(vaultconfig)
	if err != nil {
		return NewError("Failed to create vault client", http.StatusInternalServerError, err)
	}
	c.vault = vault
	if c.config.GetBool("vault.k8s.auth") {
		c.vaultK8sAuth = true

		kconfig := c.config.GetStringMapString("vault.k8s")
		role := kconfig["role"]
		loginpath := kconfig["loginpath"]
		jwtpath := kconfig["jwtpath"]
		if role == "" {
			return NewError("No vault role set", http.StatusBadRequest, nil)
		}
		if loginpath == "" {
			return NewError("No vault k8s login path set", http.StatusBadRequest, nil)
		}
		if jwtpath == "" {
			return NewError("No path for vault k8s service account jwt auth", http.StatusBadRequest, nil)
		}
		jwtbytes, err := ioutil.ReadFile(jwtpath)
		if err != nil {
			return NewError("Failed to read vault k8s service account jwt", http.StatusInternalServerError, err)
		}
		jwt := string(jwtbytes)
		c.vaultRole = role
		c.vaultLoginPath = loginpath
		c.vaultJWT = jwt

		if err := c.authVault(); err != nil {
			return err
		}
	}
	return nil
}

func (c *Config) ensureValidAuth() error {
	if !c.vaultK8sAuth {
		return nil
	}
	if c.authVaultValid() {
		return nil
	}
	return c.authVault()
}

func (c *Config) authVaultValidLocked() bool {
	return c.vaultExpire-time.Now().Round(0).Unix() > 5
}

func (c *Config) authVaultValid() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.authVaultValidLocked()
}

func (c *Config) authVault() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.authVaultValidLocked() {
		return nil
	}

	vault := c.vault.Logical()
	authsecret, err := vault.Write(c.vaultLoginPath, map[string]interface{}{
		"jwt":  c.vaultJWT,
		"role": c.vaultRole,
	})
	if err != nil {
		return NewError("Failed to auth with vault k8s", http.StatusInternalServerError, err)
	}
	c.vaultExpire = time.Now().Round(0).Unix() + int64(authsecret.Auth.LeaseDuration)
	c.vault.SetToken(authsecret.Auth.ClientToken)
	return nil
}

// IsDebug returns if the configuration is in debug mode
func (c *Config) IsDebug() bool {
	return c.LogLevel == levelDebug
}

type (
	// ConfigRegistrar sets default values on the config parser
	ConfigRegistrar interface {
		SetDefault(key string, value interface{})
	}

	configRegistrar struct {
		prefix string
		v      *viper.Viper
{'fraction_non_alphanumeric': 0.13828039430449068, 'fraction_numerical': 0.0050200803212851405, 'mean_word_length': 3.900268336314848, 'pattern_counts': {'":': 10, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4571687', 'n_tokens_mistral': 4082, 'n_tokens_neox': 3938, 'n_words': 857}