Nyimbi Odero

Series 01 · a numbered run · kept in public

Lesson 12: Security Best Practices for Distributed Systems

Welcome back! In this lesson, we will focus on security best practices for distributed systems, especially those that involve complex workflows and multiple services. Securing workflows, managing access control, and protecting sensitive data are critical concerns for ERP/CRM platforms or multi-sided marketplaces. We will explore authentication, authorization, secure communication, and data protection strategies.

Table of Contents

  1. Introduction to Security in Distributed Systems

  2. Implementing Authentication and Authorization

  3. Securing Workflows with Role-Based Access Control (RBAC)

  4. Protecting Sensitive Data: Encryption and Data Masking

  5. Secure Communication: TLS and API Gateway Security

  6. Practical Example: Securing an Order Processing Workflow

  7. Conclusion and Next Steps

1. Introduction to Security in Distributed Systems

Why Security is Critical in Distributed Systems

Distributed systems often involve multiple services, databases, and communication channels. These services need to interact securely, especially when sensitive data like customer information, financial transactions, or personal data is involved. Failing to implement proper security measures can expose your system to attacks, data breaches, and unauthorized access.

Key Security Concepts:

  1. Authentication: Verifying the identity of a user or system.

  2. Authorization: Determining what actions or resources a user or system can access.

  3. Encryption: Protecting data in transit and at rest to prevent unauthorized access.

  4. Secure Communication: Ensuring that communication between services is secure (e.g., using TLS).

  5. Data Privacy: Protecting sensitive data by anonymizing or masking it when necessary.

2. Implementing Authentication and Authorization

Authentication Strategies

In a distributed system, different services may require different levels of authentication. A common approach is to use OAuth 2.0 or OpenID Connect (OIDC) for authenticating users and services.

Step 1: Implementing OAuth 2.0 Authentication in Flask

Install Flask-OAuthlib to add OAuth 2.0 support to your Flask app:

pip install Flask-OAuthlib

Here’s how to configure OAuth 2.0 in Flask:

from flask import Flask, jsonify

from flask_oauthlib.provider import OAuth2Provider

app = Flask(name)

oauth = OAuth2Provider(app)

Define an example user database

users = {

“john_doe”: {

“password”: “password123”,

“roles”:

"Manager","Admin""Manager", "Admin"

}

}

@app.route(‘/api/protected’)

@oauth.require_oauth(‘email’)

def protected_resource():

return jsonify(message=“This is a protected resource”)

Initialize the app with OAuth configurations

app.config

′OAUTH2CLIENTID′'OAUTH2_CLIENT_ID'

= ‘your-client-id’

app.config

′OAUTH2CLIENTSECRET′'OAUTH2_CLIENT_SECRET'

= ‘your-client-secret’

if name == ‘main’:

app.run(debug=True)

Step 2: Implementing OpenID Connect

For more advanced authentication and identity management, OpenID Connect (OIDC) provides an additional layer of security on top of OAuth 2.0 by enabling identity verification.

Install Flask-OIDC:

pip install Flask-OIDC

Configure OpenID Connect in your Flask app:

from flask_oidc import OpenIDConnect

app = Flask(name)

oidc = OpenIDConnect(app)

@app.route(‘/dashboard’)

@oidc.require_login

def dashboard():

return “Welcome to your dashboard!”

if name == ‘main’:

app.run(debug=True)

OIDC ensures that user identities are verified using secure tokens, allowing you to manage users and permissions across services.

3. Securing Workflows with Role-Based Access Control (RBAC)

What is Role-Based Access Control (RBAC)?

RBAC is a security model where users are assigned roles, and each role has a set of permissions that define what actions the user can perform. For example, a Manager role might have the ability to approve orders, while a Customer role may only be able to view their order status.

Step 1: Defining Roles and Permissions in the DSL

We can extend the DSL to define roles and the permissions associated with each role for securing workflows.

DSL Syntax for RBAC:

workflow OrderProcessing {

state Pending {

transition approve -> Approved by Manager;

transition reject -> Rejected by Manager;

}

state Approved {

transition fulfill -> Fulfilled by FulfillmentSystem;

role Manager can approve, reject;

role FulfillmentSystem can fulfill;

}

state Shipped {

// End of workflow

}

}

Step 2: Generating Role-Based Access Control Code

The DSL can generate code that enforces RBAC by checking the user’s role before allowing access to a specific action or transition.

Example: RBAC for Order Processing:

def check_role(user_role, required_role):

if user_role not in required_role:

raise PermissionError(f”User role {user_role} does not have permission to perform this action”)

@app.route(‘/order/int:order_id/approve’, methods=

′POST′'POST'

)

def approve_order(order_id):

user_role = get_user_role() # Fetch the user’s role (e.g., Manager)

check_role(user_role,

"Manager""Manager"

)

Proceed with order approval

order = Order.query.get(order_id)

order.state = “Approved”

db.session.commit()

return jsonify({‘status’: ‘success’, ‘new_state’: ‘Approved’})

In this example, only users with the Manager role can approve or reject orders.

4. Protecting Sensitive Data: Encryption and Data Masking

Data Encryption

Sensitive data must be encrypted both in transit and at rest to prevent unauthorized access. In transit encryption ensures that data sent over the network is protected, while at rest encryption ensures that stored data is protected from unauthorized access.

Step 1: Encrypting Data in Transit Using TLS

To secure communication between services, you should use Transport Layer Security (TLS). Flask can be configured to use TLS for secure communication:

Generate self-signed certificate and key

openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -keyout server.key -out server.crt

Use the certificate in your Flask app:

if name == ‘main’:

app.run(ssl_context=(‘server.crt’, ‘server.key’))

Step 2: Encrypting Data at Rest Using SQLAlchemy

For encrypting sensitive fields in the database, such as passwords or credit card numbers, use libraries like Fernet from the cryptography package:

pip install cryptography

Here’s how to encrypt data at rest using SQLAlchemy:

from cryptography.fernet import Fernet

from sqlalchemy import Column, String

key = Fernet.generate_key()

cipher = Fernet(key)

class User(db.Model):

id = Column(Integer, primary_key=True)

encrypted_email = Column(String)

def set_email(self, email):

self.encrypted_email = cipher.encrypt(email.encode()).decode()

def get_email(self):

return cipher.decrypt(self.encrypted_email.encode()).decode()

Encrypt email before storing in the database

user = User()

user.set_email(“user@example.com”)

db.session.add(user)

db.session.commit()

Data Masking for Privacy Protection

Data masking hides or obfuscates sensitive data (e.g., masking all but the last four digits of a credit card number). This technique is useful when showing data to users who don’t need full access to the sensitive information.

5. Secure Communication: TLS and API Gateway Security

Transport Layer Security (TLS)

We’ve already seen how to enable TLS in Flask. In a distributed system, it’s important to ensure that all services communicate securely via TLS. If multiple services communicate over REST APIs, all traffic should be encrypted.

API Gateway for Managing Security

An API Gateway sits between clients and your services, handling requests, enforcing security policies, rate limiting, and logging. Using an API gateway such as Kong, NGINX, or AWS API Gateway can help centralize security concerns, including:

  • Rate Limiting: Preventing abuse by limiting the number of requests a client can make.

  • Authentication/Authorization: Enforcing OAuth 2.0 or JWT token validation.

  • Encryption: Ensuring that all traffic passes through TLS.

  • Throttling: Managing traffic spikes by limiting requests per second or minute.

Example: API Gateway Security in Kong

  1. Install Kong using Docker:

docker run -d —name kong —network=kong-net -e “KONG_DATABASE=off” -e “KONG_PROXY_ACCESS_LOG=/dev/stdout” -e “KONG_ADMIN_ACCESS_LOG=/dev/stdout” -e “KONG_PROXY_ERROR_LOG=/dev/stderr” -e “KONG_ADMIN_ERROR_LOG=/dev/stderr” -e “KONG_ADMIN_LISTEN=0.0.0.0:8001” -p 8000:8000 -p 8443:8443 -p 8001:8001 kong

  1. Create a service and route in Kong to protect your APIs:

curl -i -X POST http://localhost:8001/services/ \

—data “name=order-service” \

—data “url=http://localhost:

5000”

curl -i -X POST http://localhost:8001/services/order-service/routes \

—data “hosts[]=api.mydomain.com”

  1. Enable authentication and rate limiting plugins:

curl -X POST http://localhost:8001/services/order-service/plugins \

—data “name=rate-limiting” \

—data “config.minute=5”

curl -X POST http://localhost:8001/services/order-service/plugins \

—data “name=key-auth”

Now, Kong will enforce rate limiting and require an API key for accessing your services.

6. Practical Example: Securing an Order Processing Workflow

Let’s implement a secure order processing system using the strategies we’ve discussed: authentication, RBAC, encryption, and API gateway security.

DSL for Secure Order Workflow:

workflow OrderProcessing {

state Pending {

transition approve -> Approved by Manager;

transition reject -> Rejected by Manager;

}

state Approved {

transition fulfill -> Fulfilled by FulfillmentSystem;

role Manager can approve, reject;

role FulfillmentSystem can fulfill;

}

state Shipped {

// End of workflow

}

}

Step 1: Implementing Authentication and RBAC:

@app.route(‘/order/int:order_id/approve’, methods=

′POST′'POST'

)

@oidc.require_login

def approve_order(order_id):

user_role = get_user_role() # Get the user’s role via OAuth 2.0

check_role(user_role,

"Manager""Manager"

) # Ensure the user has the Manager role

order = Order.query.get(order_id)

order.state = “Approved”

db.session.commit()

return jsonify({‘status’: ‘success’, ‘new_state’: ‘Approved’})

Step 2: Encrypting Sensitive Data:

class Order(db.Model):

id = Column(Integer, primary_key=True)

encrypted_customer_email = Column(String)

def set_customer_email(self, email):

self.encrypted_customer_email = cipher.encrypt(email.encode()).decode()

def get_customer_email(self):

return cipher.decrypt(self.encrypted_customer_email.encode()).decode()

Step 3: Protecting APIs with an API Gateway:

Use Kong to enforce rate limiting, API key authentication, and secure communication via TLS for all API requests.

Enable rate limiting and authentication in Kong

curl -X POST http://localhost:8001/services/order-service/plugins \

—data “name=rate-limiting” \

—data “config.minute=5”

curl -X POST http://localhost:8001/services/order-service/plugins \

—data “name=key-auth”

7. Conclusion and Next Steps

In this lesson, we’ve:

  • Implemented authentication using OAuth 2.0 and OpenID Connect.

  • Enforced role-based access control (RBAC) for secure workflows.

  • Protected sensitive data with encryption and data masking.

  • Secured communication between services using TLS and API gateways.

These security best practices ensure that your system remains secure, even in the face of sophisticated attacks or unauthorized access attempts.

Practical Exercise

Task: Extend the DSL to define data privacy rules, where certain data fields are automatically encrypted, and only users with the necessary roles can view them. Implement this in the generated code.

Hints:

  • Use SQLAlchemy to handle encryption of sensitive fields (e.g., customer emails or payment details).

  • Modify RBAC rules to grant access to sensitive data based on the user’s role.

Looking Ahead

In the next lesson, we’ll focus on performance optimization and scalability in distributed systems. We’ll explore how to design scalable architectures, optimize workflows, and handle increased loads efficiently.

Feel free to reach out if you have any questions. See you in the next lesson!