├── conf ├── config.json ├── logback.xml ├── integ-config.json ├── local-e2e-docker-private-config.json ├── default-config.json ├── docker-config.json ├── local-e2e-docker-public-config.json ├── local-config.json ├── validator-latest-e2e-docker-public-config.json ├── local-e2e-private-config.json └── local-e2e-public-config.json ├── .github ├── workflows │ ├── .version │ ├── check-stable-dependency.yaml │ ├── build-and-test.yaml │ ├── vulnerability-scan-failure-notify.yaml │ └── validate-image.yaml └── actions │ └── install_az_cli │ └── action.yaml ├── js ├── .eslintignore └── setupJest.js ├── scripts ├── aws │ ├── pipeline │ │ ├── EUID_VERSION │ │ ├── UID2_VERSION │ │ ├── aws_nitro_eif.sh │ │ ├── amazonlinux2023.Dockerfile │ │ └── amazonlinux.Dockerfile │ ├── config-server │ │ ├── requirements.txt │ │ └── app.py │ ├── requirements.txt │ ├── eks-pod │ │ ├── server_al_2023 │ │ │ ├── README.md │ │ │ └── syslog-ng-server.conf │ │ ├── README.md │ │ ├── sockd_eks.conf │ │ ├── proxies.host.yaml │ │ └── Dockerfile │ ├── syslog-ng │ │ ├── client │ │ │ └── syslog-ng-core_4.6.0-1_amd64.deb │ │ ├── server_al_2023 │ │ │ ├── ivykis-0.43-1.amzn2023.x86_64.rpm │ │ │ ├── libnet-1.2-2.amzn2023.0.2.x86_64.rpm │ │ │ ├── syslog-ng-4.7.1.104.gcc5a7d9-1.amzn2023.x86_64.rpm │ │ │ ├── syslog-ng-logrotate-4.7.1.104.gcc5a7d9-1.amzn2023.x86_64.rpm │ │ │ └── pubkey.gpg │ │ ├── README.md │ │ ├── syslog-ng-client.conf │ │ └── syslog-ng-server.conf │ ├── logrotate │ │ ├── logrotate │ │ ├── logrotateDaily │ │ └── operator-logrotate.conf │ ├── uid2-operator-ami │ │ ├── uid2.pkrvars.hcl │ │ ├── euid.pkrvars.hcl │ │ ├── plugins.pkr.hcl │ │ ├── build.pkr.hcl │ │ ├── source.pkr.hcl │ │ └── vars.pkr.hcl │ ├── sockd.conf │ ├── proxies.host.yaml │ ├── proxies.nitro.yaml │ ├── uid2operator.service │ ├── conf │ │ ├── logback-debug.xml │ │ ├── logback.xml │ │ ├── euid-integ-config.json │ │ ├── uid2-integ-config.json │ │ ├── uid2-prod-config.json │ │ ├── euid-prod-config.json │ │ └── default-config.json │ └── Dockerfile ├── gcp-oidc │ ├── terraform │ │ ├── outputs.tf │ │ ├── terraform.tfvars │ │ ├── .gitignore │ │ └── variables.tf │ ├── requirements.txt │ ├── conf │ │ ├── logback.xml │ │ ├── integ-config.json │ │ ├── prod-config.json │ │ └── default-config.json │ ├── Dockerfile │ └── generate-deployment-artifacts.sh ├── azure-cc │ ├── deployment │ │ ├── gateway.parameters.json │ │ ├── vault.parameters.json │ │ ├── generate.py │ │ ├── vnet.parameters.json │ │ └── operator.parameters.json │ ├── conf │ │ ├── logback.xml │ │ ├── integ-uid2-config.json │ │ ├── prod-uid2-config.json │ │ └── default-config.json │ ├── Dockerfile │ └── README.md └── azure-aks │ └── deployment │ └── operator.yaml ├── .gitattributes ├── releases └── previous_release.hash ├── src ├── main │ ├── java │ │ └── com │ │ │ └── uid2 │ │ │ └── operator │ │ │ ├── service │ │ │ ├── IEncryptionScheme.java │ │ │ ├── ShutdownService.java │ │ │ ├── ITokenEncoder.java │ │ │ ├── JsonParseUtils.java │ │ │ ├── IUIDOperatorService.java │ │ │ └── RoutingContextReader.java │ │ │ ├── model │ │ │ ├── TokenValidateResult.java │ │ │ ├── IdentityMapResponseType.java │ │ │ ├── VerificationResponse.java │ │ │ ├── PublisherIdentity.java │ │ │ ├── OperatorIdentity.java │ │ │ ├── VersionedToken.java │ │ │ ├── OperatorType.java │ │ │ ├── IdentityMapV3Request.java │ │ │ ├── IdentityType.java │ │ │ ├── IdentityVersion.java │ │ │ ├── RefreshToken.java │ │ │ ├── MappedIdentity.java │ │ │ ├── OptoutCheckPolicy.java │ │ │ ├── MapRequest.java │ │ │ ├── IdentityRequest.java │ │ │ ├── CstgRequest.java │ │ │ ├── IdentityScope.java │ │ │ ├── UserIdentity.java │ │ │ ├── IdentityEnvironment.java │ │ │ ├── AdvertisingToken.java │ │ │ ├── KeyManagerSnapshot.java │ │ │ ├── StatsCollectorMessageItem.java │ │ │ ├── IdentityTokens.java │ │ │ └── RefreshResponse.java │ │ │ ├── monitoring │ │ │ ├── ILoggedStat.java │ │ │ ├── IStatsCollectorQueue.java │ │ │ ├── SiteClientVersionStat.java │ │ │ ├── ClientVersionStatRecorder.java │ │ │ ├── StatsCollectorHandler.java │ │ │ └── OperatorMetrics.java │ │ │ ├── store │ │ │ ├── IConfigStore.java │ │ │ ├── BootstrapConfigStore.java │ │ │ ├── IOptOutStore.java │ │ │ ├── RuntimeConfigStore.java │ │ │ └── OptOutCloudStorage.java │ │ │ ├── privacy │ │ │ └── tcf │ │ │ │ ├── TransparentConsentSpecialFeature.java │ │ │ │ ├── TransparentConsentParseResult.java │ │ │ │ ├── TransparentConsentPurpose.java │ │ │ │ └── TransparentConsent.java │ │ │ ├── vertx │ │ │ ├── ClientInputValidationException.java │ │ │ ├── Endpoints.java │ │ │ ├── GenericFailureHandler.java │ │ │ └── ClientVersionCapturingHandler.java │ │ │ ├── util │ │ │ ├── HttpMediaType.java │ │ │ ├── PrivacyBits.java │ │ │ ├── Tuple.java │ │ │ ├── RoutingContextUtil.java │ │ │ └── DomainNameCheckUtil.java │ │ │ ├── IdentityConst.java │ │ │ ├── reader │ │ │ ├── RotatingCloudEncryptionKeyApiProvider.java │ │ │ └── ApiStoreReader.java │ │ │ └── Const.java │ └── resources │ │ └── com.uid2.core │ │ └── test │ │ ├── sites │ │ ├── metadata.json │ │ └── sites.json │ │ ├── keysets │ │ └── metadata.json │ │ ├── services │ │ ├── metadata.json │ │ └── services.json │ │ ├── clients │ │ └── metadata.json │ │ ├── keyset_keys │ │ ├── metadata.json │ │ └── keyset_keys.json │ │ ├── service_links │ │ ├── metadata.json │ │ └── service_links.json │ │ ├── client_side_keypairs │ │ ├── metadata.json │ │ └── client_side_keypairs.json │ │ ├── cloud_encryption_keys │ │ ├── metadata.json │ │ └── cloud_encryption_keys.json │ │ ├── runtime_config │ │ └── metadata.json │ │ └── salts │ │ ├── metadataExpired.json │ │ ├── metadata.json │ │ ├── salts.txt.1670796729291 │ │ └── salts.txt.1745907348982 ├── test │ └── java │ │ └── com │ │ └── uid2 │ │ └── operator │ │ ├── benchmark │ │ └── BenchmarkRunner.java │ │ ├── RotatingKeysetProviderTest.java │ │ ├── RotatingKeysetKeyStoreTest.java │ │ ├── RotatingSiteStoreTest.java │ │ ├── RotatingClientSideKeypairStoreTest.java │ │ ├── ServiceStoreTest.java │ │ ├── ClientKeyProviderTest.java │ │ ├── MemoryAppender.java │ │ ├── ServiceLinkStoreTest.java │ │ ├── service │ │ ├── V4TokenUtilsTest.java │ │ └── TokenUtilsTest.java │ │ ├── ExtendedUIDOperatorVerticle.java │ │ ├── util │ │ └── DomainNameCheckUtilTest.java │ │ └── EUIDOperatorVerticleTest.java └── assembly │ └── static.xml ├── .gitignore ├── version.json ├── .trivyignore ├── .idea └── runConfigurations │ ├── uid2-operator_unit_tests.xml │ ├── uid2-operator_integrated.xml │ └── uid2-operator_standalone.xml ├── Dockerfile.nitro.builder ├── Dockerfile └── README.md /conf/config.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /.github/workflows/.version: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /js/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /scripts/aws/pipeline/EUID_VERSION: -------------------------------------------------------------------------------- 1 | 13 2 | -------------------------------------------------------------------------------- /scripts/aws/pipeline/UID2_VERSION: -------------------------------------------------------------------------------- 1 | 136 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.sh text eol=lf 3 | -------------------------------------------------------------------------------- /releases/previous_release.hash: -------------------------------------------------------------------------------- 1 | 26411a59c5c77c04854faa9d37814f2ca0cf3df3 -------------------------------------------------------------------------------- /scripts/aws/config-server/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==2.3.2 2 | Werkzeug==3.0.6 -------------------------------------------------------------------------------- /scripts/aws/requirements.txt: -------------------------------------------------------------------------------- 1 | requests[socks]==2.32.3 2 | boto3==1.35.59 3 | urllib3==2.6.0 4 | PyYAML===6.0.2 -------------------------------------------------------------------------------- /scripts/aws/eks-pod/server_al_2023/README.md: -------------------------------------------------------------------------------- 1 | This file is copied by the build to the root of the deployment artifacts. -------------------------------------------------------------------------------- /scripts/gcp-oidc/terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "load_balancer_ip" { 2 | value = module.gce_lb_http.external_ip 3 | } 4 | -------------------------------------------------------------------------------- /scripts/gcp-oidc/requirements.txt: -------------------------------------------------------------------------------- 1 | google-cloud-secret-manager>=2.16.0 2 | google-auth>=2.17.0 3 | google-api-core>=2.11.0 4 | packaging>=21.0 5 | -------------------------------------------------------------------------------- /scripts/aws/syslog-ng/client/syslog-ng-core_4.6.0-1_amd64.deb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IABTechLab/uid2-operator/HEAD/scripts/aws/syslog-ng/client/syslog-ng-core_4.6.0-1_amd64.deb -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/service/IEncryptionScheme.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.service; 2 | 3 | public interface IEncryptionScheme { 4 | public String getScheme(); 5 | } 6 | -------------------------------------------------------------------------------- /scripts/aws/syslog-ng/server_al_2023/ivykis-0.43-1.amzn2023.x86_64.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IABTechLab/uid2-operator/HEAD/scripts/aws/syslog-ng/server_al_2023/ivykis-0.43-1.amzn2023.x86_64.rpm -------------------------------------------------------------------------------- /scripts/aws/syslog-ng/server_al_2023/libnet-1.2-2.amzn2023.0.2.x86_64.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IABTechLab/uid2-operator/HEAD/scripts/aws/syslog-ng/server_al_2023/libnet-1.2-2.amzn2023.0.2.x86_64.rpm -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/sites/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version" : 1, 3 | "generated" : 1670883129, 4 | "sites" : { 5 | "location" : "/com.uid2.core/test/sites/sites.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/keysets/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "generated": 1609459200, 4 | "keysets": { 5 | "location": "/com.uid2.core/test/keysets/keysets.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/services/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "generated": 1609459200, 4 | "services": { 5 | "location": "/com.uid2.core/test/services/services.json" 6 | } 7 | } -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/clients/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version" : 1, 3 | "generated" : 1670883129, 4 | "client_keys" : { 5 | "location" : "/com.uid2.core/test/clients/clients.json" 6 | } 7 | } -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/TokenValidateResult.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | public enum TokenValidateResult { 4 | MATCH, 5 | MISMATCH, 6 | UNAUTHORIZED, 7 | INVALID_TOKEN, 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/monitoring/ILoggedStat.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.monitoring; 2 | 3 | public interface ILoggedStat { 4 | public String GetLogPrefix(); 5 | public Object GetValueToLog(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/keyset_keys/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "generated": 1609459200, 4 | "keyset_keys": { 5 | "location": "/com.uid2.core/test/keyset_keys/keyset_keys.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /scripts/aws/logrotate/logrotate: -------------------------------------------------------------------------------- 1 | # Run the minutely jobs 2 | SHELL=/bin/bash 3 | PATH=/sbin:/bin:/usr/sbin:/usr/bin 4 | MAILTO=root 5 | * * * * * root /usr/sbin/logrotate -s /var/lib/logrotate/logrotate.status /etc/logrotate.conf 6 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/service_links/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "generated": 1609459200, 4 | "service_links": { 5 | "location": "/com.uid2.core/test/service_links/service_links.json" 6 | } 7 | } -------------------------------------------------------------------------------- /scripts/aws/syslog-ng/server_al_2023/syslog-ng-4.7.1.104.gcc5a7d9-1.amzn2023.x86_64.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IABTechLab/uid2-operator/HEAD/scripts/aws/syslog-ng/server_al_2023/syslog-ng-4.7.1.104.gcc5a7d9-1.amzn2023.x86_64.rpm -------------------------------------------------------------------------------- /scripts/aws/syslog-ng/server_al_2023/syslog-ng-logrotate-4.7.1.104.gcc5a7d9-1.amzn2023.x86_64.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IABTechLab/uid2-operator/HEAD/scripts/aws/syslog-ng/server_al_2023/syslog-ng-logrotate-4.7.1.104.gcc5a7d9-1.amzn2023.x86_64.rpm -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/store/IConfigStore.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.store; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | 5 | public interface IConfigStore { 6 | RuntimeConfig getConfig(); 7 | void loadContent() throws Exception; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/client_side_keypairs/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "generated": 1609459200, 4 | "client_side_keypairs": { 5 | "location": "/com.uid2.core/test/client_side_keypairs/client_side_keypairs.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/cloud_encryption_keys/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "generated": 1620253519, 4 | "cloud_encryption_keys": { 5 | "location": "/com.uid2.core/test/cloud_encryption_keys/cloud_encryption_keys.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/benchmark/BenchmarkRunner.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.benchmark; 2 | 3 | public class BenchmarkRunner { 4 | public static void main(String[] args) throws Exception { 5 | org.openjdk.jmh.Main.main(args); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/check-stable-dependency.yaml: -------------------------------------------------------------------------------- 1 | name: Check Stable Dependencies 2 | on: [pull_request, workflow_dispatch] 3 | 4 | jobs: 5 | check_dependency: 6 | uses: IABTechLab/uid2-shared-actions/.github/workflows/shared-check-stable-dependency.yaml@v2 7 | secrets: inherit -------------------------------------------------------------------------------- /scripts/aws/logrotate/logrotateDaily: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | /usr/sbin/logrotate -s /var/lib/logrotate/logrotate.status /etc/logrotate.conf 4 | EXITVALUE=$? 5 | if [ $EXITVALUE != 0 ]; then 6 | /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]" 7 | fi 8 | exit 0 9 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | on: [pull_request, push, workflow_dispatch] 3 | 4 | jobs: 5 | build: 6 | uses: IABTechLab/uid2-shared-actions/.github/workflows/shared-build-and-test.yaml@v3 7 | with: 8 | java_version: 21 9 | secrets: inherit -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target* 2 | target/ 3 | target/* 4 | .idea* 5 | .idea/* 6 | .idea/ 7 | dependencies/ 8 | uid2-operator.iml 9 | build/** 10 | e2e-target 11 | .DS_Store 12 | */node_modules/* 13 | *.iml 14 | # Ignore generated credentials from google-github-actions/auth 15 | gha-creds-*.json 16 | -------------------------------------------------------------------------------- /scripts/aws/eks-pod/README.md: -------------------------------------------------------------------------------- 1 | ## Building the pod docker file 2 | 3 | Both the Pod and the AMI use the same syslog-ng packages. 4 | To build the pod locally, copy the files from: 5 | uid2-operator\scripts\aws\syslog-ng\server_al_2023 6 | to 7 | uid2-operator\scripts\aws\eks\pod\server_al_2023 8 | 9 | -------------------------------------------------------------------------------- /scripts/aws/uid2-operator-ami/uid2.pkrvars.hcl: -------------------------------------------------------------------------------- 1 | region = "us-east-1" 2 | identity_scope = "uid2" 3 | subnet_id = "subnet-03a2ae9b83ee4a1be" 4 | vpc_id = "vpc-056adf611333ebf06" 5 | ami_ou_arns = [ 6 | "arn:aws:organizations::155852253738:ou/o-v1vmbc3c9h/ou-96c8-2vbyb92d" 7 | ] 8 | -------------------------------------------------------------------------------- /scripts/aws/uid2-operator-ami/euid.pkrvars.hcl: -------------------------------------------------------------------------------- 1 | region = "eu-central-1" 2 | identity_scope = "euid" 3 | subnet_id = "subnet-0edbf47b073de1c79" 4 | vpc_id = "vpc-065000fb9082c6a90" 5 | ami_ou_arns = [ 6 | "arn:aws:organizations::155852253738:ou/o-v1vmbc3c9h/ou-96c8-2vbyb92d" 7 | ] 8 | -------------------------------------------------------------------------------- /scripts/aws/uid2-operator-ami/plugins.pkr.hcl: -------------------------------------------------------------------------------- 1 | packer { 2 | required_plugins { 3 | amazon = { 4 | version = ">= 1.0.0" 5 | source = "github.com/hashicorp/amazon" 6 | } 7 | ansible = { 8 | version = "~> 1" 9 | source = "github.com/hashicorp/ansible" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/services/services.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "service_id": 1, 4 | "site_id": 123, 5 | "name": "testName1", 6 | "roles": ["GENERATOR"] 7 | }, 8 | { 9 | "service_id": 2, 10 | "site_id": 126, 11 | "name": "testName2", 12 | "roles": ["MAPPER"] 13 | } 14 | ] -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/monitoring/IStatsCollectorQueue.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.monitoring; 2 | 3 | import com.uid2.operator.model.StatsCollectorMessageItem; 4 | import io.vertx.core.Vertx; 5 | 6 | public interface IStatsCollectorQueue { 7 | void enqueue(Vertx vertx, StatsCollectorMessageItem messageItem); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/service_links/service_links.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "link_id": "testId1", 4 | "service_id": 1, 5 | "site_id": 123, 6 | "name": "testName1" 7 | }, 8 | { 9 | "link_id": "testId2", 10 | "service_id": 2, 11 | "site_id": 123, 12 | "name": "testName2" 13 | } 14 | ] -------------------------------------------------------------------------------- /scripts/aws/logrotate/operator-logrotate.conf: -------------------------------------------------------------------------------- 1 | /var/log/operator.log 2 | { 3 | rotate 30 4 | daily 5 | maxsize 30M 6 | dateext dateformat -%Y-%m-%d-%s 7 | notifempty 8 | sharedscripts 9 | postrotate 10 | /usr/sbin/syslog-ng-ctl reload 11 | endscript 12 | } 13 | -------------------------------------------------------------------------------- /scripts/aws/syslog-ng/README.md: -------------------------------------------------------------------------------- 1 | # syslog-ng Documentation 2 | 3 | The documentation for configuring syslog-ng can be found here: [syslog-ng Administration Guide](https://support.oneidentity.com/technical-documents/syslog-ng-open-source-edition/3.38/administration-guide) 4 | 5 | The source repo is here: [syslog-ng](https://github.com/syslog-ng/syslog-ng) -------------------------------------------------------------------------------- /scripts/gcp-oidc/terraform/terraform.tfvars: -------------------------------------------------------------------------------- 1 | project_id = "uid2-test" 2 | service_account_name = "tf-test" 3 | uid_operator_image = "IMAGE_PLACEHOLDER" 4 | uid_operator_key = "" 5 | uid_operator_key_secret_name = "secret-operator-key" 6 | uid_deployment_env = "integ" 7 | debug_mode = true 8 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", "version": "5.62", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/heads/v\\d+(?:\\.\\d+)?$" ], "cloudBuild": { "setVersionVariables": true, "buildNumber": { "enabled": true, "includeCommitId": { "when": "always" } } } } 2 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/runtime_config/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version" : 1, 3 | "runtime_config": { 4 | "identity_token_expires_after_seconds": 3600, 5 | "refresh_token_expires_after_seconds": 86400, 6 | "refresh_identity_token_after_seconds": 900, 7 | "sharing_token_expiry_seconds": 2592000, 8 | "identity_environment": "test" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/privacy/tcf/TransparentConsentSpecialFeature.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.privacy.tcf; 2 | 3 | public enum TransparentConsentSpecialFeature { 4 | PreciseGeolocationData (1), 5 | ActiveScanDeviceCharacteristics (2); 6 | 7 | public final int value; 8 | private TransparentConsentSpecialFeature(int value) { 9 | this.value = value; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/vertx/ClientInputValidationException.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.vertx; 2 | 3 | public class ClientInputValidationException extends RuntimeException { 4 | public ClientInputValidationException(String message) { 5 | super(message); 6 | } 7 | 8 | public ClientInputValidationException(String message, Exception e) { 9 | super(message, e); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /scripts/aws/syslog-ng/syslog-ng-client.conf: -------------------------------------------------------------------------------- 1 | @version: 4.6 2 | @include "scl.conf" 3 | 4 | options { 5 | keep_hostname(no); 6 | chain_hostnames(no); 7 | }; 8 | 9 | source s_startup_file { 10 | file("/home/start.txt"); 11 | }; 12 | 13 | destination d_syslog_tcp { 14 | syslog("127.0.0.1" port(2011) transport("tcp")); 15 | }; 16 | 17 | log { 18 | source(s_startup_file); 19 | destination(d_syslog_tcp); 20 | }; 21 | -------------------------------------------------------------------------------- /scripts/aws/sockd.conf: -------------------------------------------------------------------------------- 1 | internal: 127.0.0.1 port = 3306 2 | external: ens5 3 | user.notprivileged: ec2-user 4 | clientmethod: none 5 | socksmethod: none 6 | logoutput: stderr 7 | 8 | client pass { 9 | from: 127.0.0.1/32 to: 127.0.0.1/32 10 | log: error connect # disconnect iooperation 11 | } 12 | 13 | socks pass { 14 | from: 127.0.0.1/32 to: 0.0.0.0/0 15 | command: bind connect 16 | protocol: tcp 17 | log: error 18 | } -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/IdentityMapResponseType.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | public enum IdentityMapResponseType { 4 | OPTOUT("optout"), 5 | INVALID_IDENTIFIER("invalid identifier"); 6 | 7 | private final String value; 8 | 9 | IdentityMapResponseType(String value) { 10 | this.value = value; 11 | } 12 | 13 | public String getValue() { 14 | return value; 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/VerificationResponse.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | public class VerificationResponse { 4 | private final String verificationToken; 5 | private final int verificationCode; 6 | 7 | public VerificationResponse(String verificationToken, int verificationCode) { 8 | this.verificationToken = verificationToken; 9 | this.verificationCode = verificationCode; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/service/ShutdownService.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.service; 2 | 3 | public class ShutdownService { 4 | public void Shutdown(int status) { 5 | System.exit(status); 6 | 7 | // according to the docs, this should not be reached as System.exit does not complete either normally or abruptly. 8 | // Added for safety 9 | throw new RuntimeException("JVM Requested to shut down"); 10 | } 11 | } -------------------------------------------------------------------------------- /scripts/azure-cc/deployment/gateway.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "vnetName": { 6 | "value": "unified-id-network" 7 | }, 8 | "gatewaySubnetName": { 9 | "value": "unified-id-subnet-gateway" 10 | }, 11 | "containerGroupIPs": { 12 | "value": [ 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/PublisherIdentity.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | public class PublisherIdentity { 4 | public final int siteId; 5 | public final int clientKeyId; 6 | public final long publisherId; 7 | 8 | public PublisherIdentity(int siteId, int clientKeyId, long publisherId) { 9 | this.siteId = siteId; 10 | this.clientKeyId = clientKeyId; 11 | this.publisherId = publisherId; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/util/HttpMediaType.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.util; 2 | 3 | public enum HttpMediaType { 4 | TEXT_PLAIN("text/plain"), 5 | APPLICATION_JSON("application/json"), 6 | APPLICATION_OCTET_STREAM("application/octet-stream"); 7 | 8 | private final String type; 9 | 10 | HttpMediaType(String type) { 11 | this.type = type; 12 | } 13 | 14 | public String getType() { 15 | return type; 16 | } 17 | } -------------------------------------------------------------------------------- /scripts/aws/uid2-operator-ami/build.pkr.hcl: -------------------------------------------------------------------------------- 1 | build { 2 | sources = ["source.amazon-ebs.linux"] 3 | 4 | provisioner "file" { 5 | source = "./artifacts" 6 | destination = "/tmp" 7 | } 8 | 9 | provisioner "ansible" { 10 | playbook_file = "./ansible/playbook.yml" 11 | extra_arguments = [ "--scp-extra-args", "'-O'" ] 12 | } 13 | 14 | post-processor "manifest" { 15 | output = "manifest.json" 16 | strip_path = true 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /scripts/aws/pipeline/aws_nitro_eif.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | # Build EIF 6 | dockerd & 7 | while (! docker stats --no-stream >/dev/null 2>&1); do 8 | # Docker takes a few seconds to initialize 9 | echo -n "." 10 | sleep 1 11 | done 12 | docker load -i $1.tar 13 | rm -f $1.tar 14 | nitro-cli build-enclave --docker-uri $1 --output-file $1.eif 15 | nitro-cli describe-eif --eif-path $1.eif | jq -r '.Measurements.PCR0' | xxd -r -p | base64 > pcr0.txt 16 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/salts/metadataExpired.json: -------------------------------------------------------------------------------- 1 | { 2 | "version" : 1, 3 | "generated" : 1670883129, 4 | "first_level" : "fOGY/aRE44peL23i+cE9MkJrzmEeNZZziNZBfq7qqk8=", 5 | "id_prefix" : "b", 6 | "id_secret" : "HF6Qz42HBbVHINxhh191dB09BCuTWyBkNtrNicO4ZCw=", 7 | "salts" : [{ 8 | "effective" : 1670796729291, 9 | "expires" : 1670796729292, 10 | "location" : "/com.uid2.core/test/salts/salts.txt.1670796729291", 11 | "size" : 5 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /scripts/aws/eks-pod/sockd_eks.conf: -------------------------------------------------------------------------------- 1 | #logoutput: stdout 2 | errorlog: stdout 3 | #debug: 2 4 | internal: 127.0.0.1 port = 3306 5 | external: eth0 6 | user.notprivileged: ec2-user 7 | clientmethod: none 8 | socksmethod: none 9 | 10 | client pass { 11 | from: 127.0.0.1/32 to: 127.0.0.1/32 12 | log: error # connect disconnect iooperation 13 | } 14 | 15 | socks pass { 16 | from: 127.0.0.1/32 to: 0.0.0.0/0 17 | command: bind connect 18 | protocol: tcp 19 | log: error 20 | } -------------------------------------------------------------------------------- /scripts/aws/proxies.host.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | socks5h-proxy: 4 | service: direct 5 | listen: vsock://-1:3305 6 | connect: tcp://127.0.0.1:3306 7 | 8 | operator-service: 9 | service: direct 10 | listen: tcp://0.0.0.0:80 11 | connect: vsock://42:8080 12 | 13 | operator-prometheus: 14 | service: direct 15 | listen: tcp://0.0.0.0:9080 16 | connect: vsock://42:9080 17 | 18 | syslogng: 19 | service: direct 20 | listen: vsock://-1:2011 21 | connect: tcp://127.0.0.1:2011 22 | -------------------------------------------------------------------------------- /scripts/aws/proxies.nitro.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | uid-operator-in: 4 | service: direct 5 | listen: vsock://-1:8080 6 | connect: tcp://127.0.0.1:8080 7 | 8 | prometheus-server: 9 | service: direct 10 | listen: vsock://-1:9080 11 | connect: tcp://127.0.0.1:9080 12 | 13 | socks5h-proxy: 14 | service: direct 15 | listen: tcp://127.0.0.1:3305 16 | connect: vsock://3:3305 17 | 18 | syslogng: 19 | service: direct 20 | listen: tcp://127.0.0.1:2011 21 | connect: vsock://3:2011 22 | -------------------------------------------------------------------------------- /scripts/aws/eks-pod/proxies.host.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | socks5h-proxy: 4 | service: direct 5 | listen: vsock://-1:3305 6 | connect: tcp://127.0.0.1:3306 7 | 8 | operator-service: 9 | service: direct 10 | listen: tcp://0.0.0.0:80 11 | connect: vsock://42:8080 12 | 13 | operator-prometheus: 14 | service: direct 15 | listen: tcp://0.0.0.0:9080 16 | connect: vsock://42:9080 17 | 18 | syslogng: 19 | service: direct 20 | listen: vsock://-1:2011 21 | connect: tcp://127.0.0.1:2011 22 | -------------------------------------------------------------------------------- /scripts/azure-cc/deployment/vault.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "operatorIdentifier": { 6 | "value": "uid-operator" 7 | }, 8 | "vaultName": { 9 | "value": "" 10 | }, 11 | "operatorKeyName": { 12 | "value": "operator-key" 13 | }, 14 | "operatorKeyValue": { 15 | "value": "" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.trivyignore: -------------------------------------------------------------------------------- 1 | # List any vulnerability that are to be accepted 2 | # See https://aquasecurity.github.io/trivy/v0.35/docs/vulnerability/examples/filter/ 3 | # for more details 4 | 5 | # UID2-4460 6 | CVE-2024-47535 exp:2026-01-01 7 | 8 | # UID2-6097 9 | CVE-2025-59375 exp:2025-12-15 10 | 11 | # UID2-6128 12 | CVE-2025-55163 exp:2025-10-30 13 | 14 | # UID2-6340 15 | CVE-2025-64720 exp:2026-06-05 16 | 17 | # UID2-6340 18 | CVE-2025-65018 exp:2026-06-05 19 | 20 | # UID2-6385 21 | CVE-2025-66293 exp:2026-06-15 -------------------------------------------------------------------------------- /scripts/aws/uid2operator.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Starter service for UID2 Operator Enclave 3 | After=network.target nitro-enclaves-allocator.service 4 | 5 | [Service] 6 | Type=oneshot 7 | RemainAfterExit=true 8 | StandardOutput=journal 9 | StandardError=journal 10 | SyslogIdentifier=uid2operator 11 | ExecStart=/opt/uid2operator/init/bin/python /opt/uid2operator/ec2.py 12 | ExecStop=/opt/uid2operator/init/bin/python /opt/uid2operator/ec2.py -o stop 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/client_side_keypairs/client_side_keypairs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "subscription_id": "4WvryDGbR5", 4 | "public_key": "UID2-X-L-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtXJdTSZAYHvoRDWiehMHoWF1BNPuqLs5w2ZHiAZ1IJc7O4/z0ojPTB0V+KYX/wxQK0hxx6kxCvHj335eI/ZQsQ==", 5 | "private_key": "UID2-Y-L-MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCC5kOSRVD+qhAZqf0L4LGbmAhdy5HpIptwXE72jxhwo6w==", 6 | "site_id": 123, 7 | "contact": "test@email.com", 8 | "created": 1692034991, 9 | "disabled": false 10 | } 11 | ] -------------------------------------------------------------------------------- /scripts/azure-cc/deployment/generate.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from hashlib import sha256 3 | 4 | def str_to_sha256(x: str) -> str: 5 | return sha256(x.encode('utf-8')).hexdigest() 6 | 7 | def print_data_sha256(data: str) -> str: 8 | print(str_to_sha256(data)) 9 | 10 | def print_data_sha256_stripped(data: str) -> str: 11 | print(str_to_sha256(data.strip())) 12 | 13 | def main(): 14 | with open(sys.argv[1], 'r') as file: 15 | data = file.read() 16 | 17 | print_data_sha256(data) 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /.idea/runConfigurations/uid2-operator_unit_tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | -------------------------------------------------------------------------------- /scripts/aws/config-server/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | import json 3 | import os 4 | 5 | app = Flask(__name__) 6 | 7 | @app.route('/getConfig', methods=['GET']) 8 | def get_config(): 9 | try: 10 | with open('/etc/secret/secret-value/config', 'r') as secret_file: 11 | secret_value = secret_file.read().strip() 12 | secret_value_json = json.loads(secret_value) 13 | return json.dumps(secret_value_json) 14 | except Exception as e: 15 | return str(e), 500 16 | 17 | if __name__ == '__main__': 18 | app.run(processes=8) 19 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/service/ITokenEncoder.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.service; 2 | 3 | import com.uid2.operator.model.AdvertisingToken; 4 | import com.uid2.operator.model.IdentityTokens; 5 | import com.uid2.operator.model.RefreshToken; 6 | 7 | import java.time.Instant; 8 | 9 | public interface ITokenEncoder { 10 | IdentityTokens encode(AdvertisingToken advertisingToken, RefreshToken refreshToken, Instant refreshFrom, Instant asOf); 11 | 12 | AdvertisingToken decodeAdvertisingToken(String base64String); 13 | 14 | RefreshToken decodeRefreshToken(String base64String); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/OperatorIdentity.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | public class OperatorIdentity { 4 | public final int siteId; 5 | public final OperatorType operatorType; 6 | public final int operatorVersion; 7 | public final int operatorKeyId; 8 | 9 | public OperatorIdentity(int siteId, OperatorType operatorType, int operatorVersion, int operatorKeyId) { 10 | this.siteId = siteId; 11 | this.operatorType = operatorType; 12 | this.operatorVersion = operatorVersion; 13 | this.operatorKeyId = operatorKeyId; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/VersionedToken.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import java.time.Instant; 4 | import java.util.Objects; 5 | import com.uid2.shared.model.TokenVersion; 6 | 7 | 8 | public abstract class VersionedToken { 9 | public final TokenVersion version; 10 | public final Instant createdAt; 11 | public final Instant expiresAt; 12 | 13 | public VersionedToken(TokenVersion version, Instant createdAt, Instant expiresAt) { 14 | this.version = version; 15 | this.createdAt = createdAt; 16 | this.expiresAt = expiresAt; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/OperatorType.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | public enum OperatorType { 4 | Service(1), 5 | Snowflake(17), 6 | Unknown(-1); 7 | 8 | public final int value; 9 | 10 | OperatorType(int value) { 11 | this.value = value; 12 | } 13 | 14 | public static OperatorType fromValue(int value) { 15 | switch (value) { 16 | case 1: 17 | return Service; 18 | case 17: 19 | return Snowflake; 20 | default: 21 | return Unknown; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/IdentityMapV3Request.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | 5 | public record IdentityMapV3Request( 6 | @JsonSetter(contentNulls = Nulls.FAIL) 7 | @JsonProperty("email") String[] email, 8 | 9 | @JsonSetter(contentNulls = Nulls.FAIL) 10 | @JsonProperty("email_hash") String[] email_hash, 11 | 12 | @JsonSetter(contentNulls = Nulls.FAIL) 13 | @JsonProperty("phone") String[] phone, 14 | 15 | @JsonSetter(contentNulls = Nulls.FAIL) 16 | @JsonProperty("phone_hash") String[] phone_hash 17 | ) { 18 | } 19 | -------------------------------------------------------------------------------- /scripts/aws/syslog-ng/syslog-ng-server.conf: -------------------------------------------------------------------------------- 1 | @version: 4.6 2 | @include "scl.conf" 3 | 4 | options { 5 | keep_hostname(no); 6 | create_dirs(yes); 7 | ts_format(iso); 8 | time_reopen(10); 9 | chain_hostnames(yes); 10 | }; 11 | 12 | source s_network { 13 | network( 14 | ip(0.0.0.0) 15 | port(2011) 16 | transport("tcp") 17 | flags(syslog-protocol) 18 | ); 19 | }; 20 | 21 | destination d_file { 22 | file( 23 | "/var/log/operator.log" 24 | dir-perm(0755) 25 | template-escape(no)); 26 | }; 27 | 28 | log { 29 | source(s_network); 30 | destination(d_file); 31 | }; 32 | -------------------------------------------------------------------------------- /src/assembly/static.xml: -------------------------------------------------------------------------------- 1 | 5 | static 6 | 7 | tar.gz 8 | 9 | false 10 | 11 | 12 | static 13 | static 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/salts/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version" : 1, 3 | "generated" : 1717548362, 4 | "first_level" : "fOGY/aRE44peL23i+cE9MkJrzmEeNZZziNZBfq7qqk8=", 5 | "id_prefix" : "b", 6 | "id_secret" : "HF6Qz42HBbVHINxhh191dB09BCuTWyBkNtrNicO4ZCw=", 7 | "salts" : [ 8 | { 9 | "effective" : 1670796729291, 10 | "expires" : 1766125493000, 11 | "location" : "/com.uid2.core/test/salts/salts.txt.1670796729291", 12 | "size" : 5 13 | },{ 14 | "effective" : 1745907348982, 15 | "expires" : 1766720293000, 16 | "location" : "/com.uid2.core/test/salts/salts.txt.1745907348982", 17 | "size" : 5 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | %d{HH:mm:ss.SSS} thread=%thread level=%-5level class=%logger{36} - %msg %ex%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/service/JsonParseUtils.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.service; 2 | 3 | import io.vertx.core.json.JsonArray; 4 | import io.vertx.core.json.JsonObject; 5 | import io.vertx.ext.web.RoutingContext; 6 | 7 | public class JsonParseUtils { 8 | public static JsonArray parseArray(JsonObject object, String key, RoutingContext rc) { 9 | JsonArray outArray; 10 | try { 11 | outArray = object.getJsonArray(key); 12 | } catch (ClassCastException e) { 13 | ResponseUtil.LogInfoAndSend400Response(rc, String.format("%s must be an array", key)); 14 | return null; 15 | } 16 | return outArray; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/azure-cc/deployment/vnet.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "vnetName": { 6 | "value": "unified-id-network" 7 | }, 8 | "computeSubnetName": { 9 | "value": "unified-id-subnet-operators" 10 | }, 11 | "gatewaySubnetName": { 12 | "value": "unified-id-subnet-gateway" 13 | }, 14 | "vnetAddressPrefix": { 15 | "value": "10.0.0.0/20" 16 | }, 17 | "computeSubnetPrefix": { 18 | "value": "10.0.0.0/24" 19 | }, 20 | "gatewaySubnetPrefix": { 21 | "value": "10.0.1.0/28" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /scripts/azure-cc/conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | %d{HH:mm:ss.SSS} thread=%thread level=%-5level class=%logger{36} - %msg %ex%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /scripts/aws/conf/logback-debug.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | REDACTED - S3 8 | \S+s3\.amazonaws\.com\/\S*X-Amz-Security-Token=\S+ 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /scripts/gcp-oidc/conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | %d{HH:mm:ss.SSS} thread=%thread level=%-5level class=%logger{36} - %msg %ex%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/IdentityType.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import com.uid2.operator.vertx.ClientInputValidationException; 4 | 5 | public enum IdentityType { 6 | Email(0), 7 | Phone(1); 8 | 9 | private final int value; 10 | 11 | IdentityType(int value) { 12 | this.value = value; 13 | } 14 | 15 | public int getValue() { 16 | return value; 17 | } 18 | 19 | public static IdentityType fromValue(int value) { 20 | return switch (value) { 21 | case 0 -> Email; 22 | case 1 -> Phone; 23 | default -> throw new ClientInputValidationException("Invalid valid for IdentityType: " + value); 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/salts/salts.txt.1670796729291: -------------------------------------------------------------------------------- 1 | 1000000,1806364800001,vgv1BwiNRCW7F3VcNXHlZh+7oHJ4G4gCshbGcVOLnss=,1814140800000,vgv1BwiNRCW7F3VcNXHlZh+7oHJ4G4gCshbGcVOLnsS=,,,,,, 2 | 1000001,1786924800001,vgv1BwiNRCW7F3VcNXHlZh+7oHJ4G4gCshbGcVOLnst=,1812844800000,,,,,,, 3 | 1000002,1798588800001,,1806364800000,,2100002,key12345key12345key12345key12340,salt1234salt1234salt1234salt1230,2000002,key12345key12345key12345key12345,salt1234salt1234salt1234salt1234 4 | 1000003,1795996800001,,1803772800000,,2000003,key12345key12345key12345key12346,salt1234salt1234salt1234salt1235,,, 5 | 1000004,1811548800001,,1819324800000,vgv1BwiNRCW7F3VcNXHlZh+7oHJ4G4gCshbGcVOLnsw=,2000004,key12345key12345key12345key12347,salt1234salt1234salt1234salt1236,,, 6 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/salts/salts.txt.1745907348982: -------------------------------------------------------------------------------- 1 | 1000000,1806364800001,vgv1BwiNRCW7F3VcNXHlZh+7oHJ4G4gCshbGcVOLnss=,1814140800000,vgv1BwiNRCW7F3VcNXHlZh+7oHJ4G4gCshbGcVOLnsS=,,,,,, 2 | 1000001,1786924800001,vgv1BwiNRCW7F3VcNXHlZh+7oHJ4G4gCshbGcVOLnst=,1812844800000,,,,,,, 3 | 1000002,1798588800001,,1806364800000,,2100002,key12345key12345key12345key12340,salt1234salt1234salt1234salt1230,2000002,key12345key12345key12345key12345,salt1234salt1234salt1234salt1234 4 | 1000003,1795996800001,,1803772800000,,2000003,key12345key12345key12345key12346,salt1234salt1234salt1234salt1235,,, 5 | 1000004,1811548800001,,1819324800000,vgv1BwiNRCW7F3VcNXHlZh+7oHJ4G4gCshbGcVOLnsw=,2000004,key12345key12345key12345key12347,salt1234salt1234salt1234salt1236,,, 6 | -------------------------------------------------------------------------------- /scripts/aws/eks-pod/server_al_2023/syslog-ng-server.conf: -------------------------------------------------------------------------------- 1 | @version: 4.6 2 | @include "scl.conf" 3 | 4 | options { 5 | keep_hostname(no); 6 | create_dirs(yes); 7 | ts_format(iso); 8 | time_reopen(10); 9 | chain_hostnames(no); 10 | }; 11 | 12 | source s_local { 13 | system(); 14 | internal(); 15 | }; 16 | 17 | source s_network { 18 | network( 19 | ip(0.0.0.0) 20 | port(2011) 21 | transport("tcp") 22 | flags(syslog-protocol) 23 | ); 24 | }; 25 | 26 | destination d_console { 27 | pipe( 28 | "/proc/1/fd/1" 29 | template("{\"@timestamp\":${MESSAGE}\n") 30 | ); 31 | }; 32 | 33 | log { 34 | source(s_local); 35 | source(s_network); 36 | destination(d_console); 37 | }; 38 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/monitoring/SiteClientVersionStat.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.monitoring; 2 | 3 | import java.util.Map; 4 | import java.util.Objects; 5 | 6 | public final class SiteClientVersionStat implements ILoggedStat { 7 | private final Integer siteId; 8 | private final Map versionCounts; 9 | 10 | public SiteClientVersionStat(Integer siteId, Map versionCounts) { 11 | this.siteId = siteId; 12 | this.versionCounts = versionCounts; 13 | } 14 | 15 | @Override 16 | public String GetLogPrefix() { 17 | return "version log; siteId=%d versions=".formatted(siteId); 18 | } 19 | 20 | @Override 21 | public Object GetValueToLog() { 22 | return versionCounts; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /scripts/azure-cc/deployment/operator.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "operatorIdentifier": { 6 | "value": "uid-operator" 7 | }, 8 | "vnetName": { 9 | "value": "unified-id-network" 10 | }, 11 | "computeSubnetName": { 12 | "value": "unified-id-subnet-operators" 13 | }, 14 | "vaultName": { 15 | "value": "" 16 | }, 17 | "operatorKeyName": { 18 | "value": "operator-key" 19 | }, 20 | "count": { 21 | "value": 2 22 | }, 23 | "deploymentEnvironment": { 24 | "value": "integ" 25 | }, 26 | "skipValidations": { 27 | "value": "false" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /scripts/aws/conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 127.0.0.1:2011 6 | 7 | 8 | REDACTED - S3 9 | \S+s3\.amazonaws\.com\/\S*X-Amz-Security-Token=\S+ 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/sites/sites.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 123, 4 | "name": "MegaTest Site", 5 | "enabled": true, 6 | "domain_names" : ["localhost", "uidapi.com"], 7 | "app_names": ["com.123.Game.App.android", "123456789", "com.123.Game.App.ios", "com.uid2.devapp"] 8 | }, 9 | { 10 | "id": 124, 11 | "name": "TestCorp Site", 12 | "enabled": true 13 | }, 14 | { 15 | "id": 125, 16 | "name": "LoremTestIpsum Site", 17 | "enabled": true 18 | }, 19 | { 20 | "id": 126, 21 | "name": "AWS Venice", 22 | "enabled": true 23 | }, 24 | { 25 | "id": 127, 26 | "name": "App Name Test Site", 27 | "enabled": true, 28 | "app_names" : ["com.UID2.operator.TEST", "13456789"], 29 | "domain_names" : ["example.com", "unifiedid.com"] 30 | } 31 | ] -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/IdentityVersion.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import com.uid2.operator.vertx.ClientInputValidationException; 4 | 5 | public enum IdentityVersion { 6 | V2(-1), // V2 raw UIDs don't encode version 7 | V3(0), 8 | V4(1); 9 | 10 | private final int value; 11 | 12 | IdentityVersion(int value) { 13 | this.value = value; 14 | } 15 | 16 | public int getValue() { 17 | return value; 18 | } 19 | 20 | public static IdentityVersion fromValue(int value) { 21 | return switch (value) { 22 | case -1 -> V2; 23 | case 0 -> V3; 24 | case 1 -> V4; 25 | default -> throw new ClientInputValidationException("Invalid valid for IdentityVersion: " + value); 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/RefreshToken.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import java.time.Instant; 4 | import com.uid2.shared.model.TokenVersion; 5 | 6 | public class RefreshToken extends VersionedToken { 7 | public final OperatorIdentity operatorIdentity; 8 | public final PublisherIdentity publisherIdentity; 9 | public final UserIdentity userIdentity; 10 | 11 | public RefreshToken(TokenVersion version, Instant createdAt, Instant expiresAt, OperatorIdentity operatorIdentity, 12 | PublisherIdentity publisherIdentity, UserIdentity userIdentity) { 13 | super(version, createdAt, expiresAt); 14 | this.operatorIdentity = operatorIdentity; 15 | this.publisherIdentity = publisherIdentity; 16 | this.userIdentity = userIdentity; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/RotatingKeysetProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator; 2 | 3 | import com.uid2.shared.cloud.EmbeddedResourceStorage; 4 | 5 | import com.uid2.shared.store.CloudPath; 6 | import com.uid2.shared.store.reader.RotatingKeysetProvider; 7 | import com.uid2.shared.store.scope.GlobalScope; 8 | import io.vertx.core.json.JsonObject; 9 | import org.junit.Test; 10 | 11 | public class RotatingKeysetProviderTest { 12 | @Test 13 | public void loadFromEmbeddedResourceStorage() throws Exception { 14 | RotatingKeysetProvider keysetProvider = new RotatingKeysetProvider( 15 | new EmbeddedResourceStorage(Main.class), 16 | new GlobalScope(new CloudPath("/com.uid2.core/test/keysets/metadata.json"))); 17 | 18 | JsonObject m = keysetProvider.getMetadata(); 19 | keysetProvider.loadContent(m); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/RotatingKeysetKeyStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator; 2 | 3 | import com.uid2.shared.cloud.EmbeddedResourceStorage; 4 | 5 | import com.uid2.shared.store.CloudPath; 6 | import com.uid2.shared.store.reader.RotatingKeysetKeyStore; 7 | import com.uid2.shared.store.scope.GlobalScope; 8 | import io.vertx.core.json.JsonObject; 9 | import org.junit.Test; 10 | 11 | public class RotatingKeysetKeyStoreTest { 12 | @Test 13 | public void loadFromEmbeddedResourceStorage() throws Exception { 14 | RotatingKeysetKeyStore keysetKeyStore = new RotatingKeysetKeyStore( 15 | new EmbeddedResourceStorage(Main.class), 16 | new GlobalScope(new CloudPath("/com.uid2.core/test/keyset_keys/metadata.json"))); 17 | 18 | JsonObject m = keysetKeyStore.getMetadata(); 19 | keysetKeyStore.loadContent(m); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/MappedIdentity.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | public class MappedIdentity { 4 | public static final MappedIdentity LogoutIdentity = new MappedIdentity(new byte[33], "", null, null); 5 | public final byte[] advertisingId; 6 | public final String bucketId; 7 | public final byte[] previousAdvertisingId; 8 | public final Long refreshFrom; 9 | 10 | public MappedIdentity(byte[] advertisingId, String bucketId, byte[] previousAdvertisingId, Long refreshFrom) { 11 | this.advertisingId = advertisingId; 12 | this.bucketId = bucketId; 13 | this.previousAdvertisingId = previousAdvertisingId; 14 | this.refreshFrom = refreshFrom; 15 | } 16 | 17 | public boolean isOptedOut() { 18 | return this.equals(LogoutIdentity) || this.bucketId == null || this.bucketId.isEmpty(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/OptoutCheckPolicy.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import com.uid2.operator.vertx.ClientInputValidationException; 4 | 5 | public enum OptoutCheckPolicy { 6 | DoNotRespect(0), 7 | RespectOptOut(1); 8 | 9 | public final int policy; 10 | OptoutCheckPolicy(int policy) { this.policy = policy; } 11 | 12 | public static OptoutCheckPolicy fromValue(int value) { 13 | switch (value) { 14 | case 0: return DoNotRespect; 15 | case 1: return RespectOptOut; 16 | default: throw new ClientInputValidationException("Invalid value for OptoutCheckPolicy: " + value); 17 | } 18 | } 19 | 20 | public static OptoutCheckPolicy defaultPolicy() { 21 | return DoNotRespect; 22 | } 23 | 24 | public static OptoutCheckPolicy respectOptOut() { 25 | return RespectOptOut; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/privacy/tcf/TransparentConsentParseResult.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.privacy.tcf; 2 | 3 | public class TransparentConsentParseResult { 4 | private final boolean success; 5 | private final String failureReason; 6 | private final TransparentConsent tcString; 7 | 8 | public TransparentConsentParseResult(TransparentConsent parsedConsent) { 9 | this.tcString = parsedConsent; 10 | this.success = true; 11 | this.failureReason = ""; 12 | } 13 | 14 | public TransparentConsentParseResult(String failureReason) { 15 | this.tcString = null; 16 | this.success = false; 17 | this.failureReason = failureReason; 18 | } 19 | 20 | public boolean isSuccess() { return success; } 21 | public TransparentConsent getTCString() { return tcString; } 22 | public String getFailureReason() { return failureReason; } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/store/BootstrapConfigStore.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.store; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | public class BootstrapConfigStore implements IConfigStore { 8 | private static final Logger logger = LoggerFactory.getLogger(BootstrapConfigStore.class); 9 | private final RuntimeConfig config; 10 | 11 | public BootstrapConfigStore(JsonObject config) { 12 | this.config = config.mapTo(RuntimeConfig.class); 13 | logger.info("Successfully loaded bootstrap config"); 14 | } 15 | 16 | @Override 17 | public RuntimeConfig getConfig() { 18 | return config; 19 | } 20 | 21 | @Override 22 | public void loadContent() throws Exception { 23 | logger.info("Remote Config FF is not enabled, bootstrap config was loaded."); 24 | return; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/MapRequest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import java.time.Instant; 4 | 5 | public final class MapRequest { 6 | public final UserIdentity userIdentity; 7 | public final OptoutCheckPolicy optoutCheckPolicy; 8 | public final Instant asOf; 9 | public final IdentityEnvironment identityEnvironment; 10 | 11 | public MapRequest( 12 | UserIdentity userIdentity, 13 | OptoutCheckPolicy optoutCheckPolicy, 14 | Instant asOf, 15 | IdentityEnvironment identityEnvironment) { 16 | this.userIdentity = userIdentity; 17 | this.optoutCheckPolicy = optoutCheckPolicy; 18 | this.asOf = asOf; 19 | this.identityEnvironment = identityEnvironment; 20 | } 21 | 22 | public boolean shouldCheckOptOut() { 23 | return optoutCheckPolicy.equals(OptoutCheckPolicy.RespectOptOut); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/RotatingSiteStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator; 2 | 3 | import com.uid2.shared.cloud.EmbeddedResourceStorage; 4 | import com.uid2.shared.store.CloudPath; 5 | import com.uid2.shared.store.reader.RotatingSiteStore; 6 | import com.uid2.shared.store.scope.GlobalScope; 7 | import io.vertx.core.json.JsonObject; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 11 | 12 | public class RotatingSiteStoreTest { 13 | @Test 14 | public void loadFromEmbeddedResourceStorage() throws Exception { 15 | RotatingSiteStore siteProvider = new RotatingSiteStore( 16 | new EmbeddedResourceStorage(Main.class), 17 | new GlobalScope(new CloudPath("/com.uid2.core/test/sites/metadata.json"))); 18 | 19 | JsonObject m = siteProvider.getMetadata(); 20 | assertDoesNotThrow(() -> siteProvider.loadContent(m)); 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/privacy/tcf/TransparentConsentPurpose.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.privacy.tcf; 2 | 3 | /** 4 | * Purposes Definitions 5 | * https://iabeurope.eu/iab-europe-transparency-consent-framework-policies/ 6 | */ 7 | public enum TransparentConsentPurpose { 8 | STORE_INFO_ON_DEVICE (1), 9 | SELECT_BASIC_ADS (2), 10 | CREATE_PERSONALIZED_ADS_PROFILE (3), 11 | SELECT_PERSONALIZED_ADS (4), 12 | CREATE_PERSONALIZED_CONTENT_PROFILE (5), 13 | SELECT_PERSONALIZED_CONTENT (6), 14 | MEASURE_AD_PERFORMANCE (7), 15 | MEASURE_CONTENT_PERFORMANCE (8), 16 | APPLY_MARKET_RESEARCH_GENERATE_INSIGHT (9), 17 | DEVELOP_AND_IMPROVE_PRODUCTS (10); 18 | 19 | public final int value; 20 | private TransparentConsentPurpose(int value) { 21 | this.value = value; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/vulnerability-scan-failure-notify.yaml: -------------------------------------------------------------------------------- 1 | name: Vulnerability Scan Failure Slack Notify 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | vulnerability_severity: 6 | description: The severity to fail the workflow if such vulnerability is detected. DO NOT override it unless a Jira ticket is raised. DO NOT use 'CRITICAL' unless a Jira ticket is raised. 7 | type: choice 8 | options: 9 | - CRITICAL,HIGH 10 | - CRITICAL,HIGH,MEDIUM 11 | - CRITICAL 12 | default: 'CRITICAL,HIGH' 13 | schedule: 14 | - cron: '0 16 * * *' # 9:00 AM GMT -7 15 | - cron: '0 0 * * *' # 5:00 PM GMT -7 16 | 17 | jobs: 18 | vulnerability-scan-failure-notify: 19 | uses: IABTechLab/uid2-shared-actions/.github/workflows/shared-vulnerability-scan-failure-notify.yaml@v3 20 | secrets: 21 | SLACK_WEBHOOK : ${{ secrets.SLACK_WEBHOOK }} 22 | with: 23 | scan_type : image 24 | java_version: "21" 25 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/IdentityRequest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | public final class IdentityRequest { 4 | public final PublisherIdentity publisherIdentity; 5 | public final UserIdentity userIdentity; 6 | public final OptoutCheckPolicy optoutCheckPolicy; 7 | public final IdentityEnvironment identityEnvironment; 8 | 9 | public IdentityRequest( 10 | PublisherIdentity publisherIdentity, 11 | UserIdentity userIdentity, 12 | OptoutCheckPolicy tokenGeneratePolicy, 13 | IdentityEnvironment identityEnvironment) { 14 | this.publisherIdentity = publisherIdentity; 15 | this.userIdentity = userIdentity; 16 | this.optoutCheckPolicy = tokenGeneratePolicy; 17 | this.identityEnvironment = identityEnvironment; 18 | } 19 | 20 | public boolean shouldCheckOptOut() { 21 | return optoutCheckPolicy.equals(OptoutCheckPolicy.RespectOptOut); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/CstgRequest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class CstgRequest { 6 | private String payload; 7 | private String iv; 8 | @JsonProperty("subscription_id") 9 | private String subscriptionId; 10 | @JsonProperty("public_key") 11 | private String publicKey; 12 | private long timestamp; 13 | 14 | @JsonProperty("app_name") 15 | private String appName; 16 | 17 | public String getPayload() { 18 | return payload; 19 | } 20 | 21 | public String getIv() { 22 | return iv; 23 | } 24 | 25 | public String getSubscriptionId() { 26 | return subscriptionId; 27 | } 28 | 29 | public String getPublicKey() { 30 | return publicKey; 31 | } 32 | 33 | public long getTimestamp() { 34 | return timestamp; 35 | } 36 | 37 | public String getAppName() { 38 | return appName; 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/store/IOptOutStore.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.store; 2 | 3 | import com.uid2.operator.model.UserIdentity; 4 | import io.vertx.core.AsyncResult; 5 | import io.vertx.core.Handler; 6 | 7 | import java.time.Instant; 8 | 9 | public interface IOptOutStore { 10 | 11 | /** 12 | * Get latest Opt-out record with respect to the UID (hashed identity) 13 | * @param firstLevelHashIdentity UID 14 | * @return The timestamp of latest opt-out record. NULL if no record. 15 | */ 16 | Instant getLatestEntry(UserIdentity firstLevelHashIdentity); 17 | 18 | long getOptOutTimestampByAdId(String adId); 19 | 20 | void addEntry(UserIdentity firstLevelHashIdentity, 21 | byte[] advertisingId, 22 | String uidTraceId, 23 | String uidInstanceId, 24 | String email, 25 | String phone, 26 | String clientIp, 27 | Handler> handler); 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/RotatingClientSideKeypairStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator; 2 | 3 | import com.uid2.shared.cloud.EmbeddedResourceStorage; 4 | import com.uid2.shared.store.CloudPath; 5 | import com.uid2.shared.store.reader.RotatingClientSideKeypairStore; 6 | import com.uid2.shared.store.scope.GlobalScope; 7 | import io.vertx.core.json.JsonObject; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 11 | 12 | public class RotatingClientSideKeypairStoreTest { 13 | @Test 14 | public void loadFromEmbeddedResourceStorage() throws Exception { 15 | RotatingClientSideKeypairStore keypairProvider = new RotatingClientSideKeypairStore( 16 | new EmbeddedResourceStorage(Main.class), 17 | new GlobalScope(new CloudPath("/com.uid2.core/test/client_side_keypairs/metadata.json"))); 18 | 19 | JsonObject m = keypairProvider.getMetadata(); 20 | assertDoesNotThrow(() -> keypairProvider.loadContent(m)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scripts/gcp-oidc/terraform/.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | crash.*.log 11 | 12 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 13 | # password, private keys, and other secrets. These should not be part of version 14 | # control as they are data points which are potentially sensitive and subject 15 | # to change depending on the environment. 16 | *.tfvars 17 | *.tfvars.json 18 | 19 | # Ignore override files as they are usually used to override resources locally and so 20 | # are not checked in 21 | override.tf 22 | override.tf.json 23 | *_override.tf 24 | *_override.tf.json 25 | 26 | # Include override files you do wish to add to version control using negated pattern 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* 31 | 32 | # Ignore CLI configuration files 33 | .terraformrc 34 | terraform.rc 35 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/IdentityScope.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import com.uid2.operator.vertx.ClientInputValidationException; 4 | 5 | public enum IdentityScope { 6 | UID2(0), 7 | EUID(1); 8 | 9 | private final int value; 10 | 11 | IdentityScope(int value) { 12 | this.value = value; 13 | } 14 | 15 | public int getValue() { 16 | return value; 17 | } 18 | 19 | public static IdentityScope fromValue(int value) { 20 | return switch (value) { 21 | case 0 -> UID2; 22 | case 1 -> EUID; 23 | default -> throw new ClientInputValidationException("Invalid value for IdentityScope: " + value); 24 | }; 25 | } 26 | 27 | public static IdentityScope fromString(String str) { 28 | return switch (str.toLowerCase()) { 29 | case "uid2" -> UID2; 30 | case "euid" -> EUID; 31 | default -> throw new ClientInputValidationException("Invalid string for IdentityScope: " + str); 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/IdentityConst.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator; 2 | 3 | import com.uid2.operator.service.EncodingUtils; 4 | 5 | public class IdentityConst { 6 | 7 | public static final String OptOutTokenIdentityForEmail = "optout@unifiedid.com"; 8 | public static final String OptOutTokenIdentityForPhone = "+00000000001"; 9 | public static final String ValidateIdentityForEmail = "validate@example.com"; 10 | public static final String ValidateIdentityForPhone = "+12345678901"; 11 | public static final byte[] ValidateIdentityForEmailHash = EncodingUtils.getSha256Bytes(IdentityConst.ValidateIdentityForEmail); 12 | public static final byte[] ValidateIdentityForPhoneHash = EncodingUtils.getSha256Bytes(IdentityConst.ValidateIdentityForPhone); 13 | public static final String OptOutIdentityForEmail = "optout@example.com"; 14 | public static final String OptOutIdentityForPhone = "+00000000000"; 15 | public static final String RefreshOptOutIdentityForEmail = "refresh-optout@example.com"; 16 | public static final String RefreshOptOutIdentityForPhone = "+00000000002"; 17 | 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /scripts/aws/syslog-ng/server_al_2023/pubkey.gpg: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQENBGYw4okBCADnaPDLp32IFXHhKE/e2kusIsiqieECEeLDcfYKT5VGYQD1yQeN 4 | prHxKm8U6dqqvmSd5ehphwrjeXY54XVWOlOT1FZpmFOLgi1XXn0syoMX/cJ2GcOV 5 | M8r4Z0CptDwp6PRvR+sLFGGENR3LueCi0RwHiw7M9jIxxuAuKH55IpWdCCshiFN5 6 | EE3AGeFbDERteyBywNZc3Q9OZXQ8y8jEp5CH8tbspQU+Qig/kGCjIWRnmkWFM6mT 7 | qdFtgWG4G6nhzvGwoD3J+IPPL02IV7Qywxl6dUBKhrLFPhorPXBSy43wlUZJY9IM 8 | kJK+EfpkSnY4v2tEfnakbHs8k1Tlw8f5exQhABEBAAG0UmN6YW5pa19zeXNsb2ct 9 | bmctYW1hem9uMjMgKE5vbmUpIDxjemFuaWsjc3lzbG9nLW5nLWFtYXpvbjIzQGNv 10 | cHIuZmVkb3JhaG9zdGVkLm9yZz6JAVgEEwEIAEIWIQSCmrP3ftEn1OdcMJPM0E5Y 11 | LFGYWQUCZjDiiQMbLwQFCQlmAYAFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AA 12 | CgkQzNBOWCxRmFm/6AgAxA1kWfcJZMLP1FdvLuadPw4QH2KYqOAIAnGb8+a08CSf 13 | Vwyhb3nFQ6h0K5sfVmrMNikmgu3cOssX/iLbjMJhBoITUkD8jpmQmO7oV6GPn1dT 14 | TIEIb1rYLtCu6/BHniyKNOPgZNmi80I+hTt5rWwOmfLlfMCGP/ob6iLs3yIAz4cE 15 | Oe5lFLMfn9IMmDJC9E5kVP9sjTUWjuW192lTTyyOdPx6m8h2dk+i//8SnYikNXEg 16 | djPNQNxf6pw0TvO8dn4qO4YNrQgfnap3s1QvVgL0tQHDINOs+t01brRMS49KhYF9 17 | y9OTIIeQw6nUaytecy5A7j5JUIaqxie0SFHqIrB35A== 18 | =IyUG 19 | -----END PGP PUBLIC KEY BLOCK----- 20 | -------------------------------------------------------------------------------- /scripts/aws/uid2-operator-ami/source.pkr.hcl: -------------------------------------------------------------------------------- 1 | source "amazon-ebs" "linux" { 2 | 3 | # source parameters 4 | source_ami_filter { 5 | filters = { 6 | name = "al2023-ami-2023*-x86_64" 7 | root-device-type = "ebs" 8 | } 9 | most_recent = true 10 | owners = ["amazon"] 11 | } 12 | 13 | # disable ami creation for testing 14 | # skip_create_ami = true 15 | 16 | # instance parameters 17 | ami_name = local.ami_name 18 | ami_ou_arns = var.ami_ou_arns 19 | instance_type = var.instance_type 20 | region = var.region 21 | subnet_id = var.subnet_id 22 | vpc_id = var.vpc_id 23 | 24 | # connection parameters 25 | communicator = var.communicator 26 | ssh_username = var.ssh_username 27 | ssh_interface = var.ssh_interface 28 | iam_instance_profile = var.iam_instance_profile 29 | 30 | tags = { 31 | Environment = var.env 32 | Service = var.service 33 | Version = var.version 34 | Name = local.ami_name 35 | Build = "packer" 36 | BuildTime = var.timestamp 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/UserIdentity.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import java.time.Instant; 4 | import java.util.Arrays; 5 | 6 | public class UserIdentity { 7 | public final IdentityScope identityScope; 8 | public final IdentityType identityType; 9 | public final byte[] id; 10 | public final int privacyBits; 11 | public final Instant establishedAt; 12 | public final Instant refreshedAt; 13 | 14 | public UserIdentity(IdentityScope identityScope, IdentityType identityType, byte[] id, int privacyBits, 15 | Instant establishedAt, Instant refreshedAt) { 16 | this.identityScope = identityScope; 17 | this.identityType = identityType; 18 | this.id = id; 19 | this.privacyBits = privacyBits; 20 | this.establishedAt = establishedAt; 21 | this.refreshedAt = refreshedAt; 22 | } 23 | 24 | public boolean matches(UserIdentity that) { 25 | return this.identityScope.equals(that.identityScope) && 26 | this.identityType.equals(that.identityType) && 27 | Arrays.equals(this.id, that.id); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scripts/azure-cc/conf/integ-uid2-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "sites_metadata_path": "https://core.uidapi.com/sites/refresh", 3 | "clients_metadata_path": "https://core.uidapi.com/clients/refresh", 4 | "keysets_metadata_path": "https://core.uidapi.com/key/keyset/refresh", 5 | "keyset_keys_metadata_path": "https://core.uidapi.com/key/keyset-keys/refresh", 6 | "client_side_keypairs_metadata_path": "https://core.uidapi.com/client_side_keypairs/refresh", 7 | "salts_metadata_path": "https://core.uidapi.com/salt/refresh", 8 | "services_metadata_path": "https://core.uidapi.com/services/refresh", 9 | "service_links_metadata_path": "https://core.uidapi.com/service_links/refresh", 10 | "optout_metadata_path": "https://optout.uidapi.com/optout/refresh", 11 | "core_attest_url": "https://core.uidapi.com/attest", 12 | "optout_api_uri": "https://optout.uidapi.com/optout/replicate", 13 | "cloud_encryption_keys_metadata_path": "https://core.uidapi.com/cloud_encryption_keys/retrieve", 14 | "runtime_config_metadata_path": "https://core.uidapi.com/operator/config", 15 | "optout_s3_folder": "uid-optout-integ/", 16 | "uid_instance_id_prefix": "unknown", 17 | "encrypted_files": true 18 | } 19 | -------------------------------------------------------------------------------- /scripts/aws/conf/euid-integ-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "sites_metadata_path": "https://core.integ.euid.eu/sites/refresh", 3 | "clients_metadata_path": "https://core.integ.euid.eu/clients/refresh", 4 | "keysets_metadata_path": "https://core.integ.euid.eu/key/keyset/refresh", 5 | "keyset_keys_metadata_path": "https://core.integ.euid.eu/key/keyset-keys/refresh", 6 | "client_side_keypairs_metadata_path": "https://core.integ.euid.eu/client_side_keypairs/refresh", 7 | "salts_metadata_path": "https://core.integ.euid.eu/salt/refresh", 8 | "services_metadata_path": "https://core.integ.euid.eu/services/refresh", 9 | "service_links_metadata_path": "https://core.integ.euid.eu/service_links/refresh", 10 | "optout_metadata_path": "https://optout.integ.euid.eu/optout/refresh", 11 | "core_attest_url": "https://core.integ.euid.eu/attest", 12 | "optout_api_uri": "https://optout.integ.euid.eu/optout/replicate", 13 | "cloud_encryption_keys_metadata_path": "https://core.integ.euid.eu/cloud_encryption_keys/retrieve", 14 | "runtime_config_metadata_path": "https://core.integ.euid.eu/operator/config", 15 | "optout_s3_folder": "optout/", 16 | "identity_scope": "euid", 17 | "encrypted_files": true 18 | } -------------------------------------------------------------------------------- /scripts/gcp-oidc/conf/integ-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "sites_metadata_path": "https://core.uidapi.com/sites/refresh", 3 | "clients_metadata_path": "https://core.uidapi.com/clients/refresh", 4 | "keysets_metadata_path": "https://core.uidapi.com/key/keyset/refresh", 5 | "keyset_keys_metadata_path": "https://core.uidapi.com/key/keyset-keys/refresh", 6 | "client_side_keypairs_metadata_path": "https://core.uidapi.com/client_side_keypairs/refresh", 7 | "salts_metadata_path": "https://core.uidapi.com/salt/refresh", 8 | "services_metadata_path": "https://core.uidapi.com/services/refresh", 9 | "service_links_metadata_path": "https://core.uidapi.com/service_links/refresh", 10 | "optout_metadata_path": "https://optout.uidapi.com/optout/refresh", 11 | "core_attest_url": "https://core.uidapi.com/attest", 12 | "cloud_encryption_keys_metadata_path": "https://core.uidapi.com/cloud_encryption_keys/retrieve", 13 | "runtime_config_metadata_path": "https://core.uidapi.com/operator/config", 14 | "optout_api_uri": "https://optout.uidapi.com/optout/replicate", 15 | "uid_instance_id_prefix": "unknown", 16 | "optout_s3_folder": "uid-optout-integ/", 17 | "encrypted_files": true 18 | } 19 | -------------------------------------------------------------------------------- /scripts/gcp-oidc/conf/prod-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "sites_metadata_path": "https://core.uidapi.com/sites/refresh", 3 | "clients_metadata_path": "https://core.uidapi.com/clients/refresh", 4 | "keysets_metadata_path": "https://core.uidapi.com/key/keyset/refresh", 5 | "keyset_keys_metadata_path": "https://core.uidapi.com/key/keyset-keys/refresh", 6 | "client_side_keypairs_metadata_path": "https://core.uidapi.com/client_side_keypairs/refresh", 7 | "salts_metadata_path": "https://core.uidapi.com/salt/refresh", 8 | "services_metadata_path": "https://core.uidapi.com/services/refresh", 9 | "service_links_metadata_path": "https://core.uidapi.com/service_links/refresh", 10 | "optout_metadata_path": "https://optout.uidapi.com/optout/refresh", 11 | "core_attest_url": "https://core.uidapi.com/attest", 12 | "cloud_encryption_keys_metadata_path": "https://core.uidapi.com/cloud_encryption_keys/retrieve", 13 | "runtime_config_metadata_path": "https://core.uidapi.com/operator/config", 14 | "optout_api_uri": "https://optout.uidapi.com/optout/replicate", 15 | "optout_s3_folder": "optout-v2/", 16 | "identity_token_expires_after_seconds": 259200, 17 | "uid_instance_id_prefix": "unknown", 18 | "encrypted_files": true 19 | } 20 | -------------------------------------------------------------------------------- /scripts/azure-cc/conf/prod-uid2-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "sites_metadata_path": "https://core.uidapi.com/sites/refresh", 3 | "clients_metadata_path": "https://core.uidapi.com/clients/refresh", 4 | "keysets_metadata_path": "https://core.uidapi.com/key/keyset/refresh", 5 | "keyset_keys_metadata_path": "https://core.uidapi.com/key/keyset-keys/refresh", 6 | "client_side_keypairs_metadata_path": "https://core.uidapi.com/client_side_keypairs/refresh", 7 | "salts_metadata_path": "https://core.uidapi.com/salt/refresh", 8 | "services_metadata_path": "https://core.uidapi.com/services/refresh", 9 | "service_links_metadata_path": "https://core.uidapi.com/service_links/refresh", 10 | "optout_metadata_path": "https://optout.uidapi.com/optout/refresh", 11 | "core_attest_url": "https://core.uidapi.com/attest", 12 | "cloud_encryption_keys_metadata_path": "https://core.uidapi.com/cloud_encryption_keys/retrieve", 13 | "runtime_config_metadata_path": "https://core.uidapi.com/operator/config", 14 | "optout_api_uri": "https://optout.uidapi.com/optout/replicate", 15 | "optout_s3_folder": "optout-v2/", 16 | "identity_token_expires_after_seconds": 259200, 17 | "uid_instance_id_prefix": "unknown", 18 | "encrypted_files": true 19 | } 20 | -------------------------------------------------------------------------------- /scripts/aws/conf/uid2-integ-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "core_attest_url": "https://core-integ.uidapi.com/attest", 3 | "optout_api_uri": "https://optout-integ.uidapi.com/optout/replicate", 4 | "sites_metadata_path": "https://core-integ.uidapi.com/sites/refresh", 5 | "clients_metadata_path": "https://core-integ.uidapi.com/clients/refresh", 6 | "client_side_keypairs_metadata_path": "https://core-integ.uidapi.com/client_side_keypairs/refresh", 7 | "keysets_metadata_path": "https://core-integ.uidapi.com/key/keyset/refresh", 8 | "keyset_keys_metadata_path": "https://core-integ.uidapi.com/key/keyset-keys/refresh", 9 | "salts_metadata_path": "https://core-integ.uidapi.com/salt/refresh", 10 | "services_metadata_path": "https://core-integ.uidapi.com/services/refresh", 11 | "service_links_metadata_path": "https://core-integ.uidapi.com/service_links/refresh", 12 | "optout_metadata_path": "https://optout-integ.uidapi.com/optout/refresh", 13 | "cloud_encryption_keys_metadata_path": "https://core-integ.uidapi.com/cloud_encryption_keys/retrieve", 14 | "runtime_config_metadata_path": "https://core-integ.uidapi.com/operator/config", 15 | "optout_s3_folder": "uid-optout-integ/", 16 | "identity_scope": "uid2", 17 | "encrypted_files": true 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/IdentityEnvironment.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.uid2.operator.vertx.ClientInputValidationException; 5 | 6 | public enum IdentityEnvironment { 7 | TEST(0), 8 | INTEG(1), 9 | PROD(2); 10 | 11 | private final int value; 12 | 13 | IdentityEnvironment(int value) { 14 | this.value = value; 15 | } 16 | 17 | public int getValue() { 18 | return value; 19 | } 20 | 21 | public static IdentityEnvironment fromValue(int value) { 22 | return switch (value) { 23 | case 0 -> TEST; 24 | case 1 -> INTEG; 25 | case 2 -> PROD; 26 | default -> throw new ClientInputValidationException("Invalid valid for IdentityEnvironment: " + value); 27 | }; 28 | } 29 | 30 | @JsonCreator 31 | public static IdentityEnvironment fromString(String value) { 32 | return switch (value.toLowerCase()) { 33 | case "test" -> TEST; 34 | case "integ" -> INTEG; 35 | case "prod" -> PROD; 36 | default -> throw new ClientInputValidationException("Invalid valid for IdentityEnvironment: " + value); 37 | }; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/reader/RotatingCloudEncryptionKeyApiProvider.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.reader; 2 | 3 | import com.uid2.shared.cloud.DownloadCloudStorage; 4 | import com.uid2.shared.model.CloudEncryptionKey; 5 | import com.uid2.shared.store.parser.CloudEncryptionKeyParser; 6 | import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider; 7 | import com.uid2.shared.store.scope.StoreScope; 8 | import io.vertx.core.json.JsonObject; 9 | 10 | import java.time.Instant; 11 | import java.util.*; 12 | 13 | public class RotatingCloudEncryptionKeyApiProvider extends RotatingCloudEncryptionKeyProvider { 14 | public RotatingCloudEncryptionKeyApiProvider(DownloadCloudStorage fileStreamProvider, StoreScope scope) { 15 | super(new ApiStoreReader<>(fileStreamProvider, scope, new CloudEncryptionKeyParser(), "cloud_encryption_keys")); 16 | } 17 | 18 | public RotatingCloudEncryptionKeyApiProvider(ApiStoreReader> reader) { 19 | super(reader); 20 | } 21 | 22 | @Override 23 | public long getVersion(JsonObject metadata) { 24 | // Since we are pulling from an api not a data file, we use the epoch time we got the keys as the version 25 | return Instant.now().getEpochSecond(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /scripts/gcp-oidc/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "project_id" { 2 | type = string 3 | } 4 | 5 | variable "region" { 6 | type = string 7 | default = "us-east1" 8 | } 9 | 10 | variable "network_name" { 11 | type = string 12 | default = "uid-operator" 13 | } 14 | 15 | variable "service_account_name" { 16 | type = string 17 | } 18 | 19 | variable "uid_operator_image" { 20 | type = string 21 | } 22 | 23 | variable "uid_deployment_env" { 24 | type = string 25 | 26 | validation { 27 | condition = contains(["integ", "prod"], var.uid_deployment_env) 28 | error_message = "Allowed values for uid_deployment_env are \"integ\" or \"prod\"." 29 | } 30 | } 31 | 32 | variable "uid_operator_key" { 33 | type = string 34 | } 35 | 36 | variable "uid_operator_key_secret_name" { 37 | type = string 38 | default = "secret-operator-key" 39 | } 40 | 41 | variable "max_replicas" { 42 | type = number 43 | default = 5 44 | } 45 | 46 | variable "min_replicas" { 47 | type = number 48 | default = 1 49 | } 50 | 51 | variable "debug_mode" { 52 | type = bool 53 | default = false 54 | } 55 | 56 | variable "ssl" { 57 | type = bool 58 | default = false 59 | } 60 | 61 | variable "ssl_certificate_domains" { 62 | type = list(string) 63 | default = [] 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/AdvertisingToken.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import java.time.Instant; 4 | import com.uid2.shared.model.TokenVersion; 5 | 6 | public class AdvertisingToken extends VersionedToken { 7 | public final OperatorIdentity operatorIdentity; 8 | public final PublisherIdentity publisherIdentity; 9 | public final UserIdentity userIdentity; 10 | public Integer siteKeyId; 11 | 12 | public AdvertisingToken(TokenVersion version, Instant createdAt, Instant expiresAt, OperatorIdentity operatorIdentity, 13 | PublisherIdentity publisherIdentity, UserIdentity userIdentity) { 14 | super(version, createdAt, expiresAt); 15 | this.operatorIdentity = operatorIdentity; 16 | this.publisherIdentity = publisherIdentity; 17 | this.userIdentity = userIdentity; 18 | this.siteKeyId = null; 19 | } 20 | 21 | public AdvertisingToken(TokenVersion version, Instant createdAt, Instant expiresAt, OperatorIdentity operatorIdentity, 22 | PublisherIdentity publisherIdentity, UserIdentity userIdentity, Integer siteKeyId) { 23 | this(version, createdAt, expiresAt, operatorIdentity, publisherIdentity, userIdentity); 24 | this.siteKeyId = siteKeyId; 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /Dockerfile.nitro.builder: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ENV enclave_platform="aws-nitro" 4 | 5 | # install build-essential, openjdk, maven, git 6 | RUN apt-get update -y \ 7 | && apt-get install -y curl -y build-essential pkg-config libssl-dev cmake openjdk-21-jdk maven git \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | # install rust 11 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y 12 | 13 | ENV PATH="/root/.cargo/bin:${PATH}" 14 | 15 | WORKDIR /build 16 | COPY src ./src 17 | COPY static ./static 18 | COPY ./pom.xml ./pom.xml 19 | 20 | # build operator jar and save package version 21 | RUN mvn package -B -Paws -DskipTests=true \ 22 | && (mvn help:evaluate -Dexpression=project.version | grep -e '^[1-9][^\[]' > ./package.version) 23 | 24 | # build libjnsm.so 25 | RUN git clone https://github.com/IABTechLab/uid2-attestation-aws.git \ 26 | && (cd uid2-attestation-aws/jnsm; cargo build --lib --release; cd ../..) \ 27 | && cp uid2-attestation-aws/jnsm/target/release/libjnsm.so . 28 | 29 | # build vsockpx 30 | RUN git clone https://github.com/IABTechLab/uid2-aws-enclave-vsockproxy.git \ 31 | && mkdir uid2-aws-enclave-vsockproxy/build \ 32 | && (cd uid2-aws-enclave-vsockproxy/build; cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo; make; cd ../..) \ 33 | && cp uid2-aws-enclave-vsockproxy/build/vsock-bridge/src/vsock-bridge ./vsockpx 34 | -------------------------------------------------------------------------------- /conf/integ-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_instances": 1, 3 | "sites_metadata_path": "http://localhost:8088/sites/refresh", 4 | "clients_metadata_path": "http://localhost:8088/clients/refresh", 5 | "keysets_metadata_path": "http://localhost:8088/key/keyset/refresh", 6 | "keyset_keys_metadata_path": "http://localhost:8088/key/keyset-keys/refresh", 7 | "client_side_keypairs_metadata_path": "http://localhost:8088/client_side_keypairs/refresh", 8 | "salts_metadata_path": "http://localhost:8088/salt/refresh", 9 | "services_metadata_path": "http://localhost:8088/services/refresh", 10 | "service_links_metadata_path": "http://localhost:8088/service_links/refresh", 11 | "optout_metadata_path": "http://localhost:8081/optout/refresh", 12 | "core_attest_url": "http://localhost:8088/attest", 13 | "core_api_token": "trusted-partner-key", 14 | "optout_api_token": "test-operator-key", 15 | "optout_api_uri": "http://localhost:8081/optout/replicate", 16 | "cloud_encryption_keys_metadata_path": "http://localhost:8088/cloud_encryption_keys/retrieve", 17 | "runtime_config_metadata_path": "http://localhost:8088/operator/config", 18 | "salts_expired_shutdown_hours": 12, 19 | "store_refresh_stale_shutdown_hours": 12, 20 | "operator_type": "public", 21 | "disable_optout_token": true, 22 | "enable_remote_config": false, 23 | "uid_instance_id_prefix": "local-operator" 24 | } -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/service/IUIDOperatorService.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.service; 2 | 3 | import com.uid2.operator.model.*; 4 | import com.uid2.shared.model.SaltEntry; 5 | import io.vertx.core.AsyncResult; 6 | import io.vertx.core.Handler; 7 | 8 | import java.time.Duration; 9 | import java.time.Instant; 10 | import java.util.List; 11 | 12 | public interface IUIDOperatorService { 13 | IdentityTokens generateIdentity(IdentityRequest request, Duration refreshIdentityAfter, Duration refreshExpiresAfter, Duration identityExpiresAfter); 14 | 15 | RefreshResponse refreshIdentity(RefreshToken token, Duration refreshIdentityAfter, Duration refreshExpiresAfter, Duration identityExpiresAfter, IdentityEnvironment env); 16 | 17 | MappedIdentity mapIdentity(MapRequest request); 18 | 19 | @Deprecated 20 | MappedIdentity map(UserIdentity userIdentity, Instant asOf, IdentityEnvironment env); 21 | 22 | List getModifiedBuckets(Instant sinceTimestamp); 23 | 24 | void invalidateTokensAsync(UserIdentity userIdentity, Instant asOf, String uidTraceId, IdentityEnvironment env, 25 | String email, String phone, String clientIp, 26 | Handler> handler); 27 | 28 | TokenValidateResult validateAdvertisingToken(int participantSiteId, String advertisingToken, UserIdentity userIdentity, Instant asOf, IdentityEnvironment env); 29 | } 30 | -------------------------------------------------------------------------------- /.github/actions/install_az_cli/action.yaml: -------------------------------------------------------------------------------- 1 | name: 'Install Azure CLI' 2 | description: 'Install Azure CLI' 3 | runs: 4 | using: 'composite' 5 | steps: 6 | - name: uninstall azure-cli 7 | shell: bash 8 | run: | 9 | sudo apt-get remove -y azure-cli 10 | 11 | - name: install azure-cli 2.61.0 12 | shell: bash 13 | run: | 14 | sudo apt-get update 15 | sudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-release 16 | sudo mkdir -p /etc/apt/keyrings 17 | curl -sLS https://packages.microsoft.com/keys/microsoft.asc | 18 | gpg --dearmor | sudo tee /etc/apt/keyrings/microsoft.gpg > /dev/null 19 | sudo chmod go+r /etc/apt/keyrings/microsoft.gpg 20 | AZ_DIST=$(lsb_release -cs) 21 | echo "Types: deb 22 | URIs: https://packages.microsoft.com/repos/azure-cli/ 23 | Suites: ${AZ_DIST} 24 | Components: main 25 | Architectures: $(dpkg --print-architecture) 26 | Signed-by: /etc/apt/keyrings/microsoft.gpg" | sudo tee /etc/apt/sources.list.d/azure-cli.sources 27 | sudo apt-get update 28 | sudo apt-get install azure-cli 29 | 30 | apt-cache policy azure-cli 31 | # Obtain the currently installed distribution 32 | AZ_DIST=$(lsb_release -cs) 33 | # Store an Azure CLI version of choice 34 | AZ_VER=2.61.0 35 | # Install a specific version 36 | sudo apt-get install azure-cli=${AZ_VER}-1~${AZ_DIST} --allow-downgrades 37 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/KeyManagerSnapshot.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import com.uid2.shared.auth.Keyset; 4 | import com.uid2.shared.auth.KeysetSnapshot; 5 | import com.uid2.shared.model.KeysetKey; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class KeyManagerSnapshot { 11 | private final KeysetSnapshot keysetSnapshot; 12 | private final Map keysetIdToKeyset; 13 | private final List keysetKeys; 14 | private final KeysetKey masterKey; 15 | private final Keyset defaultKeyset; 16 | 17 | KeyManagerSnapshot(KeysetSnapshot keysetSnapshot, Map keysetIdToKeyset, List keysetKeys, KeysetKey masterKey, Keyset defaultKeyset) { 18 | this.keysetSnapshot = keysetSnapshot; 19 | this.keysetIdToKeyset = keysetIdToKeyset; 20 | this.keysetKeys = keysetKeys; 21 | this.masterKey = masterKey; 22 | this.defaultKeyset = defaultKeyset; 23 | } 24 | 25 | public Map getAllKeysets() { 26 | return this.keysetIdToKeyset; 27 | } 28 | 29 | public List getKeysetKeys() { 30 | return this.keysetKeys; 31 | } 32 | 33 | public KeysetKey getMasterKey() { 34 | return this.masterKey; 35 | } 36 | 37 | public Keyset getDefaultKeyset() { 38 | return this.defaultKeyset; 39 | } 40 | 41 | public KeysetSnapshot getKeysetSnapshot() { 42 | return this.keysetSnapshot; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /scripts/aws/pipeline/amazonlinux2023.Dockerfile: -------------------------------------------------------------------------------- 1 | # https://gist.github.com/toricls/e17c7f2f1c024cc368dcd860804194f5 2 | FROM amazonlinux:2023 3 | 4 | RUN dnf update -y 5 | # systemd is not a hard requirement for Amazon ECS Anywhere, but the installation script currently only supports systemd to run. 6 | # Amazon ECS Anywhere can be used without systemd, if you set up your nodes and register them into your ECS cluster **without** the installation script. 7 | RUN dnf -y groupinstall "Development Tools" \ 8 | && dnf -y install systemd vim-common wget git tar libstdc++-static.x86_64 cmake cmake3 aws-nitro-enclaves-cli aws-nitro-enclaves-cli-devel \ 9 | && dnf clean all 10 | 11 | RUN systemctl enable docker 12 | 13 | RUN wget https://www.inet.no/dante/files/dante-1.4.3.tar.gz \ 14 | && echo "418a065fe1a4b8ace8fbf77c2da269a98f376e7115902e76cda7e741e4846a5d dante-1.4.3.tar.gz" > dante_checksum \ 15 | && sha256sum --check dante_checksum \ 16 | && tar -xf dante-1.4.3.tar.gz \ 17 | && cd dante-1.4.3; ./configure; make; cd .. \ 18 | && cp dante-1.4.3/sockd/sockd ./ \ 19 | && rm -rf dante-1.4.3 dante-1.4.3.tar.gz 20 | 21 | RUN git clone https://github.com/IABTechLab/uid2-aws-enclave-vsockproxy.git \ 22 | && mkdir uid2-aws-enclave-vsockproxy/build \ 23 | && cd uid2-aws-enclave-vsockproxy/build; cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo; make; cd ../.. \ 24 | && cp uid2-aws-enclave-vsockproxy/build/vsock-bridge/src/vsock-bridge ./vsockpx \ 25 | && rm -rf uid2-aws-enclave-vsockproxy 26 | 27 | COPY ./scripts/aws/pipeline/aws_nitro_eif.sh /aws_nitro_eif.sh 28 | 29 | CMD ["/usr/sbin/init"] 30 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/util/PrivacyBits.java: -------------------------------------------------------------------------------- 1 | 2 | package com.uid2.operator.util; 3 | 4 | public class PrivacyBits { 5 | 6 | private static final int BIT_LEGACY = 0; 7 | private static final int BIT_CSTG = 1; 8 | private static final int BIT_CSTG_OPTOUT = 2; 9 | //DO NOT REUSE THIS BIT. DEPRECATED from UID2-2904 work 10 | private static final int BIT_CSTG_OPTOUT_RESPONSE_DEPRECATED = 3; 11 | 12 | private int bits = 0; 13 | 14 | public static PrivacyBits fromInt(int privacyBits) { return new PrivacyBits(privacyBits); } 15 | 16 | public PrivacyBits() { 17 | } 18 | 19 | public PrivacyBits(int bits) { 20 | this.bits = bits; 21 | } 22 | 23 | public int getAsInt() { 24 | return bits; 25 | } 26 | 27 | public void setClientSideTokenGenerate() { setBit(BIT_CSTG); } 28 | public boolean isClientSideTokenGenerated() { 29 | return isBitSet(BIT_CSTG); 30 | } 31 | 32 | public void setClientSideTokenGenerateOptout() { setBit(BIT_CSTG_OPTOUT); } 33 | public boolean isClientSideTokenOptedOut() { 34 | return isBitSet(BIT_CSTG_OPTOUT); 35 | } 36 | 37 | public void setLegacyBit() { 38 | setBit(BIT_LEGACY);//unknown why this bit is set in https://github.com/IABTechLab/uid2-operator/blob/dbab58346e367c9d4122ad541ff9632dc37bd410/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java#L534 39 | } 40 | 41 | private void setBit(int position) { 42 | bits |= (1 << position); 43 | } 44 | private boolean isBitSet(int position) { 45 | return (bits & (1 << position)) != 0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # sha from https://hub.docker.com/layers/library/eclipse-temurin/21.0.8_9-jre-alpine-3.22/images/sha256-3408c45e1faee20e4e68808939a75f87efa469b927d20e12309689ead053daba 2 | FROM eclipse-temurin@sha256:4ca7eff3ab0ef9b41f5fefa35efaeda9ed8d26e161e1192473b24b3a6c348aef 3 | 4 | WORKDIR /app 5 | EXPOSE 8080 6 | 7 | ARG JAR_NAME=uid2-operator 8 | ARG JAR_VERSION=1.0.0-SNAPSHOT 9 | ARG IMAGE_VERSION=1.0.0.unknownhash 10 | ENV JAR_NAME=${JAR_NAME} 11 | ENV JAR_VERSION=${JAR_VERSION} 12 | ENV IMAGE_VERSION=${IMAGE_VERSION} 13 | ENV REGION=us-east-2 14 | 15 | COPY ./target/${JAR_NAME}-${JAR_VERSION}-jar-with-dependencies.jar /app/${JAR_NAME}-${JAR_VERSION}.jar 16 | COPY ./target/${JAR_NAME}-${JAR_VERSION}-sources.jar /app 17 | COPY ./target/${JAR_NAME}-${JAR_VERSION}-static.tar.gz /app/static.tar.gz 18 | COPY ./conf/default-config.json /app/conf/ 19 | COPY ./conf/*.xml /app/conf/ 20 | 21 | RUN tar xzvf /app/static.tar.gz --no-same-owner --no-same-permissions && rm -f /app/static.tar.gz 22 | 23 | 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 && mkdir -p /app/pod_terminating && chmod 777 -R /app/pod_terminating 24 | USER uid2-operator 25 | 26 | CMD java \ 27 | -XX:MaxRAMPercentage=95 -XX:-UseCompressedOops -XX:+PrintFlagsFinal -XX:-OmitStackTraceInFastThrow \ 28 | -Djava.security.egd=file:/dev/./urandom \ 29 | -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory \ 30 | -Dlogback.configurationFile=/app/conf/logback.xml \ 31 | -jar ${JAR_NAME}-${JAR_VERSION}.jar 32 | -------------------------------------------------------------------------------- /.idea/runConfigurations/uid2-operator_integrated.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /.idea/runConfigurations/uid2-operator_standalone.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/ServiceStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator; 2 | 3 | import com.uid2.shared.auth.Role; 4 | import com.uid2.shared.cloud.EmbeddedResourceStorage; 5 | import com.uid2.shared.model.Service; 6 | import com.uid2.shared.store.CloudPath; 7 | import com.uid2.shared.store.reader.RotatingServiceStore; 8 | import com.uid2.shared.store.scope.GlobalScope; 9 | import io.vertx.core.json.JsonObject; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Set; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | 18 | public class ServiceStoreTest { 19 | @Test 20 | public void loadFromEmbeddedResourceStorage() throws Exception { 21 | RotatingServiceStore serviceProvider = new RotatingServiceStore( 22 | new EmbeddedResourceStorage(Main.class), 23 | new GlobalScope(new CloudPath("/com.uid2.core/test/services/metadata.json"))); 24 | 25 | JsonObject m = serviceProvider.getMetadata(); 26 | assertDoesNotThrow(() -> serviceProvider.loadContent(m)); 27 | 28 | List services = new ArrayList<>(serviceProvider.getAllServices()); 29 | assertEquals(2, services.size()); 30 | 31 | Service service = serviceProvider.getService(2); 32 | assertNotNull(service); 33 | assertEquals("testName2", service.getName()); 34 | 35 | Service s1 = new Service(1, 123, "testName1", Set.of(Role.GENERATOR)); 36 | Service s2 = new Service(2, 126, "testName2", Set.of(Role.MAPPER)); 37 | assertTrue(services.contains(s1)); 38 | assertTrue(services.contains(s2)); 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /scripts/aws/conf/uid2-prod-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_instances": 4, 3 | "sites_metadata_path": "https://core-prod.uidapi.com/sites/refresh", 4 | "clients_metadata_path": "https://core-prod.uidapi.com/clients/refresh", 5 | "keysets_metadata_path": "https://core-prod.uidapi.com/key/keyset/refresh", 6 | "keyset_keys_metadata_path": "https://core-prod.uidapi.com/key/keyset-keys/refresh", 7 | "client_side_keypairs_metadata_path": "https://core-prod.uidapi.com/client_side_keypairs/refresh", 8 | "salts_metadata_path": "https://core-prod.uidapi.com/salt/refresh", 9 | "services_metadata_path": "https://core-prod.uidapi.com/services/refresh", 10 | "service_links_metadata_path": "https://core-prod.uidapi.com/service_links/refresh", 11 | "optout_metadata_path": "https://optout-prod.uidapi.com/optout/refresh", 12 | "core_attest_url": "https://core-prod.uidapi.com/attest", 13 | "cloud_encryption_keys_metadata_path": "https://core-prod.uidapi.com/cloud_encryption_keys/retrieve", 14 | "runtime_config_metadata_path": "https://core-prod.uidapi.com/operator/config", 15 | "core_api_token": "your-api-token", 16 | "optout_s3_path_compat": false, 17 | "optout_api_uri": "https://optout-prod.uidapi.com/optout/replicate", 18 | "optout_api_token": "your-api-token", 19 | "enclave_platform": "aws-nitro", 20 | "optout_synthetic_logs_enabled": false, 21 | "optout_synthetic_logs_count": 0, 22 | "optout_inmem_cache": true, 23 | "optout_s3_folder": "optout-v2/", 24 | "identity_scope": "uid2", 25 | "identity_token_expires_after_seconds": 259200, 26 | "refresh_token_expires_after_seconds": 2592000, 27 | "refresh_identity_token_after_seconds": 3600, 28 | "encrypted_files": true 29 | } -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/StatsCollectorMessageItem.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | public class StatsCollectorMessageItem { 4 | private String path; 5 | private String referer; 6 | private String apiContact; 7 | private Integer siteId; 8 | private String clientVersion; 9 | 10 | //USED by json serial 11 | public StatsCollectorMessageItem() { 12 | } 13 | 14 | public StatsCollectorMessageItem(String path, String referer, String apiContact, Integer siteId, String clientVersion) { 15 | this.path = path; 16 | this.referer = referer; 17 | this.apiContact = apiContact; 18 | this.siteId = siteId; 19 | this.clientVersion = clientVersion; 20 | } 21 | 22 | 23 | public void setReferer(String referer) { 24 | this.referer = referer; 25 | } 26 | 27 | public String getReferer() { 28 | return referer; 29 | } 30 | 31 | public void setPath(String path) { 32 | this.path = path; 33 | } 34 | 35 | public String getPath() { 36 | return path; 37 | } 38 | 39 | public String getApiContact() { 40 | return apiContact; 41 | } 42 | 43 | public void setApiContact(String apiContact) { 44 | this.apiContact = apiContact; 45 | } 46 | 47 | public Integer getSiteId() { 48 | return siteId; 49 | } 50 | 51 | public void setSiteId(Integer siteId) { 52 | this.siteId = siteId; 53 | } 54 | 55 | public String getClientVersion() { 56 | return clientVersion; 57 | } 58 | 59 | public void setClientVersion(String clientVersion) { 60 | this.clientVersion = clientVersion; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/vertx/Endpoints.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.vertx; 2 | 3 | import java.util.Set; 4 | import java.util.stream.Collectors; 5 | import java.util.stream.Stream; 6 | 7 | public enum Endpoints { 8 | OPS_HEALTHCHECK("/ops/healthcheck"), 9 | V2_TOKEN_GENERATE("/v2/token/generate"), 10 | V2_TOKEN_REFRESH("/v2/token/refresh"), 11 | V2_TOKEN_VALIDATE("/v2/token/validate"), 12 | V2_IDENTITY_BUCKETS("/v2/identity/buckets"), 13 | V2_IDENTITY_MAP("/v2/identity/map"), 14 | V2_KEY_LATEST("/v2/key/latest"), 15 | V2_KEY_SHARING("/v2/key/sharing"), 16 | V2_KEY_BIDSTREAM("/v2/key/bidstream"), 17 | V2_TOKEN_LOGOUT("/v2/token/logout"), 18 | V2_OPTOUT_STATUS("/v2/optout/status"), 19 | V2_TOKEN_CLIENTGENERATE("/v2/token/client-generate"), 20 | 21 | V3_IDENTITY_MAP("/v3/identity/map"), 22 | 23 | EUID_SDK_1_0_0("/static/js/euid-sdk-1.0.0.js"), 24 | OPENID_SDK_1_0("/static/js/openid-sdk-1.0.js"), 25 | UID2_ESP_0_0_1A("/static/js/uid2-esp-0.0.1a.js"), 26 | UID2_SDK_0_0_1A("/static/js/uid2-sdk-0.0.1a.js"), 27 | UID2_SDK_0_0_1A_SOURCE("/static/js/uid2-sdk-0.0.1a-source.ts"), 28 | UID2_SDK_0_0_1B("/static/js/uid2-sdk-0.0.1b.js"), 29 | UID2_SDK_1_0_0("/static/js/uid2-sdk-1.0.0.js"), 30 | UID2_SDK_2_0_0("/static/js/uid2-sdk-2.0.0.js") 31 | ; 32 | private final String path; 33 | 34 | Endpoints(final String path) { 35 | this.path = path; 36 | } 37 | 38 | public static Set pathSet() { 39 | return Stream.of(Endpoints.values()).map(Endpoints::toString).collect(Collectors.toSet()); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return path; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/ClientKeyProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator; 2 | 3 | import com.uid2.operator.service.EncodingUtils; 4 | import com.uid2.shared.cloud.EmbeddedResourceStorage; 5 | import com.uid2.shared.store.CloudPath; 6 | import com.uid2.shared.store.reader.RotatingClientKeyProvider; 7 | import com.uid2.shared.store.scope.GlobalScope; 8 | import io.vertx.core.json.JsonObject; 9 | import org.junit.Test; 10 | 11 | import java.security.NoSuchAlgorithmException; 12 | import java.security.SecureRandom; 13 | 14 | 15 | public class ClientKeyProviderTest { 16 | @Test 17 | public void generateNewClientKeys() throws NoSuchAlgorithmException { 18 | if (System.getenv("SLOW_DEV_URANDOM") != null) { 19 | System.err.println("ignore this test since environment variable SLOW_DEV_URANDOM is set"); 20 | return; 21 | } 22 | System.out.println("Java VM property java.security.egd: " + System.getProperty("java.security.egd")); 23 | SecureRandom random = SecureRandom.getInstanceStrong(); 24 | byte[] bytes = new byte[32]; 25 | for (int i = 0; i < 10; ++i) { 26 | random.nextBytes(bytes); 27 | System.out.format("client key: %s\n", EncodingUtils.toBase64String(bytes)); 28 | } 29 | } 30 | 31 | @Test 32 | public void loadFromEmbeddedResourceStorage() throws Exception { 33 | RotatingClientKeyProvider fileProvider = new RotatingClientKeyProvider( 34 | new EmbeddedResourceStorage(Main.class), 35 | new GlobalScope(new CloudPath("/com.uid2.core/test/clients/metadata.json"))); 36 | 37 | JsonObject m = fileProvider.getMetadata(); 38 | fileProvider.loadContent(m); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /scripts/aws/conf/euid-prod-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_instances": 4, 3 | "sites_metadata_path": "https://core.prod.euid.eu/sites/refresh", 4 | "clients_metadata_path": "https://core.prod.euid.eu/clients/refresh", 5 | "keysets_metadata_path": "https://core.prod.euid.eu/key/keyset/refresh", 6 | "keyset_keys_metadata_path": "https://core.prod.euid.eu/key/keyset-keys/refresh", 7 | "client_side_keypairs_metadata_path": "https://core.prod.euid.eu/client_side_keypairs/refresh", 8 | "salts_metadata_path": "https://core.prod.euid.eu/salt/refresh", 9 | "services_metadata_path": "https://core.prod.euid.eu/services/refresh", 10 | "service_links_metadata_path": "https://core.prod.euid.eu/service_links/refresh", 11 | "optout_metadata_path": "https://optout.prod.euid.eu/optout/refresh", 12 | "core_attest_url": "https://core.prod.euid.eu/attest", 13 | "cloud_encryption_keys_metadata_path": "https://core.prod.euid.eu/cloud_encryption_keys/retrieve", 14 | "runtime_config_metadata_path": "https://core.prod.euid.eu/operator/config", 15 | "core_api_token": "your-api-token", 16 | "optout_s3_path_compat": false, 17 | "optout_api_uri": "https://optout.prod.euid.eu/optout/replicate", 18 | "optout_api_token": "your-api-token", 19 | "enclave_platform": "aws-nitro", 20 | "optout_synthetic_logs_enabled": false, 21 | "optout_synthetic_logs_count": 0, 22 | "optout_inmem_cache": true, 23 | "optout_s3_folder": "optout/", 24 | "identity_token_expires_after_seconds": 259200, 25 | "refresh_token_expires_after_seconds": 2592000, 26 | "refresh_identity_token_after_seconds": 3600, 27 | "identity_scope": "euid", 28 | "refresh_token_v3": true, 29 | "enable_phone_support": true, 30 | "enable_v1_phone_support": false, 31 | "enable_v2_encryption": true, 32 | "encrypted_files": true 33 | } -------------------------------------------------------------------------------- /scripts/aws/eks-pod/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazonlinux:2023 2 | RUN dnf check-update && dnf update && dnf install nmap-ncat libxcrypt-compat python3 aws-nitro-enclaves-cli logrotate iproute net-tools python3.11 python3.11-pip shadow-utils -y 3 | 4 | ARG IVYKIS_RPM="ivykis-0.43-1.amzn2023.x86_64.rpm" 5 | ARG LIBNET_RPM="libnet-1.2-2.amzn2023.0.2.x86_64.rpm" 6 | ARG PUBKEY="pubkey.gpg" 7 | ARG SYSLOG_NG_RPM="syslog-ng-4.7.1.104.gcc5a7d9-1.amzn2023.x86_64.rpm" 8 | ARG SYSLOG_NG_LOGROTATE_RPM="syslog-ng-logrotate-4.7.1.104.gcc5a7d9-1.amzn2023.x86_64.rpm" 9 | 10 | COPY ./sockd /home/ 11 | COPY ./sockd_eks.conf /etc/sockd.conf 12 | COPY ./vsockpx /home 13 | COPY ./${IVYKIS_RPM} /home/syslog-ng/ 14 | COPY ./${LIBNET_RPM} /home/syslog-ng/ 15 | COPY ./${PUBKEY} /home/syslog-ng/ 16 | COPY ./${SYSLOG_NG_RPM} /home/syslog-ng/ 17 | COPY ./${SYSLOG_NG_LOGROTATE_RPM} /home/syslog-ng/ 18 | 19 | # Install syslog-ng 20 | RUN rpmkeys --import /home/syslog-ng/${PUBKEY} && \ 21 | rpm -U /home/syslog-ng/${IVYKIS_RPM} && \ 22 | rpm -U /home/syslog-ng/${LIBNET_RPM} && \ 23 | rpm -U /home/syslog-ng/${SYSLOG_NG_RPM} && \ 24 | rpm -U /home/syslog-ng/${SYSLOG_NG_LOGROTATE_RPM} && \ 25 | rpm -e gpg-pubkey-2c519859-6630e289 && \ 26 | rm -r /home/syslog-ng 27 | 28 | COPY ./syslog-ng-server.conf /etc/syslog-ng/syslog-ng.conf 29 | 30 | COPY ./entrypoint.sh /home/ 31 | COPY ./uid2operator.eif /home/ 32 | COPY ./proxies.host.yaml /home/proxies.host.yaml 33 | 34 | RUN chmod +x /home/vsockpx && chmod +x /home/entrypoint.sh && chmod +x /home/sockd 35 | 36 | COPY ./app.py /home/config-server/ 37 | COPY ./requirements.txt /home/config-server/ 38 | RUN python3 -m venv config-server 39 | RUN config-server/bin/pip3 install -r /home/config-server/requirements.txt 40 | 41 | RUN useradd ec2-user 42 | 43 | CMD ["/home/entrypoint.sh"] -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/service/RoutingContextReader.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.service; 2 | 3 | import com.uid2.shared.Const; 4 | import com.uid2.shared.auth.IAuthorizable; 5 | import com.uid2.shared.middleware.AuthMiddleware; 6 | import io.vertx.ext.web.RoutingContext; 7 | 8 | // Encapsulates non-obvious reading patterns for Routing Context 9 | public class RoutingContextReader { 10 | private final RoutingContext context; 11 | 12 | public RoutingContextReader(RoutingContext context) { 13 | this.context = context; 14 | } 15 | 16 | public String getOrigin() { return this.context.request().getHeader("origin"); } 17 | 18 | public String getReferer() { return this.context.request().getHeader("referer"); } 19 | 20 | public Integer getSiteId() { 21 | final Integer siteId = context.get(Const.RoutingContextData.SiteId); 22 | if (siteId != null) { 23 | return siteId; 24 | } 25 | 26 | final IAuthorizable profile = AuthMiddleware.getAuthClient(context); 27 | if (profile != null) { 28 | return profile.getSiteId(); 29 | } 30 | 31 | return null; 32 | } 33 | 34 | public String getContact() { 35 | IAuthorizable authClient = AuthMiddleware.getAuthClient(context); 36 | if (authClient == null) { 37 | return null; 38 | } 39 | return authClient.getContact(); 40 | } 41 | 42 | public String getPath() { 43 | return context.request().path(); 44 | } 45 | 46 | public String getServiceName() { 47 | return context.get(SecureLinkValidatorService.SERVICE_NAME, ""); 48 | } 49 | 50 | public String getLinkName() { 51 | return context.get(SecureLinkValidatorService.SERVICE_LINK_NAME, ""); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /conf/local-e2e-docker-private-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_instances": 1, 3 | "storage_mock": false, 4 | "core_attest_url": "http://core:8088/attest", 5 | "core_api_token": "OPLCLAjLRWcVlCDl9+BbwR38gzxYdiWFa751ynWLuI7JU4iA=", 6 | "sites_metadata_path": "http://core:8088/sites/refresh", 7 | "clients_metadata_path": "http://core:8088/clients/refresh", 8 | "client_side_keypairs_metadata_path": "http://core:8088/client_side_keypairs/refresh", 9 | "keys_metadata_path": "http://core:8088/key/refresh", 10 | "keys_acl_metadata_path": "http://core:8088/key/acl/refresh", 11 | "keysets_metadata_path": "http://core:8088/key/keyset/refresh", 12 | "keyset_keys_metadata_path": "http://core:8088/key/keyset-keys/refresh", 13 | "salts_metadata_path": "http://core:8088/salt/refresh", 14 | "cloud_encryption_keys_metadata_path": "http://core:8088/cloud_encryption_keys/retrieve", 15 | "runtime_config_metadata_path": "http://core:8088/operator/config", 16 | "encrypted_files": true, 17 | "identity_token_expires_after_seconds": 3600, 18 | "refresh_token_expires_after_seconds": 86400, 19 | "refresh_identity_token_after_seconds": 900, 20 | "refresh_token_v3": true, 21 | "identity_v3": false, 22 | "identity_scope": "uid2", 23 | "enable_v2_encryption": true, 24 | "client_side_token_generate": false, 25 | "validate_service_links": false, 26 | "optout_s3_bucket": "test-optout-bucket", 27 | "optout_s3_folder": "optout-v2/", 28 | "optout_metadata_path": "/optout/refresh", 29 | "optout_api_uri": "http://optout:8081/optout/replicate", 30 | "optout_delta_rotate_interval": 60, 31 | "cloud_refresh_interval": 30, 32 | "salts_expired_shutdown_hours": 12, 33 | "store_refresh_stale_shutdown_hours": 12, 34 | "operator_type": "private", 35 | "enable_remote_config": true, 36 | "uid_instance_id_prefix": "local-private-operator" 37 | } 38 | -------------------------------------------------------------------------------- /conf/default-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_verbose": true, 3 | "service_instances": 4, 4 | "core_s3_bucket": null, 5 | "core_attest_url": null, 6 | "core_api_token": null, 7 | "storage_mock": false, 8 | "optout_s3_bucket": null, 9 | "optout_s3_folder": "optout/", 10 | "optout_s3_path_compat": false, 11 | "optout_data_dir": "/opt/uid2/operator-optout/", 12 | "optout_api_token": null, 13 | "optout_api_uri": null, 14 | "optout_bloom_filter_size": 8192, 15 | "optout_delta_rotate_interval": 300, 16 | "optout_delta_backtrack_in_days": 1, 17 | "optout_partition_interval": 86400, 18 | "optout_max_partitions": 30, 19 | "optout_heap_default_capacity": 8192, 20 | "optout_status_api_enabled": true, 21 | "optout_status_max_request_size": 5000, 22 | "cloud_download_threads": 8, 23 | "cloud_upload_threads": 2, 24 | "cloud_refresh_interval": 60, 25 | "sites_metadata_path": "sites/metadata.json", 26 | "clients_metadata_path": "clients/metadata.json", 27 | "client_side_keypairs_metadata_path": "client_side_keypairs/metadata.json", 28 | "keysets_metadata_path": "keysets/metadata.json", 29 | "keyset_keys_metadata_path": "keyset_keys/metadata.json", 30 | "salts_metadata_path": "salts/metadata.json", 31 | "services_metadata_path": "services/metadata.json", 32 | "service_links_metadata_path": "service_links/metadata.json", 33 | "cloud_encryption_keys_metadata_path": "cloud_encryption_keys/metadata.json", 34 | "runtime_config_metadata_path": "runtime_config/metadata.json", 35 | "encrypted_files": false, 36 | "optout_metadata_path": null, 37 | "optout_inmem_cache": false, 38 | "enclave_platform": null, 39 | "failure_shutdown_wait_hours": 120, 40 | "sharing_token_expiry_seconds": 2592000, 41 | "operator_type": "public", 42 | "enable_remote_config": true, 43 | "uid_instance_id_prefix": "local-operator" 44 | } 45 | -------------------------------------------------------------------------------- /scripts/aws/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://hub.docker.com/layers/library/eclipse-temurin/21-jre-jammy/images/sha256-3186dd88a59659929855a6bb785b0528c812eb0b03d97fd6e2221526547ed322?context=explore 2 | FROM eclipse-temurin:21-jre-jammy 3 | 4 | WORKDIR /app 5 | 6 | ARG JAR_NAME=uid2-operator 7 | ARG JAR_VERSION=1.0.0 8 | ARG IMAGE_VERSION=1.0.0.unknownhash 9 | ARG IDENTITY_SCOPE=UID2 10 | 11 | ENV JAR_NAME=${JAR_NAME} 12 | ENV JAR_VERSION=${JAR_VERSION} 13 | ENV IMAGE_VERSION=${IMAGE_VERSION} 14 | ENV IDENTITY_SCOPE=${IDENTITY_SCOPE} 15 | ENV ENCLAVE_ENVIRONMENT="aws-nitro" 16 | ENV UID2_CONFIG_SECRET_KEY="uid2-operator-config-key" 17 | 18 | COPY ./syslog-ng-core_4.6.0-1_amd64.deb /app/dep/ 19 | COPY ./syslog-ng-ose-pub.asc /app/dep/ 20 | 21 | RUN echo "deb http://security.ubuntu.com/ubuntu focal-security main" | tee -a /etc/apt/sources.list \ 22 | && apt update -y \ 23 | && apt install -y pkg-config libssl-dev libssl1.1 net-tools curl jq netcat python3 python3-pip libcap2 libivykis0 libjson-c5 libnet1 libwrap0 \ 24 | && apt-key add /app/dep/syslog-ng-ose-pub.asc \ 25 | && apt-get install /app/dep/syslog-ng-core_4.6.0-1_amd64.deb \ 26 | && rm -rf /var/lib/apt/lists/* \ 27 | && apt-key del 6694369F 28 | RUN pip3 install boto3==1.16.9 29 | 30 | COPY ./target/${JAR_NAME}-${JAR_VERSION}-jar-with-dependencies.jar /app/${JAR_NAME}-${JAR_VERSION}.jar 31 | COPY ./static /app/static 32 | COPY ./libjnsm.so /app/lib/ 33 | COPY ./vsockpx /app/ 34 | COPY ./entrypoint.sh /app/ 35 | COPY ./proxies.nitro.yaml /app/ 36 | COPY ./conf/default-config.json /app/conf/ 37 | COPY ./conf/*.json /app/conf/ 38 | COPY ./conf/*.xml /app/conf/ 39 | COPY ./syslog-ng-client.conf /etc/syslog-ng/syslog-ng.conf 40 | 41 | RUN chmod +x /app/vsockpx && chmod +x /app/entrypoint.sh 42 | 43 | 44 | CMD ["/app/entrypoint.sh"] 45 | -------------------------------------------------------------------------------- /scripts/gcp-oidc/Dockerfile: -------------------------------------------------------------------------------- 1 | # sha from https://hub.docker.com/layers/library/eclipse-temurin/21.0.8_9-jre-alpine-3.22/images/sha256-3408c45e1faee20e4e68808939a75f87efa469b927d20e12309689ead053daba 2 | FROM eclipse-temurin@sha256:4ca7eff3ab0ef9b41f5fefa35efaeda9ed8d26e161e1192473b24b3a6c348aef 3 | 4 | LABEL "tee.launch_policy.allow_env_override"="API_TOKEN_SECRET_NAME,DEPLOYMENT_ENVIRONMENT,CORE_BASE_URL,OPTOUT_BASE_URL,DEBUG_MODE,SKIP_VALIDATIONS" 5 | LABEL "tee.launch_policy.log_redirect"="always" 6 | 7 | # Install Packages 8 | RUN apk update && apk add --no-cache jq python3 py3-pip && \ 9 | python3 -m venv /venv && \ 10 | . /venv/bin/activate && \ 11 | pip install --no-cache-dir google-cloud-secret-manager google-auth google-api-core packaging && \ 12 | rm -rf /var/cache/apk/* 13 | 14 | WORKDIR /app 15 | EXPOSE 8080 16 | EXPOSE 9080 17 | 18 | ARG JAR_NAME=uid2-operator 19 | ARG JAR_VERSION=1.0.0-SNAPSHOT 20 | ARG IMAGE_VERSION=1.0.0.unknownhash 21 | ENV JAR_NAME=${JAR_NAME} 22 | ENV JAR_VERSION=${JAR_VERSION} 23 | ENV IMAGE_VERSION=${IMAGE_VERSION} 24 | ENV REGION=default 25 | 26 | COPY ./target/${JAR_NAME}-${JAR_VERSION}-jar-with-dependencies.jar /app/${JAR_NAME}-${JAR_VERSION}.jar 27 | COPY ./target/${JAR_NAME}-${JAR_VERSION}-sources.jar /app 28 | COPY ./target/${JAR_NAME}-${JAR_VERSION}-static.tar.gz /app/static.tar.gz 29 | COPY ./conf/*.json /app/conf/ 30 | COPY ./conf/*.xml /app/conf/ 31 | 32 | RUN tar xzvf /app/static.tar.gz --no-same-owner --no-same-permissions && rm -f /app/static.tar.gz 33 | 34 | COPY ./gcp.py /app/ 35 | COPY ./confidential_compute.py /app 36 | RUN chmod a+x /app/gcp.py 37 | 38 | RUN 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 39 | 40 | CMD ["/venv/bin/python", "/app/gcp.py"] 41 | -------------------------------------------------------------------------------- /js/setupJest.js: -------------------------------------------------------------------------------- 1 | expect.extend({ 2 | toBeNonEmptyString(received) { 3 | expect(typeof received).toBe('string'); 4 | expect(received).not.toEqual(''); 5 | return { 6 | pass: true, 7 | message: () => 'Expected non-empty string' 8 | }; 9 | } 10 | }); 11 | 12 | expect.extend({ 13 | toBeInInitialisingState(uid2) { 14 | expect(uid2.getAdvertisingToken()).toBeUndefined(); 15 | expect(uid2.isLoginRequired()).toBeUndefined(); 16 | 17 | return { 18 | pass: true, 19 | message: () => 'Expected getAdvertisingToken() returns undefined and isLoginRequired() returns undefined' 20 | }; 21 | }, 22 | 23 | toBeInAvailableState(uid2, expectedAdvertisingToken) { 24 | if (expectedAdvertisingToken) { 25 | expect(uid2.getAdvertisingToken()).toBe(expectedAdvertisingToken); 26 | } else if (uid2.getAdvertisingToken() !== '') { 27 | expect(uid2.getAdvertisingToken()).toBeNonEmptyString(); 28 | } 29 | 30 | expect(uid2.isLoginRequired()).toEqual(false); 31 | 32 | return { 33 | pass: true, 34 | message: () => 'Expected getAdvertisingToken() returns a token and isLoginRequired() returns false' 35 | }; 36 | }, 37 | 38 | toBeInTemporarilyUnavailableState(uid2) { 39 | expect(uid2.getAdvertisingToken()).toBeUndefined(); 40 | expect(uid2.isLoginRequired()).toEqual(false); 41 | 42 | return { 43 | pass: true, 44 | message: () => 'Expected getAdvertisingToken() returns undefined and isLoginRequired() returns false' 45 | }; 46 | }, 47 | 48 | toBeInUnavailableState(uid2) { 49 | expect(uid2.getAdvertisingToken()).toBeUndefined(); 50 | expect(uid2.isLoginRequired()).toEqual(true); 51 | 52 | return { 53 | pass: true, 54 | message: () => 'Expected getAdvertisingToken() returns undefined and isLoginRequired() returns true' 55 | }; 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /scripts/aws/conf/default-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_verbose": true, 3 | "service_instances": 4, 4 | "core_s3_bucket": null, 5 | "core_attest_url": null, 6 | "core_api_token": null, 7 | "storage_mock": false, 8 | "optout_s3_bucket": null, 9 | "optout_s3_folder": "optout/", 10 | "optout_s3_path_compat": false, 11 | "optout_data_dir": "/opt/uid2/operator-optout/", 12 | "optout_api_token": null, 13 | "optout_api_uri": null, 14 | "optout_bloom_filter_size": 8192, 15 | "optout_delta_rotate_interval": 300, 16 | "optout_delta_backtrack_in_days": 1, 17 | "optout_partition_interval": 86400, 18 | "optout_max_partitions": 30, 19 | "optout_heap_default_capacity": 8192, 20 | "cloud_download_threads": 8, 21 | "cloud_upload_threads": 2, 22 | "cloud_refresh_interval": 60, 23 | "sites_metadata_path": "sites/metadata.json", 24 | "clients_metadata_path": "clients/metadata.json", 25 | "client_side_keypairs_metadata_path": "client_side_keypairs/metadata.json", 26 | "keysets_metadata_path": "keysets/metadata.json", 27 | "keyset_keys_metadata_path": "keyset_keys/metadata.json", 28 | "salts_metadata_path": "salts/metadata.json", 29 | "services_metadata_path": "services/metadata.json", 30 | "service_links_metadata_path": "service_links/metadata.json", 31 | "runtime_config_metadata_path": "runtime_config/metadata.json", 32 | "optout_metadata_path": null, 33 | "optout_inmem_cache": false, 34 | "enclave_platform": "aws-nitro", 35 | "failure_shutdown_wait_hours": 120, 36 | "sharing_token_expiry_seconds": 2592000, 37 | "validate_service_links": false, 38 | "identity_token_expires_after_seconds": 86400, 39 | "refresh_token_expires_after_seconds": 2592000, 40 | "refresh_identity_token_after_seconds": 3600, 41 | "operator_type": "private", 42 | "enable_remote_config": true, 43 | "uid_instance_id_prefix": "unknown", 44 | "encrypted_files": false 45 | } -------------------------------------------------------------------------------- /scripts/azure-cc/conf/default-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_verbose": true, 3 | "service_instances": 3, 4 | "core_s3_bucket": null, 5 | "core_attest_url": null, 6 | "core_api_token": null, 7 | "storage_mock": false, 8 | "optout_s3_bucket": null, 9 | "optout_s3_folder": "optout/", 10 | "optout_s3_path_compat": false, 11 | "optout_data_dir": "/opt/uid2/operator-optout/", 12 | "optout_api_token": null, 13 | "optout_api_uri": null, 14 | "optout_bloom_filter_size": 8192, 15 | "optout_delta_rotate_interval": 300, 16 | "optout_delta_backtrack_in_days": 1, 17 | "optout_partition_interval": 86400, 18 | "optout_max_partitions": 30, 19 | "optout_heap_default_capacity": 8192, 20 | "cloud_download_threads": 8, 21 | "cloud_upload_threads": 2, 22 | "cloud_refresh_interval": 60, 23 | "sites_metadata_path": "sites/metadata.json", 24 | "clients_metadata_path": "clients/metadata.json", 25 | "client_side_keypairs_metadata_path": "client_side_keypairs/metadata.json", 26 | "keysets_metadata_path": "keysets/metadata.json", 27 | "keyset_keys_metadata_path": "keyset_keys/metadata.json", 28 | "salts_metadata_path": "salts/metadata.json", 29 | "services_metadata_path": "services/metadata.json", 30 | "service_links_metadata_path": "service_links/metadata.json", 31 | "runtime_config_metadata_path": "runtime_config/metadata.json", 32 | "optout_metadata_path": null, 33 | "enclave_platform": "azure-cc", 34 | "optout_inmem_cache": true, 35 | "identity_token_expires_after_seconds": 86400, 36 | "refresh_token_expires_after_seconds": 2592000, 37 | "refresh_identity_token_after_seconds": 3600, 38 | "failure_shutdown_wait_hours": 120, 39 | "sharing_token_expiry_seconds": 2592000, 40 | "validate_service_links": false, 41 | "operator_type": "private", 42 | "enable_remote_config": true, 43 | "uid_instance_id_prefix": "unknown", 44 | "encrypted_files": false 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/IdentityTokens.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import com.uid2.shared.model.TokenVersion; 4 | 5 | import java.time.Instant; 6 | 7 | public class IdentityTokens { 8 | public static IdentityTokens LogoutToken = new IdentityTokens("", null, "", Instant.EPOCH, Instant.EPOCH, Instant.EPOCH); 9 | private final String advertisingToken; 10 | private final TokenVersion advertisingTokenVersion; 11 | private final String refreshToken; 12 | private final Instant identityExpires; 13 | private final Instant refreshExpires; 14 | private final Instant refreshFrom; 15 | 16 | public IdentityTokens(String advertisingToken, TokenVersion advertisingTokenVersion, String refreshToken, 17 | Instant identityExpires, Instant refreshExpires, Instant refreshFrom) { 18 | this.advertisingToken = advertisingToken; 19 | this.advertisingTokenVersion = advertisingTokenVersion; 20 | this.refreshToken = refreshToken; 21 | this.identityExpires = identityExpires; 22 | this.refreshExpires = refreshExpires; 23 | this.refreshFrom = refreshFrom; 24 | } 25 | 26 | public String getAdvertisingToken() { 27 | return advertisingToken; 28 | } 29 | 30 | public TokenVersion getAdvertisingTokenVersion() { 31 | return advertisingTokenVersion; 32 | } 33 | 34 | public String getRefreshToken() { 35 | return refreshToken; 36 | } 37 | 38 | public Instant getIdentityExpires() { 39 | return identityExpires; 40 | } 41 | 42 | public Instant getRefreshExpires() { 43 | return refreshExpires; 44 | } 45 | 46 | public Instant getRefreshFrom() { 47 | return refreshFrom; 48 | } 49 | 50 | public boolean isEmptyToken() { 51 | return advertisingToken == null || advertisingToken.isEmpty(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/MemoryAppender.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | import ch.qos.logback.classic.Level; 8 | import ch.qos.logback.classic.spi.ILoggingEvent; 9 | import ch.qos.logback.core.read.ListAppender; 10 | 11 | public class MemoryAppender extends ListAppender { 12 | public void reset() { 13 | this.list.clear(); 14 | } 15 | 16 | public boolean contains(String string, Level level) { 17 | return this.list.stream() 18 | .anyMatch(event -> event.toString().contains(string) 19 | && event.getLevel().equals(level)); 20 | } 21 | 22 | public int countEventsForLogger(String loggerName) { 23 | return (int) this.list.stream() 24 | .filter(event -> event.getLoggerName().contains(loggerName)) 25 | .count(); 26 | } 27 | 28 | public List search(String string) { 29 | return this.list.stream() 30 | .filter(event -> event.toString().equals(string)) 31 | .collect(Collectors.toList()); 32 | } 33 | 34 | public List checkNoThrowableLogged() { 35 | return this.list.stream() 36 | .filter(event -> event.getThrowableProxy() == null) 37 | .collect(Collectors.toList()); 38 | } 39 | 40 | public List search(String string, Level level) { 41 | return this.list.stream() 42 | .filter(event -> event.toString().contains(string) 43 | && event.getLevel().equals(level)) 44 | .collect(Collectors.toList()); 45 | } 46 | 47 | public int getSize() { 48 | return this.list.size(); 49 | } 50 | 51 | public List getLoggedEvents() { 52 | return Collections.unmodifiableList(this.list); 53 | } 54 | } -------------------------------------------------------------------------------- /scripts/gcp-oidc/conf/default-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_verbose": true, 3 | "service_instances": 12, 4 | "core_s3_bucket": null, 5 | "core_attest_url": null, 6 | "core_api_token": null, 7 | "storage_mock": false, 8 | "optout_s3_bucket": null, 9 | "optout_s3_folder": "optout/", 10 | "optout_s3_path_compat": false, 11 | "optout_data_dir": "/opt/uid2/operator-optout/", 12 | "optout_api_token": null, 13 | "optout_api_uri": null, 14 | "optout_bloom_filter_size": 8192, 15 | "optout_delta_rotate_interval": 300, 16 | "optout_delta_backtrack_in_days": 1, 17 | "optout_partition_interval": 86400, 18 | "optout_max_partitions": 30, 19 | "optout_heap_default_capacity": 8192, 20 | "cloud_download_threads": 8, 21 | "cloud_upload_threads": 2, 22 | "cloud_refresh_interval": 60, 23 | "sites_metadata_path": "sites/metadata.json", 24 | "clients_metadata_path": "clients/metadata.json", 25 | "client_side_keypairs_metadata_path": "client_side_keypairs/metadata.json", 26 | "keysets_metadata_path": "keysets/metadata.json", 27 | "keyset_keys_metadata_path": "keyset_keys/metadata.json", 28 | "salts_metadata_path": "salts/metadata.json", 29 | "services_metadata_path": "services/metadata.json", 30 | "service_links_metadata_path": "service_links/metadata.json", 31 | "runtime_config_metadata_path": "runtime_config/metadata.json", 32 | "optout_metadata_path": null, 33 | "enclave_platform": "gcp-oidc", 34 | "optout_inmem_cache": true, 35 | "identity_token_expires_after_seconds": 86400, 36 | "refresh_token_expires_after_seconds": 2592000, 37 | "refresh_identity_token_after_seconds": 3600, 38 | "failure_shutdown_wait_hours": 120, 39 | "sharing_token_expiry_seconds": 2592000, 40 | "validate_service_links": false, 41 | "operator_type": "private", 42 | "enable_remote_config": true, 43 | "uid_instance_id_prefix": "unknown", 44 | "encrypted_files": false 45 | } -------------------------------------------------------------------------------- /scripts/aws/uid2-operator-ami/vars.pkr.hcl: -------------------------------------------------------------------------------- 1 | variable "env" { 2 | description = "distinct environment/stage name" 3 | default = "production" 4 | } 5 | 6 | variable "identity_scope" { 7 | description = "The scope of the operator. uid2 or euid" 8 | default = "uid2" 9 | } 10 | 11 | variable "service" { 12 | description = "distinct name for the service" 13 | default = "operator" 14 | } 15 | 16 | variable "region" { 17 | description = "AWS region name" 18 | default = "us-east-1" 19 | } 20 | 21 | variable "instance_type" { 22 | description = "instance type to build on" 23 | default = "m5.2xlarge" 24 | } 25 | 26 | variable "vpc_id" { 27 | description = "vpc id for instance creation" 28 | } 29 | 30 | variable "subnet_id" { 31 | description = "subnet id for instance creation" 32 | } 33 | 34 | variable "communicator" { 35 | description = "communication method used for the instance" 36 | default = "ssh" 37 | } 38 | 39 | variable "ssh_username" { 40 | description = "ssh username for packer to use for provisioning" 41 | default = "ec2-user" 42 | } 43 | 44 | variable "ssh_interface" { 45 | description = "ssh interface for packer to use for provisioning" 46 | default = "session_manager" 47 | } 48 | 49 | variable "iam_instance_profile" { 50 | description = "IAM instance profile to attach to AMI instance for SSM" 51 | default = "aws-operator-self-hosted-runner.target" 52 | } 53 | 54 | variable "version" { 55 | description = "release version" 56 | } 57 | 58 | variable "ami_ou_arns" { 59 | description = "A list of Amazon Resource Names (ARN) of AWS Organizations that have access to launch the resulting AMI(s)." 60 | type = list(string) 61 | } 62 | 63 | variable "timestamp" { 64 | description = "unique timestamp" 65 | } 66 | 67 | locals { 68 | identifier = "${var.identity_scope}-${var.service}" 69 | version = "${var.version}" 70 | 71 | ami_name = "${local.identifier}-${local.version}-${var.timestamp}" 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/privacy/tcf/TransparentConsent.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.privacy.tcf; 2 | 3 | import java.util.stream.IntStream; 4 | import java.util.stream.Stream; 5 | 6 | import com.iabtcf.decoder.TCString; 7 | import com.uid2.operator.vertx.ClientInputValidationException; 8 | 9 | /** 10 | * Wrapper around com.iabtcf.decoder.TCString 11 | */ 12 | public class TransparentConsent { 13 | 14 | private final TCString tcString; 15 | 16 | public TransparentConsent(String consentString) throws ClientInputValidationException { 17 | try { 18 | this.tcString = TCString.decode(consentString); 19 | } catch(Exception e) { 20 | throw new ClientInputValidationException("unable to parse consentString", e); 21 | } 22 | } 23 | 24 | public boolean hasConsent(int vendorId, TransparentConsentPurpose ... purposes) { 25 | // DevNote: Here we enumerates a bitfield and reconstruct it. 26 | // Using raw bitfield inside TCString would be more efficient. 27 | // However, we do not have access to it 28 | 29 | final int requiredBits = Stream.of(purposes) 30 | .mapToInt(x -> x.value) 31 | .reduce(0, (f, x) -> f | 1 << x); 32 | return (IntStream.concat( 33 | this.tcString.getVendorConsent().contains(vendorId) ? 34 | this.tcString.getPurposesConsent().toStream() : 35 | IntStream.of(), 36 | this.tcString.getVendorLegitimateInterest().contains(vendorId) ? 37 | this.tcString.getPurposesLITransparency().toStream() : 38 | IntStream.of()) 39 | .reduce(0, (f, x) -> (f | (1 << x))) 40 | & requiredBits) == requiredBits; 41 | } 42 | 43 | public boolean hasSpecialFeature(TransparentConsentSpecialFeature feature) { 44 | return this.tcString.getSpecialFeatureOptIns().contains(feature.value); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/vertx/GenericFailureHandler.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.vertx; 2 | 3 | import io.vertx.core.Handler; 4 | import io.vertx.core.http.HttpClosedException; 5 | import io.vertx.core.http.HttpServerResponse; 6 | import io.vertx.ext.web.RoutingContext; 7 | import org.apache.http.impl.EnglishReasonPhraseCatalog; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | public class GenericFailureHandler implements Handler { 12 | private static final Logger LOGGER = LoggerFactory.getLogger(GenericFailureHandler.class); 13 | 14 | @Override 15 | public void handle(RoutingContext ctx) { 16 | // Status code will be 500 for the RuntimeException 17 | int statusCode = ctx.statusCode(); 18 | HttpServerResponse response = ctx.response(); 19 | String url = ctx.normalizedPath(); 20 | Throwable t = ctx.failure(); 21 | 22 | if (t != null) { 23 | // Because Vert.x swallows stack traces so cannot log stack trace 24 | // And we want to ignore HttpClosedException errors as it is (usually) caused by users and no impact 25 | if (t instanceof HttpClosedException) { 26 | LOGGER.warn("Ignoring exception - URL: [{}] - Error:", url, t); 27 | response.end(); 28 | } else if (statusCode >= 500 && statusCode < 600) { // 5xx is server error, so error 29 | LOGGER.error("URL: [{}] - Error response code: [{}] - Error:", url, statusCode, t); 30 | } else if (statusCode >= 400 && statusCode < 500) { // 4xx is user error, so just warn 31 | LOGGER.warn("URL: [{}] - Error response code: [{}] - Error:", url, statusCode, t); 32 | } 33 | } 34 | 35 | if (!response.ended() && !response.closed()) { 36 | response.setStatusCode(statusCode) 37 | .end(EnglishReasonPhraseCatalog.INSTANCE.getReason(statusCode, null)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /scripts/azure-cc/Dockerfile: -------------------------------------------------------------------------------- 1 | # sha from https://hub.docker.com/layers/library/eclipse-temurin/21.0.8_9-jre-alpine-3.22/images/sha256-3408c45e1faee20e4e68808939a75f87efa469b927d20e12309689ead053daba 2 | FROM eclipse-temurin@sha256:4ca7eff3ab0ef9b41f5fefa35efaeda9ed8d26e161e1192473b24b3a6c348aef 3 | 4 | # Install necessary packages and set up virtual environment 5 | RUN apk update && apk add --no-cache jq python3 py3-pip && \ 6 | python3 -m venv /venv && \ 7 | . /venv/bin/activate && \ 8 | pip install --no-cache-dir requests azure-identity azure-keyvault-secrets && \ 9 | rm -rf /var/cache/apk/* 10 | 11 | # Set virtual environment path 12 | ENV PATH="/venv/bin:$PATH" 13 | 14 | # Working directory 15 | WORKDIR /app 16 | 17 | # Expose necessary ports 18 | EXPOSE 8080 19 | EXPOSE 9080 20 | 21 | # ARG and ENV variables 22 | ARG JAR_NAME=uid2-operator 23 | ARG JAR_VERSION=1.0.0-SNAPSHOT 24 | ARG IMAGE_VERSION=1.0.0.unknownhash 25 | ENV JAR_NAME=${JAR_NAME} 26 | ENV JAR_VERSION=${JAR_VERSION} 27 | ENV IMAGE_VERSION=${IMAGE_VERSION} 28 | ENV REGION=default 29 | 30 | # Copy application files 31 | COPY ./target/${JAR_NAME}-${JAR_VERSION}-jar-with-dependencies.jar /app/${JAR_NAME}-${JAR_VERSION}.jar 32 | COPY ./target/${JAR_NAME}-${JAR_VERSION}-sources.jar /app 33 | COPY ./target/${JAR_NAME}-${JAR_VERSION}-static.tar.gz /app/static.tar.gz 34 | COPY ./conf/*.json /app/conf/ 35 | COPY ./conf/*.xml /app/conf/ 36 | 37 | # Extract and clean up tar.gz 38 | RUN tar xzvf /app/static.tar.gz --no-same-owner --no-same-permissions && \ 39 | rm -f /app/static.tar.gz 40 | 41 | COPY ./azr.py /app 42 | COPY ./confidential_compute.py /app 43 | RUN chmod a+x /app/*.py 44 | 45 | # Create and configure non-root user 46 | RUN adduser -D uid2-operator && \ 47 | mkdir -p /opt/uid2 && chmod 777 -R /opt/uid2 && \ 48 | chmod 705 -R /app && mkdir -p /app/file-uploads && chmod 777 -R /app/file-uploads 49 | 50 | # Switch to non-root user 51 | USER uid2-operator 52 | 53 | # Run the Python entry point 54 | CMD python3 /app/azr.py -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/ServiceLinkStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator; 2 | 3 | import com.uid2.shared.cloud.EmbeddedResourceStorage; 4 | import com.uid2.shared.model.ServiceLink; 5 | import com.uid2.shared.store.CloudPath; 6 | import com.uid2.shared.store.reader.RotatingServiceLinkStore; 7 | import com.uid2.shared.store.scope.GlobalScope; 8 | import io.vertx.core.json.JsonObject; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static org.junit.jupiter.api.Assertions.*; 15 | 16 | public class ServiceLinkStoreTest { 17 | @Test 18 | public void loadFromEmbeddedResourceStorage() throws Exception { 19 | RotatingServiceLinkStore serviceLinkProvider = new RotatingServiceLinkStore( 20 | new EmbeddedResourceStorage(Main.class), 21 | new GlobalScope(new CloudPath("/com.uid2.core/test/service_links/metadata.json"))); 22 | 23 | JsonObject m = serviceLinkProvider.getMetadata(); 24 | assertDoesNotThrow(() -> serviceLinkProvider.loadContent(m)); 25 | 26 | ServiceLink serviceLink = serviceLinkProvider.getServiceLink(1, "testId1"); 27 | assertNotNull(serviceLink); 28 | assertEquals("testName1", serviceLink.getName()); 29 | 30 | List serviceLinks = new ArrayList<>(serviceLinkProvider.getAllServiceLinks()); 31 | assertEquals(2, serviceLinks.size()); 32 | 33 | ServiceLink sl1 = new ServiceLink("testId1", 1, 123, "testName1", null); 34 | ServiceLink sl2 = new ServiceLink("testId2", 2, 123, "testName2", null); 35 | assertTrue(serviceLinks.contains(sl1)); 36 | assertTrue(serviceLinks.contains(sl2)); 37 | 38 | ServiceLink sl3 = serviceLinkProvider.getServiceLink(1, "testId1"); 39 | assertEquals(sl1, sl3); 40 | ServiceLink sl4 = serviceLinkProvider.getServiceLink(2, "testId2"); 41 | assertEquals(sl2, sl4); 42 | 43 | assertNull(serviceLinkProvider.getServiceLink(1, "missing")); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/cloud_encryption_keys/cloud_encryption_keys.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "id" : 1, 3 | "siteId" : 999, 4 | "activates" : 1720641670, 5 | "created" : 1720641670, 6 | "secret" : "mydrCudb2PZOm01Qn0SpthltmexHUAA11Hy1m+uxjVw=" 7 | }, { 8 | "id" : 2, 9 | "siteId" : 999, 10 | "activates" : 1720728070, 11 | "created" : 1720641670, 12 | "secret" : "FtdslrFSsvVXOuhOWGwEI+0QTkCvM8SGZAP3k2u3PgY=" 13 | }, { 14 | "id" : 3, 15 | "siteId" : 999, 16 | "activates" : 1720814470, 17 | "created" : 1720641670, 18 | "secret" : "/7zO6QbKrhZKIV36G+cU9UR4hZUVg5bD+KjbczICjHw=" 19 | }, { 20 | "id" : 4, 21 | "siteId" : 123, 22 | "activates" : 1720641671, 23 | "created" : 1720641671, 24 | "secret" : "XjiqRlWQQJGLr7xfV1qbueKwyzt881GVohuUkQt/ht4=" 25 | }, { 26 | "id" : 5, 27 | "siteId" : 123, 28 | "activates" : 1720728071, 29 | "created" : 1720641671, 30 | "secret" : "QmpIf5NzO+UROjl5XjB/BmF6paefM8n6ub9B2plC9aI=" 31 | }, { 32 | "id" : 6, 33 | "siteId" : 123, 34 | "activates" : 1720814471, 35 | "created" : 1720641671, 36 | "secret" : "40w9UMSYxGm+KldOWOXhBGI8QgjvUUQjivtkP4VpKV8=" 37 | }, { 38 | "id" : 7, 39 | "siteId" : 124, 40 | "activates" : 1720641671, 41 | "created" : 1720641671, 42 | "secret" : "QdwD0kQV1BwmLRD0PH1YpqgaOrgpVTfu08o98mSZ6uE=" 43 | }, { 44 | "id" : 8, 45 | "siteId" : 124, 46 | "activates" : 1720728071, 47 | "created" : 1720641671, 48 | "secret" : "yCVCM/HLf9/6k+aUNrx7w17VbyfSzI8JykLQLSR+CW0=" 49 | }, { 50 | "id" : 9, 51 | "siteId" : 124, 52 | "activates" : 1720814471, 53 | "created" : 1720641671, 54 | "secret" : "JqHl8BrTyx9XpR2lYj/5xvUpzgnibGeomETTwF4rn1U=" 55 | }, { 56 | "id" : 10, 57 | "siteId" : 127, 58 | "activates" : 1720641671, 59 | "created" : 1720641671, 60 | "secret" : "JqiG1b34AvrdO3Aj6cCcjOBJMijrDzTmrR+p9ZtP2es=" 61 | }, { 62 | "id" : 11, 63 | "siteId" : 127, 64 | "activates" : 1720728072, 65 | "created" : 1720641672, 66 | "secret" : "lp1CyHdfc7K0aO5JGpA+Ve5Z/V5LImtGEQwCg/YB0kY=" 67 | }, { 68 | "id" : 12, 69 | "siteId" : 127, 70 | "activates" : 1720814472, 71 | "created" : 1720641672, 72 | "secret" : "G99rFYJF+dnSlk/xG6fuC3WNqQxTLJbDIdVyPMbGQ6s=" 73 | } ] 74 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/util/Tuple.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.util; 2 | 3 | import java.util.Objects; 4 | 5 | public class Tuple { 6 | public static class Tuple2 { 7 | private final T1 item1; 8 | private final T2 item2; 9 | 10 | public Tuple2(T1 item1, T2 item2) { 11 | Objects.requireNonNull(item1); 12 | Objects.requireNonNull(item2); 13 | 14 | this.item1 = item1; 15 | this.item2 = item2; 16 | } 17 | 18 | public T1 getItem1() { return item1; } 19 | public T2 getItem2() { return item2; } 20 | 21 | @Override 22 | public int hashCode() { return item1.hashCode() ^ item2.hashCode(); } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (!(o instanceof Tuple2)) return false; 27 | Tuple2 pairo = (Tuple2) o; 28 | return this.item1.equals(pairo.item1) && 29 | this.item2.equals(pairo.item2); 30 | } 31 | } 32 | 33 | public static class Tuple3 { 34 | private final T1 item1; 35 | private final T2 item2; 36 | private final T3 item3; 37 | 38 | public Tuple3(T1 item1, T2 item2, T3 item3) { 39 | Objects.requireNonNull(item1); 40 | Objects.requireNonNull(item2); 41 | Objects.requireNonNull(item3); 42 | 43 | this.item1 = item1; 44 | this.item2 = item2; 45 | this.item3 = item3; 46 | } 47 | 48 | public T1 getItem1() { return item1; } 49 | public T2 getItem2() { return item2; } 50 | public T3 getItem3() { return item3; } 51 | 52 | @Override 53 | public int hashCode() { return item1.hashCode() ^ item2.hashCode() ^ item3.hashCode(); } 54 | 55 | @Override 56 | public boolean equals(Object o) { 57 | if (!(o instanceof Tuple3)) return false; 58 | Tuple3 tripleo = (Tuple3) o; 59 | return this.item1.equals(tripleo.item1) && 60 | this.item2.equals(tripleo.item2) && 61 | this.item3.equals(tripleo.item3); 62 | } 63 | } 64 | } 65 | 66 | 67 | -------------------------------------------------------------------------------- /conf/docker-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_verbose": true, 3 | "service_instances": 1, 4 | "storage_mock": true, 5 | "refresh_token_expires_after_seconds": 86400, 6 | "refresh_identity_token_after_seconds": 900, 7 | "refresh_token_v3": false, 8 | "identity_v3": false, 9 | "identity_scope": "uid2", 10 | "enable_v2_encryption": true, 11 | "optout_s3_bucket": null, 12 | "optout_s3_folder": "optout/", 13 | "optout_s3_path_compat": false, 14 | "optout_data_dir": "/opt/uid2/operator-optout/", 15 | "optout_api_token": null, 16 | "optout_api_uri": null, 17 | "optout_bloom_filter_size": 8192, 18 | "optout_delta_rotate_interval": 300, 19 | "optout_delta_backtrack_in_days": 1, 20 | "optout_partition_interval": 86400, 21 | "optout_max_partitions": 30, 22 | "optout_heap_default_capacity": 8192, 23 | "cloud_download_threads": 8, 24 | "cloud_upload_threads": 2, 25 | "cloud_refresh_interval": 60, 26 | "sites_metadata_path": "/com.uid2.core/test/sites/metadata.json", 27 | "clients_metadata_path": "/com.uid2.core/test/clients/metadata.json", 28 | "keysets_metadata_path": "/com.uid2.core/test/keysets/metadata.json", 29 | "keyset_keys_metadata_path": "/com.uid2.core/test/keyset_keys/metadata.json", 30 | "client_side_keypairs_metadata_path": "/com.uid2.core/test/client_side_keypairs/metadata.json", 31 | "salts_metadata_path": "/com.uid2.core/test/salts/metadata.json", 32 | "services_metadata_path": "/com.uid2.core/test/services/metadata.json", 33 | "service_links_metadata_path": "/com.uid2.core/test/service_links/metadata.json", 34 | "cloud_encryption_keys_metadata_path": "/com.uid2.core/test/cloud_encryption_keys/metadata.json", 35 | "runtime_config_metadata_path": "/com.uid2.core/test/runtime_config/metadata.json", 36 | "encrypted_files": false, 37 | "identity_token_expires_after_seconds": 3600, 38 | "optout_metadata_path": null, 39 | "optout_inmem_cache": false, 40 | "enclave_platform": null, 41 | "failure_shutdown_wait_hours": 120, 42 | "salts_expired_shutdown_hours": 12, 43 | "store_refresh_stale_shutdown_hours": 12, 44 | "operator_type": "public", 45 | "disable_optout_token": true, 46 | "enable_remote_config": true, 47 | "uid_instance_id_prefix": "local-operator" 48 | } 49 | -------------------------------------------------------------------------------- /scripts/gcp-oidc/generate-deployment-artifacts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | 4 | # Following environment variables must be set 5 | # - IMAGE: uid2-operator image 6 | # - IMAGE_DIGEST: uid2-operator image digest 7 | # - OUTPUT_DIR: output directory to store the artifacts 8 | # - MANIFEST_DIR: output directory to store the manifest for the enclave Id 9 | # - VERSION_NUMBER: the version number of the build 10 | 11 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 12 | INPUT_DIR=${SCRIPT_DIR}/terraform 13 | 14 | if [[ -z ${IMAGE} ]]; then 15 | echo "IMAGE cannot be empty" 16 | exit 1 17 | fi 18 | 19 | if [[ -z ${IMAGE_DIGEST} ]]; then 20 | echo "IMAGE_DIGEST cannot be empty" 21 | exit 1 22 | fi 23 | 24 | if [[ -z ${OUTPUT_DIR} ]]; then 25 | echo "OUTPUT_DIR cannot be empty" 26 | exit 1 27 | fi 28 | 29 | mkdir -p ${OUTPUT_DIR} 30 | if [[ $? -ne 0 ]]; then 31 | echo "Failed to create ${OUTPUT_DIR}" 32 | exit 1 33 | fi 34 | 35 | mkdir -p ${MANIFEST_DIR} 36 | if [[ $? -ne 0 ]]; then 37 | echo "Failed to create ${MANIFEST_DIR}" 38 | exit 1 39 | fi 40 | 41 | # Input files 42 | INPUT_FILES=( 43 | main.tf outputs.tf variables.tf terraform.tfvars 44 | ) 45 | 46 | # Copy input files to output dir 47 | for f in ${INPUT_FILES[@]}; do 48 | cp ${INPUT_DIR}/${f} ${OUTPUT_DIR}/${f} 49 | if [[ $? -ne 0 ]]; then 50 | echo "Failed to copy ${INPUT_DIR}/${f} to ${OUTPUT_DIR}" 51 | exit 1 52 | fi 53 | done 54 | 55 | # Update operator tfvars 56 | sed -i "s#IMAGE_PLACEHOLDER#${IMAGE}#g" ${OUTPUT_DIR}/terraform.tfvars 57 | if [[ $? -ne 0 ]]; then 58 | echo "Failed to pre-process tfvars file" 59 | exit 1 60 | fi 61 | 62 | # Enclave ID file 63 | echo -n "V1,false,$IMAGE_DIGEST" | openssl dgst -sha256 -binary | openssl base64 > ${MANIFEST_DIR}/gcp-oidc-enclave-id-$VERSION_NUMBER.txt 64 | if [[ $? -ne 0 ]]; then 65 | echo "Failed to generate non-debug enclave ID file" 66 | exit 1 67 | fi 68 | 69 | # Enclave ID file for debug 70 | echo -n "V1,true,$IMAGE_DIGEST" | openssl dgst -sha256 -binary | openssl base64 > ${MANIFEST_DIR}/gcp-oidc-enclave-id-debug-$VERSION_NUMBER.txt 71 | if [[ $? -ne 0 ]]; then 72 | echo "Failed to generate debug enclave ID file" 73 | exit 1 74 | fi 75 | -------------------------------------------------------------------------------- /conf/local-e2e-docker-public-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_instances": 1, 3 | "storage_mock": false, 4 | "core_attest_url": "http://core:8088/attest", 5 | "core_api_token": "UID2-O-L-999-dp9Dt0.JVoGpynN4J8nMA7FxmzsavxJa8B9H74y9xdEE=", 6 | "sites_metadata_path": "http://core:8088/sites/refresh", 7 | "clients_metadata_path": "http://core:8088/clients/refresh", 8 | "client_side_keypairs_metadata_path": "http://core:8088/client_side_keypairs/refresh", 9 | "keys_metadata_path": "http://core:8088/key/refresh", 10 | "keys_acl_metadata_path": "http://core:8088/key/acl/refresh", 11 | "keysets_metadata_path": "http://core:8088/key/keyset/refresh", 12 | "keyset_keys_metadata_path": "http://core:8088/key/keyset-keys/refresh", 13 | "salts_metadata_path": "http://core:8088/salt/refresh", 14 | "services_metadata_path": "http://core:8088/services/refresh", 15 | "service_links_metadata_path": "http://core:8088/service_links/refresh", 16 | "cloud_encryption_keys_metadata_path": "http://core:8088/cloud_encryption_keys/retrieve", 17 | "runtime_config_metadata_path": "http://core:8088/operator/config", 18 | "encrypted_files": true, 19 | "identity_token_expires_after_seconds": 3600, 20 | "refresh_token_expires_after_seconds": 86400, 21 | "refresh_identity_token_after_seconds": 900, 22 | "refresh_token_v3": true, 23 | "identity_v3": false, 24 | "identity_scope": "uid2", 25 | "enable_v2_encryption": true, 26 | "client_side_token_generate": true, 27 | "client_side_token_generate_domain_name_check_enabled": true, 28 | "client_side_token_generate_log_invalid_http_origins": true, 29 | "key_sharing_endpoint_provide_app_names": true, 30 | "validate_service_links": true, 31 | "optout_s3_bucket": "test-optout-bucket", 32 | "optout_s3_folder": "optout-v2/", 33 | "optout_metadata_path": "/optout/refresh", 34 | "optout_api_uri": "http://optout:8081/optout/replicate", 35 | "optout_delta_rotate_interval": 60, 36 | "optout_status_api_enabled": true, 37 | "cloud_refresh_interval": 30, 38 | "salts_expired_shutdown_hours": 12, 39 | "store_refresh_stale_shutdown_hours": 12, 40 | "operator_type": "public", 41 | "disable_optout_token": true, 42 | "enable_remote_config": true, 43 | "uid_instance_id_prefix": "local-public-operator" 44 | } 45 | -------------------------------------------------------------------------------- /scripts/azure-cc/README.md: -------------------------------------------------------------------------------- 1 | # UID2 Operator - Azure Confidential Container package 2 | 3 | ## Generate Deployment Artifacts 4 | 5 | Generate deployment files by following command. 6 | 7 | ``` 8 | IMAGE={IMAGE} OUTPUT_DIR=output ./deployment/generate-deployment-artifacts.sh 9 | ``` 10 | Following files will be generated: 11 | 12 | * Deployment files will be stored to directory `output` 13 | * `operator-digest.txt`: the digest will be used as enclave ID to be registered in admin portal. 14 | * Other files are used to deploy to Azure as described in the next section. 15 | * Deployment files will also be archived into file `output/uid2-operator-deployment-artifacts.zip` 16 | 17 | ## Deploy 18 | 19 | Create a resource group for running the UID2 Operator 20 | 21 | ``` 22 | az group create -g {RESOURCE_GROUP_NAME} --location {LOCATION} 23 | ``` 24 | 25 | Once resource group is created, you can create the networking required. This is optional if you need to use your existing network. However it is recommended. 26 | 27 | ``` 28 | az deployment group create --name vnet --resource-group ${RESOURCE_GROUP_NAME} --template-file vnet.json --parameters vnet.parameters.json 29 | ``` 30 | 31 | Now, create vault to store the operator key, and the identity to run operator. 32 | Please set the `vaultName` and `operatorKeyValue` parameters in `vault.parameters.json` before running below command. 33 | 34 | ``` 35 | az deployment group create --name vault --resource-group ${RESOURCE_GROUP_NAME} --template-file vault.json --parameters vault.parameters.json 36 | ``` 37 | 38 | Create the operator containers now. 39 | Please set the `vaultName` parameter in `operator.parameters.json` as the same as in `vault.parameters.json` before running below command. 40 | 41 | ``` 42 | az deployment group create --name operator --resource-group ${RESOURCE_GROUP_NAME} --template-file operator.json --parameters operator.parameters.json 43 | ``` 44 | 45 | Since the operators are created in private subnet, we need a public IP. Copy the container IP of the created containers running operators to parameter `containerGroupIPs` in `gateway.parameters.json` and run 46 | 47 | ``` 48 | az deployment group create --name gateway --resource-group ${RESOURCE_GROUP_NAME} --template-file gateway.json --parameters gateway.parameters.json 49 | ``` 50 | -------------------------------------------------------------------------------- /.github/workflows/validate-image.yaml: -------------------------------------------------------------------------------- 1 | name: Validate Docker Image 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | failure_severity: 6 | description: The severity to fail the workflow if such vulnerability is detected. DO NOT override it unless a Jira ticket is raised. 7 | type: choice 8 | options: 9 | - CRITICAL,HIGH 10 | - CRITICAL,HIGH,MEDIUM 11 | - CRITICAL (DO NOT use if JIRA ticket not raised) 12 | fail_on_error: 13 | description: If true, will fail the build if vulnerabilities are found 14 | required: true 15 | type: boolean 16 | default: true 17 | schedule: 18 | - cron: '0 20 * * *' #every day at 20:00 19 | 20 | jobs: 21 | build-publish-docker-default: 22 | uses: IABTechLab/uid2-shared-actions/.github/workflows/shared-validate-image.yaml@v3 23 | with: 24 | failure_severity: ${{ inputs.failure_severity || 'CRITICAL,HIGH' }} 25 | fail_on_error: ${{ inputs.fail_on_error || true }} 26 | cloud_provider: 'default' 27 | java_version: 21 28 | secrets: inherit 29 | build-publish-docker-aws: 30 | uses: IABTechLab/uid2-shared-actions/.github/workflows/shared-validate-image.yaml@v3 31 | with: 32 | failure_severity: ${{ inputs.failure_severity || 'CRITICAL,HIGH' }} 33 | fail_on_error: ${{ inputs.fail_on_error || true }} 34 | cloud_provider: 'aws' 35 | java_version: 21 36 | secrets: inherit 37 | needs: [build-publish-docker-default] 38 | build-publish-docker-gcp: 39 | uses: IABTechLab/uid2-shared-actions/.github/workflows/shared-validate-image.yaml@v3 40 | with: 41 | failure_severity: ${{ inputs.failure_severity || 'CRITICAL,HIGH' }} 42 | fail_on_error: ${{ inputs.fail_on_error || true }} 43 | cloud_provider: 'gcp' 44 | java_version: 21 45 | secrets: inherit 46 | needs: [build-publish-docker-aws] 47 | build-publish-docker-azure: 48 | uses: IABTechLab/uid2-shared-actions/.github/workflows/shared-validate-image.yaml@v3 49 | with: 50 | failure_severity: ${{ inputs.failure_severity || 'CRITICAL,HIGH' }} 51 | fail_on_error: ${{ inputs.fail_on_error || true }} 52 | cloud_provider: 'azure' 53 | java_version: 21 54 | secrets: inherit 55 | needs: [build-publish-docker-gcp] -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/monitoring/ClientVersionStatRecorder.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.monitoring; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.stream.Stream; 6 | 7 | public class ClientVersionStatRecorder { 8 | private static final String NOT_RECORDED = ""; 9 | private final int siteClientBucketLimit; 10 | private final Map> siteIdToVersionCounts = new HashMap<>(); 11 | 12 | public ClientVersionStatRecorder(int maxVersionBucketsPerSite) { 13 | this.siteClientBucketLimit = maxVersionBucketsPerSite; 14 | } 15 | 16 | public Stream getStatsView() { 17 | return siteIdToVersionCounts.entrySet().stream().map(entry -> new SiteClientVersionStat(entry.getKey(), entry.getValue())); 18 | } 19 | 20 | private void removeLowVersionCounts(int siteId) { 21 | var versionCounts = siteIdToVersionCounts.get(siteId); 22 | if (versionCounts == null) { 23 | return; 24 | } 25 | 26 | // Remove 3 items to avoid a couple of new version values from continuously evicting each other 27 | var lowestEntries = versionCounts.entrySet().stream() 28 | .sorted(Map.Entry.comparingByValue()) 29 | .filter(entry -> !entry.getKey().equals(NOT_RECORDED)) 30 | .limit(3) 31 | .toList(); 32 | for (var entry : lowestEntries) { 33 | var notRecordedCount = versionCounts.getOrDefault(NOT_RECORDED, 0); 34 | versionCounts.put(NOT_RECORDED, notRecordedCount + entry.getValue()); 35 | versionCounts.remove(entry.getKey()); 36 | } 37 | } 38 | 39 | public void add(Integer siteId, String clientVersion) { 40 | if (siteId == null || clientVersion == null || clientVersion.isBlank()) { 41 | return; 42 | } 43 | 44 | var clientVersionCounts = siteIdToVersionCounts.computeIfAbsent(siteId, k -> new HashMap<>()); 45 | 46 | var count = clientVersionCounts.getOrDefault(clientVersion, 0); 47 | if (count == 0 && clientVersionCounts.size() >= siteClientBucketLimit) { 48 | removeLowVersionCounts(siteId); 49 | } 50 | clientVersionCounts.put(clientVersion, count + 1); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /conf/local-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_instances": 4, 3 | "storage_mock": true, 4 | "sites_metadata_path": "/com.uid2.core/test/sites/metadata.json", 5 | "clients_metadata_path": "/com.uid2.core/test/clients/metadata.json", 6 | "keysets_metadata_path": "/com.uid2.core/test/keysets/metadata.json", 7 | "keyset_keys_metadata_path": "/com.uid2.core/test/keyset_keys/metadata.json", 8 | "client_side_keypairs_metadata_path": "/com.uid2.core/test/client_side_keypairs/metadata.json", 9 | "salts_metadata_path": "/com.uid2.core/test/salts/metadata.json", 10 | "services_metadata_path": "/com.uid2.core/test/services/metadata.json", 11 | "service_links_metadata_path": "/com.uid2.core/test/service_links/metadata.json", 12 | "cloud_encryption_keys_metadata_path": "/com.uid2.core/test/cloud_encryption_keys/metadata.json", 13 | "runtime_config_metadata_path": "/com.uid2.core/test/runtime_config/metadata.json", 14 | "identity_token_expires_after_seconds": 3600, 15 | "refresh_token_expires_after_seconds": 86400, 16 | "refresh_identity_token_after_seconds": 900, 17 | "refresh_token_v3": false, 18 | "identity_v3": false, 19 | "identity_scope": "uid2", 20 | "enable_v2_encryption": false, 21 | "sharing_token_expiry_seconds": 2592000, 22 | "cloud_download_threads": 8, 23 | "cloud_upload_threads": 2, 24 | "cloud_refresh_interval": 60, 25 | "optout_inmem_cache": false, 26 | "optout_synthetic_logs_enabled": false, 27 | "optout_data_dir": "/opt/uid2/operator-optout/", 28 | "optout_s3_folder": "optout/", 29 | "optout_bloom_filter_size": 8192, 30 | "optout_delta_rotate_interval": 300, 31 | "optout_delta_backtrack_in_days": 1, 32 | "optout_heap_default_capacity": 8192, 33 | "optout_max_partitions": 30, 34 | "optout_partition_interval": 86400, 35 | "optout_status_api_enabled": true, 36 | "client_side_token_generate": true, 37 | "client_side_token_generate_domain_name_check_enabled": true, 38 | "key_sharing_endpoint_provide_app_names": true, 39 | "client_side_token_generate_log_invalid_http_origins": true, 40 | "salts_expired_shutdown_hours": 12, 41 | "store_refresh_stale_shutdown_hours": 12, 42 | "operator_type": "public", 43 | "encrypted_files": false, 44 | "disable_optout_token": true, 45 | "enable_remote_config": true, 46 | "uid_instance_id_prefix": "local-operator" 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/service/V4TokenUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.service; 2 | 3 | import com.uid2.shared.model.SaltEntry; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Arrays; 7 | 8 | import static com.uid2.operator.service.V4TokenUtils.*; 9 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | class V4TokenUtilsTest { 13 | @Test 14 | void testBuildAdvertisingIdV4() throws Exception { 15 | SaltEntry.KeyMaterial encryptionKey = new SaltEntry.KeyMaterial( 16 | 1000000, 17 | "key12345key12345key12345key12345", 18 | "salt1234salt1234salt1234salt1234" 19 | ); 20 | byte[] firstLevelHash = TokenUtils.getFirstLevelHashFromIdentity("test@example.com", encryptionKey.salt()); 21 | byte metadata = (byte) 0b00100000; 22 | byte[] v4UID = buildAdvertisingIdV4(metadata, firstLevelHash, encryptionKey.id(), encryptionKey.key(), encryptionKey.salt()); 23 | assertEquals(33, v4UID.length); 24 | 25 | byte[] firstLevelHashLast16Bytes = Arrays.copyOfRange(firstLevelHash, firstLevelHash.length - 16, firstLevelHash.length); 26 | byte[] iv = generateIV(encryptionKey.salt(), firstLevelHashLast16Bytes, metadata, encryptionKey.id()); 27 | byte[] encryptedFirstLevelHash = encryptHash(encryptionKey.key(), firstLevelHashLast16Bytes, iv); 28 | 29 | byte extractedMetadata = v4UID[0]; 30 | byte[] keyIdBytes = Arrays.copyOfRange(v4UID, 1, 4); 31 | int extractedKeyId = ((keyIdBytes[0] & 0xFF) << 16) | ((keyIdBytes[1] & 0xFF) << 8) | (keyIdBytes[2] & 0xFF); 32 | byte[] extractedIV = Arrays.copyOfRange(v4UID, 4, 16); 33 | byte[] extractedEncryptedHash = Arrays.copyOfRange(v4UID, 16, 32); 34 | byte extractedChecksum = v4UID[32]; 35 | 36 | assertEquals(metadata, extractedMetadata); 37 | assertEquals(encryptionKey.id(), extractedKeyId); 38 | assertArrayEquals(iv, extractedIV); 39 | assertArrayEquals(encryptedFirstLevelHash, extractedEncryptedHash); 40 | 41 | // Verify checksum 42 | byte recomputedChecksum = generateChecksum(Arrays.copyOfRange(v4UID, 0, 32)); 43 | assertEquals(extractedChecksum, recomputedChecksum); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /conf/validator-latest-e2e-docker-public-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_instances": 1, 3 | "storage_mock": false, 4 | "enforce_https": false, 5 | "core_attest_url": "http://core:8088/attest", 6 | "core_api_token": "UID2-O-L-999-dp9Dt0.JVoGpynN4J8nMA7FxmzsavxJa8B9H74y9xdEE=", 7 | "sites_metadata_path": "http://core:8088/sites/refresh", 8 | "clients_metadata_path": "http://core:8088/clients/refresh", 9 | "client_side_keypairs_metadata_path": "http://core:8088/client_side_keypairs/refresh", 10 | "keys_metadata_path": "http://core:8088/key/refresh", 11 | "keys_acl_metadata_path": "http://core:8088/key/acl/refresh", 12 | "keysets_metadata_path": "http://core:8088/key/keyset/refresh", 13 | "keyset_keys_metadata_path": "http://core:8088/key/keyset-keys/refresh", 14 | "salts_metadata_path": "http://core:8088/salt/refresh", 15 | "services_metadata_path": "http://core:8088/services/refresh", 16 | "service_links_metadata_path": "http://core:8088/service_links/refresh", 17 | "cloud_encryption_keys_metadata_path": "http://core:8088/cloud_encryption_keys/retrieve", 18 | "runtime_config_metadata_path": "http://core:8088/operator/config", 19 | "encrypted_files": true, 20 | "identity_token_expires_after_seconds": 3600, 21 | "refresh_token_expires_after_seconds": 86400, 22 | "refresh_identity_token_after_seconds": 900, 23 | "refresh_token_v3": true, 24 | "identity_v3": false, 25 | "identity_scope": "uid2", 26 | "enable_v2_encryption": true, 27 | "client_side_token_generate": true, 28 | "client_side_token_generate_domain_name_check_enabled": true, 29 | "client_side_token_generate_log_invalid_http_origins": true, 30 | "key_sharing_endpoint_provide_app_names": true, 31 | "validate_service_links": true, 32 | "optout_s3_bucket": "test-optout-bucket", 33 | "optout_s3_folder": "optout-v2/", 34 | "optout_metadata_path": "http://optout:8081/optout/refresh", 35 | "optout_api_uri": "http://optout:8081/optout/replicate", 36 | "optout_delta_rotate_interval": 60, 37 | "cloud_refresh_interval": 30, 38 | "operator_type": "public", 39 | "runtime_config_store": { 40 | "type": "http", 41 | "config" : { 42 | "url": "http://core:8088/operator/config" 43 | }, 44 | "config_scan_period_ms": 300000 45 | }, 46 | "disable_optout_token": true, 47 | "enable_remote_config": true, 48 | "uid_instance_id_prefix": "local-public-operator" 49 | } 50 | -------------------------------------------------------------------------------- /conf/local-e2e-private-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_instances": 1, 3 | "storage_mock": true, 4 | "core_attest_url": "http://localhost:8088/attest", 5 | "core_api_token": "OPLCLAjLRWcVlCDl9+BbwR38gzxYdiWFa751ynWLuI7JU4iA=", 6 | "sites_metadata_path": "http://localhost:8088/sites/refresh", 7 | "clients_metadata_path": "http://localhost:8088/clients/refresh", 8 | "client_side_keypairs_metadata_path": "http://localhost:8088/client_side_keypairs/refresh", 9 | "keys_metadata_path": "http://localhost:8088/key/refresh", 10 | "keys_acl_metadata_path": "http://localhost:8088/key/acl/refresh", 11 | "keysets_metadata_path": "http://localhost:8088/key/keyset/refresh", 12 | "keyset_keys_metadata_path": "http://localhost:8088/key/keyset-keys/refresh", 13 | "salts_metadata_path": "http://localhost:8088/salt/refresh", 14 | "services_metadata_path": "http://localhost:8088/services/refresh", 15 | "service_links_metadata_path": "http://localhost:8088/service_links/refresh", 16 | "cloud_encryption_keys_metadata_path": "http://localhost:8088/cloud_encryption_keys/retrieve", 17 | "runtime_config_metadata_path": "http://localhost:8088/operator/config", 18 | "encrypted_files": true, 19 | "identity_token_expires_after_seconds": 3600, 20 | "refresh_token_expires_after_seconds": 86400, 21 | "refresh_identity_token_after_seconds": 900, 22 | "refresh_token_v3": true, 23 | "identity_v3": false, 24 | "identity_scope": "uid2", 25 | "enable_v2_encryption": true, 26 | "sharing_token_expiry_seconds": 2592000, 27 | "cloud_download_threads": 8, 28 | "cloud_upload_threads": 2, 29 | "cloud_refresh_interval": 60, 30 | "optout_inmem_cache": false, 31 | "optout_synthetic_logs_enabled": false, 32 | "optout_data_dir": "/opt/uid2/operator-optout/", 33 | "optout_s3_folder": "optout/", 34 | "optout_bloom_filter_size": 8192, 35 | "optout_delta_rotate_interval": 300, 36 | "optout_delta_backtrack_in_days": 1, 37 | "optout_heap_default_capacity": 8192, 38 | "optout_max_partitions": 30, 39 | "optout_partition_interval": 86400, 40 | "client_side_token_generate": true, 41 | "client_side_token_generate_domain_name_check_enabled": false, 42 | "client_side_token_generate_log_invalid_http_origins": true, 43 | "salts_expired_shutdown_hours": 12, 44 | "store_refresh_stale_shutdown_hours": 12, 45 | "operator_type": "private", 46 | "enable_remote_config": true, 47 | "uid_instance_id_prefix": "local-private-operator" 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/service/TokenUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.service; 2 | 3 | import com.uid2.operator.model.IdentityEnvironment; 4 | import com.uid2.operator.model.IdentityScope; 5 | import com.uid2.operator.model.IdentityType; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.Arguments; 8 | import org.junit.jupiter.params.provider.MethodSource; 9 | 10 | import java.util.stream.Stream; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | class TokenUtilsTest { 15 | @ParameterizedTest 16 | @MethodSource("v4Metadata") 17 | void testEncodeV4Metadata(IdentityScope scope, IdentityType type, IdentityEnvironment environment, byte expectedMetadata) { 18 | byte metadata = TokenUtils.encodeV4Metadata(scope, type, environment); 19 | 20 | assertEquals(expectedMetadata, metadata); 21 | } 22 | 23 | private static Stream v4Metadata() { 24 | return Stream.of( 25 | Arguments.of(IdentityScope.UID2, IdentityType.Email, IdentityEnvironment.TEST, (byte) 0b00100000), 26 | Arguments.of(IdentityScope.UID2, IdentityType.Phone, IdentityEnvironment.TEST, (byte) 0b00100100), 27 | Arguments.of(IdentityScope.EUID, IdentityType.Email, IdentityEnvironment.TEST, (byte) 0b00110000), 28 | Arguments.of(IdentityScope.EUID, IdentityType.Phone, IdentityEnvironment.TEST, (byte) 0b00110100), 29 | 30 | Arguments.of(IdentityScope.UID2, IdentityType.Email, IdentityEnvironment.INTEG, (byte) 0b01100000), 31 | Arguments.of(IdentityScope.UID2, IdentityType.Phone, IdentityEnvironment.INTEG, (byte) 0b01100100), 32 | Arguments.of(IdentityScope.EUID, IdentityType.Email, IdentityEnvironment.INTEG, (byte) 0b01110000), 33 | Arguments.of(IdentityScope.EUID, IdentityType.Phone, IdentityEnvironment.INTEG, (byte) 0b01110100), 34 | 35 | Arguments.of(IdentityScope.UID2, IdentityType.Email, IdentityEnvironment.PROD, (byte) 0b10100000), 36 | Arguments.of(IdentityScope.UID2, IdentityType.Phone, IdentityEnvironment.PROD, (byte) 0b10100100), 37 | Arguments.of(IdentityScope.EUID, IdentityType.Email, IdentityEnvironment.PROD, (byte) 0b10110000), 38 | Arguments.of(IdentityScope.EUID, IdentityType.Phone, IdentityEnvironment.PROD, (byte) 0b10110100) 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/reader/ApiStoreReader.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.reader; 2 | 3 | import com.uid2.shared.cloud.DownloadCloudStorage; 4 | import com.uid2.shared.store.ScopedStoreReader; 5 | import com.uid2.shared.store.parser.Parser; 6 | import com.uid2.shared.store.parser.ParsingResult; 7 | import com.uid2.shared.store.scope.StoreScope; 8 | import io.vertx.core.json.JsonArray; 9 | import io.vertx.core.json.JsonObject; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.ByteArrayInputStream; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.nio.charset.StandardCharsets; 17 | 18 | public class ApiStoreReader extends ScopedStoreReader { 19 | private static final Logger LOGGER = LoggerFactory.getLogger(ApiStoreReader.class); 20 | 21 | public ApiStoreReader(DownloadCloudStorage fileStreamProvider, StoreScope scope, Parser parser, String dataTypeName) { 22 | super(fileStreamProvider, scope, parser, dataTypeName); 23 | } 24 | 25 | 26 | public long loadContent(JsonObject contents) throws Exception { 27 | return loadContent(contents, dataTypeName); 28 | } 29 | 30 | @Override 31 | public long loadContent(JsonObject contents, String dataType) throws IOException { 32 | if (contents == null) { 33 | throw new IllegalArgumentException(String.format("No contents provided for loading data type %s, cannot load content", dataType)); 34 | } 35 | 36 | try { 37 | JsonArray dataArray = contents.getJsonArray(dataType); 38 | if (dataArray == null) { 39 | throw new IllegalArgumentException(String.format("No array of type: %s, found in the contents", dataType)); 40 | } 41 | 42 | String jsonString = dataArray.toString(); 43 | InputStream inputStream = new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)); 44 | 45 | ParsingResult parsed = parser.deserialize(inputStream); 46 | latestSnapshot.set(parsed.getData()); 47 | 48 | final int count = parsed.getCount(); 49 | latestEntryCount.set(count); 50 | LOGGER.info(String.format("Loaded %d %s", count, dataType)); 51 | return count; 52 | } catch (Exception e) { 53 | LOGGER.error(String.format("Unable to load %s", dataType)); 54 | throw e; 55 | } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /conf/local-e2e-public-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_instances": 1, 3 | "storage_mock": true, 4 | "core_attest_url": "http://localhost:8088/attest", 5 | "core_api_token": "UID2-O-L-999-dp9Dt0.JVoGpynN4J8nMA7FxmzsavxJa8B9H74y9xdEE=", 6 | "sites_metadata_path": "http://localhost:8088/sites/refresh", 7 | "clients_metadata_path": "http://localhost:8088/clients/refresh", 8 | "client_side_keypairs_metadata_path": "http://localhost:8088/client_side_keypairs/refresh", 9 | "keys_metadata_path": "http://localhost:8088/key/refresh", 10 | "keys_acl_metadata_path": "http://localhost:8088/key/acl/refresh", 11 | "keysets_metadata_path": "http://localhost:8088/key/keyset/refresh", 12 | "keyset_keys_metadata_path": "http://localhost:8088/key/keyset-keys/refresh", 13 | "salts_metadata_path": "http://localhost:8088/salt/refresh", 14 | "services_metadata_path": "http://localhost:8088/services/refresh", 15 | "service_links_metadata_path": "http://localhost:8088/service_links/refresh", 16 | "cloud_encryption_keys_metadata_path": "http://localhost:8088/cloud_encryption_keys/retrieve", 17 | "runtime_config_metadata_path": "http://localhost:8088/operator/config", 18 | "encrypted_files": true, 19 | "identity_token_expires_after_seconds": 3600, 20 | "refresh_token_expires_after_seconds": 86400, 21 | "refresh_identity_token_after_seconds": 900, 22 | "refresh_token_v3": true, 23 | "identity_v3": false, 24 | "identity_scope": "uid2", 25 | "enable_v2_encryption": true, 26 | "sharing_token_expiry_seconds": 2592000, 27 | "cloud_download_threads": 8, 28 | "cloud_upload_threads": 2, 29 | "cloud_refresh_interval": 60, 30 | "optout_inmem_cache": false, 31 | "optout_synthetic_logs_enabled": false, 32 | "optout_data_dir": "/opt/uid2/operator-optout/", 33 | "optout_s3_folder": "optout/", 34 | "optout_bloom_filter_size": 8192, 35 | "optout_delta_rotate_interval": 300, 36 | "optout_delta_backtrack_in_days": 1, 37 | "optout_heap_default_capacity": 8192, 38 | "optout_max_partitions": 30, 39 | "optout_partition_interval": 86400, 40 | "client_side_token_generate": true, 41 | "client_side_token_generate_domain_name_check_enabled": true, 42 | "key_sharing_endpoint_provide_app_names": true, 43 | "client_side_token_generate_log_invalid_http_origins": true, 44 | "salts_expired_shutdown_hours": 12, 45 | "store_refresh_stale_shutdown_hours": 12, 46 | "operator_type": "public", 47 | "disable_optout_token": true, 48 | "enable_remote_config": true, 49 | "uid_instance_id_prefix": "local-public-operator" 50 | } 51 | -------------------------------------------------------------------------------- /src/main/resources/com.uid2.core/test/keyset_keys/keyset_keys.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id" : 101, 4 | "keyset_id" : -1, 5 | "created" : 1622691044, 6 | "activates" : 1687635529, 7 | "expires" : 4088629662, 8 | "secret" : "vPdQIoN1nTwXIQoH4/GcHOAct7ymyrGUKXNQhgmq+E8=" 9 | }, 10 | { 11 | "id" : 102, 12 | "keyset_id" : -2, 13 | "created" : 1633990810, 14 | "activates" : 1687635529, 15 | "expires" : 4088629662, 16 | "secret" : "RgyxkP4yP1gYhCINq7O9PxM6jX+etPqSQluZxjB1aG8=" 17 | }, 18 | { 19 | "id" : 103, 20 | "keyset_id" : 2, 21 | "created" : 1633991166, 22 | "activates" : 1687635529, 23 | "expires" : 4088629662, 24 | "secret" : "c2yMYeezHfDvZ6haknCFaj0t+eAEJp/C/uh+fUAm1fo=" 25 | }, 26 | { 27 | "id" : 104, 28 | "keyset_id" : 501, 29 | "created" : 1633991166, 30 | "activates" : 1634077566, 31 | "expires" : 4088629662, 32 | "secret" : "bVZZOwCuO0fUf2P6CrwaY23erMErVQiMkGgpy2OMSxc=" 33 | }, 34 | { 35 | "id": 5, 36 | "keyset_id": 502, 37 | "secret": "vPdQIoN1nTwXIQoH4/GcHOAct7ymyrGUKXNQhgmq+E8=", 38 | "created": 1609459200, 39 | "activates": 1609469200, 40 | "expires": 4088629662 41 | }, 42 | { 43 | "id": 6, 44 | "keyset_id": 503, 45 | "secret": "RgyxkP4yP1gYhCINq7O9PxM6jX+etPqSQluZxjB1aG8=", 46 | "created": 1609459200, 47 | "activates": 1609469200, 48 | "expires": 4088629662 49 | }, 50 | { 51 | "id": 7, 52 | "keyset_id": 601, 53 | "secret": "c2yMYeezHfDvZ6haknCFaj0t+eAEJp/C/uh+fUAm1fo=", 54 | "created": 1609459200, 55 | "activates": 1609469200, 56 | "expires": 4088629662 57 | }, 58 | { 59 | "id": 8, 60 | "keyset_id": 602, 61 | "secret": "bVZZOwCuO0fUf2P6CrwaY23erMErVQiMkGgpy2OMSxc=", 62 | "created": 1609459200, 63 | "activates": 1609469200, 64 | "expires": 4088629662 65 | }, 66 | { 67 | "id": 9, 68 | "keyset_id": 701, 69 | "secret": "vPdQIoN1nTwXIQoH4/GcHOAct7ymyrGUKXNQhgmq+E8=", 70 | "created": 1609459200, 71 | "activates": 1609469200, 72 | "expires": 4088629662 73 | }, 74 | { 75 | "id": 10, 76 | "keyset_id": 801, 77 | "secret": "RgyxkP4yP1gYhCINq7O9PxM6jX+etPqSQluZxjB1aG8=", 78 | "created": 1609459200, 79 | "activates": 1609469200, 80 | "expires": 4088629662 81 | }, 82 | { 83 | "id": 11, 84 | "keyset_id": 901, 85 | "secret": "YgyxOX4yX1gYhCINq7O9XxM6jX+etXqSXluZxjB1aG1=", 86 | "created": 1713225363, 87 | "activates": 1713250563, 88 | "expires": 1715756163 89 | } 90 | ] 91 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/store/RuntimeConfigStore.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.store; 2 | 3 | import com.uid2.shared.Utils; 4 | import com.uid2.shared.cloud.DownloadCloudStorage; 5 | import com.uid2.shared.store.reader.IMetadataVersionedStore; 6 | import io.vertx.core.json.JsonObject; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.InputStream; 11 | import java.util.concurrent.atomic.AtomicReference; 12 | 13 | public class RuntimeConfigStore implements IConfigStore, IMetadataVersionedStore { 14 | private static final Logger logger = LoggerFactory.getLogger(RuntimeConfigStore.class); 15 | private final DownloadCloudStorage fileStreamProvider; 16 | private final String configMetadataPath; 17 | private final AtomicReference config = new AtomicReference<>(); 18 | 19 | public RuntimeConfigStore(DownloadCloudStorage fileStreamProvider, String configMetadataPath) { 20 | this.fileStreamProvider = fileStreamProvider; 21 | this.configMetadataPath = configMetadataPath; 22 | } 23 | 24 | @Override 25 | public JsonObject getMetadata() throws Exception { 26 | try (InputStream s = this.fileStreamProvider.download(configMetadataPath)) { 27 | return Utils.toJsonObject(s); 28 | } 29 | } 30 | 31 | @Override 32 | public long getVersion(JsonObject metadata) { 33 | return metadata.getLong("version"); 34 | } 35 | 36 | @Override 37 | public long loadContent(JsonObject metadata) throws Exception { 38 | if (metadata == null) { 39 | throw new RuntimeException("Metadata is null"); 40 | } 41 | 42 | // The config is returned as part of the metadata itself. 43 | JsonObject runtimeConfig = metadata.getJsonObject("runtime_config"); 44 | 45 | logger.info("Received new config {}", runtimeConfig == null ? null : runtimeConfig.toString()); 46 | if (runtimeConfig == null) { 47 | throw new RuntimeException("Runtime config is null"); 48 | } 49 | 50 | RuntimeConfig newRuntimeConfig = runtimeConfig.mapTo(RuntimeConfig.class); 51 | this.config.set(newRuntimeConfig); 52 | logger.info("Successfully updated runtime config"); 53 | return 1; 54 | } 55 | 56 | @Override 57 | public void loadContent() throws Exception { 58 | this.loadContent(this.getMetadata()); 59 | } 60 | 61 | @Override 62 | public RuntimeConfig getConfig() { 63 | return this.config.get(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/util/RoutingContextUtil.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.util; 2 | 3 | import com.uid2.shared.auth.ClientKey; 4 | import com.uid2.shared.auth.IAuthorizable; 5 | import com.uid2.shared.auth.IAuthorizableProvider; 6 | import com.uid2.shared.middleware.AuthMiddleware; 7 | import io.vertx.ext.web.RoutingContext; 8 | 9 | import java.net.URI; 10 | import java.net.URISyntaxException; 11 | 12 | public final class RoutingContextUtil { 13 | private static final String BEARER_TOKEN_PREFIX = "bearer "; 14 | private static final String UNKNOWN = "unknown"; 15 | 16 | private RoutingContextUtil() { 17 | } 18 | 19 | public static String getApiContact(RoutingContext rc, IAuthorizableProvider authKeyStore) { 20 | try { 21 | final String authHeaderValue = rc.request().getHeader("Authorization"); 22 | final String authKey = extractBearerToken(authHeaderValue); 23 | final IAuthorizable profile = authKeyStore.get(authKey); 24 | String apiContact = profile.getContact(); 25 | return apiContact == null ? UNKNOWN : apiContact; 26 | } catch (Exception ex) { 27 | return UNKNOWN; 28 | } 29 | } 30 | 31 | public static Integer getSiteId(RoutingContext rc) { 32 | return AuthMiddleware.getAuthClient(ClientKey.class, rc).getSiteId(); 33 | } 34 | 35 | public static String getPath(RoutingContext rc) { 36 | try { 37 | // If the current route is a known path, extract the full path from the request URI 38 | if (rc.currentRoute().getPath() != null) { 39 | return new URI(rc.request().absoluteURI()).getPath(); 40 | } 41 | } catch (NullPointerException | URISyntaxException ex) { 42 | // RoutingContextImplBase has a bug: context.currentRoute() throws with NullPointerException when called from bodyEndHandler for StaticHandlerImpl.sendFile() 43 | } 44 | 45 | return UNKNOWN; 46 | } 47 | 48 | public static String extractBearerToken(final String headerValue) { 49 | if (headerValue == null) { 50 | return null; 51 | } 52 | 53 | final String v = headerValue.trim(); 54 | if (v.length() < BEARER_TOKEN_PREFIX.length()) { 55 | return null; 56 | } 57 | 58 | final String givenPrefix = v.substring(0, BEARER_TOKEN_PREFIX.length()); 59 | 60 | if (!BEARER_TOKEN_PREFIX.equalsIgnoreCase(givenPrefix)) { 61 | return null; 62 | } 63 | return v.substring(BEARER_TOKEN_PREFIX.length()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/store/OptOutCloudStorage.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.store; 2 | 3 | import com.uid2.shared.Utils; 4 | import com.uid2.shared.attest.UidOptOutClient; 5 | import com.uid2.shared.cloud.CloudStorageException; 6 | import com.uid2.shared.cloud.URLStorageWithMetadata; 7 | import com.uid2.shared.optout.OptOutMetadata; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.net.Proxy; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | 18 | public class OptOutCloudStorage extends URLStorageWithMetadata { 19 | private static final Logger LOGGER = LoggerFactory.getLogger(OptOutCloudStorage.class); 20 | 21 | private final UidOptOutClient uidOptOutClient; 22 | private final String metadataPath; 23 | 24 | public OptOutCloudStorage(UidOptOutClient uidOptOutClient, String metadataPath) { 25 | this(uidOptOutClient, metadataPath, null); 26 | } 27 | 28 | public OptOutCloudStorage(UidOptOutClient uidOptOutClient, String metadataPath, Proxy proxy) { 29 | super(proxy); 30 | this.uidOptOutClient = uidOptOutClient; 31 | this.metadataPath = metadataPath; 32 | } 33 | 34 | @Override 35 | protected List extractListFromMetadata() throws CloudStorageException { 36 | try (InputStream input = this.uidOptOutClient.download(metadataPath)) { 37 | String jsonString = Utils.readToEnd(input); 38 | if (jsonString != null && !jsonString.isEmpty()) { 39 | OptOutMetadata m = OptOutMetadata.fromJsonString(jsonString); 40 | if (m != null) { 41 | return m.optoutLogs.stream().map(o -> o.location).collect(Collectors.toList()); 42 | } else { 43 | LOGGER.warn("Unable to parse the OptOut metadata into OptOutMetaData type. Start of the response from OptOut: {}", jsonString.substring(0, jsonString.length() > 50 ? 50 : jsonString.length())); 44 | throw new CloudStorageException("Invalid response returned from OptOut."); 45 | } 46 | } else { 47 | LOGGER.warn("Empty string returned from UidOptOutClient. Unable to read OptOut metadata"); 48 | return new ArrayList(); 49 | } 50 | } catch (Exception e) { 51 | // Intentionally not logging the exception as it may contain sensitive URLs 52 | throw new CloudStorageException("extractListFromMetadata error."); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /scripts/aws/pipeline/amazonlinux.Dockerfile: -------------------------------------------------------------------------------- 1 | # https://gist.github.com/toricls/e17c7f2f1c024cc368dcd860804194f5 2 | FROM amazonlinux:2 3 | 4 | RUN yum -y update 5 | # systemd is not a hard requirement for Amazon ECS Anywhere, but the installation script currently only supports systemd to run. 6 | # Amazon ECS Anywhere can be used without systemd, if you set up your nodes and register them into your ECS cluster **without** the installation script. 7 | RUN yum -y groupinstall "Development Tools" 8 | RUN yum -y install systemd vim-common wget git tar 9 | RUN yum clean all 10 | 11 | RUN yum -y install cmake cmake3 12 | RUN alternatives --install /usr/local/bin/cmake cmake /usr/bin/cmake 10 \ 13 | --slave /usr/local/bin/ctest ctest /usr/bin/ctest \ 14 | --slave /usr/local/bin/cpack cpack /usr/bin/cpack \ 15 | --slave /usr/local/bin/ccmake ccmake /usr/bin/ccmake \ 16 | --family cmake 17 | RUN alternatives --install /usr/local/bin/cmake cmake /usr/bin/cmake3 20 \ 18 | --slave /usr/local/bin/ctest ctest /usr/bin/ctest3 \ 19 | --slave /usr/local/bin/cpack cpack /usr/bin/cpack3 \ 20 | --slave /usr/local/bin/ccmake ccmake /usr/bin/ccmake3 \ 21 | --family cmake 22 | 23 | RUN cd /lib/systemd/system/sysinit.target.wants/; \ 24 | for i in *; do [ $i = systemd-tmpfiles-setup.service ] || rm -f $i; done 25 | RUN rm -f /lib/systemd/system/multi-user.target.wants/* \ 26 | /etc/systemd/system/*.wants/* \ 27 | /lib/systemd/system/local-fs.target.wants/* \ 28 | /lib/systemd/system/sockets.target.wants/*udev* \ 29 | /lib/systemd/system/sockets.target.wants/*initctl* \ 30 | /lib/systemd/system/basic.target.wants/* \ 31 | /lib/systemd/system/anaconda.target.wants/* 32 | 33 | RUN amazon-linux-extras install -y epel docker aws-nitro-enclaves-cli 34 | RUN yum -y install aws-nitro-enclaves-cli-devel 35 | 36 | RUN systemctl enable docker 37 | 38 | RUN wget https://www.inet.no/dante/files/dante-1.4.3.tar.gz \ 39 | && echo "418a065fe1a4b8ace8fbf77c2da269a98f376e7115902e76cda7e741e4846a5d dante-1.4.3.tar.gz" > dante_checksum \ 40 | && sha256sum --check dante_checksum \ 41 | && tar -xf dante-1.4.3.tar.gz \ 42 | && cd dante-1.4.3; ./configure; make; cd .. \ 43 | && cp dante-1.4.3/sockd/sockd ./ 44 | 45 | RUN git clone https://github.com/IABTechLab/uid2-aws-enclave-vsockproxy.git \ 46 | && mkdir uid2-aws-enclave-vsockproxy/build \ 47 | && cd uid2-aws-enclave-vsockproxy/build; cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo; make; cd ../.. \ 48 | && cp uid2-aws-enclave-vsockproxy/build/vsock-bridge/src/vsock-bridge ./vsockpx 49 | 50 | COPY ./scripts/aws/pipeline/aws_nitro_eif.sh /aws_nitro_eif.sh 51 | 52 | CMD ["/usr/sbin/init"] 53 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/monitoring/StatsCollectorHandler.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.monitoring; 2 | 3 | import com.uid2.operator.model.StatsCollectorMessageItem; 4 | import com.uid2.shared.Const; 5 | import com.uid2.shared.auth.ClientKey; 6 | import com.uid2.shared.middleware.AuthMiddleware; 7 | import io.vertx.core.Handler; 8 | import io.vertx.core.Vertx; 9 | import io.vertx.ext.web.RoutingContext; 10 | 11 | public class StatsCollectorHandler implements Handler { 12 | private final IStatsCollectorQueue _statCollectorQueue; 13 | private final Vertx vertx; 14 | 15 | 16 | public StatsCollectorHandler(IStatsCollectorQueue _statCollectorQueue, Vertx vertx) { 17 | this._statCollectorQueue = _statCollectorQueue; 18 | this.vertx = vertx; 19 | } 20 | 21 | @Override 22 | public void handle(RoutingContext routingContext) { 23 | if (routingContext == null) { 24 | throw new NullPointerException(); 25 | } 26 | 27 | //setAuthClient() has not yet been called, so getAuthClient() would return null. This is resolved by using addBodyEndHandler() 28 | routingContext.addBodyEndHandler(v -> addStatsMessageToQueue(routingContext)); 29 | 30 | routingContext.next(); //previously this next() call was at the top of this function. In V1 APIs, that happened to be an auth handler, but in v2 it was a bodyHandler. Moving next() to the end of the function ensures we don't accidentally depend on a handler that comes later. 31 | } 32 | 33 | private void addStatsMessageToQueue(RoutingContext routingContext) { 34 | final String path = routingContext.request().path(); 35 | final String referer = routingContext.request().headers().get("Referer"); 36 | final ClientKey clientKey = (ClientKey) AuthMiddleware.getAuthClient(routingContext); 37 | final String apiContact = clientKey == null ? null : clientKey.getContact(); 38 | final Integer siteId = clientKey == null ? null : clientKey.getSiteId(); 39 | final String clientVersion = getClientVersion(routingContext); 40 | 41 | final StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem(path, referer, apiContact, siteId, clientVersion); 42 | 43 | _statCollectorQueue.enqueue(vertx, messageItem); 44 | } 45 | 46 | private String getClientVersion(RoutingContext routingContext) { 47 | String clientVersion = routingContext.request().headers().get(Const.Http.ClientVersionHeader); 48 | if (clientVersion == null) { 49 | clientVersion = !routingContext.queryParam("client").isEmpty() ? routingContext.queryParam("client").get(0) : null; 50 | } 51 | return clientVersion; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UID2 Operator 2 | 3 | 4 | The UID 2 Project is subject to Tech Lab IPR’s Policy and is managed by the IAB Tech Lab Addressability Working Group and Privacy & Rearc Commit Group. Please review the governance rules [here](https://github.com/IABTechLab/uid2-core/blob/master/Software%20Development%20and%20Release%20Procedures.md) 5 | 6 | ## Building 7 | 8 | To run unit tests: 9 | 10 | ``` 11 | mvn clean test 12 | ``` 13 | 14 | To package application: 15 | 16 | ``` 17 | mvn package 18 | ``` 19 | 20 | To run application: 21 | 22 | - use `conf/local-config.json` to run standalone operator service 23 | for local debugging, which loads salts, keys and optout from mock storage provider, and doesn't communicate with uid2-core and uid2-optout. 24 | 25 | ``` 26 | mvn clean compile exec:java -Dvertx-config-path=conf/local-config.json 27 | ``` 28 | 29 | - use `conf/integ-config.json` to run optout operator that 30 | integrates with uid2-core (default runs on `localhost:8088`) and uid2-optout (default runs on `localhost:8081`) 31 | 32 | ``` 33 | mvn clean compile exec:java -Dvertx-config-path=conf/integ-config.json 34 | ``` 35 | ## Local deployment/testing on Docker 36 | 1. In [Dockerfile](Dockerfile), change the line 37 | ``` 38 | COPY ./conf/default-config.json /app/conf/ 39 | ``` 40 | to: 41 | ``` 42 | COPY ./conf/docker-config.json /app/conf/local-config.json 43 | ``` 44 | 2. Run ```mvn package``` 45 | 3. Go to `pom.xml` and find the version wrapped under `` tag 46 | 4. Run ```docker build -t uid2-operator --build-arg JAR_VERSION={version you find in step 3} .``` 47 | 5. Run ```docker run -it -p 8080:8080 uid2-operator:latest ``` 48 | 6. Go to postman and test on endpoint `http://localhost:8080/v1/token/generate?email=exampleuser4@test.uidapi.com` 49 | 50 | ## Running vulnerability scanning locally 51 | The Github actions will run Trivy for vulnerability scanning as part of the build-and-test and publish-docker pipelines. However, they can also be run locally to aid in resolving these. 52 | Trivy only runs on Linux, so you will need to install WSL. 53 | 54 | ### Installation 55 | Once WSL is installed, follow these instructions: 56 | 57 | https://aquasecurity.github.io/trivy/v0.35/getting-started/installation/ 58 | 59 | Once installed to check the code only (which is what the build-and-test pipeline does), run this command from the root directory: 60 | ``` 61 | wsl trivy fs . 62 | ``` 63 | 64 | To check the docker image (which is what the publish-docker pipeline does), build the docker image as outlined above and then run this command: 65 | ``` 66 | wsl trivy image 67 | ``` 68 | where ` is the built docker image you want to scan (uid2-latest in the example above). 69 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/util/DomainNameCheckUtil.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.util; 2 | 3 | import com.google.common.net.InternetDomainName; 4 | import com.uid2.operator.vertx.UIDOperatorVerticle; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.net.MalformedURLException; 9 | import java.net.URL; 10 | import java.util.Set; 11 | 12 | public class DomainNameCheckUtil { 13 | private static final Logger LOGGER = LoggerFactory.getLogger(DomainNameCheckUtil.class); 14 | private static final String LOCALHOST_DOMAIN_NAME = "localhost"; 15 | 16 | public static boolean isDomainNameAllowed(String origin, Set allowedDomainNameSet) { 17 | String topLevelDomainName; 18 | try { 19 | topLevelDomainName = getTopLevelDomainName(origin); 20 | } catch (MalformedURLException e) { 21 | LOGGER.info("isDomainNameAllowed Origin=" + origin + " is malformed URL. Rejecting."); 22 | return false; 23 | } catch (Exception e) { 24 | LOGGER.info("isDomainNameAllowed Origin" + origin + " not producing top level domain name correctly:." + e + " Rejecting."); 25 | return false; 26 | } 27 | 28 | if (topLevelDomainName == null) { 29 | LOGGER.info("isDomainNameAllowed Origin" + origin + " returns null top level domain name value. Rejecting."); 30 | return false; 31 | } 32 | 33 | return allowedDomainNameSet.contains(topLevelDomainName); 34 | } 35 | 36 | // this method can be reused down the line by admin service 37 | //to make sure admin and operator use the same method to parse top level domain name 38 | //so we can catch parsing error when we first add the publisher's domain name into the allowed list in admin 39 | //service (not when operator is starting getting CSTG requests from publisher websites) 40 | public static String getTopLevelDomainName(String origin) throws MalformedURLException { 41 | URL url = new URL(origin); 42 | 43 | //InternetDomainName will normalise the domain name to lower case already 44 | InternetDomainName name = InternetDomainName.from(url.getHost()); 45 | //if the domain name has a proper TLD suffix 46 | if (name.isUnderPublicSuffix()) { 47 | return name.topPrivateDomain().toString(); 48 | } 49 | //we make an exception for localhost for testing purpose only 50 | if (name.hasParent() && name.parent().toString().equals(LOCALHOST_DOMAIN_NAME)) { 51 | return LOCALHOST_DOMAIN_NAME; 52 | } else if (name.toString().equals(LOCALHOST_DOMAIN_NAME)) { 53 | return LOCALHOST_DOMAIN_NAME; 54 | } 55 | return null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/model/RefreshResponse.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.model; 2 | 3 | import java.time.Duration; 4 | 5 | public class RefreshResponse { 6 | 7 | public static RefreshResponse Invalid = new RefreshResponse(Status.Invalid, IdentityTokens.LogoutToken); 8 | public static RefreshResponse Optout = new RefreshResponse(Status.Optout, IdentityTokens.LogoutToken); 9 | public static RefreshResponse Expired = new RefreshResponse(Status.Expired, IdentityTokens.LogoutToken); 10 | public static RefreshResponse Deprecated = new RefreshResponse(Status.Deprecated, IdentityTokens.LogoutToken); 11 | public static RefreshResponse NoActiveKey = new RefreshResponse(Status.NoActiveKey, IdentityTokens.LogoutToken); 12 | private final Status status; 13 | private final IdentityTokens tokens; 14 | private final Duration durationSinceLastRefresh; 15 | private final boolean isCstg; 16 | 17 | private RefreshResponse(Status status, IdentityTokens tokens, Duration durationSinceLastRefresh, boolean isCstg) { 18 | this.status = status; 19 | this.tokens = tokens; 20 | this.durationSinceLastRefresh = durationSinceLastRefresh; 21 | this.isCstg = isCstg; 22 | } 23 | 24 | private RefreshResponse(Status status, IdentityTokens tokens) { 25 | this(status, tokens, null, false); 26 | } 27 | 28 | public static RefreshResponse createRefreshedResponse(IdentityTokens tokens, Duration durationSinceLastRefresh, boolean isCstg) { 29 | return new RefreshResponse(Status.Refreshed, tokens, durationSinceLastRefresh, isCstg); 30 | } 31 | 32 | public Status getStatus() { 33 | return status; 34 | } 35 | 36 | public IdentityTokens getTokens() { 37 | return tokens; 38 | } 39 | 40 | public Duration getDurationSinceLastRefresh() { 41 | return durationSinceLastRefresh; 42 | } 43 | 44 | public boolean isCstg() { return isCstg;} 45 | 46 | public boolean isRefreshed() { 47 | return Status.Refreshed.equals(this.status); 48 | } 49 | 50 | public boolean isOptOut() { 51 | return Status.Optout.equals(this.status); 52 | } 53 | 54 | public boolean isInvalidToken() { 55 | return Status.Invalid.equals(this.status); 56 | } 57 | 58 | public boolean isDeprecated() { 59 | return Status.Deprecated.equals(this.status); 60 | } 61 | 62 | public boolean isExpired() { 63 | return Status.Expired.equals(this.status); 64 | } 65 | 66 | public boolean noActiveKey() { 67 | return Status.NoActiveKey.equals(this.status); 68 | } 69 | 70 | public enum Status { 71 | Refreshed, 72 | Invalid, 73 | Optout, 74 | Expired, 75 | Deprecated, 76 | NoActiveKey 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/monitoring/OperatorMetrics.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.monitoring; 2 | 3 | import com.uid2.operator.model.KeyManager; 4 | import com.uid2.shared.model.KeysetKey; 5 | import com.uid2.shared.model.SaltEntry; 6 | import com.uid2.shared.store.salt.ISaltProvider; 7 | import io.micrometer.core.instrument.Gauge; 8 | 9 | import java.time.Instant; 10 | import java.util.*; 11 | 12 | import static io.micrometer.core.instrument.Metrics.globalRegistry; 13 | 14 | public class OperatorMetrics { 15 | private Set encryptionKeyGaugesBySiteId = new HashSet<>(); 16 | private ISaltProvider saltProvider; 17 | private KeyManager keyManager; 18 | 19 | public OperatorMetrics(KeyManager keyManager, ISaltProvider saltProvider) { 20 | this.keyManager = keyManager; 21 | this.saltProvider = saltProvider; 22 | } 23 | 24 | public void setup() { 25 | // salts 26 | Gauge 27 | .builder("uid2_second_level_salt_last_updated_max", () -> 28 | Arrays.stream(saltProvider.getSnapshot(Instant.now()).getAllRotatingSalts()) 29 | .map(SaltEntry::lastUpdated).max(Long::compare).orElse(null)) 30 | .description("max last updated timestamp within currently effective second level salts") 31 | .register(globalRegistry); 32 | Gauge 33 | .builder("uid2_second_level_salt_last_updated_min", () -> 34 | Arrays.stream(saltProvider.getSnapshot(Instant.now()).getAllRotatingSalts()) 35 | .map(SaltEntry::lastUpdated).min(Long::compare).orElse(null)) 36 | .description("max last updated timestamp within currently effective second level salts") 37 | .register(globalRegistry); 38 | 39 | update(); 40 | } 41 | 42 | public void update() { 43 | keyManager.getAllKeysets().values().stream() 44 | .map(k -> k.getSiteId()).distinct() 45 | .filter(s -> !encryptionKeyGaugesBySiteId.contains(s)) 46 | .forEachOrdered(siteId -> { 47 | encryptionKeyGaugesBySiteId.add(siteId); 48 | Gauge 49 | .builder("uid2_encryption_key_activates", () -> { 50 | final Instant now = Instant.now(); 51 | final KeysetKey key = keyManager.getActiveKeyBySiteId(siteId, now); 52 | return key == null ? null : key.getActivates().getEpochSecond(); 53 | }) 54 | .description("age of encryption keys by site id") 55 | .tag("site_id", String.valueOf(siteId)) 56 | .register(globalRegistry); 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/ExtendedUIDOperatorVerticle.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator; 2 | 3 | import com.uid2.operator.model.KeyManager; 4 | import com.uid2.operator.monitoring.IStatsCollectorQueue; 5 | import com.uid2.operator.service.IUIDOperatorService; 6 | import com.uid2.operator.service.SecureLinkValidatorService; 7 | import com.uid2.operator.store.IConfigStore; 8 | import com.uid2.operator.store.IOptOutStore; 9 | import com.uid2.operator.vertx.UIDOperatorVerticle; 10 | import com.uid2.shared.audit.UidInstanceIdProvider; 11 | import com.uid2.shared.store.*; 12 | import com.uid2.shared.store.salt.ISaltProvider; 13 | import io.vertx.core.Handler; 14 | import io.vertx.core.json.JsonObject; 15 | 16 | import java.time.Clock; 17 | import java.time.Instant; 18 | import java.util.Map; 19 | import java.util.Set; 20 | 21 | //An extended UIDOperatorVerticle to expose classes for testing purposes 22 | public class ExtendedUIDOperatorVerticle extends UIDOperatorVerticle { 23 | public ExtendedUIDOperatorVerticle(IConfigStore configStore, 24 | JsonObject config, 25 | boolean clientSideTokenGenerate, 26 | ISiteStore siteProvider, 27 | IClientKeyProvider clientKeyProvider, 28 | IClientSideKeypairStore clientSideKeypairProvider, 29 | KeyManager keyManager, 30 | ISaltProvider saltProvider, 31 | IOptOutStore optOutStore, 32 | Clock clock, 33 | IStatsCollectorQueue statsCollectorQueue, 34 | SecureLinkValidatorService secureLinkValidationService, 35 | Handler saltRetrievalResponseHandler, 36 | UidInstanceIdProvider uidInstanceIdProvider) { 37 | super(configStore, config, clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, keyManager, saltProvider, optOutStore, clock, statsCollectorQueue, secureLinkValidationService, saltRetrievalResponseHandler, uidInstanceIdProvider); 38 | } 39 | 40 | public IUIDOperatorService getIdService() { 41 | return this.idService; 42 | } 43 | 44 | public void setKeySharingEndpointProvideAppNames(boolean enable) { 45 | this.keySharingEndpointProvideAppNames = enable; 46 | } 47 | 48 | public void setLastInvalidOriginProcessTime(Instant lastInvalidOriginProcessTime) { 49 | this.lastInvalidOriginProcessTime = lastInvalidOriginProcessTime; 50 | } 51 | 52 | public void setSiteIdToInvalidOriginsAndAppNames(Map> siteIdToInvalidOriginsAndAppNames) { 53 | this.siteIdToInvalidOriginsAndAppNames = siteIdToInvalidOriginsAndAppNames; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/util/DomainNameCheckUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.util; 2 | 3 | import org.junit.jupiter.params.ParameterizedTest; 4 | import org.junit.jupiter.params.provider.ValueSource; 5 | 6 | import java.util.Set; 7 | 8 | import static com.uid2.operator.util.DomainNameCheckUtil.isDomainNameAllowed; 9 | import static org.junit.jupiter.api.Assertions.assertFalse; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | class DomainNameCheckUtilTest { 13 | @ParameterizedTest 14 | @ValueSource(strings = { 15 | "http://examplewebsite.com", 16 | "https://examplewebsite.com", 17 | "https://abc.examplewebsite.com:8080", 18 | "https://abc.examplewebsite.com:8080/", 19 | "https://abc.eXampleWebsIte.com:8080/", 20 | "https://abc.exAmplewEbsite.com:8080/blahh/a.html", 21 | 22 | "http://e-wb.org", 23 | "https://e-wb.org", 24 | "https://abc.e-wb.org:8080", 25 | "https://abc.e-wb.org:8080/", 26 | "https://abc.e-Wb.org:8080/", 27 | "https://abc.e-wb.org:8080/blahh/a.html", 28 | 29 | "http://aussiedomain.id.au", 30 | "https://aussiedomain.id.au/head.html" 31 | }) 32 | void testDomainNameCheckSuccess(String origin) { 33 | Set allowedDomainNamesForProd = Set.of("examplewebsite.com","e-wb.org","aussiedomain.id.au"); 34 | 35 | assertTrue(isDomainNameAllowed(origin, allowedDomainNamesForProd)); 36 | } 37 | 38 | @ParameterizedTest 39 | @ValueSource(strings = { 40 | "http://localhost", 41 | "https://localhost:8080/", 42 | "https://abc.localhost:8080", 43 | "https://abc.localhost:8080/", 44 | "https://abc.locaLHost:8080/", 45 | "https://abc.localhost:8080/blahh/a.html" 46 | }) 47 | void testLocalhostDomainNameCheck(String origin) { 48 | Set allowedDomainNamesForTesting = Set.of("examplewebsite.com", "e-wb.org", "localhost"); 49 | 50 | assertTrue(isDomainNameAllowed(origin, allowedDomainNamesForTesting)); 51 | } 52 | 53 | @ParameterizedTest 54 | @ValueSource(strings = { 55 | // Malformed URLs 56 | "examplewebsite.com", 57 | "examplewebsite.com:999999", 58 | "abc:examplewebsite.com", 59 | "/:$2231examplewebsite.com", 60 | "/:$2231examplewebsite.com/23423/sfs.html", 61 | 62 | // Disallowed domain names 63 | "http://boohoo.id.au", 64 | "https://blah12.com", 65 | "http://123.boohoo.id.au", 66 | "https://456.blah12.com" 67 | }) 68 | void testDomainNameCheckFailure(String origin) { 69 | Set allowedDomainNamesForProd = Set.of("examplewebsite.com", "e-wb.org", "aussiedomain.id.au"); 70 | 71 | assertFalse(isDomainNameAllowed(origin, allowedDomainNamesForProd)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/uid2/operator/EUIDOperatorVerticleTest.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import com.uid2.operator.model.IdentityScope; 6 | import com.uid2.shared.auth.Role; 7 | 8 | import io.vertx.core.Vertx; 9 | import io.vertx.core.json.JsonObject; 10 | import io.vertx.junit5.VertxTestContext; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | class EUIDOperatorVerticleTest extends UIDOperatorVerticleTest { 15 | @Override 16 | protected IdentityScope getIdentityScope() { 17 | return IdentityScope.EUID; 18 | } 19 | 20 | @Override 21 | protected boolean useRawUidV3() { 22 | return true; 23 | } 24 | 25 | @Override 26 | protected void addAdditionalTokenGenerateParams(JsonObject payload) { 27 | if (payload != null && !payload.containsKey("tcf_consent_string")) { 28 | payload.put("tcf_consent_string", "CPehNtWPehNtWABAMBFRACBoALAAAEJAAIYgAKwAQAKgArABAAqAAA"); 29 | } 30 | } 31 | 32 | @Test 33 | void badRequestOnInvalidTcfConsent(Vertx vertx, VertxTestContext testContext) { 34 | final int clientSiteId = 201; 35 | fakeAuth(clientSiteId, Role.GENERATOR); 36 | setupSalts(); 37 | setupKeys(); 38 | 39 | final String emailAddress = "test@uid2.com"; 40 | final JsonObject v2Payload = new JsonObject(); 41 | v2Payload.put("email", emailAddress); 42 | v2Payload.put("tcf_consent_string", "invalid_consent_string"); 43 | sendTokenGenerate(vertx, v2Payload, 400, json -> testContext.completeNow()); 44 | } 45 | 46 | @Test 47 | void noTCFString(Vertx vertx, VertxTestContext testContext) { 48 | final int clientSiteId = 201; 49 | fakeAuth(clientSiteId, Role.GENERATOR); 50 | setupSalts(); 51 | setupKeys(); 52 | 53 | final String emailAddress = "test@uid2.com"; 54 | final JsonObject v2Payload = new JsonObject(); 55 | v2Payload.put("email", emailAddress); 56 | sendTokenGenerate(vertx, v2Payload, 200, json -> testContext.completeNow(), false); 57 | 58 | } 59 | 60 | @Test 61 | void noContentOnInsufficientTcfConsent(Vertx vertx, VertxTestContext testContext) { 62 | final int clientSiteId = 201; 63 | fakeAuth(clientSiteId, Role.GENERATOR); 64 | setupSalts(); 65 | setupKeys(); 66 | 67 | final String emailAddress = "test@uid2.com"; 68 | final JsonObject v2Payload = new JsonObject(); 69 | v2Payload.put("email", emailAddress); 70 | // this TCString is missing consent for purpose #1 71 | v2Payload.put("tcf_consent_string", "CPehXK9PehXK9ABAMBFRACBoADAAAEJAAIYgAKwAQAKgArABAAqAAA"); 72 | sendTokenGenerate(vertx, v2Payload, 200, json -> { 73 | assertFalse(json.containsKey("body")); 74 | assertEquals("insufficient_user_consent", json.getString("status")); 75 | testContext.completeNow(); 76 | }); 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /scripts/azure-aks/deployment/operator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: operator-deployment 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | app.kubernetes.io/name: operator 10 | template: 11 | metadata: 12 | labels: 13 | app.kubernetes.io/name: operator 14 | annotations: 15 | microsoft.containerinstance.virtualnode.ccepolicy: CCE_POLICY_PLACEHOLDER 16 | microsoft.containerinstance.virtualnode.identity: IDENTITY_PLACEHOLDER 17 | microsoft.containerinstance.virtualnode.injectdns: "false" 18 | spec: 19 | containers: 20 | - image: "mcr.microsoft.com/aci/skr:2.7" 21 | imagePullPolicy: Always 22 | name: skr 23 | resources: 24 | limits: 25 | cpu: 2250m 26 | memory: 2256Mi 27 | requests: 28 | cpu: 100m 29 | memory: 512Mi 30 | env: 31 | - name: Port 32 | value: "9000" 33 | volumeMounts: 34 | - mountPath: /opt/confidential-containers/share/kata-containers/reference-info-base64 35 | name: endorsement-location 36 | command: 37 | - /skr.sh 38 | - name: uid2-operator 39 | image: IMAGE_PLACEHOLDER 40 | resources: 41 | limits: 42 | memory: "8Gi" 43 | imagePullPolicy: Always 44 | securityContext: 45 | runAsUser: 1000 46 | env: 47 | - name: VAULT_NAME 48 | value: VAULT_NAME_PLACEHOLDER 49 | - name: OPERATOR_KEY_SECRET_NAME 50 | value: OPERATOR_KEY_SECRET_NAME_PLACEHOLDER 51 | - name: DEPLOYMENT_ENVIRONMENT 52 | value: DEPLOYMENT_ENVIRONMENT_PLACEHOLDER 53 | - name: IMAGE_NAME 54 | value: IMAGE_PLACEHOLDER 55 | ports: 56 | - containerPort: 8080 57 | protocol: TCP 58 | - name: prometheus 59 | containerPort: 9080 60 | protocol: TCP 61 | readinessProbe: 62 | failureThreshold: 3 63 | httpGet: 64 | path: /ops/healthcheck 65 | port: 8080 66 | scheme: HTTP 67 | initialDelaySeconds: 30 68 | periodSeconds: 10 69 | successThreshold: 1 70 | timeoutSeconds: 1 71 | volumes: 72 | - name: endorsement-location 73 | hostPath: 74 | path: /opt/confidential-containers/share/kata-containers/reference-info-base64 75 | nodeSelector: 76 | virtualization: virtualnode2 77 | tolerations: 78 | - effect: NoSchedule 79 | key: virtual-kubelet.io/provider 80 | operator: Exists 81 | --- 82 | apiVersion: v1 83 | kind: Service 84 | metadata: 85 | name: operator-svc 86 | spec: 87 | type: LoadBalancer 88 | selector: 89 | app.kubernetes.io/name: operator 90 | ports: 91 | - protocol: TCP 92 | port: 80 93 | targetPort: 8080 94 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/vertx/ClientVersionCapturingHandler.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator.vertx; 2 | 3 | import com.uid2.operator.util.RoutingContextUtil; 4 | import com.uid2.operator.util.Tuple; 5 | import com.uid2.shared.Const; 6 | import com.uid2.shared.auth.IAuthorizableProvider; 7 | import io.micrometer.core.instrument.Counter; 8 | import io.micrometer.core.instrument.Metrics; 9 | import io.vertx.core.Handler; 10 | import io.vertx.ext.web.RoutingContext; 11 | 12 | import java.io.IOException; 13 | import java.nio.file.DirectoryStream; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | import java.util.HashMap; 18 | import java.util.HashSet; 19 | import java.util.Map; 20 | import java.util.Set; 21 | 22 | public class ClientVersionCapturingHandler implements Handler { 23 | private static final Map, Counter> CLIENT_VERSION_COUNTERS = new HashMap<>(); 24 | private static final Set VERSIONS = new HashSet<>(); 25 | 26 | private final IAuthorizableProvider authKeyStore; 27 | 28 | public ClientVersionCapturingHandler(String dir, String whitelistGlob, IAuthorizableProvider authKeyStore) throws IOException { 29 | this.authKeyStore = authKeyStore; 30 | 31 | try (final DirectoryStream dirStream = Files.newDirectoryStream(Paths.get(dir), whitelistGlob)) { 32 | dirStream.forEach(path -> { 33 | final String version = getFileNameWithoutExtension(path); 34 | VERSIONS.add(version); 35 | }); 36 | } 37 | } 38 | 39 | @Override 40 | public void handle(RoutingContext rc) { 41 | String clientVersion = rc.request().headers().get(Const.Http.ClientVersionHeader); 42 | if (clientVersion == null) { 43 | clientVersion = !rc.queryParam("client").isEmpty() ? rc.queryParam("client").getFirst() : null; 44 | } 45 | 46 | String apiContact = RoutingContextUtil.getApiContact(rc, authKeyStore); 47 | String path = RoutingContextUtil.getPath(rc); 48 | 49 | if (clientVersion != null && VERSIONS.contains(clientVersion)) { 50 | CLIENT_VERSION_COUNTERS.computeIfAbsent( 51 | new Tuple.Tuple2<>(apiContact, clientVersion), 52 | tuple -> Counter 53 | .builder("uid2_client_sdk_versions_total") 54 | .description("counter for how many http requests are processed per each client sdk version") 55 | .tags("site_id", "unknown", "api_contact", tuple.getItem1(), "client_version", tuple.getItem2(), "path", path) 56 | .register(Metrics.globalRegistry) 57 | ).increment(); 58 | } 59 | rc.next(); 60 | } 61 | 62 | private static String getFileNameWithoutExtension(Path path) { 63 | final String fileName = path.getFileName().toString(); 64 | return fileName.indexOf(".") > 0 ? fileName.substring(0, fileName.lastIndexOf(".")) : fileName; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/uid2/operator/Const.java: -------------------------------------------------------------------------------- 1 | package com.uid2.operator; 2 | 3 | public class Const extends com.uid2.shared.Const { 4 | public class Config extends com.uid2.shared.Const.Config { 5 | public static final String ServiceInstancesProp = "service_instances"; 6 | public static final String OptOutBloomFilterSizeProp = "optout_bloom_filter_size"; 7 | public static final String OptOutHeapDefaultCapacityProp = "optout_heap_default_capacity"; 8 | public static final String OptOutS3PathCompatProp = "optout_s3_path_compat"; 9 | public static final String OptOutApiUriProp = "optout_api_uri"; 10 | public static final String OptOutInMemCacheProp = "optout_inmem_cache"; 11 | public static final String StorageMockProp = "storage_mock"; 12 | public static final String StatsCollectorEventBus = "StatsCollector"; 13 | public static final String FailureShutdownWaitHoursProp = "failure_shutdown_wait_hours"; 14 | public static final String SharingTokenExpiryProp = "sharing_token_expiry_seconds"; 15 | public static final String MaxBidstreamLifetimeSecondsProp = "max_bidstream_lifetime_seconds"; 16 | public static final String AllowClockSkewSecondsProp = "allow_clock_skew_seconds"; 17 | public static final String MaxSharingLifetimeProp = "max_sharing_lifetime_seconds"; 18 | public static final String EnableClientSideTokenGenerate = "client_side_token_generate"; 19 | public static final String ValidateServiceLinks = "validate_service_links"; 20 | public static final String OperatorTypeProp = "operator_type"; 21 | public static final String EnclavePlatformProp = "enclave_platform"; 22 | public static final String EncryptedFiles = "encrypted_files"; 23 | public static final String PodTerminationCheckInterval = "pod_termination_check_interval"; 24 | 25 | public static final String AzureVaultNameProp = "azure_vault_name"; 26 | public static final String AzureSecretNameProp = "azure_secret_name"; 27 | 28 | public static final String GcpSecretVersionNameProp = "gcp_secret_version_name"; 29 | public static final String OptOutStatusApiEnabled = "optout_status_api_enabled"; 30 | public static final String OptOutStatusMaxRequestSize = "optout_status_max_request_size"; 31 | public static final String MaxInvalidPaths = "logging_limit_max_invalid_paths_per_interval"; 32 | public static final String MaxVersionBucketsPerSite = "logging_limit_max_version_buckets_per_site"; 33 | 34 | public static final String ConfigScanPeriodMsProp = "config_scan_period_ms"; 35 | public static final String IdentityV3Prop = "identity_v3"; 36 | public static final String DisableOptoutTokenProp = "disable_optout_token"; 37 | public static final String EnableRemoteConfigProp = "enable_remote_config"; 38 | public static final String RuntimeConfigMetadataPathProp = "runtime_config_metadata_path"; 39 | 40 | public static final String IdentityEnvironmentProp = "identity_environment"; 41 | } 42 | } 43 | --------------------------------------------------------------------------------