mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-08-30 02:08:40 +01:00
Harden connection string DNS zone matching
Detect PPE accounts from Mongo and Cassandra connection strings, build their document endpoint from the matched zone, match the PPE suffix on a label boundary, and escape every regex metacharacter in config-supplied zones. Drop the sqlx.cosmosdb.azure.com zone, which is not a real SQL zone.
This commit is contained in:
@@ -92,7 +92,6 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
"documents.azure.com",
|
"documents.azure.com",
|
||||||
"sql.cosmosdb.azure.com",
|
"sql.cosmosdb.azure.com",
|
||||||
"sql.cosmos.azure.com",
|
"sql.cosmos.azure.com",
|
||||||
"sqlx.cosmosdb.azure.com",
|
|
||||||
"sqlx.cosmos.azure.com",
|
"sqlx.cosmos.azure.com",
|
||||||
"documents-staging.windows-ppe.net",
|
"documents-staging.windows-ppe.net",
|
||||||
"sql.cosmosdb.windows-ppe.net",
|
"sql.cosmosdb.windows-ppe.net",
|
||||||
|
|||||||
@@ -11,21 +11,27 @@ describe("ConnectionStringParser", () => {
|
|||||||
const mockAccountName = "Test";
|
const mockAccountName = "Test";
|
||||||
const mockMasterKey = "some-key";
|
const mockMasterKey = "some-key";
|
||||||
|
|
||||||
// Keyed by ApiKind so adding an API to the enum fails to compile here rather than silently going
|
// The shape of each api's connection string, parameterized by the dns zone the account sits in. What
|
||||||
// untested.
|
// these tests are about is the zones, so keeping the shapes here stops them being restated once per zone.
|
||||||
|
const buildConnectionString = {
|
||||||
|
sql: (zone: string) => `AccountEndpoint=https://${mockAccountName}.${zone}:443/;AccountKey=${mockMasterKey};`,
|
||||||
|
mongo: (zone: string) => `mongodb://${mockAccountName}:${mockMasterKey}@${mockAccountName}.${zone}:10255`,
|
||||||
|
// A cassandra connection string can name the account host under either AccountEndpoint or HostName.
|
||||||
|
cassandra: (zone: string, hostKey = "AccountEndpoint") =>
|
||||||
|
`${hostKey}=${mockAccountName}.${zone};AccountKey=${mockMasterKey};`,
|
||||||
|
table: (zone: string) =>
|
||||||
|
`DefaultEndpointsProtocol=https;AccountName=${mockAccountName};AccountKey=${mockMasterKey};TableEndpoint=https://${mockAccountName}.${zone}:443/;`,
|
||||||
|
graph: (zone: string) =>
|
||||||
|
`AccountEndpoint=https://${mockAccountName}.${zone}:443/;AccountKey=${mockMasterKey};ApiKind=Gremlin;`,
|
||||||
|
};
|
||||||
|
|
||||||
const connectionStringsByApiKind: Record<DataModels.ApiKind, string> = {
|
const connectionStringsByApiKind: Record<DataModels.ApiKind, string> = {
|
||||||
[DataModels.ApiKind
|
[DataModels.ApiKind.SQL]: buildConnectionString.sql("documents.azure.com"),
|
||||||
.SQL]: `AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;AccountKey=${mockMasterKey};`,
|
[DataModels.ApiKind.MongoDB]: buildConnectionString.mongo("documents.azure.com"),
|
||||||
[DataModels.ApiKind
|
[DataModels.ApiKind.MongoDBCompute]: buildConnectionString.mongo("mongo.cosmos.azure.com"),
|
||||||
.MongoDB]: `mongodb://${mockAccountName}:${mockMasterKey}@${mockAccountName}.documents.azure.com:10255`,
|
[DataModels.ApiKind.Cassandra]: buildConnectionString.cassandra("cassandra.cosmosdb.azure.com"),
|
||||||
[DataModels.ApiKind
|
[DataModels.ApiKind.Table]: buildConnectionString.table("table.cosmosdb.azure.com"),
|
||||||
.MongoDBCompute]: `mongodb://${mockAccountName}:${mockMasterKey}@${mockAccountName}.mongo.cosmos.azure.com:10255`,
|
[DataModels.ApiKind.Graph]: buildConnectionString.graph("documents.azure.com"),
|
||||||
[DataModels.ApiKind
|
|
||||||
.Cassandra]: `AccountEndpoint=${mockAccountName}.cassandra.cosmosdb.azure.com;AccountKey=${mockMasterKey};`,
|
|
||||||
[DataModels.ApiKind
|
|
||||||
.Table]: `DefaultEndpointsProtocol=https;AccountName=${mockAccountName};AccountKey=${mockMasterKey};TableEndpoint=https://${mockAccountName}.table.cosmosdb.azure.com:443/;`,
|
|
||||||
[DataModels.ApiKind
|
|
||||||
.Graph]: `AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;AccountKey=${mockMasterKey};ApiKind=Gremlin;`,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
it("should parse a connection string for every api kind", () => {
|
it("should parse a connection string for every api kind", () => {
|
||||||
@@ -44,7 +50,6 @@ describe("ConnectionStringParser", () => {
|
|||||||
"documents.azure.com",
|
"documents.azure.com",
|
||||||
"sql.cosmosdb.azure.com",
|
"sql.cosmosdb.azure.com",
|
||||||
"sql.cosmos.azure.com",
|
"sql.cosmos.azure.com",
|
||||||
"sqlx.cosmosdb.azure.com",
|
|
||||||
"sqlx.cosmos.azure.com",
|
"sqlx.cosmos.azure.com",
|
||||||
"documents-staging.windows-ppe.net",
|
"documents-staging.windows-ppe.net",
|
||||||
"sql.cosmosdb.windows-ppe.net",
|
"sql.cosmosdb.windows-ppe.net",
|
||||||
@@ -67,17 +72,6 @@ describe("ConnectionStringParser", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse a valid sql account connection string", () => {
|
|
||||||
const metadata = parseConnectionString(
|
|
||||||
`AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;AccountKey=${mockMasterKey};`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(metadata.accountName).toBe(mockAccountName);
|
|
||||||
expect(metadata.apiKind).toBe(DataModels.ApiKind.SQL);
|
|
||||||
expect(metadata.documentEndpoint).toBe(`https://${mockAccountName}.documents.azure.com:443/`);
|
|
||||||
expect(metadata.apiEndpoint).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should keep the document endpoint given by the connection string", () => {
|
it("should keep the document endpoint given by the connection string", () => {
|
||||||
// The endpoint is taken from the connection string rather than rebuilt from the account name, so a
|
// The endpoint is taken from the connection string rather than rebuilt from the account name, so a
|
||||||
// string that omits the port keeps it omitted.
|
// string that omits the port keeps it omitted.
|
||||||
@@ -91,9 +85,7 @@ describe("ConnectionStringParser", () => {
|
|||||||
it.each(configContext.SQL_DNS_ZONES)(
|
it.each(configContext.SQL_DNS_ZONES)(
|
||||||
"should parse a sql account connection string using the %s zone",
|
"should parse a sql account connection string using the %s zone",
|
||||||
(dnsZone: string) => {
|
(dnsZone: string) => {
|
||||||
const metadata = parseConnectionString(
|
const metadata = parseConnectionString(buildConnectionString.sql(dnsZone));
|
||||||
`AccountEndpoint=https://${mockAccountName}.${dnsZone}:443/;AccountKey=${mockMasterKey};`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(metadata.accountName).toBe(mockAccountName);
|
expect(metadata.accountName).toBe(mockAccountName);
|
||||||
expect(metadata.apiKind).toBe(DataModels.ApiKind.SQL);
|
expect(metadata.apiKind).toBe(DataModels.ApiKind.SQL);
|
||||||
@@ -102,21 +94,10 @@ describe("ConnectionStringParser", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
it("should parse a valid mongo account connection string", () => {
|
|
||||||
const metadata = parseConnectionString(
|
|
||||||
`mongodb://${mockAccountName}:${mockMasterKey}@${mockAccountName}.documents.azure.com:10255`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(metadata.accountName).toBe(mockAccountName);
|
|
||||||
expect(metadata.apiKind).toBe(DataModels.ApiKind.MongoDB);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each(configContext.MONGO_DNS_ZONES)(
|
it.each(configContext.MONGO_DNS_ZONES)(
|
||||||
"should parse a mongo account connection string using the %s zone",
|
"should parse a mongo account connection string using the %s zone",
|
||||||
(dnsZone: string) => {
|
(dnsZone: string) => {
|
||||||
const metadata = parseConnectionString(
|
const metadata = parseConnectionString(buildConnectionString.mongo(dnsZone));
|
||||||
`mongodb://${mockAccountName}:${mockMasterKey}@${mockAccountName}.${dnsZone}:10255`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(metadata.accountName).toBe(mockAccountName);
|
expect(metadata.accountName).toBe(mockAccountName);
|
||||||
expect(metadata.apiKind).toBe(DataModels.ApiKind.MongoDB);
|
expect(metadata.apiKind).toBe(DataModels.ApiKind.MongoDB);
|
||||||
@@ -126,41 +107,31 @@ describe("ConnectionStringParser", () => {
|
|||||||
it.each(configContext.MONGO_COMPUTE_DNS_ZONES)(
|
it.each(configContext.MONGO_COMPUTE_DNS_ZONES)(
|
||||||
"should parse a compute mongo account connection string using the %s zone",
|
"should parse a compute mongo account connection string using the %s zone",
|
||||||
(dnsZone: string) => {
|
(dnsZone: string) => {
|
||||||
const metadata = parseConnectionString(
|
const metadata = parseConnectionString(buildConnectionString.mongo(dnsZone));
|
||||||
`mongodb://${mockAccountName}:${mockMasterKey}@${mockAccountName}.${dnsZone}:10255`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(metadata.accountName).toBe(mockAccountName);
|
expect(metadata.accountName).toBe(mockAccountName);
|
||||||
expect(metadata.apiKind).toBe(DataModels.ApiKind.MongoDBCompute);
|
expect(metadata.apiKind).toBe(DataModels.ApiKind.MongoDBCompute);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
it("should parse a valid cassandra account connection string", () => {
|
|
||||||
const metadata = parseConnectionString(
|
|
||||||
`AccountEndpoint=${mockAccountName}.cassandra.cosmosdb.azure.com;AccountKey=${mockMasterKey};`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(metadata.accountName).toBe(mockAccountName);
|
|
||||||
expect(metadata.apiKind).toBe(DataModels.ApiKind.Cassandra);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each(
|
it.each(
|
||||||
["AccountEndpoint", "HostName"].flatMap((key) =>
|
["AccountEndpoint", "HostName"].flatMap((hostKey) =>
|
||||||
configContext.CASSANDRA_DNS_ZONES.map((dnsZone) => [key, dnsZone]),
|
configContext.CASSANDRA_DNS_ZONES.map((dnsZone) => [hostKey, dnsZone]),
|
||||||
),
|
),
|
||||||
)("should parse a cassandra account connection string using %s and the %s zone", (key: string, dnsZone: string) => {
|
)(
|
||||||
const metadata = parseConnectionString(`${key}=${mockAccountName}.${dnsZone};AccountKey=${mockMasterKey};`);
|
"should parse a cassandra account connection string using %s and the %s zone",
|
||||||
|
(hostKey: string, dnsZone: string) => {
|
||||||
|
const metadata = parseConnectionString(buildConnectionString.cassandra(dnsZone, hostKey));
|
||||||
|
|
||||||
expect(metadata.accountName).toBe(mockAccountName);
|
expect(metadata.accountName).toBe(mockAccountName);
|
||||||
expect(metadata.apiKind).toBe(DataModels.ApiKind.Cassandra);
|
expect(metadata.apiKind).toBe(DataModels.ApiKind.Cassandra);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it.each(configContext.TABLE_DNS_ZONES)(
|
it.each(configContext.TABLE_DNS_ZONES)(
|
||||||
"should parse a table account connection string using the %s zone",
|
"should parse a table account connection string using the %s zone",
|
||||||
(dnsZone: string) => {
|
(dnsZone: string) => {
|
||||||
const metadata = parseConnectionString(
|
const metadata = parseConnectionString(buildConnectionString.table(dnsZone));
|
||||||
`DefaultEndpointsProtocol=https;AccountName=${mockAccountName};AccountKey=${mockMasterKey};TableEndpoint=https://${mockAccountName}.${dnsZone}:443/;`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(metadata.accountName).toBe(mockAccountName);
|
expect(metadata.accountName).toBe(mockAccountName);
|
||||||
expect(metadata.apiKind).toBe(DataModels.ApiKind.Table);
|
expect(metadata.apiKind).toBe(DataModels.ApiKind.Table);
|
||||||
@@ -168,32 +139,42 @@ describe("ConnectionStringParser", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
it("should construct the document endpoint for a table account from the account name", () => {
|
// A Mongo, Cassandra or Table connection string names the account in its own api's dns zone rather than
|
||||||
const metadata = parseConnectionString(
|
// giving the document endpoint, so the document endpoint that data plane operations go through is built
|
||||||
`DefaultEndpointsProtocol=https;AccountName=${mockAccountName};AccountKey=${mockMasterKey};TableEndpoint=https://${mockAccountName}.table.cosmosdb.azure.com:443/;`,
|
// from the account name, under a zone of the same kind as the one that matched.
|
||||||
);
|
const publicDocumentEndpoint = `https://${mockAccountName}.documents.azure.com:443/`;
|
||||||
|
const ppeDocumentEndpoint = `https://${mockAccountName}.documents-staging.windows-ppe.net:443/`;
|
||||||
|
const expectedDocumentEndpointByZone: Record<string, string> = {
|
||||||
|
"documents.azure.com": publicDocumentEndpoint,
|
||||||
|
"documents-staging.windows-ppe.net": ppeDocumentEndpoint,
|
||||||
|
"mongo.cosmos.azure.com": publicDocumentEndpoint,
|
||||||
|
"mongo.cosmos.windows-ppe.net": ppeDocumentEndpoint,
|
||||||
|
"cassandra.cosmosdb.azure.com": publicDocumentEndpoint,
|
||||||
|
"cassandra.cosmos.azure.com": publicDocumentEndpoint,
|
||||||
|
"cassandra.cosmosdb.windows-ppe.net": ppeDocumentEndpoint,
|
||||||
|
"cassandra.cosmos.windows-ppe.net": ppeDocumentEndpoint,
|
||||||
|
"table.cosmosdb.azure.com": publicDocumentEndpoint,
|
||||||
|
"table.cosmos.azure.com": publicDocumentEndpoint,
|
||||||
|
"table.cosmosdb.windows-ppe.net": ppeDocumentEndpoint,
|
||||||
|
"table.cosmos.windows-ppe.net": ppeDocumentEndpoint,
|
||||||
|
};
|
||||||
|
|
||||||
// Table connection strings only carry the table endpoint, so the document endpoint that data plane
|
it.each([
|
||||||
// operations go through is built from the account name.
|
...configContext.MONGO_DNS_ZONES.map((dnsZone) => [dnsZone, buildConnectionString.mongo(dnsZone)]),
|
||||||
expect(metadata.documentEndpoint).toBe(`https://${mockAccountName}.documents.azure.com:443/`);
|
...configContext.MONGO_COMPUTE_DNS_ZONES.map((dnsZone) => [dnsZone, buildConnectionString.mongo(dnsZone)]),
|
||||||
});
|
...configContext.CASSANDRA_DNS_ZONES.map((dnsZone) => [dnsZone, buildConnectionString.cassandra(dnsZone)]),
|
||||||
|
...configContext.TABLE_DNS_ZONES.map((dnsZone) => [dnsZone, buildConnectionString.table(dnsZone)]),
|
||||||
|
])(
|
||||||
|
"should construct the document endpoint for an account in the %s zone",
|
||||||
|
(dnsZone: string, connectionString: string) => {
|
||||||
|
const metadata = parseConnectionString(connectionString);
|
||||||
|
|
||||||
it.each(["table.cosmosdb.windows-ppe.net", "table.cosmos.windows-ppe.net"])(
|
expect(metadata.documentEndpoint).toBe(expectedDocumentEndpointByZone[dnsZone]);
|
||||||
"should construct a PPE document endpoint for a table account in the %s zone",
|
|
||||||
(dnsZone: string) => {
|
|
||||||
const metadata = parseConnectionString(
|
|
||||||
`DefaultEndpointsProtocol=https;AccountName=${mockAccountName};AccountKey=${mockMasterKey};TableEndpoint=https://${mockAccountName}.${dnsZone}:443/;`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// The constructed endpoint has to match the kind of zone the table endpoint we matched came from.
|
|
||||||
expect(metadata.documentEndpoint).toBe(`https://${mockAccountName}.documents-staging.windows-ppe.net:443/`);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
it("should parse a valid graph account connection string", () => {
|
it("should parse a valid graph account connection string", () => {
|
||||||
const metadata = parseConnectionString(
|
const metadata = parseConnectionString(buildConnectionString.graph("documents.azure.com"));
|
||||||
`AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;AccountKey=${mockMasterKey};ApiKind=Gremlin;`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(metadata.accountName).toBe(mockAccountName);
|
expect(metadata.accountName).toBe(mockAccountName);
|
||||||
expect(metadata.apiKind).toBe(DataModels.ApiKind.Graph);
|
expect(metadata.apiKind).toBe(DataModels.ApiKind.Graph);
|
||||||
@@ -202,9 +183,7 @@ describe("ConnectionStringParser", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should construct a PPE gremlin endpoint for a PPE graph account", () => {
|
it("should construct a PPE gremlin endpoint for a PPE graph account", () => {
|
||||||
const metadata = parseConnectionString(
|
const metadata = parseConnectionString(buildConnectionString.graph("documents-staging.windows-ppe.net"));
|
||||||
`AccountEndpoint=https://${mockAccountName}.documents-staging.windows-ppe.net:443/;AccountKey=${mockMasterKey};ApiKind=Gremlin;`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(metadata.accountName).toBe(mockAccountName);
|
expect(metadata.accountName).toBe(mockAccountName);
|
||||||
expect(metadata.apiKind).toBe(DataModels.ApiKind.Graph);
|
expect(metadata.apiKind).toBe(DataModels.ApiKind.Graph);
|
||||||
@@ -218,9 +197,7 @@ describe("ConnectionStringParser", () => {
|
|||||||
updateConfigContext({ DOCUMENT_ENDPOINT_ZONES: ["documents.azure.com"] });
|
updateConfigContext({ DOCUMENT_ENDPOINT_ZONES: ["documents.azure.com"] });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const metadata = parseConnectionString(
|
const metadata = parseConnectionString(buildConnectionString.table("table.cosmos.windows-ppe.net"));
|
||||||
`DefaultEndpointsProtocol=https;AccountName=${mockAccountName};AccountKey=${mockMasterKey};TableEndpoint=https://${mockAccountName}.table.cosmos.windows-ppe.net:443/;`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// The account key travels to the constructed document endpoint, so a config carrying no PPE zone
|
// The account key travels to the constructed document endpoint, so a config carrying no PPE zone
|
||||||
// has to fail on a PPE account rather than fall back to a zone the account does not own.
|
// has to fail on a PPE account rather than fall back to a zone the account does not own.
|
||||||
@@ -231,11 +208,11 @@ describe("ConnectionStringParser", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
`AccountEndpoint=https://${mockAccountName}.documents.azure.com.attacker.example:443/;AccountKey=${mockMasterKey};`,
|
buildConnectionString.sql("documents.azure.com.attacker.example"),
|
||||||
`mongodb://${mockAccountName}:${mockMasterKey}@${mockAccountName}.documents.azure.com.attacker.example:10255`,
|
buildConnectionString.mongo("documents.azure.com.attacker.example"),
|
||||||
`mongodb://${mockAccountName}:${mockMasterKey}@${mockAccountName}.mongo.cosmos.azure.com.attacker.example:10255`,
|
buildConnectionString.mongo("mongo.cosmos.azure.com.attacker.example"),
|
||||||
`AccountEndpoint=${mockAccountName}.cassandra.cosmosdb.azure.com.attacker.example;AccountKey=${mockMasterKey};`,
|
buildConnectionString.cassandra("cassandra.cosmosdb.azure.com.attacker.example"),
|
||||||
`DefaultEndpointsProtocol=https;AccountName=${mockAccountName};AccountKey=${mockMasterKey};TableEndpoint=https://${mockAccountName}.table.cosmosdb.azure.com.attacker.example:443/;`,
|
buildConnectionString.table("table.cosmosdb.azure.com.attacker.example"),
|
||||||
])("should not accept a host that only begins with a known zone: %s", (connectionString: string) => {
|
])("should not accept a host that only begins with a known zone: %s", (connectionString: string) => {
|
||||||
// The zone list is what keeps the account key from being sent somewhere arbitrary, so a host that
|
// The zone list is what keeps the account key from being sent somewhere arbitrary, so a host that
|
||||||
// appends to an allowed zone must not pass as that zone.
|
// appends to an allowed zone must not pass as that zone.
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { AccessInputMetadata, ApiKind } from "../../../Contracts/DataModels";
|
|||||||
const PpeDnsSuffix = "windows-ppe.net";
|
const PpeDnsSuffix = "windows-ppe.net";
|
||||||
const DnsPort = "443";
|
const DnsPort = "443";
|
||||||
|
|
||||||
const isPpeZone = (zone: string): boolean => zone.endsWith(PpeDnsSuffix);
|
// Match on a label boundary so a zone like "notwindows-ppe.net" is not taken for a PPE zone.
|
||||||
|
const isPpeZone = (zone: string): boolean => zone === PpeDnsSuffix || zone.endsWith(`.${PpeDnsSuffix}`);
|
||||||
|
|
||||||
// Picks the DNS zone matching the kind of account the connection string came from, since a PPE
|
// Picks the DNS zone matching the kind of account the connection string came from, since a PPE
|
||||||
// account's endpoints sit under PPE zones and every other account's do not. Returns undefined when the
|
// account's endpoints sit under PPE zones and every other account's do not. Returns undefined when the
|
||||||
@@ -16,9 +17,10 @@ export const selectEndpointZone = (zones: ReadonlyArray<string>, isPpeAccount: b
|
|||||||
// Builds an alternation matching any of the given DNS zones, e.g. "(documents\.azure\.com|sql\.cosmos\.azure\.com)".
|
// Builds an alternation matching any of the given DNS zones, e.g. "(documents\.azure\.com|sql\.cosmos\.azure\.com)".
|
||||||
// The group captures so callers can tell which zone matched, and with it whether the account is a PPE account.
|
// The group captures so callers can tell which zone matched, and with it whether the account is a PPE account.
|
||||||
// The zone has to run to the end of the host, otherwise a host that merely starts with an allowed zone
|
// The zone has to run to the end of the host, otherwise a host that merely starts with an allowed zone
|
||||||
// would pass as that zone and the account key would travel to whatever was appended to it.
|
// would pass as that zone and the account key would travel to whatever was appended to it. Zones come
|
||||||
|
// from config, so every regex metacharacter is escaped rather than just the dots.
|
||||||
export const dnsZoneAlternation = (zones: ReadonlyArray<string>): string =>
|
export const dnsZoneAlternation = (zones: ReadonlyArray<string>): string =>
|
||||||
`(${zones.map((zone) => zone.replace(/\./g, "\\.")).join("|")})(?=[:/\\s]|$)`;
|
`(${zones.map((zone) => zone.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?=[:/\\s]|$)`;
|
||||||
|
|
||||||
// The zone lists live in ConfigContext, which is populated asynchronously by initializeConfiguration,
|
// The zone lists live in ConfigContext, which is populated asynchronously by initializeConfiguration,
|
||||||
// so these are built per call rather than once at module load.
|
// so these are built per call rather than once at module load.
|
||||||
@@ -55,15 +57,19 @@ export function parseConnectionString(connectionString: string): AccessInputMeta
|
|||||||
const matches: string[] = connectionStringPart.match(endpointsRegex.mongo);
|
const matches: string[] = connectionStringPart.match(endpointsRegex.mongo);
|
||||||
accessInput.accountName = matches && matches.length > 1 && matches[2];
|
accessInput.accountName = matches && matches.length > 1 && matches[2];
|
||||||
accessInput.apiKind = ApiKind.MongoDB;
|
accessInput.apiKind = ApiKind.MongoDB;
|
||||||
|
isPpeAccount = isPpeZone(matches[3]);
|
||||||
} else if (RegExp(endpointsRegex.mongoCompute).test(connectionStringPart)) {
|
} else if (RegExp(endpointsRegex.mongoCompute).test(connectionStringPart)) {
|
||||||
const matches: string[] = connectionStringPart.match(endpointsRegex.mongoCompute);
|
const matches: string[] = connectionStringPart.match(endpointsRegex.mongoCompute);
|
||||||
accessInput.accountName = matches && matches.length > 1 && matches[2];
|
accessInput.accountName = matches && matches.length > 1 && matches[2];
|
||||||
accessInput.apiKind = ApiKind.MongoDBCompute;
|
accessInput.apiKind = ApiKind.MongoDBCompute;
|
||||||
|
isPpeAccount = isPpeZone(matches[3]);
|
||||||
} else if (endpointsRegex.cassandra.some((regex) => RegExp(regex).test(connectionStringPart))) {
|
} else if (endpointsRegex.cassandra.some((regex) => RegExp(regex).test(connectionStringPart))) {
|
||||||
endpointsRegex.cassandra.forEach((regex) => {
|
endpointsRegex.cassandra.forEach((regex) => {
|
||||||
if (RegExp(regex).test(connectionStringPart)) {
|
const matches: string[] = connectionStringPart.match(regex);
|
||||||
accessInput.accountName = connectionStringPart.match(regex)[1];
|
if (matches) {
|
||||||
|
accessInput.accountName = matches[1];
|
||||||
accessInput.apiKind = ApiKind.Cassandra;
|
accessInput.apiKind = ApiKind.Cassandra;
|
||||||
|
isPpeAccount = isPpeZone(matches[2]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (RegExp(endpointsRegex.table).test(connectionStringPart)) {
|
} else if (RegExp(endpointsRegex.table).test(connectionStringPart)) {
|
||||||
@@ -80,11 +86,17 @@ export function parseConnectionString(connectionString: string): AccessInputMeta
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Table connection strings only carry the table endpoint, so the document endpoint that data plane
|
// A Table, Mongo or Cassandra connection string names the account in its own api's dns zone rather
|
||||||
// operations go through has to be derived from the account name. Gremlin accounts additionally
|
// than giving the document endpoint, so the document endpoint that data plane operations go through
|
||||||
// need the Gremlin endpoint, which is never part of the connection string.
|
// has to be built from the account name. SQL and Gremlin strings carry it and take it as given.
|
||||||
|
// Gremlin additionally needs the Gremlin endpoint, which is never part of the connection string.
|
||||||
if (accessInput.accountName) {
|
if (accessInput.accountName) {
|
||||||
if (accessInput.apiKind === ApiKind.Table) {
|
if (
|
||||||
|
accessInput.apiKind === ApiKind.Table ||
|
||||||
|
accessInput.apiKind === ApiKind.MongoDB ||
|
||||||
|
accessInput.apiKind === ApiKind.MongoDBCompute ||
|
||||||
|
accessInput.apiKind === ApiKind.Cassandra
|
||||||
|
) {
|
||||||
const documentEndpointZone = selectEndpointZone(configContext.DOCUMENT_ENDPOINT_ZONES, isPpeAccount);
|
const documentEndpointZone = selectEndpointZone(configContext.DOCUMENT_ENDPOINT_ZONES, isPpeAccount);
|
||||||
if (!documentEndpointZone) {
|
if (!documentEndpointZone) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
Reference in New Issue
Block a user