mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -63,6 +63,7 @@ export function registerAngularDirectives() {
|
||||
'value',
|
||||
'isConfigured',
|
||||
'inputWidth',
|
||||
'labelWidth',
|
||||
['onReset', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
39
public/app/features/datasources/utils/passwordHandlers.ts
Normal file
39
public/app/features/datasources/utils/passwordHandlers.ts
Normal 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;
|
||||
};
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user