Connect AWS Account
Deploy the NightOps IAM role to your AWS account using CloudFormation or Terraform.
Prerequisites
- AWS account with admin access (or permission to create IAM roles)
- AWS CLI configured (for CLI deployment)
Option 1: CloudFormation (Recommended)
One-Click Deploy
- Log in to your NightOps dashboard
- Go to Providers → Add Provider → AWS
- Copy your unique External ID
- Click Deploy CloudFormation Stack
This opens AWS CloudFormation with the template pre-filled.
CLI Deploy
# Get your External ID from the NightOps dashboard first
EXTERNAL_ID="your-external-id-from-dashboard"
aws cloudformation create-stack \
--stack-name nightops-integration \
--template-url https://nightops-templates.s3.amazonaws.com/cloudformation/nightops-role.yaml \
--parameters \
ParameterKey=ExternalId,ParameterValue=$EXTERNAL_ID \
--capabilities CAPABILITY_NAMED_IAM \
--region us-east-1
Wait for Completion
aws cloudformation wait stack-create-complete \
--stack-name nightops-integration \
--region us-east-1
# Get the role ARN
aws cloudformation describe-stacks \
--stack-name nightops-integration \
--query 'Stacks[0].Outputs[?OutputKey==`RoleArn`].OutputValue' \
--output text
Enter Role ARN in NightOps
- Copy the Role ARN from the stack output
- Paste it in the NightOps dashboard
- Click Test Connection
- Click Save Provider
Option 2: Terraform
# nightops.tf
variable "nightops_external_id" {
description = "External ID from NightOps dashboard"
type = string
}
data "aws_iam_policy_document" "nightops_assume_role" {
statement {
effect = "Allow"
principals {
type = "AWS"
identifiers = ["arn:aws:iam::123456789012:root"] # NightOps account
}
actions = ["sts:AssumeRole"]
condition {
test = "StringEquals"
variable = "sts:ExternalId"
values = [var.nightops_external_id]
}
}
}
resource "aws_iam_role" "nightops" {
name = "NightOpsRole"
assume_role_policy = data.aws_iam_policy_document.nightops_assume_role.json
}
resource "aws_iam_role_policy_attachment" "nightops" {
role = aws_iam_role.nightops.name
policy_arn = aws_iam_policy.nightops.arn
}
resource "aws_iam_policy" "nightops" {
name = "NightOpsPolicy"
policy = data.aws_iam_policy_document.nightops.json
}
data "aws_iam_policy_document" "nightops" {
# EC2
statement {
effect = "Allow"
actions = [
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:DescribeTags"
]
resources = ["*"]
}
# RDS
statement {
effect = "Allow"
actions = [
"rds:DescribeDBInstances",
"rds:DescribeDBClusters",
"rds:StartDBInstance",
"rds:StopDBInstance",
"rds:StartDBCluster",
"rds:StopDBCluster",
"rds:ListTagsForResource"
]
resources = ["*"]
}
# ECS
statement {
effect = "Allow"
actions = [
"ecs:DescribeServices",
"ecs:DescribeClusters",
"ecs:ListServices",
"ecs:UpdateService",
"ecs:ListTagsForResource"
]
resources = ["*"]
}
# EKS
statement {
effect = "Allow"
actions = [
"eks:DescribeNodegroup",
"eks:ListNodegroups",
"eks:UpdateNodegroupConfig",
"eks:ListTagsForResource"
]
resources = ["*"]
}
# Auto Scaling
statement {
effect = "Allow"
actions = [
"autoscaling:DescribeAutoScalingGroups",
"autoscaling:UpdateAutoScalingGroup"
]
resources = ["*"]
}
# Redshift
statement {
effect = "Allow"
actions = [
"redshift:DescribeClusters",
"redshift:PauseCluster",
"redshift:ResumeCluster"
]
resources = ["*"]
}
}
output "role_arn" {
value = aws_iam_role.nightops.arn
}
Deploy:
terraform init
terraform apply -var="nightops_external_id=your-external-id"
Option 3: Manual IAM Setup
See the AWS IAM Policy documentation for the complete policy JSON.
Multi-Region Support
NightOps can manage resources across multiple AWS regions.
CloudFormation (Multi-Region)
The CloudFormation template creates a global IAM role. When adding the provider in NightOps, select which regions to scan:
Regions: us-east-1, us-west-2, eu-west-1
Limit to Specific Regions
For cost optimization or compliance, you can limit which regions NightOps can access by adding resource conditions to the IAM policy:
{
"Effect": "Allow",
"Action": ["ec2:StartInstances", "ec2:StopInstances"],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": ["us-east-1", "us-west-2"]
}
}
}
Multi-Account Setup
For AWS Organizations with multiple accounts:
Option A: Deploy to Each Account
Run the CloudFormation stack in each account you want to manage. Add each account as a separate provider in NightOps.
Option B: Cross-Account Access
Create roles in each account that trust a central NightOps role:
# In each child account
Resources:
NightOpsCrossAccountRole:
Type: AWS::IAM::Role
Properties:
RoleName: NightOpsCrossAccountRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${ManagementAccountId}:role/NightOpsRole'
Action: sts:AssumeRole
Troubleshooting
"Access Denied" on Test Connection
- Verify the External ID matches exactly
- Check the role ARN is correct
- Ensure the role trust policy includes the NightOps AWS account
"Unable to assume role"
# Verify the role exists
aws iam get-role --role-name NightOpsRole
# Test assuming the role (from NightOps account)
aws sts assume-role \
--role-arn arn:aws:iam::YOUR_ACCOUNT:role/NightOpsRole \
--role-session-name test \
--external-id YOUR_EXTERNAL_ID
CloudFormation Stack Failed
# Check stack events
aws cloudformation describe-stack-events \
--stack-name nightops-integration \
--query 'StackEvents[?ResourceStatus==`CREATE_FAILED`]'
Common issues:
- Role name already exists — delete old role or use different name
- Insufficient permissions — need IAM admin access
Security Best Practices
- Use External ID — Always configure the External ID to prevent confused deputy attacks
- Least Privilege — The NightOps policy only includes required permissions
- CloudTrail — All NightOps API calls are logged in CloudTrail
- Tag-Based Restrictions — Optionally limit access to resources with specific tags
Tag-Based Access Control
Limit NightOps to only manage tagged resources:
{
"Effect": "Allow",
"Action": ["ec2:StartInstances", "ec2:StopInstances"],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/nightops-managed": "true"
}
}
}