diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 06bf0b2..1bd4305 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -553,6 +553,7 @@ $healthplixPermFilter = 'permission:admin.access,admin.settings'; $routes->post('HealthplixGateway/generate_token', 'HealthplixGateway::generateToken', ['filter' => $healthplixPermFilter]); $routes->post('HealthplixGateway/register_patient', 'HealthplixGateway::registerPatient', ['filter' => $healthplixPermFilter]); $routes->post('HealthplixGateway/book_appointment', 'HealthplixGateway::bookAppointment', ['filter' => $healthplixPermFilter]); +$routes->post('healthplix/fetch', 'HealthplixCallback::fetch'); $routes->get('Opd_prescription/fhir_bundle/(:num)', 'Opd_prescription::fhir_bundle/$1'); $routes->get('Opd_prescription/fhir_bundle/(:num)/(:num)', 'Opd_prescription::fhir_bundle/$1/$2'); $routes->get('Opd_prescription/fhir_bundle_history/(:num)', 'Opd_prescription::fhir_bundle_history/$1'); diff --git a/app/Controllers/HealthplixCallback.php b/app/Controllers/HealthplixCallback.php new file mode 100644 index 0000000..ad0cda4 --- /dev/null +++ b/app/Controllers/HealthplixCallback.php @@ -0,0 +1,81 @@ +request->getBody(); + $decoded = $this->request->getJSON(true); + $payload = is_array($decoded) ? $decoded : []; + + $expectedSecret = $this->resolveExpectedSecret(); + $providedSecret = trim((string) $this->request->getHeaderLine('X-Healthplix-Secret')); + + if ($expectedSecret !== '' && ! hash_equals($expectedSecret, $providedSecret)) { + $this->logFetchEvent('error', 401, [ + 'message' => 'Invalid callback secret', + 'payload_bytes' => strlen($rawBody), + 'payload_keys' => array_keys($payload), + ], 'Unauthorized callback request'); + + return $this->response->setStatusCode(401)->setJSON([ + 'ok' => 0, + 'error_text' => 'Unauthorized callback request', + ]); + } + + $this->logFetchEvent('success', 200, [ + 'message' => 'Callback payload received', + 'payload_bytes' => strlen($rawBody), + 'payload_keys' => array_keys($payload), + ]); + + return $this->response->setJSON([ + 'ok' => 1, + 'message' => 'HealthPlix fetch callback received', + ]); + } + + private function resolveExpectedSecret(): string + { + $secret = trim((string) hospital_setting_value('HEALTHPLIX_FETCH_SECRET', '')); + if ($secret !== '') { + return $secret; + } + + return trim((string) env('HEALTHPLIX_FETCH_SECRET', '')); + } + + /** + * @param array $response + */ + private function logFetchEvent(string $status, int $responseCode, array $response, string $errorMessage = ''): void + { + try { + if (! $this->db || ! method_exists($this->db, 'tableExists') || ! $this->db->tableExists('abdm_api_logs')) { + return; + } + + $this->db->table('abdm_api_logs')->insert([ + 'channel' => 'healthplix', + 'event_type' => 'healthplix.fetch.callback', + 'endpoint' => (string) $this->request->getUri()->getPath(), + 'http_method' => strtoupper($this->request->getMethod()), + 'entity_type' => 'callback', + 'entity_id' => trim((string) ($this->request->getHeaderLine('X-Request-Id') ?: 'n/a')), + 'request_json' => json_encode([ + 'ip' => $this->request->getIPAddress(), + 'content_type' => (string) $this->request->getHeaderLine('Content-Type'), + ], JSON_UNESCAPED_SLASHES), + 'response_code' => $responseCode, + 'response_json' => json_encode($response, JSON_UNESCAPED_SLASHES), + 'status' => $status, + 'error_message' => $errorMessage, + 'created_at' => date('Y-m-d H:i:s'), + ]); + } catch (\Throwable $e) { + } + } +} \ No newline at end of file diff --git a/app/Controllers/Setting/HealthplixSettings.php b/app/Controllers/Setting/HealthplixSettings.php index 6b7286c..f147515 100644 --- a/app/Controllers/Setting/HealthplixSettings.php +++ b/app/Controllers/Setting/HealthplixSettings.php @@ -16,6 +16,8 @@ class HealthplixSettings extends BaseController return view('Setting/Admin/healthplix_settings', [ 'healthplix_enabled' => $this->readSettingValue('HEALTHPLIX_ENABLED') === '1' ? '1' : '0', 'healthplix_base_url' => $this->readSettingValue('HEALTHPLIX_BASE_URL') ?: 'https://consultation-edge-dev.healthplix.com', + 'healthplix_fetch_secret_masked' => $this->maskKey($this->readSettingValue('HEALTHPLIX_FETCH_SECRET')), + 'healthplix_fetch_secret_exists' => $this->readSettingValue('HEALTHPLIX_FETCH_SECRET') !== '', 'healthplix_tenant_id' => $this->readSettingValue('HEALTHPLIX_TENANT_ID'), 'healthplix_tenant_key_masked' => $this->maskKey($this->readSettingValue('HEALTHPLIX_TENANT_KEY')), 'healthplix_tenant_key_exists' => $this->readSettingValue('HEALTHPLIX_TENANT_KEY') !== '', @@ -47,6 +49,7 @@ class HealthplixSettings extends BaseController $fields = [ 'HEALTHPLIX_ENABLED' => $this->request->getPost('healthplix_enabled') === '1' ? '1' : '0', 'HEALTHPLIX_BASE_URL' => trim((string) $this->request->getPost('healthplix_base_url')), + 'HEALTHPLIX_FETCH_SECRET' => trim((string) $this->request->getPost('healthplix_fetch_secret')), 'HEALTHPLIX_TENANT_ID' => trim((string) $this->request->getPost('healthplix_tenant_id')), 'HEALTHPLIX_TENANT_KEY' => trim((string) $this->request->getPost('healthplix_tenant_key')), 'HEALTHPLIX_DOCTOR_IDENTIFIER' => trim((string) $this->request->getPost('healthplix_doctor_identifier')), diff --git a/app/Views/Setting/Admin/healthplix_settings.php b/app/Views/Setting/Admin/healthplix_settings.php index eae0ea4..4905e96 100644 --- a/app/Views/Setting/Admin/healthplix_settings.php +++ b/app/Views/Setting/Admin/healthplix_settings.php @@ -30,6 +30,20 @@ +
+
+ + +
+ + ✓ Secret saved (). Leave blank to keep existing. + + No callback secret configured yet. + +
+
+
+
API Endpoints V1
Paths are relative to the Base URL above. Pre-filled from HealthPlix API Specification Doc v1.7.
@@ -140,6 +154,7 @@ return { healthplix_enabled: $('#healthplix_enabled').is(':checked') ? '1' : '0', healthplix_base_url: ($('#healthplix_base_url').val() || '').trim(), + healthplix_fetch_secret: ($('#healthplix_fetch_secret').val() || '').trim(), healthplix_tenant_header_name: ($('#healthplix_tenant_header_name').val() || '').trim(), healthplix_tenant_id: ($('#healthplix_tenant_id').val() || '').trim(), healthplix_tenant_key: ($('#healthplix_tenant_key').val() || '').trim(), @@ -163,6 +178,15 @@ } }); + $('#healthplix_fetch_secret').on('input', function() { + var val = $(this).val().trim(); + if (val.length > 0) { + $('#healthplix_fetch_secret_hint').html('✎ New secret will be saved on Save Settings.'); + } else { + $('#healthplix_fetch_secret_hint').html('Leave blank to keep existing callback secret.'); + } + }); + $('#btn_save_healthplix').on('click', function() { postJson('', payload(), function(data) { if ((data.update || 0) !== 1) { @@ -178,7 +202,17 @@ } else { $('#healthplix_tenant_key_hint').html('Leave blank to keep existing key.'); } + + var savedSecret = ($('#healthplix_fetch_secret').val() || '').trim(); + if (savedSecret.length > 0) { + var maskedSecret = savedSecret.length <= 8 ? '********' : (savedSecret.substring(0, 4) + '****' + savedSecret.slice(-4)); + $('#healthplix_fetch_secret_hint').html('✓ Secret saved (' + maskedSecret + '). Leave blank to keep existing.'); + } else { + $('#healthplix_fetch_secret_hint').html('Leave blank to keep existing callback secret.'); + } + $('#healthplix_tenant_key').val(''); + $('#healthplix_fetch_secret').val(''); }); }); diff --git a/docs/HEALTHPLIX_APACHE_PROXY_CENTOS7.md b/docs/HEALTHPLIX_APACHE_PROXY_CENTOS7.md new file mode 100644 index 0000000..109688e --- /dev/null +++ b/docs/HEALTHPLIX_APACHE_PROXY_CENTOS7.md @@ -0,0 +1,156 @@ +# HealthPlix Reverse Proxy Setup (CentOS 7 + Apache) + +This guide is for a central public Apache server that forwards each hospital domain to its own local HMS over VPN. + +Example topology: +- Public Apache server: 139.59.13.39 +- Hospital A domain: 100001.dhms.in -> A record -> 139.59.13.39 +- Hospital A HMS private VPN IP: 10.7.0.32 +- Hospital B domain: 100002.dhms.in -> A record -> 139.59.13.39 +- Hospital B HMS private VPN IP: 10.8.0.45 + +Recommended exposure model: +- Expose only callback/fetch path to internet. +- Do not expose full HMS unless required. + +## 1) Install and enable required Apache modules + +Run on CentOS 7 Apache host: + + sudo yum install -y mod_ssl + sudo httpd -M | egrep "proxy|ssl|headers|rewrite" + +If any are missing, enable in Apache config and restart httpd. + +## 2) Reusable virtual-host template (callback-only) + +Create one file per hospital in /etc/httpd/conf.d/ + +Template file name: +- /etc/httpd/conf.d/DOMAIN.conf + +Template content: + + + ServerName DOMAIN + RewriteEngine On + RewriteRule ^/(.*)$ https://DOMAIN/$1 [R=301,L] + + + + ServerName DOMAIN + + SSLEngine On + SSLCertificateFile /etc/letsencrypt/live/DOMAIN/fullchain.pem + SSLCertificateKeyFile /etc/letsencrypt/live/DOMAIN/privkey.pem + + ProxyRequests Off + ProxyPreserveHost On + SSLProxyEngine On + + RequestHeader set X-Forwarded-Proto "https" + RequestHeader append X-Forwarded-For %{REMOTE_ADDR}s + + ProxyTimeout 60 + Timeout 60 + + # Callback/fetch endpoint only + ProxyPass /healthplix/fetch http://VPN_HMS_IP/healthplix/fetch + ProxyPassReverse /healthplix/fetch http://VPN_HMS_IP/healthplix/fetch + + ErrorLog /var/log/httpd/DOMAIN-healthplix-error.log + CustomLog /var/log/httpd/DOMAIN-healthplix-access.log combined + + +Replace: +- DOMAIN with hospital DNS name (example 100001.dhms.in) +- VPN_HMS_IP with local HMS VPN IP (example 10.7.0.32) + +## 3) Hardened variant (recommended) + +Use this inside the :443 VirtualHost when HealthPlix source IPs and secret are available. + + # 3.1 Limit endpoint to known HealthPlix source IPs + + Require all denied + Require ip 203.0.113.10 + Require ip 203.0.113.11 + + + # 3.2 Shared-secret header validation at edge + RewriteEngine On + RewriteCond %{REQUEST_URI} ^/healthplix/fetch$ + RewriteCond %{HTTP:X-Healthplix-Secret} !^REPLACE_WITH_STRONG_SECRET$ + RewriteRule ^ - [F,L] + + # 3.3 Restrict methods + + + Require all denied + + + + # 3.4 Body size limits (adjust to payload) + LimitRequestBody 1048576 + +Notes: +- Replace placeholder IPs with official HealthPlix outbound IPs. +- Replace secret with a long random value. +- Validate same secret again in HMS controller before processing. + +## 4) Multi-hospital mapping examples + +Hospital A: +- Domain: 100001.dhms.in +- Target: 10.7.0.32 + +Hospital B: +- Domain: 100002.dhms.in +- Target: 10.8.0.45 + +Hospital C: +- Domain: 100003.dhms.in +- Target: 10.9.0.20 + +All domains can point to same public IP 139.59.13.39. +Apache routes by ServerName. + +## 5) Enable and reload Apache + + sudo apachectl configtest + sudo systemctl reload httpd + +If using firewalld: + + sudo firewall-cmd --add-service=http --permanent + sudo firewall-cmd --add-service=https --permanent + sudo firewall-cmd --reload + +## 6) HMS application-side settings + +For outbound sync from HMS to HealthPlix: +- Keep HEALTHPLIX_BASE_URL as official HealthPlix API base URL. +- Keep token/patient/appointment paths as currently configured. + +For inbound fetch/callback from HealthPlix: +- Give HealthPlix callback URL as: + https://DOMAIN/healthplix/fetch + +## 7) Validation checklist + +1. DNS resolves correctly: + nslookup DOMAIN +2. TLS certificate is valid: + curl -Iv https://DOMAIN/healthplix/fetch +3. Reverse proxy reaches HMS private node: + curl -i -X POST https://DOMAIN/healthplix/fetch -H "X-Healthplix-Secret: REPLACE_WITH_STRONG_SECRET" -d '{}' +4. Apache logs show routed request in domain-specific log file. +5. HMS receives request and returns expected response. + +## 8) Operations notes + +- Keep one vhost file per hospital for isolation. +- Keep one log file pair per hospital for easier troubleshooting. +- Rotate logs and monitor 4xx/5xx rates. +- Keep VPN routes and ACLs documented and version-controlled. +- Do not log bearer tokens or patient-sensitive payloads in plaintext. diff --git a/docs/apache-proxy-templates/generated/100001.dhms.in.conf b/docs/apache-proxy-templates/generated/100001.dhms.in.conf new file mode 100644 index 0000000..ca02f69 --- /dev/null +++ b/docs/apache-proxy-templates/generated/100001.dhms.in.conf @@ -0,0 +1,51 @@ + + ServerName 100001.dhms.in + RewriteEngine On + RewriteRule ^/(.*)$ https://100001.dhms.in/$1 [R=301,L] + + + + ServerName 100001.dhms.in + + SSLEngine On + SSLCertificateFile /etc/letsencrypt/live/100001.dhms.in/fullchain.pem + SSLCertificateKeyFile /etc/letsencrypt/live/100001.dhms.in/privkey.pem + + ProxyRequests Off + ProxyPreserveHost On + SSLProxyEngine On + SSLProxyVerify require + SSLProxyCheckPeerName on + SSLProxyCheckPeerExpire on + + RequestHeader set X-Forwarded-Proto "https" + RequestHeader append X-Forwarded-For %{REMOTE_ADDR}s + + ProxyTimeout 60 + Timeout 60 + + # Replace with official HealthPlix source IPs + + Require all denied + Require ip 203.0.113.10 + Require ip 203.0.113.11 + + Require all denied + + + + # Replace with your strong shared secret + RewriteEngine On + RewriteCond %{REQUEST_URI} ^/healthplix/fetch$ + RewriteCond %{HTTP:X-Healthplix-Secret} !^CHANGE_ME_TO_STRONG_SECRET$ + RewriteRule ^ - [F,L] + + LimitRequestBody 1048576 + + # Use internal DNS name reachable via VPN so backend TLS/certificate can be used. + ProxyPass /healthplix/fetch https://khrc.dhms.in/healthplix/fetch + ProxyPassReverse /healthplix/fetch https://khrc.dhms.in/healthplix/fetch + + ErrorLog /var/log/httpd/100001-healthplix-error.log + CustomLog /var/log/httpd/100001-healthplix-access.log combined + diff --git a/docs/apache-proxy-templates/generated/100002.dhms.in.conf b/docs/apache-proxy-templates/generated/100002.dhms.in.conf new file mode 100644 index 0000000..b30b91a --- /dev/null +++ b/docs/apache-proxy-templates/generated/100002.dhms.in.conf @@ -0,0 +1,47 @@ + + ServerName 100002.dhms.in + RewriteEngine On + RewriteRule ^/(.*)$ https://100002.dhms.in/$1 [R=301,L] + + + + ServerName 100002.dhms.in + + SSLEngine On + SSLCertificateFile /etc/letsencrypt/live/100002.dhms.in/fullchain.pem + SSLCertificateKeyFile /etc/letsencrypt/live/100002.dhms.in/privkey.pem + + ProxyRequests Off + ProxyPreserveHost On + SSLProxyEngine On + + RequestHeader set X-Forwarded-Proto "https" + RequestHeader append X-Forwarded-For %{REMOTE_ADDR}s + + ProxyTimeout 60 + Timeout 60 + + # Replace with official HealthPlix source IPs + + Require all denied + Require ip 203.0.113.10 + Require ip 203.0.113.11 + + Require all denied + + + + # Replace with your strong shared secret + RewriteEngine On + RewriteCond %{REQUEST_URI} ^/healthplix/fetch$ + RewriteCond %{HTTP:X-Healthplix-Secret} !^CHANGE_ME_TO_STRONG_SECRET$ + RewriteRule ^ - [F,L] + + LimitRequestBody 1048576 + + ProxyPass /healthplix/fetch http://10.8.0.45/healthplix/fetch + ProxyPassReverse /healthplix/fetch http://10.8.0.45/healthplix/fetch + + ErrorLog /var/log/httpd/100002-healthplix-error.log + CustomLog /var/log/httpd/100002-healthplix-access.log combined + diff --git a/docs/apache-proxy-templates/generated/100003.dhms.in.conf b/docs/apache-proxy-templates/generated/100003.dhms.in.conf new file mode 100644 index 0000000..cf6179b --- /dev/null +++ b/docs/apache-proxy-templates/generated/100003.dhms.in.conf @@ -0,0 +1,47 @@ + + ServerName 100003.dhms.in + RewriteEngine On + RewriteRule ^/(.*)$ https://100003.dhms.in/$1 [R=301,L] + + + + ServerName 100003.dhms.in + + SSLEngine On + SSLCertificateFile /etc/letsencrypt/live/100003.dhms.in/fullchain.pem + SSLCertificateKeyFile /etc/letsencrypt/live/100003.dhms.in/privkey.pem + + ProxyRequests Off + ProxyPreserveHost On + SSLProxyEngine On + + RequestHeader set X-Forwarded-Proto "https" + RequestHeader append X-Forwarded-For %{REMOTE_ADDR}s + + ProxyTimeout 60 + Timeout 60 + + # Replace with official HealthPlix source IPs + + Require all denied + Require ip 203.0.113.10 + Require ip 203.0.113.11 + + Require all denied + + + + # Replace with your strong shared secret + RewriteEngine On + RewriteCond %{REQUEST_URI} ^/healthplix/fetch$ + RewriteCond %{HTTP:X-Healthplix-Secret} !^CHANGE_ME_TO_STRONG_SECRET$ + RewriteRule ^ - [F,L] + + LimitRequestBody 1048576 + + ProxyPass /healthplix/fetch http://10.9.0.20/healthplix/fetch + ProxyPassReverse /healthplix/fetch http://10.9.0.20/healthplix/fetch + + ErrorLog /var/log/httpd/100003-healthplix-error.log + CustomLog /var/log/httpd/100003-healthplix-access.log combined + diff --git a/docs/apache-proxy-templates/local-test/LOCAL_TEST_CHECKLIST.md b/docs/apache-proxy-templates/local-test/LOCAL_TEST_CHECKLIST.md new file mode 100644 index 0000000..b70dee8 --- /dev/null +++ b/docs/apache-proxy-templates/local-test/LOCAL_TEST_CHECKLIST.md @@ -0,0 +1,47 @@ +# Local-First Validation Checklist (My Host + VPN Server) + +## A) My Host local test (10.7.0.31 machine) + +1. Start CI4 locally: + php spark serve --host localhost --port 8080 + +2. Add hosts entry from my-host/hosts.example: + 127.0.0.1 100001.localtest + +3. Enable local Apache vhost: + my-host/100001.localtest.conf + +4. Reload local Apache. + +5. Test through local domain (same callback path): + curl -i -X POST http://100001.localtest/healthplix/fetch -H "X-Healthplix-Secret: CHANGE_ME_TO_STRONG_SECRET" -d '{}' + +Expected: +- Request reaches CI4 route at localhost:8080/healthplix/fetch. + +## B) VPN server test (10.7.0.1) + +1. Install config: + vpn-server/100001.dhms.in.conf -> /etc/httpd/conf.d/100001.dhms.in.conf + +2. Validate and reload Apache: + apachectl configtest + systemctl reload httpd + +3. From your VPN client machine (10.7.0.31), verify DNS and TLS: + nslookup 100001.dhms.in + curl -Iv https://100001.dhms.in/healthplix/fetch + +4. Verify callback path through edge proxy: + curl -i -X POST https://100001.dhms.in/healthplix/fetch -H "X-Healthplix-Secret: CHANGE_ME_TO_STRONG_SECRET" -d '{}' + +Expected: +- Edge server accepts request. +- Request is forwarded to khrc.dhms.in over VPN. +- HMS responds with your fetch endpoint response. + +## Notes + +- Replace placeholder source IPs and secret before production. +- If backend HMS certificate is private/self-signed, import internal CA cert to proxy trust store. +- Keep healthplix/fetch endpoint method-restricted to POST. diff --git a/docs/apache-proxy-templates/local-test/my-host/100001.localtest.conf b/docs/apache-proxy-templates/local-test/my-host/100001.localtest.conf new file mode 100644 index 0000000..f647c7a --- /dev/null +++ b/docs/apache-proxy-templates/local-test/my-host/100001.localtest.conf @@ -0,0 +1,23 @@ +# Local machine Apache config for pre-production testing +# Purpose: test callback flow locally by forwarding to CI4 dev server (php spark serve) +# Place on local Apache host and adapt paths/ports for your OS. + + + ServerName 100001.localtest + + ProxyRequests Off + ProxyPreserveHost On + + RequestHeader set X-Forwarded-Proto "http" + RequestHeader append X-Forwarded-For %{REMOTE_ADDR}s + + ProxyTimeout 60 + Timeout 60 + + # Keep same route shape as production, but target local spark server. + ProxyPass /healthplix/fetch http://localhost:8080/healthplix/fetch + ProxyPassReverse /healthplix/fetch http://localhost:8080/healthplix/fetch + + ErrorLog logs/100001-localtest-error.log + CustomLog logs/100001-localtest-access.log combined + diff --git a/docs/apache-proxy-templates/local-test/my-host/hosts.example b/docs/apache-proxy-templates/local-test/my-host/hosts.example new file mode 100644 index 0000000..6c05b2f --- /dev/null +++ b/docs/apache-proxy-templates/local-test/my-host/hosts.example @@ -0,0 +1,8 @@ +# Add to your local hosts file for local-domain testing +# Windows path: C:\Windows\System32\drivers\etc\hosts +# Linux path: /etc/hosts + +127.0.0.1 100001.localtest + +# Optional for testing real VPN domain from your machine over VPN +# 10.7.0.1 100001.dhms.in diff --git a/docs/apache-proxy-templates/local-test/vpn-server/100001.dhms.in.conf b/docs/apache-proxy-templates/local-test/vpn-server/100001.dhms.in.conf new file mode 100644 index 0000000..6addcad --- /dev/null +++ b/docs/apache-proxy-templates/local-test/vpn-server/100001.dhms.in.conf @@ -0,0 +1,51 @@ +# CentOS 7 VPN/public proxy server config +# Place on VPN server: /etc/httpd/conf.d/100001.dhms.in.conf + + + ServerName 100001.dhms.in + RewriteEngine On + RewriteRule ^/(.*)$ https://100001.dhms.in/$1 [R=301,L] + + + + ServerName 100001.dhms.in + + SSLEngine On + SSLCertificateFile /etc/letsencrypt/live/100001.dhms.in/fullchain.pem + SSLCertificateKeyFile /etc/letsencrypt/live/100001.dhms.in/privkey.pem + + ProxyRequests Off + ProxyPreserveHost On + SSLProxyEngine On + + RequestHeader set X-Forwarded-Proto "https" + RequestHeader append X-Forwarded-For %{REMOTE_ADDR}s + + ProxyTimeout 60 + Timeout 60 + + # Replace with official HealthPlix source IPs + + Require all denied + Require ip 203.0.113.10 + Require ip 203.0.113.11 + + Require all denied + + + + # Replace with your strong shared secret + RewriteEngine On + RewriteCond %{REQUEST_URI} ^/healthplix/fetch$ + RewriteCond %{HTTP:X-Healthplix-Secret} !^CHANGE_ME_TO_STRONG_SECRET$ + RewriteRule ^ - [F,L] + + LimitRequestBody 1048576 + + # Production target: HMS server over VPN + ProxyPass /healthplix/fetch https://khrc.dhms.in/healthplix/fetch + ProxyPassReverse /healthplix/fetch https://khrc.dhms.in/healthplix/fetch + + ErrorLog /var/log/httpd/100001-healthplix-error.log + CustomLog /var/log/httpd/100001-healthplix-access.log combined + diff --git a/docs/apache-proxy-templates/vhost-callback-hardened.template.conf b/docs/apache-proxy-templates/vhost-callback-hardened.template.conf new file mode 100644 index 0000000..54d00b9 --- /dev/null +++ b/docs/apache-proxy-templates/vhost-callback-hardened.template.conf @@ -0,0 +1,51 @@ +# Copy to /etc/httpd/conf.d/DOMAIN.conf and replace placeholders. +# This variant includes source IP allowlist and shared-secret header check. + + + ServerName __DOMAIN__ + RewriteEngine On + RewriteRule ^/(.*)$ https://__DOMAIN__/$1 [R=301,L] + + + + ServerName __DOMAIN__ + + SSLEngine On + SSLCertificateFile /etc/letsencrypt/live/__DOMAIN__/fullchain.pem + SSLCertificateKeyFile /etc/letsencrypt/live/__DOMAIN__/privkey.pem + + ProxyRequests Off + ProxyPreserveHost On + SSLProxyEngine On + + RequestHeader set X-Forwarded-Proto "https" + RequestHeader append X-Forwarded-For %{REMOTE_ADDR}s + + ProxyTimeout 60 + Timeout 60 + + # Allow only known HealthPlix source IPs (replace with official list) + + Require all denied + Require ip __HEALTHPLIX_IP_1__ + Require ip __HEALTHPLIX_IP_2__ + + Require all denied + + + + # Block requests without valid shared secret header + RewriteEngine On + RewriteCond %{REQUEST_URI} ^/healthplix/fetch$ + RewriteCond %{HTTP:X-Healthplix-Secret} !^__STRONG_SHARED_SECRET__$ + RewriteRule ^ - [F,L] + + # Limit payload size to 1 MB (adjust as needed) + LimitRequestBody 1048576 + + ProxyPass /healthplix/fetch http://__VPN_HMS_IP__/healthplix/fetch + ProxyPassReverse /healthplix/fetch http://__VPN_HMS_IP__/healthplix/fetch + + ErrorLog /var/log/httpd/__DOMAIN__-healthplix-error.log + CustomLog /var/log/httpd/__DOMAIN__-healthplix-access.log combined + diff --git a/docs/apache-proxy-templates/vhost-callback-only.template.conf b/docs/apache-proxy-templates/vhost-callback-only.template.conf new file mode 100644 index 0000000..1373079 --- /dev/null +++ b/docs/apache-proxy-templates/vhost-callback-only.template.conf @@ -0,0 +1,31 @@ +# Copy to /etc/httpd/conf.d/DOMAIN.conf and replace placeholders. + + + ServerName __DOMAIN__ + RewriteEngine On + RewriteRule ^/(.*)$ https://__DOMAIN__/$1 [R=301,L] + + + + ServerName __DOMAIN__ + + SSLEngine On + SSLCertificateFile /etc/letsencrypt/live/__DOMAIN__/fullchain.pem + SSLCertificateKeyFile /etc/letsencrypt/live/__DOMAIN__/privkey.pem + + ProxyRequests Off + ProxyPreserveHost On + SSLProxyEngine On + + RequestHeader set X-Forwarded-Proto "https" + RequestHeader append X-Forwarded-For %{REMOTE_ADDR}s + + ProxyTimeout 60 + Timeout 60 + + ProxyPass /healthplix/fetch http://__VPN_HMS_IP__/healthplix/fetch + ProxyPassReverse /healthplix/fetch http://__VPN_HMS_IP__/healthplix/fetch + + ErrorLog /var/log/httpd/__DOMAIN__-healthplix-error.log + CustomLog /var/log/httpd/__DOMAIN__-healthplix-access.log combined +