-
Notifications
You must be signed in to change notification settings - Fork 193
/
tokenmanager.go
172 lines (151 loc) · 4.8 KB
/
tokenmanager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package tokenmanager
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
log "github.com/F5Networks/k8s-bigip-ctlr/v3/pkg/vlogger"
"io"
"net/http"
"os"
"sync"
"time"
)
const (
//CM login url
CMLoginURL = "/api/login"
CMAccessTokenExpiration = 5 * time.Minute
)
// TokenManager is responsible for managing the authentication token.
type TokenManager struct {
mu sync.Mutex
token string
ServerURL string
credentials Credentials
SslInsecure bool
TrustedCerts string
}
// Credentials represent the username and password used for authentication.
type Credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
// TokenResponse represents the response received from the CM.
type TokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
UserID string `json:"user_id"`
}
// NewTokenManager creates a new instance of TokenManager.
func NewTokenManager(serverURL string, credentials Credentials, trustedCerts string, sslInsecure bool) *TokenManager {
return &TokenManager{
ServerURL: serverURL,
credentials: credentials,
TrustedCerts: trustedCerts,
SslInsecure: sslInsecure,
}
}
// GetToken returns the current valid saved token.
func (tm *TokenManager) GetToken() string {
tm.mu.Lock()
token := tm.token
tm.mu.Unlock()
return token
}
// FetchToken retrieves a new token from the CM.
func (tm *TokenManager) FetchToken() error {
// Prepare the request payload
payload, err := json.Marshal(tm.credentials)
if err != nil {
return err
}
// Configure CA certificates
rootCAs, _ := x509.SystemCertPool()
if rootCAs == nil {
rootCAs = x509.NewCertPool()
}
certs := []byte(tm.TrustedCerts)
// Append our certs to the system pool
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
log.Debug("[Token Manager] No certs appended, using only system certs")
}
// Create an insecure/secure client based on the SslInsecure flag
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: tm.SslInsecure,
RootCAs: rootCAs,
},
}
client := &http.Client{Transport: tr}
// Send POST request for token
resp, err := client.Post(tm.ServerURL+CMLoginURL, "application/json", bytes.NewBuffer(payload))
if err != nil {
log.Errorf("[Token Manager] Unable to establish connection with Central Manager, Probable reasons might be: invalid custom-certs (or) custom-certs not provided using --trusted-certs-cfgmap flag")
os.Exit(1)
}
defer resp.Body.Close()
// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
// Check for successful response
if resp.StatusCode != http.StatusOK {
switch resp.StatusCode {
case http.StatusUnauthorized:
log.Errorf("[Token Manager] Unauthorized to fetch token from Central Manager. "+
"Please check the credentials, status code: %d, response: %s", resp.StatusCode, body)
os.Exit(1)
case http.StatusServiceUnavailable:
return fmt.Errorf("[Token Manager] failed to get token due to service unavailability, "+
"status code: %d, response: %s", resp.StatusCode, body)
case http.StatusNotFound, http.StatusMovedPermanently:
log.Infof("[Token Manager] requested page/api not found, status code: %d, response: %s", resp.StatusCode, body)
os.Exit(1)
default:
return fmt.Errorf("failed to get token, status code: %d, response: %s", resp.StatusCode, body)
}
}
// Parse the token and its expiration time from the response
tokenResponse := TokenResponse{}
err = json.Unmarshal(body, &tokenResponse)
if err != nil {
return err
}
// Keep the token updated in the TokenManager
tm.mu.Lock()
tm.token = tokenResponse.AccessToken
tm.mu.Unlock()
return nil
}
// SyncToken maintains valid token. It fetches a new token before expiry.
func (tm *TokenManager) SyncToken(stopCh chan struct{}) {
// retryInterval is the time to wait before retrying to fetch token on the event of failure
retryInterval := time.Duration(10)
// Set ticker to 1 minute less than token expiry time to ensure token is refreshed on time
tokenUpdateTicker := time.Tick(CMAccessTokenExpiration - 1*time.Minute)
for {
select {
case <-tokenUpdateTicker:
tm.syncTokenHelper(retryInterval)
case <-stopCh:
log.Debug("[Token Manager] Stopping synchronizing token")
return
}
}
}
// syncTokenHelper is a helper function to fetch token and retry on failure
func (tm *TokenManager) syncTokenHelper(retryInterval time.Duration) {
for {
time.Sleep(retryInterval * time.Second)
err := tm.FetchToken()
if err != nil {
log.Errorf("[Token Manager] Error fetching token from Central Manager: %s", err)
log.Debugf("[Token Manager] Retrying to fetch token in %d seconds", retryInterval)
} else {
log.Debugf("[Token Manager] Successfully fetched token from Central Manager")
break
}
}
}