token_holder.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. package qlight
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "encoding/json"
  6. "fmt"
  7. "strings"
  8. "sync"
  9. "time"
  10. "github.com/ethereum/go-ethereum/log"
  11. "github.com/ethereum/go-ethereum/plugin"
  12. qlightplugin "github.com/ethereum/go-ethereum/plugin/qlight"
  13. )
  14. type TokenHolder struct {
  15. token string
  16. psi string
  17. refreshAnticipation int32
  18. plugin qlightplugin.PluginTokenManager
  19. pluginManager *plugin.PluginManager
  20. peerUpdater RunningPeerAuthUpdater
  21. timer *time.Timer
  22. eta time.Time
  23. lock sync.Mutex
  24. }
  25. func NewTokenHolder(psi string, pluginManager *plugin.PluginManager) (*TokenHolder, error) {
  26. plugin, err := getPlugin(pluginManager, new(plugin.QLightTokenManagerPluginTemplate))
  27. if err != nil {
  28. return nil, fmt.Errorf("get plugin: %w", err)
  29. }
  30. var refreshAnticipation int32
  31. if plugin != nil {
  32. refreshAnticipation, err = plugin.PluginTokenManager(context.Background())
  33. if err != nil {
  34. return nil, fmt.Errorf("fetch refresh anticipation value: %w", err)
  35. }
  36. }
  37. return NewTokenHolderWithPlugin(psi, refreshAnticipation, plugin, pluginManager), nil
  38. }
  39. func getPlugin(pluginManager plugin.PluginManagerInterface, pluginTemplate plugin.QLightTokenManagerPluginTemplateInterface) (plugin qlightplugin.PluginTokenManager, err error) {
  40. pluginTemplate, err = tokenManager(pluginManager, pluginTemplate)
  41. if err != nil {
  42. return
  43. }
  44. if pluginTemplate != nil {
  45. err = pluginTemplate.Start()
  46. if err != nil {
  47. return
  48. }
  49. plugin, err = pluginTemplate.Get()
  50. }
  51. return
  52. }
  53. func NewTokenHolderWithPlugin(psi string, refreshAnticipation int32, plugin qlightplugin.PluginTokenManager, pluginManager *plugin.PluginManager) *TokenHolder {
  54. return &TokenHolder{
  55. psi: psi,
  56. plugin: plugin,
  57. pluginManager: pluginManager,
  58. refreshAnticipation: refreshAnticipation,
  59. }
  60. }
  61. func (h *TokenHolder) SetPeerUpdater(peerUpdater RunningPeerAuthUpdater) {
  62. if h == nil || peerUpdater == nil {
  63. return
  64. }
  65. h.peerUpdater = peerUpdater
  66. }
  67. func (h *TokenHolder) RefreshPlugin(pluginManager plugin.PluginManagerInterface) error {
  68. return h.refreshPlugin(pluginManager, new(plugin.QLightTokenManagerPluginTemplate))
  69. }
  70. func (h *TokenHolder) refreshPlugin(pluginManager plugin.PluginManagerInterface, template plugin.QLightTokenManagerPluginTemplateInterface) (err error) {
  71. h.plugin, err = getPlugin(pluginManager, template)
  72. if err != nil {
  73. return
  74. }
  75. var refreshAnticipation int32
  76. if h.plugin != nil {
  77. refreshAnticipation, err = h.plugin.PluginTokenManager(context.Background())
  78. if err != nil {
  79. return
  80. }
  81. }
  82. h.refreshAnticipation = refreshAnticipation
  83. err = h.updateTimer()
  84. return
  85. }
  86. func (h *TokenHolder) HttpCredentialsProvider(ctx context.Context) (string, error) {
  87. return h.CurrentToken(), nil
  88. }
  89. func (h *TokenHolder) ReloadPlugin() error {
  90. plugin, err := getPlugin(h.pluginManager, new(plugin.QLightTokenManagerPluginTemplate))
  91. if err != nil {
  92. return err
  93. }
  94. refreshAnticipation, err := plugin.PluginTokenManager(context.Background())
  95. if err != nil {
  96. return fmt.Errorf("fetch refresh anticipation value: %w", err)
  97. }
  98. h.plugin = plugin
  99. h.refreshAnticipation = refreshAnticipation
  100. return h.updateTimer()
  101. }
  102. func (h *TokenHolder) CurrentToken() string {
  103. if h == nil {
  104. log.Warn("token holder nil, returning empty token")
  105. return ""
  106. }
  107. if h.plugin == nil {
  108. log.Warn("token plugin is missing, no update possible")
  109. return h.token
  110. }
  111. expired, err := h.tokenExpired()
  112. if err != nil {
  113. log.Warn("error while checking if token is expired", "err", err)
  114. }
  115. if !expired {
  116. return h.token
  117. }
  118. h.lock.Lock()
  119. defer h.lock.Unlock()
  120. returnedToken, err := h.plugin.TokenRefresh(context.Background(), h.token, h.psi)
  121. if err != nil {
  122. log.Error("get token from plugin", "err", err)
  123. } else {
  124. if h.token != returnedToken {
  125. log.Debug("new token from plugin")
  126. err = h.peerUpdater.UpdateTokenForRunningQPeers(returnedToken)
  127. if err != nil {
  128. log.Warn("update token to QPeers", "err", err)
  129. }
  130. }
  131. h.token = returnedToken
  132. err = h.updateTimer()
  133. if err != nil {
  134. log.Warn("update token timer", "err", err)
  135. }
  136. }
  137. return h.token
  138. }
  139. // updateTimer updates the expiration timer that will trigger automatically a token refreshment
  140. func (h *TokenHolder) updateTimer() error {
  141. if h == nil && h.plugin == nil {
  142. return nil
  143. }
  144. expireIn, err := h.tokenExpirationDelay()
  145. if err != nil {
  146. return err
  147. }
  148. if h.timer != nil && time.Now().Add(expireIn).After(h.eta) {
  149. if !h.timer.Stop() {
  150. log.Debug("token updateTimer read timer.C", "expire in", expireIn)
  151. <-h.timer.C
  152. }
  153. h.timer = nil
  154. }
  155. if h.timer == nil {
  156. if expireIn <= 0 { // automatic refresh one second after if already expired
  157. expireIn = time.Second
  158. } else {
  159. expireIn = expireIn - time.Duration(h.refreshAnticipation)*time.Millisecond
  160. }
  161. h.eta = time.Now().Add(expireIn)
  162. log.Debug("token updateTimer new", "expire in", expireIn, "eta", h.eta)
  163. h.timer = time.AfterFunc(expireIn, func() {
  164. log.Debug("token updateTimer triggered", "expire in", expireIn, "eta", h.eta)
  165. h.timer = nil
  166. h.eta = time.Now()
  167. h.CurrentToken()
  168. })
  169. }
  170. return nil
  171. }
  172. type JWT struct {
  173. ExpireAt int64 `json:"exp"`
  174. }
  175. func (h *TokenHolder) tokenExpirationDelay() (time.Duration, error) {
  176. if len(h.token) == 0 {
  177. return 0, nil
  178. }
  179. token := h.token
  180. idx := strings.Index(token, " ")
  181. if idx >= 0 {
  182. token = token[idx+1:]
  183. }
  184. split := strings.Split(token, ".")
  185. if len(split) <= 1 {
  186. return 0, nil
  187. }
  188. data, err := base64.RawStdEncoding.DecodeString(split[1])
  189. if err != nil {
  190. return 0, fmt.Errorf("decode Base64: %w", err)
  191. }
  192. jwt := &JWT{}
  193. err = json.Unmarshal(data, jwt)
  194. if err != nil {
  195. return 0, fmt.Errorf("unmarshal JSON: %w", err)
  196. }
  197. expireAt := time.Unix(jwt.ExpireAt, 0)
  198. return -time.Since(expireAt), nil // transform negative value to positive as expiration date is in future and time.Since measure in the past
  199. }
  200. func (h *TokenHolder) tokenExpired() (bool, error) {
  201. expireIn, err := h.tokenExpirationDelay()
  202. if err != nil {
  203. return true, err
  204. }
  205. return expireIn < time.Duration(h.refreshAnticipation)*time.Millisecond, nil
  206. }
  207. func (h *TokenHolder) SetCurrentToken(v string) {
  208. h.token = v
  209. }
  210. func (h *TokenHolder) SetExpirationAnticipation(v int32) {
  211. h.refreshAnticipation = v
  212. }
  213. func tokenManager(pluginManager plugin.PluginManagerInterface, template plugin.QLightTokenManagerPluginTemplateInterface) (plugin.QLightTokenManagerPluginTemplateInterface, error) {
  214. name := plugin.QLightTokenManagerPluginInterfaceName
  215. if pluginManager.IsEnabled(name) {
  216. managedPlugin := template.ManagedPlugin()
  217. log.Warn("template plugin", "tmp", template, "managed", managedPlugin)
  218. err := pluginManager.GetPluginTemplate(name, managedPlugin)
  219. return template, err
  220. }
  221. log.Info("Token Manager Plugin is not enabled")
  222. return nil, nil
  223. }