Using WireGuard for private connectivity to OCI services

Ali Mukadam
Oracle Developers
Published in
10 min readDec 7, 2023
Photo by Markus Spiske on Unsplash

I am fairly certain that I wrote on WireGuard a while ago. I even published an early Terraform module for it but strangely I cannot find the article. No matter, let’s remedy this situation.

WireGuard is a lean and mean open source VPN solution and is now also included in Oracle Linux 9. In this article, we’ll use WireGuard to securely access OCI services and other resources running on OCI e.g. our OKE Cluster’s API server, a database server and so on. For the purpose of this article, we’ll install it on our bastion host:

Setting up WireGuard is easy. Let’s first install the packages on the bastion host.

sudo su
dnf install -y wireguard-tools
cd /etc/wireguard/
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.conf && sysctl -p
firewall-cmd --permanent --add-port=51820/udp
firewall-cmd --reload
umask 077
wg genkey | tee privatekey | wg pubkey > publickey

Next, create a file on the bastion:

touch /etc/wireguard/wg0.conf

And enter the following:

[Interface]
Address = 192.168.2.1/32
SaveConfig = true
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE
ListenPort = 51820
# The server's private key
PrivateKey = qAyzaJ/aFc7e0x6FobBAEUyazaRHsv+gqTX/olxYAHU=

[Peer]
# The client's public key, generated by your local WireGuard client
PublicKey = 6zPQz85E60KX8hTqJF4MH5egMWm6gjVONOKhSTeJi14=
AllowedIPs = 192.168.2.2/32
# The client's public IP address. This is a fictional IP address. Replace with your own
Endpoint = 12.33.44.55:1342

Then, start the WireGuard service:

wg-quick up wg0

On your laptop/workstation, create a tunnel. If you are using Windows, create an empty tunnel and then update the configuration:

[Interface]
# generated by WireGuard client
PrivateKey = aMQRcY1wWKHrquh6hTfgzAFCvPn+RaB9XKZZS8yO3no=
ListenPort = 60477
Address = 192.168.2.2/32

[Peer]
# Server's public key
PublicKey = 6TWNAmXOFnfYtfjEF50FLK5r+x3eyQ0dMBiTt1T4ngk=
AllowedIPs = 10.0.0.0/16, 192.168.0.0/16
# The Server's public IP address
Endpoint = 12.33.44.56:51820

On the bastion’s NSG, add an ingress rule for WireGuard:

The rule above will allow WireGuard connection between your laptop/workstation to bastion host running WireGuard.

From your laptop/workstation, you can now initiate the connection. Once the tunnel is established, you should be able to ssh to the operator host using its private IP address and without having to jump through the bastion host:

ssh opc@10.0.0.11
The authenticity of host ‘10.0.0.11 (10.0.0.11)’ can’t be established.

[opc@o-kbitjt ~]$

Accessing OKE’s private control plane

When provisioning an OKE cluster with the Terraform OKE module, if you enable the bastion and operator hosts, you can interact with your cluster’s control plane by ssh jumping through the bastion onto the operator. With the operator, we also take the opportunity to simplify and set up tooling e.g. oci-cli, kubectl, helm.

This works well for production environment where you want to restrict user access but this approach is less efficient and less cost-effective for non-production environments. Imagine running n number of clusters and each with an operator host. This will very quickly add to your costs. Sharing an operator is also not that straightforward either as you need to ensure developers, administrators do not change each other’s contexts.

No, for non-production environment, we want the following:

  • easy to standup and tear down developers/team’s own environment
  • isolated clusters to reduce blast radius in case of mishaps
  • secure access to the cluster from local

First, let’s obtain the kubeconfig. You must already have oci-cli installed and configured locally. Navigate to the OKE page and copy the command to retrieve the kubeconfig:

Next, navigate to the NSGs page in the VCN and edit the control plane NSG to add an ingress rule:

Edit the bastion NSG and add an egress rule:

We can now run kubectl locally:

kubectl get nodes
NAME STATUS ROLES AGE VERSION
10.0.67.85 Ready node 5h30m v1.27.2
10.0.85.106 Ready node 5h30m v1.27.2
10.0.95.181 Ready node 5h30m v1.27.2

If you are operating multiple clusters, you can even take a hub-and-spoke approach to give access to multiple teams:

If you have multiple users/teams, then on the server side you can create multiple peers as below:

[Interface]
Address = 192.168.2.1/32
SaveConfig = true
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE
ListenPort = 51820
PrivateKey = qAyzaJ/aFc7e0x6FobBAEUyazaRHsv+gqTX/olxYAHU=

[Peer]
PublicKey = 6zPQz85E60KX8hTqJF4MH5egMWm6gjVONOKhSTeJi14=
AllowedIPs = 192.168.2.2/32
Endpoint = 12.33.44.55:1342

[Peer]
PublicKey = WrTYs9e3f2zE90TFqfyO6QGzST0rQ/PvyeHTmAr3RV4=
AllowedIPs = 192.168.2.3/32
Endpoint = 12.33.44.54:1342

[Peer]
PublicKey = s2DVwkosmYX7NrvPFFUGMnvTCVowosBy5U6aM0glHhk=
AllowedIPs = 192.168.2.4/32
Endpoint = 12.33.44.56:1342

Accessing a database on OCI over WireGuard

Let’s say we now want to securely access a dev database running on OCI.

Provision a database, say DBCS in a dedicated subnet. If you use NSGs, where you position your resources does not really matter and the security of your resources is implemented by the NSGs; I just like to have clear network boundaries with the NSGs.

Follow the same approach as before:

  1. Ensure your database is using a “db” NSG which accepts TCP ingress on port 1521 with source whose NSG is “bastion”
  2. Ensure your bastion NSG accepts TCP egress on port 1521 to resources whose NSG is “db”

Use sqldeveloper to connect:

You can find the IP address under Nodes:

DNS Name resolution over WireGuard for the Oracle Database

Well, this was for a single-node Database. What if instead of the IP address, you prefer to use the Database Scan address?

We must find a way to resolve the SCAN. OCI VCN allows you to configure a DNS resolver.

First, create a new NSG. Let’s call it wireguard and add the following pairs of ingresses and egresses:

Security Rules for DNS resolution over WireGuard

Navigate back to the main VCN page and click on the DNS resolver link and under Resources, click on Endpoints. Create a Listener Endpoint with the following configuration:

VCN DNS Listener Configuration

The endpoint will be given an IP address:

Edit your client WireGuard configuration to point DNS to it:

[Interface]
# generated by WireGuard client
PrivateKey = aMQRcY1wWKHrquh6hTfgzAFCvPn+RaB9XKZZS8yO3no=
ListenPort = 60477
Address = 192.168.2.2/32
# add this line for name resolution
DNS = 10.0.0.5

[Peer]
# Server's public key
PublicKey = 6TWNAmXOFnfYtfjEF50FLK5r+x3eyQ0dMBiTt1T4ngk=
AllowedIPs = 10.0.0.0/16, 192.168.0.0/16
# The Server's public IP address
Endpoint = 12.33.44.56:51820

From our local machine, we can now resolve the SCAN address:

$ nslookup mr-scan.workers.oke.oraclevcn.com
Server: wglistener.bastion.oke.oraclevcn.com
Address: 10.0.0.5

Name: mr-scan.workers.oke.oraclevcn.com
Address: 10.0.82.183

We can now change our SQL Developer configuration to use the SCAN address instead:

Local Helidon development with Oracle DB on OCI over WireGuard

Now that we can run Oracle DB on OCI and access it securely from a development workstation, we can use this approach to develop a Helidon application locally that uses an Oracle DB on OCI transparently:

Helidon accessing Oracle Database on OCI over WireGuard

Let’s regenerate the catalog application but this time with database support:

helidon init
Looking up default Helidon version
Helidon versions
(1) 4.0.1
(2) 3.2.3
(3) 2.6.4
(4) Show all versions
Enter selection (default: 1): 1

Helidon version: 4.0.1

| Helidon Flavor

Select a Flavor
(1) se | Helidon SE
(2) mp | Helidon MP
Enter selection (default: 1): 1

| Application Type

Select an Application Type
(1) quickstart | Quickstart
(2) database | Database
(3) custom | Custom
Enter selection (default: 1): 2

| Media Support

Select a JSON library
(1) jsonp | JSON-P
(2) jackson | Jackson
(3) jsonb | JSON-B
Enter selection (default: 1):

| Database

Select a Database Server
(1) h2 | H2
(2) mysql | MySQL
(3) oracledb | Oracle DB
(4) mongodb | MongoDB
Enter selection (default: 1): 3

| Customize Project

Project groupId (default: vzlabs.helidon): vzlabs.sockshop
Project artifactId (default: database-se): catalog
Project version (default: 1.0-SNAPSHOT):
Java package name (default: vzlabs.se.database): vzlabs.sockshop.catalog

Switch directory to /opt/demos/sockshop/catalog to use CLI

Start development loop? (default: n):

In the OCI Console, navigate to the PDB page of your database:

Click on DB Connection and copy the Easy Connect connection string:

Helidon’s application.yaml will already have been created:

server:
port: 8080
host: 0.0.0.0

app:
greeting: "Hello"

db:
source: jdbc
connection:
url: jdbc:oracle:thin:@mr.workers.oke.oraclevcn.com:1521/medrec_pdb1.workers.oke.oraclevcn.com
username: sockshop
password: WElcome-01#
initializationFailTimeout: -1
connectionTimeout: 2000
health-check:
type: "query"
statementName: "health-check"
statements:
health-check: "SELECT 1 FROM DUAL"
create-types: "CREATE TABLE PokeTypes (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
create-pokemons: "CREATE TABLE Pokemons (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL, id_type INTEGER NOT NULL REFERENCES PokeTypes(id))"
select-all-types: "SELECT id, name FROM PokeTypes"
select-all-pokemons: "SELECT id, name, id_type FROM Pokemons"
select-pokemon-by-id: "SELECT id, name, id_type FROM Pokemons WHERE id = :id"
select-pokemon-by-name: "SELECT id, name, id_type FROM Pokemons WHERE name = ?"
insert-type: "INSERT INTO PokeTypes(id, name) VALUES(?, ?)"
insert-pokemon: "INSERT INTO Pokemons(id, name, id_type) VALUES(?, ?, ?)"
update-pokemon-by-id: "UPDATE Pokemons SET name = :name, id_type = :idType WHERE id = :id"
delete-pokemon-by-id: "DELETE FROM Pokemons WHERE id = :id"
delete-all-types: "DELETE FROM PokeTypes"
delete-all-pokemons: "DELETE FROM Pokemons"

Use the copied connection string to replace everything after the ‘@’ character as above. Ensure you also have the necessary user created e.g.

create user sockshop identified by "WElcome-01#" default tablespace users temporary tablespace temp;

commit;

alter user sockshop quota unlimited on users;

grant connect, resource to sockshop;

You can now start the Helidon application:

helidon dev

helidon dev starting

helidon dev

| building
| build completed (1.5 seconds)
| catalog starting

2023.12.07 17:45:37.518 HikariPool-1 - Starting...
2023.12.07 17:45:37.855 HikariPool-1 - Start completed.
2023.12.07 17:45:37.905 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeGauge@8c71f846
2023.12.07 17:45:37.908 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeGauge@763f8121
2023.12.07 17:45:37.909 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeGauge@8cb1f1f9
2023.12.07 17:45:37.910 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeGauge@c923efe7
2023.12.07 17:45:37.911 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeGauge@9ef2fc51
2023.12.07 17:45:37.912 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeGauge@c47c45b3
2023.12.07 17:45:37.912 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeGauge@5007121b
2023.12.07 17:45:37.913 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeGauge@d3477c9d
2023.12.07 17:45:37.913 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeFunctionCounter@f0493908
2023.12.07 17:45:37.914 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeFunctionCounter@6b6e6c61
2023.12.07 17:45:37.914 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeGauge@59417b18
2023.12.07 17:45:37.914 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeGauge@fe8717a
2023.12.07 17:45:37.915 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeFunctionCounter@57613ee1
2023.12.07 17:45:37.916 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeFunctionCounter@cbd5f394
2023.12.07 17:45:37.917 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeFunctionCounter@6df5c008
2023.12.07 17:45:37.917 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeFunctionCounter@e26a74bb
2023.12.07 17:45:37.918 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeFunctionCounter@91b6f2f0
2023.12.07 17:45:37.918 Unexpected discovery of unknown previously-created meter; creating wrapper for io.micrometer.core.instrument.composite.CompositeFunctionCounter@62ba7a3
WEB server is up! http://localhost:8080/simple-greet
2023.12.07 17:45:40.934 Helidon SE 4.0.1 features: [Config, Encoding, Health, Media, Metrics, Observe, WebServer]
2023.12.07 17:45:40.936 [0x54def875] http://0.0.0.0:8080 bound for socket '@default'
2023.12.07 17:45:40.944 Started all channels in 11 milliseconds. 4049 milliseconds since JVM startup. Java 21.0.1+12-jvmci-23.1-b19

Once Helidon has started, it will create a couple of tables with some data:

Tables created in the Oracle DB

You can now properly building your microservice, which we’ll look at in a future post.

Summary

In this post, we look at using WireGuard, an opensource, lightweight VPN solution that is also included in Oracle Linux 9 to establish secure access to OCI services such as DBCS and OKE. We then look at how to enable some infrastructure configuration such as DNS in OCI to facilitate development. Finally, we looked at how developers can use the above set up to build their micro-services application in Helidon while keeping their database in OCI.

I would like to wrap up this article and thank my colleagues Avi Miller and Shaun Levey for their contributions to this article.

--

--