Configure GCP BeyondCorp Security Gateway Applications

The gcp:beyondcorp/securityGatewayApplication:SecurityGatewayApplication resource, part of the Pulumi GCP provider, defines application endpoints protected by a BeyondCorp Security Gateway: hostname matching, upstream routing, and protocol configuration. This guide focuses on four capabilities: endpoint matching for public and private services, VPC network routing with egress policies, API Gateway and Proxy Gateway schemas, and contextual header forwarding for identity.

Security Gateway Applications belong to Security Gateway resources and route traffic to VPC networks or external endpoints. The examples are intentionally small. Combine them with your own Security Gateways, VPC infrastructure, and upstream services.

Route traffic to public endpoints by hostname

Teams protecting access to public SaaS applications define which hostnames and ports the Security Gateway intercepts, creating a zero-trust boundary for external services.

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

const _default = new gcp.beyondcorp.SecurityGateway("default", {
    securityGatewayId: "default-sg",
    displayName: "My Security Gateway resource",
    hubs: [{
        region: "us-central1",
    }],
});
const example = new gcp.beyondcorp.SecurityGatewayApplication("example", {
    securityGatewayId: _default.securityGatewayId,
    applicationId: "google-sga",
    endpointMatchers: [{
        hostname: "google.com",
        ports: [
            80,
            443,
        ],
    }],
});
import pulumi
import pulumi_gcp as gcp

default = gcp.beyondcorp.SecurityGateway("default",
    security_gateway_id="default-sg",
    display_name="My Security Gateway resource",
    hubs=[{
        "region": "us-central1",
    }])
example = gcp.beyondcorp.SecurityGatewayApplication("example",
    security_gateway_id=default.security_gateway_id,
    application_id="google-sga",
    endpoint_matchers=[{
        "hostname": "google.com",
        "ports": [
            80,
            443,
        ],
    }])
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/beyondcorp"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_default, err := beyondcorp.NewSecurityGateway(ctx, "default", &beyondcorp.SecurityGatewayArgs{
			SecurityGatewayId: pulumi.String("default-sg"),
			DisplayName:       pulumi.String("My Security Gateway resource"),
			Hubs: beyondcorp.SecurityGatewayHubArray{
				&beyondcorp.SecurityGatewayHubArgs{
					Region: pulumi.String("us-central1"),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = beyondcorp.NewSecurityGatewayApplication(ctx, "example", &beyondcorp.SecurityGatewayApplicationArgs{
			SecurityGatewayId: _default.SecurityGatewayId,
			ApplicationId:     pulumi.String("google-sga"),
			EndpointMatchers: beyondcorp.SecurityGatewayApplicationEndpointMatcherArray{
				&beyondcorp.SecurityGatewayApplicationEndpointMatcherArgs{
					Hostname: pulumi.String("google.com"),
					Ports: pulumi.IntArray{
						pulumi.Int(80),
						pulumi.Int(443),
					},
				},
			},
		})
		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 @default = new Gcp.Beyondcorp.SecurityGateway("default", new()
    {
        SecurityGatewayId = "default-sg",
        DisplayName = "My Security Gateway resource",
        Hubs = new[]
        {
            new Gcp.Beyondcorp.Inputs.SecurityGatewayHubArgs
            {
                Region = "us-central1",
            },
        },
    });

    var example = new Gcp.Beyondcorp.SecurityGatewayApplication("example", new()
    {
        SecurityGatewayId = @default.SecurityGatewayId,
        ApplicationId = "google-sga",
        EndpointMatchers = new[]
        {
            new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationEndpointMatcherArgs
            {
                Hostname = "google.com",
                Ports = new[]
                {
                    80,
                    443,
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.beyondcorp.SecurityGateway;
import com.pulumi.gcp.beyondcorp.SecurityGatewayArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayHubArgs;
import com.pulumi.gcp.beyondcorp.SecurityGatewayApplication;
import com.pulumi.gcp.beyondcorp.SecurityGatewayApplicationArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationEndpointMatcherArgs;
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 default_ = new SecurityGateway("default", SecurityGatewayArgs.builder()
            .securityGatewayId("default-sg")
            .displayName("My Security Gateway resource")
            .hubs(SecurityGatewayHubArgs.builder()
                .region("us-central1")
                .build())
            .build());

        var example = new SecurityGatewayApplication("example", SecurityGatewayApplicationArgs.builder()
            .securityGatewayId(default_.securityGatewayId())
            .applicationId("google-sga")
            .endpointMatchers(SecurityGatewayApplicationEndpointMatcherArgs.builder()
                .hostname("google.com")
                .ports(                
                    80,
                    443)
                .build())
            .build());

    }
}
resources:
  default:
    type: gcp:beyondcorp:SecurityGateway
    properties:
      securityGatewayId: default-sg
      displayName: My Security Gateway resource
      hubs:
        - region: us-central1
  example:
    type: gcp:beyondcorp:SecurityGatewayApplication
    properties:
      securityGatewayId: ${default.securityGatewayId}
      applicationId: google-sga
      endpointMatchers:
        - hostname: google.com
          ports:
            - 80
            - 443

The endpointMatchers property defines which traffic to intercept. Each matcher combines a hostname (supporting wildcards like “*.abc.com”) with optional port numbers. The Security Gateway evaluates matchers with OR logic: traffic matching any matcher is routed through the gateway. The securityGatewayId links this application to its parent Security Gateway resource.

Forward traffic to services in a VPC network

Applications running in private VPC networks need upstream configuration to route traffic from the Security Gateway into the correct network and region.

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

const project = gcp.organizations.getProject({});
const _default = new gcp.beyondcorp.SecurityGateway("default", {
    securityGatewayId: "default-sg",
    displayName: "My Security Gateway resource",
    hubs: [{
        region: "us-central1",
    }],
});
const example = new gcp.beyondcorp.SecurityGatewayApplication("example", {
    securityGatewayId: _default.securityGatewayId,
    applicationId: "my-vm-service2",
    endpointMatchers: [{
        hostname: "my-vm-service.com",
        ports: [
            80,
            443,
        ],
    }],
    upstreams: [{
        egressPolicy: {
            regions: ["us-central1"],
        },
        network: {
            name: project.then(project => `projects/${project.projectId}/global/networks/default`),
        },
    }],
});
import pulumi
import pulumi_gcp as gcp

project = gcp.organizations.get_project()
default = gcp.beyondcorp.SecurityGateway("default",
    security_gateway_id="default-sg",
    display_name="My Security Gateway resource",
    hubs=[{
        "region": "us-central1",
    }])
example = gcp.beyondcorp.SecurityGatewayApplication("example",
    security_gateway_id=default.security_gateway_id,
    application_id="my-vm-service2",
    endpoint_matchers=[{
        "hostname": "my-vm-service.com",
        "ports": [
            80,
            443,
        ],
    }],
    upstreams=[{
        "egress_policy": {
            "regions": ["us-central1"],
        },
        "network": {
            "name": f"projects/{project.project_id}/global/networks/default",
        },
    }])
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/beyondcorp"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		project, err := organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
		if err != nil {
			return err
		}
		_default, err := beyondcorp.NewSecurityGateway(ctx, "default", &beyondcorp.SecurityGatewayArgs{
			SecurityGatewayId: pulumi.String("default-sg"),
			DisplayName:       pulumi.String("My Security Gateway resource"),
			Hubs: beyondcorp.SecurityGatewayHubArray{
				&beyondcorp.SecurityGatewayHubArgs{
					Region: pulumi.String("us-central1"),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = beyondcorp.NewSecurityGatewayApplication(ctx, "example", &beyondcorp.SecurityGatewayApplicationArgs{
			SecurityGatewayId: _default.SecurityGatewayId,
			ApplicationId:     pulumi.String("my-vm-service2"),
			EndpointMatchers: beyondcorp.SecurityGatewayApplicationEndpointMatcherArray{
				&beyondcorp.SecurityGatewayApplicationEndpointMatcherArgs{
					Hostname: pulumi.String("my-vm-service.com"),
					Ports: pulumi.IntArray{
						pulumi.Int(80),
						pulumi.Int(443),
					},
				},
			},
			Upstreams: beyondcorp.SecurityGatewayApplicationUpstreamArray{
				&beyondcorp.SecurityGatewayApplicationUpstreamArgs{
					EgressPolicy: &beyondcorp.SecurityGatewayApplicationUpstreamEgressPolicyArgs{
						Regions: pulumi.StringArray{
							pulumi.String("us-central1"),
						},
					},
					Network: &beyondcorp.SecurityGatewayApplicationUpstreamNetworkArgs{
						Name: pulumi.Sprintf("projects/%v/global/networks/default", project.ProjectId),
					},
				},
			},
		})
		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 project = Gcp.Organizations.GetProject.Invoke();

    var @default = new Gcp.Beyondcorp.SecurityGateway("default", new()
    {
        SecurityGatewayId = "default-sg",
        DisplayName = "My Security Gateway resource",
        Hubs = new[]
        {
            new Gcp.Beyondcorp.Inputs.SecurityGatewayHubArgs
            {
                Region = "us-central1",
            },
        },
    });

    var example = new Gcp.Beyondcorp.SecurityGatewayApplication("example", new()
    {
        SecurityGatewayId = @default.SecurityGatewayId,
        ApplicationId = "my-vm-service2",
        EndpointMatchers = new[]
        {
            new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationEndpointMatcherArgs
            {
                Hostname = "my-vm-service.com",
                Ports = new[]
                {
                    80,
                    443,
                },
            },
        },
        Upstreams = new[]
        {
            new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamArgs
            {
                EgressPolicy = new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamEgressPolicyArgs
                {
                    Regions = new[]
                    {
                        "us-central1",
                    },
                },
                Network = new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamNetworkArgs
                {
                    Name = $"projects/{project.Apply(getProjectResult => getProjectResult.ProjectId)}/global/networks/default",
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
import com.pulumi.gcp.beyondcorp.SecurityGateway;
import com.pulumi.gcp.beyondcorp.SecurityGatewayArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayHubArgs;
import com.pulumi.gcp.beyondcorp.SecurityGatewayApplication;
import com.pulumi.gcp.beyondcorp.SecurityGatewayApplicationArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationEndpointMatcherArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamEgressPolicyArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamNetworkArgs;
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 project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

        var default_ = new SecurityGateway("default", SecurityGatewayArgs.builder()
            .securityGatewayId("default-sg")
            .displayName("My Security Gateway resource")
            .hubs(SecurityGatewayHubArgs.builder()
                .region("us-central1")
                .build())
            .build());

        var example = new SecurityGatewayApplication("example", SecurityGatewayApplicationArgs.builder()
            .securityGatewayId(default_.securityGatewayId())
            .applicationId("my-vm-service2")
            .endpointMatchers(SecurityGatewayApplicationEndpointMatcherArgs.builder()
                .hostname("my-vm-service.com")
                .ports(                
                    80,
                    443)
                .build())
            .upstreams(SecurityGatewayApplicationUpstreamArgs.builder()
                .egressPolicy(SecurityGatewayApplicationUpstreamEgressPolicyArgs.builder()
                    .regions("us-central1")
                    .build())
                .network(SecurityGatewayApplicationUpstreamNetworkArgs.builder()
                    .name(String.format("projects/%s/global/networks/default", project.projectId()))
                    .build())
                .build())
            .build());

    }
}
resources:
  default:
    type: gcp:beyondcorp:SecurityGateway
    properties:
      securityGatewayId: default-sg
      displayName: My Security Gateway resource
      hubs:
        - region: us-central1
  example:
    type: gcp:beyondcorp:SecurityGatewayApplication
    properties:
      securityGatewayId: ${default.securityGatewayId}
      applicationId: my-vm-service2
      endpointMatchers:
        - hostname: my-vm-service.com
          ports:
            - 80
            - 443
      upstreams:
        - egressPolicy:
            regions:
              - us-central1
          network:
            name: projects/${project.projectId}/global/networks/default
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}

The upstreams property defines where to forward matched traffic. For VPC routing, the network property references your VPC by full resource path, and egressPolicy specifies which regions can handle egress. This configuration extends the basic endpoint matching pattern by adding VPC-aware routing.

Configure API Gateway schema for discovery services

API-based applications that don’t require hostname matching use the API_GATEWAY schema with external endpoints and proxy protocol configuration.

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

const _default = new gcp.beyondcorp.SecurityGateway("default", {
    securityGatewayId: "default-sg-spa-api",
    displayName: "My SPA Security Gateway resource",
});
const example_spa = new gcp.beyondcorp.SecurityGatewayApplication("example-spa", {
    securityGatewayId: _default.securityGatewayId,
    applicationId: "app-discovery",
    upstreams: [{
        external: {
            endpoints: [{
                hostname: "my.discovery.service.com",
                port: 443,
            }],
        },
        proxyProtocol: {
            allowedClientHeaders: ["header"],
        },
    }],
    schema: "API_GATEWAY",
});
import pulumi
import pulumi_gcp as gcp

default = gcp.beyondcorp.SecurityGateway("default",
    security_gateway_id="default-sg-spa-api",
    display_name="My SPA Security Gateway resource")
example_spa = gcp.beyondcorp.SecurityGatewayApplication("example-spa",
    security_gateway_id=default.security_gateway_id,
    application_id="app-discovery",
    upstreams=[{
        "external": {
            "endpoints": [{
                "hostname": "my.discovery.service.com",
                "port": 443,
            }],
        },
        "proxy_protocol": {
            "allowed_client_headers": ["header"],
        },
    }],
    schema="API_GATEWAY")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/beyondcorp"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_default, err := beyondcorp.NewSecurityGateway(ctx, "default", &beyondcorp.SecurityGatewayArgs{
			SecurityGatewayId: pulumi.String("default-sg-spa-api"),
			DisplayName:       pulumi.String("My SPA Security Gateway resource"),
		})
		if err != nil {
			return err
		}
		_, err = beyondcorp.NewSecurityGatewayApplication(ctx, "example-spa", &beyondcorp.SecurityGatewayApplicationArgs{
			SecurityGatewayId: _default.SecurityGatewayId,
			ApplicationId:     pulumi.String("app-discovery"),
			Upstreams: beyondcorp.SecurityGatewayApplicationUpstreamArray{
				&beyondcorp.SecurityGatewayApplicationUpstreamArgs{
					External: &beyondcorp.SecurityGatewayApplicationUpstreamExternalArgs{
						Endpoints: beyondcorp.SecurityGatewayApplicationUpstreamExternalEndpointArray{
							&beyondcorp.SecurityGatewayApplicationUpstreamExternalEndpointArgs{
								Hostname: pulumi.String("my.discovery.service.com"),
								Port:     pulumi.Int(443),
							},
						},
					},
					ProxyProtocol: &beyondcorp.SecurityGatewayApplicationUpstreamProxyProtocolArgs{
						AllowedClientHeaders: pulumi.StringArray{
							pulumi.String("header"),
						},
					},
				},
			},
			Schema: pulumi.String("API_GATEWAY"),
		})
		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 @default = new Gcp.Beyondcorp.SecurityGateway("default", new()
    {
        SecurityGatewayId = "default-sg-spa-api",
        DisplayName = "My SPA Security Gateway resource",
    });

    var example_spa = new Gcp.Beyondcorp.SecurityGatewayApplication("example-spa", new()
    {
        SecurityGatewayId = @default.SecurityGatewayId,
        ApplicationId = "app-discovery",
        Upstreams = new[]
        {
            new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamArgs
            {
                External = new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamExternalArgs
                {
                    Endpoints = new[]
                    {
                        new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamExternalEndpointArgs
                        {
                            Hostname = "my.discovery.service.com",
                            Port = 443,
                        },
                    },
                },
                ProxyProtocol = new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamProxyProtocolArgs
                {
                    AllowedClientHeaders = new[]
                    {
                        "header",
                    },
                },
            },
        },
        Schema = "API_GATEWAY",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.beyondcorp.SecurityGateway;
import com.pulumi.gcp.beyondcorp.SecurityGatewayArgs;
import com.pulumi.gcp.beyondcorp.SecurityGatewayApplication;
import com.pulumi.gcp.beyondcorp.SecurityGatewayApplicationArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamExternalArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamProxyProtocolArgs;
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 default_ = new SecurityGateway("default", SecurityGatewayArgs.builder()
            .securityGatewayId("default-sg-spa-api")
            .displayName("My SPA Security Gateway resource")
            .build());

        var example_spa = new SecurityGatewayApplication("example-spa", SecurityGatewayApplicationArgs.builder()
            .securityGatewayId(default_.securityGatewayId())
            .applicationId("app-discovery")
            .upstreams(SecurityGatewayApplicationUpstreamArgs.builder()
                .external(SecurityGatewayApplicationUpstreamExternalArgs.builder()
                    .endpoints(SecurityGatewayApplicationUpstreamExternalEndpointArgs.builder()
                        .hostname("my.discovery.service.com")
                        .port(443)
                        .build())
                    .build())
                .proxyProtocol(SecurityGatewayApplicationUpstreamProxyProtocolArgs.builder()
                    .allowedClientHeaders("header")
                    .build())
                .build())
            .schema("API_GATEWAY")
            .build());

    }
}
resources:
  default:
    type: gcp:beyondcorp:SecurityGateway
    properties:
      securityGatewayId: default-sg-spa-api
      displayName: My SPA Security Gateway resource
  example-spa:
    type: gcp:beyondcorp:SecurityGatewayApplication
    properties:
      securityGatewayId: ${default.securityGatewayId}
      applicationId: app-discovery
      upstreams:
        - external:
            endpoints:
              - hostname: my.discovery.service.com
                port: 443
          proxyProtocol:
            allowedClientHeaders:
              - header
      schema: API_GATEWAY

The schema property switches from default hostname-based routing to API Gateway mode. In this mode, upstreams.external.endpoints defines the backend service by hostname and port, while proxyProtocol.allowedClientHeaders controls which HTTP headers pass through to the upstream service. This pattern suits API discovery services that don’t need endpoint matchers.

Add contextual headers for proxy gateway routing

Proxy gateways that need to forward user identity, group membership, and device information use contextual headers to pass BeyondCorp context to upstream services.

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

const _default = new gcp.beyondcorp.SecurityGateway("default", {
    securityGatewayId: "default-sg-spa-proxy",
    displayName: "My SPA Security Gateway resource",
});
const example_spa = new gcp.beyondcorp.SecurityGatewayApplication("example-spa", {
    securityGatewayId: _default.securityGatewayId,
    applicationId: "app-proxy",
    endpointMatchers: [{
        hostname: "a.site.com",
        ports: [443],
    }],
    upstreams: [{
        external: {
            endpoints: [{
                hostname: "my.proxy.service.com",
                port: 443,
            }],
        },
        proxyProtocol: {
            allowedClientHeaders: [
                "header1",
                "header2",
            ],
            contextualHeaders: {
                userInfo: {
                    outputType: "PROTOBUF",
                },
                groupInfo: {
                    outputType: "JSON",
                },
                deviceInfo: {
                    outputType: "NONE",
                },
                outputType: "JSON",
            },
            metadataHeaders: {
                "metadata-header1": "value1",
                "metadata-header2": "value2",
            },
            gatewayIdentity: "RESOURCE_NAME",
            clientIp: true,
        },
    }],
    schema: "PROXY_GATEWAY",
});
import pulumi
import pulumi_gcp as gcp

default = gcp.beyondcorp.SecurityGateway("default",
    security_gateway_id="default-sg-spa-proxy",
    display_name="My SPA Security Gateway resource")
example_spa = gcp.beyondcorp.SecurityGatewayApplication("example-spa",
    security_gateway_id=default.security_gateway_id,
    application_id="app-proxy",
    endpoint_matchers=[{
        "hostname": "a.site.com",
        "ports": [443],
    }],
    upstreams=[{
        "external": {
            "endpoints": [{
                "hostname": "my.proxy.service.com",
                "port": 443,
            }],
        },
        "proxy_protocol": {
            "allowed_client_headers": [
                "header1",
                "header2",
            ],
            "contextual_headers": {
                "user_info": {
                    "output_type": "PROTOBUF",
                },
                "group_info": {
                    "output_type": "JSON",
                },
                "device_info": {
                    "output_type": "NONE",
                },
                "output_type": "JSON",
            },
            "metadata_headers": {
                "metadata-header1": "value1",
                "metadata-header2": "value2",
            },
            "gateway_identity": "RESOURCE_NAME",
            "client_ip": True,
        },
    }],
    schema="PROXY_GATEWAY")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/beyondcorp"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_default, err := beyondcorp.NewSecurityGateway(ctx, "default", &beyondcorp.SecurityGatewayArgs{
			SecurityGatewayId: pulumi.String("default-sg-spa-proxy"),
			DisplayName:       pulumi.String("My SPA Security Gateway resource"),
		})
		if err != nil {
			return err
		}
		_, err = beyondcorp.NewSecurityGatewayApplication(ctx, "example-spa", &beyondcorp.SecurityGatewayApplicationArgs{
			SecurityGatewayId: _default.SecurityGatewayId,
			ApplicationId:     pulumi.String("app-proxy"),
			EndpointMatchers: beyondcorp.SecurityGatewayApplicationEndpointMatcherArray{
				&beyondcorp.SecurityGatewayApplicationEndpointMatcherArgs{
					Hostname: pulumi.String("a.site.com"),
					Ports: pulumi.IntArray{
						pulumi.Int(443),
					},
				},
			},
			Upstreams: beyondcorp.SecurityGatewayApplicationUpstreamArray{
				&beyondcorp.SecurityGatewayApplicationUpstreamArgs{
					External: &beyondcorp.SecurityGatewayApplicationUpstreamExternalArgs{
						Endpoints: beyondcorp.SecurityGatewayApplicationUpstreamExternalEndpointArray{
							&beyondcorp.SecurityGatewayApplicationUpstreamExternalEndpointArgs{
								Hostname: pulumi.String("my.proxy.service.com"),
								Port:     pulumi.Int(443),
							},
						},
					},
					ProxyProtocol: &beyondcorp.SecurityGatewayApplicationUpstreamProxyProtocolArgs{
						AllowedClientHeaders: pulumi.StringArray{
							pulumi.String("header1"),
							pulumi.String("header2"),
						},
						ContextualHeaders: &beyondcorp.SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersArgs{
							UserInfo: &beyondcorp.SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersUserInfoArgs{
								OutputType: pulumi.String("PROTOBUF"),
							},
							GroupInfo: &beyondcorp.SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersGroupInfoArgs{
								OutputType: pulumi.String("JSON"),
							},
							DeviceInfo: &beyondcorp.SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersDeviceInfoArgs{
								OutputType: pulumi.String("NONE"),
							},
							OutputType: pulumi.String("JSON"),
						},
						MetadataHeaders: pulumi.StringMap{
							"metadata-header1": pulumi.String("value1"),
							"metadata-header2": pulumi.String("value2"),
						},
						GatewayIdentity: pulumi.String("RESOURCE_NAME"),
						ClientIp:        pulumi.Bool(true),
					},
				},
			},
			Schema: pulumi.String("PROXY_GATEWAY"),
		})
		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 @default = new Gcp.Beyondcorp.SecurityGateway("default", new()
    {
        SecurityGatewayId = "default-sg-spa-proxy",
        DisplayName = "My SPA Security Gateway resource",
    });

    var example_spa = new Gcp.Beyondcorp.SecurityGatewayApplication("example-spa", new()
    {
        SecurityGatewayId = @default.SecurityGatewayId,
        ApplicationId = "app-proxy",
        EndpointMatchers = new[]
        {
            new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationEndpointMatcherArgs
            {
                Hostname = "a.site.com",
                Ports = new[]
                {
                    443,
                },
            },
        },
        Upstreams = new[]
        {
            new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamArgs
            {
                External = new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamExternalArgs
                {
                    Endpoints = new[]
                    {
                        new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamExternalEndpointArgs
                        {
                            Hostname = "my.proxy.service.com",
                            Port = 443,
                        },
                    },
                },
                ProxyProtocol = new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamProxyProtocolArgs
                {
                    AllowedClientHeaders = new[]
                    {
                        "header1",
                        "header2",
                    },
                    ContextualHeaders = new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersArgs
                    {
                        UserInfo = new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersUserInfoArgs
                        {
                            OutputType = "PROTOBUF",
                        },
                        GroupInfo = new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersGroupInfoArgs
                        {
                            OutputType = "JSON",
                        },
                        DeviceInfo = new Gcp.Beyondcorp.Inputs.SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersDeviceInfoArgs
                        {
                            OutputType = "NONE",
                        },
                        OutputType = "JSON",
                    },
                    MetadataHeaders = 
                    {
                        { "metadata-header1", "value1" },
                        { "metadata-header2", "value2" },
                    },
                    GatewayIdentity = "RESOURCE_NAME",
                    ClientIp = true,
                },
            },
        },
        Schema = "PROXY_GATEWAY",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.beyondcorp.SecurityGateway;
import com.pulumi.gcp.beyondcorp.SecurityGatewayArgs;
import com.pulumi.gcp.beyondcorp.SecurityGatewayApplication;
import com.pulumi.gcp.beyondcorp.SecurityGatewayApplicationArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationEndpointMatcherArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamExternalArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamProxyProtocolArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersUserInfoArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersGroupInfoArgs;
import com.pulumi.gcp.beyondcorp.inputs.SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersDeviceInfoArgs;
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 default_ = new SecurityGateway("default", SecurityGatewayArgs.builder()
            .securityGatewayId("default-sg-spa-proxy")
            .displayName("My SPA Security Gateway resource")
            .build());

        var example_spa = new SecurityGatewayApplication("example-spa", SecurityGatewayApplicationArgs.builder()
            .securityGatewayId(default_.securityGatewayId())
            .applicationId("app-proxy")
            .endpointMatchers(SecurityGatewayApplicationEndpointMatcherArgs.builder()
                .hostname("a.site.com")
                .ports(443)
                .build())
            .upstreams(SecurityGatewayApplicationUpstreamArgs.builder()
                .external(SecurityGatewayApplicationUpstreamExternalArgs.builder()
                    .endpoints(SecurityGatewayApplicationUpstreamExternalEndpointArgs.builder()
                        .hostname("my.proxy.service.com")
                        .port(443)
                        .build())
                    .build())
                .proxyProtocol(SecurityGatewayApplicationUpstreamProxyProtocolArgs.builder()
                    .allowedClientHeaders(                    
                        "header1",
                        "header2")
                    .contextualHeaders(SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersArgs.builder()
                        .userInfo(SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersUserInfoArgs.builder()
                            .outputType("PROTOBUF")
                            .build())
                        .groupInfo(SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersGroupInfoArgs.builder()
                            .outputType("JSON")
                            .build())
                        .deviceInfo(SecurityGatewayApplicationUpstreamProxyProtocolContextualHeadersDeviceInfoArgs.builder()
                            .outputType("NONE")
                            .build())
                        .outputType("JSON")
                        .build())
                    .metadataHeaders(Map.ofEntries(
                        Map.entry("metadata-header1", "value1"),
                        Map.entry("metadata-header2", "value2")
                    ))
                    .gatewayIdentity("RESOURCE_NAME")
                    .clientIp(true)
                    .build())
                .build())
            .schema("PROXY_GATEWAY")
            .build());

    }
}
resources:
  default:
    type: gcp:beyondcorp:SecurityGateway
    properties:
      securityGatewayId: default-sg-spa-proxy
      displayName: My SPA Security Gateway resource
  example-spa:
    type: gcp:beyondcorp:SecurityGatewayApplication
    properties:
      securityGatewayId: ${default.securityGatewayId}
      applicationId: app-proxy
      endpointMatchers:
        - hostname: a.site.com
          ports:
            - 443
      upstreams:
        - external:
            endpoints:
              - hostname: my.proxy.service.com
                port: 443
          proxyProtocol:
            allowedClientHeaders:
              - header1
              - header2
            contextualHeaders:
              userInfo:
                outputType: PROTOBUF
              groupInfo:
                outputType: JSON
              deviceInfo:
                outputType: NONE
              outputType: JSON
            metadataHeaders:
              metadata-header1: value1
              metadata-header2: value2
            gatewayIdentity: RESOURCE_NAME
            clientIp: true
      schema: PROXY_GATEWAY

The PROXY_GATEWAY schema enables full proxy protocol configuration. The contextualHeaders block forwards BeyondCorp identity data: userInfo, groupInfo, and deviceInfo can each output as PROTOBUF, JSON, or NONE. The metadataHeaders property adds custom key-value pairs, gatewayIdentity includes the gateway resource name, and clientIp forwards the original client IP address. This gives upstream services complete visibility into the BeyondCorp security context.

Beyond these examples

These snippets focus on specific Security Gateway Application features: endpoint matching and port filtering, VPC network routing and egress policies, API Gateway and Proxy Gateway schemas, and contextual headers and identity forwarding. They’re intentionally minimal rather than full zero-trust deployments.

The examples reference pre-existing infrastructure such as BeyondCorp Security Gateway resources, VPC networks and subnets, and external services like discovery endpoints or proxy services. They focus on application configuration rather than provisioning the surrounding infrastructure.

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

  • Display names and resource descriptions (displayName)
  • Multiple upstream configurations per application
  • Advanced egress policy rules beyond region selection
  • Error handling and retry configuration

These omissions are intentional: the goal is to illustrate how each Security Gateway Application feature is wired, not provide drop-in zero-trust modules. See the BeyondCorp Security Gateway Application resource reference for all available configuration options.

Let's configure GCP BeyondCorp Security Gateway Applications

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration & Setup
What properties can't I change after creating a Security Gateway Application?
The applicationId, securityGatewayId, and project properties are immutable and cannot be changed after resource creation.
What are the naming requirements for applicationId?
The applicationId must start with a letter, contain 4-63 characters from /a-z-/, and end with a number or letter.
What's the difference between PROXY_GATEWAY and API_GATEWAY schemas?
PROXY_GATEWAY is used with endpointMatchers for proxy scenarios (matching hostnames/ports). API_GATEWAY is used without endpointMatchers for API discovery scenarios where traffic is routed directly to external endpoints.
Endpoint Matching & Routing
How does endpoint matching work with multiple matchers?
Endpoint matchers use OR logic: the application matches if any condition is met. You can specify (Hostname) alone like *.abc.com, or (Hostname & Ports) like abc.com with ports 22,33.
How do I route traffic to a VPC resource?
Configure upstreams with a network property (VPC reference) and egressPolicy specifying regions, as shown in the VPC example.
How do I route traffic to an external endpoint?
Configure upstreams.external.endpoints with hostname and port properties, as shown in the SPA API and SPA Proxy examples.

Using a different cloud?

Explore security guides for other cloud providers: