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}
@@ -65,7 +79,9 @@ func (c *HTTPClient) GenerateCredentials(ctx context.Context, role string) (*Lea
6579 if err != nil {
6680 return nil , err
6781 }
68- defer response .Body .Close ()
82+ defer func () {
83+ _ = response .Body .Close ()
84+ }()
6985 body , err := io .ReadAll (response .Body )
7086 if err != nil {
7187 return nil , err
@@ -77,6 +93,7 @@ func (c *HTTPClient) GenerateCredentials(ctx context.Context, role string) (*Lea
7793 var payload struct {
7894 LeaseID string `json:"lease_id"`
7995 LeaseDuration int64 `json:"lease_duration"`
96+ Renewable bool `json:"renewable"`
8097 Data struct {
8198 Username string `json:"username"`
8299 Password string `json:"password"`
@@ -90,15 +107,71 @@ func (c *HTTPClient) GenerateCredentials(ctx context.Context, role string) (*Lea
90107 Password : payload .Data .Password ,
91108 LeaseID : payload .LeaseID ,
92109 LeaseDuration : time .Duration (payload .LeaseDuration ) * time .Second ,
110+ Renewable : payload .Renewable ,
93111 }, nil
94112}
95113
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+
96120func (c * HTTPClient ) RevokeLease (ctx context.Context , leaseID string ) error {
97121 return resilience .Retry (ctx , c .revokeRetry , func (ctx context.Context ) error {
98122 return c .revokeLeaseOnce (ctx , leaseID )
99123 })
100124}
101125
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+
102175func (c * HTTPClient ) revokeLeaseOnce (ctx context.Context , leaseID string ) error {
103176 body , err := json .Marshal (map [string ]string {"lease_id" : leaseID })
104177 if err != nil {
@@ -115,7 +188,9 @@ func (c *HTTPClient) revokeLeaseOnce(ctx context.Context, leaseID string) error
115188 if err != nil {
116189 return err
117190 }
118- defer response .Body .Close ()
191+ defer func () {
192+ _ = response .Body .Close ()
193+ }()
119194 payload , err := io .ReadAll (response .Body )
120195 if err != nil {
121196 return err
0 commit comments