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

[6.x] RBAC Phase 1 (#19723) #21164

Merged
merged 1 commit into from
Jul 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/

import { getRootPropertiesObjects } from '../../mappings';
import { SavedObjectsRepository, ScopedSavedObjectsClientProvider, SavedObjectsRepositoryProvider } from './lib';
import { SavedObjectsClient } from './saved_objects_client';

Expand Down Expand Up @@ -58,15 +59,16 @@ export function createSavedObjectsService(server) {
}
};

const mappings = server.getKibanaIndexMappingsDsl();
const repositoryProvider = new SavedObjectsRepositoryProvider({
index: server.config().get('kibana.index'),
mappings: server.getKibanaIndexMappingsDsl(),
mappings,
onBeforeWrite,
});

const scopedClientProvider = new ScopedSavedObjectsClientProvider({
index: server.config().get('kibana.index'),
mappings: server.getKibanaIndexMappingsDsl(),
mappings,
onBeforeWrite,
defaultClientFactory({
request,
Expand All @@ -81,6 +83,7 @@ export function createSavedObjectsService(server) {
});

return {
types: Object.keys(getRootPropertiesObjects(mappings)),
SavedObjectsClient,
SavedObjectsRepository,
getSavedObjectsRepository: (...args) =>
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/security/common/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export const ALL_RESOURCE = '*';
77 changes: 66 additions & 11 deletions x-pack/plugins/security/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@ import { resolve } from 'path';
import { getUserProvider } from './server/lib/get_user';
import { initAuthenticateApi } from './server/routes/api/v1/authenticate';
import { initUsersApi } from './server/routes/api/v1/users';
import { initRolesApi } from './server/routes/api/v1/roles';
import { initPublicRolesApi } from './server/routes/api/public/roles';
import { initIndicesApi } from './server/routes/api/v1/indices';
import { initLoginView } from './server/routes/views/login';
import { initLogoutView } from './server/routes/views/logout';
import { validateConfig } from './server/lib/validate_config';
import { authenticateFactory } from './server/lib/auth_redirect';
import { checkLicense } from './server/lib/check_license';
import { initAuthenticator } from './server/lib/authentication/authenticator';
import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status';
import { initPrivilegesApi } from './server/routes/api/v1/privileges';
import { SecurityAuditLogger } from './server/lib/audit_logger';
import { AuditLogger } from '../../server/lib/audit_logger';
import { SecureSavedObjectsClient } from './server/lib/saved_objects_client/secure_saved_objects_client';
import { initAuthorizationService, registerPrivilegesWithCluster } from './server/lib/authorization';
import { watchStatusAndLicenseToInitialize } from './server/lib/watch_status_and_license_to_initialize';

export const security = (kibana) => new kibana.Plugin({
id: 'security',
Expand All @@ -37,6 +42,14 @@ export const security = (kibana) => new kibana.Plugin({
hostname: Joi.string().hostname(),
port: Joi.number().integer().min(0).max(65535)
}).default(),
authorization: Joi.object({
legacyFallback: Joi.object({
enabled: Joi.boolean().default(true)
}).default()
}).default(),
audit: Joi.object({
enabled: Joi.boolean().default(false)
}).default(),
}).default();
},

Expand Down Expand Up @@ -64,21 +77,24 @@ export const security = (kibana) => new kibana.Plugin({

return {
secureCookies: config.get('xpack.security.secureCookies'),
sessionTimeout: config.get('xpack.security.sessionTimeout')
sessionTimeout: config.get('xpack.security.sessionTimeout'),
};
}
},

async init(server) {
const thisPlugin = this;
const plugin = this;

const config = server.config();
const xpackMainPlugin = server.plugins.xpack_main;
mirrorPluginStatus(xpackMainPlugin, thisPlugin);
const xpackInfo = xpackMainPlugin.info;

const xpackInfoFeature = xpackInfo.feature(plugin.id);

// Register a function that is called whenever the xpack info changes,
// to re-compute the license check results for this plugin
xpackMainPlugin.info.feature(thisPlugin.id).registerLicenseCheckResultsGenerator(checkLicense);
xpackInfoFeature.registerLicenseCheckResultsGenerator(checkLicense);

const config = server.config();
validateConfig(config, message => server.log(['security', 'warning'], message));

// Create a Hapi auth scheme that should be applied to each request.
Expand All @@ -88,20 +104,59 @@ export const security = (kibana) => new kibana.Plugin({
// automatically assigned to all routes that don't contain an auth config.
server.auth.strategy('session', 'login', 'required');

// exposes server.plugins.security.authorization
initAuthorizationService(server);

watchStatusAndLicenseToInitialize(xpackMainPlugin, plugin, async (license) => {
if (license.allowRbac) {
await registerPrivilegesWithCluster(server);
}
});

const auditLogger = new SecurityAuditLogger(server.config(), new AuditLogger(server, 'security'));

const { savedObjects } = server;
savedObjects.setScopedSavedObjectsClientFactory(({
request,
}) => {
const adminCluster = server.plugins.elasticsearch.getCluster('admin');
const { callWithRequest, callWithInternalUser } = adminCluster;
const callCluster = (...args) => callWithRequest(request, ...args);

const callWithRequestRepository = savedObjects.getSavedObjectsRepository(callCluster);

if (!xpackInfoFeature.getLicenseCheckResults().allowRbac) {
return new savedObjects.SavedObjectsClient(callWithRequestRepository);
}

const { authorization } = server.plugins.security;
const checkPrivileges = authorization.checkPrivilegesWithRequest(request);
const internalRepository = savedObjects.getSavedObjectsRepository(callWithInternalUser);

return new SecureSavedObjectsClient({
internalRepository,
callWithRequestRepository,
errors: savedObjects.SavedObjectsClient.errors,
checkPrivileges,
auditLogger,
savedObjectTypes: savedObjects.types,
actions: authorization.actions,
});
});

getUserProvider(server);

await initAuthenticator(server);
initAuthenticateApi(server);
initUsersApi(server);
initRolesApi(server);
initPublicRolesApi(server);
initIndicesApi(server);
initPrivilegesApi(server);
initLoginView(server, xpackMainPlugin);
initLogoutView(server);

server.injectUiAppVars('login', () => {
const pluginId = 'security';
const xpackInfo = server.plugins.xpack_main.info;
const { showLogin, loginMessage, allowLogin } = xpackInfo.feature(pluginId).getLicenseCheckResults() || {};
const { showLogin, loginMessage, allowLogin } = xpackInfo.feature(plugin.id).getLicenseCheckResults() || {};

return {
loginState: {
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/security/public/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import chrome from 'ui/chrome';

const usersUrl = chrome.addBasePath('/api/security/v1/users');
const rolesUrl = chrome.addBasePath('/api/security/v1/roles');
const rolesUrl = chrome.addBasePath('/api/security/role');

export const createApiClient = (httpClient) => {
return {
Expand Down
14 changes: 14 additions & 0 deletions x-pack/plugins/security/public/services/application_privilege.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import 'angular-resource';
import { uiModules } from 'ui/modules';

const module = uiModules.get('security', ['ngResource']);
module.service('ApplicationPrivileges', ($resource, chrome) => {
const baseUrl = chrome.addBasePath('/api/security/v1/privileges');
return $resource(baseUrl);
});
3 changes: 2 additions & 1 deletion x-pack/plugins/security/public/services/shield_privileges.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ module.constant('shieldPrivileges', {
'create_index',
'view_index_metadata',
'read_cross_cluster',
]
],
applications: []
});
11 changes: 10 additions & 1 deletion x-pack/plugins/security/public/services/shield_role.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@
*/

import 'angular-resource';
import { omit } from 'lodash';
import angular from 'angular';
import { uiModules } from 'ui/modules';

const module = uiModules.get('security', ['ngResource']);
module.service('ShieldRole', ($resource, chrome) => {
return $resource(chrome.addBasePath('/api/security/v1/roles/:name'), {
return $resource(chrome.addBasePath('/api/security/role/:name'), {
name: '@name'
}, {
save: {
method: 'PUT',
transformRequest(data) {
return angular.toJson(omit(data, 'name', 'transient_metadata', '_unrecognized_applications'));
}
}
});
});
47 changes: 41 additions & 6 deletions x-pack/plugins/security/public/views/management/edit_role.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<kbn-management-app section="security" omit-breadcrumb-pages="['edit']">
<!-- This content gets injected below the localNav. -->
<div class="kuiViewContent kuiViewContent--constrainedWidth kuiViewContentItem">

<!-- Subheader -->
<div class="kuiBar kuiVerticalRhythm">

<div class="kuiBarSection">
<!-- Title -->
<h1 class="kuiTitle">
Expand Down Expand Up @@ -39,6 +41,18 @@ <h1 class="kuiTitle">
</div>
</div>

<div class="kuiBar kuiVerticalRhythm" ng-if="otherApplications.length > 0">
<div class="kuiInfoPanel kuiInfoPanel--warning">
<div class="kuiInfoPanelHeader">
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--warning fa-warning"></span>
<span class="kuiInfoPanelHeader__title">
This role contains application privileges for the {{ otherApplications.join(', ') }} application(s) that can't be edited.
If they are for other instances of Kibana, you must manage those privileges on that Kibana instance.
</span>
</div>
</div>
</div>

<!-- Form -->
<form name="form" novalidate class="kuiVerticalRhythm">
<!-- Name -->
Expand All @@ -56,7 +70,7 @@ <h1 class="kuiTitle">
ng-model="role.name"
required
pattern="[a-zA-Z_][a-zA-Z0-9_@\-\$\.]*"
maxlength="30"
maxlength="1024"
data-test-subj="roleFormNameInput"
/>

Expand Down Expand Up @@ -87,23 +101,44 @@ <h1 class="kuiTitle">
<input
class="kuiCheckBox"
type="checkbox"
ng-checked="includes(role.cluster, privilege)"
ng-click="toggle(role.cluster, privilege)"
ng-checked="includes(role.elasticsearch.cluster, privilege)"
ng-click="toggle(role.elasticsearch.cluster, privilege)"
ng-disabled="role.metadata._reserved || !isRoleEnabled(role)"
data-test-subj="clusterPrivileges-{{privilege}}"
/>
<span class="kuiOptionLabel">{{privilege}}</span>
</label>
</div>
</div>

<!-- Kibana custom privileges -->
<div class="kuiFormSection">
<label class="kuiFormLabel">
Kibana Privileges
</label>

<div ng-repeat="(key, value) in kibanaPrivilegesViewModel">
<label>
<input
class="kuiCheckBox"
type="checkbox"
ng-model="kibanaPrivilegesViewModel[key]"
ng-disabled="role.metadata._reserved || !isRoleEnabled(role)"
data-test-subj="kibanaPrivileges-{{key}}"
/>
<span class="kuiOptionLabel">{{key}}</span>
</label>
</div>
</div>

<!-- Run-as privileges -->
<div class="kuiFormSection">
<label class="kuiFormLabel">
Run As Privileges
</label>
<ui-select
multiple
ng-model="role.run_as"
ng-model="role.elasticsearch.run_as"
ng-disabled="role.metadata._reserved || !isRoleEnabled(role)"
>
<ui-select-match placeholder="Add a user...">
Expand All @@ -119,7 +154,7 @@ <h1 class="kuiTitle">
<div class="kuiFormSection">
<kbn-index-privileges-form
is-new-role="editRole.isNewRole"
indices="role.indices"
indices="role.elasticsearch.indices"
index-patterns="indexPatterns"
privileges="privileges"
field-options="editRole.fieldOptions"
Expand All @@ -140,7 +175,7 @@ <h1 class="kuiTitle">
class="kuiButton kuiButton--primary"
ng-click="saveRole(role)"
ng-if="!role.metadata._reserved && isRoleEnabled(role)"
ng-disabled="form.$invalid || !areIndicesValid(role.indices)"
ng-disabled="form.$invalid || !areIndicesValid(role.elasticsearch.indices)"
data-test-subj="roleFormSaveButton"
>
Save
Expand Down
Loading