The gcp:accesscontextmanager/servicePerimeter:ServicePerimeter resource, part of the Pulumi GCP provider, defines VPC Service Controls perimeters that restrict which GCP services projects can access and control data movement between perimeters. This guide focuses on three capabilities: service restriction configuration, ingress and egress traffic policies, and dry-run testing for policy changes.
Service perimeters belong to Access Context Manager access policies and reference access levels for identity-based controls. The examples are intentionally small. Combine them with your own access policies, access levels, and project membership configuration.
Restrict service access with a basic perimeter
Organizations implementing zero-trust security start by defining perimeters that control which GCP services projects can access.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const access_policy = new gcp.accesscontextmanager.AccessPolicy("access-policy", {
parent: "organizations/123456789",
title: "my policy",
});
const service_perimeter = new gcp.accesscontextmanager.ServicePerimeter("service-perimeter", {
parent: pulumi.interpolate`accessPolicies/${access_policy.name}`,
name: pulumi.interpolate`accessPolicies/${access_policy.name}/servicePerimeters/restrict_storage`,
title: "restrict_storage",
status: {
restrictedServices: ["storage.googleapis.com"],
},
});
const access_level = new gcp.accesscontextmanager.AccessLevel("access-level", {
parent: pulumi.interpolate`accessPolicies/${access_policy.name}`,
name: pulumi.interpolate`accessPolicies/${access_policy.name}/accessLevels/chromeos_no_lock`,
title: "chromeos_no_lock",
basic: {
conditions: [{
devicePolicy: {
requireScreenLock: false,
osConstraints: [{
osType: "DESKTOP_CHROME_OS",
}],
},
regions: [
"CH",
"IT",
"US",
],
}],
},
});
import pulumi
import pulumi_gcp as gcp
access_policy = gcp.accesscontextmanager.AccessPolicy("access-policy",
parent="organizations/123456789",
title="my policy")
service_perimeter = gcp.accesscontextmanager.ServicePerimeter("service-perimeter",
parent=access_policy.name.apply(lambda name: f"accessPolicies/{name}"),
name=access_policy.name.apply(lambda name: f"accessPolicies/{name}/servicePerimeters/restrict_storage"),
title="restrict_storage",
status={
"restricted_services": ["storage.googleapis.com"],
})
access_level = gcp.accesscontextmanager.AccessLevel("access-level",
parent=access_policy.name.apply(lambda name: f"accessPolicies/{name}"),
name=access_policy.name.apply(lambda name: f"accessPolicies/{name}/accessLevels/chromeos_no_lock"),
title="chromeos_no_lock",
basic={
"conditions": [{
"device_policy": {
"require_screen_lock": False,
"os_constraints": [{
"os_type": "DESKTOP_CHROME_OS",
}],
},
"regions": [
"CH",
"IT",
"US",
],
}],
})
package main
import (
"fmt"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/accesscontextmanager"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
access_policy, err := accesscontextmanager.NewAccessPolicy(ctx, "access-policy", &accesscontextmanager.AccessPolicyArgs{
Parent: pulumi.String("organizations/123456789"),
Title: pulumi.String("my policy"),
})
if err != nil {
return err
}
_, err = accesscontextmanager.NewServicePerimeter(ctx, "service-perimeter", &accesscontextmanager.ServicePerimeterArgs{
Parent: access_policy.Name.ApplyT(func(name string) (string, error) {
return fmt.Sprintf("accessPolicies/%v", name), nil
}).(pulumi.StringOutput),
Name: access_policy.Name.ApplyT(func(name string) (string, error) {
return fmt.Sprintf("accessPolicies/%v/servicePerimeters/restrict_storage", name), nil
}).(pulumi.StringOutput),
Title: pulumi.String("restrict_storage"),
Status: &accesscontextmanager.ServicePerimeterStatusArgs{
RestrictedServices: pulumi.StringArray{
pulumi.String("storage.googleapis.com"),
},
},
})
if err != nil {
return err
}
_, err = accesscontextmanager.NewAccessLevel(ctx, "access-level", &accesscontextmanager.AccessLevelArgs{
Parent: access_policy.Name.ApplyT(func(name string) (string, error) {
return fmt.Sprintf("accessPolicies/%v", name), nil
}).(pulumi.StringOutput),
Name: access_policy.Name.ApplyT(func(name string) (string, error) {
return fmt.Sprintf("accessPolicies/%v/accessLevels/chromeos_no_lock", name), nil
}).(pulumi.StringOutput),
Title: pulumi.String("chromeos_no_lock"),
Basic: &accesscontextmanager.AccessLevelBasicArgs{
Conditions: accesscontextmanager.AccessLevelBasicConditionArray{
&accesscontextmanager.AccessLevelBasicConditionArgs{
DevicePolicy: &accesscontextmanager.AccessLevelBasicConditionDevicePolicyArgs{
RequireScreenLock: pulumi.Bool(false),
OsConstraints: accesscontextmanager.AccessLevelBasicConditionDevicePolicyOsConstraintArray{
&accesscontextmanager.AccessLevelBasicConditionDevicePolicyOsConstraintArgs{
OsType: pulumi.String("DESKTOP_CHROME_OS"),
},
},
},
Regions: pulumi.StringArray{
pulumi.String("CH"),
pulumi.String("IT"),
pulumi.String("US"),
},
},
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var access_policy = new Gcp.AccessContextManager.AccessPolicy("access-policy", new()
{
Parent = "organizations/123456789",
Title = "my policy",
});
var service_perimeter = new Gcp.AccessContextManager.ServicePerimeter("service-perimeter", new()
{
Parent = access_policy.Name.Apply(name => $"accessPolicies/{name}"),
Name = access_policy.Name.Apply(name => $"accessPolicies/{name}/servicePerimeters/restrict_storage"),
Title = "restrict_storage",
Status = new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusArgs
{
RestrictedServices = new[]
{
"storage.googleapis.com",
},
},
});
var access_level = new Gcp.AccessContextManager.AccessLevel("access-level", new()
{
Parent = access_policy.Name.Apply(name => $"accessPolicies/{name}"),
Name = access_policy.Name.Apply(name => $"accessPolicies/{name}/accessLevels/chromeos_no_lock"),
Title = "chromeos_no_lock",
Basic = new Gcp.AccessContextManager.Inputs.AccessLevelBasicArgs
{
Conditions = new[]
{
new Gcp.AccessContextManager.Inputs.AccessLevelBasicConditionArgs
{
DevicePolicy = new Gcp.AccessContextManager.Inputs.AccessLevelBasicConditionDevicePolicyArgs
{
RequireScreenLock = false,
OsConstraints = new[]
{
new Gcp.AccessContextManager.Inputs.AccessLevelBasicConditionDevicePolicyOsConstraintArgs
{
OsType = "DESKTOP_CHROME_OS",
},
},
},
Regions = new[]
{
"CH",
"IT",
"US",
},
},
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.accesscontextmanager.AccessPolicy;
import com.pulumi.gcp.accesscontextmanager.AccessPolicyArgs;
import com.pulumi.gcp.accesscontextmanager.ServicePerimeter;
import com.pulumi.gcp.accesscontextmanager.ServicePerimeterArgs;
import com.pulumi.gcp.accesscontextmanager.inputs.ServicePerimeterStatusArgs;
import com.pulumi.gcp.accesscontextmanager.AccessLevel;
import com.pulumi.gcp.accesscontextmanager.AccessLevelArgs;
import com.pulumi.gcp.accesscontextmanager.inputs.AccessLevelBasicArgs;
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 access_policy = new AccessPolicy("access-policy", AccessPolicyArgs.builder()
.parent("organizations/123456789")
.title("my policy")
.build());
var service_perimeter = new ServicePerimeter("service-perimeter", ServicePerimeterArgs.builder()
.parent(access_policy.name().applyValue(_name -> String.format("accessPolicies/%s", _name)))
.name(access_policy.name().applyValue(_name -> String.format("accessPolicies/%s/servicePerimeters/restrict_storage", _name)))
.title("restrict_storage")
.status(ServicePerimeterStatusArgs.builder()
.restrictedServices("storage.googleapis.com")
.build())
.build());
var access_level = new AccessLevel("access-level", AccessLevelArgs.builder()
.parent(access_policy.name().applyValue(_name -> String.format("accessPolicies/%s", _name)))
.name(access_policy.name().applyValue(_name -> String.format("accessPolicies/%s/accessLevels/chromeos_no_lock", _name)))
.title("chromeos_no_lock")
.basic(AccessLevelBasicArgs.builder()
.conditions(AccessLevelBasicConditionArgs.builder()
.devicePolicy(AccessLevelBasicConditionDevicePolicyArgs.builder()
.requireScreenLock(false)
.osConstraints(AccessLevelBasicConditionDevicePolicyOsConstraintArgs.builder()
.osType("DESKTOP_CHROME_OS")
.build())
.build())
.regions(
"CH",
"IT",
"US")
.build())
.build())
.build());
}
}
resources:
service-perimeter:
type: gcp:accesscontextmanager:ServicePerimeter
properties:
parent: accessPolicies/${["access-policy"].name}
name: accessPolicies/${["access-policy"].name}/servicePerimeters/restrict_storage
title: restrict_storage
status:
restrictedServices:
- storage.googleapis.com
access-level:
type: gcp:accesscontextmanager:AccessLevel
properties:
parent: accessPolicies/${["access-policy"].name}
name: accessPolicies/${["access-policy"].name}/accessLevels/chromeos_no_lock
title: chromeos_no_lock
basic:
conditions:
- devicePolicy:
requireScreenLock: false
osConstraints:
- osType: DESKTOP_CHROME_OS
regions:
- CH
- IT
- US
access-policy:
type: gcp:accesscontextmanager:AccessPolicy
properties:
parent: organizations/123456789
title: my policy
The status block defines the enforced configuration. The restrictedServices list blocks external access to specified APIs; here, Cloud Storage is restricted while projects inside the perimeter can still communicate. The parent property links the perimeter to an access policy, and the name follows the format accessPolicies/{policy_id}/servicePerimeters/{short_name}.
Control ingress and egress with traffic policies
Data exchange between perimeters requires explicit rules that define which identities can access resources and which operations they can perform.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const access_policy = new gcp.accesscontextmanager.AccessPolicy("access-policy", {
parent: "organizations/123456789",
title: "my policy",
});
const secure_data_exchange = new gcp.accesscontextmanager.ServicePerimeters("secure-data-exchange", {
parent: pulumi.interpolate`accessPolicies/${access_policy.name}`,
servicePerimeters: [
{
name: pulumi.interpolate`accessPolicies/${access_policy.name}/servicePerimeters/`,
title: "",
status: {
restrictedServices: ["storage.googleapis.com"],
},
},
{
name: pulumi.interpolate`accessPolicies/${access_policy.name}/servicePerimeters/`,
title: "",
status: {
restrictedServices: ["bigtable.googleapis.com"],
vpcAccessibleServices: {
enableRestriction: true,
allowedServices: ["bigquery.googleapis.com"],
},
},
},
],
});
const access_level = new gcp.accesscontextmanager.AccessLevel("access-level", {
parent: pulumi.interpolate`accessPolicies/${access_policy.name}`,
name: pulumi.interpolate`accessPolicies/${access_policy.name}/accessLevels/secure_data_exchange`,
title: "secure_data_exchange",
basic: {
conditions: [{
devicePolicy: {
requireScreenLock: false,
osConstraints: [{
osType: "DESKTOP_CHROME_OS",
}],
},
regions: [
"CH",
"IT",
"US",
],
}],
},
});
const test_access = new gcp.accesscontextmanager.ServicePerimeter("test-access", {
parent: `accessPolicies/${test_accessGoogleAccessContextManagerAccessPolicy.name}`,
name: `accessPolicies/${test_accessGoogleAccessContextManagerAccessPolicy.name}/servicePerimeters/%s`,
title: "%s",
perimeterType: "PERIMETER_TYPE_REGULAR",
status: {
restrictedServices: [
"bigquery.googleapis.com",
"storage.googleapis.com",
],
accessLevels: [access_level.name],
vpcAccessibleServices: {
enableRestriction: true,
allowedServices: [
"bigquery.googleapis.com",
"storage.googleapis.com",
],
},
ingressPolicies: [{
ingressFrom: {
sources: [{
accessLevel: test_accessGoogleAccessContextManagerAccessLevel.name,
}],
identityType: "ANY_IDENTITY",
},
ingressTo: {
resources: ["*"],
operations: [
{
serviceName: "bigquery.googleapis.com",
methodSelectors: [
{
method: "BigQueryStorage.ReadRows",
},
{
method: "TableService.ListTables",
},
{
permission: "bigquery.jobs.get",
},
],
},
{
serviceName: "storage.googleapis.com",
methodSelectors: [{
method: "google.storage.objects.create",
}],
},
],
},
}],
egressPolicies: [{
egressFrom: {
identityType: "ANY_USER_ACCOUNT",
},
}],
},
});
import pulumi
import pulumi_gcp as gcp
access_policy = gcp.accesscontextmanager.AccessPolicy("access-policy",
parent="organizations/123456789",
title="my policy")
secure_data_exchange = gcp.accesscontextmanager.ServicePerimeters("secure-data-exchange",
parent=access_policy.name.apply(lambda name: f"accessPolicies/{name}"),
service_perimeters=[
{
"name": access_policy.name.apply(lambda name: f"accessPolicies/{name}/servicePerimeters/"),
"title": "",
"status": {
"restricted_services": ["storage.googleapis.com"],
},
},
{
"name": access_policy.name.apply(lambda name: f"accessPolicies/{name}/servicePerimeters/"),
"title": "",
"status": {
"restricted_services": ["bigtable.googleapis.com"],
"vpc_accessible_services": {
"enable_restriction": True,
"allowed_services": ["bigquery.googleapis.com"],
},
},
},
])
access_level = gcp.accesscontextmanager.AccessLevel("access-level",
parent=access_policy.name.apply(lambda name: f"accessPolicies/{name}"),
name=access_policy.name.apply(lambda name: f"accessPolicies/{name}/accessLevels/secure_data_exchange"),
title="secure_data_exchange",
basic={
"conditions": [{
"device_policy": {
"require_screen_lock": False,
"os_constraints": [{
"os_type": "DESKTOP_CHROME_OS",
}],
},
"regions": [
"CH",
"IT",
"US",
],
}],
})
test_access = gcp.accesscontextmanager.ServicePerimeter("test-access",
parent=f"accessPolicies/{test_access_google_access_context_manager_access_policy['name']}",
name=f"accessPolicies/{test_access_google_access_context_manager_access_policy['name']}/servicePerimeters/%s",
title="%s",
perimeter_type="PERIMETER_TYPE_REGULAR",
status={
"restricted_services": [
"bigquery.googleapis.com",
"storage.googleapis.com",
],
"access_levels": [access_level.name],
"vpc_accessible_services": {
"enable_restriction": True,
"allowed_services": [
"bigquery.googleapis.com",
"storage.googleapis.com",
],
},
"ingress_policies": [{
"ingress_from": {
"sources": [{
"access_level": test_access_google_access_context_manager_access_level["name"],
}],
"identity_type": "ANY_IDENTITY",
},
"ingress_to": {
"resources": ["*"],
"operations": [
{
"service_name": "bigquery.googleapis.com",
"method_selectors": [
{
"method": "BigQueryStorage.ReadRows",
},
{
"method": "TableService.ListTables",
},
{
"permission": "bigquery.jobs.get",
},
],
},
{
"service_name": "storage.googleapis.com",
"method_selectors": [{
"method": "google.storage.objects.create",
}],
},
],
},
}],
"egress_policies": [{
"egress_from": {
"identity_type": "ANY_USER_ACCOUNT",
},
}],
})
package main
import (
"fmt"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/accesscontextmanager"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
access_policy, err := accesscontextmanager.NewAccessPolicy(ctx, "access-policy", &accesscontextmanager.AccessPolicyArgs{
Parent: pulumi.String("organizations/123456789"),
Title: pulumi.String("my policy"),
})
if err != nil {
return err
}
_, err = accesscontextmanager.NewServicePerimeters(ctx, "secure-data-exchange", &accesscontextmanager.ServicePerimetersArgs{
Parent: access_policy.Name.ApplyT(func(name string) (string, error) {
return fmt.Sprintf("accessPolicies/%v", name), nil
}).(pulumi.StringOutput),
ServicePerimeters: accesscontextmanager.ServicePerimetersServicePerimeterArray{
&accesscontextmanager.ServicePerimetersServicePerimeterArgs{
Name: access_policy.Name.ApplyT(func(name string) (string, error) {
return fmt.Sprintf("accessPolicies/%v/servicePerimeters/", name), nil
}).(pulumi.StringOutput),
Title: pulumi.String(""),
Status: &accesscontextmanager.ServicePerimetersServicePerimeterStatusArgs{
RestrictedServices: pulumi.StringArray{
pulumi.String("storage.googleapis.com"),
},
},
},
&accesscontextmanager.ServicePerimetersServicePerimeterArgs{
Name: access_policy.Name.ApplyT(func(name string) (string, error) {
return fmt.Sprintf("accessPolicies/%v/servicePerimeters/", name), nil
}).(pulumi.StringOutput),
Title: pulumi.String(""),
Status: &accesscontextmanager.ServicePerimetersServicePerimeterStatusArgs{
RestrictedServices: pulumi.StringArray{
pulumi.String("bigtable.googleapis.com"),
},
VpcAccessibleServices: &accesscontextmanager.ServicePerimetersServicePerimeterStatusVpcAccessibleServicesArgs{
EnableRestriction: pulumi.Bool(true),
AllowedServices: pulumi.StringArray{
pulumi.String("bigquery.googleapis.com"),
},
},
},
},
},
})
if err != nil {
return err
}
access_level, err := accesscontextmanager.NewAccessLevel(ctx, "access-level", &accesscontextmanager.AccessLevelArgs{
Parent: access_policy.Name.ApplyT(func(name string) (string, error) {
return fmt.Sprintf("accessPolicies/%v", name), nil
}).(pulumi.StringOutput),
Name: access_policy.Name.ApplyT(func(name string) (string, error) {
return fmt.Sprintf("accessPolicies/%v/accessLevels/secure_data_exchange", name), nil
}).(pulumi.StringOutput),
Title: pulumi.String("secure_data_exchange"),
Basic: &accesscontextmanager.AccessLevelBasicArgs{
Conditions: accesscontextmanager.AccessLevelBasicConditionArray{
&accesscontextmanager.AccessLevelBasicConditionArgs{
DevicePolicy: &accesscontextmanager.AccessLevelBasicConditionDevicePolicyArgs{
RequireScreenLock: pulumi.Bool(false),
OsConstraints: accesscontextmanager.AccessLevelBasicConditionDevicePolicyOsConstraintArray{
&accesscontextmanager.AccessLevelBasicConditionDevicePolicyOsConstraintArgs{
OsType: pulumi.String("DESKTOP_CHROME_OS"),
},
},
},
Regions: pulumi.StringArray{
pulumi.String("CH"),
pulumi.String("IT"),
pulumi.String("US"),
},
},
},
},
})
if err != nil {
return err
}
_, err = accesscontextmanager.NewServicePerimeter(ctx, "test-access", &accesscontextmanager.ServicePerimeterArgs{
Parent: pulumi.Sprintf("accessPolicies/%v", test_accessGoogleAccessContextManagerAccessPolicy.Name),
Name: pulumi.Sprintf("accessPolicies/%v%v", test_accessGoogleAccessContextManagerAccessPolicy.Name, "/servicePerimeters/%s"),
Title: pulumi.String("%s"),
PerimeterType: pulumi.String("PERIMETER_TYPE_REGULAR"),
Status: &accesscontextmanager.ServicePerimeterStatusArgs{
RestrictedServices: pulumi.StringArray{
pulumi.String("bigquery.googleapis.com"),
pulumi.String("storage.googleapis.com"),
},
AccessLevels: pulumi.StringArray{
access_level.Name,
},
VpcAccessibleServices: &accesscontextmanager.ServicePerimeterStatusVpcAccessibleServicesArgs{
EnableRestriction: pulumi.Bool(true),
AllowedServices: pulumi.StringArray{
pulumi.String("bigquery.googleapis.com"),
pulumi.String("storage.googleapis.com"),
},
},
IngressPolicies: accesscontextmanager.ServicePerimeterStatusIngressPolicyArray{
&accesscontextmanager.ServicePerimeterStatusIngressPolicyArgs{
IngressFrom: &accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressFromArgs{
Sources: accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressFromSourceArray{
&accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressFromSourceArgs{
AccessLevel: pulumi.Any(test_accessGoogleAccessContextManagerAccessLevel.Name),
},
},
IdentityType: pulumi.String("ANY_IDENTITY"),
},
IngressTo: &accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressToArgs{
Resources: pulumi.StringArray{
pulumi.String("*"),
},
Operations: accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressToOperationArray{
&accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressToOperationArgs{
ServiceName: pulumi.String("bigquery.googleapis.com"),
MethodSelectors: accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArray{
&accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArgs{
Method: pulumi.String("BigQueryStorage.ReadRows"),
},
&accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArgs{
Method: pulumi.String("TableService.ListTables"),
},
&accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArgs{
Permission: pulumi.String("bigquery.jobs.get"),
},
},
},
&accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressToOperationArgs{
ServiceName: pulumi.String("storage.googleapis.com"),
MethodSelectors: accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArray{
&accesscontextmanager.ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArgs{
Method: pulumi.String("google.storage.objects.create"),
},
},
},
},
},
},
},
EgressPolicies: accesscontextmanager.ServicePerimeterStatusEgressPolicyArray{
&accesscontextmanager.ServicePerimeterStatusEgressPolicyArgs{
EgressFrom: &accesscontextmanager.ServicePerimeterStatusEgressPolicyEgressFromArgs{
IdentityType: pulumi.String("ANY_USER_ACCOUNT"),
},
},
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var access_policy = new Gcp.AccessContextManager.AccessPolicy("access-policy", new()
{
Parent = "organizations/123456789",
Title = "my policy",
});
var secure_data_exchange = new Gcp.AccessContextManager.ServicePerimeters("secure-data-exchange", new()
{
Parent = access_policy.Name.Apply(name => $"accessPolicies/{name}"),
ServicePerimeterDetails = new[]
{
new Gcp.AccessContextManager.Inputs.ServicePerimetersServicePerimeterArgs
{
Name = access_policy.Name.Apply(name => $"accessPolicies/{name}/servicePerimeters/"),
Title = "",
Status = new Gcp.AccessContextManager.Inputs.ServicePerimetersServicePerimeterStatusArgs
{
RestrictedServices = new[]
{
"storage.googleapis.com",
},
},
},
new Gcp.AccessContextManager.Inputs.ServicePerimetersServicePerimeterArgs
{
Name = access_policy.Name.Apply(name => $"accessPolicies/{name}/servicePerimeters/"),
Title = "",
Status = new Gcp.AccessContextManager.Inputs.ServicePerimetersServicePerimeterStatusArgs
{
RestrictedServices = new[]
{
"bigtable.googleapis.com",
},
VpcAccessibleServices = new Gcp.AccessContextManager.Inputs.ServicePerimetersServicePerimeterStatusVpcAccessibleServicesArgs
{
EnableRestriction = true,
AllowedServices = new[]
{
"bigquery.googleapis.com",
},
},
},
},
},
});
var access_level = new Gcp.AccessContextManager.AccessLevel("access-level", new()
{
Parent = access_policy.Name.Apply(name => $"accessPolicies/{name}"),
Name = access_policy.Name.Apply(name => $"accessPolicies/{name}/accessLevels/secure_data_exchange"),
Title = "secure_data_exchange",
Basic = new Gcp.AccessContextManager.Inputs.AccessLevelBasicArgs
{
Conditions = new[]
{
new Gcp.AccessContextManager.Inputs.AccessLevelBasicConditionArgs
{
DevicePolicy = new Gcp.AccessContextManager.Inputs.AccessLevelBasicConditionDevicePolicyArgs
{
RequireScreenLock = false,
OsConstraints = new[]
{
new Gcp.AccessContextManager.Inputs.AccessLevelBasicConditionDevicePolicyOsConstraintArgs
{
OsType = "DESKTOP_CHROME_OS",
},
},
},
Regions = new[]
{
"CH",
"IT",
"US",
},
},
},
},
});
var test_access = new Gcp.AccessContextManager.ServicePerimeter("test-access", new()
{
Parent = $"accessPolicies/{test_accessGoogleAccessContextManagerAccessPolicy.Name}",
Name = $"accessPolicies/{test_accessGoogleAccessContextManagerAccessPolicy.Name}/servicePerimeters/%s",
Title = "%s",
PerimeterType = "PERIMETER_TYPE_REGULAR",
Status = new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusArgs
{
RestrictedServices = new[]
{
"bigquery.googleapis.com",
"storage.googleapis.com",
},
AccessLevels = new[]
{
access_level.Name,
},
VpcAccessibleServices = new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusVpcAccessibleServicesArgs
{
EnableRestriction = true,
AllowedServices = new[]
{
"bigquery.googleapis.com",
"storage.googleapis.com",
},
},
IngressPolicies = new[]
{
new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusIngressPolicyArgs
{
IngressFrom = new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusIngressPolicyIngressFromArgs
{
Sources = new[]
{
new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusIngressPolicyIngressFromSourceArgs
{
AccessLevel = test_accessGoogleAccessContextManagerAccessLevel.Name,
},
},
IdentityType = "ANY_IDENTITY",
},
IngressTo = new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusIngressPolicyIngressToArgs
{
Resources = new[]
{
"*",
},
Operations = new[]
{
new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusIngressPolicyIngressToOperationArgs
{
ServiceName = "bigquery.googleapis.com",
MethodSelectors = new[]
{
new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArgs
{
Method = "BigQueryStorage.ReadRows",
},
new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArgs
{
Method = "TableService.ListTables",
},
new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArgs
{
Permission = "bigquery.jobs.get",
},
},
},
new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusIngressPolicyIngressToOperationArgs
{
ServiceName = "storage.googleapis.com",
MethodSelectors = new[]
{
new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArgs
{
Method = "google.storage.objects.create",
},
},
},
},
},
},
},
EgressPolicies = new[]
{
new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusEgressPolicyArgs
{
EgressFrom = new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusEgressPolicyEgressFromArgs
{
IdentityType = "ANY_USER_ACCOUNT",
},
},
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.accesscontextmanager.AccessPolicy;
import com.pulumi.gcp.accesscontextmanager.AccessPolicyArgs;
import com.pulumi.gcp.accesscontextmanager.ServicePerimeters;
import com.pulumi.gcp.accesscontextmanager.ServicePerimetersArgs;
import com.pulumi.gcp.accesscontextmanager.inputs.ServicePerimetersServicePerimeterArgs;
import com.pulumi.gcp.accesscontextmanager.inputs.ServicePerimetersServicePerimeterStatusArgs;
import com.pulumi.gcp.accesscontextmanager.inputs.ServicePerimetersServicePerimeterStatusVpcAccessibleServicesArgs;
import com.pulumi.gcp.accesscontextmanager.AccessLevel;
import com.pulumi.gcp.accesscontextmanager.AccessLevelArgs;
import com.pulumi.gcp.accesscontextmanager.inputs.AccessLevelBasicArgs;
import com.pulumi.gcp.accesscontextmanager.ServicePerimeter;
import com.pulumi.gcp.accesscontextmanager.ServicePerimeterArgs;
import com.pulumi.gcp.accesscontextmanager.inputs.ServicePerimeterStatusArgs;
import com.pulumi.gcp.accesscontextmanager.inputs.ServicePerimeterStatusVpcAccessibleServicesArgs;
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 access_policy = new AccessPolicy("access-policy", AccessPolicyArgs.builder()
.parent("organizations/123456789")
.title("my policy")
.build());
var secure_data_exchange = new ServicePerimeters("secure-data-exchange", ServicePerimetersArgs.builder()
.parent(access_policy.name().applyValue(_name -> String.format("accessPolicies/%s", _name)))
.servicePerimeters(
ServicePerimetersServicePerimeterArgs.builder()
.name(access_policy.name().applyValue(_name -> String.format("accessPolicies/%s/servicePerimeters/", _name)))
.title("")
.status(ServicePerimetersServicePerimeterStatusArgs.builder()
.restrictedServices("storage.googleapis.com")
.build())
.build(),
ServicePerimetersServicePerimeterArgs.builder()
.name(access_policy.name().applyValue(_name -> String.format("accessPolicies/%s/servicePerimeters/", _name)))
.title("")
.status(ServicePerimetersServicePerimeterStatusArgs.builder()
.restrictedServices("bigtable.googleapis.com")
.vpcAccessibleServices(ServicePerimetersServicePerimeterStatusVpcAccessibleServicesArgs.builder()
.enableRestriction(true)
.allowedServices("bigquery.googleapis.com")
.build())
.build())
.build())
.build());
var access_level = new AccessLevel("access-level", AccessLevelArgs.builder()
.parent(access_policy.name().applyValue(_name -> String.format("accessPolicies/%s", _name)))
.name(access_policy.name().applyValue(_name -> String.format("accessPolicies/%s/accessLevels/secure_data_exchange", _name)))
.title("secure_data_exchange")
.basic(AccessLevelBasicArgs.builder()
.conditions(AccessLevelBasicConditionArgs.builder()
.devicePolicy(AccessLevelBasicConditionDevicePolicyArgs.builder()
.requireScreenLock(false)
.osConstraints(AccessLevelBasicConditionDevicePolicyOsConstraintArgs.builder()
.osType("DESKTOP_CHROME_OS")
.build())
.build())
.regions(
"CH",
"IT",
"US")
.build())
.build())
.build());
var test_access = new ServicePerimeter("test-access", ServicePerimeterArgs.builder()
.parent(String.format("accessPolicies/%s", test_accessGoogleAccessContextManagerAccessPolicy.name()))
.name(String.format("accessPolicies/%s/servicePerimeters/%s", test_accessGoogleAccessContextManagerAccessPolicy.name()))
.title("%s")
.perimeterType("PERIMETER_TYPE_REGULAR")
.status(ServicePerimeterStatusArgs.builder()
.restrictedServices(
"bigquery.googleapis.com",
"storage.googleapis.com")
.accessLevels(access_level.name())
.vpcAccessibleServices(ServicePerimeterStatusVpcAccessibleServicesArgs.builder()
.enableRestriction(true)
.allowedServices(
"bigquery.googleapis.com",
"storage.googleapis.com")
.build())
.ingressPolicies(ServicePerimeterStatusIngressPolicyArgs.builder()
.ingressFrom(ServicePerimeterStatusIngressPolicyIngressFromArgs.builder()
.sources(ServicePerimeterStatusIngressPolicyIngressFromSourceArgs.builder()
.accessLevel(test_accessGoogleAccessContextManagerAccessLevel.name())
.build())
.identityType("ANY_IDENTITY")
.build())
.ingressTo(ServicePerimeterStatusIngressPolicyIngressToArgs.builder()
.resources("*")
.operations(
ServicePerimeterStatusIngressPolicyIngressToOperationArgs.builder()
.serviceName("bigquery.googleapis.com")
.methodSelectors(
ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArgs.builder()
.method("BigQueryStorage.ReadRows")
.build(),
ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArgs.builder()
.method("TableService.ListTables")
.build(),
ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArgs.builder()
.permission("bigquery.jobs.get")
.build())
.build(),
ServicePerimeterStatusIngressPolicyIngressToOperationArgs.builder()
.serviceName("storage.googleapis.com")
.methodSelectors(ServicePerimeterStatusIngressPolicyIngressToOperationMethodSelectorArgs.builder()
.method("google.storage.objects.create")
.build())
.build())
.build())
.build())
.egressPolicies(ServicePerimeterStatusEgressPolicyArgs.builder()
.egressFrom(ServicePerimeterStatusEgressPolicyEgressFromArgs.builder()
.identityType("ANY_USER_ACCOUNT")
.build())
.build())
.build())
.build());
}
}
resources:
secure-data-exchange:
type: gcp:accesscontextmanager:ServicePerimeters
properties:
parent: accessPolicies/${["access-policy"].name}
servicePerimeters:
- name: accessPolicies/${["access-policy"].name}/servicePerimeters/
title: ""
status:
restrictedServices:
- storage.googleapis.com
- name: accessPolicies/${["access-policy"].name}/servicePerimeters/
title: ""
status:
restrictedServices:
- bigtable.googleapis.com
vpcAccessibleServices:
enableRestriction: true
allowedServices:
- bigquery.googleapis.com
access-level:
type: gcp:accesscontextmanager:AccessLevel
properties:
parent: accessPolicies/${["access-policy"].name}
name: accessPolicies/${["access-policy"].name}/accessLevels/secure_data_exchange
title: secure_data_exchange
basic:
conditions:
- devicePolicy:
requireScreenLock: false
osConstraints:
- osType: DESKTOP_CHROME_OS
regions:
- CH
- IT
- US
access-policy:
type: gcp:accesscontextmanager:AccessPolicy
properties:
parent: organizations/123456789
title: my policy
test-access:
type: gcp:accesscontextmanager:ServicePerimeter
properties:
parent: accessPolicies/${["test-accessGoogleAccessContextManagerAccessPolicy"].name}
name: accessPolicies/${["test-accessGoogleAccessContextManagerAccessPolicy"].name}/servicePerimeters/%s
title: '%s'
perimeterType: PERIMETER_TYPE_REGULAR
status:
restrictedServices:
- bigquery.googleapis.com
- storage.googleapis.com
accessLevels:
- ${["access-level"].name}
vpcAccessibleServices:
enableRestriction: true
allowedServices:
- bigquery.googleapis.com
- storage.googleapis.com
ingressPolicies:
- ingressFrom:
sources:
- accessLevel: ${["test-accessGoogleAccessContextManagerAccessLevel"].name}
identityType: ANY_IDENTITY
ingressTo:
resources:
- '*'
operations:
- serviceName: bigquery.googleapis.com
methodSelectors:
- method: BigQueryStorage.ReadRows
- method: TableService.ListTables
- permission: bigquery.jobs.get
- serviceName: storage.googleapis.com
methodSelectors:
- method: google.storage.objects.create
egressPolicies:
- egressFrom:
identityType: ANY_USER_ACCOUNT
The ingressPolicies array controls inbound traffic. Each policy’s ingressFrom block specifies source access levels and identity types, while ingressTo defines target resources and allowed operations. The operations array uses serviceName to identify APIs and methodSelectors to permit specific methods or permissions. The egressPolicies array controls outbound traffic with similar structure. The vpcAccessibleServices block further restricts which services VPC resources can reach, even within the perimeter.
Test perimeter changes with dry-run mode
Before enforcing new restrictions, you can validate that policies won’t break existing workflows by testing a proposed configuration alongside the active one.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const access_policy = new gcp.accesscontextmanager.AccessPolicy("access-policy", {
parent: "organizations/123456789",
title: "my policy",
});
const service_perimeter = new gcp.accesscontextmanager.ServicePerimeter("service-perimeter", {
parent: pulumi.interpolate`accessPolicies/${access_policy.name}`,
name: pulumi.interpolate`accessPolicies/${access_policy.name}/servicePerimeters/restrict_bigquery_dryrun_storage`,
title: "restrict_bigquery_dryrun_storage",
status: {
restrictedServices: ["bigquery.googleapis.com"],
},
spec: {
restrictedServices: ["storage.googleapis.com"],
},
useExplicitDryRunSpec: true,
});
import pulumi
import pulumi_gcp as gcp
access_policy = gcp.accesscontextmanager.AccessPolicy("access-policy",
parent="organizations/123456789",
title="my policy")
service_perimeter = gcp.accesscontextmanager.ServicePerimeter("service-perimeter",
parent=access_policy.name.apply(lambda name: f"accessPolicies/{name}"),
name=access_policy.name.apply(lambda name: f"accessPolicies/{name}/servicePerimeters/restrict_bigquery_dryrun_storage"),
title="restrict_bigquery_dryrun_storage",
status={
"restricted_services": ["bigquery.googleapis.com"],
},
spec={
"restricted_services": ["storage.googleapis.com"],
},
use_explicit_dry_run_spec=True)
package main
import (
"fmt"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/accesscontextmanager"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
access_policy, err := accesscontextmanager.NewAccessPolicy(ctx, "access-policy", &accesscontextmanager.AccessPolicyArgs{
Parent: pulumi.String("organizations/123456789"),
Title: pulumi.String("my policy"),
})
if err != nil {
return err
}
_, err = accesscontextmanager.NewServicePerimeter(ctx, "service-perimeter", &accesscontextmanager.ServicePerimeterArgs{
Parent: access_policy.Name.ApplyT(func(name string) (string, error) {
return fmt.Sprintf("accessPolicies/%v", name), nil
}).(pulumi.StringOutput),
Name: access_policy.Name.ApplyT(func(name string) (string, error) {
return fmt.Sprintf("accessPolicies/%v/servicePerimeters/restrict_bigquery_dryrun_storage", name), nil
}).(pulumi.StringOutput),
Title: pulumi.String("restrict_bigquery_dryrun_storage"),
Status: &accesscontextmanager.ServicePerimeterStatusArgs{
RestrictedServices: pulumi.StringArray{
pulumi.String("bigquery.googleapis.com"),
},
},
Spec: &accesscontextmanager.ServicePerimeterSpecArgs{
RestrictedServices: pulumi.StringArray{
pulumi.String("storage.googleapis.com"),
},
},
UseExplicitDryRunSpec: pulumi.Bool(true),
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var access_policy = new Gcp.AccessContextManager.AccessPolicy("access-policy", new()
{
Parent = "organizations/123456789",
Title = "my policy",
});
var service_perimeter = new Gcp.AccessContextManager.ServicePerimeter("service-perimeter", new()
{
Parent = access_policy.Name.Apply(name => $"accessPolicies/{name}"),
Name = access_policy.Name.Apply(name => $"accessPolicies/{name}/servicePerimeters/restrict_bigquery_dryrun_storage"),
Title = "restrict_bigquery_dryrun_storage",
Status = new Gcp.AccessContextManager.Inputs.ServicePerimeterStatusArgs
{
RestrictedServices = new[]
{
"bigquery.googleapis.com",
},
},
Spec = new Gcp.AccessContextManager.Inputs.ServicePerimeterSpecArgs
{
RestrictedServices = new[]
{
"storage.googleapis.com",
},
},
UseExplicitDryRunSpec = true,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.accesscontextmanager.AccessPolicy;
import com.pulumi.gcp.accesscontextmanager.AccessPolicyArgs;
import com.pulumi.gcp.accesscontextmanager.ServicePerimeter;
import com.pulumi.gcp.accesscontextmanager.ServicePerimeterArgs;
import com.pulumi.gcp.accesscontextmanager.inputs.ServicePerimeterStatusArgs;
import com.pulumi.gcp.accesscontextmanager.inputs.ServicePerimeterSpecArgs;
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 access_policy = new AccessPolicy("access-policy", AccessPolicyArgs.builder()
.parent("organizations/123456789")
.title("my policy")
.build());
var service_perimeter = new ServicePerimeter("service-perimeter", ServicePerimeterArgs.builder()
.parent(access_policy.name().applyValue(_name -> String.format("accessPolicies/%s", _name)))
.name(access_policy.name().applyValue(_name -> String.format("accessPolicies/%s/servicePerimeters/restrict_bigquery_dryrun_storage", _name)))
.title("restrict_bigquery_dryrun_storage")
.status(ServicePerimeterStatusArgs.builder()
.restrictedServices("bigquery.googleapis.com")
.build())
.spec(ServicePerimeterSpecArgs.builder()
.restrictedServices("storage.googleapis.com")
.build())
.useExplicitDryRunSpec(true)
.build());
}
}
resources:
service-perimeter:
type: gcp:accesscontextmanager:ServicePerimeter
properties:
parent: accessPolicies/${["access-policy"].name}
name: accessPolicies/${["access-policy"].name}/servicePerimeters/restrict_bigquery_dryrun_storage
title: restrict_bigquery_dryrun_storage
status:
restrictedServices:
- bigquery.googleapis.com
spec:
restrictedServices:
- storage.googleapis.com
useExplicitDryRunSpec: true
access-policy:
type: gcp:accesscontextmanager:AccessPolicy
properties:
parent: organizations/123456789
title: my policy
The spec block defines the dry-run configuration while status remains enforced. Setting useExplicitDryRunSpec to true activates this mode, allowing you to analyze the impact of proposed changes without affecting production traffic. Here, the perimeter enforces BigQuery restrictions in status while testing Storage restrictions in spec.
Beyond these examples
These snippets focus on specific perimeter-level features: service restriction and VPC-accessible services, ingress and egress traffic policies, and dry-run testing with spec/status separation. They’re intentionally minimal rather than full VPC Service Controls deployments.
The examples reference pre-existing infrastructure such as Access Context Manager access policies, access levels for identity-based controls, and GCP projects to protect within perimeters. They focus on configuring the perimeter rather than provisioning the surrounding access control hierarchy.
To keep things focused, common perimeter patterns are omitted, including:
- Bridge perimeters for cross-perimeter resource sharing (perimeterType)
- Project and resource membership (resources property in status/spec)
- Access level integration beyond ingress policies
- VPC Service Controls for hybrid connectivity
These omissions are intentional: the goal is to illustrate how each perimeter feature is wired, not provide drop-in security modules. See the Service Perimeter resource reference for all available configuration options.
Let's configure GCP Access Context Manager Service Perimeters
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Authentication & Permissions
billingProject and set userProjectOverride to true in your provider configuration. Your account also needs the serviceusage.services.use permission on the billing project.Perimeter Types & Overlap Rules
Configuration & Dry-Run Testing
status contains the enforced configuration that actively restricts access. spec contains a dry-run configuration for testing changes without enforcement. To use spec, you must set useExplicitDryRunSpec to true.spec property with your proposed changes and set useExplicitDryRunSpec to true. This lets you analyze differences between enforced and suggested restrictions without actually enforcing them.name, parent, and perimeterType properties are immutable. You cannot change which access policy a perimeter belongs to, its resource name, or convert between regular and bridge types.Service Restrictions & Access Control
restrictedServices in the status block with an array of service names like storage.googleapis.com or bigquery.googleapis.com. These services will only be accessible from within the perimeter.ingressPolicies and egressPolicies within the status block. Ingress policies define allowed inbound traffic with sources, identity types, target resources, and allowed operations. Egress policies control outbound traffic with identity types and destinations.