diff --git a/pom.xml b/pom.xml
index 0474f97a0..3f6cc9a4d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.uid2
uid2-operator
- 5.63.22
+ 5.63.31-alpha-212-SNAPSHOT
UTF-8
diff --git a/scripts/aws/config-server/app.py b/scripts/aws/config-server/app.py
index c0c94fc63..d94857ada 100644
--- a/scripts/aws/config-server/app.py
+++ b/scripts/aws/config-server/app.py
@@ -1,4 +1,5 @@
from flask import Flask
+from datetime import datetime, timezone
import json
import os
@@ -14,5 +15,12 @@ def get_config():
except Exception as e:
return str(e), 500
+@app.route('/getCurrentTime', methods=['GET'])
+def get_time():
+ try:
+ return datetime.now(timezone.utc).isoformat(timespec="seconds")
+ except Exception as e:
+ return str(e), 500
+
if __name__ == '__main__':
app.run(processes=8)
diff --git a/scripts/aws/entrypoint.sh b/scripts/aws/entrypoint.sh
index 6d4fbe15e..f606df6ef 100755
--- a/scripts/aws/entrypoint.sh
+++ b/scripts/aws/entrypoint.sh
@@ -22,6 +22,83 @@ ifconfig lo 127.0.0.1
echo "Starting vsock proxy..."
/app/vsockpx --config /app/proxies.nitro.yaml --daemon --workers $(( ( $(nproc) + 3 ) / 4 )) --log-level 3
+TIME_SYNC_URL="http://127.0.0.1:27015/getCurrentTime"
+TIME_SYNC_PROXY="socks5h://127.0.0.1:3305"
+TIME_SYNC_TRIGGER_PORT="${TIME_SYNC_TRIGGER_PORT:-27100}"
+TIME_SYNC_OFFSET_SECONDS="${TIME_SYNC_OFFSET_SECONDS:-30}"
+
+sync_enclave_time_with_offset_once() {
+ local current_time
+ local parent_epoch
+ if current_time=$(curl -s -f -x socks5h://127.0.0.1:3305 "${TIME_SYNC_URL}"); then
+ parent_epoch=$(date -u -d "${current_time}" +%s 2>/dev/null || true)
+ if [[ -n "${parent_epoch}" ]]; then
+ parent_epoch=$((parent_epoch + TIME_SYNC_OFFSET_SECONDS))
+ if ! date -u -s "@${parent_epoch}"; then
+ echo "Time sync: failed to set enclave time from '${current_time}' with offset ${TIME_SYNC_OFFSET_SECONDS}s"
+ return 1
+ fi
+ echo "Time sync: updated enclave time to ${current_time} + ${TIME_SYNC_OFFSET_SECONDS}s"
+ fi
+ else
+ echo "Time sync: failed to fetch time from parent instance"
+ return 1
+ fi
+}
+
+sync_enclave_time_with_offset_once || true
+
+
+
+start_time_sync_server() {
+ python3 -u - <<'PY' &
+import sys
+import os
+import subprocess
+from http.server import BaseHTTPRequestHandler, HTTPServer
+
+sys.stdout.reconfigure(line_buffering=True)
+
+TIME_SYNC_URL = os.environ.get("TIME_SYNC_URL", "http://127.0.0.1:27015/getCurrentTime")
+TIME_SYNC_PROXY = os.environ.get("TIME_SYNC_PROXY", "socks5h://127.0.0.1:3305")
+TIME_SYNC_TRIGGER_PORT = int(os.environ.get("TIME_SYNC_TRIGGER_PORT", "27100"))
+
+def sync_time() -> str:
+ current_time = subprocess.check_output(
+ ["curl", "-sSf", "-x", TIME_SYNC_PROXY, TIME_SYNC_URL],
+ text=True,
+ ).strip()
+ subprocess.check_call(["date", "-u", "-s", current_time])
+ return current_time
+
+class Handler(BaseHTTPRequestHandler):
+ def do_GET(self) -> None:
+ if self.path not in ("/", "/sync"):
+ self.send_response(404)
+ self.end_headers()
+ return
+ try:
+ result = sync_time()
+ print(f"Time sync: updated enclave time to {result}", flush=True)
+ self.send_response(200)
+ self.end_headers()
+ self.wfile.write(f"OK {result}\n".encode())
+ except Exception as exc: # pragma: no cover - best effort logging
+ print(f"Time sync error: {exc}", flush=True)
+ self.send_response(500)
+ self.end_headers()
+ self.wfile.write(f"ERROR {exc}\n".encode())
+
+ def log_message(self, format, *args): # noqa: N802 - match base class
+ return
+
+server = HTTPServer(("127.0.0.1", TIME_SYNC_TRIGGER_PORT), Handler)
+server.serve_forever()
+PY
+}
+
+start_time_sync_server
+
build_parameterized_config() {
curl -s -f -o "${PARAMETERIZED_CONFIG}" -x socks5h://127.0.0.1:3305 http://127.0.0.1:27015/getConfig
REQUIRED_KEYS=("optout_base_url" "core_base_url" "core_api_token" "optout_api_token" "environment" "uid_instance_id_prefix")
diff --git a/scripts/aws/proxies.host.yaml b/scripts/aws/proxies.host.yaml
index 5a2ae0623..5ed149f3c 100644
--- a/scripts/aws/proxies.host.yaml
+++ b/scripts/aws/proxies.host.yaml
@@ -19,3 +19,8 @@ syslogng:
service: direct
listen: vsock://-1:2011
connect: tcp://127.0.0.1:2011
+time-sync:
+ service: direct
+ listen: tcp://127.0.0.1:27100
+ connect: vsock://42:27100
+
diff --git a/scripts/aws/proxies.nitro.yaml b/scripts/aws/proxies.nitro.yaml
index 0f459b150..f981a9a29 100644
--- a/scripts/aws/proxies.nitro.yaml
+++ b/scripts/aws/proxies.nitro.yaml
@@ -19,3 +19,8 @@ syslogng:
service: direct
listen: tcp://127.0.0.1:2011
connect: vsock://3:2011
+time-sync:
+ service: direct
+ listen: vsock://-1:27100
+ connect: tcp://127.0.0.1:27100
+
diff --git a/scripts/aws/uid2-operator-ami/ansible/playbook.yml b/scripts/aws/uid2-operator-ami/ansible/playbook.yml
index a5ec77809..308dadc55 100644
--- a/scripts/aws/uid2-operator-ami/ansible/playbook.yml
+++ b/scripts/aws/uid2-operator-ami/ansible/playbook.yml
@@ -19,6 +19,11 @@
name: nmap-ncat
state: latest
+ - name: Install cron
+ ansible.builtin.dnf:
+ name: cronie
+ state: latest
+
- name: Install python
ansible.builtin.dnf:
name:
@@ -167,6 +172,15 @@
dest: /etc/systemd/system/uid2operator.service
remote_src: yes
+ - name: Install time sync trigger script
+ ansible.builtin.copy:
+ dest: /usr/local/bin/uid2-time-sync
+ mode: "0755"
+ content: |
+ #!/usr/bin/env bash
+ set -euo pipefail
+ curl -sSf http://127.0.0.1:27100/sync > /dev/null
+
- name: Install AWS Nitro Enclaves CLI
ansible.builtin.dnf:
name: aws-nitro-enclaves-cli
@@ -240,6 +254,19 @@
ansible.builtin.systemd:
name: uid2operator.service
enabled: yes
+
+ - name: Ensure cron is enabled at boot
+ ansible.builtin.systemd:
+ name: crond
+ state: started
+ enabled: yes
+
+ - name: Install time sync cron job
+ ansible.builtin.copy:
+ dest: /etc/cron.d/uid2-time-sync
+ mode: "0644"
+ content: |
+ */5 * * * * root /usr/local/bin/uid2-time-sync
- name: Clean up tmp files
file: