Security: Store datasource passwords encrypted in secureJsonData (#16175)

* Store passwords in secureJsonData

* Revert unnecessary refactors

* Fix for nil jsonSecureData value

* Remove copied encryption code from migration

* Fix wrong field reference

* Remove migration and provisioning changes

* Use password getters in datasource proxy

* Refactor password handling in datasource configs

* Add provisioning warnings

* Update documentation

* Remove migration command, moved to separate PR

* Remove unused code

* Set the upgrade version

* Remove unused code

* Remove double reference
This commit is contained in:
Andrej Ocenas
2019-04-15 11:11:17 +02:00
committed by GitHub
parent 844ec82eb0
commit 66f6e16916
30 changed files with 352 additions and 85 deletions

View File

@@ -63,6 +63,7 @@ export function registerAngularDirectives() {
'value',
'isConfigured',
'inputWidth',
'labelWidth',
['onReset', { watchDepth: 'reference', wrapApply: true }],
['onChange', { watchDepth: 'reference', wrapApply: true }],
]);

View File

@@ -99,8 +99,14 @@
<input class="gf-form-input max-width-21" type="text" ng-model='current.basicAuthUser' placeholder="user" required></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-10">Password</span>
<input class="gf-form-input max-width-21" type="password" ng-model='current.basicAuthPassword' placeholder="password" required></input>
<secret-form-field
isConfigured="current.basicAuthPassword || current.secureJsonFields.basicAuthPassword"
value="current.secureJsonData.basicAuthPassword || ''"
on-reset="onBasicAuthPasswordReset"
on-change="onBasicAuthPasswordChange"
inputWidth="18"
labelWidth="10"
/>
</div>
</div>

View File

@@ -1,4 +1,5 @@
import { coreModule } from 'app/core/core';
import { createChangeHandler, createResetHandler, PasswordFieldEnum } from '../utils/passwordHandlers';
coreModule.directive('datasourceHttpSettings', () => {
return {
@@ -20,6 +21,9 @@ coreModule.directive('datasourceHttpSettings', () => {
$scope.getSuggestUrls = () => {
return [$scope.suggestUrl];
};
$scope.onBasicAuthPasswordReset = createResetHandler($scope, PasswordFieldEnum.BasicAuthPassword);
$scope.onBasicAuthPasswordChange = createChangeHandler($scope, PasswordFieldEnum.BasicAuthPassword);
},
},
};

View File

@@ -0,0 +1,35 @@
import { createResetHandler, PasswordFieldEnum, Ctrl } from './passwordHandlers';
describe('createResetHandler', () => {
Object.keys(PasswordFieldEnum).forEach(fieldKey => {
const field = PasswordFieldEnum[fieldKey];
it(`should reset existing ${field} field`, () => {
const event: any = {
preventDefault: () => {},
};
const ctrl: Ctrl = {
current: {
[field]: 'set',
secureJsonData: {
[field]: 'set',
},
secureJsonFields: {},
},
};
createResetHandler(ctrl, field)(event);
expect(ctrl).toEqual({
current: {
[field]: null,
secureJsonData: {
[field]: '',
},
secureJsonFields: {
[field]: false,
},
},
});
});
});
});

View File

@@ -0,0 +1,39 @@
/**
* Set of handlers for secure password field in Angular components. They handle backward compatibility with
* passwords stored in plain text fields.
*/
import { SyntheticEvent } from 'react';
export enum PasswordFieldEnum {
Password = 'password',
BasicAuthPassword = 'basicAuthPassword',
}
/**
* Basic shape for settings controllers in at the moment mostly angular datasource plugins.
*/
export type Ctrl = {
current: {
secureJsonFields: {};
secureJsonData?: {};
};
};
export const createResetHandler = (ctrl: Ctrl, field: PasswordFieldEnum) => (
event: SyntheticEvent<HTMLInputElement>
) => {
event.preventDefault();
// Reset also normal plain text password to remove it and only save it in secureJsonData.
ctrl.current[field] = null;
ctrl.current.secureJsonFields[field] = false;
ctrl.current.secureJsonData = ctrl.current.secureJsonData || {};
ctrl.current.secureJsonData[field] = '';
};
export const createChangeHandler = (ctrl: any, field: PasswordFieldEnum) => (
event: SyntheticEvent<HTMLInputElement>
) => {
ctrl.current.secureJsonData = ctrl.current.secureJsonData || {};
ctrl.current.secureJsonData[field] = event.currentTarget.value;
};

View File

@@ -1,8 +1,21 @@
import InfluxDatasource from './datasource';
import { InfluxQueryCtrl } from './query_ctrl';
import {
createChangeHandler,
createResetHandler,
PasswordFieldEnum,
} from '../../../features/datasources/utils/passwordHandlers';
class InfluxConfigCtrl {
static templateUrl = 'partials/config.html';
current: any;
onPasswordReset: ReturnType<typeof createResetHandler>;
onPasswordChange: ReturnType<typeof createChangeHandler>;
constructor() {
this.onPasswordReset = createResetHandler(this, PasswordFieldEnum.Password);
this.onPasswordChange = createChangeHandler(this, PasswordFieldEnum.Password);
}
}
class InfluxAnnotationsQueryCtrl {

View File

@@ -16,9 +16,14 @@
<span class="gf-form-label width-10">User</span>
<input type="text" class="gf-form-input" ng-model='ctrl.current.user' placeholder=""></input>
</div>
<div class="gf-form max-width-15">
<span class="gf-form-label width-10">Password</span>
<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder=""></input>
<div class="gf-form">
<secret-form-field
isConfigured="ctrl.current.password || ctrl.current.secureJsonFields.password"
value="ctrl.current.secureJsonData.password || ''"
on-reset="ctrl.onPasswordReset"
on-change="ctrl.onPasswordChange"
inputWidth="9"
/>
</div>
</div>
</div>

View File

@@ -1,24 +1,20 @@
import { SyntheticEvent } from 'react';
import {
createChangeHandler,
createResetHandler,
PasswordFieldEnum,
} from '../../../features/datasources/utils/passwordHandlers';
export class MssqlConfigCtrl {
static templateUrl = 'partials/config.html';
current: any;
onPasswordReset: ReturnType<typeof createResetHandler>;
onPasswordChange: ReturnType<typeof createChangeHandler>;
/** @ngInject */
constructor($scope) {
this.current.jsonData.encrypt = this.current.jsonData.encrypt || 'false';
this.onPasswordReset = createResetHandler(this, PasswordFieldEnum.Password);
this.onPasswordChange = createChangeHandler(this, PasswordFieldEnum.Password);
}
onPasswordReset = (event: SyntheticEvent<HTMLInputElement>) => {
event.preventDefault();
this.current.secureJsonFields.password = false;
this.current.secureJsonData = this.current.secureJsonData || {};
this.current.secureJsonData.password = '';
};
onPasswordChange = (event: SyntheticEvent<HTMLInputElement>) => {
this.current.secureJsonData = this.current.secureJsonData || {};
this.current.secureJsonData.password = event.currentTarget.value;
};
}

View File

@@ -1,8 +1,21 @@
import { MysqlDatasource } from './datasource';
import { MysqlQueryCtrl } from './query_ctrl';
import {
createChangeHandler,
createResetHandler,
PasswordFieldEnum,
} from '../../../features/datasources/utils/passwordHandlers';
class MysqlConfigCtrl {
static templateUrl = 'partials/config.html';
current: any;
onPasswordReset: ReturnType<typeof createResetHandler>;
onPasswordChange: ReturnType<typeof createChangeHandler>;
constructor() {
this.onPasswordReset = createResetHandler(this, PasswordFieldEnum.Password);
this.onPasswordChange = createChangeHandler(this, PasswordFieldEnum.Password);
}
}
const defaultQuery = `SELECT

View File

@@ -16,9 +16,14 @@
<span class="gf-form-label width-7">User</span>
<input type="text" class="gf-form-input" ng-model='ctrl.current.user' placeholder="user"></input>
</div>
<div class="gf-form max-width-15">
<span class="gf-form-label width-7">Password</span>
<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder="password"></input>
<div class="gf-form">
<secret-form-field
isConfigured="ctrl.current.secureJsonFields.password"
value="ctrl.current.secureJsonData.password"
on-reset="ctrl.onPasswordReset"
on-change="ctrl.onPasswordChange"
inputWidth="9"
/>
</div>
</div>

View File

@@ -1,5 +1,9 @@
import _ from 'lodash';
import { SyntheticEvent } from 'react';
import {
createChangeHandler,
createResetHandler,
PasswordFieldEnum,
} from '../../../features/datasources/utils/passwordHandlers';
export class PostgresConfigCtrl {
static templateUrl = 'partials/config.html';
@@ -7,6 +11,8 @@ export class PostgresConfigCtrl {
current: any;
datasourceSrv: any;
showTimescaleDBHelp: boolean;
onPasswordReset: ReturnType<typeof createResetHandler>;
onPasswordChange: ReturnType<typeof createChangeHandler>;
/** @ngInject */
constructor($scope, datasourceSrv) {
@@ -15,6 +21,8 @@ export class PostgresConfigCtrl {
this.current.jsonData.postgresVersion = this.current.jsonData.postgresVersion || 903;
this.showTimescaleDBHelp = false;
this.autoDetectFeatures();
this.onPasswordReset = createResetHandler(this, PasswordFieldEnum.Password);
this.onPasswordChange = createChangeHandler(this, PasswordFieldEnum.Password);
}
autoDetectFeatures() {
@@ -53,18 +61,6 @@ export class PostgresConfigCtrl {
this.showTimescaleDBHelp = !this.showTimescaleDBHelp;
}
onPasswordReset = (event: SyntheticEvent<HTMLInputElement>) => {
event.preventDefault();
this.current.secureJsonFields.password = false;
this.current.secureJsonData = this.current.secureJsonData || {};
this.current.secureJsonData.password = '';
};
onPasswordChange = (event: SyntheticEvent<HTMLInputElement>) => {
this.current.secureJsonData = this.current.secureJsonData || {};
this.current.secureJsonData.password = event.currentTarget.value;
};
// the value portion is derived from postgres server_version_num/100
postgresVersions = [
{ name: '9.3', value: 903 },