From 66186994b2d3a7fd26dd2598d943afc74e066312 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 09:14:48 +0100 Subject: [PATCH 1/3] Add and test isValidBuilder --- packages/sdk/src/builder.spec.ts | 64 ++++++++++++++++++++++++++++++++ packages/sdk/src/builder.ts | 16 ++++++++ packages/sdk/types/builder.d.ts | 1 + 3 files changed, 81 insertions(+) create mode 100644 packages/sdk/src/builder.spec.ts create mode 100644 packages/sdk/src/builder.ts create mode 100644 packages/sdk/types/builder.d.ts diff --git a/packages/sdk/src/builder.spec.ts b/packages/sdk/src/builder.spec.ts new file mode 100644 index 00000000..d83cbb9a --- /dev/null +++ b/packages/sdk/src/builder.spec.ts @@ -0,0 +1,64 @@ +import { isValidBuilder } from "./builder"; + +describe("builder", () => { + describe("isValidBuilder", () => { + // Valid cases + + it("returns true for simple examples", () => { + expect(isValidBuilder("myorg/super-optimizer:0.1.2")).toEqual(true); + expect(isValidBuilder("myorg/super-optimizer:42")).toEqual(true); + }); + + it("supports images with no organization", () => { + // from https://hub.docker.com/_/ubuntu + expect(isValidBuilder("ubuntu:xenial-20200212")).toEqual(true); + // from https://hub.docker.com/_/rust + expect(isValidBuilder("rust:1.40.0")).toEqual(true); + }); + + it("supports images with multi level names", () => { + expect(isValidBuilder("myorg/department-x/office-y/technology-z/super-optimizer:0.1.2")).toEqual(true); + }); + + it("returns true for tags with lower and upper chars", () => { + expect(isValidBuilder("myorg/super-optimizer:0.1.2-alpha")).toEqual(true); + expect(isValidBuilder("myorg/super-optimizer:0.1.2-Alpha")).toEqual(true); + }); + + it("allows very long images", () => { + // This is > 2 KiB of data + expect( + isValidBuilder( + "myorgisnicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenice/super-optimizer:42", + ), + ).toEqual(true); + }); + + // Invalid cases + + it("returns false for missing or empty tag", () => { + expect(isValidBuilder("myorg/super-optimizer")).toEqual(false); + expect(isValidBuilder("myorg/super-optimizer:")).toEqual(false); + }); + + it("returns false for name components starting or ending with a separator", () => { + expect(isValidBuilder(".myorg/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("-myorg/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("_myorg/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg./super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg-/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg_/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/.super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/-super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/_super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/super-optimizer.:42")).toEqual(false); + expect(isValidBuilder("myorg/super-optimizer-:42")).toEqual(false); + expect(isValidBuilder("myorg/super-optimizer_:42")).toEqual(false); + }); + + it("returns false for upper case character in name component", () => { + expect(isValidBuilder("mYorg/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/super-Optimizer:42")).toEqual(false); + }); + }); +}); diff --git a/packages/sdk/src/builder.ts b/packages/sdk/src/builder.ts new file mode 100644 index 00000000..422aca86 --- /dev/null +++ b/packages/sdk/src/builder.ts @@ -0,0 +1,16 @@ +// A docker image regexp. We remove support for non-standard registries for simplicity. +// https://docs.docker.com/engine/reference/commandline/tag/#extended-description +// +// An image name is made up of slash-separated name components (optionally prefixed by a registry hostname). +// Name components may contain lowercase characters, digits and separators. +// A separator is defined as a period, one or two underscores, or one or more dashes. A name component may not start or end with a separator. +// +// A tag name must be valid ASCII and may contain lowercase and uppercase letters, digits, underscores, periods and dashes. +// A tag name may not start with a period or a dash and may contain a maximum of 128 characters. +const dockerImagePattern = new RegExp( + "^[a-z0-9][a-z0-9._-]*[a-z0-9](/[a-z0-9][a-z0-9._-]*[a-z0-9])*:[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$", +); + +export function isValidBuilder(builder: string): boolean { + return !!builder.match(dockerImagePattern); +} diff --git a/packages/sdk/types/builder.d.ts b/packages/sdk/types/builder.d.ts new file mode 100644 index 00000000..68e18c0a --- /dev/null +++ b/packages/sdk/types/builder.d.ts @@ -0,0 +1 @@ +export declare function isValidBuilder(builder: string): boolean; From 5de5d7ce61b2eabf6ad9aca8d89219b558c2570d Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 12:14:47 +0100 Subject: [PATCH 2/3] Add max length and min name components to isValidBuilder --- packages/sdk/src/builder.spec.ts | 31 +++++++++++++++---------------- packages/sdk/src/builder.ts | 6 +++++- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/sdk/src/builder.spec.ts b/packages/sdk/src/builder.spec.ts index d83cbb9a..20926f4c 100644 --- a/packages/sdk/src/builder.spec.ts +++ b/packages/sdk/src/builder.spec.ts @@ -9,13 +9,6 @@ describe("builder", () => { expect(isValidBuilder("myorg/super-optimizer:42")).toEqual(true); }); - it("supports images with no organization", () => { - // from https://hub.docker.com/_/ubuntu - expect(isValidBuilder("ubuntu:xenial-20200212")).toEqual(true); - // from https://hub.docker.com/_/rust - expect(isValidBuilder("rust:1.40.0")).toEqual(true); - }); - it("supports images with multi level names", () => { expect(isValidBuilder("myorg/department-x/office-y/technology-z/super-optimizer:0.1.2")).toEqual(true); }); @@ -25,15 +18,6 @@ describe("builder", () => { expect(isValidBuilder("myorg/super-optimizer:0.1.2-Alpha")).toEqual(true); }); - it("allows very long images", () => { - // This is > 2 KiB of data - expect( - isValidBuilder( - "myorgisnicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenice/super-optimizer:42", - ), - ).toEqual(true); - }); - // Invalid cases it("returns false for missing or empty tag", () => { @@ -60,5 +44,20 @@ describe("builder", () => { expect(isValidBuilder("mYorg/super-optimizer:42")).toEqual(false); expect(isValidBuilder("myorg/super-Optimizer:42")).toEqual(false); }); + + it("returns false for long images", () => { + expect( + isValidBuilder( + "myorgisnicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenice/super-optimizer:42", + ), + ).toEqual(false); + }); + + it("returns false for images with no organization", () => { + // Those are valid dockerhub images from https://hub.docker.com/_/ubuntu and https://hub.docker.com/_/rust + // but not valid in the context of CosmWasm Verify + expect(isValidBuilder("ubuntu:xenial-20200212")).toEqual(false); + expect(isValidBuilder("rust:1.40.0")).toEqual(false); + }); }); }); diff --git a/packages/sdk/src/builder.ts b/packages/sdk/src/builder.ts index 422aca86..31a790c7 100644 --- a/packages/sdk/src/builder.ts +++ b/packages/sdk/src/builder.ts @@ -8,9 +8,13 @@ // A tag name must be valid ASCII and may contain lowercase and uppercase letters, digits, underscores, periods and dashes. // A tag name may not start with a period or a dash and may contain a maximum of 128 characters. const dockerImagePattern = new RegExp( - "^[a-z0-9][a-z0-9._-]*[a-z0-9](/[a-z0-9][a-z0-9._-]*[a-z0-9])*:[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$", + "^[a-z0-9][a-z0-9._-]*[a-z0-9](/[a-z0-9][a-z0-9._-]*[a-z0-9])+:[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$", ); +/** Max length in bytes/characters (regexp enforces all ASCII, even if that is not required by the standard) */ +const builderMaxLength = 128; + export function isValidBuilder(builder: string): boolean { + if (builder.length > builderMaxLength) return false; return !!builder.match(dockerImagePattern); } From efb272e50d806b9a29240aaa3b0f5e5475ae62aa Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 12:23:03 +0100 Subject: [PATCH 3/3] Check builder format in SigningCosmWasmClient.upload --- packages/sdk/src/signingcosmwasmclient.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/sdk/src/signingcosmwasmclient.ts b/packages/sdk/src/signingcosmwasmclient.ts index 2dc60779..aa89a894 100644 --- a/packages/sdk/src/signingcosmwasmclient.ts +++ b/packages/sdk/src/signingcosmwasmclient.ts @@ -2,6 +2,7 @@ import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import pako from "pako"; +import { isValidBuilder } from "./builder"; import { CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; import { makeSignBytes } from "./encoding"; import { findAttribute, Log } from "./logs"; @@ -32,6 +33,15 @@ function singleAmount(amount: number, denom: string): readonly Coin[] { return [{ amount: amount.toString(), denom: denom }]; } +function prepareBuilder(buider: string | undefined): string { + if (buider === undefined) { + return ""; // normalization needed by backend + } else { + if (!isValidBuilder(buider)) throw new Error("The builder (Docker Hub image with tag) is not valid"); + return buider; + } +} + const defaultFees: FeeTable = { upload: { amount: singleAmount(25000, "ucosm"), @@ -105,7 +115,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { /** Uploads code and returns a receipt, including the code ID */ public async upload(wasmCode: Uint8Array, meta: UploadMeta = {}, memo = ""): Promise { const source = meta.source || ""; - const builder = meta.builder || ""; + const builder = prepareBuilder(meta.builder); const compressed = pako.gzip(wasmCode, { level: 9 }); const storeCodeMsg: MsgStoreCode = {