-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathcleanup.go
More file actions
155 lines (136 loc) · 3.74 KB
/
Copy pathcleanup.go
File metadata and controls
155 lines (136 loc) · 3.74 KB
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
package main
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/thand-io/agent/cmd/elevate/domain"
"github.com/thand-io/agent/cmd/elevate/handler"
)
// CleanupRunner revokes and removes expired grants on startup and periodically.
type CleanupRunner struct {
store handler.StateStore
grants handler.GrantEngine
clock handler.Clock
interval time.Duration
retention time.Duration
logger *slog.Logger
}
// NewCleanupRunner builds a cleanup runner that sweeps expired grants.
func NewCleanupRunner(store handler.StateStore, grants handler.GrantEngine, clock handler.Clock, interval time.Duration, retention time.Duration, logger *slog.Logger) (*CleanupRunner, error) {
if store == nil {
return nil, fmt.Errorf("state store is required")
}
if grants == nil {
return nil, fmt.Errorf("grant engine is required")
}
if clock == nil {
return nil, fmt.Errorf("clock is required")
}
if interval <= 0 {
return nil, fmt.Errorf("cleanup interval must be > 0")
}
if retention <= 0 {
return nil, fmt.Errorf("state retention must be > 0")
}
if logger == nil {
logger = slog.Default()
}
return &CleanupRunner{
store: store,
grants: grants,
clock: clock,
interval: interval,
retention: retention,
logger: logger,
}, nil
}
// Run executes one startup sweep and then continues periodic sweeps until context cancellation.
func (c *CleanupRunner) Run(ctx context.Context) error {
if err := c.runOnce(ctx); err != nil {
if isCleanupShutdownError(ctx, err) {
return nil
}
return err
}
ticker := time.NewTicker(c.interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil
case <-ticker.C:
if err := c.runOnce(ctx); err != nil {
if isCleanupShutdownError(ctx, err) {
return nil
}
return err
}
}
}
}
func (c *CleanupRunner) runOnce(ctx context.Context) error {
grants, err := c.store.List(ctx)
if err != nil {
return fmt.Errorf("list state grants: %w", err)
}
nowMono := c.clock.NowMonoNS()
nowWall := c.clock.NowWallUTC()
for _, g := range grants {
if isCompleted(g) {
if !isRetentionExpired(g, nowWall, c.retention) {
continue
}
if err := c.store.Delete(ctx, g.RequestID); err != nil {
return fmt.Errorf("delete retained grant %q: %w", g.RequestID, err)
}
continue
}
if !isExpired(g, nowMono, nowWall) {
continue
}
if !g.WasAlreadyPrivileged {
if err := c.grants.Revoke(ctx, domain.RevokeRequest{
RequestID: g.RequestID,
WorkflowID: g.WorkflowID,
Username: g.Username,
}); err != nil {
return fmt.Errorf("revoke expired grant %q: %w", g.RequestID, err)
}
c.logger.Info("admin revoked by cleanup",
"component", "elevate_cleanup",
"request_id", g.RequestID,
"workflow_id", g.WorkflowID,
"username", g.Username,
"reason", "expired",
)
}
g.CompletedAtWallUTC = nowWall
if err := c.store.Put(ctx, g); err != nil {
return fmt.Errorf("persist completed grant %q: %w", g.RequestID, err)
}
}
return nil
}
func isCompleted(grant domain.GrantState) bool {
return !grant.CompletedAtWallUTC.IsZero()
}
func isRetentionExpired(grant domain.GrantState, nowWallUTC time.Time, retention time.Duration) bool {
if grant.CompletedAtWallUTC.IsZero() || nowWallUTC.IsZero() || retention <= 0 {
return false
}
return !grant.CompletedAtWallUTC.Add(retention).After(nowWallUTC)
}
func isExpired(grant domain.GrantState, nowMonoNS int64, nowWallUTC time.Time) bool {
return domain.IsExpiredGrantState(grant, nowMonoNS, nowWallUTC)
}
func isCleanupShutdownError(ctx context.Context, err error) bool {
if err == nil {
return false
}
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return true
}
return ctx.Err() != nil && errors.Is(err, ctx.Err())
}