Skip to main content

Just-in-time PostgreSQL access

Overview

This tutorial will deploy an example cluster to highlight Otterize's PostgreSQL capabilities. Within that cluster is a client service that hits an endpoint on a server, which then connects to a database. The server runs two different database operations:

  1. An INSERT operation to append a table within the database
  2. A SELECT operation to validate the updates.

The server needs appropriate permissions to access the database. You could use one admin user for all services, which is insecure and is the cause for many security breaches. With Otterize, you can specify required access, and have Otterize create users and perform correctly scoped SQL GRANTs just in time, as the service spins up and down.

In this tutorial, we will:

  • Deploy an example cluster
  • Deploy Otterize in our cluster and give it access to our database instance
  • Declare a ClientIntents resource for the server, specifying required access
  • See that the required access has been granted

Prerequisites

1. Minikube Cluster

Prepare a Kubernetes cluster with Minikube

For this tutorial you'll need a local Kubernetes cluster. Having a cluster with a CNI that supports NetworkPolicies isn't required for this tutorial, but is recommended so that your cluster works with other tutorials.

If you don't have the Minikube CLI, first install it.

Then start your Minikube cluster with Calico, in order to enforce network policies.

minikube start --cpus=4 --memory 4096 --disk-size 32g --cni=calico

2. Deploy Otterize

To deploy Otterize, head over to Otterize Cloud and associate a Kubernetes cluster on the Integrations page, and follow the instructions. If you already have a Kubernetes cluster connected, skip this step.

Tutorial

Deploy tutorial services and request database credentials

This will set up the namespace we will use for our tutorial and deploy the client, server, and database.

Our server's Deployment spec will specify an annotation on the Pod, which requests that the credentials operator will provision a username and password for the server.

  template:
metadata:
annotations:
credentials-operator.otterize.com/user-password-secret-name: server-creds

This specifies that the secret server-creds will have keys with the username and password to connect to the database. The secret will only be created once the database is integrated with Otterize Cloud.

kubectl create namespace otterize-tutorial-postgres
kubectl apply -n otterize-tutorial-postgres -f https://docs.otterize.com/code-examples/postgres/client-server-database.yaml

Deploy a PostgreSQLServerConfig to allow Otterize DB access

apiVersion: k8s.otterize.com/v1alpha3
kind: PostgreSQLServerConfig
metadata:
name: postgres-tutorial-db
spec:
address: database.otterize-tutorial-postgres.svc.cluster.local:5432
credentials:
username: otterize-tutorial
password: jeffdog523

The above CRD tells Otterize how to access a database instance named postgres-tutorial-db, meaning that when intents are applied requesting access permissions to postgres-tutorial-db, Otterize operators will be able to configure them.

In this tutorial, the database workload already comes with the predefined username & password, but for future uses a role will have to be created in the database to grant Otterize access as well as the ability to configure other users.

caution

The type PostgreSQLServerConfig should be considered as sensitive and require high cluster privileges to access.

Let's apply the above PostgreSQLServerConfig so Otterize will know how to access our database instance.

kubectl apply -f pgserverconf.yaml

View logs for the server

After the client, server, and database are up and running, we can see that the server does not have the appropriate access to the database by inspecting the logs with the following command.

kubectl logs -f -n otterize-tutorial-postgres deploy/server

Example log:

Unable to perform INSERT operation
Unable to perform SELECT operation

Define your ClientIntents

ClientIntents are Otterize’s way of defining access through unique relationships, which lead to perfectly scoped access. In this example, we provide our server workload the ability to insert and select records to allow it to access the database.

Below is our intents.yaml file. As you can see, it is scoped to our database named otterize-tutorial and our public.example table. We also have limited the access to just SELECT and INSERT operations. We could add more databases, tables, or operations if our service required more access.

Specifying the table and operations is optional. If you don't specify the table, access will be granted to all tables in the specified database. If you don't specify the operations, all operations will be allowed.

apiVersion: k8s.otterize.com/v1alpha3
kind: ClientIntents
metadata:
name: client-intents-for-server
namespace: otterize-tutorial-postgres
spec:
service:
name: server
calls:
- name: postgres-tutorial-db # Same name as our PostgresSQLServerConfig metadata.name
type: database
databaseResources:
- databaseName: otterize-tutorial
table: public.example
operations:
- SELECT
- INSERT

We can now apply our intents. Behind the scenes,the Otterize credentials-operator created the user for our server workload while the intents-operator ran GRANT queries on the database, making our SELECT and INSERT errors disappear.

kubectl apply -f intents.yaml

Example log:

Successfully INSERTED into our table

Successfully SELECTED, most recent value: 2024-04-30T13:20:46Z

That’s it! If your service’s functionality changes, adding or removing access is as simple as updating your ClientIntents definitions. For fun, try altering the operations to just SELECT or INSERT.

Teardown

To remove the deployed examples, run:

kubectl delete clientintents.k8s.otterize.com -n otterize-tutorial-postgres client-intents-for-server && \
kubectl delete namespace otterize-tutorial-postgres