func (a *accessService) createPostAccessTokenRequest(scope, proxyForPrincipal string, expiry int64) (*http.Request, error) {
	u := fmt.Sprintf("https://%s/oauth2/token", strings.TrimPrefix(strings.TrimPrefix(a.athenzURL, "https://"), "http://"))
	q := url.Values{}
	q.Add("grant_type", "client_credentials")
	q.Add("scope", scope)
	if proxyForPrincipal != "" {
		q.Add("proxy_for_principal", proxyForPrincipal)
	}
	if expiry <= 0 {
		expiry = int64(a.expiry / time.Second)
	}
	if expiry > 0 {
		q.Add("expires_in", strconv.FormatInt(expiry, 10))
	}
	req, err := http.NewRequest(http.MethodPost, u, strings.NewReader(q.Encode()))
	if err != nil {
		glg.Debugf("fail to create request object, error: %s", err)
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	return req, nil
}
createGetAccessTokenRequest creates Athenz's postAccessTokenRequest.
package service
import (
	"bytes"
	"context"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
	"github.com/kpango/fastime"
	"github.com/kpango/gache"
	"github.com/kpango/glg"
	"github.com/kpango/ntokend"
	"github.com/pkg/errors"
	"github.com/yahoojapan/athenz-client-sidecar/v2/config"
	"golang.org/x/sync/singleflight"
)
type AccessService interface {
	StartAccessUpdater(context.Context) <-chan error
	RefreshAccessTokenCache(ctx context.Context) <-chan error
	GetAccessProvider() AccessProvider
}
type accessService struct {
	cfg                   config.AccessToken
	token                 ntokend.TokenProvider
	athenzURL             string
	athenzPrincipleHeader string
	tokenCache            gache.Gache
	group                 singleflight.Group
	expiry                time.Duration
	httpClient            atomic.Value
	rootCAs               *x509.CertPool
	certPath              string
	certKeyPath           string
	refreshPeriod    time.Duration
	errRetryMaxCount int
	errRetryInterval time.Duration
}
type accessCacheData struct {
	token             *AccessTokenResponse
	domain            string
	role              string
	proxyForPrincipal string
	expiresIn         int64
}
type AccessTokenResponse struct {
	AccessToken string `json:"access_token"`
	TokenType string `json:"token_type"`
	ExpiresIn int64 `json:"expires_in,omitempty"`
	Scope string `json:"scope,omitempty"`
	RefreshToken string `json:"refresh_token,omitempty"`
	IDToken string `json:"id_token,omitempty"`
}
type AccessProvider func(ctx context.Context, domain string, role string, proxyForPrincipal string, expiresIn int64) (*AccessTokenResponse, error)
var (
	ErrAccessTokenRequestFailed = errors.New("Failed to fetch AccessToken")
)
const (
	scopeSeparator = " "
)
func NewAccessService(cfg config.AccessToken, token ntokend.TokenProvider) (AccessService, error) {
	var (
		err              error
		exp              = defaultExpiry
		refreshPeriod    = defaultRefreshPeriod
		errRetryInterval = defaultErrRetryInterval
	)
	if !cfg.Enable {
		return nil, ErrDisabled
	}
	if cfg.Expiry != "" {
		if exp, err = time.ParseDuration(cfg.Expiry); err != nil {
			return nil, errors.Wrap(ErrInvalidSetting, "Expiry: "+err.Error())
		}
	}
	if cfg.RefreshPeriod != "" {
		if refreshPeriod, err = time.ParseDuration(cfg.RefreshPeriod); err != nil {
			return nil, errors.Wrap(ErrInvalidSetting, "RefreshPeriod: "+err.Error())
		}
	}
	if cfg.Retry.Delay != "" {
		if errRetryInterval, err = time.ParseDuration(cfg.Retry.Delay); err != nil {
			return nil, errors.Wrap(ErrInvalidSetting, "ErrRetryInterval: "+err.Error())
		}
	}
	if exp != 0 && refreshPeriod > exp {
		return nil, errors.Wrap(ErrInvalidSetting, "refresh period > token expiry time")
	}
	errRetryMaxCount := defaultErrRetryMaxCount
	if cfg.Retry.Attempts > 0 {
		errRetryMaxCount = cfg.Retry.Attempts
	} else if cfg.Retry.Attempts != 0 {
		return nil, errors.Wrap(ErrInvalidSetting, "ErrRetryMaxCount < 0")
	}
	if token == nil && cfg.CertPath == "" {
		return nil, errors.Wrap(ErrInvalidSetting, "Neither NToken nor client certificate is set.")
	}
	var cp *x509.CertPool
	if cfg.AthenzCAPath != "" {
		var err error
		caPath := config.GetActualValue(cfg.AthenzCAPath)
		_, err = os.Stat(caPath)
		if os.IsNotExist(err) {
			return nil, errors.Wrap(ErrInvalidSetting, "Athenz CA not exist")
		}
		cp, err = NewX509CertPool(caPath)
		if err != nil {
			return nil, errors.Wrap(ErrInvalidSetting, err.Error())
		}
	}
	certPath := cfg.CertPath
	certKeyPath := cfg.CertKeyPath
	if token != nil {
		certPath = ""
		certKeyPath = ""
	}
	tlsConfig, err := NewTLSClientConfig(cp, certPath, certKeyPath)
	if err != nil {
		return nil, errors.Wrap(ErrInvalidSetting, err.Error())
	}
	var httpClient atomic.Value
	httpClient.Store(&http.Client{
		Transport: &http.Transport{
			TLSClientConfig: tlsConfig,
		},
	})
	return &accessService{
		cfg:                   cfg,
		token:                 token,
		athenzURL:             cfg.AthenzURL,
		athenzPrincipleHeader: cfg.PrincipalAuthHeader,
		tokenCache:            gache.New(),
		expiry:                exp,
		httpClient:            httpClient,
		rootCAs:               cp,
		certPath:              certPath,
		certKeyPath:           certKeyPath,
		refreshPeriod:         refreshPeriod,
		errRetryMaxCount:      errRetryMaxCount,
		errRetryInterval:      errRetryInterval,
	}, nil
}
func (a *accessService) StartAccessUpdater(ctx context.Context) <-chan error {
	glg.Info("Starting access token updater")
	ech := make(chan error, 100)
	go func() {
		defer close(ech)
		ticker := time.NewTicker(a.refreshPeriod)
		for {
			select {
			case <-ctx.Done():
				glg.Info("Stopping access token updater")
				ticker.Stop()
				ech <- ctx.Err()
				return
			case <-ticker.C:
				for err := range a.RefreshAccessTokenCache(ctx) {
					ech <- errors.Wrap(err, "error update access token")
				}
			}
		}
	}()
	a.tokenCache.StartExpired(ctx, cachePurgePeriod)
	a.tokenCache.EnableExpiredHook().SetExpiredHook(func(ctx context.Context, k string) {
		glg.Warnf("the following cache is expired, key: %v", k)
	})
	return ech
}
func (a *accessService) GetAccessProvider() AccessProvider {
	return a.getAccessToken
}
func (a *accessService) getAccessToken(ctx context.Context, domain, role, proxyForPrincipal string, expiresIn int64) (*AccessTokenResponse, error) {
	tok, ok := a.getCache(domain, role, proxyForPrincipal)
	if !ok {
		return a.updateAccessToken(ctx, domain, role, proxyForPrincipal, expiresIn)
	}
	return tok, nil
}
func (a *accessService) RefreshAccessTokenCache(ctx context.Context) <-chan error {
	glg.Info("RefreshAccessTokenCache started")
	echan := make(chan error, a.tokenCache.Len()*(a.errRetryMaxCount+1))
	go func() {
		defer close(echan)
		a.tokenCache.Foreach(ctx, func(key string, val interface{}, exp int64) bool {
			domain, role, principal := decode(key)
			cd := val.(*accessCacheData)
			for err := range a.updateAccessTokenWithRetry(ctx, domain, role, principal, cd.expiresIn) {
				echan <- err
			}
			return true
		})
	}()
	return echan
}
func (a *accessService) updateAccessTokenWithRetry(ctx context.Context, domain, role, proxyForPrincipal string, expiresIn int64) <-chan error {
	glg.Debugf("updateAccessTokenWithRetry started, domain: %s, role: %s, proxyForPrincipal: %s, expiresIn: %d", domain, role, proxyForPrincipal, expiresIn)
	echan := make(chan error, a.errRetryMaxCount+1)
	go func() {
		defer close(echan)
		for i := 0; i <= a.errRetryMaxCount; i++ {
			if _, err := a.updateAccessToken(ctx, domain, role, proxyForPrincipal, expiresIn); err != nil {
				echan <- err
				time.Sleep(a.errRetryInterval)
			} else {
				glg.Debug("update success")
				break
			}
		}
	}()
	return echan
}
func (a *accessService) updateAccessToken(ctx context.Context, domain, role, proxyForPrincipal string, expiresIn int64) (*AccessTokenResponse, error) {
	key := encode(domain, role, proxyForPrincipal)
	expTimeDelta := fastime.Now().Add(time.Minute)
	at, err, _ := a.group.Do(key, func() (interface{}, error) {
		at, e := a.fetchAccessToken(ctx, domain, role, proxyForPrincipal, expiresIn)
		if e != nil {
			return nil, e
		}
		a.tokenCache.SetWithExpire(key, &accessCacheData{
			token:             at,
			domain:            domain,
			role:              role,
			proxyForPrincipal: proxyForPrincipal,
			expiresIn:         expiresIn,
		}, time.Unix(at.ExpiresIn, 0).Sub(expTimeDelta))
		glg.Debugf("token is cached, domain: %s, role: %s, proxyForPrincipal: %s, expiry time: %v", domain, role, proxyForPrincipal, at.ExpiresIn)
		return at, nil
	})
	if err != nil {
		return nil, err
	}
	return at.(*AccessTokenResponse), err
}
func (a *accessService) fetchAccessToken(ctx context.Context, domain, role, proxyForPrincipal string, expiry int64) (*AccessTokenResponse, error) {
	glg.Debugf("get access token, domain: %s, role: %s, proxyForPrincipal: %s, expiry: %d", domain, role, proxyForPrincipal, expiry)
	scope := createScope(domain, role)
	glg.Debugf("request access token scope: %v", scope)
	req, err := a.createPostAccessTokenRequest(scope, proxyForPrincipal, expiry)
	if err != nil {
		glg.Debugf("fail to create request object, error: %s", err)
		return nil, err
	}
	glg.Debugf("request url: %v", req.URL)
	if a.token != nil {
		token, err := a.token()
		if err != nil {
			return nil, err
		}
		req.Header.Set(a.athenzPrincipleHeader, token)
	} else if a.certPath != "" {
		tcc, err := NewTLSClientConfig(a.rootCAs, a.certPath, a.certKeyPath)
		if err != nil {
			return nil, err
		}
		a.httpClient.Store(&http.Client{
			Transport: &http.Transport{
				TLSClientConfig: tcc,
			},
		})
	} else {
		return nil, ErrNoCredentials
	}
	res, err := a.httpClient.Load().(*http.Client).Do(req.WithContext(ctx))
	if err != nil {
		return nil, err
	}
	defer flushAndClose(res.Body)
	if res.StatusCode != http.StatusOK {
		buf := new(bytes.Buffer)
		if _, err := buf.ReadFrom(res.Body); err != nil {
			glg.Debugf("cannot read response body, err: %v", err)
		}
		glg.Debugf("error return from server, response:%+v, body: %v", res, buf.String())
		return nil, ErrAccessTokenRequestFailed
	}
	var atRes *AccessTokenResponse
	if err = json.NewDecoder(res.Body).Decode(&atRes); err != nil {
		return nil, err
	}
	return atRes, nil
}
func createScope(domain, role string) string {
	if role != "" {
		roles := strings.Split(role, roleSeparator)
		scopes := make([]string, len(roles))
		for i, r := range roles {
			scopes[i] = domain + ":role." + r
		}
		return strings.Join(scopes, scopeSeparator)
	}
	return domain + ":domain"
}
func (a *accessService) getCache(domain, role, principal string) (*AccessTokenResponse, bool) {
	val, ok := a.tokenCache.Get(encode(domain, role, principal))
	if !ok {
		return nil, false
	}
	return val.(*accessCacheData).token, ok
}