Added recipe for Apigee X with SWP

This commit is contained in:
apichick
2025-06-09 00:01:54 +02:00
committed by Wiktor Niesiobędzki
parent 077d8719dd
commit a2f4d9185b
12 changed files with 484 additions and 0 deletions

3
.gitignore vendored
View File

@@ -58,3 +58,6 @@ blueprints/gke/autopilot/bundle/monitoring/kustomization.yaml
blueprints/gke/autopilot/bundle/locust/kustomization.yaml
blueprints/gke/autopilot/bundle.tar.gz
blueprints/gke/patterns/batch/job-*.yaml
modules/apigee/recipe-apigee-swp/bundle.zip
modules/apigee/recipe-apigee-swp/deploy-apiproxy.sh

View File

@@ -18,6 +18,7 @@ This module simplifies the creation of a Apigee resources (organization, environ
- [New endpoint attachment](#new-endpoint-attachment)
- [Apigee add-ons](#apigee-add-ons)
- [IAM](#iam)
- [Recipes](#recipes)
- [Variables](#variables)
- [Outputs](#outputs)
<!-- END TOC -->
@@ -355,6 +356,10 @@ module "apigee" {
# tftest modules=1 resources=10
```
<!-- BEGIN TFDOC -->
## Recipes
- [Apigee X with Secure Web Proxy](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/blob/master/modules/apigee/recipe-apigee-swp)
## Variables
| name | description | type | required | default |

View File

@@ -0,0 +1,57 @@
# Apigee X with Secure Web Proxy
This recipe demonstrates how to configure Apigee X with Secure Web Proxy (SWP). This is a common solution when you need your Apigee X runtime to connect to numerous on-premises backends, but prefer to avoid establishing VPC peering between the Apigee X Google-managed VPC and the VPC where hybrid connectivity and advertising Apigee X runtime IP ranges to the on-premises network.
The diagram below depicts the architecture deployed:
![Architecture](./diagram.png)
In this recipe the SWP gateway has been co-located with Apigee X in the same project for ease of deployment. It's important to note that the SWP gateway's deployment is flexible and can be independently placed in a different project. Our current setup uses a privately accessible VM as the backend target for SWP. In a real-world scenario, with hybrid connectivity configured in the SWP gateway's VPC, the backend could alternatively be an on-premises host.
Once the terraform configuration is applied you can verify that all is working by running the following:
* Deploy a sample proxy to Apigee X
./deploy-apiproxy.sh
* Make a request to the proxy
curl -v &lt;API_URL&gt;/test
Note: The API_URL is returned as a terraform output
<!-- BEGIN TFDOC -->
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [analytics_region](variables.tf#L27) | Region. | <code>string</code> | ✓ | |
| [instance_region](variables.tf#L32) | Region. | <code>string</code> | ✓ | |
| [network_config](variables.tf#L37) | Network configuration. | <code title="object&#40;&#123;&#10; subnet_ip_cidr_range &#61; string&#10; subnet_psc_ip_cidr_range &#61; string&#10; subnet_proxy_only_ip_cidr_range &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [project_id](variables.tf#L46) | Project ID. | <code>string</code> | ✓ | |
| [_testing](variables.tf#L17) | Populate this variable to avoid triggering the data source. | <code title="object&#40;&#123;&#10; name &#61; string&#10; number &#61; number&#10; services_enabled &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| [api_url](outputs.tf#L17) | API url. | |
<!-- END TFDOC -->
## Test
```hcl
module "recipe_apigee_swp" {
source = "./fabric/modules/apigee/recipe-apigee-swp"
project_id = "project-1"
_testing = {
name = "project-1"
number = 1234567890
}
instance_region = "europe-west1"
analytics_region = "europe-west1"
network_config = {
subnet_ip_cidr_range = "10.16.0.0/24"
subnet_psc_ip_cidr_range = "10.16.1.0/24"
subnet_proxy_only_ip_cidr_range = "10.16.2.0/24"
}
}
# tftest modules=10 resources=43

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ProxyEndpoint name="default">
<HTTPProxyConnection>
<BasePath>/test</BasePath>
</HTTPProxyConnection>
<RouteRule name="default">
<TargetEndpoint>default</TargetEndpoint>
</RouteRule>
</ProxyEndpoint>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TargetEndpoint name="default">
<PreFlow name="PreFlow">
<Request />
<Response />
</PreFlow>
<Flows />
<PostFlow name="PostFlow">
<Request />
<Response />
</PostFlow>
<HTTPTargetConnection>
<URL>http://10.16.0.4</URL>
</HTTPTargetConnection>
</TargetEndpoint>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<APIProxy revision="1" name="test">
<Description>test</Description>
</APIProxy>

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

View File

@@ -0,0 +1,257 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
# Added this because there is a bug on the provider
provider "google" {
region = var.instance_region
}
locals {
hostname = "${module.addresses.global_addresses.apigee.address}.nip.io"
environment = "dev"
envgroup = "apis"
environments = {
(local.environment) = {
envgroups = [local.envgroup]
forward_proxy_uri = "http://${module.apigee.endpoint_attachment_hosts["swp"]}:8080"
}
}
instances = {
(var.instance_region) = {
environments = keys(local.environments)
} }
}
module "project" {
source = "../../../modules/project"
name = var.project_id
project_reuse = {
use_data_source = var._testing == null
project_attributes = var._testing
}
services = [
"apigee.googleapis.com",
"compute.googleapis.com",
"networksecurity.googleapis.com",
"networkservices.googleapis.com",
]
}
module "vpc" {
source = "../../../modules/net-vpc"
project_id = module.project.id
name = "vpc"
subnets = [
{
ip_cidr_range = var.network_config.subnet_ip_cidr_range
name = "subnet-${var.instance_region}"
region = var.instance_region
}
]
subnets_psc = [
{
ip_cidr_range = var.network_config.subnet_psc_ip_cidr_range
name = "subnet-psc-${var.instance_region}"
region = var.instance_region
}
]
subnets_proxy_only = [
{
ip_cidr_range = var.network_config.subnet_proxy_only_ip_cidr_range
name = "subnet-proxy-only-${var.instance_region}"
region = var.instance_region
active = true
}
]
}
module "firewall" {
source = "../../../modules/net-vpc-firewall"
project_id = module.project.id
network = module.vpc.name
default_rules_config = {
disabled = true
}
ingress_rules = {
allow-ingress-http = {
description = "Allow ingress to http servers."
targets = ["http-server"]
rules = [{ protocol = "tcp", ports = [80] }]
source_ranges = [var.network_config.subnet_proxy_only_ip_cidr_range]
}
}
}
module "apigee" {
source = "../../../modules/apigee"
project_id = module.project.project_id
organization = {
analytics_region = var.analytics_region
billing_type = "EVALUATION"
runtime_type = "CLOUD"
retention = "MINIMUM"
disable_vpc_peering = true
}
envgroups = {
"apis" = [local.hostname]
}
environments = local.environments
instances = local.instances
endpoint_attachments = {
swp = {
region = var.instance_region
service_attachment = module.swp.service_attachment
}
}
}
module "ext_lb" {
source = "../../../modules/net-lb-app-ext"
name = "glb"
project_id = module.project.id
forwarding_rules_config = {
"" = {
address = (
module.addresses.global_addresses.apigee.address
)
}
}
protocol = "HTTPS"
use_classic_version = false
backend_service_configs = {
default = {
backends = [for k, v in module.apigee.instances : { backend = "neg-${k}" }]
protocol = "HTTPS"
health_checks = []
}
}
neg_configs = {
for k, v in module.apigee.instances :
"neg-${k}" => { psc = {
region = k
target_service = v.service_attachment
network = module.vpc.self_link
subnetwork = module.vpc.subnets_psc["${var.instance_region}/subnet-psc-${var.instance_region}"].self_link
}
}
}
ssl_certificates = {
managed_configs = {
default = {
domains = [local.hostname]
}
}
}
}
module "swp" {
source = "../../../modules/net-swp"
project_id = module.project.id
region = var.instance_region
name = "gateway"
network = module.vpc.id
subnetwork = module.vpc.subnet_self_links["${var.instance_region}/subnet-${var.instance_region}"]
gateway_config = {
addresses = [module.addresses.internal_addresses["gateway"].address]
ports = [8080]
}
service_attachment = {
nat_subnets = [module.vpc.subnets_psc["${var.instance_region}/subnet-psc-${var.instance_region}"].self_link]
automatic_connection = true
}
policy_rules = {
allowed-hosts = {
priority = 1000
allow = true
session_matcher = "host() == '${module.nginx_vm.internal_ip}'"
}
}
}
module "addresses" {
source = "../../../modules/net-address"
project_id = module.project.project_id
internal_addresses = {
gateway = {
region = var.instance_region
subnetwork = module.vpc.subnet_self_links["${var.instance_region}/subnet-${var.instance_region}"]
}
}
global_addresses = {
apigee = {}
}
}
module "nginx_vm" {
source = "../../../modules/compute-vm"
project_id = module.project.project_id
zone = "${var.instance_region}-b"
name = "nginx"
network_interfaces = [{
network = module.vpc.self_link
subnetwork = module.vpc.subnet_self_links["${var.instance_region}/subnet-${var.instance_region}"]
}]
metadata = {
startup-script = <<-EOF
#! /bin/bash
apt-get update
apt-get install -y nginx
EOF
}
service_account = {
auto_create = true
}
tags = [
"http-server"
]
}
resource "local_file" "target_endpoint_file" {
content = templatefile("${path.module}/templates/targets/default.xml.tpl", {
ip_address = module.nginx_vm.internal_ip
})
filename = "${path.module}/bundle/apiproxy/targets/default.xml"
file_permission = "0644"
}
# tflint-ignore: terraform_unused_declarations
data "archive_file" "bundle" {
type = "zip"
source_dir = "${path.module}/bundle"
output_path = "${path.module}/bundle.zip"
depends_on = [
local_file.target_endpoint_file
]
}
resource "local_file" "deploy_apiproxy_file" {
content = templatefile("${path.module}/templates/deploy-apiproxy.sh.tpl", {
organization = module.apigee.org_name
environment = local.environment
})
filename = "${path.module}/deploy-apiproxy.sh"
file_permission = "0755"
}
module "nat" {
source = "../../../modules/net-cloudnat"
project_id = module.project.project_id
region = var.instance_region
name = "nat-${var.instance_region}"
router_network = module.vpc.self_link
}

View File

@@ -0,0 +1,20 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
output "api_url" {
description = "API url."
value = "https://${local.hostname}/test"
}

View File

@@ -0,0 +1,20 @@
#!/bin/bash
ORGANIZATION=${organization}
ENVIRONMENT=${environment}
export TOKEN=$(gcloud auth print-access-token)
curl -v -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type:application/octet-stream" \
-T 'bundle.zip' \
"https://apigee.googleapis.com/v1/organizations/$ORGANIZATION/apis?name=test&action=import"
curl -v -X POST \
-H "Authorization: Bearer $TOKEN" \
"https://apigee.googleapis.com/v1/organizations/$ORGANIZATION/environments/$ENVIRONMENT/apis/test/revisions/1/deployments"
curl -v \
-H "Authorization: Bearer $TOKEN" \
"https://apigee.googleapis.com/v1/organizations/$ORGANIZATION/environments/$ENVIRONMENT/apis/test/revisions/1/deployments"

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TargetEndpoint name="default">
<PreFlow name="PreFlow">
<Request />
<Response />
</PreFlow>
<Flows />
<PostFlow name="PostFlow">
<Request />
<Response />
</PostFlow>
<HTTPTargetConnection>
<URL>http://${ip_address}</URL>
</HTTPTargetConnection>
</TargetEndpoint>

View File

@@ -0,0 +1,49 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
variable "_testing" {
description = "Populate this variable to avoid triggering the data source."
type = object({
name = string
number = number
services_enabled = optional(list(string), [])
})
default = null
}
variable "analytics_region" {
description = "Region."
type = string
}
variable "instance_region" {
description = "Region."
type = string
}
variable "network_config" {
description = "Network configuration."
type = object({
subnet_ip_cidr_range = string
subnet_psc_ip_cidr_range = string
subnet_proxy_only_ip_cidr_range = string
})
}
variable "project_id" {
description = "Project ID."
type = string
}