Skip to main content

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)

One-Click Deploy

  1. Log in to your NightOps dashboard
  2. Go to Providers → Add Provider → AWS
  3. Copy your unique External ID
  4. 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

  1. Copy the Role ARN from the stack output
  2. Paste it in the NightOps dashboard
  3. Click Test Connection
  4. 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

  1. Verify the External ID matches exactly
  2. Check the role ARN is correct
  3. 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

  1. Use External ID — Always configure the External ID to prevent confused deputy attacks
  2. Least Privilege — The NightOps policy only includes required permissions
  3. CloudTrail — All NightOps API calls are logged in CloudTrail
  4. 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"
}
}
}

Next Steps