66 "encoding/json"
77 "fmt"
88 "io"
9+ "math"
910 "net/http"
1011 "strings"
1112 "time"
@@ -19,6 +20,7 @@ type HTTPClientConfig struct {
1920 Token string
2021 Namespace string
2122 Client * http.Client
23+ RenewRetry resilience.RetryConfig
2224 RevokeRetry resilience.RetryConfig
2325}
2426
@@ -27,6 +29,7 @@ type HTTPClient struct {
2729 token string
2830 namespace string
2931 client * http.Client
32+ renewRetry resilience.RetryConfig
3033 revokeRetry resilience.RetryConfig
3134}
3235
@@ -45,11 +48,22 @@ func NewHTTPClient(cfg HTTPClientConfig) *HTTPClient {
4548 if revokeRetry .MaxDelay == 0 {
4649 revokeRetry .MaxDelay = time .Second
4750 }
51+ renewRetry := cfg .RenewRetry
52+ if renewRetry .MaxAttempts == 0 {
53+ renewRetry .MaxAttempts = 3
54+ }
55+ if renewRetry .InitialDelay == 0 {
56+ renewRetry .InitialDelay = 100 * time .Millisecond
57+ }
58+ if renewRetry .MaxDelay == 0 {
59+ renewRetry .MaxDelay = time .Second
60+ }
4861 return & HTTPClient {
4962 baseURL : strings .TrimRight (cfg .BaseURL , "/" ),
5063 token : cfg .Token ,
5164 namespace : cfg .Namespace ,
5265 client : client ,
66+ renewRetry : renewRetry ,
5367 revokeRetry : revokeRetry ,
5468 }
5569}
@@ -79,6 +93,7 @@ func (c *HTTPClient) GenerateCredentials(ctx context.Context, role string) (*Lea
7993 var payload struct {
8094 LeaseID string `json:"lease_id"`
8195 LeaseDuration int64 `json:"lease_duration"`
96+ Renewable bool `json:"renewable"`
8297 Data struct {
8398 Username string `json:"username"`
8499 Password string `json:"password"`
@@ -92,15 +107,71 @@ func (c *HTTPClient) GenerateCredentials(ctx context.Context, role string) (*Lea
92107 Password : payload .Data .Password ,
93108 LeaseID : payload .LeaseID ,
94109 LeaseDuration : time .Duration (payload .LeaseDuration ) * time .Second ,
110+ Renewable : payload .Renewable ,
95111 }, nil
96112}
97113
114+ func (c * HTTPClient ) RenewLease (ctx context.Context , leaseID string , increment time.Duration ) (* LeaseCredentials , error ) {
115+ return resilience .RetryValue (ctx , c .renewRetry , func (ctx context.Context ) (* LeaseCredentials , error ) {
116+ return c .renewLeaseOnce (ctx , leaseID , increment )
117+ })
118+ }
119+
98120func (c * HTTPClient ) RevokeLease (ctx context.Context , leaseID string ) error {
99121 return resilience .Retry (ctx , c .revokeRetry , func (ctx context.Context ) error {
100122 return c .revokeLeaseOnce (ctx , leaseID )
101123 })
102124}
103125
126+ func (c * HTTPClient ) renewLeaseOnce (ctx context.Context , leaseID string , increment time.Duration ) (* LeaseCredentials , error ) {
127+ payload , err := json .Marshal (map [string ]any {
128+ "lease_id" : leaseID ,
129+ "increment" : int (math .Ceil (increment .Seconds ())),
130+ })
131+ if err != nil {
132+ return nil , resilience .Permanent (err )
133+ }
134+ request , err := http .NewRequestWithContext (ctx , http .MethodPost , c .baseURL + "/v1/sys/leases/renew" , bytes .NewReader (payload ))
135+ if err != nil {
136+ return nil , resilience .Permanent (err )
137+ }
138+ c .applyHeaders (request )
139+ request .Header .Set ("Content-Type" , "application/json" )
140+
141+ response , err := c .client .Do (request )
142+ if err != nil {
143+ return nil , err
144+ }
145+ defer func () {
146+ _ = response .Body .Close ()
147+ }()
148+ body , err := io .ReadAll (response .Body )
149+ if err != nil {
150+ return nil , err
151+ }
152+ if response .StatusCode >= 400 {
153+ wrapped := fmt .Errorf ("%w: vault renew lease returned %d: %s" , core .ErrForbidden , response .StatusCode , string (body ))
154+ if response .StatusCode == http .StatusTooManyRequests || response .StatusCode >= http .StatusInternalServerError {
155+ return nil , wrapped
156+ }
157+ return nil , resilience .Permanent (wrapped )
158+ }
159+
160+ var renewed struct {
161+ LeaseID string `json:"lease_id"`
162+ LeaseDuration int64 `json:"lease_duration"`
163+ Renewable bool `json:"renewable"`
164+ }
165+ if err := json .Unmarshal (body , & renewed ); err != nil {
166+ return nil , resilience .Permanent (err )
167+ }
168+ return & LeaseCredentials {
169+ LeaseID : renewed .LeaseID ,
170+ LeaseDuration : time .Duration (renewed .LeaseDuration ) * time .Second ,
171+ Renewable : renewed .Renewable ,
172+ }, nil
173+ }
174+
104175func (c * HTTPClient ) revokeLeaseOnce (ctx context.Context , leaseID string ) error {
105176 body , err := json .Marshal (map [string ]string {"lease_id" : leaseID })
106177 if err != nil {
0 commit comments