<|start_filename|>main_test.go<|end_filename|>
package main

import (
	"os"
	"reflect"
	"testing"

	"github.com/kpango/glg"
	"github.com/pkg/errors"
	"github.com/yahoojapan/authorization-proxy/v4/config"
)

func TestParseParams(t *testing.T) {
	type test struct {
		name       string
		beforeFunc func()
		checkFunc  func(*params) error
		checkErr   bool
	}
	tests := []test{
		func() test {
			return test{
				name: "check parseParams set default value",
				beforeFunc: func() {
					os.Args = []string{""}
				},
				checkFunc: func(p *params) error {
					if p.configFilePath != "/etc/athenz/provider/config.yaml" {
						return errors.Errorf("unexpected file path. got: %s, want: /etc/athenz/provider/config.yaml", p.configFilePath)
					}
					if p.showVersion != false {
						return errors.Errorf("unexpected showVersion flag. got: %v, want : false", p.showVersion)
					}
					return nil
				},
				checkErr: false,
			}
		}(),
		func() test {
			return test{
				name: "check parse error",
				checkFunc: func(p *params) error {
					return nil
				},
				beforeFunc: func() {
					os.Args = []string{"", "-="}
				},
				checkErr: true,
			}
		}(),
		func() test {
			return test{
				name: "check parseParams set user flags",
				beforeFunc: func() {
					os.Args = []string{"", "-f", "/dummy/path", "-version", "true"}
				},
				checkFunc: func(p *params) error {
					if p.configFilePath != "/dummy/path" {
						return errors.Errorf("unexpected file path. got: %s, want: /dummy/path", p.configFilePath)
					}
					if p.showVersion != true {
						return errors.Errorf("unexpected showVersion flag. got: %v, want: true", p.showVersion)
					}

					return nil
				},
				checkErr: false,
			}
		}(),
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.beforeFunc != nil {
				tt.beforeFunc()
			}

			got, err := parseParams()
			if err != nil && !tt.checkErr {
				t.Errorf("unexpected error: %v", err)
			}
			if err := tt.checkFunc(got); err != nil {
				t.Errorf("checkFunc() error: %v", err)
			}
		})
	}
}

func Test_run(t *testing.T) {
	type args struct {
		cfg config.Config
	}
	type test struct {
		name      string
		args      args
		checkFunc func([]error) error
	}
	tests := []test{
		{
			name: "run with log level, default",
			args: args{
				cfg: config.Config{
					Athenz: config.Athenz{
						URL: "invalid://url",
					},
					Log: config.Log{
						Level: "",
					},
				},
			},
			checkFunc: func(gotErrs []error) error {
				wantExitingErr := "usecase returned error: cannot newAuthzD(cfg): error creating authorizerd: Unsupported scheme, only support HTTP or HTTPS"
				if gotErrs == nil || gotErrs[0].Error() != wantExitingErr {
					return errors.Errorf("Unexpected exit: %v", gotErrs)
				}

				g := glg.Get()
				got := []glg.MODE{
					g.GetCurrentMode(glg.FATAL),
					g.GetCurrentMode(glg.ERR),
					g.GetCurrentMode(glg.WARN),
					g.GetCurrentMode(glg.INFO),
					g.GetCurrentMode(glg.DEBG),
				}
				want := []glg.MODE{
					glg.NONE,
					glg.NONE,
					glg.NONE,
					glg.NONE,
					glg.NONE,
				}
				if !reflect.DeepEqual(got, want) {
					return errors.Errorf("got: %v, want: %v", got, want)
				}
				return nil
			},
		},
		{
			name: "run with log level, fatal",
			args: args{
				cfg: config.Config{
					Athenz: config.Athenz{
						URL: "invalid://url",
					},
					Log: config.Log{
						Level: "fatal",
					},
				},
			},
			checkFunc: func(gotErrs []error) error {
				wantExitingErr := "usecase returned error: cannot newAuthzD(cfg): error creating authorizerd: Unsupported scheme, only support HTTP or HTTPS"
				if gotErrs == nil || gotErrs[0].Error() != wantExitingErr {
					return errors.Errorf("Unexpected exit: %v", gotErrs)
				}

				g := glg.Get()
				got := []glg.MODE{
					g.GetCurrentMode(glg.FATAL),
					g.GetCurrentMode(glg.ERR),
					g.GetCurrentMode(glg.WARN),
					g.GetCurrentMode(glg.INFO),
					g.GetCurrentMode(glg.DEBG),
				}
				want := []glg.MODE{
					glg.STD,
					glg.NONE,
					glg.NONE,
					glg.NONE,
					glg.NONE,
				}
				if !reflect.DeepEqual(got, want) {
					return errors.Errorf("got: %v, want: %v", got, want)
				}
				return nil
			},
		},
		{
			name: "run with log level, error",
			args: args{
				cfg: config.Config{
					Athenz: config.Athenz{
						URL: "invalid://url",
					},
					Log: config.Log{
						Level: "error",
					},
				},
			},
			checkFunc: func(gotErrs []error) error {
				wantExitingErr := "usecase returned error: cannot newAuthzD(cfg): error creating authorizerd: Unsupported scheme, only support HTTP or HTTPS"
				if gotErrs == nil || gotErrs[0].Error() != wantExitingErr {
					return errors.Errorf("Unexpected exit: %v", gotErrs)
				}

				g := glg.Get()
				got := []glg.MODE{
					g.GetCurrentMode(glg.FATAL),
					g.GetCurrentMode(glg.ERR),
					g.GetCurrentMode(glg.WARN),
					g.GetCurrentMode(glg.INFO),
					g.GetCurrentMode(glg.DEBG),
				}
				want := []glg.MODE{
					glg.STD,
					glg.STD,
					glg.NONE,
					glg.NONE,
					glg.NONE,
				}
				if !reflect.DeepEqual(got, want) {
					return errors.Errorf("got: %v, want: %v", got, want)
				}
				return nil
			},
		},
		{
			name: "run with log level, warn",
			args: args{
				cfg: config.Config{
					Athenz: config.Athenz{
						URL: "invalid://url",
					},
					Log: config.Log{
						Level: "warn",
					},
				},
			},
			checkFunc: func(gotErrs []error) error {
				wantExitingErr := "usecase returned error: cannot newAuthzD(cfg): error creating authorizerd: Unsupported scheme, only support HTTP or HTTPS"
				if gotErrs == nil || gotErrs[0].Error() != wantExitingErr {
					return errors.Errorf("Unexpected exit: %v", gotErrs)
				}

				g := glg.Get()
				got := []glg.MODE{
					g.GetCurrentMode(glg.FATAL),
					g.GetCurrentMode(glg.ERR),
					g.GetCurrentMode(glg.WARN),
					g.GetCurrentMode(glg.INFO),
					g.GetCurrentMode(glg.DEBG),
				}
				want := []glg.MODE{
					glg.STD,
					glg.STD,
					glg.STD,
					glg.NONE,
					glg.NONE,
				}
				if !reflect.DeepEqual(got, want) {
					return errors.Errorf("got: %v, want: %v", got, want)
				}
				return nil
			},
		},
		{
			name: "run with log level, info",
			args: args{
				cfg: config.Config{
					Athenz: config.Athenz{
						URL: "invalid://url",
					},
					Log: config.Log{
						Level: "info",
					},
				},
			},
			checkFunc: func(gotErrs []error) error {
				wantExitingErr := "usecase returned error: cannot newAuthzD(cfg): error creating authorizerd: Unsupported scheme, only support HTTP or HTTPS"
				if gotErrs == nil || gotErrs[0].Error() != wantExitingErr {
					return errors.Errorf("Unexpected exit: %v", gotErrs)
				}

				g := glg.Get()
				got := []glg.MODE{
					g.GetCurrentMode(glg.FATAL),
					g.GetCurrentMode(glg.ERR),
					g.GetCurrentMode(glg.WARN),
					g.GetCurrentMode(glg.INFO),
					g.GetCurrentMode(glg.DEBG),
				}
				want := []glg.MODE{
					glg.STD,
					glg.STD,
					glg.STD,
					glg.STD,
					glg.NONE,
				}
				if !reflect.DeepEqual(got, want) {
					return errors.Errorf("got: %v, want: %v", got, want)
				}
				return nil
			},
		},
		{
			name: "run with log level, debug",
			args: args{
				cfg: config.Config{
					Athenz: config.Athenz{
						URL: "invalid://url",
					},
					Log: config.Log{
						Level: "debug",
					},
				},
			},
			checkFunc: func(gotErrs []error) error {
				wantExitingErr := "usecase returned error: cannot newAuthzD(cfg): error creating authorizerd: Unsupported scheme, only support HTTP or HTTPS"
				if gotErrs == nil || gotErrs[0].Error() != wantExitingErr {
					return errors.Errorf("Unexpected exit: %v", gotErrs)
				}

				g := glg.Get()
				got := []glg.MODE{
					g.GetCurrentMode(glg.FATAL),
					g.GetCurrentMode(glg.ERR),
					g.GetCurrentMode(glg.WARN),
					g.GetCurrentMode(glg.INFO),
					g.GetCurrentMode(glg.DEBG),
				}
				want := []glg.MODE{
					glg.STD,
					glg.STD,
					glg.STD,
					glg.STD,
					glg.STD,
				}
				if !reflect.DeepEqual(got, want) {
					return errors.Errorf("got: %v, want: %v", got, want)
				}
				return nil
			},
		},
		{
			name: "run with log color",
			args: args{
				cfg: config.Config{
					Athenz: config.Athenz{
						URL: "invalid://url",
					},
					Log: config.Log{
						Level: "info",
						Color: true,
					},
				},
			},
			checkFunc: func(gotErrs []error) error {
				wantExitingErr := "usecase returned error: cannot newAuthzD(cfg): error creating authorizerd: Unsupported scheme, only support HTTP or HTTPS"
				if gotErrs == nil || gotErrs[0].Error() != wantExitingErr {
					return errors.Errorf("Unexpected exit: %v", gotErrs)
				}

				// glg.logger.isColor is private, cannot test
				return nil
			},
		},
		{
			name: "invalid log level",
			args: args{
				cfg: config.Config{
					Log: config.Log{
						Level: "invalid",
					},
				},
			},
			checkFunc: func(gotErrs []error) error {
				want := "invalid log level"
				if len(gotErrs) != 1 {
					return errors.New("len(gotErrs) != 1")
				}
				if gotErrs[0].Error() != want {
					return errors.Errorf("gotErrs: %v, want: %v", gotErrs[0], want)
				}
				return nil
			},
		},
		{
			name: "run error",
			args: args{
				cfg: config.Config{
					Authorization: config.Authorization{
						PublicKey: config.PublicKey{
							RefreshPeriod: "dummy",
						},
					},
				},
			},
			checkFunc: func(gotErrs []error) error {
				want := "usecase returned error: cannot newAuthzD(cfg): error create pubkeyd: invalid refresh period: time: invalid duration dummy"
				if len(gotErrs) != 1 {
					return errors.New("len(gotErrs) != 1")
				}
				if gotErrs[0].Error() != want {
					return errors.Errorf("gotErr: %v, want: %v", gotErrs[0], want)
				}
				return nil
			},
		},
		{
			name: "daemon init error",
			args: args{
				cfg: config.Config{
					Athenz: config.Athenz{
						URL: "127.0.0.1",
					},
					Authorization: config.Authorization{
						RoleToken: config.RoleToken{
							Enable: true,
						},
					},
				},
			},
			checkFunc: func(gotErrs []error) error {
				want1 := "daemon init error: error when processing pubkey: Error updating ZMS athenz pubkey: error fetch public key entries: error make http request: Get \"https://127.0.0.1/domain/sys.auth/service/zms\": dial tcp 127.0.0.1:443: connect: connection refused"
				want2 := "daemon init error: error when processing pubkey: Error updating ZTS athenz pubkey: error fetch public key entries: error make http request: Get \"https://127.0.0.1/domain/sys.auth/service/zts\": dial tcp 127.0.0.1:443: connect: connection refused"
				if len(gotErrs) != 1 {
					return errors.New("len(gotErrs) != 1")
				}
				if gotErrs[0].Error() != want1 && gotErrs[0].Error() != want2 {
					return errors.Errorf("gotErrs: %v, want: %v", gotErrs[0], want1)
				}
				return nil
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotErrs := run(tt.args.cfg)
			if err := tt.checkFunc(gotErrs); err != nil {
				t.Errorf("run() fails: %v", err)
			}
		})
	}
}

func Test_getVersion(t *testing.T) {
	tests := []struct {
		name       string
		want       string
		beforeFunc func()
	}{
		{
			name:       "default",
			want:       "development version",
			beforeFunc: func() {},
		},
		{
			name: "Version already set",
			want: "1.2.333",
			beforeFunc: func() {
				Version = "1.2.333"
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.beforeFunc != nil {
				tt.beforeFunc()
			}
			if got := getVersion(); got != tt.want {
				t.Errorf("getVersion() = %v, want %v", got, tt.want)
			}
		})
	}
}


<|start_filename|>service/option.go<|end_filename|>
package service

import (
	"net/http"

	"github.com/yahoojapan/authorization-proxy/v4/config"
)

// Option represents a functional option
type Option func(*server)

// WithServerConfig returns a ServerConfig functional option
func WithServerConfig(cfg config.Server) Option {
	return func(s *server) {
		s.cfg = cfg
	}
}

// WithServerHandler returns a ServerHandler functional option
func WithServerHandler(h http.Handler) Option {
	return func(s *server) {
		s.srvHandler = h
	}
}

// WithDebugHandler returns a DebugHandler functional option
func WithDebugHandler(h http.Handler) Option {
	return func(s *server) {
		s.dsHandler = h
	}
}


<|start_filename|>config/config.go<|end_filename|>
/*
Copyright (C)  2018 Yahoo Japan Corporation Athenz team.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
	"os"
	"strings"
	"time"

	authorizerd "github.com/yahoojapan/athenz-authorizer/v5"

	"github.com/pkg/errors"
	yaml "gopkg.in/yaml.v2"
)

const (
	// currentVersion represents the current configuration version.
	currentVersion = "v2.0.0"
)

// Config represents the configuration (config.yaml) of authorization proxy.
type Config struct {
	// Version represents the configuration file version.
	Version string `yaml:"version"`

	// Server represents the authorization proxy and the health check server configuration.
	Server Server `yaml:"server"`

	// Athenz represents the Athenz server connection configuration.
	Athenz Athenz `yaml:"athenz"`

	// Proxy represents the proxy destination configuration.
	Proxy Proxy `yaml:"proxy"`

	// Authorization represents the detail authorization configuration.
	Authorization Authorization `yaml:"authorization"`

	// Log represents the logger configuration.
	Log Log `yaml:"log"`
}

// Server represents the authorization proxy and the health check server configuration.
type Server struct {
	// Port represents the server listening port.
	Port int `yaml:"port"`

	// Timeout represents the maximum request handling duration.
	Timeout string `yaml:"timeout"`

	// ShutdownTimeout represents the duration before force shutdown.
	ShutdownTimeout string `yaml:"shutdownTimeout"`

	// ShutdownDelay represents the delay duration between the health check server shutdown and the client sidecar server shutdown.
	ShutdownDelay string `yaml:"shutdownDelay"`

	// TLS represents the TLS configuration of the authorization proxy.
	TLS TLS `yaml:"tls"`

	// HealthCheck represents the health check server configuration.
	HealthCheck HealthCheck `yaml:"healthCheck"`

	// Debug represents the debug server configuration.
	Debug Debug `yaml:"debug"`
}

// TLS represents the TLS configuration of the authorization proxy.
type TLS struct {
	// Enable represents whether to enable TLS.
	Enable bool `yaml:"enable"`

	// CertPath represents the server certificate file path.
	CertPath string `yaml:"certPath"`

	// KeyPath represents the private key file path of the server certificate.
	KeyPath string `yaml:"keyPath"`

	// CAPath represents the CA certificate chain file path for verifying client certificates.
	CAPath string `yaml:"caPath"`
}

// HealthCheck represents the health check server configuration.
type HealthCheck struct {
	// Port represents the server listening port.
	Port int `yaml:"port"`

	// Endpoint represents the health check endpoint (pattern).
	Endpoint string `yaml:"endpoint"`
}

// Debug represents the debug server configuration.
type Debug struct {
	// Enable represents if user want to enable debug server functionality.
	Enable bool `yaml:"enable"`

	// Port represents debug server port.
	Port int `yaml:"port"`

	// Dump represents whether to enable memory dump functionality.
	Dump bool `yaml:"dump"`

	// Profiling represents whether to enable profiling functionality.
	Profiling bool `yaml:"profiling"`
}

// Athenz represents the Athenz server connection configuration.
type Athenz struct {
	// URL represents the Athenz (ZMS or ZTS) API URL.
	URL string `yaml:"url"`

	// Timeout represents the request timeout duration to Athenz server.
	Timeout string `yaml:"timeout"`

	// CAPath represents the CA certificate chain file path for verifying Athenz server certificate.
	CAPath string `yaml:"caPath"`
}

// Proxy represents the proxy destination configuration.
type Proxy struct {
	// Scheme represents the HTTP URL scheme of the proxy destination, default is http.
	Scheme string `yaml:"scheme"`

	// Host represents the proxy destination host, for example, localhost.
	Host string `yaml:"host"`

	// Port represents the proxy destination port number.
	Port uint16 `yaml:"port"`

	// BufferSize represents the reverse proxy buffer size.
	BufferSize uint64 `yaml:"bufferSize"`

	// OriginHealthCheckPaths represents health check paths of your origin application.
	// WARNING!!! Setting this configuration may introduce security hole in your system. ONLY set this configuration as the application's health check endpoint.
	// Tips for performance: define your health check endpoint with a different length from the most frequently used endpoint, for example, use `/healthcheck` (len: 12) when `/most_used` (len: 10), instead of `/healthccc` (len: 10)
	OriginHealthCheckPaths []string `yaml:"originHealthCheckPaths"`

	// Transport exposes http.Transport parameters
	Transport Transport `yaml:"transport,omitempty"`
}

// Authorization represents the detail authorization configuration.
type Authorization struct {
	// AthenzDomains represents Athenz domains containing the RBAC policies.
	AthenzDomains []string `yaml:"athenzDomains"`

	// PublicKey represents the configuration to fetch Athenz public keys.
	PublicKey PublicKey `yaml:"publicKey"`

	// Policy represents the configuration to fetch Athenz policies.
	Policy Policy `yaml:"policy"`

	// JWK represents the configuration to fetch Athenz JWK.
	JWK JWK `yaml:"jwk"`

	// AccessToken represents the configuration to control access token verification.
	AccessToken AccessToken `yaml:"accessToken"`

	// RoleToken represents the configuration to control role token verification.
	RoleToken RoleToken `yaml:"roleToken"`
}

// PublicKey represents the configuration to fetch Athenz public keys.
type PublicKey struct {
	// SysAuthDomain represents the system authentication domain of Athenz.
	SysAuthDomain string `yaml:"sysAuthDomain"`

	// RefreshPeriod represents the duration of the refresh period.
	RefreshPeriod string `yaml:"refreshPeriod"`

	// RetryDelay represents the duration between each retry.
	RetryDelay string `yaml:"retryDelay"`

	// ETagExpiry represents the duration before Etag expires.
	ETagExpiry string `yaml:"eTagExpiry"`

	// ETagPurgePeriod represents the duration of purging expired items in the ETag cache.
	ETagPurgePeriod string `yaml:"eTagPurgePeriod"`
}

// Policy represents the configuration to fetch Athenz policies.
type Policy struct {
	// Disable decides whether to check the policy.
	Disable bool `yaml:"disable"`

	// ExpiryMargin represents the policy expiry margin to force refresh policies beforehand.
	ExpiryMargin string `yaml:"expiryMargin"`

	// RefreshPeriod represents the duration of the refresh period.
	RefreshPeriod string `yaml:"refreshPeriod"`

	// PurgePeriod represents the duration of purging expired items in the cache.
	PurgePeriod string `yaml:"purgePeriod"`

	// RetryDelay represents the duration between each retry.
	RetryDelay string `yaml:"retryDelay"`

	// RetryAttempts represents number of attempts to retry.
	RetryAttempts int `yaml:"retryAttempts"`

	// MappingRules represents translation rules for determining action and resource.
	MappingRules map[string][]authorizerd.Rule `yaml:"mappingRules"`
}

// JWK represents the configuration to fetch Athenz JWK.
type JWK struct {
	// RefreshPeriod represents the duration of the refresh period.
	RefreshPeriod string `yaml:"refreshPeriod"`

	// RetryDelay represents the duration between each retry.
	RetryDelay string `yaml:"retryDelay"`

	// URLs represents URLs that delivers JWK Set excluding athenz.
	URLs []string `yaml:"urls"`
}

// AccessToken represents the configuration to control access token verification.
type AccessToken struct {
	// Enable decides whether to verify access token.
	Enable bool `yaml:"enable"`

	// VerifyCertThumbprint represents whether to enforce certificate thumbprint verification.
	VerifyCertThumbprint bool `yaml:"verifyCertThumbprint"`

	// VerifyClientID represents whether to enforce certificate common name and client_id verification.
	VerifyClientID bool `yaml:"verifyClientID"`

	// AuthorizedClientIDs represents list of allowed client_id and common name.
	AuthorizedClientIDs map[string][]string `yaml:"authorizedClientIDs"`

	// CertBackdateDuration represents the certificate issue time backdating duration. (for usecase: new cert + old token)
	CertBackdateDuration string `yaml:"certBackdateDuration"`

	// CertOffsetDuration represents the certificate issue time offset when comparing with the issue time of the access token. (for usecase: new cert + old token)
	CertOffsetDuration string `yaml:"certOffsetDuration"`
}

// RoleToken represents the configuration to control role token verification.
type RoleToken struct {
	// Enable decides whether to verify role token.
	Enable bool `yaml:"enable"`

	// RoleAuthHeader represents the HTTP header for extracting the role token.
	RoleAuthHeader string `yaml:"roleAuthHeader"`
}

// Log represents the logger configuration.
type Log struct {
	// Level represents the logger output level. Values: "debug", "info", "warn", "error", "fatal".
	Level string `yaml:"level"`

	// Color represents whether to print ANSI escape code.
	Color bool `yaml:"color"`
}

// Transport exposes a subset of Transport parameters. reference: https://github.com/golang/go/blob/master/src/net/http/transport.go#L95
type Transport struct {
	TLSHandshakeTimeout    time.Duration `yaml:"tlsHandshakeTimeout,omitempty"`
	DisableKeepAlives      bool          `yaml:"disableKeepAlives,omitempty"`
	DisableCompression     bool          `yaml:"disableCompression,omitempty"`
	MaxIdleConns           int           `yaml:"maxIdleConns,omitempty"`
	MaxIdleConnsPerHost    int           `yaml:"maxIdleConnsPerHost,omitempty"`
	MaxConnsPerHost        int           `yaml:"maxConnsPerHost,omitempty"`
	IdleConnTimeout        time.Duration `yaml:"idleConnTimeout,omitempty"`
	ResponseHeaderTimeout  time.Duration `yaml:"responseHeaderTimeout,omitempty"`
	ExpectContinueTimeout  time.Duration `yaml:"expectContinueTimeout,omitempty"`
	MaxResponseHeaderBytes int64         `yaml:"maxResponseHeaderBytes,omitempty"`
	WriteBufferSize        int           `yaml:"writeBufferSize,omitempty"`
	ReadBufferSize         int           `yaml:"readBufferSize,omitempty"`
	ForceAttemptHTTP2      bool          `yaml:"forceAttemptHTTP2,omitempty"`
}

// New returns the decoded configuration YAML file as *Config struct. Returns non-nil error if any.
func New(path string) (*Config, error) {
	f, err := os.OpenFile(path, os.O_RDONLY, 0600)
	if err != nil {
		return nil, errors.Wrap(err, "OpenFile failed")
	}
	cfg := new(Config)
	err = yaml.NewDecoder(f).Decode(&cfg)
	if err != nil {
		return nil, errors.Wrap(err, "decode file failed")
	}

	return cfg, nil
}

// GetVersion returns the current configuration version of the authorization proxy.
func GetVersion() string {
	return currentVersion
}

// GetActualValue returns the environment variable value if the given val has "_" prefix and suffix, otherwise returns val directly.
func GetActualValue(val string) string {
	if checkPrefixAndSuffix(val, "_", "_") {
		return os.Getenv(strings.TrimPrefix(strings.TrimSuffix(val, "_"), "_"))
	}
	return val
}

// checkPrefixAndSuffix checks if the given string has given prefix and suffix.
func checkPrefixAndSuffix(str, pref, suf string) bool {
	return strings.HasPrefix(str, pref) && strings.HasSuffix(str, suf)
}


<|start_filename|>router/debug_router.go<|end_filename|>
/*
Copyright (C)  2018 Yahoo Japan Corporation Athenz team.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package router

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/kpango/glg"
	"github.com/yahoojapan/authorization-proxy/v4/config"
	"github.com/yahoojapan/authorization-proxy/v4/handler"
	"github.com/yahoojapan/authorization-proxy/v4/service"
)

// NewDebugRouter return the ServeMux with debug endpoints
func NewDebugRouter(cfg config.Server, a service.Authorizationd) *http.ServeMux {
	http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = 32
	mux := http.NewServeMux()

	dur, err := time.ParseDuration(cfg.Timeout)
	if err != nil {
		dur = time.Second * 3
	}

	for _, route := range NewDebugRoutes(cfg.Debug, a) {
		mux.Handle(route.Pattern, routing(route.Methods, dur, route.HandlerFunc))
	}

	return mux
}

func routing(m []string, t time.Duration, h handler.Func) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		for _, method := range m {
			if strings.EqualFold(r.Method, method) || method == "*" {

				ctx, cancel := context.WithTimeout(r.Context(), t)
				defer cancel()
				start := time.Now()
				ech := make(chan error)
				go func() {
					ech <- h(w, r.WithContext(ctx))
					close(ech)
				}()

				for {
					select {
					case err := <-ech:
						if err != nil {
							http.Error(w,
								fmt.Sprintf("Error: %s\t%s",
									err.Error(),
									http.StatusText(http.StatusInternalServerError)),
								http.StatusInternalServerError)
							glg.Error(err)
						}
						return
					case <-ctx.Done():
						glg.Errorf("Handler Time Out: %v", time.Since(start))
						return
					}
				}
			}
		}

		_, err := io.Copy(ioutil.Discard, r.Body)
		if err != nil {
			glg.Fatalln(err)
		}
		err = r.Body.Close()
		if err != nil {
			glg.Fatalln(err)
		}
		http.Error(w,
			fmt.Sprintf("Method: %s\t%s",
				r.Method,
				http.StatusText(http.StatusMethodNotAllowed)),
			http.StatusMethodNotAllowed)
	})
}


<|start_filename|>handler/transport_test.go<|end_filename|>
package handler

import (
	"errors"
	"net/http"
	"reflect"
	"testing"

	authorizerd "github.com/yahoojapan/athenz-authorizer/v5"

	"github.com/yahoojapan/authorization-proxy/v4/config"
	"github.com/yahoojapan/authorization-proxy/v4/service"
)

type readCloseCounter struct {
	CloseCount int
	ReadErr    error
}

func (r *readCloseCounter) Read(b []byte) (int, error) {
	return 0, r.ReadErr
}

func (r *readCloseCounter) Close() error {
	r.CloseCount++
	return nil
}

func Test_transport_RoundTrip(t *testing.T) {
	type fields struct {
		RoundTripper http.RoundTripper
		prov         service.Authorizationd
		cfg          config.Proxy
	}
	type args struct {
		r    *http.Request
		body *readCloseCounter
	}
	tests := []struct {
		name           string
		fields         fields
		args           args
		want           *http.Response
		wantErr        bool
		wantCloseCount int
	}{
		{
			name: "verify role token failed",
			fields: fields{
				RoundTripper: nil,
				prov: &service.AuthorizerdMock{
					VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
						return nil, errors.New("dummy error")
					},
				},
				cfg: config.Proxy{},
			},
			args: args{
				r: func() *http.Request {
					r, _ := http.NewRequest("GET", "http://athenz.io", nil)
					return r
				}(),
				body: &readCloseCounter{
					ReadErr: errors.New("readCloseCounter.Read not implemented"),
				},
			},
			wantErr:        true,
			wantCloseCount: 1,
		},
		{
			name: "verify role token success",
			fields: fields{
				RoundTripper: &RoundTripperMock{
					RoundTripFunc: func(req *http.Request) (*http.Response, error) {
						return &http.Response{
							StatusCode: 999,
						}, nil
					},
				},
				prov: &service.AuthorizerdMock{
					VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
						return &PrincipalMock{
							NameFunc: func() string {
								return ""
							},
							RolesFunc: func() []string {
								return []string{}
							},
							DomainFunc: func() string {
								return ""
							},
							IssueTimeFunc: func() int64 {
								return 0
							},
							ExpiryTimeFunc: func() int64 {
								return 0
							},
						}, nil
					},
				},
				cfg: config.Proxy{},
			},
			args: args{
				r: func() *http.Request {
					r, _ := http.NewRequest("GET", "http://athenz.io", nil)
					return r
				}(),
				body: &readCloseCounter{
					ReadErr: errors.New("readCloseCounter.Read not implemented"),
				},
			},
			want: &http.Response{
				StatusCode: 999,
			},
			wantErr:        false,
			wantCloseCount: 0,
		},
		{
			name: "verify role token success (empty bypass URLs)",
			fields: fields{
				RoundTripper: &RoundTripperMock{
					RoundTripFunc: func(req *http.Request) (*http.Response, error) {
						return &http.Response{
							StatusCode: 999,
						}, nil
					},
				},
				prov: &service.AuthorizerdMock{
					VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
						return &PrincipalMock{
							NameFunc: func() string {
								return ""
							},
							RolesFunc: func() []string {
								return []string{}
							},
							DomainFunc: func() string {
								return ""
							},
							IssueTimeFunc: func() int64 {
								return 0
							},
							ExpiryTimeFunc: func() int64 {
								return 0
							},
						}, nil
					},
				},
				cfg: config.Proxy{
					OriginHealthCheckPaths: []string{},
				},
			},
			args: args{
				r: func() *http.Request {
					r, _ := http.NewRequest("GET", "http://athenz.io", nil)
					return r
				}(),
				body: &readCloseCounter{
					ReadErr: errors.New("readCloseCounter.Read not implemented"),
				},
			},
			want: &http.Response{
				StatusCode: 999,
			},
			wantErr:        false,
			wantCloseCount: 0,
		},
		{
			name: "OriginHealthCheckPaths match, bypass role token verification",
			fields: fields{
				RoundTripper: &RoundTripperMock{
					RoundTripFunc: func(req *http.Request) (*http.Response, error) {
						return &http.Response{
							StatusCode: 200,
						}, nil
					},
				},
				prov: &service.AuthorizerdMock{
					VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
						return nil, errors.New("role token error")
					},
				},
				cfg: config.Proxy{
					OriginHealthCheckPaths: []string{
						"/healthz",
					},
				},
			},
			args: args{
				r: func() *http.Request {
					r, _ := http.NewRequest("GET", "http://athenz.io/healthz", nil)
					return r
				}(),
				body: &readCloseCounter{
					ReadErr: errors.New("readCloseCounter.Read not implemented"),
				},
			},
			want: &http.Response{
				StatusCode: 200,
			},
			wantErr:        false,
			wantCloseCount: 0,
		},
		{
			name: "OriginHealthCheckPaths ANY match, bypass role token verification",
			fields: fields{
				RoundTripper: &RoundTripperMock{
					RoundTripFunc: func(req *http.Request) (*http.Response, error) {
						return &http.Response{
							StatusCode: 200,
						}, nil
					},
				},
				prov: &service.AuthorizerdMock{
					VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
						return nil, errors.New("role token error")
					},
				},
				cfg: config.Proxy{
					OriginHealthCheckPaths: []string{
						"/healthz",
						"/healthz/",
					},
				},
			},
			args: args{
				r: func() *http.Request {
					r, _ := http.NewRequest("GET", "http://athenz.io/healthz/", nil)
					return r
				}(),
				body: &readCloseCounter{
					ReadErr: errors.New("readCloseCounter.Read not implemented"),
				},
			},
			want: &http.Response{
				StatusCode: 200,
			},
			wantErr:        false,
			wantCloseCount: 0,
		},
		{
			name: "OriginHealthCheckPaths NONE match, verify role token",
			fields: fields{
				RoundTripper: nil,
				prov: &service.AuthorizerdMock{
					VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
						return nil, errors.New("role token error")
					},
				},
				cfg: config.Proxy{
					OriginHealthCheckPaths: []string{
						"/healthz",
					},
				},
			},
			args: args{
				r: func() *http.Request {
					r, _ := http.NewRequest("GET", "http://athenz.io/healthz/", nil)
					return r
				}(),
				body: &readCloseCounter{
					ReadErr: errors.New("readCloseCounter.Read not implemented"),
				},
			},
			wantErr:        true,
			wantCloseCount: 1,
		},
		{
			name: "OriginHealthCheckPaths NOT set, verify role token",
			fields: fields{
				RoundTripper: nil,
				prov: &service.AuthorizerdMock{
					VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
						return nil, errors.New("role token error")
					},
				},
				cfg: config.Proxy{},
			},
			args: args{
				r: func() *http.Request {
					r, _ := http.NewRequest("GET", "http://athenz.io/healthz", nil)
					return r
				}(),
				body: &readCloseCounter{
					ReadErr: errors.New("readCloseCounter.Read not implemented"),
				},
			},
			wantErr:        true,
			wantCloseCount: 1,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tr := &transport{
				RoundTripper: tt.fields.RoundTripper,
				prov:         tt.fields.prov,
				cfg:          tt.fields.cfg,
			}
			if tt.args.body != nil {
				tt.args.r.Body = tt.args.body
			}
			got, err := tr.RoundTrip(tt.args.r)
			if (err != nil) != tt.wantErr {
				t.Errorf("transport.RoundTrip() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("transport.RoundTrip() = %v, want %v", got, tt.want)
			}
			if tt.args.body != nil {
				if tt.args.body.CloseCount != tt.wantCloseCount {
					t.Errorf("Body was closed %d times, expected %d", tt.args.body.CloseCount, tt.wantCloseCount)
				}
			}
		})
	}
}


<|start_filename|>service/authorizerd_mock.go<|end_filename|>
package service

import (
	"context"
	"crypto/x509"
	"net/http"

	authorizerd "github.com/yahoojapan/athenz-authorizer/v5"
)

// AuthorizerdMock is a mock of Authorizerd
type AuthorizerdMock struct {
	InitFunc              func(context.Context) error
	StartFunc             func(context.Context) <-chan error
	VerifyFunc            func(r *http.Request, act, res string) (authorizerd.Principal, error)
	VerifyAccessTokenFunc func(ctx context.Context, tok, act, res string, cert *x509.Certificate) (authorizerd.Principal, error)
	VerifyRoleTokenFunc   func(ctx context.Context, tok, act, res string) (authorizerd.Principal, error)
	VerifyRoleJWTFunc     func(ctx context.Context, tok, act, res string) error
	VerifyRoleCertFunc    func(ctx context.Context, peerCerts []*x509.Certificate, act, res string) (authorizerd.Principal, error)
	GetPolicyCacheFunc    func(ctx context.Context) map[string]interface{}
}

// Init is a mock implementation of Authorizerd.Init
func (am *AuthorizerdMock) Init(ctx context.Context) error {
	return am.InitFunc(ctx)
}

// Start is a mock implementation of Authorizerd.Start
func (am *AuthorizerdMock) Start(ctx context.Context) <-chan error {
	return am.StartFunc(ctx)
}

// Verify is a mock implementation of Authorizerd.Verify
func (am *AuthorizerdMock) Verify(r *http.Request, act, res string) error {
	_, err := am.VerifyFunc(r, act, res)
	return err
}

// Authorize is a mock implementation of Authorizerd.Authorize
func (am *AuthorizerdMock) Authorize(r *http.Request, act, res string) (authorizerd.Principal, error) {
	return am.VerifyFunc(r, act, res)
}

// VerifyAccessToken is a mock implementation of Authorizerd.VerifyAccessToken
func (am *AuthorizerdMock) VerifyAccessToken(ctx context.Context, tok, act, res string, cert *x509.Certificate) error {
	_, err := am.VerifyAccessTokenFunc(ctx, tok, act, res, cert)
	return err
}

// AuthorizeAccessToken is a mock implementation of Authorizerd.AuthorizeAccessToken
func (am *AuthorizerdMock) AuthorizeAccessToken(ctx context.Context, tok, act, res string, cert *x509.Certificate) (authorizerd.Principal, error) {
	return am.VerifyAccessTokenFunc(ctx, tok, act, res, cert)
}

// VerifyRoleToken is a mock implementation of Authorizerd.VerifyRoleToken
func (am *AuthorizerdMock) VerifyRoleToken(ctx context.Context, tok, act, res string) error {
	_, err := am.VerifyRoleTokenFunc(ctx, tok, act, res)
	return err
}

// AuthorizeRoleToken is a mock implementation of Authorizerd.AuthorizeRoleToken
func (am *AuthorizerdMock) AuthorizeRoleToken(ctx context.Context, tok, act, res string) (authorizerd.Principal, error) {
	return am.VerifyRoleTokenFunc(ctx, tok, act, res)
}

// VerifyRoleJWT is a mock implementation of Authorizerd.VerifyRoleJWT
func (am *AuthorizerdMock) VerifyRoleJWT(ctx context.Context, tok, act, res string) error {
	return am.VerifyRoleJWTFunc(ctx, tok, act, res)
}

// VerifyRoleCert is a mock implementation of Authorizerd.VerifyRoleCert
func (am *AuthorizerdMock) VerifyRoleCert(ctx context.Context, peerCerts []*x509.Certificate, act, res string) error {
	_, err := am.VerifyRoleCertFunc(ctx, peerCerts, act, res)
	return err
}

// AuthorizeRoleCert is a mock implementation of Authorizerd.AuthorizeRoleCert
func (am *AuthorizerdMock) AuthorizeRoleCert(ctx context.Context, peerCerts []*x509.Certificate, act, res string) (authorizerd.Principal, error) {
	return am.VerifyRoleCertFunc(ctx, peerCerts, act, res)
}

// GetPolicyCache is a mock implementation of Authorizerd.GetPolicyCache
func (am *AuthorizerdMock) GetPolicyCache(ctx context.Context) map[string]interface{} {
	return am.GetPolicyCacheFunc(ctx)
}


<|start_filename|>usecase/authz_proxyd_test.go<|end_filename|>
package usecase

import (
	"context"
	"fmt"
	authorizerd "github.com/yahoojapan/athenz-authorizer/v5"
	"reflect"
	"sort"
	"sync"
	"testing"
	"time"

	"github.com/yahoojapan/authorization-proxy/v4/config"
	"github.com/yahoojapan/authorization-proxy/v4/service"

	"github.com/pkg/errors"
)

func TestNew(t *testing.T) {
	type args struct {
		cfg config.Config
	}
	type test struct {
		name      string
		args      args
		checkFunc func(AuthzProxyDaemon) error
		wantErr   bool
	}
	tests := []test{
		func() test {
			cfg := config.Config{
				Athenz: config.Athenz{
					URL: "athenz.io",
				},
				Authorization: config.Authorization{
					AthenzDomains: []string{"dummyDom1", "dummyDom2"},
					PublicKey: config.PublicKey{
						SysAuthDomain:   "dummy.sys.auth",
						RefreshPeriod:   "10s",
						ETagExpiry:      "10s",
						ETagPurgePeriod: "10s",
					},
					Policy: config.Policy{
						ExpiryMargin:  "10s",
						RefreshPeriod: "10s",
						PurgePeriod:   "10s",
					},
					AccessToken: config.AccessToken{
						Enable: true,
					},
				},
				Server: config.Server{
					HealthCheck: config.HealthCheck{
						Endpoint: "/dummy",
					},
				},
				Proxy: config.Proxy{
					BufferSize: 512,
				},
			}
			return test{
				name: "new success",
				args: args{
					cfg: cfg,
				},
				checkFunc: func(got AuthzProxyDaemon) error {
					if got == nil {
						return errors.New("got is nil")
					}
					if !reflect.DeepEqual(got.(*authzProxyDaemon).cfg, cfg) {
						return errors.New("got.cfg does not equal")
					}
					if got.(*authzProxyDaemon).athenz == nil {
						return errors.New("got.athenz is nil")
					}
					if got.(*authzProxyDaemon).server == nil {
						return errors.New("got.server is nil")
					}
					return nil
				},
			}
		}(),
		{
			name: "new error",
			args: args{
				cfg: config.Config{
					Authorization: config.Authorization{
						PublicKey: config.PublicKey{
							RefreshPeriod: "dummy",
						},
					},
				},
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := New(tt.args.cfg)
			if (err != nil) != tt.wantErr {
				t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if tt.checkFunc != nil {
				if err = tt.checkFunc(got); err != nil {
					t.Errorf("New() error = %v", err)
					return
				}
			}
		})
	}
}

func Test_authzProxyDaemon_Init(t *testing.T) {
	type fields struct {
		athenz service.Authorizationd
	}
	type args struct {
		ctx context.Context
	}
	tests := []struct {
		name       string
		fields     fields
		args       args
		wantErrStr string
	}{
		{
			name: "init sccuess",
			fields: fields{
				athenz: &service.AuthorizerdMock{
					InitFunc: func(ctx context.Context) error {
						return nil
					},
				},
			},
			args:       args{ctx: context.Background()},
			wantErrStr: "",
		},
		{
			name: "init fail",
			fields: fields{
				athenz: &service.AuthorizerdMock{
					InitFunc: func(ctx context.Context) error {
						return errors.New("authorizerd error")
					},
				},
			},
			args:       args{ctx: context.Background()},
			wantErrStr: "authorizerd error",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := &authzProxyDaemon{
				athenz: tt.fields.athenz,
			}
			err := g.Init(tt.args.ctx)
			if (err == nil && tt.wantErrStr != "") || (err != nil && err.Error() != tt.wantErrStr) {
				t.Errorf("authzProxyDaemon.Init() error = %v, wantErr %v", err, tt.wantErrStr)
				return
			}
		})
	}
}

func Test_authzProxyDaemon_Start(t *testing.T) {
	type fields struct {
		cfg    config.Config
		athenz service.Authorizationd
		server service.Server
	}
	type args struct {
		ctx context.Context
	}
	type test struct {
		name      string
		fields    fields
		args      args
		wantErrs  []error
		checkFunc func(<-chan []error, []error) error
	}
	getLessErrorFunc := func(errs []error) func(i, j int) bool {
		return func(i, j int) bool {
			return errs[i].Error() < errs[j].Error()
		}
	}
	tests := []test{
		func() test {
			ctx, cancel := context.WithCancel(context.Background())
			return test{
				name: "Daemon start success",
				fields: fields{
					athenz: &service.AuthorizerdMock{
						StartFunc: func(ctx context.Context) <-chan error {
							ech := make(chan error)
							go func() {
								defer close(ech)
								<-ctx.Done()
								ech <- ctx.Err()
							}()
							return ech
						},
					},
					server: &service.ServerMock{
						ListenAndServeFunc: func(ctx context.Context) <-chan []error {
							ech := make(chan []error)
							go func() {
								defer close(ech)
								<-ctx.Done()
								// simulate graceful shutdown
								time.Sleep(1 * time.Millisecond)

								ech <- []error{ctx.Err()}
							}()
							return ech
						},
					},
				},
				args: args{
					ctx: ctx,
				},
				wantErrs: []error{
					errors.WithMessage(context.Canceled, "authorizerd: 1 times appeared"),
					context.Canceled,
				},
				checkFunc: func(got <-chan []error, wantErrs []error) error {
					cancel()
					mux := &sync.Mutex{}

					gotErrs := make([][]error, 0)
					mux.Lock()
					go func() {
						defer mux.Unlock()
						err, ok := <-got
						if !ok {
							return
						}
						gotErrs = append(gotErrs, err)
					}()
					time.Sleep(time.Second)

					mux.Lock()
					defer mux.Unlock()

					// check only send errors once and the errors are expected ignoring order
					sort.Slice(gotErrs[0], getLessErrorFunc(gotErrs[0]))
					sort.Slice(wantErrs, getLessErrorFunc(wantErrs))
					gotErrsStr := fmt.Sprintf("%v", gotErrs[0])
					wantErrsStr := fmt.Sprintf("%v", wantErrs)
					if len(gotErrs) != 1 || !reflect.DeepEqual(gotErrsStr, wantErrsStr) {
						return errors.Errorf("Invalid err, got: %v, want: %v", gotErrsStr, wantErrsStr)
					}

					return nil
				},
			}
		}(),
		func() test {
			ctx, cancel := context.WithCancel(context.Background())
			dummyErr := errors.New("dummy")
			return test{
				name: "Server fails",
				fields: fields{
					athenz: &service.AuthorizerdMock{
						StartFunc: func(ctx context.Context) <-chan error {
							ech := make(chan error)
							go func() {
								defer close(ech)
								<-ctx.Done()
								ech <- ctx.Err()
							}()
							return ech
						},
					},
					server: &service.ServerMock{
						ListenAndServeFunc: func(ctx context.Context) <-chan []error {
							ech := make(chan []error)
							go func() {
								defer close(ech)
								ech <- []error{errors.WithMessage(dummyErr, "server fails")}
							}()
							return ech
						},
					},
				},
				args: args{
					ctx: ctx,
				},
				wantErrs: []error{
					errors.WithMessage(context.Canceled, "authorizerd: 1 times appeared"),
					errors.WithMessage(dummyErr, "server fails"),
				},
				checkFunc: func(got <-chan []error, wantErrs []error) error {
					mux := &sync.Mutex{}

					gotErrs := make([][]error, 0)
					mux.Lock()
					go func() {
						defer mux.Unlock()
						err, ok := <-got
						if !ok {
							return
						}
						gotErrs = append(gotErrs, err)
					}()
					time.Sleep(time.Second)

					mux.Lock()
					defer mux.Unlock()

					// check only send errors once and the errors are expected ignoring order
					sort.Slice(gotErrs[0], getLessErrorFunc(gotErrs[0]))
					sort.Slice(wantErrs, getLessErrorFunc(wantErrs))
					gotErrsStr := fmt.Sprintf("%v", gotErrs[0])
					wantErrsStr := fmt.Sprintf("%v", wantErrs)
					if len(gotErrs) != 1 || !reflect.DeepEqual(gotErrsStr, wantErrsStr) {
						return errors.Errorf("Invalid err, got: %v, want: %v", gotErrsStr, wantErrsStr)
					}

					cancel()
					return nil
				},
			}
		}(),
		func() test {
			ctx, cancel := context.WithCancel(context.Background())
			dummyErr := errors.New("dummy")
			return test{
				name: "Authorizer daemon fails, multiple times",
				fields: fields{
					athenz: &service.AuthorizerdMock{
						StartFunc: func(ctx context.Context) <-chan error {
							ech := make(chan error)
							go func() {
								defer close(ech)

								// simulate fails
								ech <- errors.WithMessage(dummyErr, "authorizer daemon fails")
								ech <- errors.WithMessage(dummyErr, "authorizer daemon fails")
								ech <- errors.WithMessage(dummyErr, "authorizer daemon fails")

								// return only if context cancel
								<-ctx.Done()
								ech <- ctx.Err()
							}()
							return ech
						},
					},
					server: &service.ServerMock{
						ListenAndServeFunc: func(ctx context.Context) <-chan []error {
							ech := make(chan []error)
							go func() {
								defer close(ech)
								<-ctx.Done()
								// simulate graceful shutdown
								time.Sleep(1 * time.Millisecond)

								ech <- []error{ctx.Err()}
							}()
							return ech
						},
					},
				},
				args: args{
					ctx: ctx,
				},
				wantErrs: []error{
					errors.WithMessage(errors.Cause(errors.WithMessage(dummyErr, "authorizer daemon fails")), "authorizerd: 3 times appeared"),
					errors.WithMessage(context.Canceled, "authorizerd: 1 times appeared"),
					context.Canceled,
				},
				checkFunc: func(got <-chan []error, wantErrs []error) error {
					cancel()
					mux := &sync.Mutex{}

					gotErrs := make([][]error, 0)
					mux.Lock()
					go func() {
						defer mux.Unlock()
						err, ok := <-got
						if !ok {
							return
						}
						gotErrs = append(gotErrs, err)
					}()
					time.Sleep(time.Second)

					mux.Lock()
					defer mux.Unlock()

					// check only send errors once and the errors are expected ignoring order
					sort.Slice(gotErrs[0], getLessErrorFunc(gotErrs[0]))
					sort.Slice(wantErrs, getLessErrorFunc(wantErrs))
					gotErrsStr := fmt.Sprintf("%v", gotErrs[0])
					wantErrsStr := fmt.Sprintf("%v", wantErrs)
					if len(gotErrs) != 1 || !reflect.DeepEqual(gotErrsStr, wantErrsStr) {
						return errors.Errorf("Invalid err, got: %v, want: %v", gotErrsStr, wantErrsStr)
					}

					return nil
				},
			}
		}(),
		func() test {
			ctx, cancel := context.WithCancel(context.Background())
			return test{
				name: "Daemon start end successfully, server shutdown without error",
				fields: fields{
					athenz: &service.AuthorizerdMock{
						StartFunc: func(ctx context.Context) <-chan error {
							ech := make(chan error)
							go func() {
								defer close(ech)
								// return only if context cancel
								<-ctx.Done()
								ech <- ctx.Err()
							}()
							return ech
						},
					},
					server: &service.ServerMock{
						ListenAndServeFunc: func(ctx context.Context) <-chan []error {
							ech := make(chan []error)
							go func() {
								defer close(ech)
								<-ctx.Done()
								// simulate graceful shutdown
								time.Sleep(1 * time.Millisecond)

								ech <- []error{}
							}()
							return ech
						},
					},
				},
				args: args{
					ctx: ctx,
				},
				wantErrs: []error{
					errors.WithMessage(context.Canceled, "authorizerd: 1 times appeared"),
					errors.New(""),
				},
				checkFunc: func(got <-chan []error, wantErrs []error) error {
					cancel()
					mux := &sync.Mutex{}

					gotErrs := make([][]error, 0)
					mux.Lock()
					go func() {
						defer mux.Unlock()
						err, ok := <-got
						if !ok {
							return
						}
						gotErrs = append(gotErrs, err)
					}()
					time.Sleep(time.Second)

					mux.Lock()
					defer mux.Unlock()

					// check only send errors once and the errors are expected ignoring order
					sort.Slice(gotErrs[0], getLessErrorFunc(gotErrs[0]))
					sort.Slice(wantErrs, getLessErrorFunc(wantErrs))
					gotErrsStr := fmt.Sprintf("%v", gotErrs[0])
					wantErrsStr := fmt.Sprintf("%v", wantErrs)
					if len(gotErrs) != 1 || !reflect.DeepEqual(gotErrsStr, wantErrsStr) {
						return errors.Errorf("Invalid err, got: %v, want: %v", gotErrsStr, wantErrsStr)
					}

					return nil
				},
			}
		}(),
		func() test {
			ctx, cancel := context.WithCancel(context.Background())
			dummyErr := errors.New("dummy")
			return test{
				name: "Daemon start end successfully, server shutdown >1 errors",
				fields: fields{
					athenz: &service.AuthorizerdMock{
						StartFunc: func(ctx context.Context) <-chan error {
							ech := make(chan error)
							go func() {
								defer close(ech)
								// return only if context cancel
								<-ctx.Done()
								ech <- ctx.Err()
							}()
							return ech
						},
					},
					server: &service.ServerMock{
						ListenAndServeFunc: func(ctx context.Context) <-chan []error {
							ech := make(chan []error)
							go func() {
								defer close(ech)
								<-ctx.Done()
								// simulate graceful shutdown
								time.Sleep(1 * time.Millisecond)

								ech <- []error{dummyErr, ctx.Err()}
							}()
							return ech
						},
					},
				},
				args: args{
					ctx: ctx,
				},
				wantErrs: []error{
					errors.WithMessage(context.Canceled, "authorizerd: 1 times appeared"),
					errors.Wrap(dummyErr, context.Canceled.Error()),
				},
				checkFunc: func(got <-chan []error, wantErrs []error) error {
					cancel()
					mux := &sync.Mutex{}

					gotErrs := make([][]error, 0)
					mux.Lock()
					go func() {
						defer mux.Unlock()
						err, ok := <-got
						if !ok {
							return
						}
						gotErrs = append(gotErrs, err)
					}()
					time.Sleep(time.Second)

					mux.Lock()
					defer mux.Unlock()

					// check only send errors once and the errors are expected ignoring order
					sort.Slice(gotErrs[0], getLessErrorFunc(gotErrs[0]))
					sort.Slice(wantErrs, getLessErrorFunc(wantErrs))
					gotErrsStr := fmt.Sprintf("%v", gotErrs[0])
					wantErrsStr := fmt.Sprintf("%v", wantErrs)
					if len(gotErrs) != 1 || !reflect.DeepEqual(gotErrsStr, wantErrsStr) {
						return errors.Errorf("Invalid err, got: %v, want: %v", gotErrsStr, wantErrsStr)
					}

					return nil
				},
			}
		}(),
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := &authzProxyDaemon{
				cfg:    tt.fields.cfg,
				athenz: tt.fields.athenz,
				server: tt.fields.server,
			}
			got := g.Start(tt.args.ctx)
			if err := tt.checkFunc(got, tt.wantErrs); err != nil {
				t.Errorf("authzProxyDaemon.Start() error: %v", err)
			}
		})
	}
}

// this requires integration test
func Test_newAuthzD(t *testing.T) {
	type args struct {
		cfg config.Config
	}
	tests := []struct {
		name       string
		args       args
		want       bool
		wantErrStr string
	}{
		{
			name: "test new Authorization fail, Athenz.Timeout",
			args: args{
				cfg: config.Config{
					Athenz: config.Athenz{
						Timeout: "invalid",
					},
				},
			},
			want:       false,
			wantErrStr: "newAuthzD(): Athenz.Timeout: time: invalid duration invalid",
		},
		{
			name: "test new Authorization fail, Athenz.CAPath",
			args: args{
				cfg: config.Config{
					Athenz: config.Athenz{
						CAPath: "../test/data/non_existing_ca.pem",
					},
				},
			},
			want:       false,
			wantErrStr: "newAuthzD(): Athenz.CAPath: x509.SystemCertPool(): open ../test/data/non_existing_ca.pem: no such file or directory",
		},
		{
			name: "test new Authorization fail, authorizerd.New",
			args: args{
				cfg: config.Config{
					Authorization: config.Authorization{
						PublicKey: config.PublicKey{
							RefreshPeriod: "invalid_period",
						},
					},
				},
			},
			want:       false,
			wantErrStr: "error create pubkeyd: invalid refresh period: time: invalid duration invalid_period",
		},
		{
			name: "test new Authorization fail, invalid MappingRules",
			args: args{
				cfg: config.Config{
					Authorization: config.Authorization{
						Policy: config.Policy{
							MappingRules: map[string][]authorizerd.Rule{
								"": {
									authorizerd.Rule{
										Method:   "get",
										Path:     "/path",
										Action:   "action",
										Resource: "resource",
									},
								},
							},
						},
						RoleToken: config.RoleToken{
							Enable:         true,
							RoleAuthHeader: "Athenz-Role-Auth",
						},
					},
				},
			},
			want:       false,
			wantErrStr: "newAuthzD(): Failed to create a MappingRules: domain is empty",
		},
		{
			name: "test success new Authorization",
			args: args{
				cfg: config.Config{
					Athenz: config.Athenz{
						URL:     "athenz.io",
						Timeout: "30s",
						CAPath:  "../test/data/dummyCa.pem",
					},
					Authorization: config.Authorization{
						AthenzDomains: []string{"dummyDom1", "dummyDom2"},
						PublicKey: config.PublicKey{
							SysAuthDomain:   "dummy.sys.auth",
							RefreshPeriod:   "10s",
							ETagExpiry:      "10s",
							ETagPurgePeriod: "10s",
						},
						Policy: config.Policy{
							ExpiryMargin:  "10s",
							RefreshPeriod: "10s",
							PurgePeriod:   "10s",
						},
						RoleToken: config.RoleToken{
							Enable:         true,
							RoleAuthHeader: "Athenz-Role-Auth",
						},
					},
				},
			},
			want: true,
		},
		{
			name: "test success access token enable, verify thumbprint disable",
			args: args{
				cfg: config.Config{
					Authorization: config.Authorization{
						PublicKey: config.PublicKey{
							SysAuthDomain:   "10s",
							ETagExpiry:      "10s",
							ETagPurgePeriod: "10s",
						},
						Policy: config.Policy{
							ExpiryMargin:  "10s",
							RefreshPeriod: "10s",
							PurgePeriod:   "10s",
						},
						AccessToken: config.AccessToken{
							Enable:               true,
							VerifyCertThumbprint: false,
							CertBackdateDuration: "10s",
							CertOffsetDuration:   "10s",
						},
					},
					Athenz: config.Athenz{
						URL: "dummy-athenz-url",
					},
				},
			},
			want: true,
		},
		{
			name: "test success access token enable, verify client_id disable",
			args: args{
				cfg: config.Config{
					Authorization: config.Authorization{
						PublicKey: config.PublicKey{
							SysAuthDomain:   "10s",
							ETagExpiry:      "10s",
							ETagPurgePeriod: "10s",
						},
						Policy: config.Policy{
							ExpiryMargin:  "10s",
							RefreshPeriod: "10s",
							PurgePeriod:   "10s",
						},
						AccessToken: config.AccessToken{
							Enable:         true,
							VerifyClientID: false,
							AuthorizedClientIDs: map[string][]string{
								"dummyCN1": {
									"dummyClientID1",
									"dummyClientID2",
								},
							},
						},
					},
					Athenz: config.Athenz{
						URL: "dummy-athenz-url",
					},
				},
			},
			want: true,
		},
		{
			name: "test success policy disable",
			args: args{
				cfg: config.Config{
					Authorization: config.Authorization{
						PublicKey: config.PublicKey{
							SysAuthDomain:   "10s",
							ETagExpiry:      "10s",
							ETagPurgePeriod: "10s",
						},
						Policy: config.Policy{
							Disable: true,
						},
						RoleToken: config.RoleToken{
							Enable:         true,
							RoleAuthHeader: "Athenz-Role-Auth",
						},
					},
				},
			},
			want: true,
		},
		{
			name: "test success mappingRules set",
			args: args{
				cfg: config.Config{
					Authorization: config.Authorization{
						Policy: config.Policy{
							MappingRules: map[string][]authorizerd.Rule{
								"domain": {
									authorizerd.Rule{
										Method:   "get",
										Path:     "/path",
										Action:   "action",
										Resource: "resource",
									},
								},
							},
						},
						RoleToken: config.RoleToken{
							Enable:         true,
							RoleAuthHeader: "Athenz-Role-Auth",
						},
					},
				},
			},
			want: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := newAuthzD(tt.args.cfg)

			if (err == nil && tt.wantErrStr != "") || (err != nil && err.Error() != tt.wantErrStr) {
				t.Errorf("newAuthzD() error = %v, wantErr %v", err, tt.wantErrStr)
				return
			}
			if (got != nil) != tt.want {
				t.Errorf("newAuthzD()= %v, want= %v", got, tt.want)
				return
			}
		})
	}
}


<|start_filename|>usecase/authz_proxyd.go<|end_filename|>
/*
Copyright (C)  2018 Yahoo Japan Corporation Athenz team.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package usecase

import (
	"context"
	"crypto/tls"
	"net/http"
	"time"

	"github.com/kpango/glg"
	"github.com/pkg/errors"
	"golang.org/x/sync/errgroup"

	"github.com/yahoojapan/authorization-proxy/v4/config"
	"github.com/yahoojapan/authorization-proxy/v4/handler"
	"github.com/yahoojapan/authorization-proxy/v4/infra"
	"github.com/yahoojapan/authorization-proxy/v4/router"
	"github.com/yahoojapan/authorization-proxy/v4/service"

	authorizerd "github.com/yahoojapan/athenz-authorizer/v5"
)

// AuthzProxyDaemon represents Authorization Proxy daemon behavior.
type AuthzProxyDaemon interface {
	Init(ctx context.Context) error
	Start(ctx context.Context) <-chan []error
}

type authzProxyDaemon struct {
	cfg    config.Config
	athenz service.Authorizationd
	server service.Server
}

// New returns a Authorization Proxy daemon, or error occurred.
// The daemon contains a token service authentication and authorization server.
// This function will also initialize the mapping rules for the authentication and authorization check.
func New(cfg config.Config) (AuthzProxyDaemon, error) {
	athenz, err := newAuthzD(cfg)
	if err != nil {
		return nil, errors.Wrap(err, "cannot newAuthzD(cfg)")
	}

	debugMux := router.NewDebugRouter(cfg.Server, athenz)

	return &authzProxyDaemon{
		cfg:    cfg,
		athenz: athenz,
		server: service.NewServer(
			service.WithServerConfig(cfg.Server),
			service.WithServerHandler(handler.New(cfg.Proxy, infra.NewBuffer(cfg.Proxy.BufferSize), athenz)),
			service.WithDebugHandler(debugMux)),
	}, nil
}

// Init initializes child daemons synchronously.
func (g *authzProxyDaemon) Init(ctx context.Context) error {
	return g.athenz.Init(ctx)
}

// Start returns a channel of error slice . This error channel reports the errors inside the Authorizer daemon and the Authorization Proxy server.
func (g *authzProxyDaemon) Start(ctx context.Context) <-chan []error {
	ech := make(chan []error)
	var emap map[string]uint64 // used for returning value from child goroutine, should not r/w in this goroutine
	var eg *errgroup.Group
	eg, ctx = errgroup.WithContext(ctx)

	// handle authorizer daemon error, return on channel close
	eg.Go(func() error {
		// closure, only this goroutine should write on the variable and the map
		emap = make(map[string]uint64, 1)
		pch := g.athenz.Start(ctx)

		for err := range pch {
			if err != nil {
				glg.Errorf("pch: %v", err)
				// count errors by cause
				cause := errors.Cause(err).Error()
				_, ok := emap[cause]
				if !ok {
					emap[cause] = 1
				} else {
					emap[cause]++
				}
			}
		}

		return nil
	})

	// handle proxy server error, return on server shutdown done
	eg.Go(func() error {
		errs := <-g.server.ListenAndServe(ctx)
		glg.Errorf("sch: %v", errs)

		if len(errs) == 0 {
			// cannot be nil so that the context can cancel
			return errors.New("")
		}
		var baseErr error
		for i, err := range errs {
			if i == 0 {
				baseErr = err
			} else {
				baseErr = errors.Wrap(baseErr, err.Error())
			}
		}
		return baseErr
	})

	// wait for shutdown, and summarize errors
	go func() {
		defer close(ech)

		<-ctx.Done()
		err := eg.Wait()

		/*
			Read on emap is safe here, if and only if:
			1. emap is not used in the parenet goroutine
			2. the writer goroutine returns only if all errors are written, i.e. pch is closed
			3. this goroutine should wait for the writer goroutine to end, i.e. eg.Wait()
		*/
		// aggregate all errors as array
		perrs := make([]error, 0, len(emap))
		for errMsg, count := range emap {
			perrs = append(perrs, errors.WithMessagef(errors.New(errMsg), "authorizerd: %d times appeared", count))
		}

		// proxy server go func, should always return not nil error
		ech <- append(perrs, err)
	}()

	return ech
}

func newAuthzD(cfg config.Config) (service.Authorizationd, error) {
	client := http.DefaultClient
	if cfg.Athenz.Timeout != "" {
		t, err := time.ParseDuration(cfg.Athenz.Timeout)
		if err != nil {
			return nil, errors.Wrap(err, "newAuthzD(): Athenz.Timeout")
		}
		client.Timeout = t
	}
	if cfg.Athenz.CAPath != "" {
		cp, err := service.NewX509CertPool(cfg.Athenz.CAPath)
		if err != nil {
			return nil, errors.Wrap(err, "newAuthzD(): Athenz.CAPath")
		}
		client.Transport = &http.Transport{
			TLSClientConfig: &tls.Config{
				RootCAs: cp,
			},
		}
	}

	authzCfg := cfg.Authorization
	sharedOpts := []authorizerd.Option{
		authorizerd.WithAthenzURL(cfg.Athenz.URL),
		authorizerd.WithHTTPClient(client),
	}
	pubkeyOpts := []authorizerd.Option{
		authorizerd.WithPubkeySysAuthDomain(authzCfg.PublicKey.SysAuthDomain),
		authorizerd.WithPubkeyRefreshPeriod(authzCfg.PublicKey.RefreshPeriod),
		authorizerd.WithPubkeyETagExpiry(authzCfg.PublicKey.ETagExpiry),
		authorizerd.WithPubkeyETagPurgePeriod(authzCfg.PublicKey.ETagPurgePeriod),
		authorizerd.WithPubkeyRetryDelay(authzCfg.PublicKey.RetryDelay),
	}
	var policyOpts []authorizerd.Option
	if authzCfg.Policy.Disable {
		policyOpts = []authorizerd.Option{
			authorizerd.WithDisablePolicyd(),
		}
	} else {
		policyOpts = []authorizerd.Option{
			authorizerd.WithAthenzDomains(authzCfg.AthenzDomains...),
			authorizerd.WithPolicyExpiryMargin(authzCfg.Policy.ExpiryMargin),
			authorizerd.WithPolicyRefreshPeriod(authzCfg.Policy.RefreshPeriod),
			authorizerd.WithPolicyPurgePeriod(authzCfg.Policy.PurgePeriod),
			authorizerd.WithPolicyRetryDelay(authzCfg.Policy.RetryDelay),
			authorizerd.WithPolicyRetryAttempts(authzCfg.Policy.RetryAttempts),
		}

		if rules := authzCfg.Policy.MappingRules; rules != nil {
			translator, err := authorizerd.NewMappingRules(rules)
			if err != nil {
				return nil, errors.Wrap(err, "newAuthzD(): Failed to create a MappingRules")
			}
			policyOpts = append(policyOpts, authorizerd.WithTranslator(translator))
		}
	}
	var rtOpts []authorizerd.Option
	if authzCfg.RoleToken.Enable {
		rtOpts = []authorizerd.Option{
			authorizerd.WithEnableRoleToken(),
			authorizerd.WithRoleAuthHeader(cfg.Authorization.RoleToken.RoleAuthHeader),
		}
	} else {
		rtOpts = []authorizerd.Option{
			authorizerd.WithDisableRoleToken(),
		}
	}
	rcOpts := []authorizerd.Option{
		authorizerd.WithDisableRoleCert(),
	}

	var atOpts []authorizerd.Option
	var jwkOpts []authorizerd.Option
	if authzCfg.AccessToken.Enable {
		atOpts = []authorizerd.Option{
			authorizerd.WithAccessTokenParam(
				authorizerd.NewAccessTokenParam(
					authzCfg.AccessToken.Enable,
					authzCfg.AccessToken.VerifyCertThumbprint,
					authzCfg.AccessToken.CertBackdateDuration,
					authzCfg.AccessToken.CertOffsetDuration,
					authzCfg.AccessToken.VerifyClientID,
					authzCfg.AccessToken.AuthorizedClientIDs,
				),
			),
		}
		jwkOpts = []authorizerd.Option{
			authorizerd.WithEnableJwkd(),
			// use value in config.go in later version
			authorizerd.WithJwkRefreshPeriod(authzCfg.JWK.RefreshPeriod),
			authorizerd.WithJwkRetryDelay(authzCfg.JWK.RetryDelay),
			authorizerd.WithJwkURLs(authzCfg.JWK.URLs),
		}
	} else {
		atOpts = []authorizerd.Option{
			authorizerd.WithAccessTokenParam(
				authorizerd.NewAccessTokenParam(
					false,
					false,
					"0h",
					"0h",
					false,
					nil,
				),
			),
		}
		jwkOpts = []authorizerd.Option{
			authorizerd.WithDisableJwkd(),
		}
	}

	authzOptss := [][]authorizerd.Option{
		sharedOpts,
		pubkeyOpts,
		policyOpts,
		rtOpts,
		rcOpts,
		atOpts,
		jwkOpts,
	}
	var authzOptsLen int
	for _, opts := range authzOptss {
		authzOptsLen += len(opts)
	}
	authzOpts := make([]authorizerd.Option, 0, authzOptsLen)
	for _, opts := range authzOptss {
		authzOpts = append(authzOpts, opts...)
	}
	return authorizerd.New(authzOpts...)
}


<|start_filename|>handler/handler_test.go<|end_filename|>
package handler

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"reflect"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/pkg/errors"
	authorizerd "github.com/yahoojapan/athenz-authorizer/v5"
	"github.com/yahoojapan/authorization-proxy/v4/config"
	"github.com/yahoojapan/authorization-proxy/v4/infra"
	"github.com/yahoojapan/authorization-proxy/v4/service"
)

func TestNew(t *testing.T) {
	type args struct {
		cfg  config.Proxy
		bp   httputil.BufferPool
		prov service.Authorizationd
	}
	type test struct {
		name      string
		args      args
		checkFunc func(http.Handler) error
	}
	pm := PrincipalMock{
		NameFunc: func() string {
			return "rt_principal"
		},
		RolesFunc: func() []string {
			return []string{"rt_role1", "rt_role2", "rt_role3"}
		},
		DomainFunc: func() string {
			return "rt_domain"
		},
		IssueTimeFunc: func() int64 {
			return 1595908257
		},
		ExpiryTimeFunc: func() int64 {
			return 1595908265
		},
	}
	oatm := OAuthAccessTokenMock{
		PrincipalMock: PrincipalMock{
			NameFunc: func() string {
				return "at_principal"
			},
			RolesFunc: func() []string {
				return []string{"at_role1", "at_role2", "at_role3"}
			},
			DomainFunc: func() string {
				return "at_domain"
			},
			IssueTimeFunc: func() int64 {
				return 1595908267
			},
			ExpiryTimeFunc: func() int64 {
				return 1595908275
			},
		},
		ClientIDFunc: func() string {
			return "client_id"
		},
	}
	tests := []test{
		func() test {
			handler := http.HandlerFunc(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				header := map[string]string{
					"X-Athenz-Principal":  r.Header.Get("X-Athenz-Principal"),
					"X-Athenz-Role":       r.Header.Get("X-Athenz-Role"),
					"X-Athenz-Domain":     r.Header.Get("X-Athenz-Domain"),
					"X-Athenz-Issued-At":  r.Header.Get("X-Athenz-Issued-At"),
					"X-Athenz-Expires-At": r.Header.Get("X-Athenz-Expires-At"),
				}

				body, err1 := json.Marshal(header)
				if err1 != nil {
					w.WriteHeader(http.StatusNotImplemented)
					return
				}

				_, err2 := w.Write(body)
				if err2 != nil {
					w.WriteHeader(http.StatusNotImplemented)
					return
				}
				w.WriteHeader(http.StatusOK)
			}))
			srv := httptest.NewServer(handler)

			return test{
				name: "Check that the request with role token headers is redirected",
				args: args{
					cfg: config.Proxy{
						Host: strings.Split(strings.Replace(srv.URL, "http://", "", 1), ":")[0],
						Port: func() uint16 {
							a, _ := strconv.ParseInt(strings.Split(srv.URL, ":")[2], 0, 64)
							return uint16(a)
						}(),
					},
					bp: infra.NewBuffer(64),
					prov: &service.AuthorizerdMock{
						VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
							return &pm, nil
						},
					},
				},
				checkFunc: func(h http.Handler) error {
					rw := httptest.NewRecorder()
					r := httptest.NewRequest("GET", "http://dummy.com", nil)
					h.ServeHTTP(rw, r)
					if rw.Code != http.StatusOK {
						return errors.Errorf("unexpected status code, got: %v, want: %v", rw.Code, http.StatusOK)
					}
					header := make(map[string]string)
					json.Unmarshal(rw.Body.Bytes(), &header)

					f := func(key string, want string) error {
						if header[key] != want {
							return errors.Errorf("unexpected header %v, got: %v, want %v", key, header[key], want)
						}
						return nil
					}

					var key, want string
					key, want = "X-Athenz-Principal", "rt_principal"
					if err := f(key, want); err != nil {
						return err
					}
					key, want = "X-Athenz-Role", "rt_role1,rt_role2,rt_role3"
					if err := f(key, want); err != nil {
						return err
					}
					key, want = "X-Athenz-Domain", "rt_domain"
					if err := f(key, want); err != nil {
						return err
					}
					key, want = "X-Athenz-Issued-At", "1595908257"
					if err := f(key, want); err != nil {
						return err
					}
					key, want = "X-Athenz-Expires-At", "1595908265"
					if err := f(key, want); err != nil {
						return err
					}

					key, want = "X-Athenz-Client-ID", "nil"
					if _, ok := header[key]; ok {
						return errors.Errorf("unexpected header %v, got: %v, want %v", key, header[key], want)
					}

					return nil
				},
			}
		}(),
		func() test {
			handler := http.HandlerFunc(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				header := map[string]string{
					"X-Athenz-Principal":  r.Header.Get("X-Athenz-Principal"),
					"X-Athenz-Role":       r.Header.Get("X-Athenz-Role"),
					"X-Athenz-Domain":     r.Header.Get("X-Athenz-Domain"),
					"X-Athenz-Issued-At":  r.Header.Get("X-Athenz-Issued-At"),
					"X-Athenz-Expires-At": r.Header.Get("X-Athenz-Expires-At"),
					"X-Athenz-Client-ID":  r.Header.Get("X-Athenz-Client-ID"),
				}

				body, err1 := json.Marshal(header)
				if err1 != nil {
					w.WriteHeader(http.StatusNotImplemented)
					return
				}

				_, err2 := w.Write(body)
				if err2 != nil {
					w.WriteHeader(http.StatusNotImplemented)
					return
				}
				w.WriteHeader(http.StatusOK)
			}))
			srv := httptest.NewServer(handler)

			return test{
				name: "Check that the request with access token headers is redirected",
				args: args{
					cfg: config.Proxy{
						Host: strings.Split(strings.Replace(srv.URL, "http://", "", 1), ":")[0],
						Port: func() uint16 {
							a, _ := strconv.ParseInt(strings.Split(srv.URL, ":")[2], 0, 64)
							return uint16(a)
						}(),
					},
					bp: infra.NewBuffer(64),
					prov: &service.AuthorizerdMock{
						VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
							return &oatm, nil
						},
					},
				},
				checkFunc: func(h http.Handler) error {
					rw := httptest.NewRecorder()
					r := httptest.NewRequest("GET", "http://dummy.com", nil)
					h.ServeHTTP(rw, r)
					if rw.Code != http.StatusOK {
						return errors.Errorf("unexpected status code, got: %v, want: %v", rw.Code, http.StatusOK)
					}
					header := make(map[string]string)
					json.Unmarshal(rw.Body.Bytes(), &header)

					f := func(key string, want string) error {
						if header[key] != want {
							return errors.Errorf("unexpected header %v, got: %v, want %v", key, header[key], want)
						}
						return nil
					}

					var key, want string
					key, want = "X-Athenz-Principal", "at_principal"
					if err := f(key, want); err != nil {
						return err
					}
					key, want = "X-Athenz-Role", "at_role1,at_role2,at_role3"
					if err := f(key, want); err != nil {
						return err
					}
					key, want = "X-Athenz-Domain", "at_domain"
					if err := f(key, want); err != nil {
						return err
					}
					key, want = "X-Athenz-Issued-At", "1595908267"
					if err := f(key, want); err != nil {
						return err
					}
					key, want = "X-Athenz-Expires-At", "1595908275"
					if err := f(key, want); err != nil {
						return err
					}
					key, want = "X-Athenz-Client-ID", "client_id"
					if err := f(key, want); err != nil {
						return err
					}

					return nil
				},
			}
		}(),
		func() test {
			handler := http.HandlerFunc(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				_, err := w.Write([]byte("dummyContent"))
				if err != nil {
					w.WriteHeader(http.StatusNotImplemented)
					return
				}
				w.WriteHeader(http.StatusOK)
			}))
			srv := httptest.NewServer(handler)

			return test{
				name: "check request unauthorized",
				args: args{
					cfg: config.Proxy{
						Host: strings.Split(strings.Replace(srv.URL, "http://", "", 1), ":")[0],
						Port: func() uint16 {
							a, _ := strconv.ParseInt(strings.Split(srv.URL, ":")[2], 0, 64)
							return uint16(a)
						}(),
					},
					bp: infra.NewBuffer(64),
					prov: &service.AuthorizerdMock{
						VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
							return nil, errors.New("deny")
						},
					},
				},
				checkFunc: func(h http.Handler) error {
					rw := httptest.NewRecorder()
					r := httptest.NewRequest("GET", "http://dummy.com", nil)
					h.ServeHTTP(rw, r)
					if rw.Code != http.StatusUnauthorized {
						return errors.Errorf("unexpected status code, got: %v, want: %v", rw.Code, http.StatusUnauthorized)
					}
					return nil
				},
			}
		}(),
		func() test {
			handler := http.HandlerFunc(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				_, err := w.Write([]byte("dummyContent"))
				if err != nil {
					w.WriteHeader(http.StatusNotImplemented)
					return
				}
				w.WriteHeader(http.StatusOK)
			}))
			srv := httptest.NewServer(handler)

			return test{
				name: "check request can redirect to configured scheme",
				args: args{
					cfg: config.Proxy{
						Host: strings.Split(strings.Replace(srv.URL, "http://", "", 1), ":")[0],
						Port: func() uint16 {
							a, _ := strconv.ParseInt(strings.Split(srv.URL, ":")[2], 0, 64)
							return uint16(a)
						}(),
						Scheme: "http",
					},
					bp: infra.NewBuffer(64),
					prov: &service.AuthorizerdMock{
						VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
							return &pm, nil
						},
					},
				},
				checkFunc: func(h http.Handler) error {
					rw := httptest.NewRecorder()
					r := httptest.NewRequest("GET", "https://dummy.com", nil)
					h.ServeHTTP(rw, r)
					if rw.Code != http.StatusOK {
						return errors.Errorf("unexpected status code, got: %v, want: %v", rw.Code, http.StatusOK)
					}
					if fmt.Sprintf("%v", rw.Body) != "dummyContent" {
						return errors.Errorf("unexpected http response, got: %v, want %v", rw.Body, "dummyContent")
					}
					return nil
				},
			}
		}(),
		func() test {
			return test{
				name: "check request destination cannot reach",
				args: args{
					cfg: config.Proxy{
						Host: "dummyHost",
						Port: 9999,
					},
					bp: infra.NewBuffer(64),
					prov: &service.AuthorizerdMock{
						VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
							return &pm, nil
						},
					},
				},
				checkFunc: func(h http.Handler) error {
					rw := httptest.NewRecorder()
					r := httptest.NewRequest("GET", "http://dummy.com", nil)
					h.ServeHTTP(rw, r)
					if rw.Code != http.StatusBadGateway {
						return errors.Errorf("unexpected status code, got: %v, want: %v", rw.Code, http.StatusBadGateway)
					}
					return nil
				},
			}
		}(),
		func() test {
			handler := http.HandlerFunc(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				_, err := w.Write([]byte("dummyContent"))
				if err != nil {
					w.WriteHeader(http.StatusNotImplemented)
					return
				}
				w.WriteHeader(http.StatusOK)
			}))
			srv := httptest.NewServer(handler)

			return test{
				name: "check context done",
				args: args{
					cfg: config.Proxy{
						Host: strings.Split(strings.Replace(srv.URL, "http://", "", 1), ":")[0],
						Port: func() uint16 {
							a, _ := strconv.ParseInt(strings.Split(srv.URL, ":")[2], 0, 64)
							return uint16(a)
						}(),
					},
					bp: infra.NewBuffer(64),
					prov: &service.AuthorizerdMock{
						VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
							return nil, context.Canceled
						},
					},
				},
				checkFunc: func(h http.Handler) error {
					rw := httptest.NewRecorder()
					r := httptest.NewRequest("GET", "http://dummy.com", nil)
					h.ServeHTTP(rw, r)
					if rw.Code != http.StatusRequestTimeout {
						return errors.Errorf("unexpected status code, got: %v, want: %v", rw.Code, http.StatusOK)
					}
					return nil
				},
			}
		}(),
		func() test {
			handler := http.HandlerFunc(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(http.StatusOK)
			}))
			srv := httptest.NewServer(handler)

			return test{
				name: "check error on new request",
				args: args{
					cfg: config.Proxy{
						Host: strings.Split(strings.Replace(srv.URL, "http://", "", 1), ":")[0],
						Port: func() uint16 {
							a, _ := strconv.ParseInt(strings.Split(srv.URL, ":")[2], 0, 64)
							return uint16(a)
						}(),
					},
					bp: infra.NewBuffer(64),
					prov: &service.AuthorizerdMock{
						VerifyFunc: func(r *http.Request, act, res string) (authorizerd.Principal, error) {
							return &pm, nil
						},
					},
				},
				checkFunc: func(h http.Handler) error {
					rw := httptest.NewRecorder()
					r := httptest.NewRequest("GET", "http://dummy.com", nil)
					r.Method = "invalid_method()"
					h.ServeHTTP(rw, r)
					if rw.Code != http.StatusBadGateway {
						return errors.Errorf("unexpected status code, got: %v, want: %v", rw.Code, http.StatusBadGateway)
					}
					return nil
				},
			}
		}(),
		{
			name: "check custom transport is used",
			args: args{
				cfg: config.Proxy{
					Transport: config.Transport{
						MaxIdleConnsPerHost: 442,
					},
				},
			},
			checkFunc: func(h http.Handler) error {
				got := h.(*httputil.ReverseProxy).Transport.(*transport).RoundTripper.(*http.Transport).MaxIdleConnsPerHost
				want := 442
				if got != want {
					return errors.Errorf("unexpected MaxConnsPerHost in custom transport, got: %v, want: %v", got, want)
				}
				return nil
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := New(tt.args.cfg, tt.args.bp, tt.args.prov)
			if err := tt.checkFunc(got); err != nil {
				t.Errorf("New() error: %v", err)
			}
		})
	}
}

func Test_transportFromCfg(t *testing.T) {
	type args struct {
		cfg config.Transport
	}
	tests := []struct {
		name string
		args args
		want *http.Transport
	}{
		{
			name: "transport from default",
			args: args{
				cfg: config.Transport{},
			},
			want: &http.Transport{},
		},
		{
			name: "transport from custom values",
			args: args{
				cfg: config.Transport{
					TLSHandshakeTimeout:    468 * time.Second,
					DisableKeepAlives:      true,
					DisableCompression:     true,
					MaxIdleConns:           471,
					MaxIdleConnsPerHost:    472,
					MaxConnsPerHost:        473,
					IdleConnTimeout:        474 * time.Second,
					ResponseHeaderTimeout:  475 * time.Second,
					ExpectContinueTimeout:  476 * time.Second,
					MaxResponseHeaderBytes: 477,
					WriteBufferSize:        478,
					ReadBufferSize:         479,
					ForceAttemptHTTP2:      true,
				},
			},
			want: &http.Transport{
				TLSHandshakeTimeout:    468 * time.Second,
				DisableKeepAlives:      true,
				DisableCompression:     true,
				MaxIdleConns:           471,
				MaxIdleConnsPerHost:    472,
				MaxConnsPerHost:        473,
				IdleConnTimeout:        474 * time.Second,
				ResponseHeaderTimeout:  475 * time.Second,
				ExpectContinueTimeout:  476 * time.Second,
				MaxResponseHeaderBytes: 477,
				WriteBufferSize:        478,
				ReadBufferSize:         479,
				ForceAttemptHTTP2:      true,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := transportFromCfg(tt.args.cfg); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("transportFromCfg() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_handleError(t *testing.T) {
	type args struct {
		rw  http.ResponseWriter
		r   *http.Request
		err error
	}
	type test struct {
		name      string
		args      args
		checkFunc func() error
	}
	tests := []test{
		func() test {
			rw := httptest.NewRecorder()
			return test{
				name: "handleError status return bad gateway",
				args: args{
					rw:  rw,
					r:   httptest.NewRequest("GET", "http://127.0.0.1", bytes.NewBufferString("test")),
					err: errors.New("other error"),
				},
				checkFunc: func() error {
					if rw.Code != http.StatusBadGateway {
						return errors.Errorf("invalid status code: %v", rw.Code)
					}
					return nil
				},
			}
		}(),
		func() test {
			rw := httptest.NewRecorder()
			return test{
				name: "handleError status return verify role token",
				args: args{
					rw:  rw,
					r:   httptest.NewRequest("GET", "http://127.0.0.1", bytes.NewBufferString("test")),
					err: errors.New(ErrMsgUnverified),
				},
				checkFunc: func() error {
					if rw.Code != http.StatusUnauthorized {
						return errors.Errorf("invalid status code: %v", rw.Code)
					}
					return nil
				},
			}
		}(),
		func() test {
			rw := httptest.NewRecorder()
			return test{
				name: "handleError status return request timeout",
				args: args{
					rw:  rw,
					r:   httptest.NewRequest("GET", "http://127.0.0.1", bytes.NewBufferString("test")),
					err: context.Canceled,
				},
				checkFunc: func() error {
					if rw.Code != http.StatusRequestTimeout {
						return errors.Errorf("invalid status code: %v", rw.Code)
					}
					return nil
				},
			}
		}(),
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			handleError(tt.args.rw, tt.args.r, tt.args.err)
			if err := tt.checkFunc(); err != nil {
				t.Errorf("handleError error: %v", err)
			}
		})
	}
}


<|start_filename|>handler/roundtripper_mock.go<|end_filename|>
package handler

import "net/http"

// RoundTripperMock is a mock of RoundTripper
type RoundTripperMock struct {
	RoundTripFunc func(req *http.Request) (*http.Response, error)
}

// RoundTrip is a mock implementation of RoundTripper.RoundTrip
func (rt *RoundTripperMock) RoundTrip(req *http.Request) (*http.Response, error) {
	return rt.RoundTripFunc(req)
}


<|start_filename|>config/config_test.go<|end_filename|>
/*
Copyright (C)  2018 Yahoo Japan Corporation Athenz team.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
	"fmt"
	"os"
	"reflect"
	"testing"
	"time"

	authorizerd "github.com/yahoojapan/athenz-authorizer/v5"

	"github.com/kpango/glg"
)

func TestNew(t *testing.T) {
	type args struct {
		path string
	}
	type test struct {
		name       string
		args       args
		beforeFunc func() error
		afterFunc  func() error
		want       *Config
		wantErr    error
	}
	tests := []test{
		{
			name: "Test file content not valid",
			args: args{
				path: "../test/data/not_valid_config.yaml",
			},
			wantErr: fmt.Errorf("decode file failed: yaml: line 11: could not find expected ':'"),
		},
		{
			name: "Open file error",
			args: args{
				path: "./tmp",
			},
			beforeFunc: func() error {
				f, err := os.Create("./tmp")
				if err != nil {
					return err
				}
				defer f.Close()

				err = f.Chmod(0000)
				if err != nil {
					return err
				}

				return nil
			},
			afterFunc: func() error {
				return os.Remove("./tmp")
			},
			wantErr: fmt.Errorf("OpenFile failed: open ./tmp: permission denied"),
		},
		{
			name: "Test file content valid",
			args: args{
				path: "../test/data/example_config.yaml",
			},
			want: &Config{
				Version: "v2.0.0",
				Server: Server{
					Port:            8082,
					Timeout:         "10s",
					ShutdownTimeout: "10s",
					ShutdownDelay:   "9s",
					TLS: TLS{
						Enable:   true,
						CertPath: "test/data/dummyServer.crt",
						KeyPath:  "test/data/dummyServer.key",
						CAPath:   "test/data/dummyCa.pem",
					},
					HealthCheck: HealthCheck{
						Port:     6082,
						Endpoint: "/healthz",
					},
					Debug: Debug{
						Enable:    false,
						Port:      6083,
						Dump:      true,
						Profiling: true,
					},
				},
				Athenz: Athenz{
					URL:     "https://athenz.io:4443/zts/v1",
					Timeout: "30s",
					CAPath:  "_athenz_root_ca_",
				},
				Proxy: Proxy{
					Scheme:                 "http",
					Host:                   "localhost",
					Port:                   80,
					BufferSize:             4096,
					OriginHealthCheckPaths: []string{},
					Transport: Transport{
						TLSHandshakeTimeout:    10 * time.Second,
						DisableKeepAlives:      false,
						DisableCompression:     false,
						MaxIdleConns:           100,
						MaxIdleConnsPerHost:    0,
						MaxConnsPerHost:        0,
						IdleConnTimeout:        90 * time.Second,
						ResponseHeaderTimeout:  time.Duration(0),
						ExpectContinueTimeout:  1 * time.Second,
						MaxResponseHeaderBytes: 0,
						WriteBufferSize:        0,
						ReadBufferSize:         0,
						ForceAttemptHTTP2:      true,
					},
				},
				Authorization: Authorization{
					PublicKey: PublicKey{
						SysAuthDomain:   "sys.auth",
						RefreshPeriod:   "24h",
						ETagExpiry:      "168h",
						ETagPurgePeriod: "84h",
					},
					AthenzDomains: []string{
						"provider-domain1",
						"provider-domain2",
					},
					Policy: Policy{
						ExpiryMargin:  "48h",
						RefreshPeriod: "1h",
						PurgePeriod:   "24h",
						RetryDelay:    "",
						RetryAttempts: 0,
						MappingRules: map[string][]authorizerd.Rule{
							"domain1": {
								authorizerd.Rule{
									Method:   "get",
									Path:     "/path1/{path2}",
									Action:   "action",
									Resource: "path1.{path2}",
								},
								authorizerd.Rule{
									Method:   "get",
									Path:     "/path?param={value}",
									Action:   "action",
									Resource: "path.{value}",
								},
							},
							"domain2": {
								authorizerd.Rule{
									Method:   "get",
									Path:     "/path1/{path2}?param={value}",
									Action:   "action",
									Resource: "{path2}.{value}",
								},
							},
						},
					},
					JWK: JWK{
						RefreshPeriod: "",
						RetryDelay:    "",
						URLs:          []string{"http://your-jwk-set-url1", "https://your-jwk-set-url2"},
					},
					AccessToken: AccessToken{
						Enable:               true,
						VerifyCertThumbprint: true,
						VerifyClientID:       true,
						AuthorizedClientIDs: map[string][]string{
							"common_name1": {"client_id1", "client_id2"},
							"common_name2": {"client_id1", "client_id2"},
						},
						CertBackdateDuration: "1h",
						CertOffsetDuration:   "1h",
					},
					RoleToken: RoleToken{
						Enable:         true,
						RoleAuthHeader: "Athenz-Role-Auth",
					},
				},
				Log: Log{
					Level: "debug",
					Color: true,
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.beforeFunc != nil {
				if err := tt.beforeFunc(); err != nil {
					t.Error(err)
				}
			}
			if tt.afterFunc != nil {
				defer func() {
					if err := tt.afterFunc(); err != nil {
						t.Error(err)
					}
				}()
			}

			got, err := New(tt.args.path)

			if tt.wantErr == nil && err != nil {
				t.Errorf("New() unexpected error, got: %v", err)
				return
			}
			if tt.wantErr != nil {
				if err == nil {
					t.Errorf("want error: %v, got nil", tt.wantErr)
					return
				}
				if err.Error() != tt.wantErr.Error() {
					t.Errorf("New() error, got: %v, want: %v", err, tt.wantErr)
					return
				}
			}
			glg.Debugf("%v want: %v", got, tt.want)
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("New()= %v, want= %v", got, tt.want)
				return
			}
		})
	}
}

func TestGetVersion(t *testing.T) {
	tests := []struct {
		name string
		want string
	}{
		{
			name: "Test get version return sidecar version",
			want: "v2.0.0",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := GetVersion(); got != tt.want {
				t.Errorf("GetVersion() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestGetActualValue(t *testing.T) {
	type args struct {
		val string
	}
	tests := []struct {
		name       string
		args       args
		beforeFunc func() error
		afterFunc  func() error
		want       string
	}{
		{
			name: "GetActualValue without env var",
			args: args{
				val: "test_env",
			},
			want: "test_env",
		},
		{
			name: "GetActualValue with env var",
			args: args{
				val: "_dummy_key_",
			},
			beforeFunc: func() error {
				return os.Setenv("dummy_key", "dummy_value")
			},
			afterFunc: func() error {
				return os.Unsetenv("dummy_key")
			},
			want: "dummy_value",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.beforeFunc != nil {
				if err := tt.beforeFunc(); err != nil {
					t.Error(err)
				}
			}
			if tt.afterFunc != nil {
				defer func() {
					if err := tt.afterFunc(); err != nil {
						t.Error(err)
					}
				}()
			}

			if got := GetActualValue(tt.args.val); got != tt.want {
				t.Errorf("GetVersion() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCheckPrefixAndSuffix(t *testing.T) {
	type args struct {
		str  string
		pref string
		suf  string
	}
	tests := []struct {
		name string
		args args
		want bool
	}{
		{
			name: "Check true prefix and suffix",
			args: args{
				str:  "_dummy_",
				pref: "_",
				suf:  "_",
			},
			want: true,
		},
		{
			name: "Check false prefix and suffix",
			args: args{
				str:  "dummy",
				pref: "_",
				suf:  "_",
			},
			want: false,
		},
		{
			name: "Check true prefix but false suffix",
			args: args{
				str:  "_dummy",
				pref: "_",
				suf:  "_",
			},
			want: false,
		},
		{
			name: "Check false prefix but true suffix",
			args: args{
				str:  "dummy_",
				pref: "_",
				suf:  "_",
			},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := checkPrefixAndSuffix(tt.args.str, tt.args.pref, tt.args.suf); got != tt.want {
				t.Errorf("GetVersion() = %v, want %v", got, tt.want)
			}
		})
	}
}


<|start_filename|>service/server_mock.go<|end_filename|>
package service

import (
	"context"
	"net/http"
)

// ServerMock is a mock of Server
type ServerMock struct {
	ListenAndServeFunc func(context.Context) <-chan []error
}

// ListenAndServe is a mock implementation of Server.ListenAndServe
func (sm *ServerMock) ListenAndServe(ctx context.Context) <-chan []error {
	return sm.ListenAndServeFunc(ctx)
}

// ResponseWriterMock is a mock of ResponseWriter
type ResponseWriterMock struct {
	header    http.Header
	writeFunc func(buf []byte) (int, error)
	code      int
}

// Header is a mock implementation of ResponseWriter.Header
func (rw *ResponseWriterMock) Header() http.Header {
	return rw.header
}

// Write is a mock implementation of ResponseWriter.Write
func (rw *ResponseWriterMock) Write(buf []byte) (int, error) {
	return rw.writeFunc(buf)
}

// WriteHeader is a mock implementation of ResponseWriter.WriteHeader
func (rw *ResponseWriterMock) WriteHeader(code int) {
	rw.code = code
}


<|start_filename|>router/debug_routes_test.go<|end_filename|>
package router

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/pprof"
	"reflect"
	"testing"

	"github.com/yahoojapan/authorization-proxy/v4/config"
	"github.com/yahoojapan/authorization-proxy/v4/service"
)

func TestNewDebugRoutes(t *testing.T) {
	type args struct {
		cfg config.Debug
		a   service.Authorizationd
	}
	type test struct {
		name      string
		args      args
		checkFunc func(got, want []Route) error
		want      []Route
	}
	tests := []test{
		test{
			name: "return all enable success",
			args: args{
				cfg: config.Debug{
					Dump:      true,
					Profiling: true,
				},
				a: nil,
			},
			checkFunc: func(got, want []Route) error {
				for i, gotValue := range got {
					wantValue := want[i]
					if gotValue.Name != wantValue.Name {
						return errors.New("name not match")
					}
					if !reflect.DeepEqual(gotValue.Methods, wantValue.Methods) {
						return errors.New("methods not match")
					}
					if gotValue.Pattern != wantValue.Pattern {
						return errors.New("pattern not match")
					}
					if reflect.ValueOf(gotValue.HandlerFunc).Pointer() != reflect.ValueOf(wantValue.HandlerFunc).Pointer() {
						return errors.New("handler not match")
					}
				}
				return nil
			},
			want: []Route{
				{
					"GetPolicyCache",
					[]string{
						http.MethodGet,
					},
					"/debug/cache/policy",
					NewPolicyCacheHandler(nil),
				},
				{
					"Debug pprof",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/",
					toHandler(pprof.Index),
				},
				{
					"Debug cmdline",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/cmdline",
					toHandler(pprof.Cmdline),
				},
				{
					"Debug profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/profile",
					toHandler(pprof.Profile),
				},
				{
					"Debug symbol profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/symbol",
					toHandler(pprof.Symbol),
				},
				{
					"Debug trace profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/trace",
					toHandler(pprof.Trace),
				},
				{
					"Debug heap profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/heap",
					toHandler(pprof.Handler("heap").ServeHTTP),
				},
				{
					"Debug goroutine profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/goroutine",
					toHandler(pprof.Handler("goroutine").ServeHTTP),
				},
				{
					"Debug thread profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/threadcreate",
					toHandler(pprof.Handler("threadcreate").ServeHTTP),
				},
				{
					"Debug block profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/block",
					toHandler(pprof.Handler("block").ServeHTTP),
				},
			},
		},
		test{
			name: "return enable dump only success",
			args: args{
				cfg: config.Debug{
					Dump:      true,
					Profiling: false,
				},
				a: nil,
			},
			checkFunc: func(got, want []Route) error {
				for i, gotValue := range got {
					wantValue := want[i]
					if gotValue.Name != wantValue.Name {
						return errors.New("name not match")
					}
					if !reflect.DeepEqual(gotValue.Methods, wantValue.Methods) {
						return errors.New("methods not match")
					}
					if gotValue.Pattern != wantValue.Pattern {
						return errors.New("pattern not match")
					}
					if reflect.ValueOf(gotValue.HandlerFunc).Pointer() != reflect.ValueOf(wantValue.HandlerFunc).Pointer() {
						return errors.New("handler not match")
					}
				}
				return nil
			},
			want: []Route{
				{
					"GetPolicyCache",
					[]string{
						http.MethodGet,
					},
					"/debug/cache/policy",
					NewPolicyCacheHandler(nil),
				},
			},
		},
		test{
			name: "return enable profiling success",
			args: args{
				cfg: config.Debug{
					Dump:      false,
					Profiling: true,
				},
				a: nil,
			},
			checkFunc: func(got, want []Route) error {
				for i, gotValue := range got {
					wantValue := want[i]
					if gotValue.Name != wantValue.Name {
						return errors.New("name not match")
					}
					if !reflect.DeepEqual(gotValue.Methods, wantValue.Methods) {
						return errors.New("methods not match")
					}
					if gotValue.Pattern != wantValue.Pattern {
						return errors.New("pattern not match")
					}
					if reflect.ValueOf(gotValue.HandlerFunc).Pointer() != reflect.ValueOf(wantValue.HandlerFunc).Pointer() {
						return errors.New("handler not match")
					}
				}
				return nil
			},
			want: []Route{
				{
					"Debug pprof",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/",
					toHandler(pprof.Index),
				},
				{
					"Debug cmdline",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/cmdline",
					toHandler(pprof.Cmdline),
				},
				{
					"Debug profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/profile",
					toHandler(pprof.Profile),
				},
				{
					"Debug symbol profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/symbol",
					toHandler(pprof.Symbol),
				},
				{
					"Debug trace profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/trace",
					toHandler(pprof.Trace),
				},
				{
					"Debug heap profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/heap",
					toHandler(pprof.Handler("heap").ServeHTTP),
				},
				{
					"Debug goroutine profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/goroutine",
					toHandler(pprof.Handler("goroutine").ServeHTTP),
				},
				{
					"Debug thread profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/threadcreate",
					toHandler(pprof.Handler("threadcreate").ServeHTTP),
				},
				{
					"Debug block profile",
					[]string{
						http.MethodGet,
					},
					"/debug/pprof/block",
					toHandler(pprof.Handler("block").ServeHTTP),
				},
			},
		},
		test{
			name: "disable all and return success",
			args: args{
				cfg: config.Debug{
					Dump:      false,
					Profiling: false,
				},
				a: nil,
			},
			checkFunc: func(got, want []Route) error {
				if len(got) != len(want) {
					return fmt.Errorf("got: %v, want: %v", got, want)
				}
				return nil
			},
			want: nil,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := NewDebugRoutes(tt.args.cfg, tt.args.a)
			if err := tt.checkFunc(got, tt.want); err != nil {
				t.Errorf("NewDebugRoutes() error: %v", err)
			}
		})
	}
}


<|start_filename|>handler/handler.go<|end_filename|>
/*
Copyright (C)  2018 Yahoo Japan Corporation Athenz team.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package handler

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"strings"
	"time"

	"github.com/kpango/glg"
	"github.com/pkg/errors"

	"github.com/yahoojapan/authorization-proxy/v4/config"
	"github.com/yahoojapan/authorization-proxy/v4/service"
)

// Func represents the a handle function type
type Func func(http.ResponseWriter, *http.Request) error

// New creates a handler for handling different HTTP requests based on the given services. It also contains a reverse proxy for handling proxy request.
func New(cfg config.Proxy, bp httputil.BufferPool, prov service.Authorizationd) http.Handler {
	scheme := "http"
	if cfg.Scheme != "" {
		scheme = cfg.Scheme
	}

	host := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)

	return &httputil.ReverseProxy{
		BufferPool: bp,
		Director: func(r *http.Request) {
			u := *r.URL
			u.Scheme = scheme
			u.Host = host
			req, err := http.NewRequest(r.Method, u.String(), r.Body)
			if err != nil {
				glg.Error(errors.Wrap(err, "NewRequest returned error"))
				r.URL.Scheme = scheme
				return
			}
			req.Header = r.Header
			req.TLS = r.TLS
			*r = *req
		},
		Transport: &transport{
			prov:         prov,
			RoundTripper: transportFromCfg(cfg.Transport),
			cfg:          cfg,
		},
		ErrorHandler: handleError,
	}
}

func transportFromCfg(cfg config.Transport) *http.Transport {
	isZero := func(v interface{}) bool {
		switch v.(type) {
		case int:
			return v == 0
		case int64:
			return v == 0
		case time.Duration:
			return v == time.Duration(0)
		case bool:
			return v == false
		default:
			glg.Fatal("Undefined type on proxy transport config")
			return false
		}
	}

	t := &http.Transport{}
	if !isZero(cfg.TLSHandshakeTimeout) {
		t.TLSHandshakeTimeout = cfg.TLSHandshakeTimeout
	}
	if !isZero(cfg.DisableKeepAlives) {
		t.DisableKeepAlives = cfg.DisableKeepAlives
	}
	if !isZero(cfg.DisableCompression) {
		t.DisableCompression = cfg.DisableCompression
	}
	if !isZero(cfg.MaxIdleConns) {
		t.MaxIdleConns = cfg.MaxIdleConns
	}
	if !isZero(cfg.MaxIdleConnsPerHost) {
		t.MaxIdleConnsPerHost = cfg.MaxIdleConnsPerHost
	}
	if !isZero(cfg.MaxConnsPerHost) {
		t.MaxConnsPerHost = cfg.MaxConnsPerHost
	}
	if !isZero(cfg.IdleConnTimeout) {
		t.IdleConnTimeout = cfg.IdleConnTimeout
	}
	if !isZero(cfg.ResponseHeaderTimeout) {
		t.ResponseHeaderTimeout = cfg.ResponseHeaderTimeout
	}
	if !isZero(cfg.ExpectContinueTimeout) {
		t.ExpectContinueTimeout = cfg.ExpectContinueTimeout
	}
	if !isZero(cfg.MaxResponseHeaderBytes) {
		t.MaxResponseHeaderBytes = cfg.MaxResponseHeaderBytes
	}
	if !isZero(cfg.WriteBufferSize) {
		t.WriteBufferSize = cfg.WriteBufferSize
	}
	if !isZero(cfg.ReadBufferSize) {
		t.ReadBufferSize = cfg.ReadBufferSize
	}
	if !isZero(cfg.ForceAttemptHTTP2) {
		t.ForceAttemptHTTP2 = cfg.ForceAttemptHTTP2
	}

	glg.Debugf("proxy transport: %+v\n", t)
	return t
}

func handleError(rw http.ResponseWriter, r *http.Request, err error) {
	if r != nil && r.Body != nil {
		io.Copy(ioutil.Discard, r.Body)
		r.Body.Close()
	}
	status := http.StatusUnauthorized
	if !strings.Contains(err.Error(), ErrMsgUnverified) {
		glg.Warn("handleError: " + err.Error())
		status = http.StatusBadGateway
	}
	// request context canceled
	if errors.Cause(err) == context.Canceled {
		status = http.StatusRequestTimeout
	}
	rw.WriteHeader(status)
}


<|start_filename|>handler/principal_mock.go<|end_filename|>
package handler

// PrincipalMock is a mock of Principal
type PrincipalMock struct {
	NameFunc       func() string
	RolesFunc      func() []string
	DomainFunc     func() string
	IssueTimeFunc  func() int64
	ExpiryTimeFunc func() int64
}

// OAuthAccessTokenMock is a mock of OAuthAccessToken
type OAuthAccessTokenMock struct {
	PrincipalMock
	ClientIDFunc func() string
}

// Name is a mock implementation of Principal
func (p *PrincipalMock) Name() string {
	return p.NameFunc()
}

// Roles is a mock implementation of Principal
func (p *PrincipalMock) Roles() []string {
	return p.RolesFunc()
}

// Domain is a mock implementation of Principal
func (p *PrincipalMock) Domain() string {
	return p.DomainFunc()
}

// IssueTime is a mock implementation of Principal
func (p *PrincipalMock) IssueTime() int64 {
	return p.IssueTimeFunc()
}

// ExpiryTime is a mock implementation of Principal
func (p *PrincipalMock) ExpiryTime() int64 {
	return p.ExpiryTimeFunc()
}

// ClientID is a mock implementation of OAuthAccessToken
func (oat *OAuthAccessTokenMock) ClientID() string {
	return oat.ClientIDFunc()
}


<|start_filename|>router/debug_routes.go<|end_filename|>
/*
Copyright (C)  2018 Yahoo Japan Corporation Athenz team.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package router

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/pprof"

	"github.com/yahoojapan/authorization-proxy/v4/config"
	"github.com/yahoojapan/authorization-proxy/v4/handler"
	"github.com/yahoojapan/authorization-proxy/v4/service"
)

// Route contains information and handler of an API endpoint
type Route struct {
	Name        string
	Methods     []string
	Pattern     string
	HandlerFunc handler.Func
}

// NewDebugRoutes returns debug endpoint information. If Dump flag is enabled then the cache dump feature endpoint will be included.
// If Profiling flag is enable then the pprof interface endpoint will be included.
func NewDebugRoutes(cfg config.Debug, a service.Authorizationd) []Route {
	var routes []Route

	if cfg.Dump {
		routes = append(routes, Route{
			"GetPolicyCache",
			[]string{
				http.MethodGet,
			},
			"/debug/cache/policy",
			NewPolicyCacheHandler(a),
		})
	}

	if cfg.Profiling {
		routes = append(routes, []Route{
			{
				"Debug pprof",
				[]string{
					http.MethodGet,
				},
				"/debug/pprof/",
				toHandler(pprof.Index),
			},
			{
				"Debug cmdline",
				[]string{
					http.MethodGet,
				},
				"/debug/pprof/cmdline",
				toHandler(pprof.Cmdline),
			},
			{
				"Debug profile",
				[]string{
					http.MethodGet,
				},
				"/debug/pprof/profile",
				toHandler(pprof.Profile),
			},
			{
				"Debug symbol profile",
				[]string{
					http.MethodGet,
				},
				"/debug/pprof/symbol",
				toHandler(pprof.Symbol),
			},
			{
				"Debug trace profile",
				[]string{
					http.MethodGet,
				},
				"/debug/pprof/trace",
				toHandler(pprof.Trace),
			},
			{
				"Debug heap profile",
				[]string{
					http.MethodGet,
				},
				"/debug/pprof/heap",
				toHandler(pprof.Handler("heap").ServeHTTP),
			},
			{
				"Debug goroutine profile",
				[]string{
					http.MethodGet,
				},
				"/debug/pprof/goroutine",
				toHandler(pprof.Handler("goroutine").ServeHTTP),
			},
			{
				"Debug thread profile",
				[]string{
					http.MethodGet,
				},
				"/debug/pprof/threadcreate",
				toHandler(pprof.Handler("threadcreate").ServeHTTP),
			},
			{
				"Debug block profile",
				[]string{
					http.MethodGet,
				},
				"/debug/pprof/block",
				toHandler(pprof.Handler("block").ServeHTTP),
			},
		}...)
	}

	return routes
}

// NewPolicyCacheHandler returns the handler function to handle get policy cache request.
func NewPolicyCacheHandler(authd service.Authorizationd) func(w http.ResponseWriter, r *http.Request) error {
	return func(w http.ResponseWriter, r *http.Request) error {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", fmt.Sprintf("%s;%s", "application/json", "charset=UTF-8"))
		e := json.NewEncoder(w)
		e.SetIndent("", "\t")
		pc := authd.GetPolicyCache(r.Context())
		return e.Encode(pc)
	}
}

func toHandler(f http.HandlerFunc) func(w http.ResponseWriter, r *http.Request) error {
	return func(w http.ResponseWriter, r *http.Request) error {
		f(w, r)
		return nil
	}
}


<|start_filename|>handler/transport.go<|end_filename|>
/*
Copyright (C)  2018 Yahoo Japan Corporation Athenz team.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package handler

import (
	"net/http"
	"strconv"
	"strings"

	authorizerd "github.com/yahoojapan/athenz-authorizer/v5"

	"github.com/yahoojapan/authorization-proxy/v4/config"
	"github.com/yahoojapan/authorization-proxy/v4/service"

	"github.com/kpango/glg"
	"github.com/pkg/errors"
)

type transport struct {
	http.RoundTripper

	prov service.Authorizationd
	cfg  config.Proxy
}

// Based on the following.
// https://github.com/golang/oauth2/blob/bf48bf16ab8d622ce64ec6ce98d2c98f916b6303/transport.go
func (t *transport) RoundTrip(r *http.Request) (*http.Response, error) {
	for _, urlPath := range t.cfg.OriginHealthCheckPaths {
		if urlPath == r.URL.Path {
			glg.Info("Authorization checking skipped on: " + r.URL.Path)
			r.TLS = nil
			return t.RoundTripper.RoundTrip(r)
		}
	}

	reqBodyClosed := false
	if r.Body != nil {
		defer func() {
			if !reqBodyClosed {
				r.Body.Close()
			}
		}()
	}

	p, err := t.prov.Authorize(r, r.Method, r.URL.Path)
	if err != nil {
		return nil, errors.Wrap(err, ErrMsgUnverified)
	}

	req2 := cloneRequest(r) // per RoundTripper contract

	req2.Header.Set("X-Athenz-Principal", p.Name())
	req2.Header.Set("X-Athenz-Role", strings.Join(p.Roles(), ","))
	req2.Header.Set("X-Athenz-Domain", p.Domain())
	req2.Header.Set("X-Athenz-Issued-At", strconv.FormatInt(p.IssueTime(), 10))
	req2.Header.Set("X-Athenz-Expires-At", strconv.FormatInt(p.ExpiryTime(), 10))

	if c, ok := p.(authorizerd.OAuthAccessToken); ok {
		req2.Header.Set("X-Athenz-Client-ID", c.ClientID())
	}

	req2.TLS = nil
	// req.Body is assumed to be closed by the base RoundTripper.
	reqBodyClosed = true
	return t.RoundTripper.RoundTrip(req2)
}

// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
	// shallow copy of the struct
	r2 := new(http.Request)
	*r2 = *r
	// deep copy of the Header
	r2.Header = make(http.Header, len(r.Header))
	for k, s := range r.Header {
		r2.Header[k] = append([]string(nil), s...)
	}
	return r2
}


<|start_filename|>service/tls.go<|end_filename|>
/*
Copyright (C)  2018 Yahoo Japan Corporation Athenz team.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package service

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"

	"github.com/pkg/errors"
	"github.com/yahoojapan/authorization-proxy/v4/config"
)

// NewTLSConfig returns a *tls.Config struct or error.
// It reads TLS configuration and initializes *tls.Config struct.
// It initializes TLS configuration, for example the CA certificate and key to start TLS server.
// Server and CA Certificate, and private key will read from files from file paths defined in environment variables.
func NewTLSConfig(cfg config.TLS) (*tls.Config, error) {
	t := &tls.Config{
		MinVersion: tls.VersionTLS12,
		CurvePreferences: []tls.CurveID{
			tls.CurveP521,
			tls.CurveP384,
			tls.CurveP256,
			tls.X25519,
		},
		SessionTicketsDisabled: true,
		// PreferServerCipherSuites: true,
		// CipherSuites: []uint16{
		// tls.TLS_RSA_WITH_RC4_128_SHA,
		// tls.TLS_RSA_WITH_AES_128_CBC_SHA,
		// tls.TLS_RSA_WITH_AES_256_CBC_SHA,
		// tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
		// tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
		// tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
		// tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
		// tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
		// tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		// tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
		// tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
		// tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
		// tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
		// tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
		// tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
		// tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		// tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
		// tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		// tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, // Maybe this is work on TLS 1.2
		// tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // TLS1.3 Feature
		// tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, // TLS1.3 Feature
		// tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, // Go 1.8 only
		// tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, // Go 1.8 only
		// },
		ClientAuth: tls.NoClientCert,
	}

	cert := config.GetActualValue(cfg.CertPath)
	key := config.GetActualValue(cfg.KeyPath)
	ca := config.GetActualValue(cfg.CAPath)

	if cert != "" && key != "" {
		crt, err := tls.LoadX509KeyPair(cert, key)
		if err != nil {
			return nil, errors.Wrap(err, "tls.LoadX509KeyPair(cert, key)")
		}
		t.Certificates = make([]tls.Certificate, 1)
		t.Certificates[0] = crt
	}

	if ca != "" {
		pool, err := NewX509CertPool(ca)
		if err != nil {
			return nil, errors.Wrap(err, "NewX509CertPool(ca)")
		}
		t.ClientCAs = pool
		t.ClientAuth = tls.RequireAndVerifyClientCert
	}

	return t, nil
}

// NewX509CertPool returns *x509.CertPool struct or error.
// The CertPool will read the certificate from the path, and append the content to the system certificate pool.
func NewX509CertPool(path string) (*x509.CertPool, error) {
	var pool *x509.CertPool
	c, err := ioutil.ReadFile(path)
	if err == nil && c != nil {
		pool, err = x509.SystemCertPool()
		if err != nil || pool == nil {
			pool = x509.NewCertPool()
		}
		if !pool.AppendCertsFromPEM(c) {
			err = errors.New("Certification Failed")
		}
	}
	return pool, errors.Wrap(err, "x509.SystemCertPool()")
}


<|start_filename|>handler/error.go<|end_filename|>
/*
Copyright (C)  2018 Yahoo Japan Corporation Athenz team.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package handler

// RFC7807Error represents the error message fulfilling RFC7807 standard.
type RFC7807Error struct {
	Type          string `json:"type"`
	Title         string `json:"title"`
	Status        int
	InvalidParams []InvalidParam `json:"invalid-params,omitempty"`
	Detail        string         `json:"detail"`
	Instance      string         `json:"instance"`
	RoleToken     string         `json:"role_token"`
}

// InvalidParam represents the invalid parameters requested by the user.
type InvalidParam struct {
	Name   string `json:"name"`
	Reason string `json:"reason"`
}

const (
	// ProblemJSONContentType represents the media type of the error response
	ProblemJSONContentType = "application/problem+json"

	// HTTPStatusClientClosedRequest represents a non-standard status code meaning that the client closed the connection before the server answered the request
	HTTPStatusClientClosedRequest = 499

	// ErrMsgUnverified "unauthenticated/unauthorized"
	ErrMsgUnverified = "unauthenticated/unauthorized"
)
