diff --git a/core/src/config/common.ts b/core/src/config/common.ts index 85080c465b..796e687658 100644 --- a/core/src/config/common.ts +++ b/core/src/config/common.ts @@ -118,6 +118,7 @@ export interface Schema extends Joi.Root { environment: () => Joi.StringSchema gitUrl: () => GitUrlSchema posixPath: () => PosixPathSchema + hostname: () => Joi.StringSchema } export let joi: Schema = Joi.extend({ @@ -354,6 +355,44 @@ joi = joi.extend({ }, }) +/** + * Add a joi.hostname() type. Like joi.string().hostname() with the exception that it allows + * wildcards in the first DNS label and returns a custom error if it finds wildcards in labels + * other than the first. + */ +joi = joi.extend({ + base: Joi.string(), + type: "hostname", + messages: { + base: "{{#label}} must be a valid hostname.", + wildcardLabel: "{{#label}} only first DNS label my contain a wildcard.", + }, + validate(value: string, { error }) { + const baseSchema = joi.string().hostname() + const wildcardLabel = "*." + let result: Joi.ValidationResult + + const labels = value.split(".") + // Hostname includes a wildcard label that is not the first label + if (!value.startsWith(wildcardLabel) && labels.includes("*")) { + return { value, errors: error("wildcardLabel") } + } + + if (value.startsWith(wildcardLabel)) { + const restLabels = value.slice(wildcardLabel.length) + result = baseSchema.validate(restLabels) + } else { + result = baseSchema.validate(value) + } + + if (result.error) { + return { value, errors: error("base") } + } + + return { value } + }, +}) + export const joiPrimitive = () => joi .alternatives() diff --git a/core/src/plugins/kubernetes/config.ts b/core/src/plugins/kubernetes/config.ts index 3545e5778d..15396dee8e 100644 --- a/core/src/plugins/kubernetes/config.ts +++ b/core/src/plugins/kubernetes/config.ts @@ -264,7 +264,7 @@ const tlsCertificateSchema = () => .example("wildcard"), hostnames: joi .array() - .items(joi.string().hostname()) + .items(joi.hostname()) .description( "A list of hostnames that this certificate should be used for. " + "If you don't specify these, they will be automatically read from the certificate." diff --git a/core/src/types/service.ts b/core/src/types/service.ts index 0f5d1effaf..8a67d9ea2a 100644 --- a/core/src/types/service.ts +++ b/core/src/types/service.ts @@ -102,7 +102,7 @@ export interface ServiceIngress extends ServiceIngressSpec { } export const ingressHostnameSchema = () => - joi.string().hostname().description(dedent` + joi.hostname().description(dedent` The hostname that should route to this service. Defaults to the default hostname configured in the provider configuration. Note that if you're developing locally you may need to add this hostname to your hosts file. diff --git a/core/test/unit/src/config/common.ts b/core/test/unit/src/config/common.ts index da07d1ccf6..676690356e 100644 --- a/core/test/unit/src/config/common.ts +++ b/core/test/unit/src/config/common.ts @@ -276,6 +276,24 @@ describe("joi.posixPath", () => { }) }) +describe("joi.hostname", () => { + const schema = joi.hostname() + + it("should accept valid hostnames", () => { + const result = schema.validate("foo.bar.bas") + expect(result.error).to.be.undefined + }) + it("should accept hostnames with a wildcard in the first DNS label", () => { + const result = schema.validate("*.bar.bas") + expect(result.error).to.be.undefined + }) + it("should reject hostnames with wildcard DNS labels that are not the first label", () => { + const result = schema.validate("foo.*.bas") + expect(result.error).to.exist + expect(result!.error!.message).to.eql(`"value" only first DNS label my contain a wildcard.`) + }) +}) + describe("joi.environment", () => { const schema = joi.environment() diff --git a/docs/reference/module-types/container.md b/docs/reference/module-types/container.md index 549e08235b..f08af42aed 100644 --- a/docs/reference/module-types/container.md +++ b/docs/reference/module-types/container.md @@ -1030,9 +1030,9 @@ The hostname that should route to this service. Defaults to the default hostname Note that if you're developing locally you may need to add this hostname to your hosts file. -| Type | Required | -| -------- | -------- | -| `string` | No | +| Type | Required | +| ---------- | -------- | +| `hostname` | No | ### `services[].ingresses[].linkUrl` diff --git a/docs/reference/module-types/maven-container.md b/docs/reference/module-types/maven-container.md index 1809448358..387afb25ce 100644 --- a/docs/reference/module-types/maven-container.md +++ b/docs/reference/module-types/maven-container.md @@ -1038,9 +1038,9 @@ The hostname that should route to this service. Defaults to the default hostname Note that if you're developing locally you may need to add this hostname to your hosts file. -| Type | Required | -| -------- | -------- | -| `string` | No | +| Type | Required | +| ---------- | -------- | +| `hostname` | No | ### `services[].ingresses[].linkUrl`