github.com/Axway/agent-sdk@v1.1.101/pkg/apic/auth/apicauth.go (about)

     1  // Package auth implements the apic service account token management.
     2  // Contributed by Xenon team
     3  package auth
     4  
     5  import (
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/Axway/agent-sdk/pkg/api"
    13  	"github.com/Axway/agent-sdk/pkg/authz/oauth"
    14  	"github.com/Axway/agent-sdk/pkg/config"
    15  	"github.com/Axway/agent-sdk/pkg/util/log"
    16  )
    17  
    18  func closeHelper(closer io.Closer) {
    19  	if err := closer.Close(); err != nil {
    20  		log.Warnf("Failed to close: %v", err)
    21  	}
    22  }
    23  
    24  // PlatformTokenGetter - Interface for token getter
    25  type PlatformTokenGetter interface {
    26  	tokenGetterCloser
    27  }
    28  
    29  // ApicAuth provides authentication methods for calls against APIC Cloud services.
    30  type ApicAuth struct {
    31  	tenantID string
    32  	tokenGetterCloser
    33  }
    34  
    35  // Authenticate applies the authentication headers
    36  func (aa *ApicAuth) Authenticate(hs HeaderSetter) error {
    37  	token, err := aa.GetToken()
    38  	if err != nil {
    39  		return err
    40  	}
    41  
    42  	hs.SetHeader("Authorization", fmt.Sprintf("Bearer %s", token))
    43  	hs.SetHeader("X-Axway-Tenant-Id", aa.tenantID)
    44  
    45  	return nil
    46  }
    47  
    48  // AuthenticateNet applies the authentication headers
    49  func (aa *ApicAuth) AuthenticateNet(req *http.Request) error {
    50  	return aa.Authenticate(NetHeaderSetter{req})
    51  }
    52  
    53  // NewWithStatic returns an ApicAuth that uses a fixed token
    54  func NewWithStatic(tenantID, token string) *ApicAuth {
    55  	return &ApicAuth{
    56  		tenantID,
    57  		staticTokenGetter(token),
    58  	}
    59  }
    60  
    61  // NewWithFlow returns an ApicAuth that uses the axway authentication flow
    62  func NewWithFlow(tenantID, privKey, publicKey, password, url, aud, clientID string, singleURL string, timeout time.Duration) *ApicAuth {
    63  	return &ApicAuth{
    64  		tenantID,
    65  		tokenGetterWithChannel(NewPlatformTokenGetter(privKey, publicKey, password, url, aud, clientID, singleURL, timeout)),
    66  	}
    67  }
    68  
    69  // NewPlatformTokenGetter returns a token getter for axway ID
    70  func NewPlatformTokenGetter(privKey, publicKey, password, url, aud, clientID string, singleURL string, timeout time.Duration) PlatformTokenGetter {
    71  	cfg := config.NewCentralConfig(config.GenericService)
    72  	centralCfg, _ := cfg.(*config.CentralConfiguration)
    73  	centralCfg.SingleURL = singleURL
    74  
    75  	acfg := cfg.GetAuthConfig()
    76  	authCfg, _ := acfg.(*config.AuthConfiguration)
    77  	authCfg.ClientID = clientID
    78  	authCfg.PrivateKey = privKey
    79  	authCfg.PublicKey = publicKey
    80  	authCfg.KeyPwd = password
    81  	authCfg.URL = url
    82  	authCfg.Timeout = timeout
    83  
    84  	return NewPlatformTokenGetterWithCentralConfig(cfg)
    85  }
    86  
    87  // NewPlatformTokenGetterWithCentralConfig returns a token getter for axway ID
    88  func NewPlatformTokenGetterWithCentralConfig(centralCfg config.CentralConfig) PlatformTokenGetter {
    89  	return &platformTokenGetter{
    90  		cfg: centralCfg,
    91  		keyReader: oauth.NewKeyReader(
    92  			centralCfg.GetAuthConfig().GetPrivateKey(),
    93  			centralCfg.GetAuthConfig().GetPublicKey(),
    94  			centralCfg.GetAuthConfig().GetKeyPassword()),
    95  	}
    96  }
    97  
    98  type funcTokenGetter func() (string, error)
    99  
   100  // GetToken returns the fixed token.
   101  func (f funcTokenGetter) GetToken() (string, error) {
   102  	return f()
   103  }
   104  
   105  func (f funcTokenGetter) Close() error {
   106  	return nil
   107  }
   108  
   109  // staticTokenGetter returns a token getter with a fixed token
   110  func staticTokenGetter(token string) funcTokenGetter {
   111  	return funcTokenGetter(func() (string, error) { return token, nil })
   112  }
   113  
   114  // platformTokenGetter can get an access token from apic platform.
   115  type platformTokenGetter struct {
   116  	cfg           config.CentralConfig
   117  	keyReader     oauth.KeyReader
   118  	axwayIDClient oauth.AuthClient
   119  }
   120  
   121  // Close a PlatformTokenGetter
   122  func (ptp *platformTokenGetter) Close() error {
   123  	return nil
   124  }
   125  
   126  func (ptp *platformTokenGetter) initAxwayIDPClient() error {
   127  	privateKey, err := ptp.keyReader.GetPrivateKey()
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	publicKey, err := ptp.keyReader.GetPublicKey()
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	apiClient := api.NewClient(
   138  		ptp.cfg.GetTLSConfig(),
   139  		ptp.cfg.GetProxyURL(),
   140  		api.WithTimeout(ptp.cfg.GetAuthConfig().GetTimeout()),
   141  		api.WithSingleURL())
   142  
   143  	ptp.axwayIDClient, err = oauth.NewAuthClient(ptp.cfg.GetAuthConfig().GetTokenURL(), apiClient,
   144  		oauth.WithServerName("AxwayId"),
   145  		oauth.WithKeyPairAuth(
   146  			ptp.cfg.GetAuthConfig().GetClientID(),
   147  			"",
   148  			ptp.cfg.GetAuthConfig().GetAudience(),
   149  			privateKey,
   150  			publicKey, "", oauth.SigningMethodRS256))
   151  
   152  	return err
   153  }
   154  
   155  // GetToken returns a token from cache if not expired or fetches a new token
   156  func (ptp *platformTokenGetter) GetToken() (string, error) {
   157  	if ptp.axwayIDClient == nil {
   158  		err := ptp.initAxwayIDPClient()
   159  		if err != nil {
   160  			return "", err
   161  		}
   162  	}
   163  
   164  	token, err := ptp.axwayIDClient.GetToken()
   165  	if err != nil && strings.Contains(err.Error(), "bad response") {
   166  		log.Debug("please check the value for CENTRAL_AUTH_URL: The Amplify login URL.  Otherwise, possibly a clock syncing issue. Please check NTP daemon, if being used, that is up and running correctly.")
   167  	}
   168  	return token, err
   169  }
   170  
   171  // TokenGetter provides a bearer token to be used in api calls. Might block
   172  type TokenGetter interface {
   173  	GetToken() (string, error)
   174  }
   175  
   176  // TokenGetterCloser can get a token and clean up resources if needed.
   177  type tokenGetterCloser interface {
   178  	TokenGetter
   179  	Close() error
   180  }
   181  
   182  // NetHeaderSetter sets headers an a net/http request
   183  type NetHeaderSetter struct {
   184  	*http.Request
   185  }
   186  
   187  // SetHeader sets a header on a net/http request
   188  func (nhs NetHeaderSetter) SetHeader(key, value string) {
   189  	nhs.Header.Set(key, value)
   190  }
   191  
   192  // HeaderSetter sets a header for a request
   193  type HeaderSetter interface {
   194  	// SetHeader sets a header on a http request
   195  	SetHeader(key, value string)
   196  }
   197  
   198  // channelTokenGetter uses a channel to ensure synchronized access to the wrapped token getter
   199  type channelTokenGetter struct {
   200  	tokenGetter tokenGetterCloser
   201  	responses   chan struct {
   202  		string
   203  		error
   204  	}
   205  	requests chan struct{}
   206  }
   207  
   208  // tokenGetterWithChannel wraps a token getter in a channelTokenGetter
   209  func tokenGetterWithChannel(tokenGetter tokenGetterCloser) *channelTokenGetter {
   210  	requests := make(chan struct{})
   211  	responses := make(chan struct {
   212  		string
   213  		error
   214  	})
   215  
   216  	ctg := &channelTokenGetter{tokenGetter, responses, requests}
   217  
   218  	go ctg.loop()
   219  
   220  	return ctg
   221  }
   222  
   223  // loop reads requests and responds with token from the embedded token getter
   224  func (ctg *channelTokenGetter) loop() {
   225  	defer close(ctg.responses)
   226  	defer closeHelper(ctg.tokenGetter)
   227  	for {
   228  		if _, ok := <-ctg.requests; !ok { // wait for a request
   229  			break // if input channel is closed, stop
   230  		}
   231  
   232  		t, err := ctg.tokenGetter.GetToken()
   233  		ctg.responses <- struct { // send back a response
   234  			string
   235  			error
   236  		}{t, err}
   237  
   238  	}
   239  }
   240  
   241  func (ctg *channelTokenGetter) GetToken() (string, error) {
   242  	ctg.requests <- struct{}{}
   243  	resp, ok := <-ctg.responses
   244  	if !ok {
   245  		return "", fmt.Errorf("[apicauth] channelTokenGetter closed")
   246  	}
   247  	return resp.string, resp.error
   248  
   249  }
   250  
   251  func (ctg *channelTokenGetter) Close() error {
   252  	close(ctg.requests)
   253  	return nil
   254  }
   255  
   256  // tokenAuth -
   257  type tokenAuth struct {
   258  	tenantID       string
   259  	tokenRequester TokenGetter
   260  }
   261  
   262  // Config the auth config
   263  type Config struct {
   264  	PrivateKey  string        `mapstructure:"private_key"`
   265  	PublicKey   string        `mapstructure:"public_key"`
   266  	KeyPassword string        `mapstructure:"key_password"`
   267  	URL         string        `mapstructure:"url"`
   268  	Audience    string        `mapstructure:"audience"`
   269  	ClientID    string        `mapstructure:"client_id"`
   270  	Timeout     time.Duration `mapstructure:"timeout"`
   271  }
   272  
   273  // NewTokenAuth Create a new auth token requester
   274  func NewTokenAuth(ac Config, tenantID string) TokenGetter {
   275  	instance := &tokenAuth{tenantID: tenantID}
   276  	tokenURL := ac.URL + "/realms/Broker/protocol/openid-connect/token"
   277  	aud := ac.URL + "/realms/Broker"
   278  
   279  	cfg := &config.CentralConfiguration{}
   280  	singleURL := cfg.GetSingleURL()
   281  
   282  	instance.tokenRequester = NewPlatformTokenGetter(
   283  		ac.PrivateKey,
   284  		ac.PublicKey,
   285  		ac.KeyPassword,
   286  		tokenURL,
   287  		aud,
   288  		ac.ClientID,
   289  		singleURL,
   290  		ac.Timeout,
   291  	)
   292  	return instance
   293  }
   294  
   295  // GetToken gets a token
   296  func (t tokenAuth) GetToken() (string, error) {
   297  	return t.tokenRequester.GetToken()
   298  }