DEV Community

Daniel Worsnup
Daniel Worsnup

Posted on • Originally published at danielworsnup.com

Shortcut Linking with AWS Identity Center SSO

Cross-posted from my tech blog.

When an alarm goes off at 3 AM, every second counts. Navigating through the AWS Console can be a frustrating experience when troubleshooting a critical issue. What if you could send your on-call engineers directly to the exact CloudWatch alarm page they need, bypassing the maze of AWS Console navigation?

This post demonstrates how to build a system that creates shortcut links to AWS resources through AWS Identity Center (formerly AWS SSO), enabling your team to go from alert notification to viewing the relevant resource in seconds.

The Problem with Traditional AWS Alerts

Traditional CloudWatch alarm notifications contain basic information such as:

  • Alarm name
  • State change (OK, ALARM, INSUFFICIENT_DATA)
  • Timestamp
  • Reason for the alarm

However, they don't provide a direct way to access the resource. Engineers receiving these alerts must:

  1. Log in to the AWS Console
  2. Navigate to the correct AWS account
  3. Switch to the right region
  4. Find the CloudWatch service
  5. Locate the specific alarm

This process consumes valuable minutes that could be better spent addressing the actual issue.

The Solution: Shortcut Linking with AWS Identity Center

This solution streamlines incident response by creating authenticated shortcut links to specific AWS resources. These links handle the entire authentication process and navigation, taking engineers directly to the relevant resource with a single click.

Here's the high-level workflow:

  1. CloudWatch alarm triggers and sends a notification to an SNS topic
  2. A Lambda function processes the SNS message
  3. The Lambda extracts the resource details (region, account ID, alarm name)
  4. It constructs an AWS Identity Center shortcut link that points directly to the resource
  5. The function sends a formatted message with this shortcut link to the on-call channel

The result? Engineers can view the exact resource in seconds rather than minutes.

What Does It Look Like?

The implementation suggested in this article will generate the shortcut link and send it to a Discord channel, which looks like this:

All alarms via Discord Webhook

However, you can modify the Lambda code to send the alert to a different channel such as Slack, PagerDuty, or email.

Understanding AWS IAM Identity Center

Before diving into implementation, it's important to understand the technology that makes this possible. AWS IAM Identity Center provides a centralized way to manage access across multiple AWS accounts. For our shortcut linking solution, it offers three key capabilities:

  1. Single Authentication Point: Users authenticate once and can access multiple AWS accounts
  2. Role-Based Access: Automatically assumes the appropriate IAM role in the target account
  3. Direct Resource Navigation: Can link directly to specific resources across accounts

When an engineer clicks our shortcut link, here's what happens behind the scenes:

  1. They're directed to the Identity Center login page (if not already authenticated)
  2. After authentication, Identity Center automatically:
    • Federates them into the specified AWS account
    • Assumes the designated IAM role
    • Redirects to the exact resource URL

This eliminates all manual navigation steps, saving critical time during incidents.

Finding Your Identity Center Values

To create shortcut links that work with your AWS Identity Center setup, you'll need two key values:

  1. SSO Start URL (ssoStartUrl): The entry point to your AWS Identity Center portal.

    • Log in to the AWS Management Console
    • Go to the AWS IAM Identity Center service
    • In the "Dashboard" section, find the "AWS access portal URL" - this is your ssoStartUrl
    • It typically looks like https://d-xxxxxxxxxx.awsapps.com/start
  2. SSO Role Name (ssoRoleName): The permission set name that users will assume in the target AWS account.

    • In the IAM Identity Center console, go to "AWS accounts"
    • Select a permission set that your users have been assigned
    • The name of this permission set (e.g., "AWSAdministratorAccess", "ReadOnlyAccess") is your ssoRoleName
    • This role must have sufficient permissions to view the resources you're linking to

These values will be passed to our Lambda function as environment variables, making them easy to update if your Identity Center configuration changes.

Setting Up the Infrastructure with AWS CDK

Let's build this solution using AWS CDK. We'll create these components:

  1. A Discord webhook integration for receiving alert notifications
  2. An SNS topic and Lambda function to process alarms and send shortcut links
  3. CloudWatch alarms configured to publish to our SNS topic

Let's break down each component:

1. Setting Up Discord Webhooks

First, set up Discord to receive our enhanced notifications via a Webhook:

  1. Open Discord and navigate to your server
  2. Click on the server name and select Server Settings
  3. Select IntegrationsWebhooks
  4. Click New Webhook, then customize the name and destination channel
  5. Copy the webhook URL (we'll store this securely in AWS Secrets Manager)

Note that webhook URLs should be treated as secrets since they allow posting to your Discord channel.

2. Building the CDK Infrastructure

Let's build our solution with CDK by creating a MonitoringStack that contains our Discord webhook secret, SNS topic, Lambda function, and CloudWatch alarm for monitoring DynamoDB throttling:

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as subs from 'aws-cdk-lib/aws-sns-subscriptions';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import { SnsAction } from 'aws-cdk-lib/aws-cloudwatch-actions';

interface MonitoringStackProps extends cdk.StackProps {
  tableName: string;
  ssoStartUrl: string;
  ssoRoleName: string;
}

export class MonitoringStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: MonitoringStackProps) {
    super(scope, id, props);

    // Store Discord webhook URL securely in Secrets Manager
    const discordWebhookSecret = new secretsmanager.Secret(this, 'DiscordWebhookSecret', {
      secretName: 'discord/webhook',
    });

    // Create SNS topic that will receive all our CloudWatch alarms
    const alarmTopic = new sns.Topic(this, 'AlarmTopic', {
      topicName: 'cloud-watch-alarms',
    });

    // Create Lambda function to process notifications and create shortcut links
    const notifyLambda = new lambda.Function(this, 'NotifyLambda', {
      runtime: lambda.Runtime.NODEJS_22_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda'), 
      environment: {
        // Pass the secret ARN and SSO values to the Lambda
        DISCORD_WEBHOOK_SECRET_ARN: discordWebhookSecret.secretArn,
        SSO_START_URL: props.ssoStartUrl,
        SSO_ROLE_NAME: props.ssoRoleName,
      },
    });

    // Grant the Lambda permission to read the webhook secret
    discordWebhookSecret.grantRead(notifyLambda);

    // Subscribe the Lambda to the SNS topic
    alarmTopic.addSubscription(new subs.LambdaSubscription(notifyLambda));

    // As an example, monitor one of your DynamoDB tables
    const table = dynamodb.Table.fromTableName(
      this,
      'MonitoredTable',
      props.tableName
    );

    // Set an alarm on the ReadThrottleEvents metric
    const readThrottleAlarm = new cloudwatch.Alarm(this, 'TableReadThrottleAlarm', {
      metric: new cloudwatch.Metric({
        namespace: 'AWS/DynamoDB',
        metricName: 'ReadThrottleEvents',
        dimensionsMap: {
          TableName: props.tableName,
        },
        statistic: 'Sum',
      }),
      threshold: 1, // Alarm on a single throttle event
      evaluationPeriods: 1, // In a single period
      alarmName: `${props.tableName}-ReadThrottleAlarm`,
    });

    // When the alarm triggers, publish a message to the SNS topic
    readThrottleAlarm.addAlarmAction(new SnsAction(alarmTopic));
  }
}
Enter fullscreen mode Exit fullscreen mode

This CDK application:

  1. Creates the notification infrastructure:

    • Securely stores the Discord webhook URL in AWS Secrets Manager
    • Sets up an SNS topic as the collection point for all alarms
    • Deploys a Lambda function that transforms alarms into shortcut-linked notifications
    • Passes Identity Center configuration to the Lambda as environment variables
  2. Sets up DynamoDB monitoring:

    • References an existing DynamoDB table
    • Creates an alarm for read throttling events
    • Routes the alarm to our SNS topic

Here's how to deploy this stack with your specific SSO values:

// In your bin/<app-name>.ts file or equivalent
const app = new cdk.App();
new MonitoringStack(app, 'MonitoringStack', {
  tableName: 'your-dynamodb-table-name',
  ssoStartUrl: 'https://d-xxxxxxxxxx.awsapps.com/start',
  ssoRoleName: 'AWSAdministratorAccess', // Or another permission set name
});
Enter fullscreen mode Exit fullscreen mode

After deployment, add your Discord Webhook URL as the secret value in AWS Secrets Manager.

The Lambda Function: Creating Shortcut Links

Now for the core of our solution—the Lambda function that processes CloudWatch alarm notifications and creates shortcut links. Note that you can spend time customizing how the alert renders in Discord to meet your specific needs. Choose your own colors, emojis, layout, and more by customizing the Lambda function!

import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";

// Emoji for Discord message based on alarm state
const getStateDiscordEmoji = (state) => {
  if (state === 'OK') return ':green_square:';
  if (state === 'ALARM') return ':red_square:';
  if (state === 'INSUFFICIENT_DATA') return ':yellow_square:';
  return ':grey_question:';
};

// Color for Discord message based on alarm state
const getStateDiscordColor = (state) => {
  if (state === 'OK') return 65280;
  if (state === 'ALARM') return 16711680;
  if (state === 'INSUFFICIENT_DATA') return 16776960;
  return 13421772;
};

// Helper to extract parts of the alarm ARN
const parseAlarmArn = (alarmArn) => {
  const match = alarmArn.match(/^arn:aws:cloudwatch:(.+):(.+):alarm:(.+)$/);

  if (!match) {
    throw new Error(`Invalid alarm ARN: ${alarmArn}`);
  }

  return {
    region: match[1],
    accountId: match[2],
    alarmName: match[3],
  };
};

// Helper functions for building properly formatted alarm URL
const getAlarmUrl = (alarmName, region) => {
  const alarmUrl = new URL('https://console.aws.amazon.com/cloudwatch/home');
  alarmUrl.searchParams.set('region', region);
  alarmUrl.hash = `alarmsV2:alarm/${encodeURIComponent(alarmName).replaceAll('%', '$')}`;
  return alarmUrl.href;
};

// Helper function for building properly formatted SSO shortcut URL
const getSsoShortcutUrl = (accountId, roleName, destinationUrl) => {
  const hashParams = new URLSearchParams({
    account_id: accountId,
    role_name: roleName,
    destination: destinationUrl,
  });

  const ssoShortcutUrl = new URL(`${process.env.SSO_START_URL}/`);
  ssoShortcutUrl.hash = `/console?${hashParams}`;
  return ssoShortcutUrl.href;
};

// Initialize the client outside the handler for better performance
const secretsManagerClient = new SecretsManagerClient();

export const handler = async (event) => {
  // Parse the SNS message from CloudWatch
  const {
    AWSAccountId,
    AlarmArn,
    AlarmName,
    NewStateReason,
    NewStateValue,
    StateChangeTime,
    Trigger,
  } = JSON.parse(event.Records[0].Sns.Message);
  const { region } = parseAlarmArn(AlarmArn);
  const stateEmoji = getStateDiscordEmoji(NewStateValue);
  const stateColor = getStateDiscordColor(NewStateValue);

  // Build the direct link to this alarm in CloudWatch
  const alarmUrl = getAlarmUrl(AlarmName, region);

  // Create the Identity Center shortcut link
  const ssoRoleName = process.env.SSO_ROLE_NAME;
  const alarmSsoShortcutUrl = getSsoShortcutUrl(AWSAccountId, ssoRoleName, alarmUrl);

  // Retrieve the Discord webhook URL from Secrets Manager
  const { SecretString: webhookUrl } = await secretsManagerClient.send(
    new GetSecretValueCommand({
      SecretId: process.env.DISCORD_WEBHOOK_SECRET_ARN
    })
  );

  const title = `${stateEmoji} **${NewStateValue}**`;
  const description = `Alarm **${AlarmName}** entered state **${NewStateValue}** due to "_${NewStateReason}_"`;

  // Create a formatted Discord message with the shortcut link
  const message = {
    content: null,
    embeds: [{
      title,
      description,
      url: alarmSsoShortcutUrl,
      color: stateColor,
      fields: [
        {
          name: 'Account',
          value: AWSAccountId,
          inline: true
        },
        {
          name: 'Region',
          value: region,
          inline: true
        },
        {
          name: 'Time',
          value: StateChangeTime,
          inline: true
        },
        {
          name: 'Alarm',
          value: AlarmName,
          inline: true
        },
        {
          name: 'State',
          value: NewStateValue,
          inline: true
        },
        {
          name: 'Reason',
          value: NewStateReason,
          inline: true
        },
        {
          name: 'View Alarm',
          value: `[Open Alarm in CloudWatch Console](${alarmSsoShortcutUrl})`
        },
      ],
    }],
  };

  // Send the notification to Discord
  await fetch(webhookUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(message)
  });
};
Enter fullscreen mode Exit fullscreen mode

How It All Works Together

Here's how these components create a seamless incident response workflow:

  1. Alert Triggering: A CloudWatch alarm changes state and publishes a notification to the SNS topic.

  2. Link Creation: The Lambda function:

    • Extracts essential information from the alert
    • Creates a direct link to the specific resource
    • Wraps it in an Identity Center authentication link
    • Formats a user-friendly Discord message
  3. Engineer Response: When an engineer receives the notification:

    • They click the shortcut link
    • If needed, they authenticate with AWS Identity Center
    • They're automatically redirected to the exact resource
    • They can immediately begin troubleshooting

When an alert comes in, engineers see a nicely formatted Discord message and can access the resource within seconds—dramatically faster than the traditional multi-step navigation process:

OK alarm via Discord webhook

Extending the Solution

This approach isn't limited to CloudWatch alarms. You can adapt the URL-building logic above to create shortcut links to any AWS resource:

  • EC2 instances: Link directly to specific instances having issues
  • Lambda functions: Jump to logs or metrics for failing functions
  • DynamoDB tables: Link to tables with throttling or capacity issues

You can also integrate with other notification channels like Slack, Microsoft Teams, or PagerDuty.

Security Best Practices

When implementing this solution, adhere to these security considerations:

  1. Least Privilege: Configure the Lambda function with only the permissions needed to read from Secrets Manager and receive SNS messages.
  2. Secret Management: Store sensitive information like webhook URLs in AWS Secrets Manager.
  3. IAM Roles: Ensure the role specified in your SSO shortcut links has appropriate permissions for your team members.

Conclusion

By combining CloudWatch alarms with AWS Identity Center shortcut linking, you create a powerful incident response tool that:

  1. Dramatically reduces time to resolution
  2. Eliminates navigation friction during critical incidents
  3. Provides engineers with context-rich notifications
  4. Works seamlessly across multiple AWS accounts

This approach not only improves your team's efficiency but also reduces the stress of on-call rotations by streamlining the response process.

The next time an alarm wakes someone up at 3 AM, they'll be one click away from the exact resource they need to fix—and that makes all the difference.

Happy building!

Like this post?

Follow me on Twitter where I tweet about frontend things: @thesnups

Top comments (1)

Collapse
 
emmanuel_gelatimesa_54fe profile image
Emmanuel Gelati mesa

I love terraform code, write infra code in JavaScript is only terrible