diff --git a/src/commands/container/login.ts b/src/commands/container/login.ts index b4b976e62b..6af19e4319 100644 --- a/src/commands/container/login.ts +++ b/src/commands/container/login.ts @@ -1,4 +1,4 @@ -import {Command, flags} from '@heroku-cli/command' +import {Command, flags, vars} from '@heroku-cli/command' import {ux} from '@oclif/core/ux' import {debug} from '../../lib/container/debug.js' @@ -45,8 +45,7 @@ export default class Login extends Command { async run() { const {flags} = await this.parse(Login) const {verbose} = flags - const herokuHost = process.env.HEROKU_HOST || 'heroku.com' - const registry = `registry.${herokuHost}` + const registry = `registry.${vars.host}` const password = this.heroku.auth if (verbose) { diff --git a/src/commands/container/logout.ts b/src/commands/container/logout.ts index 30f6b11648..6f03892e7b 100644 --- a/src/commands/container/logout.ts +++ b/src/commands/container/logout.ts @@ -1,4 +1,4 @@ -import {Command, flags} from '@heroku-cli/command' +import {Command, flags, vars} from '@heroku-cli/command' import {ux} from '@oclif/core/ux' import {debug} from '../../lib/container/debug.js' @@ -24,8 +24,7 @@ export default class Logout extends Command { async run() { const {flags} = await this.parse(Logout) const {verbose} = flags - const herokuHost = process.env.HEROKU_HOST || 'heroku.com' - const registry = `registry.${herokuHost}` + const registry = `registry.${vars.host}` if (verbose) { debug.enabled = true diff --git a/src/commands/container/pull.ts b/src/commands/container/pull.ts index dc9f94415f..14ea4eff14 100644 --- a/src/commands/container/pull.ts +++ b/src/commands/container/pull.ts @@ -1,4 +1,4 @@ -import {Command, flags} from '@heroku-cli/command' +import {Command, flags, vars} from '@heroku-cli/command' import * as Heroku from '@heroku-cli/schema' import {color, hux} from '@heroku/heroku-cli-util' @@ -34,8 +34,7 @@ export default class Pull extends Command { const {body: appBody} = await this.heroku.get(`/apps/${app}`) ensureContainerStack(appBody, 'pull') - const herokuHost = process.env.HEROKU_HOST || 'heroku.com' - const registry = `registry.${herokuHost}` + const registry = `registry.${vars.host}` if (verbose) { debug.enabled = true diff --git a/src/commands/container/push.ts b/src/commands/container/push.ts index 59d55a96ba..a12960124e 100644 --- a/src/commands/container/push.ts +++ b/src/commands/container/push.ts @@ -1,4 +1,4 @@ -import {Command, flags} from '@heroku-cli/command' +import {Command, flags, vars} from '@heroku-cli/command' import * as Heroku from '@heroku-cli/schema' import {color, hux} from '@heroku/heroku-cli-util' import {ux} from '@oclif/core/ux' @@ -48,8 +48,7 @@ export default class Push extends Command { const {body: appBody} = await this.heroku.get(`/apps/${app}`) ensureContainerStack(appBody, 'push') - const herokuHost = process.env.HEROKU_HOST || 'heroku.com' - const registry = `registry.${herokuHost}` + const registry = `registry.${vars.host}` const dockerfiles = this.dockerHelper.getDockerfiles(process.cwd(), recursive) const possibleJobs = this.dockerHelper.getJobs(`${registry}/${app}`, dockerfiles) const jobs = await this.selectJobs(possibleJobs, processTypes as string[], recursive) diff --git a/src/commands/container/release.ts b/src/commands/container/release.ts index 0788dbd1b8..ae1248b584 100644 --- a/src/commands/container/release.ts +++ b/src/commands/container/release.ts @@ -1,4 +1,4 @@ -import {Command, flags} from '@heroku-cli/command' +import {Command, flags, vars} from '@heroku-cli/command' import * as Heroku from '@heroku-cli/schema' import * as color from '@heroku/heroku-cli-util/color' import {ux} from '@oclif/core/ux' @@ -43,7 +43,6 @@ export default class ContainerRelease extends Command { const {body: appBody} = await this.heroku.get(`/apps/${app}`) ensureContainerStack(appBody, 'release') - const herokuHost: string = process.env.HEROKU_HOST || 'heroku.com' const updateData: any[] = [] for (const process of argv) { const image = `${app}/${process}` @@ -55,7 +54,7 @@ export default class ContainerRelease extends Command { Accept: 'application/vnd.docker.distribution.manifest.v2+json', Authorization: `Basic ${Buffer.from(`:${this.heroku.auth}`).toString('base64')}`, }, - hostname: `registry.${herokuHost}`, + hostname: `registry.${vars.host}`, }, ) let imageID diff --git a/src/commands/container/run.ts b/src/commands/container/run.ts index 761172509c..8c544eb736 100644 --- a/src/commands/container/run.ts +++ b/src/commands/container/run.ts @@ -1,4 +1,4 @@ -import {Command, flags} from '@heroku-cli/command' +import {Command, flags, vars} from '@heroku-cli/command' import * as Heroku from '@heroku-cli/schema' import {color, hux} from '@heroku/heroku-cli-util' import {ux} from '@oclif/core/ux' @@ -43,8 +43,7 @@ export default class Run extends Command { const processType = argv.shift() as string const command: string = argv.join(' ') - const herokuHost = process.env.HEROKU_HOST || 'heroku.com' - const registry = `registry.${herokuHost}` + const registry = `registry.${vars.host}` const dockerfiles = this.dockerHelper.getDockerfiles(process.cwd(), false) const possibleJobs = this.dockerHelper.getJobs(`${registry}/${app}`, dockerfiles) diff --git a/test/unit/commands/container/login.unit.test.ts b/test/unit/commands/container/login.unit.test.ts index ac3a90704e..73f0d901eb 100644 --- a/test/unit/commands/container/login.unit.test.ts +++ b/test/unit/commands/container/login.unit.test.ts @@ -30,6 +30,35 @@ describe('container:login', function () { sandbox.assert.calledOnce(login) }) + context('when HEROKU_HOST is set to an invalid domain', function () { + let originalHost: string | undefined + + beforeEach(function () { + originalHost = process.env.HEROKU_HOST + process.env.HEROKU_HOST = 'attacker.com' + }) + + afterEach(function () { + if (originalHost === undefined) { + delete process.env.HEROKU_HOST + } else { + process.env.HEROKU_HOST = originalHost + } + }) + + it('rejects invalid HEROKU_HOST and uses default registry', async function () { + const version = sandbox.stub(DockerHelper.prototype, 'version').resolves([19, 12]) + const login = sandbox.stub(DockerHelper.prototype, 'cmd') + .withArgs('docker', ['login', '--username=_', '--password-stdin', 'registry.heroku.com'], {input: 'heroku_token'}) + + const {stderr} = await runCommand(Cmd) + + expect(stderr).to.contain("Invalid HEROKU_HOST 'attacker.com'") + sandbox.assert.calledOnce(version) + sandbox.assert.calledOnce(login) + }) + }) + it('logs to the docker registry with an old version', async function () { const version = sandbox.stub(DockerHelper.prototype, 'version').returns(new Promise(function (resolve) { resolve([17, 0]) diff --git a/test/unit/commands/container/logout.unit.test.ts b/test/unit/commands/container/logout.unit.test.ts index 07f33b1742..ca8d887a31 100644 --- a/test/unit/commands/container/logout.unit.test.ts +++ b/test/unit/commands/container/logout.unit.test.ts @@ -16,6 +16,33 @@ describe('container logout', function () { return sandbox.restore() }) + context('when HEROKU_HOST is set to an invalid domain', function () { + let originalHost: string | undefined + + beforeEach(function () { + originalHost = process.env.HEROKU_HOST + process.env.HEROKU_HOST = 'attacker.com' + }) + + afterEach(function () { + if (originalHost === undefined) { + delete process.env.HEROKU_HOST + } else { + process.env.HEROKU_HOST = originalHost + } + }) + + it('rejects invalid HEROKU_HOST and uses default registry', async function () { + const logout = sandbox.stub(DockerHelper.prototype, 'cmd') + .withArgs('docker', ['logout', 'registry.heroku.com']) + + const {stderr} = await runCommand(Cmd) + + expect(stderr).to.contain("Invalid HEROKU_HOST 'attacker.com'") + sandbox.assert.calledOnce(logout) + }) + }) + it('logs out of the docker registry', async function () { const logout = sandbox.stub(DockerHelper.prototype, 'cmd') .withArgs('docker', ['logout', 'registry.heroku.com']) diff --git a/test/unit/commands/container/release.unit.test.ts b/test/unit/commands/container/release.unit.test.ts index 5fcdedf6dd..f7cf99854d 100644 --- a/test/unit/commands/container/release.unit.test.ts +++ b/test/unit/commands/container/release.unit.test.ts @@ -46,6 +46,55 @@ describe('container release', function () { expect(oclif.exit).to.equal(1) }) + context('when HEROKU_HOST is set to an invalid domain', function () { + let originalHost: string | undefined + let registry: nock.Scope + + beforeEach(function () { + originalHost = process.env.HEROKU_HOST + process.env.HEROKU_HOST = 'attacker.com' + api + .get('/apps/testapp') + .reply(200, {name: 'testapp', stack: {name: 'container'}}) + registry = nock('https://registry.heroku.com:443') + }) + + afterEach(function () { + if (originalHost === undefined) { + delete process.env.HEROKU_HOST + } else { + process.env.HEROKU_HOST = originalHost + } + + registry.done() + }) + + it('rejects invalid host and sends request to registry.heroku.com', async function () { + api + .patch('/apps/testapp/formation', { + updates: [ + {docker_image: 'image_id', type: 'web'}, + ], + }) + .reply(200, {}) + .get('/apps/testapp/releases') + .reply(200, []) + .get('/apps/testapp/releases') + .reply(200, [{id: 'release_id'}]) + registry + .get('/v2/testapp/web/manifests/latest') + .reply(200, {config: {digest: 'image_id'}, schemaVersion: 2}) + + const {stderr} = await runCommand(Cmd, [ + '--app', + 'testapp', + 'web', + ]) + + expect(stderr).to.contain("Invalid HEROKU_HOST 'attacker.com'") + }) + }) + context('when the app is a container app', function () { let registry: nock.Scope beforeEach(function () {