Documentation Index Fetch the complete documentation index at: https://mintlify.com/bgdnvk/clanker/llms.txt
Use this file to discover all available pages before exploring further.
Clanker provides a comprehensive IAM security agent that analyzes AWS Identity and Access Management configurations, identifies security vulnerabilities, and generates automated remediation plans. The IAM integration includes security analysis, policy review, and credential auditing capabilities.
Architecture overview
The IAM agent consists of three main components:
IAM Client (/internal/iam/client.go) - AWS SDK wrapper for IAM operations
Analyzer SubAgent (/internal/iam/analyzer/) - Security finding detection
Fixer SubAgent (/internal/iam/fixer/) - Automated remediation planning
type Agent struct {
client * Client
analyzer * analyzer . SubAgent
fixer * fixer . SubAgent
conversation * ConversationHistory
debug bool
}
Configuration
The IAM agent uses AWS profiles and supports multiple accounts:
aws :
default_profile : "default"
profiles :
- name : "production"
region : "us-east-1"
- name : "development"
region : "us-west-2"
Client initialization
Create an IAM agent with profile and region:
func NewAgentWithOptions ( opts AgentOptions ) ( * Agent , error ) {
client , err := NewClient ( opts . Profile , opts . Region , opts . Debug )
if err != nil {
return nil , fmt . Errorf ( "failed to create IAM client: %w " , err )
}
accountID := client . GetAccountID ()
conversation := NewConversationHistory ( accountID )
analyzerClient := & analyzerClientAdapter { client : client }
fixerClient := & fixerClientAdapter { client : client }
return & Agent {
client : client ,
analyzer : analyzer . NewSubAgent ( analyzerClient , opts . Debug ),
fixer : fixer . NewSubAgent ( fixerClient , opts . Debug ),
conversation : conversation ,
debug : opts . Debug ,
}, nil
}
The client automatically retrieves the AWS account ID:
func NewClient ( profile , region string , debug bool ) ( * Client , error ) {
opts := [] func ( * config . LoadOptions ) error {}
if profile != "" {
opts = append ( opts , config . WithSharedConfigProfile ( profile ))
}
if region != "" {
opts = append ( opts , config . WithRegion ( region ))
}
cfg , err := config . LoadDefaultConfig ( ctx , opts ... )
if err != nil {
return nil , fmt . Errorf ( "failed to load AWS config: %w " , err )
}
client := & Client {
iam : iam . NewFromConfig ( cfg ),
sts : sts . NewFromConfig ( cfg ),
profile : profile ,
region : region ,
}
// Get account ID
callerIdentity , err := client . sts . GetCallerIdentity ( ctx , & sts . GetCallerIdentityInput {})
if err == nil && callerIdentity . Account != nil {
client . accountID = * callerIdentity . Account
}
return client , nil
}
Security analysis
Analyze account
Perform comprehensive security analysis across all IAM resources:
clanker ask --iam "analyze my AWS account for security issues"
The analyzer performs multiple checks:
func ( a * SubAgent ) AnalyzeAccount ( ctx context . Context ) ([] SecurityFinding , error ) {
var findings [] SecurityFinding
// Analyze all roles
roleFindings , err := a . analyzeAllRoles ( ctx )
findings = append ( findings , roleFindings ... )
// Analyze all policies
policyFindings , err := a . analyzeAllPolicies ( ctx )
findings = append ( findings , policyFindings ... )
// Analyze credential report
credentialFindings , err := a . analyzeCredentials ( ctx )
findings = append ( findings , credentialFindings ... )
return findings , nil
}
Analyze specific role
Focus analysis on a single IAM role:
clanker ask --iam --role-arn "arn:aws:iam::123456789012:role/MyRole" "analyze this role"
Implementation:
func ( a * SubAgent ) AnalyzeRole ( ctx context . Context , roleName string ) ([] SecurityFinding , error ) {
var findings [] SecurityFinding
detail , err := a . client . GetRoleDetails ( ctx , roleName )
if err != nil {
return nil , fmt . Errorf ( "failed to get role details: %w " , err )
}
// Analyze trust policy
trustFindings := AnalyzeTrustPolicy ( detail . RoleName , detail . AssumeRolePolicyDocument )
findings = append ( findings , trustFindings ... )
// Analyze attached policies
for _ , policy := range detail . AttachedPolicies {
policyDetail , err := a . client . GetPolicyDocument ( ctx , policy . PolicyARN )
if err != nil {
continue
}
policyFindings := AnalyzePermissions ( policy . PolicyARN , policyDetail . PolicyDocument )
findings = append ( findings , policyFindings ... )
}
// Analyze inline policies
for _ , policy := range detail . InlinePolicies {
policyFindings := AnalyzePermissions (
fmt . Sprintf ( " %s (inline: %s )" , detail . RoleARN , policy . PolicyName ),
policy . PolicyDocument ,
)
findings = append ( findings , policyFindings ... )
}
// Check if role is unused
if detail . LastUsed == nil {
findings = append ( findings , SecurityFinding {
ID : GenerateFindingID (),
Severity : SeverityLow ,
Type : FindingUnusedRole ,
ResourceARN : detail . RoleARN ,
Description : fmt . Sprintf ( "Role %s has never been used" , detail . RoleName ),
Remediation : "Consider deleting unused roles to reduce attack surface" ,
})
}
return findings , nil
}
Analyze specific policy
clanker ask --iam --policy-arn "arn:aws:iam::123456789012:policy/MyPolicy" "check for security issues"
Security finding types
The analyzer detects multiple security issue types:
const (
FindingOverpermissivePolicy = "overpermissive_policy"
FindingAdminAccess = "admin_access"
FindingWildcardResource = "wildcard_resource"
FindingUnusedRole = "unused_role"
FindingCrossAccountTrust = "cross_account_trust"
FindingMissingMFA = "missing_mfa"
FindingOldAccessKeys = "old_access_keys"
FindingInactiveKeys = "inactive_keys"
FindingRootAccountUsage = "root_account_usage"
FindingPublicS3Access = "public_s3_access"
FindingExcessivePermissions = "excessive_permissions"
FindingMissingResourceScoping = "missing_resource_scoping"
)
Severity levels
const (
SeverityCritical = "critical"
SeverityHigh = "high"
SeverityMedium = "medium"
SeverityLow = "low"
SeverityInfo = "info"
)
Security finding structure
type SecurityFinding struct {
ID string `json:"id"`
Severity string `json:"severity"`
Type string `json:"type"`
ResourceARN string `json:"resource_arn"`
Description string `json:"description"`
Remediation string `json:"remediation"`
Actions [] string `json:"actions,omitempty"`
Resources [] string `json:"resources,omitempty"`
}
Generate fix plan
Request automated remediation for security findings:
clanker ask --iam "fix overpermissive IAM policies"
The fixer generates a detailed remediation plan:
func ( f * SubAgent ) GenerateFixPlan ( ctx context . Context , finding SecurityFinding ) ( * FixPlan , error ) {
plan := & FixPlan {
ID : generatePlanID (),
Finding : finding ,
CreatedAt : time . Now (),
}
switch finding . Type {
case FindingOverpermissivePolicy :
commands , notes , warnings := f . planOverpermissivePolicyFix ( ctx , finding )
plan . Commands = commands
plan . Notes = notes
plan . Warnings = warnings
plan . Summary = "Restrict overly permissive IAM policy"
case FindingAdminAccess :
commands , notes , warnings := f . planAdminAccessFix ( ctx , finding )
plan . Commands = commands
plan . Notes = notes
plan . Warnings = warnings
plan . Summary = "Review and restrict administrative IAM access"
case FindingWildcardResource :
commands , notes , warnings := f . planWildcardResourceFix ( ctx , finding )
plan . Commands = commands
plan . Notes = notes
plan . Warnings = warnings
plan . Summary = "Add resource scoping to IAM policy"
// ... additional finding types
}
return plan , nil
}
Fix plan structure
type FixPlan struct {
ID string `json:"id"`
Summary string `json:"summary"`
Finding SecurityFinding `json:"finding"`
Commands [] FixCommand `json:"commands"`
Notes [] string `json:"notes,omitempty"`
Warnings [] string `json:"warnings,omitempty"`
CreatedAt time . Time `json:"created_at"`
}
type FixCommand struct {
ID string `json:"id"`
Action string `json:"action"`
ResourceARN string `json:"resource_arn"`
Parameters map [ string ] interface {} `json:"parameters"`
Reason string `json:"reason"`
Rollback * FixCommand `json:"rollback,omitempty"`
}
Apply fix plan
Execute automated remediation (requires confirmation):
func ( f * SubAgent ) ApplyPlan ( ctx context . Context , plan * FixPlan , confirm bool ) error {
if err := f . ValidatePlan ( plan ); err != nil {
return fmt . Errorf ( "invalid plan: %w " , err )
}
if ! confirm {
return fmt . Errorf ( "plan execution requires confirmation" )
}
for i , cmd := range plan . Commands {
if err := f . executeCommand ( ctx , cmd ); err != nil {
return fmt . Errorf ( "command %d ( %s ) failed: %w " , i + 1 , cmd . Action , err )
}
}
return nil
}
Supported fix actions
const (
ActionUpdatePolicy = "update_policy"
ActionCreatePolicyVersion = "create_policy_version"
ActionAttachPolicy = "attach_policy"
ActionDetachPolicy = "detach_policy"
ActionDeletePolicyVersion = "delete_policy_version"
ActionDeactivateAccessKey = "deactivate_access_key"
ActionDeleteAccessKey = "delete_access_key"
ActionRotateAccessKey = "rotate_access_key"
ActionUpdateTrustPolicy = "update_trust_policy"
)
Credential analysis
The IAM agent generates and analyzes AWS credential reports:
func ( c * Client ) GetCredentialReport ( ctx context . Context ) ( * CredentialReport , error ) {
// Generate credential report
for i := 0 ; i < 10 ; i ++ {
_ , err := c . iam . GenerateCredentialReport ( ctx , & iam . GenerateCredentialReportInput {})
if err == nil {
break
}
time . Sleep ( time . Second * 2 )
}
// Get the report
resp , err := c . iam . GetCredentialReport ( ctx , & iam . GetCredentialReportInput {})
if err != nil {
return nil , fmt . Errorf ( "failed to get credential report: %w " , err )
}
report := & CredentialReport {}
if resp . GeneratedTime != nil {
report . GeneratedTime = * resp . GeneratedTime
}
// Parse CSV content
reader := csv . NewReader ( strings . NewReader ( string ( resp . Content )))
records , err := reader . ReadAll ()
if err != nil {
return nil , fmt . Errorf ( "failed to parse credential report: %w " , err )
}
// Parse entries and check for:
// - Missing MFA
// - Old access keys
// - Inactive keys
// - Unused passwords
return report , nil
}
Credential report entry:
type CredentialReportEntry struct {
User string `json:"user"`
ARN string `json:"arn"`
UserCreationTime time . Time `json:"user_creation_time"`
PasswordEnabled bool `json:"password_enabled"`
PasswordLastUsed * time . Time `json:"password_last_used,omitempty"`
MFAActive bool `json:"mfa_active"`
AccessKey1Active bool `json:"access_key_1_active"`
AccessKey1LastRotated * time . Time `json:"access_key_1_last_rotated,omitempty"`
AccessKey1LastUsedDate * time . Time `json:"access_key_1_last_used_date,omitempty"`
AccessKey2Active bool `json:"access_key_2_active"`
AccessKey2LastRotated * time . Time `json:"access_key_2_last_rotated,omitempty"`
}
IAM operations
List roles
func ( c * Client ) ListRoles ( ctx context . Context ) ([] RoleInfo , error ) {
paginator := iam . NewListRolesPaginator ( c . iam , & iam . ListRolesInput {})
var roles [] RoleInfo
for paginator . HasMorePages () {
page , err := paginator . NextPage ( ctx )
if err != nil {
return roles , fmt . Errorf ( "failed to list roles: %w " , err )
}
for _ , role := range page . Roles {
roleInfo := RoleInfo {
RoleName : aws . ToString ( role . RoleName ),
RoleARN : aws . ToString ( role . Arn ),
Path : aws . ToString ( role . Path ),
MaxSessionDuration : aws . ToInt32 ( role . MaxSessionDuration ),
}
// ... populate additional fields
roles = append ( roles , roleInfo )
}
}
return roles , nil
}
Get role details
func ( c * Client ) GetRoleDetails ( ctx context . Context , roleName string ) ( * RoleDetail , error ) {
roleResp , err := c . iam . GetRole ( ctx , & iam . GetRoleInput {
RoleName : aws . String ( roleName ),
})
if err != nil {
return nil , fmt . Errorf ( "failed to get role %s : %w " , roleName , err )
}
detail := & RoleDetail { /* ... */ }
// Get attached policies
attachedResp , err := c . iam . ListAttachedRolePolicies ( ctx , & iam . ListAttachedRolePoliciesInput {
RoleName : aws . String ( roleName ),
})
if err == nil {
for _ , policy := range attachedResp . AttachedPolicies {
detail . AttachedPolicies = append ( detail . AttachedPolicies , PolicyInfo {
PolicyName : aws . ToString ( policy . PolicyName ),
PolicyARN : aws . ToString ( policy . PolicyArn ),
})
}
}
// Get inline policies
inlineResp , err := c . iam . ListRolePolicies ( ctx , & iam . ListRolePoliciesInput {
RoleName : aws . String ( roleName ),
})
// ... retrieve inline policy documents
return detail , nil
}
Update policies
func ( c * Client ) CreatePolicyVersion ( ctx context . Context , policyARN , document string , setAsDefault bool ) error {
_ , err := c . iam . CreatePolicyVersion ( ctx , & iam . CreatePolicyVersionInput {
PolicyArn : aws . String ( policyARN ),
PolicyDocument : aws . String ( document ),
SetAsDefault : setAsDefault ,
})
return err
}
func ( c * Client ) UpdateAssumeRolePolicy ( ctx context . Context , roleName , document string ) error {
_ , err := c . iam . UpdateAssumeRolePolicy ( ctx , & iam . UpdateAssumeRolePolicyInput {
RoleName : aws . String ( roleName ),
PolicyDocument : aws . String ( document ),
})
return err
}
Natural language queries
Use natural language for IAM security analysis:
clanker ask --iam "Which roles have administrative access?"
clanker ask --iam "Find IAM users without MFA enabled"
clanker ask --iam "Show me access keys older than 90 days"
clanker ask --iam "What roles allow cross-account access?"
Best practices
Start with read-only analysis
Always run analyze commands before attempting fixes. Review findings manually before applying automated remediation.
Use scoped queries for targeted analysis
Scope IAM analysis to specific roles or policies using --role-arn or --policy-arn flags for faster, focused results.
Review fix plans before applying
Automated remediation plans include warnings and notes. Review these carefully, especially for production environments.
Monitor credential reports regularly
Schedule regular credential report analysis to catch credential hygiene issues early: clanker ask --iam "analyze credential report for security issues"
Leverage conversation history
The IAM agent maintains conversation history per AWS account. Use this for tracking remediation progress over time.
Example workflows
Complete security audit
# 1. Analyze entire account
clanker ask --iam "perform comprehensive security analysis"
# 2. Review findings by severity
clanker ask --iam "show me critical and high severity findings"
# 3. Generate fix plan for highest priority issue
clanker ask --iam "fix the most critical security issue"
# 4. Review and apply fix plan (manual step)
# Apply fix plan via separate command after review
Role-specific security review
# Analyze specific role
clanker ask --iam --role-arn "arn:aws:iam::123456789012:role/LambdaExecutionRole" \
"analyze this role for security issues"
# Review trust policy
clanker ask --iam --role-arn "arn:aws:iam::123456789012:role/LambdaExecutionRole" \
"what principals can assume this role?"
# Check for least privilege
clanker ask --iam --role-arn "arn:aws:iam::123456789012:role/LambdaExecutionRole" \
"does this role follow least privilege principle?"
Credential hygiene
# Generate and analyze credential report
clanker ask --iam "check credential report for security issues"
# Find old access keys
clanker ask --iam "list access keys older than 90 days"
# Find users without MFA
clanker ask --iam "which users don't have MFA enabled?"
Error handling
The IAM agent provides detailed error messages:
if err != nil {
return & Response {
Type : ResponseTypeError ,
Error : fmt . Errorf ( "failed to analyze role: %w " , err ),
}, nil
}
Common errors:
Access Denied : IAM client lacks required permissions
Role Not Found : Specified role ARN doesn’t exist
Invalid Policy Document : Policy JSON is malformed
Account Not Accessible : Profile doesn’t have STS permissions
The IAM agent requires significant IAM permissions. For production use, grant least privilege permissions:
iam:ListRoles
iam:GetRole
iam:ListPolicies
iam:GetPolicy
iam:GetPolicyVersion
iam:GetCredentialReport
iam:GenerateCredentialReport
For remediation, additional write permissions are required.