Configure AWS OpenSearch Serverless Access Policies

The aws:opensearch/serverlessAccessPolicy:ServerlessAccessPolicy resource, part of the Pulumi AWS provider, defines data access policies that control which principals can perform operations on OpenSearch Serverless collections and indexes. This guide focuses on three capabilities: IAM and SAML principal authorization, collection and index-level permissions, and read-only vs full-access patterns.

Access policies reference existing OpenSearch Serverless collections and IAM or SAML principals. The examples are intentionally small. Combine them with your own collection infrastructure and identity management.

Grant full access to collections and indexes

Most deployments start by granting a principal full access to collections and indexes for development or administrative workflows.

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

const current = aws.getCallerIdentity({});
const example = new aws.opensearch.ServerlessAccessPolicy("example", {
    name: "example",
    type: "data",
    description: "read and write permissions",
    policy: JSON.stringify([{
        Rules: [
            {
                ResourceType: "index",
                Resource: ["index/example-collection/*"],
                Permission: ["aoss:*"],
            },
            {
                ResourceType: "collection",
                Resource: ["collection/example-collection"],
                Permission: ["aoss:*"],
            },
        ],
        Principal: [current.then(current => current.arn)],
    }]),
});
import pulumi
import json
import pulumi_aws as aws

current = aws.get_caller_identity()
example = aws.opensearch.ServerlessAccessPolicy("example",
    name="example",
    type="data",
    description="read and write permissions",
    policy=json.dumps([{
        "Rules": [
            {
                "ResourceType": "index",
                "Resource": ["index/example-collection/*"],
                "Permission": ["aoss:*"],
            },
            {
                "ResourceType": "collection",
                "Resource": ["collection/example-collection"],
                "Permission": ["aoss:*"],
            },
        ],
        "Principal": [current.arn],
    }]))
package main

import (
	"encoding/json"

	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/opensearch"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		current, err := aws.GetCallerIdentity(ctx, &aws.GetCallerIdentityArgs{}, nil)
		if err != nil {
			return err
		}
		tmpJSON0, err := json.Marshal([]map[string]interface{}{
			map[string]interface{}{
				"Rules": []map[string]interface{}{
					map[string]interface{}{
						"ResourceType": "index",
						"Resource": []string{
							"index/example-collection/*",
						},
						"Permission": []string{
							"aoss:*",
						},
					},
					map[string]interface{}{
						"ResourceType": "collection",
						"Resource": []string{
							"collection/example-collection",
						},
						"Permission": []string{
							"aoss:*",
						},
					},
				},
				"Principal": []*string{
					current.Arn,
				},
			},
		})
		if err != nil {
			return err
		}
		json0 := string(tmpJSON0)
		_, err = opensearch.NewServerlessAccessPolicy(ctx, "example", &opensearch.ServerlessAccessPolicyArgs{
			Name:        pulumi.String("example"),
			Type:        pulumi.String("data"),
			Description: pulumi.String("read and write permissions"),
			Policy:      pulumi.String(json0),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var current = Aws.GetCallerIdentity.Invoke();

    var example = new Aws.OpenSearch.ServerlessAccessPolicy("example", new()
    {
        Name = "example",
        Type = "data",
        Description = "read and write permissions",
        Policy = JsonSerializer.Serialize(new[]
        {
            new Dictionary<string, object?>
            {
                ["Rules"] = new[]
                {
                    new Dictionary<string, object?>
                    {
                        ["ResourceType"] = "index",
                        ["Resource"] = new[]
                        {
                            "index/example-collection/*",
                        },
                        ["Permission"] = new[]
                        {
                            "aoss:*",
                        },
                    },
                    new Dictionary<string, object?>
                    {
                        ["ResourceType"] = "collection",
                        ["Resource"] = new[]
                        {
                            "collection/example-collection",
                        },
                        ["Permission"] = new[]
                        {
                            "aoss:*",
                        },
                    },
                },
                ["Principal"] = new[]
                {
                    current.Apply(getCallerIdentityResult => getCallerIdentityResult.Arn),
                },
            },
        }),
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.AwsFunctions;
import com.pulumi.aws.inputs.GetCallerIdentityArgs;
import com.pulumi.aws.opensearch.ServerlessAccessPolicy;
import com.pulumi.aws.opensearch.ServerlessAccessPolicyArgs;
import static com.pulumi.codegen.internal.Serialization.*;
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) {
        final var current = AwsFunctions.getCallerIdentity(GetCallerIdentityArgs.builder()
            .build());

        var example = new ServerlessAccessPolicy("example", ServerlessAccessPolicyArgs.builder()
            .name("example")
            .type("data")
            .description("read and write permissions")
            .policy(serializeJson(
                jsonArray(jsonObject(
                    jsonProperty("Rules", jsonArray(
                        jsonObject(
                            jsonProperty("ResourceType", "index"),
                            jsonProperty("Resource", jsonArray("index/example-collection/*")),
                            jsonProperty("Permission", jsonArray("aoss:*"))
                        ), 
                        jsonObject(
                            jsonProperty("ResourceType", "collection"),
                            jsonProperty("Resource", jsonArray("collection/example-collection")),
                            jsonProperty("Permission", jsonArray("aoss:*"))
                        )
                    )),
                    jsonProperty("Principal", jsonArray(current.arn()))
                ))))
            .build());

    }
}
resources:
  example:
    type: aws:opensearch:ServerlessAccessPolicy
    properties:
      name: example
      type: data
      description: read and write permissions
      policy:
        fn::toJSON:
          - Rules:
              - ResourceType: index
                Resource:
                  - index/example-collection/*
                Permission:
                  - aoss:*
              - ResourceType: collection
                Resource:
                  - collection/example-collection
                Permission:
                  - aoss:*
            Principal:
              - ${current.arn}
variables:
  current:
    fn::invoke:
      function: aws:getCallerIdentity
      arguments: {}

The policy property contains a JSON document with Rules that specify ResourceType (collection or index), Resource patterns, and Permission arrays. The aoss:* permission grants all operations. Principal references the current caller’s ARN, allowing that identity to read, write, and manage the collection and its indexes.

Restrict access to read-only operations

Applications that only query data benefit from read-only permissions that prevent accidental modifications.

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

const current = aws.getCallerIdentity({});
const example = new aws.opensearch.ServerlessAccessPolicy("example", {
    name: "example",
    type: "data",
    description: "read-only permissions",
    policy: JSON.stringify([{
        Rules: [
            {
                ResourceType: "index",
                Resource: ["index/example-collection/*"],
                Permission: [
                    "aoss:DescribeIndex",
                    "aoss:ReadDocument",
                ],
            },
            {
                ResourceType: "collection",
                Resource: ["collection/example-collection"],
                Permission: ["aoss:DescribeCollectionItems"],
            },
        ],
        Principal: [current.then(current => current.arn)],
    }]),
});
import pulumi
import json
import pulumi_aws as aws

current = aws.get_caller_identity()
example = aws.opensearch.ServerlessAccessPolicy("example",
    name="example",
    type="data",
    description="read-only permissions",
    policy=json.dumps([{
        "Rules": [
            {
                "ResourceType": "index",
                "Resource": ["index/example-collection/*"],
                "Permission": [
                    "aoss:DescribeIndex",
                    "aoss:ReadDocument",
                ],
            },
            {
                "ResourceType": "collection",
                "Resource": ["collection/example-collection"],
                "Permission": ["aoss:DescribeCollectionItems"],
            },
        ],
        "Principal": [current.arn],
    }]))
package main

import (
	"encoding/json"

	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/opensearch"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		current, err := aws.GetCallerIdentity(ctx, &aws.GetCallerIdentityArgs{}, nil)
		if err != nil {
			return err
		}
		tmpJSON0, err := json.Marshal([]map[string]interface{}{
			map[string]interface{}{
				"Rules": []interface{}{
					map[string]interface{}{
						"ResourceType": "index",
						"Resource": []string{
							"index/example-collection/*",
						},
						"Permission": []string{
							"aoss:DescribeIndex",
							"aoss:ReadDocument",
						},
					},
					map[string]interface{}{
						"ResourceType": "collection",
						"Resource": []string{
							"collection/example-collection",
						},
						"Permission": []string{
							"aoss:DescribeCollectionItems",
						},
					},
				},
				"Principal": []*string{
					current.Arn,
				},
			},
		})
		if err != nil {
			return err
		}
		json0 := string(tmpJSON0)
		_, err = opensearch.NewServerlessAccessPolicy(ctx, "example", &opensearch.ServerlessAccessPolicyArgs{
			Name:        pulumi.String("example"),
			Type:        pulumi.String("data"),
			Description: pulumi.String("read-only permissions"),
			Policy:      pulumi.String(json0),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var current = Aws.GetCallerIdentity.Invoke();

    var example = new Aws.OpenSearch.ServerlessAccessPolicy("example", new()
    {
        Name = "example",
        Type = "data",
        Description = "read-only permissions",
        Policy = JsonSerializer.Serialize(new[]
        {
            new Dictionary<string, object?>
            {
                ["Rules"] = new[]
                {
                    new Dictionary<string, object?>
                    {
                        ["ResourceType"] = "index",
                        ["Resource"] = new[]
                        {
                            "index/example-collection/*",
                        },
                        ["Permission"] = new[]
                        {
                            "aoss:DescribeIndex",
                            "aoss:ReadDocument",
                        },
                    },
                    new Dictionary<string, object?>
                    {
                        ["ResourceType"] = "collection",
                        ["Resource"] = new[]
                        {
                            "collection/example-collection",
                        },
                        ["Permission"] = new[]
                        {
                            "aoss:DescribeCollectionItems",
                        },
                    },
                },
                ["Principal"] = new[]
                {
                    current.Apply(getCallerIdentityResult => getCallerIdentityResult.Arn),
                },
            },
        }),
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.AwsFunctions;
import com.pulumi.aws.inputs.GetCallerIdentityArgs;
import com.pulumi.aws.opensearch.ServerlessAccessPolicy;
import com.pulumi.aws.opensearch.ServerlessAccessPolicyArgs;
import static com.pulumi.codegen.internal.Serialization.*;
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) {
        final var current = AwsFunctions.getCallerIdentity(GetCallerIdentityArgs.builder()
            .build());

        var example = new ServerlessAccessPolicy("example", ServerlessAccessPolicyArgs.builder()
            .name("example")
            .type("data")
            .description("read-only permissions")
            .policy(serializeJson(
                jsonArray(jsonObject(
                    jsonProperty("Rules", jsonArray(
                        jsonObject(
                            jsonProperty("ResourceType", "index"),
                            jsonProperty("Resource", jsonArray("index/example-collection/*")),
                            jsonProperty("Permission", jsonArray(
                                "aoss:DescribeIndex", 
                                "aoss:ReadDocument"
                            ))
                        ), 
                        jsonObject(
                            jsonProperty("ResourceType", "collection"),
                            jsonProperty("Resource", jsonArray("collection/example-collection")),
                            jsonProperty("Permission", jsonArray("aoss:DescribeCollectionItems"))
                        )
                    )),
                    jsonProperty("Principal", jsonArray(current.arn()))
                ))))
            .build());

    }
}
resources:
  example:
    type: aws:opensearch:ServerlessAccessPolicy
    properties:
      name: example
      type: data
      description: read-only permissions
      policy:
        fn::toJSON:
          - Rules:
              - ResourceType: index
                Resource:
                  - index/example-collection/*
                Permission:
                  - aoss:DescribeIndex
                  - aoss:ReadDocument
              - ResourceType: collection
                Resource:
                  - collection/example-collection
                Permission:
                  - aoss:DescribeCollectionItems
            Principal:
              - ${current.arn}
variables:
  current:
    fn::invoke:
      function: aws:getCallerIdentity
      arguments: {}

This configuration restricts permissions to describe and read operations. For indexes, aoss:DescribeIndex and aoss:ReadDocument allow querying without writes. For collections, aoss:DescribeCollectionItems permits listing contents. This pattern extends the full-access example by replacing aoss:* with specific operation permissions.

Grant access to SAML-federated identities

Organizations using SAML federation can grant access to federated users and groups without creating individual IAM principals.

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

const example = new aws.opensearch.ServerlessAccessPolicy("example", {
    name: "example",
    type: "data",
    description: "saml permissions",
    policy: JSON.stringify([{
        Rules: [
            {
                ResourceType: "index",
                Resource: ["index/example-collection/*"],
                Permission: ["aoss:*"],
            },
            {
                ResourceType: "collection",
                Resource: ["collection/example-collection"],
                Permission: ["aoss:*"],
            },
        ],
        Principal: [
            "saml/123456789012/myprovider/user/Annie",
            "saml/123456789012/anotherprovider/group/Accounting",
        ],
    }]),
});
import pulumi
import json
import pulumi_aws as aws

example = aws.opensearch.ServerlessAccessPolicy("example",
    name="example",
    type="data",
    description="saml permissions",
    policy=json.dumps([{
        "Rules": [
            {
                "ResourceType": "index",
                "Resource": ["index/example-collection/*"],
                "Permission": ["aoss:*"],
            },
            {
                "ResourceType": "collection",
                "Resource": ["collection/example-collection"],
                "Permission": ["aoss:*"],
            },
        ],
        "Principal": [
            "saml/123456789012/myprovider/user/Annie",
            "saml/123456789012/anotherprovider/group/Accounting",
        ],
    }]))
package main

import (
	"encoding/json"

	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/opensearch"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		tmpJSON0, err := json.Marshal([]map[string]interface{}{
			map[string]interface{}{
				"Rules": []map[string]interface{}{
					map[string]interface{}{
						"ResourceType": "index",
						"Resource": []string{
							"index/example-collection/*",
						},
						"Permission": []string{
							"aoss:*",
						},
					},
					map[string]interface{}{
						"ResourceType": "collection",
						"Resource": []string{
							"collection/example-collection",
						},
						"Permission": []string{
							"aoss:*",
						},
					},
				},
				"Principal": []string{
					"saml/123456789012/myprovider/user/Annie",
					"saml/123456789012/anotherprovider/group/Accounting",
				},
			},
		})
		if err != nil {
			return err
		}
		json0 := string(tmpJSON0)
		_, err = opensearch.NewServerlessAccessPolicy(ctx, "example", &opensearch.ServerlessAccessPolicyArgs{
			Name:        pulumi.String("example"),
			Type:        pulumi.String("data"),
			Description: pulumi.String("saml permissions"),
			Policy:      pulumi.String(json0),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.OpenSearch.ServerlessAccessPolicy("example", new()
    {
        Name = "example",
        Type = "data",
        Description = "saml permissions",
        Policy = JsonSerializer.Serialize(new[]
        {
            new Dictionary<string, object?>
            {
                ["Rules"] = new[]
                {
                    new Dictionary<string, object?>
                    {
                        ["ResourceType"] = "index",
                        ["Resource"] = new[]
                        {
                            "index/example-collection/*",
                        },
                        ["Permission"] = new[]
                        {
                            "aoss:*",
                        },
                    },
                    new Dictionary<string, object?>
                    {
                        ["ResourceType"] = "collection",
                        ["Resource"] = new[]
                        {
                            "collection/example-collection",
                        },
                        ["Permission"] = new[]
                        {
                            "aoss:*",
                        },
                    },
                },
                ["Principal"] = new[]
                {
                    "saml/123456789012/myprovider/user/Annie",
                    "saml/123456789012/anotherprovider/group/Accounting",
                },
            },
        }),
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.opensearch.ServerlessAccessPolicy;
import com.pulumi.aws.opensearch.ServerlessAccessPolicyArgs;
import static com.pulumi.codegen.internal.Serialization.*;
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 ServerlessAccessPolicy("example", ServerlessAccessPolicyArgs.builder()
            .name("example")
            .type("data")
            .description("saml permissions")
            .policy(serializeJson(
                jsonArray(jsonObject(
                    jsonProperty("Rules", jsonArray(
                        jsonObject(
                            jsonProperty("ResourceType", "index"),
                            jsonProperty("Resource", jsonArray("index/example-collection/*")),
                            jsonProperty("Permission", jsonArray("aoss:*"))
                        ), 
                        jsonObject(
                            jsonProperty("ResourceType", "collection"),
                            jsonProperty("Resource", jsonArray("collection/example-collection")),
                            jsonProperty("Permission", jsonArray("aoss:*"))
                        )
                    )),
                    jsonProperty("Principal", jsonArray(
                        "saml/123456789012/myprovider/user/Annie", 
                        "saml/123456789012/anotherprovider/group/Accounting"
                    ))
                ))))
            .build());

    }
}
resources:
  example:
    type: aws:opensearch:ServerlessAccessPolicy
    properties:
      name: example
      type: data
      description: saml permissions
      policy:
        fn::toJSON:
          - Rules:
              - ResourceType: index
                Resource:
                  - index/example-collection/*
                Permission:
                  - aoss:*
              - ResourceType: collection
                Resource:
                  - collection/example-collection
                Permission:
                  - aoss:*
            Principal:
              - saml/123456789012/myprovider/user/Annie
              - saml/123456789012/anotherprovider/group/Accounting

The Principal array accepts SAML identity paths in the format saml/{account-id}/{provider-name}/user/{username} or saml/{account-id}/{provider-name}/group/{groupname}. This allows centralized identity management through your SAML provider rather than managing IAM users directly.

Beyond these examples

These snippets focus on specific access policy features: IAM and SAML principal authorization, collection and index-level permissions, and read-only vs full-access patterns. They’re intentionally minimal rather than full access control systems.

The examples reference pre-existing infrastructure such as OpenSearch Serverless collections and SAML identity providers (for federation example). They focus on configuring access policies rather than provisioning collections or identity infrastructure.

To keep things focused, common access policy patterns are omitted, including:

  • Policy versioning and updates (policyVersion)
  • Cross-region access policies
  • Wildcard resource patterns beyond single collection
  • Time-based or conditional access rules

These omissions are intentional: the goal is to illustrate how each access policy feature is wired, not provide drop-in security modules. See the OpenSearch Serverless Access Policy resource reference for all available configuration options.

Let's configure AWS OpenSearch Serverless Access Policies

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Policy Structure & Configuration
How do I structure the access policy JSON?
The policy is a JSON array containing objects with Rules and Principal fields. Each rule specifies ResourceType (either index or collection), Resource (the resource path), and Permission (an array of allowed actions).
What's the difference between index and collection resource types?
Collection permissions (e.g., aoss:DescribeCollectionItems) apply to the collection itself, while index permissions (e.g., aoss:ReadDocument) apply to indices within the collection. Most policies need both types to function properly.
What value should I use for the type field?
The type field must be set to data, which is currently the only valid value for OpenSearch Serverless access policies.
Permissions & Access Control
How do I grant read-only access to a collection?
Use specific permissions instead of aoss:*. For indices, use aoss:DescribeIndex and aoss:ReadDocument. For collections, use aoss:DescribeCollectionItems.
Can I use wildcards in resource paths?
Yes, use patterns like index/example-collection/* to match all indices within a collection. This is the standard pattern for granting index-level permissions.
How do I grant access to SAML identities?
In the Principal array, use the format saml/account-id/provider/user/username for individual users or saml/account-id/provider/group/groupname for groups.

Using a different cloud?

Explore security guides for other cloud providers: