- A 64-bit Linux environment (AMD, Intel, or Arm).
- Basic familiarity with Kubernetes manifests and
kubectl
commands.
Ahoy, intrepid explorers of the digital ocean! 🏴☠️ Welcome to a journey where we navigate through the intricate waves of Authentication (AuthN) and Authorization (AuthZ) with Coastal Containers Ltd. In this adventurous lab, we'll delve into the depths of securing the vessel manifest system, ensuring that the treasures of data are safeguarded with the formidable technologies of Envoy Proxy and Open Policy Agent (OPA).
graph TD
subgraph "Vessel Manifest Server"
manifest-srv[Golang Manifest Server]
opa-agent[OPA Agent]
envoy-proxy[Envoy Proxy]
spire-agent[SPIRE Agent]
spire-agent <--> |SDS| envoy-proxy
opa-agent <--> |Request AuthZ| envoy-proxy
envoy-proxy --> |Traffic| manifest-srv
end
subgraph "Vessel Manifest Database"
postgres-db[PostgreSQL Database]
envoy-proxy-2[Envoy Proxy]
spire-agent-2[SPIRE Agent]
spire-agent-2 <--> |SDS| envoy-proxy-2
envoy-proxy-2 --> |Traffic| postgres-db
end
envoy-proxy <-->|mTLS| envoy-proxy-2
curl-test[Pilot Boats] <-->|curl request| envoy-proxy
In the charted course above, we visualize the integration of SPIRE, OPA, and Envoy to fortify the security of Coastal Containers Ltd's vessel manifest system. Let's set sail and explore how these technologies intertwine to create a secure harbor for our data:
- Golang Manifest Server: The heart of our journey, where the vessel manifest data is stored and managed.
- OPA Agent: Our vigilant lookout, ensuring that only authorized requests can access the manifest server by enforcing policies defined in Rego language.
- Envoy Proxy: The steadfast gatekeeper, managing and directing the traffic between the server and database, ensuring secure communication via mTLS.
- SPIRE Agent: The trusted shipwright, providing SPIFFE Verifiable Identity Documents (SVIDs) to establish trust between services in our system.
- Pilot Boat(s): Torchbearers and trusted navigators of Coastal Containers Fleet, pilot boats lead international freighters through intrepid waters, and will serve as our test case issuing curl requests to the vessel manifest server.
- PostgreSQL Database: The treasure trove where all the precious vessel manifest data is securely stored.
- Envoy Proxy: Another loyal gatekeeper, safeguarding the database and ensuring only secure, encrypted traffic can access the stored data.
- SPIRE Agent: Ensuring that the database service has a verifiable identity, providing SVIDs to establish mTLS with the server.
- SPIRE and Envoy: SPIRE Agent uses the Secret Discovery Service (SDS) to provide SVIDs to Envoy, establishing a secure identity and facilitating mTLS communication between the server and database.
- OPA and Envoy: OPA Agent intercepts requests to the server, consulting its policies to decide whether to allow access. It communicates with Envoy to enforce these authorization decisions, ensuring only legitimate requests can sail through to the manifest server.
- Envoy Proxies Communication: The two Envoy proxies manage the mTLS communication between the server and database, ensuring that data transmitted across the services is secure and encrypted.
With the integration of SPIRE, OPA, and Envoy, Coastal Containers Ltd embarks on a journey where the vessel manifest system is fortified with robust AuthN and AuthZ. SPIRE ensures that each service in the system can prove its identity, OPA ensures that requests are scrutinized against policy to ensure they are authorized, and Envoy ensures that all communication is securely encrypted with mTLS.
Join us in this lab as we navigate through the implementation and testing of this secure integration, ensuring that the treasures of vessel manifest data are shielded from the marauding pirates of the digital sea! 🏴☠️🌊🔐
To set sail, spin up the Kubernetes demo cluster using Kind by issuing the following make
command while in the root
lab directory
make cluster-up
make deploy-spire create-registration-entries
If this completed as-expected you should see the following registration entries have been created:
Entry ID : bace24d7-8204-4783-8990-099643ab90d4
SPIFFE ID : spiffe://coastal-containers.example/agent/spire-agent
Parent ID : spiffe://coastal-containers.example/spire/server
Revision : 0
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : k8s_psat:agent_ns:spire
Selector : k8s_psat:agent_sa:spire-agent
Selector : k8s_psat:cluster:kind-kind
Entry ID : b72f5d81-bf62-44fe-8cd9-590925635f71
SPIFFE ID : spiffe://coastal-containers.example/app/manifest/database
Parent ID : spiffe://coastal-containers.example/agent/spire-agent
Revision : 0
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : k8s:container-name:envoy
Selector : k8s:ns:default
Selector : k8s:pod-label:app:postgres
Selector : k8s:sa:default
Entry ID : d034cdb0-06ee-44be-ad74-660eac553283
SPIFFE ID : spiffe://coastal-containers.example/app/manifest/server
Parent ID : spiffe://coastal-containers.example/agent/spire-agent
Revision : 0
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : k8s:container-name:envoy
Selector : k8s:ns:default
Selector : k8s:pod-label:app:server
Selector : k8s:sa:default
Entry ID : a9352e9e-ce57-4369-bc5d-357d1d843fa7
SPIFFE ID : spiffe://coastal-containers.example/app/manifest/pilot-boat-0
Parent ID : spiffe://coastal-containers.example/agent/spire-agent
Revision : 0
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : k8s:container-name:envoy
Selector : k8s:ns:default
Selector : k8s:pod-label:app:pilot-boat-0
Selector : k8s:sa:default
Entry ID : f453be5b-3300-486b-a7be-7787aeec4cd5
SPIFFE ID : spiffe://coastal-containers.example/app/manifest/pilot-boat-1
Parent ID : spiffe://coastal-containers.example/agent/spire-agent
Revision : 0
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : k8s:container-name:envoy
Selector : k8s:ns:default
Selector : k8s:pod-label:app:pilot-boat-1
Selector : k8s:sa:default
Build and load the vessel manifest server image by running:
make cluster-build-load-image DIR=server
The vessel manifest server
is the north star of our digital ship, steering the management and access of the vessel
data within our system. It's crafted using Golang and serves as a pivotal component in our architecture, acting as a
liaison between the user requests and the stored data in the PostgreSQL database
.
Key Responsibilities:
-
API Endpoint Exposure:
- The server exposes various API endpoints that allow interaction with the vessel manifest data. These endpoints enable functionalities like fetching, creating, updating, and deleting vessel manifest records.
-
Data Management:
- It communicates with the PostgreSQL
database
to perform CRUD (Create, Read, Update, Delete) operations, ensuring that the data is accurately maintained and retrieved upon valid requests.
- It communicates with the PostgreSQL
-
Request Handling:
- The server processes incoming HTTP requests, extracts necessary information, and performs the required operation, whether it be fetching data from or making modifications to the database.
API Endpoints and Functionalities:
GET /api/vessels
: Retrieve a list of all vessel manifests.GET /api/vessels/{id}
: Retrieve the details of a specific vessel using its unique identifier.POST /api/vessels
: Create a new vessel manifest record.PUT /api/vessels/{id}
: Update the details of an existing vessel manifest.DELETE /api/vessels/{id}
: Remove a vessel manifest record from the database.
By executing the make cluster-build-load-image DIR=workload/server
command, we build the Docker image of our server
and load it into our kind cluster. This ensures that the server, with all its functionalities and integrations, is
packaged and ready to be deployed within our Kubernetes environment. If you would like to investigate the functionality
of the server workload, review the files contained within the server directory.
In the subsequent steps, we'll navigate through deploying this server and ensuring that it sails smoothly within our charted architecture map.
In order to supply the vessel manifest server
and database
with the required Envoy and OPA configuration, we will
need to pass these files as Kubernetes configmaps.
For the vessel manifest server
, we will need configmaps to store the envoy.yaml,
opa-config.yaml, and opa-policy.rego
files. Functionally, we will be creating two configmaps for this purpose, server-envoy
to hold the
envoy.yaml, and opa-policy
to hold the
opa-config.yaml / opa-policy.rego files.
For the vessel manifest database
, we will need a configmap to store the
envoy.yaml file. Functionally, this proxy configuration will be stored in the
database-envoy
configmap.
To begin, first create the server-envoy
configmap for the vessel manifest server:
kubectl create configmap server-envoy --from-file=server/envoy/envoy.yaml
Next, create the opa-policy
configmap for the vessel manifest server:
kubectl create configmap opa-policy --from-file=server/opa/opa-policy.rego --from-file=server/opa/opa-config.yaml
📝Note: The opa-policy
configmap is used to hold both the opa-config.yaml and
opa-policy.rego files, passing them into the server
via a mounted volume.
After this, create the database-envoy
configmap for the vessel manifest database
:
kubectl create configmap database-envoy --from-file=database/envoy/envoy.yaml
These ConfigMaps will be mounted into the respective pods, providing the necessary configurations for Envoy and OPA to
operate effectively within our system. With these configurations in place, we ensure secure, policy-enforced
communication between our server
and database
.
Before we move on, however, it's important to first understand what the Envoy and OPA configuration is actually doing.
Envoy Configuration:
Envoy Proxy acts as a mediator in the communication between the server
and database
, ensuring secure and controlled
traffic flow. The configuration files, namely envoy.yaml
, dictate how Envoy should manage the traffic, apply security
protocols, and interact with other services.
Some key components of these configuration files are:
- Listeners: Define how Envoy should manage incoming connections.
listeners:
- name: local_proxy
address:
socket_address:
address: 0.0.0.0
port_value: 8081
Here, a listener named local_proxy
is configured to listen to all incoming traffic on port 8081
.
- Clusters: specify the services to which Envoy should proxy the traffic.
clusters:
- name: database
connect_timeout: 0.25s
type: strict_dns
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: database
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: postgres
port_value: 9001
A cluster named database
is defined, which Envoy will use to discover the database
service.
- TLS Context: : Ensures mTLS communication between services, utilizing SVIDS (based on the registered SPIFFE IDS) for secure identity verification.
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
tls_certificate_sds_secret_configs:
- name: "spiffe://coastal-containers.example/app/manifest/server"
sds_config:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
envoy_grpc:
cluster_name: spire_agent
combined_validation_context:
default_validation_context:
match_typed_subject_alt_names:
- san_type: URI
matcher:
exact: "spiffe://coastal-containers.example/app/manifest/database"
validation_context_sds_secret_config:
name: "spiffe://coastal-containers.example"
sds_config:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
envoy_grpc:
cluster_name: spire_agent
This section configures the TLS settings to establish secure communication utilizing SPIRE for dynamic certificate
management. You can also notice the use of the SDS module within the spire_agent
cluster for SVID handling.
OPA Configuration:
The opa-config.yaml file defines how OPA should be configured within the system, particularly regarding its interaction with Envoy for policy enforcement.
Key components of this configuration include:
- Decision Logs: Specifies where OPA should log decision outputs.
decision_logs:
console: true
Here, decision logs are directed to the console for easy debugging and observation.
- Plugins: Configures the Envoy External Authorization gRPC server within OPA.
plugins:
envoy_ext_authz_grpc:
addr: :8182
query: data.envoy.authz.allow
OPA is configured to run the Envoy External Authorization gRPC server on port 8182
and to use the
data.envoy.authz.allow
rule for making authorization decisions.
OPA Policies:
The opa-policy.rego file contains the actual policy logic written in Rego language, which OPA will enforce.
Key components of this configuration include:
- Default Deny: Establishes that all requests are denied by default.
default allow := false
Allow Rule: Defines the conditions under which a request should be allowed.
allow {
http_request.method == "GET"
source_svid == "spiffe://coastal-containers.example/app/manifest/pilot-boat-0"
}
This rule allows requests that use the GET
request method and originate from a service with the SPIFFE ID
spiffe://coastal-containers.example/app/manifest/pilot-boat-0
(i.e. pilot-boat-0
).
To create a registration entry and deploy the postgres vessel manifest database, run:
make deploy-database
If the database
is up and running, you should see:
Database workload deployed on the cluster.
This can also be verified by running:
kubectl get pods
The output of this command should show the running postgres
pod, like shown here:
NAME READY STATUS RESTARTS AGE
postgres-67769c7cb6-b8pm5 2/2 Running 0 172m
Create a registration entry and deploy the vessel manifest server by running:
make deploy-server
If the server
is up and running, you should see:
Server workload deployed on the cluster.
This can also be verified by running:
kubectl get pods
The output of this command should show the running server
and postgres
pod, like shown here:
NAME READY STATUS RESTARTS AGE
postgres-67769c7cb6-gkvdz 2/2 Running 0 50s
server-6b5f6c6d69-66v4x 3/3 Running 2 (16s ago) 21s
Once this step complete, the vessel manifest server and database should be running with issued SVIDs:
spiffe://coastal-containers.example/app/manifest/server
for the server.spiffe://coastal-containers.example/app/manifest/database
for the database.
With your demo architecture running and registered with SPIRE, we can test the OPA policies using curl
containers
represented as 'pilot boat' workloads attempting to signal freighters by sending them GET
requests. Per Coastal
Containers use-case, we must answer a few questions in order to prove the AuthZ policies are working as-expected.
- Can we issue a
GET
request to the vessel manifestserver
frompilot-boat-0
? - Can we issue a
GET
request to the vessel manifestserver
frompilot-boat-1
?
To answer these questions, let's deploy both workloads.
First, we need to deploy their associated envoy configmaps:
kubectl create configmap pilot-boat-0-envoy --from-file=pilot-boat-0/envoy/envoy.yaml
kubectl create configmap pilot-boat-1-envoy --from-file=pilot-boat-1/envoy/envoy.yaml
Next, we can create the workloads by running:
make deploy-pilot-boat-0
make deploy-pilot-boat-1
Issue a GET
request from pilot-boat-0
to the server with:
kubectl exec -it pilot-boat-0 -c curl -- curl -v -H "Accept: application/json" server/api/vessels
If it worked properly, you should see an output similar to this:
* Trying 10.96.39.5:80...
* Connected to server (10.96.39.5) port 80
> GET /api/vessels HTTP/1.1
> Host: server
> User-Agent: curl/8.4.0
> Accept: application/json
>
< HTTP/1.1 200 OK
< date: Thu, 30 Nov 2023 11:49:15 GMT
< content-length: 406
< content-type: text/plain; charset=utf-8
< x-envoy-upstream-service-time: 10
< server: envoy
<
[{"ID":1,"CreatedAt":"2023-11-30T11:48:54.280982Z","UpdatedAt":"2023-11-30T11:48:54.280982Z","DeletedAt":null,"Name":"Maritime Mover"},{"ID":2,"CreatedAt":"2023-11-30T11:48:54.297235Z","UpdatedAt":"2023-11-30T11:48:54.297235Z","DeletedAt":null,"Name":"Tidal Transporter"},{"ID":3,"CreatedAt":"2023-11-30T11:48:54.306254Z","UpdatedAt":"2023-11-30T11:48:54.306254Z","DeletedAt":null,"Name":"Cargo Clipper"}]
* Connection #0 to host server left intact
This output shows the list of created vessels that are owned and operated by Coastal Containers Ltd, along with various
operations performed on them (e.g. CreatedAt
& UpdatedAt
). Once the curl
command completes, the exec function will
exit automatically.
You can view the opa decision log by executing the following command
kubectl logs -l=app=server -c opa | tail -1 | jq
If you cross reference the opa-policy.rego, you can see GET
request from
pilot-boat-0
should be allowed, and this can be seen in the decision log.
{
"input": {
"attributes": {
"request": {
"http": {
"method": "GET"
}
},
"source": {
"principal": "spiffe://coastal-containers.example/app/manifest/pilot-boat-0"
}
}
},
"result": true
}
After this, run the following command to test if we can issue a GET
request from pilot-boat-1
:
kubectl exec -it pilot-boat-1 -c curl -- curl -v -H "Accept: application/json" server/api/vessels
You should see the expected output:
* Trying 10.96.39.5:80...
* Connected to server (10.96.39.5) port 80
> GET /api/vessels HTTP/1.1
> Host: server
> User-Agent: curl/8.4.0
> Accept: application/json
>
< HTTP/1.1 403 Forbidden
< date: Thu, 30 Nov 2023 11:51:11 GMT
< server: envoy
< content-length: 0
< x-envoy-upstream-service-time: 9
<
* Connection #0 to host server left intact
This output indicates that the curl GET
request was unsuccessful due to the policy defined in our
opa-policy.rego file. The resulting AuthZ enforcement decision occurs due to the
fact that our rego policy will ONLY allow requests from the
spiffe://coastal-containers.example/app/manifest/pilot-boat-0
SPIFFE ID registered to pilot-boat-0
. As the GET
request was unsuccessful, the curl
container gracefully terminated and deleted after
attempting this operation.
You can verify this by checking the opa decision log by executing the following command
kubectl logs -l=app=server -c opa | tail -1 | jq
The following truncated output shows that a GET
request from pilot-boat-1
is rejected.
{
"input": {
"attributes": {
"request": {
"http": {
"method": "GET"
}
},
"source": {
"principal": "spiffe://coastal-containers.example/app/manifest/pilot-boat-1"
}
}
},
"result": false
}
Your journey doesn’t end here, however! The sea of knowledge is vast and there’s always more to explore. For further learning on OPA and AuthZ, navigate through the envoy-jwt-opa & envoy-opa demos in the spire-tutorials GitHub repository. This repo provides additional examples and scenarios for deploying SPIRE and integrating it peripheral tooling.
Additionally, you can try expanding on this demo by setting up more client workloads which send various CRUD requests
(e.g. GET
, PUT
, and DELETE
) to the vessel manifest server
. Issue a registration entry to create their SVIDs,
making sure to add them to an allow
rule within the opa-policy.rego file. Once
this is done, try testing your client's capabilities to issue various requests to the vessel manifest server
. Can you
get it working, and are you able to issue requests from invalid SVIDs that aren't within the scope of the defined Rego
policy?
To tear down the Kind cluster, run:
make cluster-down
Bravo, Navigator! 🚢 You've successfully sailed through the intrepid waters of AuthZ by integrating SPIRE with Open Policy Agent (OPA) and Envoy, thereby enhancing the security posture of Coastal Containers vessel manifest system with policy enforcement.
Throughout the course of this hand-on exercise, you've learned and practically implemented the following:
-
SPIRE for Workload Identity: You've explored how SPIRE provides a robust identity framework, assigning SPIFFE IDs
-
to workloads and ensuring secure, identity-verified communication through mTLS.
-
OPA for Policy Enforcement: You've witnessed the power of OPA in enforcing fine-grained, dynamic access control
-
policies, ensuring that only authorized entities can access specific resources.
-
Envoy for Traffic Control: You've utilized Envoy Proxy to manage and control the traffic between the vessel
-
manifest server and database, ensuring that all communications are secure and policy-compliant.
-
Integration Magic: You've integrated SPIRE, OPA, and Envoy, creating a secure, policy-enforced communication
-
channel between the server and database, ensuring that only authorized, authenticated requests are allowed.
Your journey doesn’t end here, however! The sea of knowledge is vast and there’s always more to explore. For further learning and to explore more examples, navigate through the SPIRE with Envoy and OPA tutorial on GitHub. This repository provides additional examples and scenarios where SPIRE, Envoy, and OPA are utilized to secure communication and enforce policies in for a sample frontend and backend app deployment.
May your future voyages in the vast ocean of cloud-native technologies be equally successful and enlightening! 🌊⚓️📘