diff --git a/.github/workflows/publish-azure-cc-enclave-docker.yaml b/.github/workflows/publish-azure-cc-enclave-docker.yaml
new file mode 100644
index 000000000..0e214d516
--- /dev/null
+++ b/.github/workflows/publish-azure-cc-enclave-docker.yaml
@@ -0,0 +1,146 @@
+name: Publish Azure Confidential Container Enclave Docker
+on:
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: 'The tag to apply to the Docker file'
+ type: string
+ release_type:
+ description: The type of version number to return. Must be one of [Snapshot, Patch, Minor or Major]
+ required: true
+ type: string
+ java_version:
+ type: string
+ default: '11'
+ publish_vulnerabilities:
+ type: string
+ default: 'true'
+
+env:
+ REGISTRY: ghcr.io
+ MAVEN_PROFILE: azure
+ ENCLAVE_PROTOCOL: azure-cc
+ IMAGE_NAME: ${{ github.repository }}
+ DOCKER_CONTEXT_PATH: scripts/azure-cc
+
+jobs:
+ build-publish-docker:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ security-events: write
+ packages: write
+ id-token: write
+ outputs:
+ jar_version: ${{ steps.version.outputs.new_version }}
+ steps:
+ - name: Set up JDK
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: ${{ inputs.java_version }}
+
+ - name: Checkout full history
+ uses: actions/checkout@v3
+ with:
+ # git-restore-mtime requires full git history. The default fetch-depth value (1) creates a shallow checkout.
+ fetch-depth: 0
+
+ - name: Restore timestamps
+ uses: thetradedesk/git-restore-mtime-action@v1.2
+
+ - name: Set version number
+ id: version
+ uses: IABTechLab/uid2-shared-actions/actions/version_number@main
+ with:
+ type: ${{ inputs.release_type }}
+
+ - name: Update pom.xml
+ run: |
+ current_version=$(grep -o '.*' pom.xml | head -1 | sed 's/\(.*\)<\/version>/\1/')
+ new_version=${{ steps.version.outputs.new_version }}
+ sed -i "0,/$current_version/s//$new_version/" pom.xml
+ echo "Version number updated from $current_version to $new_version"
+
+ - name: Package JAR
+ id: package
+ run: |
+ mvn -B package -P ${{ env.MAVEN_PROFILE }}
+ echo "jar_version=$(mvn help:evaluate -Dexpression=project.version | grep -e '^[1-9][^\[]')" >> $GITHUB_OUTPUT
+ echo "git_commit=$(git show --format="%h" --no-patch)" >> $GITHUB_OUTPUT
+ cp -r target ${{ env.DOCKER_CONTEXT_PATH }}/
+
+ - name: Commit pom.xml and version.json
+ uses: EndBug/add-and-commit@v9
+ with:
+ add: 'pom.xml version.json'
+ author_name: Release Workflow
+ author_email: unifiedid-admin+release@thetradedesk.com
+ message: 'Released ${{ inputs.release_type }} version: ${{ steps.version.outputs.new_version }}'
+
+ - name: Log in to the Docker container registry
+ uses: docker/login-action@v2
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=raw,value=${{ steps.version.outputs.new_version }}-${{ env.ENCLAVE_PROTOCOL }}
+ type=raw,value=${{ inputs.tag }}
+
+ - name: Build and export to Docker
+ uses: docker/build-push-action@v3
+ with:
+ context: ${{ env.DOCKER_CONTEXT_PATH }}
+ load: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ build-args: |
+ JAR_VERSION=${{ steps.version.outputs.new_version }}
+ IMAGE_VERSION=${{ steps.version.outputs.new_version }}
+ BUILD_TARGET=${{ env.ENCLAVE_PROTOCOL }}
+
+ - name: Generate Trivy vulnerability scan report
+ uses: aquasecurity/trivy-action@master
+ if: inputs.publish_vulnerabilities == 'true'
+ with:
+ image-ref: ${{ steps.meta.outputs.tags }}
+ format: 'sarif'
+ exit-code: '0'
+ ignore-unfixed: true
+ severity: 'CRITICAL,HIGH'
+ output: 'trivy-results.sarif'
+ hide-progress: true
+
+ - name: Upload Trivy scan report to GitHub Security tab
+ uses: github/codeql-action/upload-sarif@v2
+ if: inputs.publish_vulnerabilities == 'true'
+ with:
+ sarif_file: 'trivy-results.sarif'
+
+ - name: Test with Trivy vulnerability scanner
+ uses: aquasecurity/trivy-action@master
+ with:
+ image-ref: ${{ steps.meta.outputs.tags }}
+ format: 'table'
+ exit-code: '1'
+ ignore-unfixed: true
+ severity: 'CRITICAL'
+ hide-progress: true
+
+ - name: Push to Docker
+ uses: docker/build-push-action@v3
+ with:
+ context: ${{ env.DOCKER_CONTEXT_PATH }}
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ build-args: |
+ JAR_VERSION=${{ steps.version.outputs.new_version }}
+ IMAGE_VERSION=${{ steps.version.outputs.new_version }}
diff --git a/e2e/e2e.sh b/e2e/e2e.sh
index a4c8f99f1..2af308c17 100644
--- a/e2e/e2e.sh
+++ b/e2e/e2e.sh
@@ -2,7 +2,7 @@
set -x
# to facilitate local test
-NGROK_TOKEN=
+NGROK_TOKEN=2WYf3ac4ILCcKr9ZmyJ2gsJYY93_3HxCWivUUw8noe683bBxY
IMAGE_HASH=
CORE_VERSION=2.12.0-a9d204eec0-default
OPTOUT_VERSION=2.6.18-60727cf243-default
diff --git a/scripts/azure-cc/Dockerfile b/scripts/azure-cc/Dockerfile
new file mode 100644
index 000000000..b975a44fe
--- /dev/null
+++ b/scripts/azure-cc/Dockerfile
@@ -0,0 +1,31 @@
+FROM eclipse-temurin@sha256:de8e6219ff5360811a453a9237713679a9d9106ba5150290ef37fb23e246ce7d
+
+# Install Packages
+RUN apk update && apk add jq
+
+WORKDIR /app
+EXPOSE 8080
+
+ARG JAR_NAME=uid2-operator
+ARG JAR_VERSION=1.0.0-SNAPSHOT
+ARG IMAGE_VERSION=1.0.0.unknownhash
+ENV JAR_NAME=${JAR_NAME}
+ENV JAR_VERSION=${JAR_VERSION}
+ENV IMAGE_VERSION=${IMAGE_VERSION}
+ENV LOGBACK_CONF=${LOGBACK_CONF:-./conf/logback.xml}
+
+COPY ./target/${JAR_NAME}-${JAR_VERSION}-jar-with-dependencies.jar /app/${JAR_NAME}-${JAR_VERSION}.jar
+COPY ./target/${JAR_NAME}-${JAR_VERSION}-sources.jar /app
+COPY ./target/${JAR_NAME}-${JAR_VERSION}-static.tar.gz /app/static.tar.gz
+COPY ./conf/*.json /app/conf/
+COPY ./conf/*.xml /app/conf/
+
+RUN tar xzvf /app/static.tar.gz --no-same-owner --no-same-permissions && rm -f /app/static.tar.gz
+
+COPY ./entrypoint.sh /app/
+RUN chmod a+x /app/entrypoint.sh
+
+RUN adduser -D uid2-operator && mkdir -p /opt/uid2 && chmod 777 -R /opt/uid2 && mkdir -p /app && chmod 705 -R /app && mkdir -p /app/file-uploads && chmod 777 -R /app/file-uploads
+USER uid2-operator
+
+CMD ["/app/entrypoint.sh"]
\ No newline at end of file
diff --git a/scripts/azure-cc/README.md b/scripts/azure-cc/README.md
new file mode 100644
index 000000000..d00b794f6
--- /dev/null
+++ b/scripts/azure-cc/README.md
@@ -0,0 +1 @@
+# UID2 Operator - Azure Confidential Container package
\ No newline at end of file
diff --git a/scripts/azure-cc/conf/default-config.json b/scripts/azure-cc/conf/default-config.json
new file mode 100644
index 000000000..b1ddb71d5
--- /dev/null
+++ b/scripts/azure-cc/conf/default-config.json
@@ -0,0 +1,42 @@
+{
+ "service_verbose": true,
+ "service_instances": 12,
+ "core_s3_bucket": null,
+ "core_attest_url": null,
+ "core_api_token": null,
+ "storage_mock": false,
+ "optout_s3_bucket": null,
+ "optout_s3_folder": "optout/",
+ "optout_s3_path_compat": false,
+ "optout_data_dir": "/opt/uid2/operator-optout/",
+ "optout_api_token": null,
+ "optout_api_uri": null,
+ "optout_bloom_filter_size": 8192,
+ "optout_delta_rotate_interval": 300,
+ "optout_delta_backtrack_in_days": 1,
+ "optout_partition_interval": 86400,
+ "optout_max_partitions": 30,
+ "optout_heap_default_capacity": 8192,
+ "cloud_download_threads": 8,
+ "cloud_upload_threads": 2,
+ "cloud_refresh_interval": 60,
+ "sites_metadata_path": "sites/metadata.json",
+ "clients_metadata_path": "clients/metadata.json",
+ "client_side_keypairs_metadata_path": "client_side_keypairs/metadata.json",
+ "keysets_metadata_path": "keysets/metadata.json",
+ "keyset_keys_metadata_path": "keyset_keys/metadata.json",
+ "salts_metadata_path": "salts/metadata.json",
+ "services_metadata_path": "services/metadata.json",
+ "service_links_metadata_path": "service_links/metadata.json",
+ "optout_metadata_path": null,
+ "enclave_platform": "azure-cc",
+ "optout_inmem_cache": true,
+ "identity_token_expires_after_seconds": 86400,
+ "refresh_token_expires_after_seconds": 2592000,
+ "refresh_identity_token_after_seconds": 3600,
+ "enforce_https": true,
+ "allow_legacy_api": false,
+ "failure_shutdown_wait_hours": 120,
+ "sharing_token_expiry_seconds": 2592000,
+ "validate_service_links": false
+}
diff --git a/scripts/azure-cc/conf/integ-uid2-config.json b/scripts/azure-cc/conf/integ-uid2-config.json
new file mode 100644
index 000000000..2cd4be5c3
--- /dev/null
+++ b/scripts/azure-cc/conf/integ-uid2-config.json
@@ -0,0 +1,14 @@
+{
+ "sites_metadata_path": "https://core-integ.uidapi.com/sites/refresh",
+ "clients_metadata_path": "https://core-integ.uidapi.com/clients/refresh",
+ "keysets_metadata_path": "https://core-integ.uidapi.com/key/keyset/refresh",
+ "keyset_keys_metadata_path": "https://core-integ.uidapi.com/key/keyset-keys/refresh",
+ "client_side_keypairs_metadata_path": "https://core-integ.uidapi.com/client_side_keypairs/refresh",
+ "salts_metadata_path": "https://core-integ.uidapi.com/salt/refresh",
+ "services_metadata_path": "https://core-integ.uidapi.com/services/refresh",
+ "service_links_metadata_path": "https://core-integ.uidapi.com/service_links/refresh",
+ "optout_metadata_path": "https://optout-integ.uidapi.com/optout/refresh",
+ "core_attest_url": "https://core-integ.uidapi.com/attest",
+ "optout_api_uri": "https://optout-integ.uidapi.com/optout/replicate",
+ "optout_s3_folder": "uid-optout-integ/"
+}
diff --git a/scripts/azure-cc/conf/logback.xml b/scripts/azure-cc/conf/logback.xml
new file mode 100644
index 000000000..56c8e8df2
--- /dev/null
+++ b/scripts/azure-cc/conf/logback.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %ex%n
+
+
+
+
+
+
+
+
diff --git a/scripts/azure-cc/conf/prod-uid2-config.json b/scripts/azure-cc/conf/prod-uid2-config.json
new file mode 100644
index 000000000..a6419b6a5
--- /dev/null
+++ b/scripts/azure-cc/conf/prod-uid2-config.json
@@ -0,0 +1,14 @@
+{
+ "sites_metadata_path": "https://core-prod.uidapi.com/sites/refresh",
+ "clients_metadata_path": "https://core-prod.uidapi.com/clients/refresh",
+ "keysets_metadata_path": "https://core-prod.uidapi.com/key/keyset/refresh",
+ "keyset_keys_metadata_path": "https://core-prod.uidapi.com/key/keyset-keys/refresh",
+ "client_side_keypairs_metadata_path": "https://core-prod.uidapi.com/client_side_keypairs/refresh",
+ "salts_metadata_path": "https://core-prod.uidapi.com/salt/refresh",
+ "services_metadata_path": "https://core-prod.uidapi.com/services/refresh",
+ "service_links_metadata_path": "https://core-prod.uidapi.com/service_links/refresh",
+ "optout_metadata_path": "https://optout-prod.uidapi.com/optout/refresh",
+ "core_attest_url": "https://core-prod.uidapi.com/attest",
+ "optout_api_uri": "https://optout-prod.uidapi.com/optout/replicate",
+ "optout_s3_folder": "optout-v2/"
+}
diff --git a/scripts/azure-cc/entrypoint.sh b/scripts/azure-cc/entrypoint.sh
new file mode 100644
index 000000000..999c580d5
--- /dev/null
+++ b/scripts/azure-cc/entrypoint.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+#
+# This script must be compatible with Ash (provided in eclipse-temurin Docker image) and Bash
+
+# -- set API tokens
+if [ -z "${API_TOKEN_SECRET_NAME}" ]; then
+ echo "API_TOKEN_SECRET_NAME cannot be empty"
+ exit 1
+fi
+
+# TODO(lun.wang) fetch token from Azure Key Vault
+API_TOKEN=${API_TOKEN_SECRET_NAME}
+if [ $? -ne 0 -o -z "${API_TOKEN}" ]; then
+ echo "Failed to get API token"
+ exit 1
+fi
+
+export core_api_token="${API_TOKEN}"
+export optout_api_token="${API_TOKEN}"
+
+# -- locate config file
+if [ -z "${DEPLOYMENT_ENVIRONMENT}" ]; then
+ echo "DEPLOYMENT_ENVIRONMENT cannot be empty"
+ exit 1
+fi
+if [ "${DEPLOYMENT_ENVIRONMENT}" != 'prod' -a "${DEPLOYMENT_ENVIRONMENT}" != 'integ' ]; then
+ echo "Unrecognized DEPLOYMENT_ENVIRONMENT ${DEPLOYMENT_ENVIRONMENT}"
+ exit 1
+fi
+
+TARGET_CONFIG="/app/conf/${DEPLOYMENT_ENVIRONMENT}-uid2-config.json"
+if [ ! -f "${TARGET_CONFIG}" ]; then
+ echo "Unrecognized config ${TARGET_CONFIG}"
+ exit 1
+fi
+
+FINAL_CONFIG="/tmp/final-config.json"
+echo "-- copying ${TARGET_CONFIG} to ${FINAL_CONFIG}"
+cp ${TARGET_CONFIG} ${FINAL_CONFIG}
+if [ $? -ne 0 ]; then
+ echo "Failed to create ${FINAL_CONFIG} with error code $?"
+ exit 1
+fi
+
+# -- replace base URLs if both CORE_BASE_URL and OPTOUT_BASE_URL are provided
+# -- using hardcoded domains is fine because they should not be changed frequently
+if [ -n "${CORE_BASE_URL}" -a -n "${OPTOUT_BASE_URL}" -a "${DEPLOYMENT_ENVIRONMENT}" != 'prod' ]; then
+ echo "-- replacing URLs by ${CORE_BASE_URL} and ${OPTOUT_BASE_URL}"
+ sed -i "s#https://core-integ.uidapi.com#${CORE_BASE_URL}#g" ${FINAL_CONFIG}
+
+ sed -i "s#https://optout-integ.uidapi.com#${OPTOUT_BASE_URL}#g" ${FINAL_CONFIG}
+fi
+
+# -- start operator
+echo "-- starting java application"
+java \
+ -XX:MaxRAMPercentage=95 -XX:-UseCompressedOops -XX:+PrintFlagsFinal \
+ -Djava.security.egd=file:/dev/./urandom \
+ -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory \
+ -Dlogback.configurationFile=${LOGBACK_CONF} \
+ -Dvertx-config-path=${FINAL_CONFIG} \
+ -jar ${JAR_NAME}-${JAR_VERSION}.jar