My solution for Medilync was to create a secure infrastructure that supports three distinct interaction types with the company's connected environment:
-
Public Visitors (Patients):
Can access the public static website over HTTPS. They have no access to the/loginor/dashboardendpoints and do not possess credentials. This ensures clear separation between the informational frontend and any data-handling backends. -
Medical Professionals (Doctors, Nurses, Lab Techs):
Access the internal web application via OpenVPN only. Once connected, they can reach/loginand/dashboard, where the Flask web app authenticates them and grants role-based access to patient records. All connections are encrypted in transit, and authentication is enforced using TLS certificates issued by an internal intermediate CA. -
Developers & System Administrators:
Also connect via OpenVPN, but only receive access to developer and staging environments. Their roles are scoped tightly—developers cannot access patient records, and sysadmins connect via a jump-box to manage and monitor systems without touching sensitive medical data.
The infrastructure is segmented into three subnets for strict traffic separation, aligning with Zero Trust principles.
Figure 1: Medilync topology blueprint.
- Runs
openvpn - NAT and port forwarding configured via
sysctl.confandufwby editing /etc/ufw/before.rules - Certificates issued by internal CA (
step-ca) with full trust chain - Only entry point into the internal private network
- DNS:
vpn.mrcrlr.com
# etc/ufw/before.rules
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.8.0.0/24 -o <INTERFACE> -j MASQUERADE
COMMIT# VPN partial core config server-side
port 1194 # OpenVPN server port
proto udp # Protocol: use UDP (preferred for VPNs)
dev tun # Use TUN device (layer 3 routing)
ca ca-chain.crt # Full certificate chain (root + intermediate)
cert vpn.crt # Server certificate
key vpn.key # Server private key
dh none # No DH params (using ECDH with tls-groups)
tls-groups X25519:prime256v1 # ECDH curves to prefer (modern and fast)
tls-auth ta.key 0 # TLS w shared key (HMAC, 0 = server side)
tls-version-min 1.2 # Enforce minimum TLS version
cipher AES-256-GCM # Preferred cipher (AEAD)
auth SHA256 # HMAC digest (used with cipher for AEAD)- Runs
nginxas HTTPS reverse proxy, certs provided by Let's Encrypt - Public Access: Serves static site
- Internal Access: Forwards VPN-connected requests to
/loginand/dashboardonly - DNS:
medilync.mrcrlr.com
# /etc/nginx/sites-available/medilync
# Configuration snippet
upstream flask_backend {
server app1.medilync.local:443;
server app2.medilync.local:443;
server app3.medilync.local:443;
}- Routes outbound internet traffic for internal systems using
nftables - Strictly enforces no inbound connections via both security groups and
nftables.conf
# /etc/nftables.conf
# Partial config for outbound routing with NAT
table inet nat {
chain prerouting {
type nat hook prerouting priority filter; policy accept;
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr { 10.10.20.0/24, 10.10.30.0/24 } oifname "ens5" masquerade
}
}- Collects logs from all VMs via
rsyslogover RELP+TLS - Used for internal system access (via
ProxyJump) - All ssh keys managed by 1password
- Auditable access point for sysadmins
# /etc/rsyslog.d/60-relp-server.conf
module(load="imrelp") # Load RELP input module
# Template to write logs into per-host files
# --- additional configs here --- #
# Ruleset to apply log writing
# --- additional configs here --- #
# RELP input with TLS
input(
type="imrelp"
port="6514"
tls="on"
tls.caCert="/etc/rsyslog-tls/ca-cert.pem"
tls.myCert="/etc/rsyslog-tls/rsyslog-jump.crt"
tls.myPrivKey="/etc/rsyslog-tls/rsyslog-jump.key"
tls.authmode="name"
tls.permittedpeer="*.medilync.local"
ruleset="remote-logs"
)- Developer environment
- Pull requests only from GitHub "staging" branch via fine-grained access token
- Runs
Flaskviagunicorn+systemdundermedilync.service Nginxserves flask-app (HTTPS only) on port 5000- Can connect to the database with restricted test credentials
- Production web app instances
- Automated pull requests from GitHub repo "main" branch using a
systemddeploy timer with access control tightly scoped by FGAT – read-only permissions - Each deploy installs
requirements.txtand restartsFlaskapp - runs viagunicorn Nginxserves web app (HTTPS only) on port 5000, internal access only
- Contains two tables:
Patients— Sensitive medical dataAccess_Audit— Tracks and governs access levels, passwordsscrypthashed
- Access controlled by web app logic using RBAC rules
- 5 tiers of access rights (e.g., Patients: health, admin, dev, breakglass. Access_Audit: flask-app) govern table visibility
- Enforced encryption at rest via underlying VM volume configuration
| Control Type | Implementation |
|---|---|
| Segmentation | Three isolated subnets with SGs enforcing one-way flows |
| Encrypted Transport | OpenVPN + HTTPS + RELP over TLS |
| Encrypted Storage | All EBS volumes encrypted by platform-managed keys |
| IAM & Role Separation | VPN user roles mapped to different scopes in app + infra (not yet implemented) |
| Zero Trust Access | No direct public access to internal resources; VPN-only admin access |
| Logging & Monitoring | Centralized, TLS-encrypted logging via rsyslog RELP, stored securely on jump box |
The following table summarizes the IAM roles, their access paths, scopes, permissions, and the security principles applied in the Medilync cloud infrastructure. This design follows RBAC (Role-Based Access Control), ABAC (Attribute-Based Access Control via tags), Least Privilege, and enforces MFA for all privileged roles and VPN access.
| Role | Access Path & Resources | Access Level | Justification (Least Privilege) | IAM Management Approach |
|---|---|---|---|---|
| Admin | SSH to all VMs and Web Portals (staging and prod) all VPN only, all AWS resources, KMS, PKI and root CA, logs, IAM | Full (infra and app, but no personal data access in database) | Needs to manage all infrastructure, users, and security. | IAM group, full policy, MFA, ABAC tags |
| Developer | SSH to Golden box and Web portal (staging and prod) all via VPN only, Github repo | SSH, tightly scoped database access for testing | Needs to develop/test code, no access to production database or prod VMs. | IAM group, tag-based ABAC, scoped policy, MFA |
| Healthcare | Web portal via VPN only | Full patient access but time-limited sessions, tightly monitored | Needs to access patient data, no infrastructure access or dev resources. | IAM group, tag-based ABAC, MFA |
| Breakglass | All VMs (via VPN + jump box), AWS resources, KMS, logs, direct database access | Full (infra, app, and direct DB) | Emergency-only: used for critical incidents when normal access is insufficient. Highly monitored, requires justification, and is time-limited. | IAM group, breakglass policy, strict logging, MFA, ABAC tags, approval workflow |
| Finance | Web portal via VPN only | Read-only. However accounting data could be pulled to a separate database for better separation of access controls. (e.g., S3 Bucket with services such as Glue and Athena) | Needs to view data necessary for billing, no infrastructure or patient data access. | IAM group, MFA, read-only policy, ABAC tags |
| Auditor | SSH to Jump box only (with VPN) | Read-only. Although, here again, we could push logs to a separate DB for better access control. | Must review logs and audit company activity for compliance, no write access. | IAM group, MFA, read-only policy, ABAC tags |
- RBAC: All users are assigned to groups that grant only the permissions required for their job function, enforced both in AWS IAM, unique issued VPN cert, and in the web application throu the access_audit table in the database.
- ABAC: All AWS resources should be tagged (e.g.,
environment=prod,role=healthcare), and IAM policies use these tags to restrict access. For example, developers can only access resources taggedenvironment=dev. - Least Privilege: Permissions are scoped as narrowly as possible. Healthcare staff can only access patient data via the web app, no access to the database or servers directly. Developers cannot access production data or VMs.
- MFA: Multi-factor authentication is enforced for all privileged roles (Admin, Healthcare) and for VPN access.
The Breakglass role is reserved for emergency situations where standard roles (Admin, Healthcare, etc.) are insufficient to resolve a critical incident (e.g., database corruption, ransomware, or urgent patient data recovery). This role grants full access to all infrastructure, including direct database access, and is protected by the following controls:
- MFA and Approval: Access requires multi-factor authentication and explicit approval (e.g., via ticket or manager sign-off).
- Strict Logging: All actions are logged and monitored in real time. Alerts are generated for any use of this role.
- Time-limited: Access is granted only for the minimum time necessary and is immediately revoked after the incident.
- Justification Required: Users must provide a business justification for breakglass access, which is reviewed post-incident.
This ensures that the principle of least privilege is maintained under normal operations, while still allowing for rapid response in emergencies.
Resource map:
Subnets:
Routes:
Virtual machines:
Security groups:

Figure 2: Connecting to VPN from local machine.
Figure 3: SSH into VPN-gateway.
Figure 4: SSH into Load Balancer.
Figure 5: SSH into NAT-Gateway.
Figure 6: SSH into Golden Server (Dev/Staging Environment).
Figure 7, 8, 9: SSH into App Server 1, 2, and 3 (Production).
Figure 10: Demonstrating role-based access control in web application.
Figure 11: All databse traffic is fully encrypted and authenticated via internal CA signed certs.
Figure 12: All logs encrypted over rsyslog RELP over TLS.
Figure 13: SSH into Databse Server.
Figure 14: SSH into all machines through VPN tunnel with ProxyJump. SSH keys managed by 1Password