Skip to content

Commit

Permalink
Adding SAS_IP_RANGE option
Browse files Browse the repository at this point in the history
  • Loading branch information
SoTrx committed Jun 28, 2021
1 parent 484a485 commit 1f166d6
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 17 deletions.
38 changes: 36 additions & 2 deletions bySaS/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe("Unit test bySas", () => {
describe.each([
["None", undefined],
["SAS_LIMIT_HOURS", 6],
])("Adding '%s' to env", (varName, value) => {
])("Setting SAS_LIMIT to env", (varName, value) => {
it("Full test", async () => {
env[varName] = String(value);
const DEFAULT_SAS_LIMIT = 24;
Expand All @@ -53,8 +53,9 @@ describe("Unit test bySas", () => {
expect(response.status).toBe(200);
console.log(response.body);
expect(response.body).toBeDefined();
const qs = decode(response.body.split("?")[1]);

// Checking sas validity span. When not provided this value should be 24h
const qs = decode(response.body.split("?")[1]);
const sasValidFrom = new Date(qs.st as string);
const sasValidTo = new Date(qs.se as string);
const timeDiff = sasValidTo.getTime() - sasValidFrom.getTime();
Expand All @@ -64,6 +65,39 @@ describe("Unit test bySas", () => {
//expect(response.body)
}, 15000);
});

describe.each([
["None", undefined],
["SAS_IP_RANGE", "176.134.171.0-176.134.171.255"],
])("Setting SAS_IP_RANGE to valid values", (varName, value) => {
it("Full test", async () => {
env[varName] = String(value);
const DEFAULT_IP_RANGE = undefined;
const [context, request] = formatUploadRequest("test", null, {
"x-forwarded-for": "1.1.1.1",
});
const response = await func(context, request);
expect(response.status).toBe(200);
console.log(response.body);
expect(response.body).toBeDefined();
const qs = decode(response.body.split("?")[1]);
expect(qs.sip).toBe(value ?? DEFAULT_IP_RANGE);
}, 15000);
});
});

describe.each([
["SAS_IP_RANGE", "176.134.171.0"],
["SAS_IP_RANGE", "somerandomstring"],
["SAS_IP_RANGE", 6],
])("Setting SAS_IP_RANGE to invalid values", (varName, value) => {
it("Full test", async () => {
env[varName] = String(value);
const [context, request] = formatUploadRequest("test", null, {
"x-forwarded-for": "1.1.1.1",
});
expect(func(context, request)).rejects.toThrow();
}, 15000);
});

/**
Expand Down
38 changes: 24 additions & 14 deletions bySaS/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ import {
} from "@azure/storage-blob";
import { v4 as uuidv4 } from "uuid";
import HTTP_CODES from "http-status-enum";
import { formatReply, getEnvVar } from "../common/index";
import { formatReply, getEnvVar, parseIpRange } from "../common/index";

// Keep the Sas key valid 24h
const DEFAULT_HOURS_LIMIT = 24;
// Any ip can upload to the container by default
const DEFAULT_IP_RANGE = "any";

export default async function httpTrigger(
context: Context,
Expand All @@ -32,6 +35,16 @@ export default async function httpTrigger(
context.log.error(req.headers);
return formatReply(`Malformed request`, HTTP_CODES.UNPROCESSABLE_ENTITY);
}

const hoursLimit = getEnvVar<number>(
"SAS_LIMIT_HOURS",
DEFAULT_HOURS_LIMIT,
context
);
// 176.134.171.0-176.134.171.255
const ipRange = getEnvVar<string>("SAS_IP_RANGE", DEFAULT_IP_RANGE, context);
let startIp: string, endIp: string;
if (ipRange !== DEFAULT_IP_RANGE) [startIp, endIp] = parseIpRange(ipRange);

context.log.verbose(
`Blob Storage QS is ${env.BlobStorageQS}. Input container is ${env.InputContainer}`
Expand All @@ -51,21 +64,15 @@ export default async function httpTrigger(
return formatReply(`Unexpected error.`, HTTP_CODES.INTERNAL_SERVER_ERROR);
}

const hoursLimit = getEnvVar<number>(
"SAS_LIMIT_HOURS",
DEFAULT_HOURS_LIMIT,
context
);

// @TODO: A "file count by ip" limit could be implemented
const sasKey = await generateSasKeyForClient(
blobClient,
inboundIp,
[startIp, endIp],
hoursLimit
);

context.log.info(
`Successfully generated SAS key for file ${filename} and ip ${inboundIp}`
`Successfully generated SAS key for file ${filename} (requester IP : ${inboundIp})`
);
return formatReply(JSON.stringify({ key: sasKey }));
}
Expand All @@ -79,7 +86,7 @@ export default async function httpTrigger(
*/
function generateSasKeyForClient(
blobClient: BlobClient,
inboundIp: string,
ipRange: [string, string] = undefined,
hours = 24
): Promise<string> {
// Set start date to five minutes ago, to avoid some clock drifting shenaningans
Expand All @@ -90,14 +97,17 @@ function generateSasKeyForClient(
new Date(startDate).getTime() + 1000 * 3600 * hours
);

// Only allows the calling ip to write to the Blob for a limited amount of time
return blobClient.generateSasUrl({
const options: Record<string, unknown> = {
startsOn: startDate,
expiresOn: expiresDate,
//ipRange: { start: inboundIp, end: inboundIp },
// Only allow the client to write to the Blob, not to read or list the container.
permissions: BlobSASPermissions.parse("w"),
});
};
// Restrict the IP range if the options was specified
if (ipRange !== undefined)
options.ipRange = { start: ipRange[0], end: ipRange[1] };

return blobClient.generateSasUrl(options);
}
/**
*
Expand Down
19 changes: 19 additions & 0 deletions common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,22 @@ export function getEnvVar<T>(varName: string, fallback: T, ctx: Context): T {
}
return envVar as unknown as T;
}

/**
* Ensure an ip range is something like '176.134.171.0-176.134.171.255'. Throws if the range isn't valid
* @param ipRange candidate ip range
* @returns both ip separated
*/
export function parseIpRange(ipRange: string): [string, string] {
const ipRegex = /(?=.*[^\.]$)((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.?){4}/;
const ipArray = ipRange.split("-");
if (
// A range only has two IPs
ipArray.length !== 2 ||
// That must be valid IPs
ipArray.map((ip) => ipRegex.test(ip)).some((valid) => !valid)
)
throw new Error(`Invalid ip range provided ${ipRange}`);

return ipArray as [string, string];
}
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ This function uses two **mandatory** variables :

These **optional** variables can also be specified :

- **SAS_LIMIT_HOURS** : For a SaS-based upload, how many hours keep the SaS key valid. Default is 24
- **SAS_LIMIT_HOURS** : For a SaS-based upload, how many hours should the SaS key be valid. Default is 24
- **SAS_IP_RANGE** : For a SaS-based upload, the IP range allowed to upload to the Blob Container. Default is any IP. The IP range must be in the format `[ipv4]-[ipv4]`.

See [application settings documentation](https://docs.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings#settings) for more details.

Expand Down

0 comments on commit 1f166d6

Please sign in to comment.