Skip to content

Commit

Permalink
Authentication strategies for registry (#324)
Browse files Browse the repository at this point in the history
* Authentication strategies for registry

* Tests

* Add registry auth to other e2e tests
  • Loading branch information
AleF83 authored May 11, 2021
1 parent 8da841a commit fb7d3d2
Show file tree
Hide file tree
Showing 26 changed files with 368 additions and 6 deletions.
7 changes: 5 additions & 2 deletions services/src/modules/config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as path from 'path';
import * as envVar from 'env-var';
import { LoggerOptions } from 'pino';
import { PlaygroundConfig } from 'apollo-server-core';
import { AuthenticationConfig } from './authentication/types';
import { CorsConfiguration } from './cors';
import { PlaygroundConfig } from 'apollo-server-core';

const envVarExt = envVar.from(process.env, {
asSet: (value: string) => new Set(value.split(',')),
Expand All @@ -26,7 +26,10 @@ export const resourceUpdateInterval = envVarExt.get('RESOURCE_UPDATE_INTERVAL').

// GraphQL configuration
export const enableGraphQLTracing = envVarExt.get('GRAPHQL_TRACING').default('true').asBool();
export const enableGraphQLPlayground = envVarExt.get('GRAPHQL_PLAYGROUND').default('true').asJsonObject() as PlaygroundConfig;
export const enableGraphQLPlayground = envVarExt
.get('GRAPHQL_PLAYGROUND')
.default('true')
.asJsonObject() as PlaygroundConfig;
export const enableGraphQLIntrospection = envVarExt.get('GRAPHQL_INTROSPECTION').default('true').asBoolStrict();

// Policies
Expand Down
22 changes: 20 additions & 2 deletions services/src/registry.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { ApolloServer } from 'apollo-server-fastify';
import * as fastify from 'fastify';
import * as corsPlugin from 'fastify-cors';
import * as fastifyMetrics from 'fastify-metrics';
import * as jwtPlugin from 'fastify-jwt';
import * as authPlugin from 'fastify-auth';
import * as config from './modules/config';
import { RegistryRequestContext, resolvers, typeDefs } from './modules/registry-schema';
import logger from './modules/logger';
import * as opaHelper from './modules/opa-helper';
import { handleSignals, handleUncaughtErrors } from './modules/shutdown-handler';
import { loadPlugins } from './modules/plugins';
import { ActiveDirectoryAuth } from './modules/upstreams';
import { jwtDecoderPlugin } from './modules/authentication';
import { anonymousPlugin, authStrategies, getSecret, jwtDecoderPlugin } from './modules/authentication';

export const app = new ApolloServer({
typeDefs,
Expand All @@ -35,18 +38,33 @@ export async function createServer() {

const server = fastify();
server
.register(corsPlugin as any, config.corsConfiguration)
.register(authPlugin)
.register(jwtPlugin, {
secret: getSecret,
verify: {
algorithms: ['RS256'],
},
})
.register(jwtDecoderPlugin)
.register(anonymousPlugin)
.register(app.createHandler({ path: '/graphql' }))
.register(fastifyMetrics, { endpoint: '/metrics' });

server.after(() => {
server.addHook('onRequest', server.auth(authStrategies));
server.addHook('onClose', () => app.stop());
});

logger.info('Stitch registry is ready to start');
return server;
}

async function runServer(server: fastify.FastifyInstance) {
const address = await server.listen(config.httpPort, '0.0.0.0');
logger.info({ address }, 'Stitch registry started');

handleSignals(() => app.stop());
handleSignals(() => server.close(() => logger.info('Stitch registry stopped successfully')));
handleUncaughtErrors();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Authentication - Verify JWT Invalid Authentication Scheme 1`] = `
Object {
"error": "Unauthorized",
"message": "Unauthorized",
"status": 401,
"statusCode": 401,
}
`;

exports[`Authentication - Verify JWT Invalid JWT 1`] = `
Object {
"error": "Unauthorized",
"message": "Unauthorized",
"status": 401,
"statusCode": 401,
}
`;

exports[`Authentication - Verify JWT JWK uri is not resolved 1`] = `
Object {
"error": "Unauthorized",
"message": "Unauthorized",
"status": 401,
"statusCode": 401,
}
`;

exports[`Authentication - Verify JWT Multiple audiences 1`] = `
Object {
"plugins": Array [
Object {
"name": "argument-injection-globals.js",
"version": "N/A",
},
Object {
"name": "async-function.js",
"version": "N/A",
},
Object {
"name": "crashing.js",
"version": "N/A",
},
Object {
"name": "folder-with-index-file",
"version": "N/A",
},
Object {
"name": "folder-with-node-module",
"version": "N/A",
},
Object {
"name": "folder-with-npm-module",
"version": "N/A",
},
Object {
"name": "folder-with-package-json-file",
"version": "N/A",
},
Object {
"name": "function.js",
"version": "N/A",
},
Object {
"name": "promise.js",
"version": "N/A",
},
Object {
"name": "single-file.js",
"version": "N/A",
},
Object {
"name": "transform-apollo-server-plugins",
"version": "1.0.0",
},
Object {
"name": "transform-base-schema",
"version": "1.0.0",
},
Object {
"name": "transform-resource-group.js",
"version": "N/A",
},
Object {
"name": "with-config.js",
"version": "N/A",
},
],
}
`;

exports[`Authentication - Verify JWT Unexpected audience 1`] = `
Object {
"error": "Unauthorized",
"message": "Unauthorized",
"status": 401,
"statusCode": 401,
}
`;

exports[`Authentication - Verify JWT Unknown issuer 1`] = `
Object {
"error": "Unauthorized",
"message": "Unauthorized",
"status": 401,
"statusCode": 401,
}
`;

exports[`Authentication - Verify JWT Valid JWT 1`] = `
Object {
"plugins": Array [
Object {
"name": "argument-injection-globals.js",
"version": "N/A",
},
Object {
"name": "async-function.js",
"version": "N/A",
},
Object {
"name": "crashing.js",
"version": "N/A",
},
Object {
"name": "folder-with-index-file",
"version": "N/A",
},
Object {
"name": "folder-with-node-module",
"version": "N/A",
},
Object {
"name": "folder-with-npm-module",
"version": "N/A",
},
Object {
"name": "folder-with-package-json-file",
"version": "N/A",
},
Object {
"name": "function.js",
"version": "N/A",
},
Object {
"name": "promise.js",
"version": "N/A",
},
Object {
"name": "single-file.js",
"version": "N/A",
},
Object {
"name": "transform-apollo-server-plugins",
"version": "1.0.0",
},
Object {
"name": "transform-base-schema",
"version": "1.0.0",
},
Object {
"name": "transform-resource-group.js",
"version": "N/A",
},
Object {
"name": "with-config.js",
"version": "N/A",
},
],
}
`;
20 changes: 19 additions & 1 deletion services/tests/e2e/authentication/anonymous.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fetch from 'node-fetch';

describe('Authentication - Anonymous', () => {
describe('Gateway Authentication - Anonymous', () => {
const metricsEndpoint = 'http://localhost:8080/metrics';

test('Without authorization header', async () => {
Expand All @@ -17,3 +17,21 @@ describe('Authentication - Anonymous', () => {
expect(response.status).toEqual(401);
});
});

describe('Registry Authentication - Anonymous', () => {
const metricsEndpoint = 'http://localhost:8090/metrics';

test('Without authorization header', async () => {
const response = await fetch(metricsEndpoint);
expect(response.ok).toBeTruthy();
});

test('With authorization header', async () => {
const response = await fetch(metricsEndpoint, {
headers: {
authorization: 'Something',
},
});
expect(response.status).toEqual(401);
});
});
78 changes: 78 additions & 0 deletions services/tests/e2e/authentication/registry-jwt.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { gql, GraphQLClient } from 'graphql-request';
import GraphQLErrorSerializer from '../../utils/graphql-error-serializer';
import { getToken, getInvalidToken } from '../../helpers/get-token';

const query = gql`
query {
plugins {
name
version
}
}
`;

describe('Authentication - Verify JWT', () => {
const registryClient = new GraphQLClient('http://localhost:8090/graphql');

beforeAll(() => {
expect.addSnapshotSerializer(GraphQLErrorSerializer);
});

test('Valid JWT', async () => {
const accessToken = await getToken({ scope: 'stitch-registry' });
registryClient.setHeader('Authorization', `Bearer ${accessToken}`);

const result = await registryClient.request(query);
expect(result).toMatchSnapshot();
});

test('Invalid JWT', async () => {
const accessToken = await getInvalidToken();
registryClient.setHeader('Authorization', `Bearer ${accessToken}`);

const result = await registryClient.request(query).catch(e => e.response);
expect(result).toMatchSnapshot();
});

test('Invalid Authentication Scheme', async () => {
const credentials = Buffer.from('user:pwd').toString('base64');
registryClient.setHeader('Authorization', `Basic ${credentials}`);

const result = await registryClient.request(query).catch(e => e.response);
expect(result).toMatchSnapshot();
});

test('Unknown issuer', async () => {
const accessToken = await getInvalidToken({ issuer: 'http://microsoft.com' });
registryClient.setHeader('Authorization', `Bearer ${accessToken}`);

const result = await registryClient.request(query).catch(e => e.response);
expect(result).toMatchSnapshot();
});

test('Unexpected audience', async () => {
const accessToken = await getInvalidToken({ audience: 'Stitch Other' });
registryClient.setHeader('Authorization', `Bearer ${accessToken}`);

const result = await registryClient.request(query).catch(e => e.response);
expect(result).toMatchSnapshot();
});

test('JWK uri is not resolved', async () => {
const accessToken = await getToken({ tokenEndpoint: 'http://localhost:8071/connect/token' });
registryClient.setHeader('Authorization', `Bearer ${accessToken}`);

const result = await registryClient.request(query).catch(e => e.response);
expect(result).toMatchSnapshot();
});

test('Multiple audiences', async () => {
const accessToken = await getToken({
scope: 'stitch-registry stitch-gateway',
});
registryClient.setHeader('Authorization', `Bearer ${accessToken}`);

const result = await registryClient.request(query);
expect(result).toMatchSnapshot();
});
});
3 changes: 3 additions & 0 deletions services/tests/e2e/authentication/verify-jwt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ describe('Authentication - Verify JWT', () => {
});

test('Setup policies', async () => {
const registryAccessToken = await getToken({ scope: 'stitch-registry' });
registryClient.setHeader('Authorization', `Bearer ${registryAccessToken}`);

const schemaResponse = await registryClient.request<RegistryMutationResponse>(updateSchemasMutation, {
schema,
});
Expand Down
3 changes: 3 additions & 0 deletions services/tests/e2e/authorization/auth-base-policy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ describe('Authorization - Base policy', () => {
beforeAll(async () => {
expect.addSnapshotSerializer(GraphQLErrorSerializer);

const registryAccessToken = await getToken({ scope: 'stitch-registry' });
registryClient.setHeader('Authorization', `Bearer ${registryAccessToken}`);

const policiesResponse = await registryClient.request<RegistryMutationResponse>(updatePoliciesMutation, {
policies,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ describe('Authorization - Policy directive order', () => {
const accessToken = await getToken();
gatewayClient.setHeader('Authorization', `Bearer ${accessToken}`);

const registryAccessToken = await getToken({ scope: 'stitch-registry' });
registryClient.setHeader('Authorization', `Bearer ${registryAccessToken}`);

expect.addSnapshotSerializer(GraphQLErrorSerializer);
});

Expand Down
3 changes: 3 additions & 0 deletions services/tests/e2e/authorization/auth-jwt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ describe('authorization', () => {
beforeAll(async () => {
expect.addSnapshotSerializer(GraphQLErrorSerializer);

const registryAccessToken = await getToken({ scope: 'stitch-registry' });
registryClient.setHeader('Authorization', `Bearer ${registryAccessToken}`);

defaultAccessToken = await getToken();
});

Expand Down
Loading

0 comments on commit fb7d3d2

Please sign in to comment.