Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add whitelistHosts option. #1451

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cli/schema/cypress.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,14 @@
"default": null,
"description": "A String or Array of hosts that you wish to block traffic for. Please read the notes for examples on using this https://on.cypress.io/configuration#blacklistHosts"
},
"whitelistHosts": {
"type": ["string", "array"],
"items": {
"type": "string"
},
"default": null,
"description": "A String or Array of hosts that you wish to allow traffic for. Please read the notes for examples on using this https://on.cypress.io/configuration#whitelistHosts"
},
"modifyObstructiveCode": {
"type": "boolean",
"default": true,
Expand Down
35 changes: 35 additions & 0 deletions packages/server/__snapshots__/whitelist_hosts_spec.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
exports['e2e whitelist passes 1'] = `
Started video recording: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4

(Tests Starting)


whitelist
✓ forces non-whitelisted hosts to return 503


1 passing


(Tests Finished)

- Tests: 1
- Passes: 1
- Failures: 0
- Pending: 0
- Duration: 10 seconds
- Screenshots: 0
- Video Recorded: true
- Cypress Version: 1.2.3


(Video)

- Started processing: Compressing to 32 CRF
- Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (0 seconds)


(All Done)

`

21 changes: 17 additions & 4 deletions packages/server/lib/config.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,12 @@ configKeys = toWords """
ignoreTestFiles
testFiles defaultCommandTimeout
trashAssetsBeforeRuns execTimeout
userAgent pageLoadTimeout
viewportWidth requestTimeout
viewportHeight responseTimeout
video taskTimeout
blacklistHosts pageLoadTimeout
whitelistHosts responseTimeout
userAgent requestTimeout
viewportWidth taskTimeout
viewportHeight
video
videoCompression
videoUploadOnPasses
watchForFileChanges
Expand All @@ -90,6 +92,7 @@ CONFIG_DEFAULTS = {
reporter: "spec"
reporterOptions: null
blacklistHosts: null
whitelistHosts: null
clientRoute: "/__/"
xhrRoute: "/xhrs/"
socketIoRoute: "/__socket.io"
Expand Down Expand Up @@ -133,6 +136,7 @@ validationRules = {
animationDistanceThreshold: v.isNumber
baseUrl: v.isFullyQualifiedUrl
blacklistHosts: v.isStringOrArrayOfStrings
whitelistHosts: v.isStringOrArrayOfStrings
modifyObstructiveCode: v.isBoolean
chromeWebSecurity: v.isBoolean
defaultCommandTimeout: v.isNumber
Expand Down Expand Up @@ -268,6 +272,15 @@ module.exports = {
config.cypressEnv = process.env["CYPRESS_ENV"]
delete config.envFile

if hosts = config.hosts
config.hosts = toObjectFromPipes(hosts)

if blacklistHosts = config.blacklistHosts
config.blacklistHosts = toArrayFromPipes(blacklistHosts)

if whitelistHosts = config.whitelistHosts
config.whitelistHosts = toArrayFromPipes(whitelistHosts)

## when headless
if config.isTextTerminal
## dont ever watch for file changes
Expand Down
27 changes: 25 additions & 2 deletions packages/server/lib/controllers/proxy.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ cwd = require("../cwd")
cors = require("../util/cors")
buffers = require("../util/buffers")
rewriter = require("../util/rewriter")
blacklist = require("../util/blacklist")
hostlist = require("../util/hostlist")
conditional = require("../util/conditional_stream")
{ passthruStream } = require("../util/passthru_stream")

Expand Down Expand Up @@ -88,7 +88,7 @@ module.exports = {
## if we have black listed hosts
if blh = config.blacklistHosts
## and url matches any of our blacklisted hosts
if matched = blacklist.matches(req.proxiedUrl, blh)
if matched = hostlist.matches(req.proxiedUrl, blh)
## then bail and return with 503
## and set a custom header
res.set("x-cypress-matched-blacklisted-host", matched)
Expand All @@ -100,6 +100,29 @@ module.exports = {

return res.status(503).end()

## if we have white listed hosts
if wlh = config.whitelistHosts
## and url does not match any of our whitelisted hosts
if not matched = hostlist.matches(req.proxiedUrl, wlh)
## then bail and return with 503
## and set a custom header
res.set("x-cypress-not-matched-whitelisted-host", '1')

debug("blacklisting request %o as not on whitelist", {
url: req.proxiedUrl
})

return res.status(503).end()

# if req.headers.accept is "text/event-stream"
# return nodeProxy.web(req, res, {
# secure: false
# ignorePath: true
# target: req.proxiedUrl
# timeout: 0
# proxyTimeout: 0
# })

thr = passthruStream()

@getHttpContent(thr, req, res, remoteState, config, request)
Expand Down
11 changes: 8 additions & 3 deletions packages/server/lib/server.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ origin = require("./util/origin")
ensureUrl = require("./util/ensure-url")
appData = require("./util/app_data")
buffers = require("./util/buffers")
blacklist = require("./util/blacklist")
hostlist = require("./util/hostlist")
statusCode = require("./util/status_code")
headersUtil = require("./util/headers")
allowDestroy = require("./util/server_destroy")
Expand Down Expand Up @@ -161,7 +161,7 @@ class Server

createServer: (app, config, project, request, onWarning) ->
new Promise (resolve, reject) =>
{port, fileServerFolder, socketIoRoute, baseUrl, blacklistHosts} = config
{port, fileServerFolder, socketIoRoute, baseUrl, blacklistHosts, whitelistHosts} = config

@_server = http.createServer(app)

Expand Down Expand Up @@ -212,10 +212,15 @@ class Server
## we cannot allow it to make a direct
## connection
if blacklistHosts and not isMatching
isMatching = blacklist.matches(urlToCheck, blacklistHosts)
isMatching = hostlist.matches(urlToCheck, blacklistHosts)

debug("HTTPS request #{urlToCheck} matches blacklist?", isMatching)

if whitelistHosts and not isMatching
isMatching = not hostlist.matches(urlToCheck, whitelistHosts)

debug("HTTPS request #{urlToCheck} does not match whitelist?", isMatching)

## make a direct connection only if
## our req url does not match the origin policy
## which is the superDomain + port
Expand Down
21 changes: 21 additions & 0 deletions packages/server/lib/util/hostlist.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
_ = require("lodash")
minimatch = require("minimatch")
uri = require("./uri")

matches = (urlToCheck, listHosts) ->
## normalize into flat array
listHosts = [].concat(listHosts)

urlToCheck = uri.stripProtocolAndDefaultPorts(urlToCheck)

matchUrl = (hostMatcher) ->
## use minimatch against the url
## to see if any match
minimatch(urlToCheck, hostMatcher)

_.find(listHosts, matchUrl)


module.exports = {
matches
}
33 changes: 33 additions & 0 deletions packages/server/test/e2e/whitelist_hosts_spec.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
e2e = require("../support/helpers/e2e")

onServer = (app) ->
app.get "/", (req, res) ->
res.send("<html>hi there</html>")

app.get "/req", (req, res) ->
res.sendStatus(200)

app.get "/status", (req, res) ->
res.sendStatus(503)

describe "e2e whitelist", ->
e2e.setup({
servers: [{
port: 3131
onServer
}, {
port: 3232
onServer
}]
settings: {
baseUrl: "http://localhost:3232"
whitelistHosts: "localhost:3232"
}
})

it "passes", ->
e2e.exec(@, {
spec: "whitelist_hosts_spec.coffee"
snapshot: true
expectedExitCode: 0
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
describe "whitelist", ->
it "forces non-whitelisted hosts to return 503", ->
cy
.visit("http://localhost:3232")

.window().then (win) ->
new Promise (resolve) ->
xhr = new win.XMLHttpRequest
xhr.open("GET", "http://localhost:3232/req")
xhr.setRequestHeader('Content-Type', 'text/plain')
xhr.send()
xhr.onload = ->
resolve(xhr)
.its("status").should("eq", 200)

.window().then (win) ->
new Promise (resolve) ->
xhr = new win.XMLHttpRequest
xhr.open("GET", "http://localhost:3131/req")
xhr.setRequestHeader('Content-Type', 'text/plain')
xhr.send()
xhr.onerror = ->
## cross origin requests which return 503
## result in a zero status code
resolve(xhr)
.its("status").should("eq", 0)
36 changes: 36 additions & 0 deletions packages/server/test/unit/config_spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,24 @@ describe "lib/config", ->
@setup({watchForFileChanges: 42})
@expectValidationFails("be a boolean")

context "whitelistHosts", ->
it "passes if a string", ->
@setup({whitelistHosts: "google.com"})
@expectValidationPasses()

it "passes if an array of strings", ->
@setup({whitelistHosts: ["google.com"]})
@expectValidationPasses()

it "fails if not a string or array", ->
@setup({whitelistHosts: 5})
@expectValidationFails("be a string or an array of string")

it "fails if not an array of strings", ->
@setup({whitelistHosts: [5]})
@expectValidationFails("be a string or an array of string")
@expectValidationFails("the value was: [5]")

context "blacklistHosts", ->
it "passes if a string", ->
@setup({blacklistHosts: "google.com"})
Expand All @@ -427,6 +445,9 @@ describe "lib/config", ->
it "includes blacklistHosts", ->
@includes("blacklistHosts")

it "includes whitelistHosts", ->
@includes("whitelistHosts")

context ".resolveConfigValues", ->
beforeEach ->
@expected = (obj) ->
Expand Down Expand Up @@ -614,6 +635,19 @@ describe "lib/config", ->
it "supportFile=false", ->
@defaults "supportFile", false, {supportFile: false}

it "whitelistHosts=null", ->
@defaults("whitelistHosts", null)

it "whitelistHosts=[a,b]", ->
@defaults("whitelistHosts", ["a", "b"], {
whitelistHosts: ["a", "b"]
})

it "whitelistHosts=a|b", ->
@defaults("whitelistHosts", ["a", "b"], {
whitelistHosts: "a|b"
})

it "blacklistHosts=null", ->
@defaults("blacklistHosts", null)

Expand Down Expand Up @@ -734,6 +768,7 @@ describe "lib/config", ->
port: { value: 1234, from: "cli" },
hosts: { value: null, from: "default" }
blacklistHosts: { value: null, from: "default" }
whitelistHosts: { value: null, from: "default" }
userAgent: { value: null, from: "default" }
reporter: { value: "json", from: "cli" },
reporterOptions: { value: null, from: "default" },
Expand Down Expand Up @@ -800,6 +835,7 @@ describe "lib/config", ->
port: { value: 2020, from: "config" },
hosts: { value: null, from: "default" }
blacklistHosts: { value: null, from: "default" }
whitelistHosts: { value: null, from: "default" }
userAgent: { value: null, from: "default" }
reporter: { value: "spec", from: "default" },
reporterOptions: { value: null, from: "default" },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require("../spec_helper")

blacklist = require("#{root}lib/util/blacklist")
hostlist = require("#{root}lib/util/hostlist")

hosts = [
"*.google.com"
Expand All @@ -11,17 +11,17 @@ hosts = [
]

matchesStr = (url, host, val) ->
m = blacklist.matches(url, host)
m = hostlist.matches(url, host)
expect(!!m).to.eq(val, "url: '#{url}' did not pass")

matchesArray = (url, val) ->
m = blacklist.matches(url, hosts)
m = hostlist.matches(url, hosts)
expect(!!m).to.eq(val, "url: '#{url}' did not pass")

matchesHost = (url, host) ->
expect(blacklist.matches(url, hosts)).to.eq(host)
expect(hostlist.matches(url, hosts)).to.eq(host)

describe "lib/util/blacklist", ->
describe "lib/util/hostlist", ->
it "handles hosts, ports, wildcards", ->
matchesArray("https://mail.google.com/foo", true)
matchesArray("https://shop.apple.com/bar", true)
Expand Down