Skip to content
Step-by-step: Imple...
 
Notifications
Clear all

Step-by-step: Implementing just-in-time access for our AWS accounts using Slack approvals.

5 Posts
5 Users
0 Reactions
4 Views
(@datadog_dave)
Reputable Member
Joined: 2 months ago
Posts: 157
Topic starter   [#14978]

Hey everyone! 👋 I've been working on tightening up our cloud security posture lately, and one of the best changes we made was implementing Just-In-Time (JIT) access for our AWS accounts. Instead of engineers having standing IAM privileges, they now request elevated access only when needed, approved via Slack. It's been a game-changer for reducing our attack surface and satisfying audit requirements.

Here's a simplified breakdown of our setup, using a combo of AWS IAM, Lambda, and Slack's workflows:

**The Flow:**
1. An engineer needs temporary admin rights to an AWS account.
2. They run a simple slash command in Slack (`/aws-access`).
3. A Slack workflow sends a formatted request to a private #cloud-access channel, tagging the on-call infra lead.
4. The approver clicks "Approve" or "Deny" in the Slack message.
5. If approved, an AWS Lambda function attaches the necessary IAM policy to the user's role for a set duration (like 2 hours).
6. The engineer gets a DM with the approval and a link to the AWS console.

**Core AWS Lambda Logic (Python snippet):**
The Lambda function triggered by the Slack approval does the heavy lifting. Here's the core policy attachment logic:

```python
import boto3
import json
from datetime import datetime, timedelta

def attach_jit_policy(role_name, account_id, duration_hours):
iam = boto3.client('iam')
policy_arn = f'arn:aws:iam::{account_id}:policy/JIT-Admin-Policy'

# Calculate expiration
expiry_time = datetime.utcnow() + timedelta(hours=duration_hours)

# Create or update the role's assume role policy to allow this temporary attachment
trust_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": f"arn:aws:iam::{account_id}:role/{role_name}"
},
"Action": "sts:AssumeRole",
"Condition": {
"DateLessThan": {
"aws:CurrentTime": expiry_time.isoformat() + "Z"
}
}
}
]
}

iam.update_assume_role_policy(
RoleName=role_name,
PolicyDocument=json.dumps(trust_policy)
)
print(f"JIT policy attached to {role_name} until {expiry_time}")
```

**Why this works for us:**
- **No standing privileges:** Our engineers' base IAM roles are basically read-only.
- **Audit trail:** Every request and approval is logged in Slack and CloudTrail.
- **Fast for engineers:** They get access in minutes, not days.
- **Safe:** We enforce MFA and have a break-glass procedure outside of Slack for true emergencies.

We started with AWS, but I'm planning to adapt this for our GCP projects next. Has anyone else built something similar? I'm curious about how you handle escalations or if you've integrated with PagerDuty or Opsgenie for approvals instead.


Dashboards or it didn't happen.


   
Quote
(@bench_beast)
Reputable Member
Joined: 1 month ago
Posts: 231
 

Do you have the code for the actual Lambda trigger and IAM attachment? The snippet is cut off.

I ask because I've benchmarked a few assistants on generating that exact function. The biggest failure point is the policy document's Principal/Role trust boundary. Most generated code misses the condition for `aws:MultiFactorAuthPresent`. Without it, you're not really JIT, you're just automated.


Benchmarks don't lie.


   
ReplyQuote
(@bob88)
Trusted Member
Joined: 1 week ago
Posts: 48
 

The MultiFactorAuthPresent condition is a good callout. But the real risk isn't the Lambda code missing it, it's that the policy being attached can't enforce it. The Lambda is attaching an *inline* policy, which overrides the identity-level session context. You need the MFA condition on the *role's trust policy*, which this flow doesn't touch. That's a fundamental design gap.

Here's what we had to change: the Lambda doesn't attach an inline policy. Instead, it updates the trust relationship of a dedicated JIT IAM role to assume the requesting user as a principal, with the MFA condition baked in. The user then assumes that role via STS. The code is different, but the security boundary is correct.

If you're just slapping an admin policy onto a user's existing role, you've automated standing access, not created JIT.


Migrate once, test twice.


   
ReplyQuote
(@emma78)
Trusted Member
Joined: 1 week ago
Posts: 43
 

Okay, that's a crucial distinction I hadn't considered. So the trust policy update is the actual security gate, not the attached permissions.

How do you handle the cleanup? Does the same Lambda revert the trust relationship after a set time, or is there a separate process to remove the user as a principal?



   
ReplyQuote
(@cost_observer_42)
Estimable Member
Joined: 1 month ago
Posts: 122
 

So the "game-changer" you're seeing, is that just reflected in fewer IAM findings from your security scans, or has it actually moved the needle on your AWS bill? Automated privilege grants can sometimes lead to more broad, longer-lived sessions than the old standing permissions.


cost_observer_42


   
ReplyQuote