Building a3s: How I Brought the k9s Experience to AWS IAM Management
August 27, 2025
Last week, I was knee-deep in debugging IAM role permissions for a Lambda function. I had fifteen browser tabs open - AWS Console, documentation, different role views - and I was constantly switching between them, waiting for pages to load, clicking through menus. The whole experience felt like swimming through molasses.
That’s when it hit me: why isn’t there something like k9s for AWS? If you’ve ever used k9s for Kubernetes, you know exactly what I mean - that beautiful, responsive terminal UI that makes navigating complex resources feel effortless. I wanted that same experience for AWS IAM. So I built it.
The Problem: AWS Console Fatigue
Every cloud engineer knows this dance. You need to check an IAM role’s trust policy. Click, wait, load. Now you need to see its attached policies. Click, wait, load. Want to view the actual policy document? More clicks, more waiting. And don’t even get me started on searching through hundreds of roles to find the one you need.
The AWS CLI is powerful, but let’s be honest - nobody remembers this syntax off the top of their head:
aws iam list-roles --query 'Roles[?contains(RoleName,`lambda`)].{Name:RoleName,Arn:Arn}' --output table
And even if you do, parsing JSON output in your terminal isn’t exactly a pleasant experience. I found myself constantly piping to jq
, losing context, and switching between terminal tabs just to understand a single role’s permissions.
Enter a3s: AWS Terminal User Interface
I decided to build a3s (pronounced “ace”) - a terminal-based user interface for AWS resources that brings the speed and efficiency of k9s to AWS management. The MVP focuses on IAM roles, but the architecture is designed to expand to other AWS services.
Here’s what makes a3s different:
Vim-Like Navigation That Just Works
j/k or ↑/↓ Navigate up/down
Enter View role details
/ Search roles in real-time
g/G Jump to top/bottom
Tab Switch between tabs
Esc Go back
No mouse needed. Your fingers never leave the home row. Every action is instant and responsive thanks to async loading - no more watching spinners while AWS APIs respond.
Real-Time Search That Actually Helps
Press /
and start typing. The list filters instantly as you type. Looking for Lambda execution roles? Type “lambda” and watch the list update in real-time. No page refreshes, no waiting. It’s the search experience the AWS Console should have had from day one.
Tabbed Interface for Complete Role Information
When you drill into a role, you get everything organized in tabs:
- Overview: Role ARN, creation date, last activity
- Trust Policy: See who can assume this role
- Policies: All attached managed and inline policies with an interactive JSON viewer
- Tags: Resource tags and metadata
Each policy document is viewable in a scrollable, syntax-highlighted view. No more squinting at minified JSON or copying to an external editor.
The Technical Implementation
Building a3s taught me a lot about creating responsive TUIs in Go. Here’s the stack I chose and why:
Bubble Tea + Lipgloss: The Perfect TUI Combo
Bubble Tea provides the Model-View-Update architecture that makes complex UI state manageable. Every user action triggers an update, the model changes, and the view re-renders. It’s like React for the terminal, but the update patterns are more explicit:
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "/":
m.searchMode = true
return m, textinput.Blink
case "enter":
if m.searchMode {
return m, m.performSearch()
}
return m, m.loadRoleDetails()
}
case roleLoadedMsg:
m.roles = msg.roles
m.loading = false
}
return m, nil
}
Lipgloss handles all the styling. Creating that k9s-inspired look with consistent colors, borders, and layouts was surprisingly straightforward:
var (
headerStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#FFD700")).
Background(lipgloss.Color("#1e1e1e")).
Padding(0, 1)
selectedStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFFFFF")).
Background(lipgloss.Color("#5D4E75"))
)
AWS SDK v2: Fast and Async
The AWS SDK for Go v2 provides excellent support for concurrent API calls. When you open a role’s detail view, a3s fires off parallel requests for the trust policy, attached policies, inline policies, and tags. Each request updates the UI as it completes, so you see data immediately instead of waiting for everything to load.
State Management That Scales
The architecture separates concerns cleanly, using Bubble Tea’s Model-View-Update pattern:
Each component has a single responsibility:
- List Model: Manages the table of roles, search filtering, and navigation
- Detail Model: Handles the tabbed interface and policy viewing
- AWS Service: Abstracts all AWS API calls with proper error handling and concurrent loading
This separation makes adding new resources straightforward. Want to add EC2 instance management? Create a new model, wire up the AWS calls, and the UI framework handles the rest.
What’s Next: The Roadmap
The IAM role viewer is just the beginning. Here’s what’s coming:
Phase 2: Enhanced IAM
- User and group management
- Policy creation and editing
- Permission boundary visualization
Phase 3: Core AWS Services
- EC2 instances and security groups
- Lambda functions with log viewing
- S3 bucket browsing
Phase 4: Advanced Features
- Multi-account support
- Resource relationship graphs
- CloudFormation stack visualization
Try It Yourself
Installing a3s is simple if you have Go installed:
git clone https://github.com/johnoct/a3s.git
cd a3s
go build -o a3s cmd/a3s/main.go
sudo mv a3s /usr/local/bin/
Then just run a3s
in your terminal. It’ll use your default AWS profile and region, or you can specify them:
a3s -profile production -region us-west-2
Key Learnings
- Bubble Tea’s message pattern is powerful: The Cmd return type lets you trigger async operations that send messages back to your model, enabling responsive UIs without blocking
- Concurrent AWS API calls transform the experience: Using
sync.WaitGroup
and goroutines to fetch role policies in parallel reduced load times from 3-5 seconds to under 1 second - Terminal styling libraries have matured: Lipgloss makes complex layouts trivial - you can create bordered, padded, colored components with method chaining
- Vim keybindings are non-negotiable for developer tools: Supporting hjkl navigation, / for search, and gg/G for jumping makes adoption instant for terminal users
- Local tools beat web interfaces for speed: Zero network latency for UI interactions, combined with efficient API batching, creates a fundamentally better experience than clicking through AWS Console
- Architecture matters from day one: Separating UI models from AWS service logic made adding new resource types straightforward - the same patterns work for any AWS service