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