@@ -16,6 +16,7 @@ import (
1616 "path"
1717 "runtime"
1818 "slices"
19+ "strings"
1920 "sync"
2021 "syscall"
2122 "text/template"
@@ -44,9 +45,15 @@ type Config struct {
4445 Templates []Template `yaml:"templates"`
4546}
4647
48+ type TemplateWithID struct {
49+ ID int
50+ Template Template
51+ }
52+
4753type InfisicalConfig struct {
48- Address string `yaml:"address"`
49- ExitAfterAuth bool `yaml:"exit-after-auth"`
54+ Address string `yaml:"address"`
55+ ExitAfterAuth bool `yaml:"exit-after-auth"`
56+ RevokeCredentialsOnShutdown bool `yaml:"revoke-credentials-on-shutdown"`
5057}
5158
5259type AuthConfig struct {
@@ -143,7 +150,7 @@ func (d *DynamicSecretLeaseManager) Append(lease DynamicSecretLease) {
143150 defer d .mutex .Unlock ()
144151
145152 index := slices .IndexFunc (d .leases , func (s DynamicSecretLease ) bool {
146- if lease .SecretPath == s .SecretPath && lease .Environment == s .Environment && lease .ProjectSlug == s .ProjectSlug && lease .Slug == s .Slug {
153+ if lease .SecretPath == s .SecretPath && lease .Environment == s .Environment && lease .ProjectSlug == s .ProjectSlug && lease .Slug == s .Slug && lease . LeaseID == s . LeaseID {
147154 return true
148155 }
149156 return false
@@ -161,7 +168,7 @@ func (d *DynamicSecretLeaseManager) RegisterTemplate(projectSlug, environment, s
161168 defer d .mutex .Unlock ()
162169
163170 index := slices .IndexFunc (d .leases , func (lease DynamicSecretLease ) bool {
164- if lease .SecretPath == secretPath && lease .Environment == environment && lease .ProjectSlug == projectSlug && lease .Slug == slug {
171+ if lease .SecretPath == secretPath && lease .Environment == environment && lease .ProjectSlug == projectSlug && lease .Slug == slug && slices . Contains ( lease . TemplateIDs , templateId ) {
165172 return true
166173 }
167174 return false
@@ -172,12 +179,12 @@ func (d *DynamicSecretLeaseManager) RegisterTemplate(projectSlug, environment, s
172179 }
173180}
174181
175- func (d * DynamicSecretLeaseManager ) GetLease (projectSlug , environment , secretPath , slug string ) * DynamicSecretLease {
182+ func (d * DynamicSecretLeaseManager ) GetLease (projectSlug , environment , secretPath , slug string , templateId int ) * DynamicSecretLease {
176183 d .mutex .Lock ()
177184 defer d .mutex .Unlock ()
178185
179186 for _ , lease := range d .leases {
180- if lease .SecretPath == secretPath && lease .Environment == environment && lease .ProjectSlug == projectSlug && lease .Slug == slug {
187+ if lease .SecretPath == secretPath && lease .Environment == environment && lease .ProjectSlug == projectSlug && lease .Slug == slug && slices . Contains ( lease . TemplateIDs , templateId ) {
181188 return & lease
182189 }
183190 }
@@ -384,7 +391,7 @@ func dynamicSecretTemplateFunction(accessToken string, dynamicSecretManager *Dyn
384391 if argLength == 5 {
385392 ttl = args [4 ]
386393 }
387- dynamicSecretData := dynamicSecretManager .GetLease (projectSlug , envSlug , secretPath , slug )
394+ dynamicSecretData := dynamicSecretManager .GetLease (projectSlug , envSlug , secretPath , slug , templateId )
388395 if dynamicSecretData != nil {
389396 dynamicSecretManager .RegisterTemplate (projectSlug , envSlug , secretPath , slug , templateId )
390397 return dynamicSecretData .Data , nil
@@ -498,16 +505,18 @@ type AgentManager struct {
498505 accessTokenRefreshedTime time.Time
499506 mutex sync.Mutex
500507 filePaths []Sink // Store file paths if needed
501- templates []Template
508+ templates []TemplateWithID
502509 dynamicSecretLeases * DynamicSecretLeaseManager
503510
504511 authConfigBytes []byte
505512 authStrategy util.AuthStrategyType
506513
507- newAccessTokenNotificationChan chan bool
508- removeUniversalAuthClientSecretOnRead bool
509- cachedUniversalAuthClientSecret string
510- exitAfterAuth bool
514+ newAccessTokenNotificationChan chan bool
515+ cachedUniversalAuthClientSecret string
516+ exitAfterAuth bool
517+ revokeCredentialsOnShutdown bool
518+
519+ isShuttingDown bool
511520
512521 infisicalClient infisicalSdk.InfisicalClientInterface
513522}
@@ -521,22 +530,30 @@ type NewAgentMangerOptions struct {
521530
522531 NewAccessTokenNotificationChan chan bool
523532 ExitAfterAuth bool
533+ RevokeCredentialsOnShutdown bool
524534}
525535
526536func NewAgentManager (options NewAgentMangerOptions ) * AgentManager {
527537 customHeaders , err := util .GetInfisicalCustomHeadersMap ()
528538 if err != nil {
529539 util .HandleError (err , "Unable to get custom headers" )
530540 }
541+
542+ templates := make ([]TemplateWithID , len (options .Templates ))
543+ for i , template := range options .Templates {
544+ templates [i ] = TemplateWithID {ID : i + 1 , Template : template }
545+ }
546+
531547 return & AgentManager {
532548 filePaths : options .FileDeposits ,
533- templates : options . Templates ,
549+ templates : templates ,
534550
535551 authConfigBytes : options .AuthConfigBytes ,
536552 authStrategy : options .AuthStrategy ,
537553
538554 newAccessTokenNotificationChan : options .NewAccessTokenNotificationChan ,
539555 exitAfterAuth : options .ExitAfterAuth ,
556+ revokeCredentialsOnShutdown : options .RevokeCredentialsOnShutdown ,
540557
541558 infisicalClient : infisicalSdk .NewInfisicalClient (context .Background (), infisicalSdk.Config {
542559 SiteUrl : config .INFISICAL_URL ,
@@ -755,6 +772,145 @@ func (tm *AgentManager) FetchNewAccessToken() error {
755772 return nil
756773}
757774
775+ func (tm * AgentManager ) RevokeCredentials () error {
776+ var token string
777+
778+ log .Info ().Msg ("revoking credentials..." )
779+
780+ token = tm .GetToken ()
781+
782+ if token == "" {
783+ return fmt .Errorf ("no access token found" )
784+ }
785+ // lock the dynamic secret leases to prevent renewals during the revoke process
786+ tm .dynamicSecretLeases .mutex .Lock ()
787+ defer tm .dynamicSecretLeases .mutex .Unlock ()
788+
789+ dynamicSecretLeases := tm .dynamicSecretLeases .leases
790+
791+ customHeaders , err := util .GetInfisicalCustomHeadersMap ()
792+ if err != nil {
793+ return fmt .Errorf ("unable to get custom headers: %v" , err )
794+ }
795+
796+ for _ , lease := range dynamicSecretLeases {
797+
798+ temporaryInfisicalClient := infisicalSdk .NewInfisicalClient (context .Background (), infisicalSdk.Config {
799+ SiteUrl : config .INFISICAL_URL ,
800+ UserAgent : api .USER_AGENT ,
801+ AutoTokenRefresh : false ,
802+ CustomHeaders : customHeaders ,
803+ })
804+
805+ temporaryInfisicalClient .Auth ().SetAccessToken (token )
806+
807+ _ , err = temporaryInfisicalClient .DynamicSecrets ().Leases ().DeleteById (infisicalSdk.DeleteDynamicSecretLeaseOptions {
808+ LeaseId : lease .LeaseID ,
809+ ProjectSlug : lease .ProjectSlug ,
810+ SecretPath : lease .SecretPath ,
811+ EnvironmentSlug : lease .Environment ,
812+ })
813+
814+ if err != nil {
815+
816+ if strings .Contains (err .Error (), "status-code=404" ) {
817+ log .Info ().Msgf ("dynamic secret lease %s not found, skipping" , lease .LeaseID )
818+ continue
819+ }
820+
821+ log .Error ().Msgf ("unable to revoke dynamic secret lease %s: %v" , lease .LeaseID , err )
822+ continue
823+ }
824+
825+ // write to the lease file, and make it an empty file
826+
827+ // find the template that this lease is associated with
828+ templateIndex := slices .IndexFunc (tm .templates , func (t TemplateWithID ) bool {
829+ for _ , templateID := range lease .TemplateIDs {
830+ if t .ID == templateID {
831+ return true
832+ }
833+ }
834+ return false
835+ })
836+
837+ if templateIndex != - 1 {
838+ template := tm .templates [templateIndex ]
839+ if _ , err := os .Stat (template .Template .DestinationPath ); ! os .IsNotExist (err ) {
840+ if err := os .WriteFile (template .Template .DestinationPath , []byte ("" ), 0644 ); err != nil {
841+ log .Warn ().Msgf ("unable to erase lease from file '%s' because %v" , template .Template .DestinationPath , err )
842+ }
843+ }
844+ }
845+
846+ log .Info ().Msgf ("successfully revoked dynamic secret lease [id=%s] [project-slug=%s]" , lease .LeaseID , lease .ProjectSlug )
847+ }
848+
849+ var deletedTokens []string
850+
851+ for _ , sink := range tm .filePaths {
852+ if sink .Type == "file" {
853+ tokenBytes , err := os .ReadFile (sink .Config .Path )
854+ if err != nil {
855+ log .Error ().Msgf ("unable to read token from file '%s' because %v" , sink .Config .Path , err )
856+ continue
857+ }
858+
859+ token := string (tokenBytes )
860+ if token != "" {
861+ log .Info ().Msgf ("revoking token from file '%s'" , sink .Config .Path )
862+
863+ temporaryInfisicalClient := infisicalSdk .NewInfisicalClient (context .Background (), infisicalSdk.Config {
864+ SiteUrl : config .INFISICAL_URL ,
865+ UserAgent : api .USER_AGENT ,
866+ AutoTokenRefresh : false ,
867+ CustomHeaders : customHeaders ,
868+ })
869+
870+ temporaryInfisicalClient .Auth ().SetAccessToken (token )
871+ err := temporaryInfisicalClient .Auth ().RevokeAccessToken ()
872+ if err != nil {
873+ log .Error ().Msgf ("unable to revoke access token from file '%s' because %v" , sink .Config .Path , err )
874+ continue
875+ }
876+
877+ if _ , err := os .Stat (sink .Config .Path ); ! os .IsNotExist (err ) {
878+ if err := os .WriteFile (sink .Config .Path , []byte ("" ), 0644 ); err != nil {
879+ log .Warn ().Msgf ("unable to erase access token from file '%s' because %v" , sink .Config .Path , err )
880+ continue
881+ }
882+ }
883+
884+ log .Info ().Msgf ("successfully revoked access token from file '%s'" , sink .Config .Path )
885+
886+ deletedTokens = append (deletedTokens , token )
887+ }
888+ }
889+ }
890+
891+ // check to see if the active token was already deleted, if not, delete it
892+ if ! slices .Contains (deletedTokens , token ) {
893+ temporaryInfisicalClient := infisicalSdk .NewInfisicalClient (context .Background (), infisicalSdk.Config {
894+ SiteUrl : config .INFISICAL_URL ,
895+ UserAgent : api .USER_AGENT ,
896+ AutoTokenRefresh : false ,
897+ CustomHeaders : customHeaders ,
898+ })
899+ temporaryInfisicalClient .Auth ().SetAccessToken (token )
900+ err := temporaryInfisicalClient .Auth ().RevokeAccessToken ()
901+ if err != nil {
902+ log .Error ().Msgf ("unable to revoke token because %v" , err )
903+ }
904+
905+ log .Info ().Msgf ("successfully revoked active access token" )
906+ deletedTokens = append (deletedTokens , token )
907+ }
908+
909+ log .Info ().Msgf ("successfully revoked %d access tokens" , len (deletedTokens ))
910+
911+ return nil
912+ }
913+
758914// Refreshes the existing access token
759915func (tm * AgentManager ) RefreshAccessToken (accessToken string ) error {
760916 httpClient , err := util .GetRestyClientWithCustomHeaders ()
@@ -782,6 +938,11 @@ func (tm *AgentManager) RefreshAccessToken(accessToken string) error {
782938
783939func (tm * AgentManager ) ManageTokenLifecycle () {
784940 for {
941+
942+ if tm .isShuttingDown {
943+ return
944+ }
945+
785946 accessTokenMaxTTLExpiresInTime := tm .accessTokenFetchedTime .Add (tm .accessTokenMaxTTL - (5 * time .Second ))
786947 accessTokenRefreshedTime := tm .accessTokenRefreshedTime
787948
@@ -945,6 +1106,10 @@ func (tm *AgentManager) MonitorSecretChanges(secretTemplate Template, templateId
9451106 return
9461107 default :
9471108 {
1109+ if tm .isShuttingDown {
1110+ return
1111+ }
1112+
9481113 tm .dynamicSecretLeases .Prune ()
9491114 token := tm .GetToken ()
9501115 if token != "" {
@@ -1068,7 +1233,7 @@ var agentCmd = &cobra.Command{
10681233
10691234 tokenRefreshNotifier := make (chan bool )
10701235 sigChan := make (chan os.Signal , 1 )
1071- signal .Notify (sigChan , syscall .SIGINT , syscall .SIGTERM )
1236+ signal .Notify (sigChan , syscall .SIGINT , syscall .SIGTERM , syscall . SIGQUIT )
10721237
10731238 filePaths := agentConfig .Sinks
10741239
@@ -1085,25 +1250,51 @@ var agentCmd = &cobra.Command{
10851250 NewAccessTokenNotificationChan : tokenRefreshNotifier ,
10861251 ExitAfterAuth : agentConfig .Infisical .ExitAfterAuth ,
10871252 AuthStrategy : authStrategy ,
1253+ RevokeCredentialsOnShutdown : agentConfig .Infisical .RevokeCredentialsOnShutdown ,
10881254 })
10891255
10901256 tm .dynamicSecretLeases = NewDynamicSecretLeaseManager (sigChan )
10911257
10921258 go tm .ManageTokenLifecycle ()
10931259
1094- for i , template := range agentConfig . Templates {
1095- log .Info ().Msgf ("template engine started for template %v..." , i + 1 )
1096- go tm .MonitorSecretChanges (template , i , sigChan )
1260+ for _ , template := range tm . templates {
1261+ log .Info ().Msgf ("template engine started for template %v..." , template . ID )
1262+ go tm .MonitorSecretChanges (template . Template , template . ID , sigChan )
10971263 }
10981264
10991265 for {
11001266 select {
11011267 case <- tokenRefreshNotifier :
11021268 go tm .WriteTokenToFiles ()
11031269 case <- sigChan :
1104- log .Info ().Msg ("agent is gracefully shutting..." )
1105- // TODO: check if we are in the middle of writing files to disk
1106- os .Exit (1 )
1270+ tm .isShuttingDown = true
1271+ log .Info ().Msg ("agent is gracefully shutting down..." )
1272+
1273+ exitCode := 0
1274+
1275+ if ! tm .exitAfterAuth && tm .revokeCredentialsOnShutdown {
1276+
1277+ done := make (chan error , 1 )
1278+
1279+ go func () {
1280+ done <- tm .RevokeCredentials ()
1281+ }()
1282+
1283+ select {
1284+ case err := <- done :
1285+ if err != nil {
1286+ log .Error ().Msgf ("unable to revoke credentials [err=%v]" , err )
1287+ exitCode = 1
1288+ }
1289+ // 5 minute timeout to prevent any hanging edge cases
1290+ case <- time .After (5 * time .Minute ):
1291+ log .Warn ().Msg ("credential revocation timed out after 5 minutes, forcing exit" )
1292+ exitCode = 1
1293+ }
1294+
1295+ }
1296+
1297+ os .Exit (exitCode )
11071298 }
11081299 }
11091300
0 commit comments