Overview
Agent tokens are JWT tokens specifically designed for Garnet runtime security agents. They provide authentication for agents to report events, send heartbeats, and create profiles. Agent tokens are limited in scope and can only access agent-specific operations.
Agent tokens are automatically generated when you create a new agent through the API.
How It Works
Agent authentication uses a custom header-based approach:
Agent token is created when a new agent is registered
Token is stored securely by the agent
Agent includes token in X-Agent-Token header for each request
API validates token and extracts agent ID for authorization
Configuration
Creating an Agent
Agent tokens are generated during agent creation:
import (
" context "
" github.com/garnet-org/api/client "
" github.com/garnet-org/api/types "
)
func createAgent ( userClient * client . Client ) ( string , error ) {
createReq := types . CreateAgent {
Name : "production-agent-01" ,
OS : "linux" ,
Arch : "amd64" ,
Version : "1.0.0" ,
Kind : types . AgentKindKubernetes ,
IP : "10.0.1.42" ,
Labels : map [ string ] string {
"environment" : "production" ,
"region" : "us-west-2" ,
},
}
var resp types . AgentCreated
err := userClient . Post ( context . Background (), & resp , "/api/v1/agents" , createReq )
if err != nil {
return "" , err
}
// resp.Token contains the agent token
return resp . Token , nil
}
The agent token is only returned once during creation. Store it securely immediately.
Configuring Client with Agent Token
// WithAgentToken configures the client to use an agent token for authentication.
func ( c * Client ) WithAgentToken ( token string ) * Client {
client := c . Clone ()
client . AuthToken = token
client . TokenType = TokenTypeAgent
return client
}
// Create client with agent token
agentToken := os . Getenv ( "GARNET_AGENT_TOKEN" )
agentClient := client . New ( "" , "" ). WithAgentToken ( agentToken )
Agent tokens are sent in the X-Agent-Token header:
X-Agent-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
The SDK automatically sets this header:
switch c . TokenType {
case TokenTypeAgent :
req . Header . Set ( "X-Agent-Token" , c . AuthToken )
}
Agent-Specific Operations
Agent tokens have restricted access to specific endpoints:
Send Heartbeat
Agents should regularly send heartbeat signals to update their last_seen timestamp:
func sendHeartbeat ( agentClient * client . Client ) error {
var resp map [ string ] string
err := agentClient . Post (
context . Background (),
& resp ,
"/api/v1/agent_heartbeat" ,
nil ,
)
return err
}
// Run heartbeat every 30 seconds
ticker := time . NewTicker ( 30 * time . Second )
for range ticker . C {
if err := sendHeartbeat ( agentClient ); err != nil {
log . Printf ( "Heartbeat failed: %v " , err )
}
}
Ingest Events
Agents report security events using the v2 ashkaal format:
import " github.com/garnet-org/api/types "
func reportEvent ( agentClient * client . Client , event types . CreateOrUpdateEventV2 ) error {
var resp types . EventV2CreatedOrUpdated
err := agentClient . Put (
context . Background (),
& resp ,
"/api/v1/events_v2" ,
event ,
)
if err != nil {
return fmt . Errorf ( "failed to ingest event: %w " , err )
}
log . Printf ( "Event ingested: %s (status: %d )" , resp . ID , resp . Status )
return nil
}
The agent ID is automatically extracted from the token. You don’t need to include it in the request.
Create Profiles
Agents can create or update ashkaal profiles:
func createProfile ( agentClient * client . Client , profile types . CreateProfile ) error {
var resp types . CreatedProfile
err := agentClient . Post (
context . Background (),
& resp ,
"/api/v1/profiles" ,
profile ,
)
return err
}
Authorization Scope
Allowed Operations
Agent tokens can only access:
POST /api/v1/agent_heartbeat - Send heartbeat
PUT /api/v1/events_v2 - Ingest events
POST /api/v1/profiles - Create/update profiles
Restricted Operations
Agent tokens cannot access:
User information endpoints
Agent management (create, update, delete agents)
Issue management
Token management
Network policy endpoints
Project settings
Attempting to access unauthorized endpoints with an agent token returns 401 Unauthorized.
Security Best Practices
Secure Token Storage
Store agent tokens securely on the agent host: // Store in encrypted configuration file
// Or use secret management systems (HashiCorp Vault, AWS Secrets Manager)
token := loadTokenFromSecureStorage ()
Limit Token Exposure
Never log agent tokens
Don’t include tokens in error messages
Avoid passing tokens as command-line arguments
// ❌ BAD: Token in logs
log . Printf ( "Using token: %s " , token )
// ✅ GOOD: No token exposure
log . Printf ( "Agent authenticated successfully" )
Rotate Tokens Regularly
Implement token rotation by:
Creating a new agent
Migrating to new agent token
Deleting old agent
Monitor Agent Activity
Track agent heartbeats and event submissions to detect compromised tokens: if time . Since ( lastHeartbeat ) > 5 * time . Minute {
log . Warn ( "Agent heartbeat missing - possible token issue" )
}
Use TLS
Always communicate over HTTPS to prevent token interception: client := client . New ( "https://api.garnet.ai" , "" )
agentClient := client . WithAgentToken ( token )
Error Handling
Authentication Errors
err := agentClient . Post ( ctx , & resp , "/api/v1/agent_heartbeat" , nil )
if err != nil {
if strings . Contains ( err . Error (), "401" ) {
// Token is invalid or expired
log . Fatal ( "Agent authentication failed: invalid token" )
}
if strings . Contains ( err . Error (), "404" ) {
// Agent not found (possibly deleted)
log . Fatal ( "Agent not found - may need re-registration" )
}
}
Common Issues
Error Code Cause Solution 401 Invalid or expired token Verify token is correct and agent exists 401 Wrong endpoint Agent tokens can only access agent-specific endpoints 404 Agent not found Agent may have been deleted; create new agent 400 Invalid event format Validate event payload matches ashkaal format
Complete Agent Example
package main
import (
" context "
" log "
" os "
" time "
" github.com/garnet-org/api/client "
" github.com/garnet-org/api/types "
)
type Agent struct {
client * client . Client
}
func NewAgent ( token string ) * Agent {
c := client . New ( "https://api.garnet.ai" , "" )
return & Agent {
client : c . WithAgentToken ( token ),
}
}
func ( a * Agent ) Start ( ctx context . Context ) error {
// Send initial heartbeat
if err := a . sendHeartbeat ( ctx ); err != nil {
return fmt . Errorf ( "initial heartbeat failed: %w " , err )
}
// Start heartbeat ticker
ticker := time . NewTicker ( 30 * time . Second )
defer ticker . Stop ()
for {
select {
case <- ctx . Done ():
return ctx . Err ()
case <- ticker . C :
if err := a . sendHeartbeat ( ctx ); err != nil {
log . Printf ( "Heartbeat failed: %v " , err )
}
}
}
}
func ( a * Agent ) sendHeartbeat ( ctx context . Context ) error {
var resp map [ string ] string
return a . client . Post ( ctx , & resp , "/api/v1/agent_heartbeat" , nil )
}
func ( a * Agent ) ReportEvent ( ctx context . Context , event types . CreateOrUpdateEventV2 ) error {
var resp types . EventV2CreatedOrUpdated
return a . client . Put ( ctx , & resp , "/api/v1/events_v2" , event )
}
func main () {
token := os . Getenv ( "GARNET_AGENT_TOKEN" )
if token == "" {
log . Fatal ( "GARNET_AGENT_TOKEN not set" )
}
agent := NewAgent ( token )
ctx := context . Background ()
if err := agent . Start ( ctx ); err != nil {
log . Fatalf ( "Agent failed: %v " , err )
}
}
Testing Agent Authentication
Test agent authentication in development:
func TestAgentAuth ( t * testing . T ) {
// Create test agent with user token
userClient := client . New ( "https://staging-api.garnet.ai" , userToken )
createReq := types . CreateAgent {
Name : "test-agent" ,
OS : "linux" ,
Arch : "amd64" ,
Version : "1.0.0" ,
Kind : types . AgentKindKubernetes ,
}
var agentResp types . AgentCreated
err := userClient . Post ( context . Background (), & agentResp , "/api/v1/agents" , createReq )
require . NoError ( t , err )
// Test agent token
agentClient := userClient . WithAgentToken ( agentResp . Token )
var heartbeat map [ string ] string
err = agentClient . Post ( context . Background (), & heartbeat , "/api/v1/agent_heartbeat" , nil )
require . NoError ( t , err )
require . Equal ( t , "ok" , heartbeat [ "status" ])
}
Next Steps
Project Tokens Learn about programmatic access tokens
User Tokens Understand user authentication