Configure AWS CloudWatch Observability Access Manager Links

The aws:oam/link:Link resource, part of the Pulumi AWS provider, connects a source AWS account to a monitoring account’s CloudWatch Observability Access Manager sink, controlling which telemetry types and data flow between accounts. This guide focuses on three capabilities: basic cross-account link setup, log group filtering by name pattern, and metric filtering by namespace.

Links depend on a Sink and SinkPolicy that must exist in the monitoring account before the link is created. The examples are intentionally small. Combine them with your own sink configuration and IAM permissions for cross-account access.

Connect a source account to a monitoring sink

Cross-account observability starts by creating a link from a source account to a monitoring account’s sink, defining which telemetry flows between accounts.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const exampleSink = new aws.oam.Sink("example", {});
const exampleSinkPolicy = new aws.oam.SinkPolicy("example", {sinkIdentifier: exampleSink.arn});
const example = new aws.oam.Link("example", {
    labelTemplate: "$AccountName",
    resourceTypes: ["AWS::CloudWatch::Metric"],
    sinkIdentifier: exampleSink.arn,
    tags: {
        Env: "prod",
    },
}, {
    dependsOn: [exampleSinkPolicy],
});
import pulumi
import pulumi_aws as aws

example_sink = aws.oam.Sink("example")
example_sink_policy = aws.oam.SinkPolicy("example", sink_identifier=example_sink.arn)
example = aws.oam.Link("example",
    label_template="$AccountName",
    resource_types=["AWS::CloudWatch::Metric"],
    sink_identifier=example_sink.arn,
    tags={
        "Env": "prod",
    },
    opts = pulumi.ResourceOptions(depends_on=[example_sink_policy]))
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/oam"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		exampleSink, err := oam.NewSink(ctx, "example", nil)
		if err != nil {
			return err
		}
		exampleSinkPolicy, err := oam.NewSinkPolicy(ctx, "example", &oam.SinkPolicyArgs{
			SinkIdentifier: exampleSink.Arn,
		})
		if err != nil {
			return err
		}
		_, err = oam.NewLink(ctx, "example", &oam.LinkArgs{
			LabelTemplate: pulumi.String("$AccountName"),
			ResourceTypes: pulumi.StringArray{
				pulumi.String("AWS::CloudWatch::Metric"),
			},
			SinkIdentifier: exampleSink.Arn,
			Tags: pulumi.StringMap{
				"Env": pulumi.String("prod"),
			},
		}, pulumi.DependsOn([]pulumi.Resource{
			exampleSinkPolicy,
		}))
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var exampleSink = new Aws.Oam.Sink("example");

    var exampleSinkPolicy = new Aws.Oam.SinkPolicy("example", new()
    {
        SinkIdentifier = exampleSink.Arn,
    });

    var example = new Aws.Oam.Link("example", new()
    {
        LabelTemplate = "$AccountName",
        ResourceTypes = new[]
        {
            "AWS::CloudWatch::Metric",
        },
        SinkIdentifier = exampleSink.Arn,
        Tags = 
        {
            { "Env", "prod" },
        },
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            exampleSinkPolicy,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.oam.Sink;
import com.pulumi.aws.oam.SinkPolicy;
import com.pulumi.aws.oam.SinkPolicyArgs;
import com.pulumi.aws.oam.Link;
import com.pulumi.aws.oam.LinkArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var exampleSink = new Sink("exampleSink");

        var exampleSinkPolicy = new SinkPolicy("exampleSinkPolicy", SinkPolicyArgs.builder()
            .sinkIdentifier(exampleSink.arn())
            .build());

        var example = new Link("example", LinkArgs.builder()
            .labelTemplate("$AccountName")
            .resourceTypes("AWS::CloudWatch::Metric")
            .sinkIdentifier(exampleSink.arn())
            .tags(Map.of("Env", "prod"))
            .build(), CustomResourceOptions.builder()
                .dependsOn(exampleSinkPolicy)
                .build());

    }
}
resources:
  example:
    type: aws:oam:Link
    properties:
      labelTemplate: $AccountName
      resourceTypes:
        - AWS::CloudWatch::Metric
      sinkIdentifier: ${exampleSink.arn}
      tags:
        Env: prod
    options:
      dependsOn:
        - ${exampleSinkPolicy}
  exampleSink:
    type: aws:oam:Sink
    name: example
  exampleSinkPolicy:
    type: aws:oam:SinkPolicy
    name: example
    properties:
      sinkIdentifier: ${exampleSink.arn}

The link sends data from the source account to the monitoring account’s sink. The labelTemplate property controls how the source account appears in monitoring dashboards (here using the account name). The resourceTypes array specifies which telemetry types to share; this example shares CloudWatch metrics. The dependsOn meta-argument ensures the SinkPolicy exists before creating the link, preventing authorization failures.

Filter log groups by name pattern

When sharing logs across accounts, you can limit which log groups appear in the monitoring account to reduce noise and costs.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.oam.Link("example", {
    labelTemplate: "$AccountName",
    linkConfiguration: {
        logGroupConfiguration: {
            filter: "LogGroupName LIKE 'aws/lambda/%' OR LogGroupName LIKE 'AWSLogs%'",
        },
    },
    resourceTypes: ["AWS::Logs::LogGroup"],
    sinkIdentifier: exampleAwsOamSink.arn,
}, {
    dependsOn: [exampleAwsOamSinkPolicy],
});
import pulumi
import pulumi_aws as aws

example = aws.oam.Link("example",
    label_template="$AccountName",
    link_configuration={
        "log_group_configuration": {
            "filter": "LogGroupName LIKE 'aws/lambda/%' OR LogGroupName LIKE 'AWSLogs%'",
        },
    },
    resource_types=["AWS::Logs::LogGroup"],
    sink_identifier=example_aws_oam_sink["arn"],
    opts = pulumi.ResourceOptions(depends_on=[example_aws_oam_sink_policy]))
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/oam"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := oam.NewLink(ctx, "example", &oam.LinkArgs{
			LabelTemplate: pulumi.String("$AccountName"),
			LinkConfiguration: &oam.LinkLinkConfigurationArgs{
				LogGroupConfiguration: &oam.LinkLinkConfigurationLogGroupConfigurationArgs{
					Filter: pulumi.String("LogGroupName LIKE 'aws/lambda/%' OR LogGroupName LIKE 'AWSLogs%'"),
				},
			},
			ResourceTypes: pulumi.StringArray{
				pulumi.String("AWS::Logs::LogGroup"),
			},
			SinkIdentifier: pulumi.Any(exampleAwsOamSink.Arn),
		}, pulumi.DependsOn([]pulumi.Resource{
			exampleAwsOamSinkPolicy,
		}))
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.Oam.Link("example", new()
    {
        LabelTemplate = "$AccountName",
        LinkConfiguration = new Aws.Oam.Inputs.LinkLinkConfigurationArgs
        {
            LogGroupConfiguration = new Aws.Oam.Inputs.LinkLinkConfigurationLogGroupConfigurationArgs
            {
                Filter = "LogGroupName LIKE 'aws/lambda/%' OR LogGroupName LIKE 'AWSLogs%'",
            },
        },
        ResourceTypes = new[]
        {
            "AWS::Logs::LogGroup",
        },
        SinkIdentifier = exampleAwsOamSink.Arn,
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            exampleAwsOamSinkPolicy,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.oam.Link;
import com.pulumi.aws.oam.LinkArgs;
import com.pulumi.aws.oam.inputs.LinkLinkConfigurationArgs;
import com.pulumi.aws.oam.inputs.LinkLinkConfigurationLogGroupConfigurationArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Link("example", LinkArgs.builder()
            .labelTemplate("$AccountName")
            .linkConfiguration(LinkLinkConfigurationArgs.builder()
                .logGroupConfiguration(LinkLinkConfigurationLogGroupConfigurationArgs.builder()
                    .filter("LogGroupName LIKE 'aws/lambda/%' OR LogGroupName LIKE 'AWSLogs%'")
                    .build())
                .build())
            .resourceTypes("AWS::Logs::LogGroup")
            .sinkIdentifier(exampleAwsOamSink.arn())
            .build(), CustomResourceOptions.builder()
                .dependsOn(exampleAwsOamSinkPolicy)
                .build());

    }
}
resources:
  example:
    type: aws:oam:Link
    properties:
      labelTemplate: $AccountName
      linkConfiguration:
        logGroupConfiguration:
          filter: LogGroupName LIKE 'aws/lambda/%' OR LogGroupName LIKE 'AWSLogs%'
      resourceTypes:
        - AWS::Logs::LogGroup
      sinkIdentifier: ${exampleAwsOamSink.arn}
    options:
      dependsOn:
        - ${exampleAwsOamSinkPolicy}

The linkConfiguration block enables filtering. The logGroupConfiguration.filter property uses a SQL-like expression to match log group names. This example shares only Lambda function logs and AWS service logs, excluding application-specific log groups. The filter evaluates against the LogGroupName attribute using LIKE patterns.

Filter metrics by namespace

Metric filtering lets you share specific AWS service metrics rather than all CloudWatch metrics from the source account.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.oam.Link("example", {
    labelTemplate: "$AccountName",
    linkConfiguration: {
        metricConfiguration: {
            filter: "Namespace IN ('AWS/EC2', 'AWS/ELB', 'AWS/S3')",
        },
    },
    resourceTypes: ["AWS::CloudWatch::Metric"],
    sinkIdentifier: exampleAwsOamSink.arn,
}, {
    dependsOn: [exampleAwsOamSinkPolicy],
});
import pulumi
import pulumi_aws as aws

example = aws.oam.Link("example",
    label_template="$AccountName",
    link_configuration={
        "metric_configuration": {
            "filter": "Namespace IN ('AWS/EC2', 'AWS/ELB', 'AWS/S3')",
        },
    },
    resource_types=["AWS::CloudWatch::Metric"],
    sink_identifier=example_aws_oam_sink["arn"],
    opts = pulumi.ResourceOptions(depends_on=[example_aws_oam_sink_policy]))
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/oam"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := oam.NewLink(ctx, "example", &oam.LinkArgs{
			LabelTemplate: pulumi.String("$AccountName"),
			LinkConfiguration: &oam.LinkLinkConfigurationArgs{
				MetricConfiguration: &oam.LinkLinkConfigurationMetricConfigurationArgs{
					Filter: pulumi.String("Namespace IN ('AWS/EC2', 'AWS/ELB', 'AWS/S3')"),
				},
			},
			ResourceTypes: pulumi.StringArray{
				pulumi.String("AWS::CloudWatch::Metric"),
			},
			SinkIdentifier: pulumi.Any(exampleAwsOamSink.Arn),
		}, pulumi.DependsOn([]pulumi.Resource{
			exampleAwsOamSinkPolicy,
		}))
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.Oam.Link("example", new()
    {
        LabelTemplate = "$AccountName",
        LinkConfiguration = new Aws.Oam.Inputs.LinkLinkConfigurationArgs
        {
            MetricConfiguration = new Aws.Oam.Inputs.LinkLinkConfigurationMetricConfigurationArgs
            {
                Filter = "Namespace IN ('AWS/EC2', 'AWS/ELB', 'AWS/S3')",
            },
        },
        ResourceTypes = new[]
        {
            "AWS::CloudWatch::Metric",
        },
        SinkIdentifier = exampleAwsOamSink.Arn,
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            exampleAwsOamSinkPolicy,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.oam.Link;
import com.pulumi.aws.oam.LinkArgs;
import com.pulumi.aws.oam.inputs.LinkLinkConfigurationArgs;
import com.pulumi.aws.oam.inputs.LinkLinkConfigurationMetricConfigurationArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Link("example", LinkArgs.builder()
            .labelTemplate("$AccountName")
            .linkConfiguration(LinkLinkConfigurationArgs.builder()
                .metricConfiguration(LinkLinkConfigurationMetricConfigurationArgs.builder()
                    .filter("Namespace IN ('AWS/EC2', 'AWS/ELB', 'AWS/S3')")
                    .build())
                .build())
            .resourceTypes("AWS::CloudWatch::Metric")
            .sinkIdentifier(exampleAwsOamSink.arn())
            .build(), CustomResourceOptions.builder()
                .dependsOn(exampleAwsOamSinkPolicy)
                .build());

    }
}
resources:
  example:
    type: aws:oam:Link
    properties:
      labelTemplate: $AccountName
      linkConfiguration:
        metricConfiguration:
          filter: Namespace IN ('AWS/EC2', 'AWS/ELB', 'AWS/S3')
      resourceTypes:
        - AWS::CloudWatch::Metric
      sinkIdentifier: ${exampleAwsOamSink.arn}
    options:
      dependsOn:
        - ${exampleAwsOamSinkPolicy}

The metricConfiguration.filter property restricts which metrics flow to the monitoring account. This example shares only EC2, ELB, and S3 metrics by filtering on the Namespace attribute. Custom application metrics and other AWS service metrics remain in the source account only.

Beyond these examples

These snippets focus on specific link-level features: cross-account telemetry sharing, log group and metric filtering, and source account labeling. They’re intentionally minimal rather than complete cross-account monitoring solutions.

The examples rely on pre-existing infrastructure such as a Sink resource in the monitoring account and a SinkPolicy authorizing the link. They focus on configuring the link rather than provisioning the monitoring account’s infrastructure.

To keep things focused, common link patterns are omitted, including:

  • Sink and SinkPolicy creation (monitoring account setup)
  • IAM permissions for cross-account access
  • Multiple resource types in a single link
  • Tag-based organization and cost tracking

These omissions are intentional: the goal is to illustrate how each link feature is wired, not provide drop-in cross-account monitoring modules. See the CloudWatch Observability Access Manager Link resource reference for all available configuration options.

Let's configure AWS CloudWatch Observability Access Manager Links

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Resource Dependencies & Creation
Why is my Link creation failing?
Link creation may fail if the aws.oam.SinkPolicy for the attached aws.oam.Sink isn’t created first. Use dependsOn to declare an explicit dependency on the SinkPolicy resource.
How do I ensure my Link is created in the correct order?
Add the SinkPolicy to your Link’s dependsOn array, as shown in the basic usage example. This ensures the policy exists before the Link is created.
Configuration & Resource Types
What are the required properties for creating a Link?
You must specify labelTemplate (human-readable name for the source account), sinkIdentifier (the sink to use), and resourceTypes (data types to share, like AWS::CloudWatch::Metric or AWS::Logs::LogGroup).
What properties can't be changed after creating a Link?
Both labelTemplate and sinkIdentifier are immutable. You’ll need to recreate the Link to change either property.
What resource types can I share with a monitoring account?
Common types include AWS::CloudWatch::Metric for metrics and AWS::Logs::LogGroup for logs. Specify these in the resourceTypes array.
Filtering & Data Selection
How do I filter which log groups are shared?
Use linkConfiguration.logGroupConfiguration.filter with syntax like LogGroupName LIKE 'aws/lambda/%' OR LogGroupName LIKE 'AWSLogs%' to specify patterns.
How do I filter which metrics are shared?
Use linkConfiguration.metricConfiguration.filter with syntax like Namespace IN ('AWS/EC2', 'AWS/ELB', 'AWS/S3') to specify namespaces.

Using a different cloud?

Explore monitoring guides for other cloud providers: