Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions packages/agentproxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"

"github.com/Infisical/infisical-merge/packages/api"
"github.com/go-resty/resty/v2"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
Expand Down Expand Up @@ -56,6 +59,9 @@ const (
maxConcurrentConns = 512

leaseRevokeShutdownTimeout = 5 * time.Second

// usageReportTimeout bounds each best-effort "last used" report to the backend.
usageReportTimeout = 5 * time.Second
)

var errHostBlocked = errors.New("host blocked by policy")
Expand Down Expand Up @@ -84,6 +90,11 @@ type proxyServer struct {
cache *agentCache
leases *leaseStore
transport http.RoundTripper

// usage holds the set of proxied-service IDs brokered since the last flush (deduped per tick).
usageMu sync.Mutex
usage map[string]struct{}
usageFlushing atomic.Bool
}

func newProxyServer(opts Options) *proxyServer {
Expand All @@ -94,6 +105,40 @@ func newProxyServer(opts Options) *proxyServer {
cache: newAgentCache(opts.ProxyToken, leases),
leases: leases,
transport: newUpstreamTransport(),
usage: make(map[string]struct{}),
}
}

func (ps *proxyServer) recordUsage(serviceID string) {
ps.usageMu.Lock()
if ps.usage == nil {
ps.usage = make(map[string]struct{})
}
ps.usage[serviceID] = struct{}{}
ps.usageMu.Unlock()
}

func (ps *proxyServer) flushUsage() {
if !ps.usageFlushing.CompareAndSwap(false, true) {
return
}
defer ps.usageFlushing.Store(false)

ps.usageMu.Lock()
if len(ps.usage) == 0 {
ps.usageMu.Unlock()
return
}
snapshot := ps.usage
ps.usage = make(map[string]struct{})
ps.usageMu.Unlock()

client := resty.New().SetAuthToken(ps.opts.ProxyToken()).SetTimeout(usageReportTimeout)
for serviceID := range snapshot {
if err := api.CallReportProxiedServiceUsage(client, serviceID); err != nil {
log.Debug().Err(err).Msg("failed to report proxied service usage; dropping batch")
return
}
}
}

Expand Down Expand Up @@ -185,6 +230,7 @@ func (ps *proxyServer) pollLoop() {
defer ticker.Stop()
for range ticker.C {
ps.cache.refreshActive()
go ps.flushUsage()
}
}

Expand Down Expand Up @@ -338,6 +384,7 @@ func (ps *proxyServer) forwardHTTP(w http.ResponseWriter, r *http.Request, schem
decision, status = decisionError, http.StatusBadGateway
case outcome.service != nil:
decision, status = decisionBrokered, resp.StatusCode
ps.recordUsage(outcome.service.id)
default:
decision, status = decisionPassthrough, resp.StatusCode
}
Expand Down
15 changes: 15 additions & 0 deletions packages/api/agent_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,18 @@ func CallListProxiedServices(httpClient *resty.Client, request ListProxiedServic
}
return res, nil
}

func CallReportProxiedServiceUsage(httpClient *resty.Client, serviceID string) error {
response, err := httpClient.
R().
SetHeader("User-Agent", USER_AGENT).
Post(fmt.Sprintf("%v/v1/proxied-services/%s/report-usage", config.INFISICAL_URL, serviceID))

if err != nil {
return NewGenericRequestError("CallReportProxiedServiceUsage", err)
}
if response.IsError() {
return NewAPIErrorWithResponse("CallReportProxiedServiceUsage", response, nil)
}
return nil
}
Loading