diff --git a/.github/workflows/terraform-docs.yml b/.github/workflows/terraform-docs.yml
new file mode 100644
index 0000000..54e0ad2
--- /dev/null
+++ b/.github/workflows/terraform-docs.yml
@@ -0,0 +1,17 @@
+name: Generate terraform docs
+on:
+ - pull_request
+
+jobs:
+ docs:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.pull_request.head.ref }}
+
+ - name: Render terraform docs and push changes back to PR
+ uses: terraform-docs/gh-actions@main
+ with:
+ working-dir: .
+ git-push: "true"
\ No newline at end of file
diff --git a/.github/workflows/terraform-fmt.yml b/.github/workflows/terraform-fmt.yml
new file mode 100644
index 0000000..051e79f
--- /dev/null
+++ b/.github/workflows/terraform-fmt.yml
@@ -0,0 +1,17 @@
+name: Generate terraform docs
+on:
+ - pull_request
+
+jobs:
+ format:
+ runs-on: ubuntu-latest
+ name: Terraform Format Check
+ steps:
+ - name: Setup terraform
+ uses: hashicorp/setup-terraform@v2
+ with:
+ terraform_version: ${{ inputs.terraform_version }}
+
+ - name: Terraform fmt check
+ run: terraform fmt -check -diff -recursive
+ id: fmt
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5224284
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,36 @@
+# Local .terraform directories
+**/.terraform/*
+
+# .tfstate files
+*.tfstate
+*.tfstate.*
+
+# Crash log files
+crash.log
+crash.*.log
+
+# Exclude all .tfvars files, which are likely to contain sensitive data, such as
+# password, private keys, and other secrets. These should not be part of version
+# control as they are data points which are potentially sensitive and subject
+# to change depending on the environment.
+*.tfvars
+*.tfvars.json
+
+# Ignore override files as they are usually used to override resources locally and so
+# are not checked in
+override.tf
+override.tf.json
+*_override.tf
+*_override.tf.json
+
+# Include override files you do wish to add to version control using negated pattern
+# !example_override.tf
+
+# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
+# example: *tfplan*
+
+# Ignore CLI configuration files
+.terraformrc
+terraform.rc
+
+.idea
\ No newline at end of file
diff --git a/.terraform-docs.yml b/.terraform-docs.yml
new file mode 100644
index 0000000..616f685
--- /dev/null
+++ b/.terraform-docs.yml
@@ -0,0 +1,12 @@
+formatter: "markdown"
+output:
+ file: "README.md"
+ mode: inject
+
+sections:
+ hide:
+ - resources
+ - modules
+
+header-from: docs/.header.md
+footer-from: docs/.footer.md
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..abc1a1c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,64 @@
+
+# HA VPN between GCP and OCI
+
+This repository contains a drop-in Terraform module that sets up a HA VPN between Azure and Oracle Cloud Infrastructure (OCI).
+
+## Features:
+
+- On GCP side a HA VPN is set up with two or four tunnels. (See [documentation](https://cloud.google.com/network-connectivity/docs/vpn/how-to/creating-ha-vpn))
+- On OCI side two Site-to-Site VPN connections are set up with two connections.
+- Both sides are configured to establish BGP sessions between each other, so two platforms automatically learn the routes from each other.
+- Proper routes are propagated from the GCP side to enable Private Google Access from OCI.
+
+## Installation
+
+On OCI side: A compartment, a DRG
+On GCP side: A project, a VPC network
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [google](#requirement\_google) | >= 3.30.0, < 5.0 |
+| [oci](#requirement\_oci) | ~> 5.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [google](#provider\_google) | >= 3.30.0, < 5.0 |
+| [oci](#provider\_oci) | ~> 5.0 |
+| [random](#provider\_random) | n/a |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [google_compute_network.vpc](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/compute_network) | data source |
+| [google_project.gcp_project](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/project) | data source |
+| [oci_core_ipsec_connection_tunnels.gcp](https://registry.terraform.io/providers/hashicorp/oci/latest/docs/data-sources/core_ipsec_connection_tunnels) | data source |
+| [oci_identity_compartment.compartment](https://registry.terraform.io/providers/hashicorp/oci/latest/docs/data-sources/identity_compartment) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [four\_tunnels\_redundancy](#input\_four\_tunnels\_redundancy) | Whether to deploy four tunnels or not. When set to `false`, only two tunnels are established. | `bool` | `false` | no |
+| [gcp\_asn](#input\_gcp\_asn) | Specifies the ASN of GCP side of the BGP session | `number` | `65516` | no |
+| [gcp\_network\_name](#input\_gcp\_network\_name) | Specifies the name of the VPC the VPN will be located in | `string` | n/a | yes |
+| [gcp\_project\_id](#input\_gcp\_project\_id) | Specifies the project ID of Google project the VPN will be located in | `string` | n/a | yes |
+| [gcp\_vpn\_region](#input\_gcp\_vpn\_region) | Specifies the GCP region the VPN will be located in | `string` | n/a | yes |
+| [oci\_compartment\_id](#input\_oci\_compartment\_id) | OCID of the compartment where the VPN will be created | `string` | n/a | yes |
+| [oci\_drg\_id](#input\_oci\_drg\_id) | OCID of the DRG (Dynamic Routing Gateway) where the VPN will be connected to | `string` | n/a | yes |
+| [shared\_secret](#input\_shared\_secret) | Shared secret for the VPN connection. When left empty, a random secret is created and shared between GCP and OCI. | `string` | `null` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [shared\_secret](#output\_shared\_secret) | Shared Secret that was used to establish the VPN connection |
+
+## License
+
+[Apache License 2.0](LICENSE)
+
\ No newline at end of file
diff --git a/docs/.footer.md b/docs/.footer.md
new file mode 100644
index 0000000..d31958c
--- /dev/null
+++ b/docs/.footer.md
@@ -0,0 +1,3 @@
+## License
+
+[Apache License 2.0](LICENSE)
\ No newline at end of file
diff --git a/docs/.header.md b/docs/.header.md
new file mode 100644
index 0000000..b48c44e
--- /dev/null
+++ b/docs/.header.md
@@ -0,0 +1,15 @@
+# HA VPN between GCP and OCI
+
+This repository contains a drop-in Terraform module that sets up a HA VPN between Azure and Oracle Cloud Infrastructure (OCI).
+
+## Features:
+
+- On GCP side a HA VPN is set up with two or four tunnels. (See [documentation](https://cloud.google.com/network-connectivity/docs/vpn/how-to/creating-ha-vpn))
+- On OCI side two Site-to-Site VPN connections are set up with two connections.
+- Both sides are configured to establish BGP sessions between each other, so two platforms automatically learn the routes from each other.
+- Proper routes are propagated from the GCP side to enable Private Google Access from OCI.
+
+## Installation
+
+On OCI side: A compartment, a DRG
+On GCP side: A project, a VPC network
\ No newline at end of file
diff --git a/gcp.tf b/gcp.tf
new file mode 100644
index 0000000..0d57a87
--- /dev/null
+++ b/gcp.tf
@@ -0,0 +1,136 @@
+data "google_project" "gcp_project" {
+ project_id = var.gcp_project_id
+}
+
+data "google_compute_network" "vpc" {
+ project = data.google_project.gcp_project.project_id
+ name = var.gcp_network_name
+}
+
+
+resource "google_compute_ha_vpn_gateway" "ha_gateway" {
+ name = "to-oci"
+ project = data.google_project.gcp_project.project_id
+ region = var.gcp_vpn_region
+ network = data.google_compute_network.vpc.name
+}
+
+resource "google_compute_external_vpn_gateway" "external_gateway" {
+ count = 2
+ name = "oci-${count.index + 1}"
+ project = data.google_project.gcp_project.project_id
+ redundancy_type = "TWO_IPS_REDUNDANCY"
+
+ interface {
+ id = "0"
+ ip_address = data.oci_core_ipsec_connection_tunnels.gcp[count.index].ip_sec_connection_tunnels[0].vpn_ip
+ }
+
+ interface {
+ id = "1"
+ ip_address = data.oci_core_ipsec_connection_tunnels.gcp[count.index].ip_sec_connection_tunnels[1].vpn_ip
+ }
+}
+
+
+resource "google_compute_router" "router" {
+
+ name = "vpn-to-oci"
+ project = data.google_project.gcp_project.project_id
+ region = var.gcp_vpn_region
+ network = data.google_compute_network.vpc.name
+ bgp {
+ advertise_mode = "CUSTOM"
+ advertised_groups = ["ALL_SUBNETS"]
+ advertised_ip_ranges {
+ range = "199.36.153.8/30"
+ description = "private.googleapis.com"
+ }
+ advertised_ip_ranges {
+ range = "199.36.153.4/30"
+ description = "restricted.googleapis.com"
+ }
+ asn = var.gcp_asn
+ }
+}
+
+
+resource "google_compute_vpn_tunnel" "tunnels_primary" {
+ depends_on = [
+ oci_core_ipsec_connection_tunnel_management.primary,
+ ]
+ count = 2
+ project = data.google_project.gcp_project.project_id
+ region = var.gcp_vpn_region
+ name = "oci-primary-${count.index + 1}"
+ router = google_compute_router.router.id
+ peer_external_gateway = google_compute_external_vpn_gateway.external_gateway[count.index].id
+ peer_external_gateway_interface = 0
+ vpn_gateway_interface = count.index
+ ike_version = 2
+ shared_secret = local.shared_secret
+ vpn_gateway = google_compute_ha_vpn_gateway.ha_gateway.self_link
+}
+
+
+resource "google_compute_vpn_tunnel" "tunnels_secondary" {
+ depends_on = [
+ oci_core_ipsec_connection_tunnel_management.secondary,
+ ]
+ count = var.four_tunnels_redundancy ? 2 : 0
+ project = data.google_project.gcp_project.project_id
+ region = var.gcp_vpn_region
+ name = "oci-secondary-${count.index + 1}"
+ router = google_compute_router.router.id
+ peer_external_gateway = google_compute_external_vpn_gateway.external_gateway[count.index].id
+ peer_external_gateway_interface = 1
+ vpn_gateway_interface = count.index
+ ike_version = 2
+ shared_secret = local.shared_secret
+ vpn_gateway = google_compute_ha_vpn_gateway.ha_gateway.self_link
+}
+
+resource "google_compute_router_interface" "router_interface_primary" {
+ count = 2
+ project = data.google_project.gcp_project.project_id
+ region = var.gcp_vpn_region
+ name = "oci-primary-${count.index + 1}"
+ router = google_compute_router.router.name
+ vpn_tunnel = google_compute_vpn_tunnel.tunnels_primary[count.index].name
+}
+
+resource "google_compute_router_interface" "router_interface_secondary" {
+ count = var.four_tunnels_redundancy ? 2 : 0
+ project = data.google_project.gcp_project.project_id
+ region = var.gcp_vpn_region
+ name = "oci-secondary-${count.index + 1}"
+ router = google_compute_router.router.name
+ vpn_tunnel = google_compute_vpn_tunnel.tunnels_secondary[count.index].name
+}
+
+
+
+resource "google_compute_router_peer" "bgp_peer_primary" {
+ count = 2
+ project = data.google_project.gcp_project.project_id
+ region = var.gcp_vpn_region
+ name = "oci-primary-${count.index + 1}"
+ router = google_compute_router.router.name
+ ip_address = "169.254.2${count.index + 1}.1" #oci_core_ipsec_connection_tunnel_management.primary[count.index].bgp_session_info[0].customer_interface_ip
+ peer_ip_address = "169.254.2${count.index + 1}.2" #oci_core_ipsec_connection_tunnel_management.primary[count.index].bgp_session_info[0].oracle_interface_ip
+ peer_asn = oci_core_ipsec_connection_tunnel_management.primary[count.index].bgp_session_info[0].oracle_bgp_asn
+ interface = google_compute_router_interface.router_interface_primary[count.index].name
+}
+
+
+resource "google_compute_router_peer" "bgp_peer_secondary" {
+ count = var.four_tunnels_redundancy ? 2 : 0
+ project = data.google_project.gcp_project.project_id
+ region = var.gcp_vpn_region
+ name = "oci-secondary-${count.index + 1}"
+ router = google_compute_router.router.name
+ ip_address = "169.254.3${count.index + 1}.1" #oci_core_ipsec_connection_tunnel_management.primary[count.index].bgp_session_info[0].customer_interface_ip
+ peer_ip_address = "169.254.3${count.index + 1}.2" #oci_core_ipsec_connection_tunnel_management.primary[count.index].bgp_session_info[0].oracle_interface_ip
+ peer_asn = oci_core_ipsec_connection_tunnel_management.secondary[count.index].bgp_session_info[0].oracle_bgp_asn
+ interface = google_compute_router_interface.router_interface_secondary[count.index].name
+}
\ No newline at end of file
diff --git a/main.tf b/main.tf
new file mode 100644
index 0000000..c05c6d5
--- /dev/null
+++ b/main.tf
@@ -0,0 +1,28 @@
+terraform {
+ required_providers {
+ oci = {
+ version = "~> 5.0"
+ }
+ google = {
+ source = "hashicorp/google"
+ version = ">= 3.30.0, < 5.0"
+ }
+ }
+}
+
+resource "random_password" "shared_secret" {
+ count = var.shared_secret == null ? 1 : 0
+ length = 16
+ lower = true
+ special = false
+}
+
+locals {
+ shared_secret = var.shared_secret == null ? random_password.shared_secret[0].result : var.shared_secret
+}
+
+output "shared_secret" {
+ sensitive = true
+ value = local.shared_secret
+ description = "Shared Secret that was used to establish the VPN connection"
+}
\ No newline at end of file
diff --git a/oci.tf b/oci.tf
new file mode 100644
index 0000000..ad53440
--- /dev/null
+++ b/oci.tf
@@ -0,0 +1,64 @@
+data "oci_identity_compartment" "compartment" {
+ id = var.oci_compartment_id
+}
+
+resource "oci_core_cpe" "gcp" {
+ count = 2
+ compartment_id = data.oci_identity_compartment.compartment.id
+ ip_address = google_compute_ha_vpn_gateway.ha_gateway.vpn_interfaces[count.index].ip_address
+ display_name = "gcp-${count.index + 1}"
+}
+
+resource "oci_core_ipsec" "gcp" {
+ count = 2
+ compartment_id = data.oci_identity_compartment.compartment.id
+ cpe_id = oci_core_cpe.gcp[count.index].id
+ drg_id = var.oci_drg_id
+ display_name = "to-gcp-${count.index + 1}"
+ static_routes = [
+ "1.1.1.1/32" #https://github.com/oracle/terraform-provider-oci/issues/1509#issuecomment-1126299971
+ ]
+
+}
+
+data "oci_core_ipsec_connection_tunnels" "gcp" {
+ count = 2
+ ipsec_id = oci_core_ipsec.gcp[count.index].id
+}
+
+resource "oci_core_ipsec_connection_tunnel_management" "primary" {
+ count = 2
+ #Required
+ ipsec_id = oci_core_ipsec.gcp[count.index].id
+ tunnel_id = data.oci_core_ipsec_connection_tunnels.gcp[count.index].ip_sec_connection_tunnels[0].id
+ routing = "BGP"
+ #Optional
+ display_name = "to-gcp-${count.index + 1}-primary"
+ bgp_session_info {
+ #Optional
+ customer_bgp_asn = var.gcp_asn
+ customer_interface_ip = "169.254.2${count.index + 1}.1/30"
+ oracle_interface_ip = "169.254.2${count.index + 1}.2/30"
+ }
+
+ shared_secret = local.shared_secret
+ ike_version = "V2"
+}
+
+resource "oci_core_ipsec_connection_tunnel_management" "secondary" {
+ count = 2
+ #Required
+ ipsec_id = oci_core_ipsec.gcp[count.index].id
+ tunnel_id = data.oci_core_ipsec_connection_tunnels.gcp[count.index].ip_sec_connection_tunnels[1].id
+ routing = "BGP"
+ #Optional
+ display_name = "to-gcp-${count.index + 1}-primary"
+ bgp_session_info {
+ #Optional
+ customer_bgp_asn = var.gcp_asn
+ customer_interface_ip = "169.254.3${count.index + 1}.1/30"
+ oracle_interface_ip = "169.254.3${count.index + 1}.2/30"
+ }
+ shared_secret = local.shared_secret
+ ike_version = "V2"
+}
\ No newline at end of file
diff --git a/variables.tf b/variables.tf
new file mode 100644
index 0000000..d921406
--- /dev/null
+++ b/variables.tf
@@ -0,0 +1,46 @@
+# Common variables
+
+variable "shared_secret" {
+ type = string
+ default = null
+ description = "Shared secret for the VPN connection. When left empty, a random secret is created and shared between GCP and OCI."
+}
+
+# GCP variables
+variable "gcp_project_id" {
+ type = string
+ description = "Specifies the project ID of Google project the VPN will be located in"
+}
+
+variable "gcp_network_name" {
+ type = string
+ description = "Specifies the name of the VPC the VPN will be located in"
+}
+
+variable "gcp_vpn_region" {
+ type = string
+ description = "Specifies the GCP region the VPN will be located in"
+}
+
+variable "gcp_asn" {
+ type = number
+ description = "Specifies the ASN of GCP side of the BGP session"
+ default = 65516
+}
+
+# OCI
+variable "oci_compartment_id" {
+ type = string
+ description = "OCID of the compartment where the VPN will be created"
+}
+
+variable "oci_drg_id" {
+ type = string
+ description = "OCID of the DRG (Dynamic Routing Gateway) where the VPN will be connected to"
+}
+
+variable "four_tunnels_redundancy" {
+ type = bool
+ default = false
+ description = "Whether to deploy four tunnels or not. When set to `false`, only two tunnels are established."
+}
\ No newline at end of file