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

fix(docker): use a HEAD request to the real resource auth. #14744

Merged
merged 9 commits into from
Apr 6, 2022
129 changes: 92 additions & 37 deletions lib/modules/datasource/docker/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,12 @@ describe('modules/datasource/docker/index', () => {
});
});
describe('getAuthHeaders', () => {
beforeEach(() => {
it('returns "authType token" if both provided', async () => {
httpMock
.scope('https://my.local.registry')
.get('/v2/', undefined, { badheaders: ['authorization'] })
.reply(401, '', { 'www-authenticate': 'Authenticate you must' });
hostRules.hosts.mockReturnValue([]);
});

it('returns "authType token" if both provided', async () => {
hostRules.find.mockReturnValue({
authType: 'some-authType',
token: 'some-token',
Expand All @@ -130,6 +127,11 @@ describe('modules/datasource/docker/index', () => {
});

it('returns "Bearer token" if only token provided', async () => {
httpMock
.scope('https://my.local.registry')
.get('/v2/', undefined, { badheaders: ['authorization'] })
.reply(401, '', { 'www-authenticate': 'Authenticate you must' });
hostRules.hosts.mockReturnValue([]);
hostRules.find.mockReturnValue({
token: 'some-token',
});
Expand All @@ -149,6 +151,11 @@ describe('modules/datasource/docker/index', () => {
});

it('fails', async () => {
httpMock
.scope('https://my.local.registry')
.get('/v2/', undefined, { badheaders: ['authorization'] })
.reply(401, '', { 'www-authenticate': 'Authenticate you must' });
hostRules.hosts.mockReturnValue([]);
httpMock.clear(false);

httpMock
Expand All @@ -164,6 +171,35 @@ describe('modules/datasource/docker/index', () => {

expect(headers).toBeNull();
});

it('use a HEAD request to the real resource and use the auth header values to get a token', async () => {
httpMock
.scope('https://my.local.registry')
.head('/v2/my/node/resource')
.reply(401, '', {
'www-authenticate':
'Bearer realm="https://my.local.registry/oauth2/token",service="my.local.registry",scope="repository:my/node:whatever"',
})
.get(
'/oauth2/token?service=my.local.registry&scope=repository:my/node:whatever'
)
.reply(200, { token: 'some-token' });

const headers = await getAuthHeaders(
http,
'https://my.local.registry',
'my/node/prefix',
'head',
'https://my.local.registry/v2/my/node/resource'
);

// do not inline, otherwise we get false positive from codeql
expect(headers).toMatchInlineSnapshot(`
Object {
"authorization": "Bearer some-token",
}
`);
});
});

describe('getDigest', () => {
Expand Down Expand Up @@ -219,7 +255,7 @@ describe('modules/datasource/docker/index', () => {
.get('/')
.reply(401, '', {
'www-authenticate':
'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:samalba/my-app:pull "',
'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/some-dep:pull "',
vlagorce marked this conversation as resolved.
Show resolved Hide resolved
})
.head('/library/some-dep/manifests/latest')
.reply(200, {}, { 'docker-content-digest': 'some-digest' });
Expand All @@ -245,7 +281,7 @@ describe('modules/datasource/docker/index', () => {
.twice()
.reply(401, '', {
'www-authenticate':
'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:samalba/my-app:pull "',
'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/some-dep:pull "',
})
.head('/library/some-dep/manifests/some-new-value')
.reply(200, undefined, {})
Expand Down Expand Up @@ -493,7 +529,7 @@ describe('modules/datasource/docker/index', () => {
.get('/')
.reply(401, '', {
'www-authenticate':
'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:samalba/my-app:pull "',
'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/some-other-dep:pull "',
})
.head('/library/some-other-dep/manifests/8.0.0-alpine')
.reply(200, {}, { 'docker-content-digest': 'some-digest' });
Expand Down Expand Up @@ -529,7 +565,7 @@ describe('modules/datasource/docker/index', () => {
it('returns null if no token', async () => {
httpMock
.scope(baseUrl)
.get('/')
.head('/library/node/tags/list?n=10000')
.reply(200, '', {})
.get('/library/node/tags/list?n=10000')
.reply(403);
Expand All @@ -545,7 +581,7 @@ describe('modules/datasource/docker/index', () => {
const tags = ['1.0.0'];
httpMock
.scope('https://registry.company.com/v2')
.get('/')
.head('/node/tags/list?n=10000')
.reply(200, '', {})
.get('/node/tags/list?n=10000')
.reply(
Expand Down Expand Up @@ -576,7 +612,7 @@ describe('modules/datasource/docker/index', () => {
const tags = ['1.0.0'];
httpMock
.scope('https://registry.company.com/v2')
.get('/')
.head('/node/tags/list?n=10000')
.reply(200, '', {})
.get('/node/tags/list?n=10000')
.reply(200, { tags }, {})
Expand Down Expand Up @@ -634,7 +670,7 @@ describe('modules/datasource/docker/index', () => {
it('uses lower tag limit for ECR deps', async () => {
httpMock
.scope(amazonUrl)
.get('/')
.head('/node/tags/list?n=1000')
.reply(200, '', {})
// The tag limit parameter `n` needs to be limited to 1000 for ECR
// See https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_DescribeRepositories.html#ECR-DescribeRepositories-request-maxResults
Expand All @@ -659,7 +695,7 @@ describe('modules/datasource/docker/index', () => {
it('resolves requests to ECR proxy', async () => {
httpMock
.scope('https://ecr-proxy.company.com/v2')
.get('/')
.head('/node/tags/list?n=10000')
.reply(200, '', {})
.get('/node/tags/list?n=10000')
.reply(
Expand Down Expand Up @@ -723,7 +759,7 @@ describe('modules/datasource/docker/index', () => {

httpMock
.scope('https://ecr-proxy.company.com/v2')
.get('/')
.head('/node/tags/list?n=10000')
.reply(200, '', {})
.get('/node/tags/list?n=10000')
.reply(405, maxResultsErrorBody, {
Expand All @@ -744,7 +780,7 @@ describe('modules/datasource/docker/index', () => {
it('returns null when the response code is not 405', async () => {
httpMock
.scope('https://ecr-proxy.company.com/v2')
.get('/')
.head('/node/tags/list?n=10000')
.reply(200, '', {})
.get('/node/tags/list?n=10000')
.reply(
Expand Down Expand Up @@ -775,7 +811,7 @@ describe('modules/datasource/docker/index', () => {
it('returns null when no response headers are present', async () => {
httpMock
.scope('https://ecr-proxy.company.com/v2')
.get('/')
.head('/node/tags/list?n=10000')
.reply(200, '', {})
.get('/node/tags/list?n=10000')
.reply(405, {
Expand All @@ -798,7 +834,7 @@ describe('modules/datasource/docker/index', () => {
it('returns null when the expected docker header is missing', async () => {
httpMock
.scope('https://ecr-proxy.company.com/v2')
.get('/')
.head('/node/tags/list?n=10000')
.reply(200, '', {})
.get('/node/tags/list?n=10000')
.reply(
Expand Down Expand Up @@ -827,7 +863,7 @@ describe('modules/datasource/docker/index', () => {
it('returns null when the response body does not contain an errors object', async () => {
httpMock
.scope('https://ecr-proxy.company.com/v2')
.get('/')
.head('/node/tags/list?n=10000')
.reply(200, '', {})
.get('/node/tags/list?n=10000')
.reply(
Expand All @@ -848,7 +884,7 @@ describe('modules/datasource/docker/index', () => {
it('returns null when the response body does not contain errors', async () => {
httpMock
.scope('https://ecr-proxy.company.com/v2')
.get('/')
.head('/node/tags/list?n=10000')
.reply(200, '', {})
.get('/node/tags/list?n=10000')
.reply(
Expand All @@ -871,7 +907,7 @@ describe('modules/datasource/docker/index', () => {
it('returns null when the the response errors does not have a message property', async () => {
httpMock
.scope('https://ecr-proxy.company.com/v2')
.get('/')
.head('/node/tags/list?n=10000')
.reply(200, '', {})
.get('/node/tags/list?n=10000')
.reply(
Expand All @@ -898,7 +934,7 @@ describe('modules/datasource/docker/index', () => {
it('returns null when the the error message does not have the expected max results error', async () => {
httpMock
.scope('https://ecr-proxy.company.com/v2')
.get('/')
.head('/node/tags/list?n=10000')
.reply(200, '', {})
.get('/node/tags/list?n=10000')
.reply(
Expand Down Expand Up @@ -928,7 +964,7 @@ describe('modules/datasource/docker/index', () => {
const tags = ['1.0.0'];
httpMock
.scope(baseUrl)
.get('/')
.head('/library/node/tags/list?n=10000')
.reply(401, '', {
'www-authenticate':
'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/node:pull "',
Expand Down Expand Up @@ -956,7 +992,7 @@ describe('modules/datasource/docker/index', () => {
const tags = ['1.0.0'];
httpMock
.scope(baseUrl)
.get('/')
.head('/library/node/tags/list?n=10000')
.reply(401, '', {
'www-authenticate':
'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/node:pull "',
Expand Down Expand Up @@ -984,7 +1020,7 @@ describe('modules/datasource/docker/index', () => {
const tags = ['1.0.0'];
httpMock
.scope('https://k8s.gcr.io/v2/')
.get('/')
.head('/kubernetes-dashboard-amd64/tags/list?n=10000')
.reply(401, '', {
'www-authenticate':
'Bearer realm="https://k8s.gcr.io/v2/token",service="k8s.gcr.io"',
Expand All @@ -1009,7 +1045,7 @@ describe('modules/datasource/docker/index', () => {
it('returns null on error', async () => {
httpMock
.scope(baseUrl)
.get('/')
.head('/my/node/tags/list?n=10000')
.reply(200, null)
.get('/my/node/tags/list?n=10000')
.replyWithError('error');
Expand All @@ -1023,7 +1059,7 @@ describe('modules/datasource/docker/index', () => {
it('strips trailing slash from registry', async () => {
httpMock
.scope(baseUrl)
.get('/')
.head('/my/node/tags/list?n=10000')
.reply(401, '', {
'www-authenticate':
'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:my/node:pull "',
Expand All @@ -1048,9 +1084,12 @@ describe('modules/datasource/docker/index', () => {

it('returns null if no auth', async () => {
hostRules.find.mockReturnValue({});
httpMock.scope(baseUrl).get('/').reply(401, undefined, {
'www-authenticate': 'Basic realm="My Private Docker Registry Server"',
});
httpMock
.scope(baseUrl)
.head('/library/node/tags/list?n=10000')
.reply(401, undefined, {
'www-authenticate': 'Basic realm="My Private Docker Registry Server"',
});
const res = await getPkgReleases({
datasource: DockerDatasource.id,
depName: 'node',
Expand All @@ -1062,7 +1101,9 @@ describe('modules/datasource/docker/index', () => {
httpMock
.scope('https://registry.company.com/v2')
.get('/')
.times(3)
.times(2)
.reply(200)
.head('/node/tags/list?n=10000')
.reply(200)
.get('/node/tags/list?n=10000')
.reply(200, {
Expand Down Expand Up @@ -1125,7 +1166,9 @@ describe('modules/datasource/docker/index', () => {
httpMock
.scope('https://registry.company.com/v2')
.get('/')
.times(4)
.times(3)
.reply(200)
.head('/node/tags/list?n=10000')
.reply(200)
.get('/node/tags/list?n=10000')
.reply(200, { tags: ['abc'] })
Expand Down Expand Up @@ -1165,7 +1208,8 @@ describe('modules/datasource/docker/index', () => {
httpMock
.scope('https://registry.company.com/v2')
.get('/')
.times(2)
.reply(200)
.head('/node/tags/list?n=10000')
.reply(200)
.get('/node/tags/list?n=10000')
.reply(200, { tags: ['latest'] })
Expand All @@ -1189,7 +1233,8 @@ describe('modules/datasource/docker/index', () => {
httpMock
.scope('https://registry.company.com/v2')
.get('/')
.times(2)
.reply(200)
.head('/node/tags/list?n=10000')
.reply(200)
.get('/node/tags/list?n=10000')
.reply(200, { tags: ['latest'] })
Expand All @@ -1212,7 +1257,8 @@ describe('modules/datasource/docker/index', () => {
httpMock
.scope('https://registry.company.com/v2')
.get('/')
.times(2)
.reply(200)
.head('/node/tags/list?n=10000')
.reply(200)
.get('/node/tags/list?n=10000')
.reply(200, { tags: ['latest'] })
Expand All @@ -1232,7 +1278,9 @@ describe('modules/datasource/docker/index', () => {
httpMock
.scope('https://registry.company.com/v2')
.get('/')
.times(4)
.times(3)
.reply(200)
.head('/node/tags/list?n=10000')
.reply(200)
.get('/node/tags/list?n=10000')
.reply(200, { tags: ['1'] })
Expand Down Expand Up @@ -1276,7 +1324,9 @@ describe('modules/datasource/docker/index', () => {
httpMock
.scope('https://registry.company.com/v2')
.get('/')
.times(4)
.times(3)
.reply(200)
.head('/node/tags/list?n=10000')
.reply(200)
.get('/node/tags/list?n=10000')
.reply(200, { tags: ['1'] })
Expand Down Expand Up @@ -1319,7 +1369,8 @@ describe('modules/datasource/docker/index', () => {
httpMock
.scope('https://registry.company.com/v2')
.get('/')
.times(2)
.reply(200)
.head('/node/tags/list?n=10000')
.reply(200)
.get('/node/tags/list?n=10000')
.reply(200, { tags: ['latest'] })
Expand All @@ -1345,7 +1396,11 @@ describe('modules/datasource/docker/index', () => {
badheaders: ['authorization'],
})
.get('/')
.times(3)
.times(2)
.reply(401, '', {
'www-authenticate': 'Basic realm="My Private Docker Registry Server"',
})
.head('/node/tags/list?n=10000')
.reply(401, '', {
'www-authenticate': 'Basic realm="My Private Docker Registry Server"',
});
Expand Down