This project demonstrates how to integrate several security mechanisms and technologies to tackle common security challenges such as authentication, authorization, and secure communication.
Distributed systems involve multiple services or microservices that must communicate securely across a network. Key security challenges in such architectures include:
- Secure Communication: Ensuring that data transmitted between services is encrypted and safe from eavesdropping or tampering.
- Authentication: Verifying the identities of users and services to prevent unauthorized access.
- Authorization: Controlling what authenticated users and services are allowed to do, based on their roles and permissions.
- Policy Enforcement: Implementing fine-grained access controls and policies that govern service-to-service and user interactions.
- Certificate Management: Managing digital certificates for encrypting data and establishing trust between services.
./provisioning.sh
docker-compose up
Authentication is done using Keycloak. The Keycloak clients are defined in the realm file inside keycloak/
.
Two clients are defined:
appTest-login-client
- This client is used to authenticate users using OpenID Connectclient_credentials-test
- This client is used to authenticate services using client credentials
Keycloak Console URL: http://localhost:9000 - credential: admin/password
- Go to Login page
- After login, the user will be redirected to a callback page with an authorization code which can be used to retrieve a Jwt token.
- See the
authn.js
file in thenginx/njs
directory to learn more.
We created a client credentials client in the keycloak to demonstrate how to authenticate using client credentials. This type of client is used to authenticate services.
Create a token using client credentials to authenticate services.
curl -X POST "http://localhost:9000/realms/tenantA/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=client_credentials-test" \
-d "client_secret=your-client-secret-here"
We offer two types of authorization:
- Service-to-service authorization
- User access control
Service-to-service authorization is performed in two levels:
- Service-to-service communication using mutual TLS with Nginx
- Service access control using Open Policy Agent(OPA)
User access control is done using JWT tokens. We use three strategies to validate JWT tokens:
- Retrieve the certs from the Keycloak server and validate the token
- Use x5t(Thumbprint) embedded in the token to retrieve the public key from a local truststore and validate the token
- Using embedded certificate to validate the token after validating the certificate against a CA
See the nginx/njs/token.js
The OPA policies are defined in the opa/
directory.
OPA is used to enforce policies for service-to-service communication and for user access control.
The project uses shell script to simulate the certificate authority and generate certificates for services.
The certificates are generated using the ./provisioning.sh
script. The certificates are generated in the certificates/gen
directory.
To test the service to service communication using certificates, you can use the test_services.sh
script.
Server certificates can be updated without restarting the service by running the following command:
curl --insecure https://localhost/certs --cacert certificates/gen/ca.crt --cert certificates/gen/serviceB/client.crt --key certificates/gen/serviceB/client.key -F cert=@certificates/gen/serviceA/client.crt -F key=@certificates/gen/serviceA/client.key