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 }