mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-01-31 16:35:23 +00:00
Added Bearer Authorization spec
Closes: #1249 Change-type: minor Signed-off-by: Miguel Casqueira <miguel@balena.io>
This commit is contained in:
parent
698435a5c8
commit
2007a98940
@ -12,18 +12,21 @@ import { checkTruthy } from './lib/validation';
|
||||
|
||||
import log from './lib/supervisor-console';
|
||||
|
||||
function getKeyFromReq(req: express.Request): string | null {
|
||||
const queryKey = req.query.apikey;
|
||||
if (queryKey != null) {
|
||||
return queryKey;
|
||||
function getKeyFromReq(req: express.Request): string | undefined {
|
||||
// Check query for key
|
||||
if (req.query.apikey) {
|
||||
return req.query.apikey;
|
||||
}
|
||||
const maybeHeaderKey = req.get('Authorization');
|
||||
if (!maybeHeaderKey) {
|
||||
return null;
|
||||
// Get Authorization header to search for key
|
||||
const authHeader = req.get('Authorization');
|
||||
// Check header for key
|
||||
if (!authHeader) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const match = maybeHeaderKey.match(/^ApiKey (\w+)$/);
|
||||
return match != null ? match[1] : null;
|
||||
// Check authHeader with various schemes
|
||||
const match = authHeader.match(/^(?:ApiKey|Bearer) (\w+)$/i);
|
||||
// Return key from match or undefined
|
||||
return match?.[1];
|
||||
}
|
||||
|
||||
function authenticate(config: Config): express.RequestHandler {
|
||||
@ -87,6 +90,14 @@ export class SupervisorAPI {
|
||||
|
||||
private api = express();
|
||||
private server: Server | null = null;
|
||||
// Holds the function which should apply iptables rules
|
||||
private applyRules: SupervisorAPI['applyListeningRules'] =
|
||||
process.env.TEST === '1'
|
||||
? () => {
|
||||
// don't try to alter iptables
|
||||
// rules while we're running in tests
|
||||
}
|
||||
: this.applyListeningRules.bind(this);
|
||||
|
||||
public constructor({
|
||||
config,
|
||||
@ -176,13 +187,12 @@ export class SupervisorAPI {
|
||||
apiTimeout: number,
|
||||
): Promise<void> {
|
||||
const localMode = await this.config.get('localMode');
|
||||
await this.applyListeningRules(localMode || false, port, allowedInterfaces);
|
||||
|
||||
await this.applyRules(localMode || false, port, allowedInterfaces);
|
||||
// Monitor the switching of local mode, and change which interfaces will
|
||||
// be listened to based on that
|
||||
this.config.on('change', changedConfig => {
|
||||
if (changedConfig.localMode != null) {
|
||||
this.applyListeningRules(
|
||||
this.applyRules(
|
||||
changedConfig.localMode || false,
|
||||
port,
|
||||
allowedInterfaces,
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { expect } from 'chai';
|
||||
import { fs } from 'mz';
|
||||
import * as requestLib from 'request';
|
||||
|
||||
import Config from '../src/config';
|
||||
import Database from '../src/db';
|
||||
import EventTracker from '../src/event-tracker';
|
||||
import SupervisorAPI from '../src/supervisor-api';
|
||||
|
||||
const mockedOptions = {
|
||||
listenPort: 48484,
|
||||
listenPort: 12345,
|
||||
timeout: 30000,
|
||||
dbPath: './test/data/supervisor-api.sqlite',
|
||||
};
|
||||
|
||||
const VALID_SECRET = 'secure_api_secret';
|
||||
@ -16,11 +19,16 @@ const ALLOWED_INTERFACES = ['lo']; // Only need loopback since this is for testi
|
||||
|
||||
describe('SupervisorAPI authentication', () => {
|
||||
let api: SupervisorAPI;
|
||||
const mockedConfig = new Config({ db: new Database() });
|
||||
// Set apiSecret that we can test with
|
||||
mockedConfig.set({ apiSecret: VALID_SECRET });
|
||||
|
||||
before(() => {
|
||||
before(async () => {
|
||||
const db = new Database({
|
||||
databasePath: mockedOptions.dbPath,
|
||||
});
|
||||
await db.init();
|
||||
const mockedConfig = new Config({ db });
|
||||
await mockedConfig.init();
|
||||
// Set apiSecret that we can test with
|
||||
await mockedConfig.set({ apiSecret: VALID_SECRET });
|
||||
api = new SupervisorAPI({
|
||||
config: mockedConfig,
|
||||
eventTracker: new EventTracker(),
|
||||
@ -34,9 +42,13 @@ describe('SupervisorAPI authentication', () => {
|
||||
);
|
||||
});
|
||||
|
||||
after(done => {
|
||||
after(async () => {
|
||||
api.stop();
|
||||
done();
|
||||
try {
|
||||
await fs.unlink(mockedOptions.dbPath);
|
||||
} catch (e) {
|
||||
/* noop */
|
||||
}
|
||||
});
|
||||
|
||||
it('finds no apiKey and rejects', async () => {
|
||||
@ -56,17 +68,50 @@ describe('SupervisorAPI authentication', () => {
|
||||
expect(response.statusCode).to.equal(200);
|
||||
});
|
||||
|
||||
it('finds apiKey from Authorization header (Bearer scheme)', async () => {
|
||||
const response = await postAsync(`/v1/blink`, {
|
||||
Authorization: `Bearer ${VALID_SECRET}`,
|
||||
});
|
||||
expect(response.statusCode).to.equal(200);
|
||||
});
|
||||
|
||||
it('finds apiKey from Authorization header (case insensitive)', async () => {
|
||||
const randomCases = [
|
||||
'Bearer',
|
||||
'bearer',
|
||||
'BEARER',
|
||||
'BeAReR',
|
||||
'ApiKey',
|
||||
'apikey',
|
||||
'APIKEY',
|
||||
'ApIKeY',
|
||||
];
|
||||
for (const scheme of randomCases) {
|
||||
const response = await postAsync(`/v1/blink`, {
|
||||
Authorization: `${scheme} ${VALID_SECRET}`,
|
||||
});
|
||||
expect(response.statusCode).to.equal(200);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects invalid apiKey from query', async () => {
|
||||
const response = await postAsync(`/v1/blink?apikey=${INVALID_SECRET}`);
|
||||
expect(response.statusCode).to.equal(401);
|
||||
});
|
||||
|
||||
it('rejects apiKey from Authorization header (ApiKey scheme)', async () => {
|
||||
it('rejects invalid apiKey from Authorization header (ApiKey scheme)', async () => {
|
||||
const response = await postAsync(`/v1/blink`, {
|
||||
Authorization: `ApiKey ${INVALID_SECRET}`,
|
||||
});
|
||||
expect(response.statusCode).to.equal(401);
|
||||
});
|
||||
|
||||
it('rejects invalid apiKey from Authorization header (Bearer scheme)', async () => {
|
||||
const response = await postAsync(`/v1/blink`, {
|
||||
Authorization: `Bearer ${INVALID_SECRET}`,
|
||||
});
|
||||
expect(response.statusCode).to.equal(401);
|
||||
});
|
||||
});
|
||||
|
||||
function postAsync(path: string, headers = {}): Promise<any> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user