Kestra Cross-Cloud JIT Access
Self-service just-in-time access for AWS and Azure, built with Kestra. A user requests access via Slack, an approver clicks Approve, and Kestra provisions temporary read access on both clouds — then revokes it automatically.
Companion blog post: Beyond IAM: Temporary Cloud Access That Works Across AWS and Azure
Architecture
Cloudflare DNS (kestra.your-domain.com)
└── ALB (HTTPS/443)
├── /slack/* → Slack Bot (port 5000)
└── /* → Kestra (port 8080)
└── EC2 (private subnet) — Docker Compose
├── kestra/kestra:latest
├── postgres:16
└── slack-bot (Flask)
Prerequisites
- AWS CLI + Azure CLI authenticated
- Terraform >= 1.7
- Slack app with
chat:write,im:write,commandsscopes and interactivity enabled
Deployment
Copy and fill in the env files (gitignored):
cp terraform/aws/.env.example terraform/aws/.env
cp terraform/azure/.env.example terraform/azure/.env
cp terraform/kestra/.env.example terraform/kestra/.env
Then deploy in order:
./deploy.sh aws # VPC, EC2, ALB, IAM
./deploy.sh azure # Resource group, storage, service principal
./deploy.sh kestra # Flow + KV store values
After ./deploy.sh aws, point a Cloudflare CNAME at the alb_dns_name output.
Slack App Setup
- Add slash command
/request-access→https://your-domain/slack/request-access - Enable Interactivity, set Request URL →
https://your-domain/slack/interact
Usage
/request-access john.doe 30 Need to investigate production issue
The approver receives a DM with Approve/Deny buttons. On approval, Kestra grants:
- AWS:
AmazonS3ReadOnlyAccesson the IAM user - Azure:
Storage Blob Data Reader+Readeron the resource group
Access is revoked automatically after the requested duration.
Utilities
./scripts/list-permissions.sh john.doe # List AWS + Azure permissions for a user