// Package config contains the configuration logic for CFSSL.
package config

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/asn1"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/cloudflare/cfssl/auth"
	cferr "github.com/cloudflare/cfssl/errors"
	"github.com/cloudflare/cfssl/helpers"
	"github.com/cloudflare/cfssl/log"
	ocspConfig "github.com/cloudflare/cfssl/ocsp/config"
	"github.com/zmap/zlint/lints"
)

// A CSRWhitelist stores booleans for fields in the CSR. If a CSRWhitelist is
// not present in a SigningProfile, all of these fields may be copied from the
// CSR into the signed certificate. If a CSRWhitelist *is* present in a
// SigningProfile, only those fields with a `true` value in the CSRWhitelist may
// be copied from the CSR to the signed certificate. Note that some of these
// fields, like Subject, can be provided or partially provided through the API.
// Since API clients are expected to be trusted, but CSRs are not, fields
// provided through the API are not subject to whitelisting through this
// mechanism.
type CSRWhitelist struct {
	Subject, PublicKeyAlgorithm, PublicKey, SignatureAlgorithm bool
	DNSNames, IPAddresses, EmailAddresses, URIs                bool
}

// OID is our own version of asn1's ObjectIdentifier, so we can define a custom
// JSON marshal / unmarshal.
type OID asn1.ObjectIdentifier

// CertificatePolicy represents the ASN.1 PolicyInformation structure from
// https://tools.ietf.org/html/rfc3280.html#page-106.
// Valid values of Type are "id-qt-unotice" and "id-qt-cps"
type CertificatePolicy struct {
	ID         OID
	Qualifiers []CertificatePolicyQualifier
}

// CertificatePolicyQualifier represents a single qualifier from an ASN.1
// PolicyInformation structure.
type CertificatePolicyQualifier struct {
	Type  string
	Value string
}

// AuthRemote is an authenticated remote signer.
type AuthRemote struct {
	RemoteName  string `json:"remote"`
	AuthKeyName string `json:"auth_key"`
}

// CAConstraint specifies various CA constraints on the signed certificate.
// CAConstraint would verify against (and override) the CA
// extensions in the given CSR.
type CAConstraint struct {
	IsCA           bool `json:"is_ca"`
	MaxPathLen     int  `json:"max_path_len"`
	MaxPathLenZero bool `json:"max_path_len_zero"`
}

// A SigningProfile stores information that the CA needs to store
// signature policy.
type SigningProfile struct {
	Usage               []string     `json:"usages"`
	IssuerURL           []string     `json:"issuer_urls"`
	OCSP                string       `json:"ocsp_url"`
	CRL                 string       `json:"crl_url"`
	CAConstraint        CAConstraint `json:"ca_constraint"`
	OCSPNoCheck         bool         `json:"ocsp_no_check"`
	ExpiryString        string       `json:"expiry"`
	BackdateString      string       `json:"backdate"`
	AuthKeyName         string       `json:"auth_key"`
	PrevAuthKeyName     string       `json:"prev_auth_key"` // to suppport key rotation
	RemoteName          string       `json:"remote"`
	NotBefore           time.Time    `json:"not_before"`
	NotAfter            time.Time    `json:"not_after"`
	NameWhitelistString string       `json:"name_whitelist"`
	AuthRemote          AuthRemote   `json:"auth_remote"`
	CTLogServers        []string     `json:"ct_log_servers"`
	AllowedExtensions   []OID        `json:"allowed_extensions"`
	CertStore           string       `json:"cert_store"`
	// LintErrLevel controls preissuance linting for the signing profile.
	// 0 = no linting is performed [default]
	// 2..3 = reserved
	// 3 = all lint results except pass are considered errors
	// 4 = all lint results except pass and notice are considered errors
	// 5 = all lint results except pass, notice and warn are considered errors
	// 6 = all lint results except pass, notice, warn and error are considered errors.
	// 7 = lint is performed, no lint results are treated as errors.
	LintErrLevel lints.LintStatus `json:"lint_error_level"`
	// IgnoredLints lists zlint lint names to ignore. Any lint results from
	// matching lints will be ignored no matter what the configured LintErrLevel
	// is.
	IgnoredLints []string `json:"ignored_lints"`

	Policies                    []CertificatePolicy
	Expiry                      time.Duration
	Backdate                    time.Duration
	Provider                    auth.Provider
	PrevProvider                auth.Provider // to suppport key rotation
	RemoteProvider              auth.Provider
	RemoteServer                string
	RemoteCAs                   *x509.CertPool
	ClientCert                  *tls.Certificate
	CSRWhitelist                *CSRWhitelist
	NameWhitelist               *regexp.Regexp
	ExtensionWhitelist          map[string]bool
	ClientProvidesSerialNumbers bool
	// IgnoredLintsMap is a bool map created from IgnoredLints when the profile is
	// loaded. It facilitates set membership testing.
	IgnoredLintsMap map[string]bool
}

// UnmarshalJSON unmarshals a JSON string into an OID.
func (oid *OID) UnmarshalJSON(data []byte) (err error) {
	if data[0] != '"' || data[len(data)-1] != '"' {
		return errors.New("OID JSON string not wrapped in quotes." + string(data))
	}
	data = data[1 : len(data)-1]
	parsedOid, err := parseObjectIdentifier(string(data))
	if err != nil {
		return err
	}
	*oid = OID(parsedOid)
	return
}

// MarshalJSON marshals an oid into a JSON string.
func (oid OID) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf(`"%v"`, asn1.ObjectIdentifier(oid))), nil
}

func parseObjectIdentifier(oidString string) (oid asn1.ObjectIdentifier, err error) {
	validOID, err := regexp.MatchString("\\d(\\.\\d+)*", oidString)
	if err != nil {
		return
	}
	if !validOID {
		err = errors.New("Invalid OID")
		return
	}

	segments := strings.Split(oidString, ".")
	oid = make(asn1.ObjectIdentifier, len(segments))
	for i, intString := range segments {
		oid[i], err = strconv.Atoi(intString)
		if err != nil {
			return
		}
	}
	return
}

const timeFormat = "2006-01-02T15:04:05"

// populate is used to fill in the fields that are not in JSON
//
// First, the ExpiryString parameter is needed to parse
// expiration timestamps from JSON. The JSON decoder is not able to
// decode a string time duration to a time.Duration, so this is called
// when loading the configuration to properly parse and fill out the
// Expiry parameter.
// This function is also used to create references to the auth key
// and default remote for the profile.
// It returns true if ExpiryString is a valid representation of a
// time.Duration, and the AuthKeyString and RemoteName point to
// valid objects. It returns false otherwise.
func (p *SigningProfile) populate(cfg *Config) error {
	if p == nil {
		return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("can't parse nil profile"))
	}

	var err error
	if p.RemoteName == "" && p.AuthRemote.RemoteName == "" {
		log.Debugf("parse expiry in profile")
		if p.ExpiryString == "" {
			return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("empty expiry string"))
		}

		dur, err := time.ParseDuration(p.ExpiryString)
		if err != nil {
			return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
		}

		log.Debugf("expiry is valid")
		p.Expiry = dur

		if p.BackdateString != "" {
			dur, err = time.ParseDuration(p.BackdateString)
			if err != nil {
				return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
			}

			p.Backdate = dur
		}

		if !p.NotBefore.IsZero() && !p.NotAfter.IsZero() && p.NotAfter.Before(p.NotBefore) {
			return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
		}

		if len(p.Policies) > 0 {
			for _, policy := range p.Policies {
				for _, qualifier := range policy.Qualifiers {
					if qualifier.Type != "" && qualifier.Type != "id-qt-unotice" && qualifier.Type != "id-qt-cps" {
						return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
							errors.New("invalid policy qualifier type"))
					}
				}
			}
		}
	} else if p.RemoteName != "" {
		log.Debug("match remote in profile to remotes section")
		if p.AuthRemote.RemoteName != "" {
			log.Error("profile has both a remote and an auth remote specified")
			return cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
		}
		if remote := cfg.Remotes[p.RemoteName]; remote != "" {
			if err := p.updateRemote(remote); err != nil {
				return err
			}
		} else {
			return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
				errors.New("failed to find remote in remotes section"))
		}
	} else {
		log.Debug("match auth remote in profile to remotes section")
		if remote := cfg.Remotes[p.AuthRemote.RemoteName]; remote != "" {
			if err := p.updateRemote(remote); err != nil {
				return err
			}
		} else {
			return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
				errors.New("failed to find remote in remotes section"))
		}
	}

	if p.AuthKeyName != "" {
		log.Debug("match auth key in profile to auth_keys section")
		if key, ok := cfg.AuthKeys[p.AuthKeyName]; ok {
			if key.Type == "standard" {
				p.Provider, err = auth.New(key.Key, nil)
				if err != nil {
					log.Debugf("failed to create new standard auth provider: %v", err)
					return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
						errors.New("failed to create new standard auth provider"))
				}
			} else {
				log.Debugf("unknown authentication type %v", key.Type)
				return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
					errors.New("unknown authentication type"))
			}
		} else {
			return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
				errors.New("failed to find auth_key in auth_keys section"))
		}
	}

	if p.PrevAuthKeyName != "" {
		log.Debug("match previous auth key in profile to auth_keys section")
		if key, ok := cfg.AuthKeys[p.PrevAuthKeyName]; ok {
			if key.Type == "standard" {
				p.PrevProvider, err = auth.New(key.Key, nil)
				if err != nil {
					log.Debugf("failed to create new standard auth provider: %v", err)
					return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
						errors.New("failed to create new standard auth provider"))
				}
			} else {
				log.Debugf("unknown authentication type %v", key.Type)
				return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
					errors.New("unknown authentication type"))
			}
		} else {
			return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
				errors.New("failed to find prev_auth_key in auth_keys section"))
		}
	}

	if p.AuthRemote.AuthKeyName != "" {
		log.Debug("match auth remote key in profile to auth_keys section")
		if key, ok := cfg.AuthKeys[p.AuthRemote.AuthKeyName]; ok == true {
			if key.Type == "standard" {
				p.RemoteProvider, err = auth.New(key.Key, nil)
				if err != nil {
					log.Debugf("failed to create new standard auth provider: %v", err)
					return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
						errors.New("failed to create new standard auth provider"))
				}
			} else {
				log.Debugf("unknown authentication type %v", key.Type)
				return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
					errors.New("unknown authentication type"))
			}
		} else {
			return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
				errors.New("failed to find auth_remote's auth_key in auth_keys section"))
		}
	}

	if p.NameWhitelistString != "" {
		log.Debug("compiling whitelist regular expression")
		rule, err := regexp.Compile(p.NameWhitelistString)
		if err != nil {
			return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
				errors.New("failed to compile name whitelist section"))
		}
		p.NameWhitelist = rule
	}

	p.ExtensionWhitelist = map[string]bool{}
	for _, oid := range p.AllowedExtensions {
		p.ExtensionWhitelist[asn1.ObjectIdentifier(oid).String()] = true
	}

	p.IgnoredLintsMap = map[string]bool{}
	for _, lintName := range p.IgnoredLints {
		p.IgnoredLintsMap[lintName] = true
	}

	return nil
}

// updateRemote takes a signing profile and initializes the remote server object
// to the hostname:port combination sent by remote.
func (p *SigningProfile) updateRemote(remote string) error {
	if remote != "" {
		p.RemoteServer = remote
	}
	return nil
}

// OverrideRemotes takes a signing configuration and updates the remote server object
// to the hostname:port combination sent by remote
func (p *Signing) OverrideRemotes(remote string) error {
	if remote != "" {
		var err error
		for _, profile := range p.Profiles {
			err = profile.updateRemote(remote)
			if err != nil {
				return err
			}
		}
		err = p.Default.updateRemote(remote)
		if err != nil {
			return err
		}
	}
	return nil
}

// SetClientCertKeyPairFromFile updates the properties to set client certificates for mutual
// authenticated TLS remote requests
func (p *Signing) SetClientCertKeyPairFromFile(certFile string, keyFile string) error {
	if certFile != "" && keyFile != "" {
		cert, err := helpers.LoadClientCertificate(certFile, keyFile)
		if err != nil {
			return err
		}
		for _, profile := range p.Profiles {
			profile.ClientCert = cert
		}
		p.Default.ClientCert = cert
	}
	return nil
}

// SetRemoteCAsFromFile reads root CAs from file and updates the properties to set remote CAs for TLS
// remote requests
func (p *Signing) SetRemoteCAsFromFile(caFile string) error {
	if caFile != "" {
		remoteCAs, err := helpers.LoadPEMCertPool(caFile)
		if err != nil {
			return err
		}
		p.SetRemoteCAs(remoteCAs)
	}
	return nil
}

// SetRemoteCAs updates the properties to set remote CAs for TLS
// remote requests
func (p *Signing) SetRemoteCAs(remoteCAs *x509.CertPool) {
	for _, profile := range p.Profiles {
		profile.RemoteCAs = remoteCAs
	}
	p.Default.RemoteCAs = remoteCAs
}

// NeedsRemoteSigner returns true if one of the profiles has a remote set
func (p *Signing) NeedsRemoteSigner() bool {
	for _, profile := range p.Profiles {
		if profile.RemoteServer != "" {
			return true
		}
	}

	if p.Default.RemoteServer != "" {
		return true
	}

	return false
}

// NeedsLocalSigner returns true if one of the profiles doe not have a remote set
func (p *Signing) NeedsLocalSigner() bool {
	for _, profile := range p.Profiles {
		if profile.RemoteServer == "" {
			return true
		}
	}

	if p.Default.RemoteServer == "" {
		return true
	}

	return false
}

// Usages parses the list of key uses in the profile, translating them
// to a list of X.509 key usages and extended key usages.  The unknown
// uses are collected into a slice that is also returned.
func (p *SigningProfile) Usages() (ku x509.KeyUsage, eku []x509.ExtKeyUsage, unk []string) {
	for _, keyUse := range p.Usage {
		if kuse, ok := KeyUsage[keyUse]; ok {
			ku |= kuse
		} else if ekuse, ok := ExtKeyUsage[keyUse]; ok {
			eku = append(eku, ekuse)
		} else {
			unk = append(unk, keyUse)
		}
	}
	return
}

// A valid profile must be a valid local profile or a valid remote profile.
// A valid local profile has defined at least key usages to be used, and a
// valid local default profile has defined at least a default expiration.
// A valid remote profile (default or not) has remote signer initialized.
// In addition, a remote profile must has a valid auth provider if auth
// key defined. A valid profile must not include a lint_error_level outside of
// [0,8).
func (p *SigningProfile) validProfile(isDefault bool) bool {
	if p == nil {
		return false
	}

	if p.AuthRemote.RemoteName == "" && p.AuthRemote.AuthKeyName != "" {
		log.Debugf("invalid auth remote profile: no remote signer specified")
		return false
	}

	if p.RemoteName != "" {
		log.Debugf("validate remote profile")

		if p.RemoteServer == "" {
			log.Debugf("invalid remote profile: no remote signer specified")
			return false
		}

		if p.AuthKeyName != "" && p.Provider == nil {
			log.Debugf("invalid remote profile: auth key name is defined but no auth provider is set")
			return false
		}

		if p.AuthRemote.RemoteName != "" {
			log.Debugf("invalid remote profile: auth remote is also specified")
			return false
		}
	} else if p.AuthRemote.RemoteName != "" {
		log.Debugf("validate auth remote profile")
		if p.RemoteServer == "" {
			log.Debugf("invalid auth remote profile: no remote signer specified")
			return false
		}

		if p.AuthRemote.AuthKeyName == "" || p.RemoteProvider == nil {
			log.Debugf("invalid auth remote profile: no auth key is defined")
			return false
		}
	} else {
		log.Debugf("validate local profile")
		if !isDefault {
			if len(p.Usage) == 0 {
				log.Debugf("invalid local profile: no usages specified")
				return false
			} else if _, _, unk := p.Usages(); len(unk) == len(p.Usage) {
				log.Debugf("invalid local profile: no valid usages")
				return false
			}
		} else {
			if p.Expiry == 0 {
				log.Debugf("invalid local profile: no expiry set")
				return false
			}
		}
	}

	if p.LintErrLevel < 0 || p.LintErrLevel >= 8 {
		log.Debugf("invalid profile: lint_error_level outside of range [0,8)")
		return false
	}

	log.Debugf("profile is valid")
	return true
}

// This checks if the SigningProfile object contains configurations that are only effective with a local signer
// which has access to CA private key.
func (p *SigningProfile) hasLocalConfig() bool {
	if p.Usage != nil ||
		p.IssuerURL != nil ||
		p.OCSP != "" ||
		p.ExpiryString != "" ||
		p.BackdateString != "" ||
		p.CAConstraint.IsCA != false ||
		!p.NotBefore.IsZero() ||
		!p.NotAfter.IsZero() ||
		p.NameWhitelistString != "" ||
		len(p.CTLogServers) != 0 {
		return true
	}
	return false
}

// warnSkippedSettings prints a log warning message about skipped settings
// in a SigningProfile, usually due to remote signer.
func (p *Signing) warnSkippedSettings() {
	const warningMessage = `The configuration value by "usages", "issuer_urls", "ocsp_url", "crl_url", "ca_constraint", "expiry", "backdate", "not_before", "not_after", "cert_store" and "ct_log_servers" are skipped`
	if p == nil {
		return
	}

	if (p.Default.RemoteName != "" || p.Default.AuthRemote.RemoteName != "") && p.Default.hasLocalConfig() {
		log.Warning("default profile points to a remote signer: ", warningMessage)
	}

	for name, profile := range p.Profiles {
		if (profile.RemoteName != "" || profile.AuthRemote.RemoteName != "") && profile.hasLocalConfig() {
			log.Warningf("Profiles[%s] points to a remote signer: %s", name, warningMessage)
		}
	}
}

// Signing codifies the signature configuration policy for a CA.
type Signing struct {
	Profiles map[string]*SigningProfile `json:"profiles"`
	Default  *SigningProfile            `json:"default"`
}

// Config stores configuration information for the CA.
type Config struct {
	Signing  *Signing           `json:"signing"`
	OCSP     *ocspConfig.Config `json:"ocsp"`
	AuthKeys map[string]AuthKey `json:"auth_keys,omitempty"`
	Remotes  map[string]string  `json:"remotes,omitempty"`
}

// Valid ensures that Config is a valid configuration. It should be
// called immediately after parsing a configuration file.
func (c *Config) Valid() bool {
	return c.Signing.Valid()
}

// Valid checks the signature policies, ensuring they are valid
// policies. A policy is valid if it has defined at least key usages
// to be used, and a valid default profile has defined at least a
// default expiration.
func (p *Signing) Valid() bool {
	if p == nil {
		return false
	}

	log.Debugf("validating configuration")
	if !p.Default.validProfile(true) {
		log.Debugf("default profile is invalid")
		return false
	}

	for _, sp := range p.Profiles {
		if !sp.validProfile(false) {
			log.Debugf("invalid profile")
			return false
		}
	}

	p.warnSkippedSettings()

	return true
}

// KeyUsage contains a mapping of string names to key usages.
var KeyUsage = map[string]x509.KeyUsage{
	"signing":            x509.KeyUsageDigitalSignature,
	"digital signature":  x509.KeyUsageDigitalSignature,
	"content commitment": x509.KeyUsageContentCommitment,
	"key encipherment":   x509.KeyUsageKeyEncipherment,
	"key agreement":      x509.KeyUsageKeyAgreement,
	"data encipherment":  x509.KeyUsageDataEncipherment,
	"cert sign":          x509.KeyUsageCertSign,
	"crl sign":           x509.KeyUsageCRLSign,
	"encipher only":      x509.KeyUsageEncipherOnly,
	"decipher only":      x509.KeyUsageDecipherOnly,
}

// ExtKeyUsage contains a mapping of string names to extended key
// usages.
var ExtKeyUsage = map[string]x509.ExtKeyUsage{
	"any":              x509.ExtKeyUsageAny,
	"server auth":      x509.ExtKeyUsageServerAuth,
	"client auth":      x509.ExtKeyUsageClientAuth,
	"code signing":     x509.ExtKeyUsageCodeSigning,
	"email protection": x509.ExtKeyUsageEmailProtection,
	"s/mime":           x509.ExtKeyUsageEmailProtection,
	"ipsec end system": x509.ExtKeyUsageIPSECEndSystem,
	"ipsec tunnel":     x509.ExtKeyUsageIPSECTunnel,
	"ipsec user":       x509.ExtKeyUsageIPSECUser,
	"timestamping":     x509.ExtKeyUsageTimeStamping,
	"ocsp signing":     x509.ExtKeyUsageOCSPSigning,
	"microsoft sgc":    x509.ExtKeyUsageMicrosoftServerGatedCrypto,
	"netscape sgc":     x509.ExtKeyUsageNetscapeServerGatedCrypto,
}

// An AuthKey contains an entry for a key used for authentication.
type AuthKey struct {
	// Type contains information needed to select the appropriate
	// constructor. For example, "standard" for HMAC-SHA-256,
	// "standard-ip" for HMAC-SHA-256 incorporating the client's
	// IP.
	Type string `json:"type"`
	// Key contains the key information, such as a hex-encoded
	// HMAC key.
	Key string `json:"key"`
}

// DefaultConfig returns a default configuration specifying basic key
// usage and a 1 year expiration time. The key usages chosen are
// signing, key encipherment, client auth and server auth.
func DefaultConfig() *SigningProfile {
	d := helpers.OneYear
	return &SigningProfile{
		Usage:        []string{"signing", "key encipherment", "server auth", "client auth"},
		Expiry:       d,
		ExpiryString: "8760h",
	}
}

// LoadFile attempts to load the configuration file stored at the path
// and returns the configuration. On error, it returns nil.
func LoadFile(path string) (*Config, error) {
	log.Debugf("loading configuration file from %s", path)
	if path == "" {
		return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid path"))
	}

	body, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("could not read configuration file"))
	}

	return LoadConfig(body)
}

// LoadConfig attempts to load the configuration from a byte slice.
// On error, it returns nil.
func LoadConfig(config []byte) (*Config, error) {
	var cfg = &Config{}
	err := json.Unmarshal(config, &cfg)
	if err != nil {
		return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
			errors.New("failed to unmarshal configuration: "+err.Error()))
	}

	if cfg.Signing == nil {
		return nil, errors.New("No \"signing\" field present")
	}

	if cfg.Signing.Default == nil {
		log.Debugf("no default given: using default config")
		cfg.Signing.Default = DefaultConfig()
	} else {
		if err := cfg.Signing.Default.populate(cfg); err != nil {
			return nil, err
		}
	}

	for k := range cfg.Signing.Profiles {
		if err := cfg.Signing.Profiles[k].populate(cfg); err != nil {
			return nil, err
		}
	}

	if !cfg.Valid() {
		return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid configuration"))
	}

	log.Debugf("configuration ok")
	return cfg, nil
}
