Move code to pkg
This commit is contained in:
parent
bd4c822670
commit
f1b085fa36
465 changed files with 656 additions and 680 deletions
123
pkg/types/constraints.go
Normal file
123
pkg/types/constraints.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ryanuber/go-glob"
|
||||
)
|
||||
|
||||
// Constraint holds a parsed constraint expression.
|
||||
type Constraint struct {
|
||||
Key string `export:"true"`
|
||||
// MustMatch is true if operator is "==" or false if operator is "!="
|
||||
MustMatch bool `export:"true"`
|
||||
// TODO: support regex
|
||||
Regex string `export:"true"`
|
||||
}
|
||||
|
||||
// NewConstraint receives a string and return a *Constraint, after checking syntax and parsing the constraint expression.
|
||||
func NewConstraint(exp string) (*Constraint, error) {
|
||||
sep := ""
|
||||
constraint := &Constraint{}
|
||||
|
||||
switch {
|
||||
case strings.Contains(exp, "=="):
|
||||
sep = "=="
|
||||
constraint.MustMatch = true
|
||||
case strings.Contains(exp, "!="):
|
||||
sep = "!="
|
||||
constraint.MustMatch = false
|
||||
default:
|
||||
return nil, errors.New("constraint expression missing valid operator: '==' or '!='")
|
||||
}
|
||||
|
||||
kv := strings.SplitN(exp, sep, 2)
|
||||
if len(kv) == 2 {
|
||||
// At the moment, it only supports tags
|
||||
if kv[0] != "tag" {
|
||||
return nil, errors.New("constraint must be tag-based. Syntax: tag==us-*")
|
||||
}
|
||||
|
||||
constraint.Key = kv[0]
|
||||
constraint.Regex = kv[1]
|
||||
return constraint, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("incorrect constraint expression: %s", exp)
|
||||
}
|
||||
|
||||
func (c *Constraint) String() string {
|
||||
if c.MustMatch {
|
||||
return c.Key + "==" + c.Regex
|
||||
}
|
||||
return c.Key + "!=" + c.Regex
|
||||
}
|
||||
|
||||
var _ encoding.TextUnmarshaler = (*Constraint)(nil)
|
||||
|
||||
// UnmarshalText defines how unmarshal in TOML parsing
|
||||
func (c *Constraint) UnmarshalText(text []byte) error {
|
||||
constraint, err := NewConstraint(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Key = constraint.Key
|
||||
c.MustMatch = constraint.MustMatch
|
||||
c.Regex = constraint.Regex
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ encoding.TextMarshaler = (*Constraint)(nil)
|
||||
|
||||
// MarshalText encodes the receiver into UTF-8-encoded text and returns the result.
|
||||
func (c *Constraint) MarshalText() (text []byte, err error) {
|
||||
return []byte(c.String()), nil
|
||||
}
|
||||
|
||||
// MatchConstraintWithAtLeastOneTag tests a constraint for one single service.
|
||||
func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool {
|
||||
for _, tag := range tags {
|
||||
if glob.Glob(c.Regex, tag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Set []*Constraint.
|
||||
func (cs *Constraints) Set(str string) error {
|
||||
exps := strings.Split(str, ",")
|
||||
if len(exps) == 0 {
|
||||
return fmt.Errorf("bad Constraint format: %s", str)
|
||||
}
|
||||
for _, exp := range exps {
|
||||
constraint, err := NewConstraint(exp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*cs = append(*cs, constraint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Constraints holds a Constraint parser.
|
||||
type Constraints []*Constraint
|
||||
|
||||
// Get []*Constraint
|
||||
func (cs *Constraints) Get() interface{} { return []*Constraint(*cs) }
|
||||
|
||||
// String returns []*Constraint in string.
|
||||
func (cs *Constraints) String() string { return fmt.Sprintf("%+v", *cs) }
|
||||
|
||||
// SetValue sets []*Constraint into the parser.
|
||||
func (cs *Constraints) SetValue(val interface{}) {
|
||||
*cs = val.(Constraints)
|
||||
}
|
||||
|
||||
// Type exports the Constraints type as a string.
|
||||
func (cs *Constraints) Type() string {
|
||||
return "constraint"
|
||||
}
|
44
pkg/types/dns_resolvers.go
Normal file
44
pkg/types/dns_resolvers.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DNSResolvers is a list of DNSes that we will try to resolve the challenged FQDN against
|
||||
type DNSResolvers []string
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (r *DNSResolvers) String() string {
|
||||
return strings.Join(*r, ",")
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (r *DNSResolvers) Set(value string) error {
|
||||
entryPoints := strings.Split(value, ",")
|
||||
if len(entryPoints) == 0 {
|
||||
return fmt.Errorf("wrong DNSResolvers format: %s", value)
|
||||
}
|
||||
for _, entryPoint := range entryPoints {
|
||||
*r = append(*r, entryPoint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get return the DNSResolvers list
|
||||
func (r *DNSResolvers) Get() interface{} {
|
||||
return *r
|
||||
}
|
||||
|
||||
// SetValue sets the DNSResolvers list
|
||||
func (r *DNSResolvers) SetValue(val interface{}) {
|
||||
*r = val.(DNSResolvers)
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (r *DNSResolvers) Type() string {
|
||||
return "dnsresolvers"
|
||||
}
|
182
pkg/types/domain_test.go
Normal file
182
pkg/types/domain_test.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDomain_ToStrArray(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
domain Domain
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "with Main and SANs",
|
||||
domain: Domain{
|
||||
Main: "foo.com",
|
||||
SANs: []string{"bar.foo.com", "bir.foo.com"},
|
||||
},
|
||||
expected: []string{"foo.com", "bar.foo.com", "bir.foo.com"},
|
||||
},
|
||||
{
|
||||
desc: "without SANs",
|
||||
domain: Domain{
|
||||
Main: "foo.com",
|
||||
},
|
||||
expected: []string{"foo.com"},
|
||||
},
|
||||
{
|
||||
desc: "without Main",
|
||||
domain: Domain{
|
||||
SANs: []string{"bar.foo.com", "bir.foo.com"},
|
||||
},
|
||||
expected: []string{"bar.foo.com", "bir.foo.com"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
domains := test.domain.ToStrArray()
|
||||
assert.EqualValues(t, test.expected, domains)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomain_Set(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
rawDomains []string
|
||||
expected Domain
|
||||
}{
|
||||
{
|
||||
desc: "with 3 domains",
|
||||
rawDomains: []string{"foo.com", "bar.foo.com", "bir.foo.com"},
|
||||
expected: Domain{
|
||||
Main: "foo.com",
|
||||
SANs: []string{"bar.foo.com", "bir.foo.com"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with 1 domain",
|
||||
rawDomains: []string{"foo.com"},
|
||||
expected: Domain{
|
||||
Main: "foo.com",
|
||||
SANs: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "",
|
||||
rawDomains: nil,
|
||||
expected: Domain{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
domain := Domain{}
|
||||
domain.Set(test.rawDomains)
|
||||
|
||||
assert.Equal(t, test.expected, domain)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchDomain(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
certDomain string
|
||||
domain string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
desc: "exact match",
|
||||
certDomain: "traefik.wtf",
|
||||
domain: "traefik.wtf",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "wildcard and root domain",
|
||||
certDomain: "*.traefik.wtf",
|
||||
domain: "traefik.wtf",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "wildcard and sub domain",
|
||||
certDomain: "*.traefik.wtf",
|
||||
domain: "sub.traefik.wtf",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "wildcard and sub sub domain",
|
||||
certDomain: "*.traefik.wtf",
|
||||
domain: "sub.sub.traefik.wtf",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "double wildcard and sub sub domain",
|
||||
certDomain: "*.*.traefik.wtf",
|
||||
domain: "sub.sub.traefik.wtf",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "sub sub domain and invalid wildcard",
|
||||
certDomain: "sub.*.traefik.wtf",
|
||||
domain: "sub.sub.traefik.wtf",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "sub sub domain and valid wildcard",
|
||||
certDomain: "*.sub.traefik.wtf",
|
||||
domain: "sub.sub.traefik.wtf",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "dot replaced by a cahr",
|
||||
certDomain: "sub.sub.traefik.wtf",
|
||||
domain: "sub.sub.traefikiwtf",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "*",
|
||||
certDomain: "*",
|
||||
domain: "sub.sub.traefik.wtf",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "?",
|
||||
certDomain: "?",
|
||||
domain: "sub.sub.traefik.wtf",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "...................",
|
||||
certDomain: "...................",
|
||||
domain: "sub.sub.traefik.wtf",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "wildcard and *",
|
||||
certDomain: "*.traefik.wtf",
|
||||
domain: "*.*.traefik.wtf",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
domains := MatchDomain(test.domain, test.certDomain)
|
||||
assert.Equal(t, test.expected, domains)
|
||||
})
|
||||
}
|
||||
}
|
93
pkg/types/domains.go
Normal file
93
pkg/types/domains.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Domain holds a domain name with SANs.
|
||||
type Domain struct {
|
||||
Main string
|
||||
SANs []string
|
||||
}
|
||||
|
||||
// ToStrArray convert a domain into an array of strings.
|
||||
func (d *Domain) ToStrArray() []string {
|
||||
var domains []string
|
||||
if len(d.Main) > 0 {
|
||||
domains = []string{d.Main}
|
||||
}
|
||||
return append(domains, d.SANs...)
|
||||
}
|
||||
|
||||
// Set sets a domains from an array of strings.
|
||||
func (d *Domain) Set(domains []string) {
|
||||
if len(domains) > 0 {
|
||||
d.Main = domains[0]
|
||||
d.SANs = domains[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Domains parse []Domain.
|
||||
type Domains []Domain
|
||||
|
||||
// Set []Domain
|
||||
func (ds *Domains) Set(str string) error {
|
||||
fargs := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
|
||||
// get function
|
||||
slice := strings.FieldsFunc(str, fargs)
|
||||
if len(slice) < 1 {
|
||||
return fmt.Errorf("parse error ACME.Domain. Unable to parse %s", str)
|
||||
}
|
||||
|
||||
d := Domain{
|
||||
Main: slice[0],
|
||||
}
|
||||
|
||||
if len(slice) > 1 {
|
||||
d.SANs = slice[1:]
|
||||
}
|
||||
|
||||
*ds = append(*ds, d)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get []Domain.
|
||||
func (ds *Domains) Get() interface{} { return []Domain(*ds) }
|
||||
|
||||
// String returns []Domain in string.
|
||||
func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
|
||||
|
||||
// SetValue sets []Domain into the parser
|
||||
func (ds *Domains) SetValue(val interface{}) {
|
||||
*ds = val.([]Domain)
|
||||
}
|
||||
|
||||
// MatchDomain returns true if a domain match the cert domain.
|
||||
func MatchDomain(domain string, certDomain string) bool {
|
||||
if domain == certDomain {
|
||||
return true
|
||||
}
|
||||
|
||||
for len(certDomain) > 0 && certDomain[len(certDomain)-1] == '.' {
|
||||
certDomain = certDomain[:len(certDomain)-1]
|
||||
}
|
||||
|
||||
labels := strings.Split(domain, ".")
|
||||
for i := range labels {
|
||||
labels[i] = "*"
|
||||
candidate := strings.Join(labels, ".")
|
||||
if certDomain == candidate {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CanonicalDomain returns a lower case domain with trim space.
|
||||
func CanonicalDomain(domain string) string {
|
||||
return strings.ToLower(strings.TrimSpace(domain))
|
||||
}
|
8
pkg/types/host_resolver.go
Normal file
8
pkg/types/host_resolver.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package types
|
||||
|
||||
// HostResolverConfig contain configuration for CNAME Flattening.
|
||||
type HostResolverConfig struct {
|
||||
CnameFlattening bool `description:"A flag to enable/disable CNAME flattening" export:"true"`
|
||||
ResolvConfig string `description:"resolv.conf used for DNS resolving" export:"true"`
|
||||
ResolvDepth int `description:"The maximal depth of DNS recursive resolving" export:"true"`
|
||||
}
|
44
pkg/types/http_code_range.go
Normal file
44
pkg/types/http_code_range.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HTTPCodeRanges holds HTTP code ranges
|
||||
type HTTPCodeRanges [][2]int
|
||||
|
||||
// NewHTTPCodeRanges creates HTTPCodeRanges from a given []string.
|
||||
// Break out the http status code ranges into a low int and high int
|
||||
// for ease of use at runtime
|
||||
func NewHTTPCodeRanges(strBlocks []string) (HTTPCodeRanges, error) {
|
||||
var blocks HTTPCodeRanges
|
||||
for _, block := range strBlocks {
|
||||
codes := strings.Split(block, "-")
|
||||
// if only a single HTTP code was configured, assume the best and create the correct configuration on the user's behalf
|
||||
if len(codes) == 1 {
|
||||
codes = append(codes, codes[0])
|
||||
}
|
||||
lowCode, err := strconv.Atoi(codes[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
highCode, err := strconv.Atoi(codes[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blocks = append(blocks, [2]int{lowCode, highCode})
|
||||
}
|
||||
return blocks, nil
|
||||
}
|
||||
|
||||
// Contains tests whether the passed status code is within
|
||||
// one of its HTTP code ranges.
|
||||
func (h HTTPCodeRanges) Contains(statusCode int) bool {
|
||||
for _, block := range h {
|
||||
if statusCode >= block[0] && statusCode <= block[1] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
201
pkg/types/logs.go
Normal file
201
pkg/types/logs.go
Normal file
|
@ -0,0 +1,201 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
)
|
||||
|
||||
const (
|
||||
// AccessLogKeep is the keep string value
|
||||
AccessLogKeep = "keep"
|
||||
// AccessLogDrop is the drop string value
|
||||
AccessLogDrop = "drop"
|
||||
// AccessLogRedact is the redact string value
|
||||
AccessLogRedact = "redact"
|
||||
)
|
||||
|
||||
// TraefikLog holds the configuration settings for the traefik logger.
|
||||
type TraefikLog struct {
|
||||
LogLevel string `description:"Log level set to traefik logs." export:"true"`
|
||||
FilePath string `json:"file,omitempty" description:"Traefik log file path. Stdout is used when omitted or empty"`
|
||||
Format string `json:"format,omitempty" description:"Traefik log format: json | common"`
|
||||
}
|
||||
|
||||
// AccessLog holds the configuration settings for the access logger (middlewares/accesslog).
|
||||
type AccessLog struct {
|
||||
FilePath string `json:"file,omitempty" description:"Access log file path. Stdout is used when omitted or empty" export:"true"`
|
||||
Format string `json:"format,omitempty" description:"Access log format: json | common" export:"true"`
|
||||
Filters *AccessLogFilters `json:"filters,omitempty" description:"Access log filters, used to keep only specific access logs" export:"true"`
|
||||
Fields *AccessLogFields `json:"fields,omitempty" description:"AccessLogFields" export:"true"`
|
||||
BufferingSize int64 `json:"bufferingSize,omitempty" description:"Number of access log lines to process in a buffered way. Default 0." export:"true"`
|
||||
}
|
||||
|
||||
// AccessLogFilters holds filters configuration
|
||||
type AccessLogFilters struct {
|
||||
StatusCodes StatusCodes `json:"statusCodes,omitempty" description:"Keep access logs with status codes in the specified range" export:"true"`
|
||||
RetryAttempts bool `json:"retryAttempts,omitempty" description:"Keep access logs when at least one retry happened" export:"true"`
|
||||
MinDuration parse.Duration `json:"duration,omitempty" description:"Keep access logs when request took longer than the specified duration" export:"true"`
|
||||
}
|
||||
|
||||
// FieldHeaders holds configuration for access log headers
|
||||
type FieldHeaders struct {
|
||||
DefaultMode string `json:"defaultMode,omitempty" description:"Default mode for fields: keep | drop | redact" export:"true"`
|
||||
Names FieldHeaderNames `json:"names,omitempty" description:"Override mode for headers" export:"true"`
|
||||
}
|
||||
|
||||
// StatusCodes holds status codes ranges to filter access log
|
||||
type StatusCodes []string
|
||||
|
||||
// Set adds strings elem into the the parser
|
||||
// it splits str on , and ;
|
||||
func (s *StatusCodes) Set(str string) error {
|
||||
fargs := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
// get function
|
||||
slice := strings.FieldsFunc(str, fargs)
|
||||
*s = append(*s, slice...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get StatusCodes
|
||||
func (s *StatusCodes) Get() interface{} { return *s }
|
||||
|
||||
// String return slice in a string
|
||||
func (s *StatusCodes) String() string { return fmt.Sprintf("%v", *s) }
|
||||
|
||||
// SetValue sets StatusCodes into the parser
|
||||
func (s *StatusCodes) SetValue(val interface{}) {
|
||||
*s = val.(StatusCodes)
|
||||
}
|
||||
|
||||
// FieldNames holds maps of fields with specific mode
|
||||
type FieldNames map[string]string
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (f *FieldNames) String() string {
|
||||
return fmt.Sprintf("%+v", *f)
|
||||
}
|
||||
|
||||
// Get return the FieldNames map
|
||||
func (f *FieldNames) Get() interface{} {
|
||||
return *f
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a space-separated list, so we split it.
|
||||
func (f *FieldNames) Set(value string) error {
|
||||
// When arguments are passed through YAML, escaped double quotes
|
||||
// might be added to this string, and they would break the last
|
||||
// key/value pair. This ensures the string is clean.
|
||||
value = strings.Trim(value, "\"")
|
||||
|
||||
fields := strings.Fields(value)
|
||||
|
||||
for _, field := range fields {
|
||||
n := strings.SplitN(field, "=", 2)
|
||||
if len(n) == 2 {
|
||||
(*f)[n[0]] = n[1]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetValue sets the FieldNames map with val
|
||||
func (f *FieldNames) SetValue(val interface{}) {
|
||||
*f = val.(FieldNames)
|
||||
}
|
||||
|
||||
// FieldHeaderNames holds maps of fields with specific mode
|
||||
type FieldHeaderNames map[string]string
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (f *FieldHeaderNames) String() string {
|
||||
return fmt.Sprintf("%+v", *f)
|
||||
}
|
||||
|
||||
// Get return the FieldHeaderNames map
|
||||
func (f *FieldHeaderNames) Get() interface{} {
|
||||
return *f
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a space-separated list, so we split it.
|
||||
func (f *FieldHeaderNames) Set(value string) error {
|
||||
// When arguments are passed through YAML, escaped double quotes
|
||||
// might be added to this string, and they would break the last
|
||||
// key/value pair. This ensures the string is clean.
|
||||
value = strings.Trim(value, "\"")
|
||||
|
||||
fields := strings.Fields(value)
|
||||
|
||||
for _, field := range fields {
|
||||
n := strings.SplitN(field, "=", 2)
|
||||
(*f)[n[0]] = n[1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetValue sets the FieldHeaderNames map with val
|
||||
func (f *FieldHeaderNames) SetValue(val interface{}) {
|
||||
*f = val.(FieldHeaderNames)
|
||||
}
|
||||
|
||||
// AccessLogFields holds configuration for access log fields
|
||||
type AccessLogFields struct {
|
||||
DefaultMode string `json:"defaultMode,omitempty" description:"Default mode for fields: keep | drop" export:"true"`
|
||||
Names FieldNames `json:"names,omitempty" description:"Override mode for fields" export:"true"`
|
||||
Headers *FieldHeaders `json:"headers,omitempty" description:"Headers to keep, drop or redact" export:"true"`
|
||||
}
|
||||
|
||||
// Keep check if the field need to be kept or dropped
|
||||
func (f *AccessLogFields) Keep(field string) bool {
|
||||
defaultKeep := true
|
||||
if f != nil {
|
||||
defaultKeep = checkFieldValue(f.DefaultMode, defaultKeep)
|
||||
|
||||
if v, ok := f.Names[field]; ok {
|
||||
return checkFieldValue(v, defaultKeep)
|
||||
}
|
||||
}
|
||||
return defaultKeep
|
||||
}
|
||||
|
||||
// KeepHeader checks if the headers need to be kept, dropped or redacted and returns the status
|
||||
func (f *AccessLogFields) KeepHeader(header string) string {
|
||||
defaultValue := AccessLogKeep
|
||||
if f != nil && f.Headers != nil {
|
||||
defaultValue = checkFieldHeaderValue(f.Headers.DefaultMode, defaultValue)
|
||||
|
||||
if v, ok := f.Headers.Names[header]; ok {
|
||||
return checkFieldHeaderValue(v, defaultValue)
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func checkFieldValue(value string, defaultKeep bool) bool {
|
||||
switch value {
|
||||
case AccessLogKeep:
|
||||
return true
|
||||
case AccessLogDrop:
|
||||
return false
|
||||
default:
|
||||
return defaultKeep
|
||||
}
|
||||
}
|
||||
|
||||
func checkFieldHeaderValue(value string, defaultValue string) string {
|
||||
if value == AccessLogKeep || value == AccessLogDrop || value == AccessLogRedact {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
419
pkg/types/logs_test.go
Normal file
419
pkg/types/logs_test.go
Normal file
|
@ -0,0 +1,419 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStatusCodesSet(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
value string
|
||||
expected StatusCodes
|
||||
}{
|
||||
{
|
||||
desc: "One value should return StatusCodes of size 1",
|
||||
value: "200",
|
||||
expected: StatusCodes{"200"},
|
||||
},
|
||||
{
|
||||
desc: "Two values separated by comma should return StatusCodes of size 2",
|
||||
value: "200,400",
|
||||
expected: StatusCodes{"200", "400"},
|
||||
},
|
||||
{
|
||||
desc: "Two values separated by semicolon should return StatusCodes of size 2",
|
||||
value: "200;400",
|
||||
expected: StatusCodes{"200", "400"},
|
||||
},
|
||||
{
|
||||
desc: "Three values separated by comma and semicolon should return StatusCodes of size 3",
|
||||
value: "200,400;500",
|
||||
expected: StatusCodes{"200", "400", "500"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var statusCodes StatusCodes
|
||||
err := statusCodes.Set(test.value)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, test.expected, statusCodes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusCodesGet(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
values StatusCodes
|
||||
expected StatusCodes
|
||||
}{
|
||||
{
|
||||
desc: "Should return 1 value",
|
||||
values: StatusCodes{"200"},
|
||||
expected: StatusCodes{"200"},
|
||||
},
|
||||
{
|
||||
desc: "Should return 2 values",
|
||||
values: StatusCodes{"200", "400"},
|
||||
expected: StatusCodes{"200", "400"},
|
||||
},
|
||||
{
|
||||
desc: "Should return 3 values",
|
||||
values: StatusCodes{"200", "400", "500"},
|
||||
expected: StatusCodes{"200", "400", "500"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := test.values.Get()
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusCodesString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
values StatusCodes
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Should return 1 value",
|
||||
values: StatusCodes{"200"},
|
||||
expected: "[200]",
|
||||
},
|
||||
{
|
||||
desc: "Should return 2 values",
|
||||
values: StatusCodes{"200", "400"},
|
||||
expected: "[200 400]",
|
||||
},
|
||||
{
|
||||
desc: "Should return 3 values",
|
||||
values: StatusCodes{"200", "400", "500"},
|
||||
expected: "[200 400 500]",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := test.values.String()
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusCodesSetValue(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
values StatusCodes
|
||||
expected StatusCodes
|
||||
}{
|
||||
{
|
||||
desc: "Should return 1 value",
|
||||
values: StatusCodes{"200"},
|
||||
expected: StatusCodes{"200"},
|
||||
},
|
||||
{
|
||||
desc: "Should return 2 values",
|
||||
values: StatusCodes{"200", "400"},
|
||||
expected: StatusCodes{"200", "400"},
|
||||
},
|
||||
{
|
||||
desc: "Should return 3 values",
|
||||
values: StatusCodes{"200", "400", "500"},
|
||||
expected: StatusCodes{"200", "400", "500"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var slice StatusCodes
|
||||
slice.SetValue(test.values)
|
||||
assert.Equal(t, test.expected, slice)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsNamesSet(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
value string
|
||||
expected *FieldNames
|
||||
}{
|
||||
{
|
||||
desc: "One value should return FieldNames of size 1",
|
||||
value: "field-1=foo",
|
||||
expected: &FieldNames{
|
||||
"field-1": "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Two values separated by space should return FieldNames of size 2",
|
||||
value: "field-1=foo field-2=bar",
|
||||
expected: &FieldNames{
|
||||
"field-1": "foo",
|
||||
"field-2": "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fieldsNames := &FieldNames{}
|
||||
err := fieldsNames.Set(test.value)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, fieldsNames)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsNamesGet(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
values FieldNames
|
||||
expected FieldNames
|
||||
}{
|
||||
{
|
||||
desc: "Should return 1 value",
|
||||
values: FieldNames{"field-1": "foo"},
|
||||
expected: FieldNames{"field-1": "foo"},
|
||||
},
|
||||
{
|
||||
desc: "Should return 2 values",
|
||||
values: FieldNames{"field-1": "foo", "field-2": "bar"},
|
||||
expected: FieldNames{"field-1": "foo", "field-2": "bar"},
|
||||
},
|
||||
{
|
||||
desc: "Should return 3 values",
|
||||
values: FieldNames{"field-1": "foo", "field-2": "bar", "field-3": "powpow"},
|
||||
expected: FieldNames{"field-1": "foo", "field-2": "bar", "field-3": "powpow"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := test.values.Get()
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsNamesString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
values FieldNames
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Should return 1 value",
|
||||
values: FieldNames{"field-1": "foo"},
|
||||
expected: "map[field-1:foo]",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := test.values.String()
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsNamesSetValue(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
values FieldNames
|
||||
expected *FieldNames
|
||||
}{
|
||||
{
|
||||
desc: "Should return 1 value",
|
||||
values: FieldNames{"field-1": "foo"},
|
||||
expected: &FieldNames{"field-1": "foo"},
|
||||
},
|
||||
{
|
||||
desc: "Should return 2 values",
|
||||
values: FieldNames{"field-1": "foo", "field-2": "bar"},
|
||||
expected: &FieldNames{"field-1": "foo", "field-2": "bar"},
|
||||
},
|
||||
{
|
||||
desc: "Should return 3 values",
|
||||
values: FieldNames{"field-1": "foo", "field-2": "bar", "field-3": "powpow"},
|
||||
expected: &FieldNames{"field-1": "foo", "field-2": "bar", "field-3": "powpow"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fieldsNames := &FieldNames{}
|
||||
fieldsNames.SetValue(test.values)
|
||||
assert.Equal(t, test.expected, fieldsNames)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsHeadersNamesSet(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
value string
|
||||
expected *FieldHeaderNames
|
||||
}{
|
||||
{
|
||||
desc: "One value should return FieldNames of size 1",
|
||||
value: "X-HEADER-1=foo",
|
||||
expected: &FieldHeaderNames{
|
||||
"X-HEADER-1": "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Two values separated by space should return FieldNames of size 2",
|
||||
value: "X-HEADER-1=foo X-HEADER-2=bar",
|
||||
expected: &FieldHeaderNames{
|
||||
"X-HEADER-1": "foo",
|
||||
"X-HEADER-2": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Two values separated by space with escaped double quotes should return FieldNames of size 2",
|
||||
value: "\"X-HEADER-1=foo X-HEADER-2=bar\"",
|
||||
expected: &FieldHeaderNames{
|
||||
"X-HEADER-1": "foo",
|
||||
"X-HEADER-2": "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
headersNames := &FieldHeaderNames{}
|
||||
err := headersNames.Set(test.value)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, headersNames)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsHeadersNamesGet(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
values FieldHeaderNames
|
||||
expected FieldHeaderNames
|
||||
}{
|
||||
{
|
||||
desc: "Should return 1 value",
|
||||
values: FieldHeaderNames{"X-HEADER-1": "foo"},
|
||||
expected: FieldHeaderNames{"X-HEADER-1": "foo"},
|
||||
},
|
||||
{
|
||||
desc: "Should return 2 values",
|
||||
values: FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar"},
|
||||
expected: FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar"},
|
||||
},
|
||||
{
|
||||
desc: "Should return 3 values",
|
||||
values: FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar", "X-HEADER-3": "powpow"},
|
||||
expected: FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar", "X-HEADER-3": "powpow"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := test.values.Get()
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsHeadersNamesString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
values FieldHeaderNames
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Should return 1 value",
|
||||
values: FieldHeaderNames{"X-HEADER-1": "foo"},
|
||||
expected: "map[X-HEADER-1:foo]",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := test.values.String()
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsHeadersNamesSetValue(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
values FieldHeaderNames
|
||||
expected *FieldHeaderNames
|
||||
}{
|
||||
{
|
||||
desc: "Should return 1 value",
|
||||
values: FieldHeaderNames{"X-HEADER-1": "foo"},
|
||||
expected: &FieldHeaderNames{"X-HEADER-1": "foo"},
|
||||
},
|
||||
{
|
||||
desc: "Should return 2 values",
|
||||
values: FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar"},
|
||||
expected: &FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar"},
|
||||
},
|
||||
{
|
||||
desc: "Should return 3 values",
|
||||
values: FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar", "X-HEADER-3": "powpow"},
|
||||
expected: &FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar", "X-HEADER-3": "powpow"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
headersNames := &FieldHeaderNames{}
|
||||
headersNames.SetValue(test.values)
|
||||
assert.Equal(t, test.expected, headersNames)
|
||||
})
|
||||
}
|
||||
}
|
82
pkg/types/metrics.go
Normal file
82
pkg/types/metrics.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Metrics provides options to expose and send Traefik metrics to different third party monitoring systems
|
||||
type Metrics struct {
|
||||
Prometheus *Prometheus `description:"Prometheus metrics exporter type" export:"true"`
|
||||
Datadog *Datadog `description:"DataDog metrics exporter type" export:"true"`
|
||||
StatsD *Statsd `description:"StatsD metrics exporter type" export:"true"`
|
||||
InfluxDB *InfluxDB `description:"InfluxDB metrics exporter type"`
|
||||
}
|
||||
|
||||
// Prometheus can contain specific configuration used by the Prometheus Metrics exporter
|
||||
type Prometheus struct {
|
||||
Buckets Buckets `description:"Buckets for latency metrics" export:"true"`
|
||||
EntryPoint string `description:"EntryPoint" export:"true"`
|
||||
Middlewares []string `description:"Middlewares" export:"true"`
|
||||
}
|
||||
|
||||
// Datadog contains address and metrics pushing interval configuration
|
||||
type Datadog struct {
|
||||
Address string `description:"DataDog's address"`
|
||||
PushInterval string `description:"DataDog push interval" export:"true"`
|
||||
}
|
||||
|
||||
// Statsd contains address and metrics pushing interval configuration
|
||||
type Statsd struct {
|
||||
Address string `description:"StatsD address"`
|
||||
PushInterval string `description:"StatsD push interval" export:"true"`
|
||||
}
|
||||
|
||||
// InfluxDB contains address, login and metrics pushing interval configuration
|
||||
type InfluxDB struct {
|
||||
Address string `description:"InfluxDB address"`
|
||||
Protocol string `description:"InfluxDB address protocol (udp or http)"`
|
||||
PushInterval string `description:"InfluxDB push interval" export:"true"`
|
||||
Database string `description:"InfluxDB database used when protocol is http" export:"true"`
|
||||
RetentionPolicy string `description:"InfluxDB retention policy used when protocol is http" export:"true"`
|
||||
Username string `description:"InfluxDB username (only with http)" export:"true"`
|
||||
Password string `description:"InfluxDB password (only with http)" export:"true"`
|
||||
}
|
||||
|
||||
// Statistics provides options for monitoring request and response stats
|
||||
type Statistics struct {
|
||||
RecentErrors int `description:"Number of recent errors logged" export:"true"`
|
||||
}
|
||||
|
||||
// Buckets holds Prometheus Buckets
|
||||
type Buckets []float64
|
||||
|
||||
// Set adds strings elem into the the parser
|
||||
// it splits str on "," and ";" and apply ParseFloat to string
|
||||
func (b *Buckets) Set(str string) error {
|
||||
fargs := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
// get function
|
||||
slice := strings.FieldsFunc(str, fargs)
|
||||
for _, bucket := range slice {
|
||||
bu, err := strconv.ParseFloat(bucket, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*b = append(*b, bu)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get []float64
|
||||
func (b *Buckets) Get() interface{} { return *b }
|
||||
|
||||
// String return slice in a string
|
||||
func (b *Buckets) String() string { return fmt.Sprintf("%v", *b) }
|
||||
|
||||
// SetValue sets []float64 into the parser
|
||||
func (b *Buckets) SetValue(val interface{}) {
|
||||
*b = val.(Buckets)
|
||||
}
|
10
pkg/types/route_appender.go
Normal file
10
pkg/types/route_appender.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"github.com/containous/mux"
|
||||
)
|
||||
|
||||
// RouteAppender appends routes on a router (/api, /ping ...)
|
||||
type RouteAppender interface {
|
||||
Append(systemRouter *mux.Router)
|
||||
}
|
93
pkg/types/tls.go
Normal file
93
pkg/types/tls.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
)
|
||||
|
||||
// ClientTLS holds TLS specific configurations as client
|
||||
// CA, Cert and Key can be either path or file contents
|
||||
type ClientTLS struct {
|
||||
CA string `description:"TLS CA" json:"ca,omitempty"`
|
||||
CAOptional bool `description:"TLS CA.Optional" json:"caOptional,omitempty"`
|
||||
Cert string `description:"TLS cert" json:"cert,omitempty"`
|
||||
Key string `description:"TLS key" json:"key,omitempty"`
|
||||
InsecureSkipVerify bool `description:"TLS insecure skip verify" json:"insecureSkipVerify,omitempty"`
|
||||
}
|
||||
|
||||
// CreateTLSConfig creates a TLS config from ClientTLS structures
|
||||
func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, error) {
|
||||
if clientTLS == nil {
|
||||
log.FromContext(ctx).Warnf("clientTLS is nil")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
caPool := x509.NewCertPool()
|
||||
clientAuth := tls.NoClientCert
|
||||
if clientTLS.CA != "" {
|
||||
var ca []byte
|
||||
if _, errCA := os.Stat(clientTLS.CA); errCA == nil {
|
||||
var err error
|
||||
ca, err = ioutil.ReadFile(clientTLS.CA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read CA. %s", err)
|
||||
}
|
||||
} else {
|
||||
ca = []byte(clientTLS.CA)
|
||||
}
|
||||
|
||||
if !caPool.AppendCertsFromPEM(ca) {
|
||||
return nil, fmt.Errorf("failed to parse CA")
|
||||
}
|
||||
|
||||
if clientTLS.CAOptional {
|
||||
clientAuth = tls.VerifyClientCertIfGiven
|
||||
} else {
|
||||
clientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
|
||||
if !clientTLS.InsecureSkipVerify && (len(clientTLS.Cert) == 0 || len(clientTLS.Key) == 0) {
|
||||
return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created")
|
||||
}
|
||||
|
||||
cert := tls.Certificate{}
|
||||
_, errKeyIsFile := os.Stat(clientTLS.Key)
|
||||
|
||||
if len(clientTLS.Cert) > 0 && len(clientTLS.Key) > 0 {
|
||||
var err error
|
||||
if _, errCertIsFile := os.Stat(clientTLS.Cert); errCertIsFile == nil {
|
||||
if errKeyIsFile == nil {
|
||||
cert, err = tls.LoadX509KeyPair(clientTLS.Cert, clientTLS.Key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load TLS keypair: %v", err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("tls cert is a file, but tls key is not")
|
||||
}
|
||||
} else {
|
||||
if errKeyIsFile != nil {
|
||||
cert, err = tls.X509KeyPair([]byte(clientTLS.Cert), []byte(clientTLS.Key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load TLS keypair: %v", err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("TLS key is a file, but tls cert is not")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TLSConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: caPool,
|
||||
InsecureSkipVerify: clientTLS.InsecureSkipVerify,
|
||||
ClientAuth: clientAuth,
|
||||
}
|
||||
return TLSConfig, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue