From a5d4ad6cc20b22444920e009e3b594ab8b3d3b00 Mon Sep 17 00:00:00 2001 From: Etienne Tremel Date: Tue, 30 Jun 2020 20:52:45 +0200 Subject: [PATCH 1/3] fix: remove authorization header from core.windows.net requests for Azure docker registries --- lib/util/http/index.spec.ts | 82 ++++++++++++++++++++++++++++++++++++- lib/util/http/index.ts | 53 ++++++++++++++++-------- 2 files changed, 116 insertions(+), 19 deletions(-) diff --git a/lib/util/http/index.spec.ts b/lib/util/http/index.spec.ts index f97e240dc77c21..e2a73c8a690acb 100644 --- a/lib/util/http/index.spec.ts +++ b/lib/util/http/index.spec.ts @@ -2,7 +2,7 @@ import nock from 'nock'; import { getName } from '../../../test/util'; import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import * as hostRules from '../host-rules'; -import { Http } from '.'; +import { Http, removeAuthorizationHeaders } from '.'; const baseUrl = 'http://renovate.com'; @@ -106,4 +106,84 @@ describe(getName(__filename), () => { expect(data).toBe('{}'); expect(nock.isDone()).toBe(true); }); + + it('removeAuthorizationHeaders Amazon', async () => { + expect( + await removeAuthorizationHeaders({ + auth: 'auth', + headers: { + authorization: 'auth', + }, + hostname: 'amazon.com', + href: 'https://amazon.com', + search: 'something X-Amz-Algorithm something', + }) + ).toEqual({ + headers: {}, + hostname: 'amazon.com', + href: 'https://amazon.com', + search: 'something X-Amz-Algorithm something', + }); + }); + + it('removeAuthorizationHeaders Amazon ports', async () => { + expect( + await removeAuthorizationHeaders({ + auth: 'auth', + headers: { + authorization: 'auth', + }, + hostname: 'amazon.com', + href: 'https://amazon.com', + port: 3000, + search: 'something X-Amz-Algorithm something', + }) + ).toEqual({ + headers: {}, + hostname: 'amazon.com', + href: 'https://amazon.com', + search: 'something X-Amz-Algorithm something', + }); + }); + + it('removeAuthorizationHeaders Azure blob', async () => { + expect( + await removeAuthorizationHeaders({ + auth: 'auth', + headers: { + authorization: 'auth', + }, + hostname: 'store123.blob.core.windows.net', + href: + 'https://.blob.core.windows.net///docker/registry/v2/blobs', + }) + ).toEqual({ + headers: {}, + hostname: 'store123.blob.core.windows.net', + href: + 'https://.blob.core.windows.net///docker/registry/v2/blobs', + }); + }); + + it('removeAuthorizationHeaders keep auth', async () => { + expect( + await removeAuthorizationHeaders({ + auth: 'auth', + headers: { + authorization: 'auth', + }, + hostname: 'renovate.com', + href: 'https://renovate.com', + search: 'something', + }) + ).toEqual({ + auth: 'auth', + headers: { + authorization: 'auth', + }, + hostname: 'renovate.com', + href: 'https://renovate.com', + search: 'something', + }); + }); }); diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts index c140432f724c77..96101174a6f6c8 100644 --- a/lib/util/http/index.ts +++ b/lib/util/http/index.ts @@ -57,6 +57,40 @@ async function resolveResponse( } } +// isAmazon return true if request options contains Amazon related headers +function isAmazon(opts: any): boolean { + return opts.search?.includes('X-Amz-Algorithm'); +} + +// isAzureBlob return true if request options contains Azure container registry related data +function isAzureBlob(opts: any): boolean { + return ( + opts.hostname?.endsWith('blob.core.windows.net') && // lgtm [js/incomplete-url-substring-sanitization] + opts.href?.includes('/docker/registry') + ); +} + +// removeAuthorizationHeaders from the redirect options +export function removeAuthorizationHeaders(opts: any): any { + // Check if request has been redirected to Amazon or an Azure blob (ACR) + if (isAmazon(opts) || isAzureBlob(opts)) { + // if there is no port in the redirect URL string, then delete it from the redirect options. + // This can be evaluated for removal after upgrading to Got v10 + const portInUrl = opts.href.split('/')[2].split(':')[1]; + if (!portInUrl) { + // eslint-disable-next-line no-param-reassign + delete opts.port; // Redirect will instead use 80 or 443 for HTTP or HTTPS respectively + } + + // registry is hosted on Amazon or Azure blob, redirect url includes + // authentication which is not required and should be removed + delete opts.headers.authorization; // eslint-disable-line no-param-reassign + delete opts.auth; // eslint-disable-line no-param-reassign + } + + return opts; +} + export class Http { constructor(private hostType: string, private options?: HttpOptions) {} @@ -79,24 +113,7 @@ export class Http { options.retry = 0; } options.hooks = { - beforeRedirect: [ - (opts: any): void => { - // Check if request has been redirected to Amazon - if (opts.search?.includes('X-Amz-Algorithm')) { - // if there is no port in the redirect URL string, then delete it from the redirect options. - // This can be evaluated for removal after upgrading to Got v10 - const portInUrl = opts.href.split('/')[2].split(':')[1]; - if (!portInUrl) { - // eslint-disable-next-line no-param-reassign - delete opts.port; // Redirect will instead use 80 or 443 for HTTP or HTTPS respectively - } - - // registry is hosted on amazon, redirect url includes authentication. - delete opts.headers.authorization; // eslint-disable-line no-param-reassign - delete opts.auth; // eslint-disable-line no-param-reassign - } - }, - ], + beforeRedirect: [removeAuthorizationHeaders], }; options.headers = { ...options.headers, From ae19aa0866f6ce1e7847f15b2201ed35352db61e Mon Sep 17 00:00:00 2001 From: Etienne Tremel Date: Wed, 1 Jul 2020 11:50:19 +0200 Subject: [PATCH 2/3] fix: lgtm alert suppression for js/incomplete-url-substring-sanitization --- lib/util/http/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts index 96101174a6f6c8..ab15913f340e1e 100644 --- a/lib/util/http/index.ts +++ b/lib/util/http/index.ts @@ -65,9 +65,9 @@ function isAmazon(opts: any): boolean { // isAzureBlob return true if request options contains Azure container registry related data function isAzureBlob(opts: any): boolean { return ( - opts.hostname?.endsWith('blob.core.windows.net') && // lgtm [js/incomplete-url-substring-sanitization] + opts.hostname?.endsWith('blob.core.windows.net') && opts.href?.includes('/docker/registry') - ); + ); // lgtm [js/incomplete-url-substring-sanitization] } // removeAuthorizationHeaders from the redirect options From ef5d38e6df9b3b1ec377a1406797b5495b7d1ccf Mon Sep 17 00:00:00 2001 From: Etienne Tremel Date: Wed, 1 Jul 2020 13:40:06 +0200 Subject: [PATCH 3/3] refactor: move removeAuthorization function to lib/util/http/auth.ts --- lib/util/http/auth.spec.ts | 108 ++++++++++++++++++++++++++++++++++++ lib/util/http/auth.ts | 36 ++++++++++++ lib/util/http/index.spec.ts | 82 +-------------------------- lib/util/http/index.ts | 38 +------------ 4 files changed, 147 insertions(+), 117 deletions(-) create mode 100644 lib/util/http/auth.spec.ts diff --git a/lib/util/http/auth.spec.ts b/lib/util/http/auth.spec.ts new file mode 100644 index 00000000000000..42fc1473c7d2e8 --- /dev/null +++ b/lib/util/http/auth.spec.ts @@ -0,0 +1,108 @@ +import { getName } from '../../../test/util'; +import { removeAuthorization } from './auth'; + +describe(getName(__filename), () => { + it('removeAuthorization no authorization', () => { + const opts = { + hostname: 'amazon.com', + href: 'https://amazon.com', + search: 'something X-Amz-Algorithm something', + }; + + removeAuthorization(opts); + + expect(opts).toEqual({ + hostname: 'amazon.com', + href: 'https://amazon.com', + search: 'something X-Amz-Algorithm something', + }); + }); + + it('removeAuthorization Amazon', () => { + const opts = { + auth: 'auth', + headers: { + authorization: 'auth', + }, + hostname: 'amazon.com', + href: 'https://amazon.com', + search: 'something X-Amz-Algorithm something', + }; + + removeAuthorization(opts); + + expect(opts).toEqual({ + headers: {}, + hostname: 'amazon.com', + href: 'https://amazon.com', + search: 'something X-Amz-Algorithm something', + }); + }); + + it('removeAuthorization Amazon ports', () => { + const opts = { + auth: 'auth', + headers: { + authorization: 'auth', + }, + hostname: 'amazon.com', + href: 'https://amazon.com', + port: 3000, + search: 'something X-Amz-Algorithm something', + }; + + removeAuthorization(opts); + + expect(opts).toEqual({ + headers: {}, + hostname: 'amazon.com', + href: 'https://amazon.com', + search: 'something X-Amz-Algorithm something', + }); + }); + + it('removeAuthorization Azure blob', () => { + const opts = { + auth: 'auth', + headers: { + authorization: 'auth', + }, + hostname: 'store123.blob.core.windows.net', + href: + 'https://.blob.core.windows.net///docker/registry/v2/blobs', + }; + + removeAuthorization(opts); + + expect(opts).toEqual({ + headers: {}, + hostname: 'store123.blob.core.windows.net', + href: + 'https://.blob.core.windows.net///docker/registry/v2/blobs', + }); + }); + + it('removeAuthorization keep auth', () => { + const opts = { + auth: 'auth', + headers: { + authorization: 'auth', + }, + hostname: 'renovate.com', + href: 'https://renovate.com', + search: 'something', + }; + + removeAuthorization(opts); + + expect(opts).toEqual({ + auth: 'auth', + headers: { + authorization: 'auth', + }, + hostname: 'renovate.com', + href: 'https://renovate.com', + search: 'something', + }); + }); +}); diff --git a/lib/util/http/auth.ts b/lib/util/http/auth.ts index f9bdc50e1e3950..31d3f523579fa2 100644 --- a/lib/util/http/auth.ts +++ b/lib/util/http/auth.ts @@ -24,3 +24,39 @@ export function applyAuthorization(inOptions: any): any { } return options; } + +// isAmazon return true if request options contains Amazon related headers +function isAmazon(options: any): boolean { + return options.search?.includes('X-Amz-Algorithm'); +} + +// isAzureBlob return true if request options contains Azure container registry related data +function isAzureBlob(options: any): boolean { + return ( + options.hostname?.endsWith('.blob.core.windows.net') && // lgtm [js/incomplete-url-substring-sanitization] + options.href?.includes('/docker/registry') + ); +} + +// removeAuthorization from the redirect options +export function removeAuthorization(options: any): void { + if (!options.auth && !options.headers?.authorization) { + return; + } + + // Check if request has been redirected to Amazon or an Azure blob (ACR) + if (isAmazon(options) || isAzureBlob(options)) { + // if there is no port in the redirect URL string, then delete it from the redirect options. + // This can be evaluated for removal after upgrading to Got v10 + const portInUrl = options.href.split('/')[2].split(':')[1]; + if (!portInUrl) { + // eslint-disable-next-line no-param-reassign + delete options.port; // Redirect will instead use 80 or 443 for HTTP or HTTPS respectively + } + + // registry is hosted on Amazon or Azure blob, redirect url includes + // authentication which is not required and should be removed + delete options.headers.authorization; // eslint-disable-line no-param-reassign + delete options.auth; // eslint-disable-line no-param-reassign + } +} diff --git a/lib/util/http/index.spec.ts b/lib/util/http/index.spec.ts index e2a73c8a690acb..f97e240dc77c21 100644 --- a/lib/util/http/index.spec.ts +++ b/lib/util/http/index.spec.ts @@ -2,7 +2,7 @@ import nock from 'nock'; import { getName } from '../../../test/util'; import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import * as hostRules from '../host-rules'; -import { Http, removeAuthorizationHeaders } from '.'; +import { Http } from '.'; const baseUrl = 'http://renovate.com'; @@ -106,84 +106,4 @@ describe(getName(__filename), () => { expect(data).toBe('{}'); expect(nock.isDone()).toBe(true); }); - - it('removeAuthorizationHeaders Amazon', async () => { - expect( - await removeAuthorizationHeaders({ - auth: 'auth', - headers: { - authorization: 'auth', - }, - hostname: 'amazon.com', - href: 'https://amazon.com', - search: 'something X-Amz-Algorithm something', - }) - ).toEqual({ - headers: {}, - hostname: 'amazon.com', - href: 'https://amazon.com', - search: 'something X-Amz-Algorithm something', - }); - }); - - it('removeAuthorizationHeaders Amazon ports', async () => { - expect( - await removeAuthorizationHeaders({ - auth: 'auth', - headers: { - authorization: 'auth', - }, - hostname: 'amazon.com', - href: 'https://amazon.com', - port: 3000, - search: 'something X-Amz-Algorithm something', - }) - ).toEqual({ - headers: {}, - hostname: 'amazon.com', - href: 'https://amazon.com', - search: 'something X-Amz-Algorithm something', - }); - }); - - it('removeAuthorizationHeaders Azure blob', async () => { - expect( - await removeAuthorizationHeaders({ - auth: 'auth', - headers: { - authorization: 'auth', - }, - hostname: 'store123.blob.core.windows.net', - href: - 'https://.blob.core.windows.net///docker/registry/v2/blobs', - }) - ).toEqual({ - headers: {}, - hostname: 'store123.blob.core.windows.net', - href: - 'https://.blob.core.windows.net///docker/registry/v2/blobs', - }); - }); - - it('removeAuthorizationHeaders keep auth', async () => { - expect( - await removeAuthorizationHeaders({ - auth: 'auth', - headers: { - authorization: 'auth', - }, - hostname: 'renovate.com', - href: 'https://renovate.com', - search: 'something', - }) - ).toEqual({ - auth: 'auth', - headers: { - authorization: 'auth', - }, - hostname: 'renovate.com', - href: 'https://renovate.com', - search: 'something', - }); - }); }); diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts index ab15913f340e1e..78a57d43e088e3 100644 --- a/lib/util/http/index.ts +++ b/lib/util/http/index.ts @@ -4,7 +4,7 @@ import got from 'got'; import { ExternalHostError } from '../../types/errors/external-host-error'; import * as memCache from '../cache/memory'; import { clone } from '../clone'; -import { applyAuthorization } from './auth'; +import { applyAuthorization, removeAuthorization } from './auth'; import { applyHostRules } from './host-rules'; interface OutgoingHttpHeaders { @@ -57,40 +57,6 @@ async function resolveResponse( } } -// isAmazon return true if request options contains Amazon related headers -function isAmazon(opts: any): boolean { - return opts.search?.includes('X-Amz-Algorithm'); -} - -// isAzureBlob return true if request options contains Azure container registry related data -function isAzureBlob(opts: any): boolean { - return ( - opts.hostname?.endsWith('blob.core.windows.net') && - opts.href?.includes('/docker/registry') - ); // lgtm [js/incomplete-url-substring-sanitization] -} - -// removeAuthorizationHeaders from the redirect options -export function removeAuthorizationHeaders(opts: any): any { - // Check if request has been redirected to Amazon or an Azure blob (ACR) - if (isAmazon(opts) || isAzureBlob(opts)) { - // if there is no port in the redirect URL string, then delete it from the redirect options. - // This can be evaluated for removal after upgrading to Got v10 - const portInUrl = opts.href.split('/')[2].split(':')[1]; - if (!portInUrl) { - // eslint-disable-next-line no-param-reassign - delete opts.port; // Redirect will instead use 80 or 443 for HTTP or HTTPS respectively - } - - // registry is hosted on Amazon or Azure blob, redirect url includes - // authentication which is not required and should be removed - delete opts.headers.authorization; // eslint-disable-line no-param-reassign - delete opts.auth; // eslint-disable-line no-param-reassign - } - - return opts; -} - export class Http { constructor(private hostType: string, private options?: HttpOptions) {} @@ -113,7 +79,7 @@ export class Http { options.retry = 0; } options.hooks = { - beforeRedirect: [removeAuthorizationHeaders], + beforeRedirect: [removeAuthorization], }; options.headers = { ...options.headers,