diff --git a/ooniapi/common/src/common/config.py b/ooniapi/common/src/common/config.py index 803493c3f..55d3bbca0 100644 --- a/ooniapi/common/src/common/config.py +++ b/ooniapi/common/src/common/config.py @@ -41,3 +41,4 @@ class Settings(BaseSettings): failed_reports_bucket: str = ( "" # for uploading reports that couldn't be sent to fastpath ) + psiphon_conffile: str = "" # path to psiphon config file diff --git a/ooniapi/services/ooniprobe/src/ooniprobe/routers/v1/probe_services.py b/ooniapi/services/ooniprobe/src/ooniprobe/routers/v1/probe_services.py index 8d4bc2e65..d886e1eee 100644 --- a/ooniapi/services/ooniprobe/src/ooniprobe/routers/v1/probe_services.py +++ b/ooniapi/services/ooniprobe/src/ooniprobe/routers/v1/probe_services.py @@ -9,6 +9,7 @@ from fastapi import APIRouter, Depends, HTTPException, Response, Request from prometheus_client import Counter, Info, Gauge from pydantic import Field +import ujson from ...utils import ( generate_report_id, @@ -590,3 +591,45 @@ def random_web_test_helpers(th_list: List[str]) -> List[Dict]: for th_addr in th_list: out.append({"address": th_addr, "type": "https"}) return out + + +class PsiphonServer(BaseModel): + OnlyAfterAttempts: int + SkipVerify: bool + URL: str + + +class PsiphonConfig(BaseModel): + ClientPlatform: str + ClientVersion: str + EstablishTunnelTimeoutSeconds: int + LocalHttpProxyPort: int + LocalSocksProxyPort: int + PropagationChannelId: str + RemoteServerListDownloadFilename: str + RemoteServerListSignaturePublicKey: str + RemoteServerListURLs: List[PsiphonServer] = [] + SponsorId: str + TargetApiProtocol: str + UseIndistinguishableTLS: bool + + +@router.get("/test-list/psiphon-config", tags=["ooniprobe"], response_model=PsiphonConfig) +def list_tor_targets( + request: Request, + settings: Settings = Depends(get_settings), + ) -> PsiphonConfig: + + token = request.headers.get("Authorization") + if token == None: + # XXX not actually validated + pass + try: + with open(settings.psiphon_conffile, 'r') as f: + resp = ujson.load(f) + return resp + except FileNotFoundError: + log.info("psiphon-config: failed to open json") + except ujson.JSONDecodeError: + log.info("psiphon-config: failed to parse json") + raise HTTPException(status_code=401, detail="Invalid psiphon-config") diff --git a/ooniapi/services/ooniprobe/tests/conftest.py b/ooniapi/services/ooniprobe/tests/conftest.py index f15438931..46b9f2c51 100644 --- a/ooniapi/services/ooniprobe/tests/conftest.py +++ b/ooniapi/services/ooniprobe/tests/conftest.py @@ -116,7 +116,8 @@ def test_settings(alembic_migration, docker_ip, docker_services, geoip_db_dir, f clickhouse_url=f"clickhouse://test:test@{docker_ip}:{port}", geoip_db_dir=geoip_db_dir, collector_id="1", - fastpath_url=fastpath_server + fastpath_url=fastpath_server, + psiphon_conffile="./tests/fixtures/data/psiphon-config.json" ) diff --git a/ooniapi/services/ooniprobe/tests/fixtures/data/psiphon-config.json b/ooniapi/services/ooniprobe/tests/fixtures/data/psiphon-config.json new file mode 100644 index 000000000..fef6caab0 --- /dev/null +++ b/ooniapi/services/ooniprobe/tests/fixtures/data/psiphon-config.json @@ -0,0 +1 @@ +{"ClientPlatform":"ooni-probe","ClientVersion":"1","EstablishTunnelTimeoutSeconds":0,"LocalHttpProxyPort":0,"LocalSocksProxyPort":0,"PropagationChannelId":"4480E33669AF0554","RemoteServerListDownloadFilename":"server_list_compressed","RemoteServerListSignaturePublicKey":"MIICIDANBgkqhkiG9w0BAQEFAAOCAg0AMIICCAKCAgEAt7Ls+/39r+T6zNW7GiVpJfzq/xvL9SBH5rIFnk0RXYEYavax3WS6HOD35eTAqn8AniOwiH+DOkvgSKF2caqk/y1dfq47Pdymtwzp9ikpB1C5OfAysXzBiwVJlCdajBKvBZDerV1cMvRzCKvKwRmvDmHgphQQ7WfXIGbRbmmk6opMBh3roE42KcotLFtqp0RRwLtcBRNtCdsrVsjiI1Lqz/lH+T61sGjSjQ3CHMuZYSQJZo/KrvzgQXpkaCTdbObxHqb6/+i1qaVOfEsvjoiyzTxJADvSytVtcTjijhPEV6XskJVHE1Zgl+7rATr/pDQkw6DPCNBS1+Y6fy7GstZALQXwEDN/qhQI9kWkHijT8ns+i1vGg00Mk/6J75arLhqcodWsdeG/M/moWgqQAnlZAGVtJI1OgeF5fsPpXu4kctOfuZlGjVZXQNW34aOzm8r8S0eVZitPlbhcPiR4gT/aSMz/wd8lZlzZYsje/Jr8u/YtlwjjreZrGRmG8KMOzukV3lLmMppXFMvl4bxv6YFEmIuTsOhbLTwFgh7KYNjodLj/LsqRVfwz31PgWQFTEPICV7GCvgVlPRxnofqKSjgTWI4mxDhBpVcATvaoBl1L/6WLbFvBsoAUBItWwctO2xalKxF5szhGm8lccoc5MZr8kfE0uxMgsxz4er68iCID+rsCAQM=","RemoteServerListURLs":[{"OnlyAfterAttempts":0,"SkipVerify":false,"URL":"aHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3BzaXBob24vd2ViLzNzdHItaGFpMy1ha2p6L3NlcnZlcl9saXN0X2NvbXByZXNzZWQ="},{"OnlyAfterAttempts":2,"SkipVerify":true,"URL":"aHR0cHM6Ly93d3cuZGlhbW9uZGJlcmxpbmdhbWVycGxhbmV0LmNvbS5nbG9iYWwucHJvZC5mYXN0bHkubmV0L3dlYi8zc3RyLWhhaTMtYWtqei9zZXJ2ZXJfbGlzdF9jb21wcmVzc2Vk"},{"OnlyAfterAttempts":2,"SkipVerify":true,"URL":"aHR0cHM6Ly93d3cueHlkaWFtb25kZGJleHBlcnQuY29tLmdsb2JhbC5wcm9kLmZhc3RseS5uZXQvd2ViLzNzdHItaGFpMy1ha2p6L3NlcnZlcl9saXN0X2NvbXByZXNzZWQ="},{"OnlyAfterAttempts":2,"SkipVerify":true,"URL":"aHR0cHM6Ly93d3cuYmxvZ3NmbWNhbmNlcmNpdGl6ZW4uY29tLmdsb2JhbC5wcm9kLmZhc3RseS5uZXQvd2ViLzNzdHItaGFpMy1ha2p6L3NlcnZlcl9saXN0X2NvbXByZXNzZWQ="}],"SponsorId":"6DF96CB8DA41AD91","TargetApiProtocol":"ssh","UseIndistinguishableTLS":true} diff --git a/ooniapi/services/ooniprobe/tests/integ/test_psiphon_config.py b/ooniapi/services/ooniprobe/tests/integ/test_psiphon_config.py new file mode 100644 index 000000000..acec04b79 --- /dev/null +++ b/ooniapi/services/ooniprobe/tests/integ/test_psiphon_config.py @@ -0,0 +1,7 @@ +def test_psiphon_config(client): + resp = client.get("/api/v1/test-list/psiphon-config").json() + for k in ['ClientPlatform', 'ClientVersion', 'EstablishTunnelTimeoutSeconds', + 'LocalHttpProxyPort', 'LocalSocksProxyPort', 'PropagationChannelId', + 'RemoteServerListDownloadFilename', 'RemoteServerListSignaturePublicKey', + 'RemoteServerListURLs', 'SponsorId', 'TargetApiProtocol', 'UseIndistinguishableTLS']: + assert k in resp