Commit cc76f656 authored by Jochen Kressin's avatar Jochen Kressin
Browse files

Merge remote-tracking branch 'remotes/origin/5.3.1-proxy' into 5.3.2

# Conflicts:
#	build.sh
#	package.json
parents c0c93ef1 51184287
......@@ -43,7 +43,13 @@ export default function (kibana) {
tenants: Joi.object().keys({
enable_private: Joi.boolean().default(true),
enable_global: Joi.boolean().default(true),
preferred: Joi.array(),
}).default(),
}).default(),
jwt: Joi.object().keys({
enabled: Joi.boolean().default(false),
url_param: Joi.string().default('authorization'),
header: Joi.string().default('Authorization')
}).default()
}).default();
return obj;
......@@ -177,12 +183,31 @@ export default function (kibana) {
password: config.get("searchguard.cookie.password")
});
this.status.yellow("Search Guard multitenancy enabled");
} else {
this.status.yellow("Search Guard multitenancy disabled");
}
if(config.get('searchguard.jwt.enabled')) {
require('./lib/jwt/headers')(pluginRoot, server, this, APP_ROOT, API_ROOT);
server.state('searchguard_jwt', {
ttl: null,
path: '/',
isSecure: false,
isHttpOnly: false,
clearInvalid: true, // remove invalid cookies
strictHeader: true, // don't allow violations of RFC 6265
encoding: 'iron',
password: config.get("searchguard.cookie.password")
});
this.status.yellow("Search Guard copy JWT params enabled");
} else {
this.status.yellow("Search Guard copy JWT params disabled");
}
this.status.green('Search Guard plugin initialised.');
}
......
......@@ -64,12 +64,14 @@ module.exports = function (pluginRoot, server, kbnServer, APP_ROOT, API_ROOT) {
// get the preferred tenant of the user
let globalTenantEnabled = server.config().get("searchguard.multitenancy.tenants.enable_global");
let privateTenantEnabled = server.config().get("searchguard.multitenancy.tenants.enable_private");
let preferredTenant = server.plugins.searchguard.getSearchGuardBackend().getTenantByPreference(request, user.username, user.tenants, globalTenantEnabled, privateTenantEnabled);
let preferredTenants = server.config().get("searchguard.multitenancy.tenants.preferred");
let finalTenant = server.plugins.searchguard.getSearchGuardBackend().getTenantByPreference(request, user.username, user.tenants, preferredTenants, globalTenantEnabled, privateTenantEnabled);
return reply({
username: user.username,
tenants: user.tenants
}).state('searchguard_tenant', preferredTenant);
}).state('searchguard_tenant', finalTenant);
} else {
// no MT, nothing more to do
return reply({
......
......@@ -29,7 +29,7 @@ module.exports = function (pluginRoot, server, kbnServer, APP_ROOT, API_ROOT) {
path: `${API_ROOT}/v1/auth/authinfo`,
handler: (request, reply) => {
try {
let authinfo = server.plugins.searchguard.getSearchGuardBackend().authinfo(request.headers.authorization);
let authinfo = server.plugins.searchguard.getSearchGuardBackend().authinfo(request.headers);
return reply(authinfo);
} catch(error) {
return reply(Boom.badImplementation());
......
import _ from 'lodash';
export default function (originalHeaders, headersToKeep) {
const normalizeHeader = function (header) {
if (!header) {
return '';
}
header = header.toString();
return header.trim().toLowerCase();
};
const headersToKeepNormalized = headersToKeep.map(normalizeHeader);
const originalHeadersNormalized = _.mapKeys(originalHeaders, function (headerValue, headerName) {
return normalizeHeader(headerName);
});
return _.pick(originalHeaders, headersToKeepNormalized);
}
......@@ -15,6 +15,7 @@
*/
import _ from 'lodash';
import filterAuthHeaders from './filter_auth_headers';
import SearchGuardPlugin from './searchguard_plugin';
import AuthenticationError from '../auth/authentication_error';
import User from '../auth/user';
......@@ -25,11 +26,12 @@ import User from '../auth/user';
export default class SearchGuardBackend {
constructor(server) {
const config = Object.assign({ plugins: [SearchGuardPlugin], auth: false }, server.config().get('elasticsearch'));
const config = Object.assign({ plugins: [SearchGuardPlugin], auth: true }, server.config().get('elasticsearch'));
this._cluster = server.plugins.elasticsearch.createCluster('security',
config
);
this._client = this._cluster._client;
this._esconfig = server.config().get('elasticsearch');
}
async authenticate(credentials) {
......@@ -50,12 +52,11 @@ export default class SearchGuardBackend {
}
}
async authinfo(authHeader) {
async authinfo(headers) {
try {
const authHeaders = filterAuthHeaders(headers, this._esconfig.requestHeadersWhitelist);
const response = await this._client.searchguard.authinfo({
headers: {
authorization: authHeader
}
headers: authHeaders
});
return response
} catch(error) {
......@@ -67,12 +68,11 @@ export default class SearchGuardBackend {
}
}
async multitenancyinfo(authHeader) {
async multitenancyinfo(headers) {
try {
const authHeaders = filterAuthHeaders(headers, this._esconfig.requestHeadersWhitelist);
const response = await this._client.searchguard.multitenancyinfo({
headers: {
authorization: authHeader
}
headers: authHeaders
});
return response
} catch(error) {
......@@ -105,7 +105,7 @@ export default class SearchGuardBackend {
return prefs;
}
getTenantByPreference(request, username, tenants, globalEnabled, privateEnabled) {
getTenantByPreference(request, username, tenants, preferredTenants, globalEnabled, privateEnabled) {
// delete user from tenants first to check if we have a tenant to choose from at all
// keep original preferences untouched, we need the original values again
// http://stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object
......@@ -118,22 +118,33 @@ export default class SearchGuardBackend {
}
// get users preferred tenant
var prefs = request.state.searchguard_preferences;
if (prefs) {
var preferredTenant = prefs[username];
// user has a preferred tenant, check if it is accessible
if (preferredTenant && tenants[preferredTenant]) {
if (preferredTenant && tenants[preferredTenant] != undefined) {
return preferredTenant;
}
// special case: in tenants returned from SG, the private tenant is
// the username of the logged in user, but the header value is __user__
if (preferredTenant == "__user__" && tenants[username] && privateEnabled) {
if (preferredTenant == "__user__" && tenants[username] != undefined && privateEnabled) {
return "__user__";
}
}
// no preference, or tenant no accessible anymore, choose either global or private
// no preference in cookie, or tenant no accessible anymore, evaluate preferredTenants from kibana config
if (preferredTenants && !_.isEmpty(preferredTenants)) {
for (var i = 0; i < preferredTenants.length; i++) {
var check = preferredTenants[i];
if (tenants[check] != undefined) {
return check;
}
}
}
// no pref in cookie, no preferred tenant in kibana, use GLOBAL, Private or the first tenant in the list
if (globalEnabled) {
return "";
}
......@@ -141,9 +152,41 @@ export default class SearchGuardBackend {
if (privateEnabled) {
return "__user__";
}
// sort tenants by putting the keys in an array first
var tenantkeys = [];
var k;
for (k in tenants) {
tenantkeys.push(k);
}
tenantkeys.sort();
return tenantkeys[0];
}
validateTenant(username, requestedTenant, tenants, globalEnabled, privateEnabled) {
// delete user from tenants first to check if we have a tenant to choose from at all
// keep original preferences untouched, we need the original values again
// http://stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object
var tenantsCopy = JSON.parse(JSON.stringify(tenants));
delete tenantsCopy[username];
// sanity check
if (!globalEnabled && !privateEnabled && _.isEmpty(tenantsCopy)) {
return null;
}
if (tenants[requestedTenant] != undefined) {
return requestedTenant;
}
if (requestedTenant == "private" && tenants[username] && privateEnabled) {
return "__user__";
}
// this point can be reached if global and private are disabled,
// and the preferred tenant is not accessible anymore.
if (requestedTenant == "global" && globalEnabled) {
return "";
}
return null;
}
}
......@@ -28,13 +28,13 @@ export default function (Client, config, components) {
Client.prototype.searchguard.prototype.authinfo = ca({
url: {
fmt: '_searchguard/authinfo'
fmt: '/_searchguard/authinfo'
}
});
Client.prototype.searchguard.prototype.multitenancyinfo = ca({
url: {
fmt: '_searchguard/kibanainfo'
fmt: '/_searchguard/kibanainfo'
}
});
};
......
......@@ -16,6 +16,7 @@
import Boom from 'boom';
import {assign} from 'lodash';
const querystring = require('querystring')
export default function (pluginRoot, server, APP_ROOT, API_ROOT) {
const config = server.config();
......@@ -38,7 +39,7 @@ export default function (pluginRoot, server, APP_ROOT, API_ROOT) {
if (request.url.path.indexOf(API_ROOT) === 0 || request.method !== 'get') {
return reply(Boom.forbidden(error));
} else {
const nextUrl = encodeURIComponent(request.path);
const nextUrl = encodeURIComponent(request.url.path);
return reply.redirect(`${basePath}${APP_ROOT}/login?nextUrl=${nextUrl}`);
}
}
......
/**
* Copyright 2017 floragunn GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {assign} from 'lodash';
export default function (pluginRoot, server, APP_ROOT, API_ROOT) {
const config = server.config();
const basePath = config.get('server.basePath');
const backend = server.plugins.searchguard.getSearchGuardBackend();
const urlparamname = server.config().get('searchguard.jwt.url_param');
const headername = server.config().get('searchguard.jwt.header');
server.ext('onPostAuth', async function (request, next) {
var jwtBearer = request.state.searchguard_jwt;
var jwtAuthParam = request.query[urlparamname];
if(jwtAuthParam != null) {
jwtBearer = jwtAuthParam;
next.state('searchguard_jwt', jwtBearer);
}
if (jwtBearer != null) {
var headerValue = "Bearer " + jwtBearer;
var headers = {};
headers[headername] = headerValue;
assign(request.headers, headers);
}
return next.continue();
});
}
......@@ -20,12 +20,48 @@ export default function (pluginRoot, server, APP_ROOT, API_ROOT) {
const config = server.config();
const basePath = config.get('server.basePath');
const global_enabled = config.get("searchguard.multitenancy.tenants.enable_global");
const private_enabled = config.get("searchguard.multitenancy.tenants.enable_private");
const preferredTenants = config.get("searchguard.multitenancy.tenants.preferred");
const backend = server.plugins.searchguard.getSearchGuardBackend();
server.ext('onPostAuth', function (request, next) {
server.ext('onPostAuth', async function (request, next) {
// default is the tenant stored in the tenants cookie
var selectedTenant = request.state.searchguard_tenant;
// check for tenant in request
if (request.query && request.query.sg_tenant) {
let requestedTenant = request.query.sg_tenant;
let response = await backend.authinfo(request.headers);
selectedTenant = backend.validateTenant(response.user_name, requestedTenant, response.sg_tenants, global_enabled, private_enabled);
if(selectedTenant != null) {
// save validated tenant as preference
let prefcookie = backend.updateAndGetTenantPreferences(request, response.user_name, selectedTenant);
next.state('searchguard_tenant', selectedTenant)
next.state('searchguard_preferences', prefcookie);
}
}
// no tenant in request, check for tenant cookie. We need to check here again
// since for SSO like JWT the login functions are not called. So this means
// no searchguard_tenant and no query param
if(selectedTenant == null) {
let response = await backend.authinfo(request.headers);
selectedTenant = backend.getTenantByPreference(request, response.user_name, response.sg_tenants, preferredTenants, global_enabled, private_enabled);
if(selectedTenant != null) {
// save validated tenant as preference
let prefcookie = backend.updateAndGetTenantPreferences(request, response.user_name, selectedTenant);
next.state('searchguard_tenant', selectedTenant)
next.state('searchguard_preferences', prefcookie);
}
}
if (selectedTenant != null) {
assign(request.headers, {'sg_tenant' : selectedTenant});
}
return next.continue();
});
}
......@@ -48,7 +48,7 @@ module.exports = function (pluginRoot, server, kbnServer, APP_ROOT, API_ROOT) {
method: 'GET',
path: `${API_ROOT}/v1/multitenancy/info`,
handler: (request, reply) => {
let mtinfo = server.plugins.searchguard.getSearchGuardBackend().multitenancyinfo(request.headers.authorization);
let mtinfo = server.plugins.searchguard.getSearchGuardBackend().multitenancyinfo(request.headers);
return reply(mtinfo);
}
});
......
......@@ -19,9 +19,9 @@
<input type="password" class="form-control kuiTextInput" id="password" name="password" placeholder="Password"
ng-model="ui.credentials.password" required/>
</div>
<button type="submit" class="btn btn-default btn-login" style="{{ui.buttonstyle}}">Log in</button>
<button id="login" type="submit" class="btn btn-default btn-login" style="{{ui.buttonstyle}}">Log in</button>
</form>
<p class="error-message" ng-if="ui.errorMessage">{{ ui.errorMessage }}</p>
<p class="error-message" id="errorMessage" ng-if="ui.errorMessage">{{ ui.errorMessage }}</p>
</div>
......@@ -74,12 +74,12 @@
</div>
<div class="container">
<div class="container-fluid">
<div class="row">
<div class="col-xs-12">
<div class="page-header">
<h2 class="text-center" ng-bind="ctrl.tenantLabel"></h2>
<h5 class="error-message" ng-if="ctrl.errorMessage" ng-bind="ctrl.errorMessage"></h5>
<h2 class="text-center" ng-bind="ctrl.tenantLabel" id="tenantLabel"></h2>
<h5 class="error-message" ng-if="ctrl.errorMessage" ng-bind="ctrl.errorMessage" id="tenantErrorMessage"></h5>
</div>
</div>
</div>
......@@ -105,17 +105,17 @@
<div ng-if="!ctrl.GLOBAL_USER_WRITEABLE">read only</div>
</td>
<td class="actions kuiTableRowCell">
<button class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
<button id="show_dashboard_global" class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
ng-click="ctrl.selectTenant(ctrl.GLOBAL_USER_LABEL, ctrl.GLOBAL_USER_VALUE, 'dash')">Show Dashboards
</button>
</td>
<td class="actions kuiTableRowCell">
<button class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
<button id="show_visualization_global" class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
ng-click="ctrl.selectTenant(ctrl.GLOBAL_USER_LABEL, ctrl.GLOBAL_USER_VALUE, 'vis')">Show Visualizations
</button>
</td>
<td class="actions kuiTableRowCell">
<button class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == ''"
<button id="select_global" class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == ''"
ng-show="ctrl.currentTenant != ctrl.GLOBAL_USER_VALUE"
ng-click="ctrl.selectTenant(ctrl.GLOBAL_USER_LABEL, ctrl.GLOBAL_USER_VALUE)">Select
</button>
......@@ -129,17 +129,17 @@
<div>read/write</div>
</td>
<td class="actions kuiTableRowCell">
<button class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
<button id="show_dashboard_private" class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
ng-click="ctrl.selectTenant(ctrl.PRIVATE_USER_LABEL, ctrl.PRIVATE_USER_VALUE, 'dash')">Show Dashboards
</button>
</td>
<td class="actions kuiTableRowCell">
<button class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
<button id="show_visualization_private" class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
ng-click="ctrl.selectTenant(ctrl.PRIVATE_USER_LABEL, ctrl.PRIVATE_USER_VALUE, 'vis')">Show Visualizations
</button>
</td>
<td class="actions kuiTableRowCell">
<button class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == ctrl.PRIVATE_USER_VALUE"
<button id="select_private" class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == ctrl.PRIVATE_USER_VALUE"
ng-click="ctrl.selectTenant(ctrl.PRIVATE_USER_LABEL, ctrl.PRIVATE_USER_VALUE)">Select
</button>
</td>
......@@ -153,17 +153,17 @@
<div ng-if="!ctrl.tenants[tenantkey]">read only</div>
</td>
<td class="actions kuiTableRowCell">
<button class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
<button id="show_dashboard_{{tenantkey}}" class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
ng-click="ctrl.selectTenant(tenantkey, tenantkey, 'dash')">Show Dashboards
</button>
</td>
<td class="actions kuiTableRowCell">
<button class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
<button id="show_visualization_{{tenantkey}}" class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
ng-click="ctrl.selectTenant(tenantkey, tenantkey, 'vis')">Show Visualizations
</button>
</td>
<td class="actions kuiTableRowCell">
<button class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
<button id="select_{{tenantkey}}" class="kuiButton kuiButton--primary" ng-disabled="ctrl.currentTenant == tenantkey"
ng-click="ctrl.selectTenant(tenantkey, tenantkey)">Select
</button>
</td>
......
#!/bin/bash
PLUGIN_NAME=searchguard-kibana
PLUGIN_VERSION=5.3.1-3-SNAPSHOT
echo "Uploading ./releases/$PLUGIN_VERSION/$PLUGIN_NAME-$PLUGIN_VERSION.zip"
cresponse=$(curl --write-out %{http_code} --silent --output uploadresult -X POST -F fileUpload=@./releases/$PLUGIN_VERSION/$PLUGIN_NAME-$PLUGIN_VERSION.zip 'https://www.filestackapi.com/api/store/S3?key=$FILESTACK_KEY')
response="$(echo "$cresponse" | cut -c1-3)"
if ! [[ $response == "200" ]] ; then
echo "Upload failed with status $response"
exit 1
fi
echo "Upload response: $response"
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment